diff --git a/.gitignore b/.gitignore index acffc298..dbac1925 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,9 @@ apps/desktop/src-tauri/target/release/ .superpowers/apps/desktop/node_modules /apps/desktop/src-tauri/target /apps/desktop/node_modules + +/apps/desktop-ui/node_modules +/apps/desktop-ui/.next + +/apps/web/node_modules +/apps/web/.next diff --git a/apps/api-runner/README.md b/apps/api-runner/README.md deleted file mode 100644 index 63e814c8..00000000 --- a/apps/api-runner/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# @mydevtools/api-runner - -Headless runner for [mydevtools](https://mydevtools.tech) API client collections — Postman / Newman compatible. - -Same scripts, same `pm.*` API, same JUnit output as the web runner. - -## Install - -```bash -npm i -g @mydevtools/api-runner -``` - -## Run - -```bash -mydevtools-api run my-collection.json -mydevtools-api run my-collection.json --env prod.env.json --data users.csv -mydevtools-api run my-collection.json --reporter junit --reporter-out report.xml --bail -``` - -## Flags - -| Flag | Description | -|---|---| -| `--data ` | CSV or JSON-array data file. One iteration per row; row keys override `{{vars}}`. | -| `--env ` | Postman Environment v2 export. Enabled values become the base `{{var}}` map. | -| `--iterations ` | Iterations when no data file is given. Default 1. | -| `--reporter ` | `cli` (default) or `junit`. | -| `--reporter-out ` | Required for `--reporter junit`. | -| `--bail` | Stop on the first failing assertion / network error. | - -## Exit codes - -| Code | Meaning | -|---|---| -| 0 | All tests passed | -| 1 | Some tests / requests failed | -| 2 | Usage error | - -## Differences vs the web runner (v0.1) - -- No cookie jar (Node `fetch` has no jar) — provide auth via env vars. -- No OAuth refresh flow — pre-mint a bearer token and put it in env. -- No SSE / streaming (the web has `/api/proxy-stream`; the CLI does plain `await response.text()`). -- No GraphQL introspection. - -Everything else — scripts, env mutations, `{{response.body.x}}` chaining, -folder inheritance, data-driven iterations, JUnit XML — matches the web runner -bit-for-bit. diff --git a/apps/api-runner/dist/csv.d.ts b/apps/api-runner/dist/csv.d.ts deleted file mode 100644 index a413adfa..00000000 --- a/apps/api-runner/dist/csv.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -/** Same CSV/JSON data-file parser as the web app — verbatim port for portability. */ -export declare function parseCsv(text: string): Record[]; -export declare function parseDataFile(text: string): Record[]; diff --git a/apps/api-runner/dist/csv.js b/apps/api-runner/dist/csv.js deleted file mode 100644 index bc4b9ebb..00000000 --- a/apps/api-runner/dist/csv.js +++ /dev/null @@ -1,77 +0,0 @@ -/** Same CSV/JSON data-file parser as the web app — verbatim port for portability. */ -export function parseCsv(text) { - const rows = parseRows(text); - if (rows.length === 0) - return []; - const [headers, ...body] = rows; - return body - .filter((r) => r.length > 1 || (r.length === 1 && r[0] !== "")) - .map((cells) => { - const obj = {}; - headers.forEach((h, i) => { obj[h] = cells[i] ?? ""; }); - return obj; - }); -} -function parseRows(text) { - const rows = []; - let row = []; - let cell = ""; - let inQuote = false; - for (let i = 0; i < text.length; i++) { - const c = text[i]; - if (inQuote) { - if (c === '"' && text[i + 1] === '"') { - cell += '"'; - i++; - } - else if (c === '"') { - inQuote = false; - } - else - cell += c; - } - else { - if (c === '"') { - inQuote = true; - } - else if (c === ",") { - row.push(cell); - cell = ""; - } - else if (c === "\n" || c === "\r") { - row.push(cell); - cell = ""; - rows.push(row); - row = []; - if (c === "\r" && text[i + 1] === "\n") - i++; - } - else - cell += c; - } - } - if (cell.length > 0 || row.length > 0) { - row.push(cell); - rows.push(row); - } - return rows; -} -export function parseDataFile(text) { - const t = text.trimStart(); - if (t.startsWith("[")) { - const parsed = JSON.parse(t); - if (!Array.isArray(parsed)) - throw new Error("JSON data file must be an array"); - return parsed.map((row) => { - if (!row || typeof row !== "object") - return {}; - const out = {}; - for (const [k, v] of Object.entries(row)) { - out[k] = v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v); - } - return out; - }); - } - return parseCsv(text); -} -//# sourceMappingURL=csv.js.map \ No newline at end of file diff --git a/apps/api-runner/dist/csv.js.map b/apps/api-runner/dist/csv.js.map deleted file mode 100644 index 46c4cd3c..00000000 --- a/apps/api-runner/dist/csv.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"csv.js","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF,MAAM,UAAU,QAAQ,CAAC,IAAY;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAChC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;IAC/B,OAAO,IAAI;SACN,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;SAC9D,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACX,MAAM,GAAG,GAA2B,EAAE,CAAA;QACtC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;QACtD,OAAO,GAAG,CAAA;IACd,CAAC,CAAC,CAAA;AACV,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC3B,MAAM,IAAI,GAAe,EAAE,CAAA;IAC3B,IAAI,GAAG,GAAa,EAAE,CAAA;IACtB,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,IAAI,OAAO,GAAG,KAAK,CAAA;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,IAAI,OAAO,EAAE,CAAC;YACV,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAAC,IAAI,IAAI,GAAG,CAAC;gBAAC,CAAC,EAAE,CAAA;YAAC,CAAC;iBACrD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBAAC,OAAO,GAAG,KAAK,CAAA;YAAC,CAAC;;gBAClC,IAAI,IAAI,CAAC,CAAA;QAClB,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBAAC,OAAO,GAAG,IAAI,CAAA;YAAC,CAAC;iBAC5B,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAAC,IAAI,GAAG,EAAE,CAAA;YAAC,CAAC;iBAC5C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gBAChC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAAC,IAAI,GAAG,EAAE,CAAA;gBACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAAC,GAAG,GAAG,EAAE,CAAA;gBACxB,IAAI,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI;oBAAE,CAAC,EAAE,CAAA;YAC/C,CAAC;;gBAAM,IAAI,IAAI,CAAC,CAAA;QACpB,CAAC;IACL,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAClB,CAAC;IACD,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACtC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,CAAA;IAC1B,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;QAC9E,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,GAAY,EAAE,EAAE;YAC/B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,OAAO,EAAE,CAAA;YAC9C,MAAM,GAAG,GAA2B,EAAE,CAAA;YACtC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,EAAE,CAAC;gBAClE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;YACnF,CAAC;YACD,OAAO,GAAG,CAAA;QACd,CAAC,CAAC,CAAA;IACN,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC"} \ No newline at end of file diff --git a/apps/api-runner/dist/index.d.ts b/apps/api-runner/dist/index.d.ts deleted file mode 100644 index 7dba6914..00000000 --- a/apps/api-runner/dist/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node -/** - * mydevtools-api CLI. - * Headless equivalent of the web-side collection runner. Same scripts, same - * substitution layering, same JUnit output — runs over a Postman v2.1 export. - */ -export {}; diff --git a/apps/api-runner/dist/index.js b/apps/api-runner/dist/index.js deleted file mode 100644 index 7eea8817..00000000 --- a/apps/api-runner/dist/index.js +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env node -/** - * mydevtools-api CLI. - * Headless equivalent of the web-side collection runner. Same scripts, same - * substitution layering, same JUnit output — runs over a Postman v2.1 export. - */ -import { readFile, writeFile } from "node:fs/promises"; -import { importPostmanCollection, parsePostmanEnvironment } from "./postman.js"; -import { parseDataFile } from "./csv.js"; -import { runCollection } from "./runner.js"; -import { buildJUnitXml } from "./junit.js"; -function parseArgs(argv) { - const out = { iterations: 1, bail: false, help: false }; - if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") { - out.help = true; - return out; - } - out.cmd = argv[0]; - let i = 1; - while (i < argv.length) { - const a = argv[i]; - if (a === "--data" || a === "-d") { - out.dataPath = argv[++i]; - } - else if (a === "--env" || a === "-e") { - out.envPath = argv[++i]; - } - else if (a === "--iterations" || a === "-n") { - out.iterations = Math.max(1, Number(argv[++i]) || 1); - } - else if (a === "--reporter" || a === "-r") { - out.reporter = argv[++i]; - } - else if (a === "--reporter-out" || a === "-o") { - out.reporterOut = argv[++i]; - } - else if (a === "--bail") { - out.bail = true; - } - else if (a === "-h" || a === "--help") { - out.help = true; - } - else if (!out.collectionPath) { - out.collectionPath = a; - } - else - throw new Error(`Unknown argument: ${a}`); - i++; - } - return out; -} -function printUsage() { - console.error(`Usage: mydevtools-api run [options] - -Options: - -d, --data CSV or JSON array of iteration rows (each row's keys - override env vars for one iteration). - -e, --env Postman Environment export (v2 schema) — enabled - values become the base \`{{var}}\` map. - -n, --iterations Iterations to run when no data file is given. Default 1. - -r, --reporter 'cli' (default) or 'junit'. - -o, --reporter-out Write the JUnit XML here. Required for --reporter junit. - --bail Stop on the first failing request. - -h, --help Show this message. - -Exit codes: - 0 — all tests passed - 1 — one or more tests / requests failed - 2 — usage error -`); -} -function fmtRow(r) { - const ok = !r.networkError && r.tests.every((t) => t.pass); - const status = r.status !== undefined ? r.status : "—"; - const time = r.time !== undefined ? `${r.time}ms` : ""; - return ` ${ok ? "✓" : "✗"} ${r.method.padEnd(6)} ${status} ${time} ${r.requestName}`; -} -async function main() { - const args = parseArgs(process.argv.slice(2)); - if (args.help || !args.cmd) { - printUsage(); - process.exit(args.help ? 0 : 2); - } - if (args.cmd !== "run" || !args.collectionPath) { - printUsage(); - process.exit(2); - } - if (args.reporter === "junit" && !args.reporterOut) { - console.error("--reporter-out is required when --reporter junit is used"); - process.exit(2); - } - const collection = importPostmanCollection(await readFile(args.collectionPath, "utf-8")); - const dataRows = args.dataPath ? parseDataFile(await readFile(args.dataPath, "utf-8")) : undefined; - const env = args.envPath ? parsePostmanEnvironment(await readFile(args.envPath, "utf-8")) : {}; - console.log(`Running ${collection.name}` + (dataRows ? ` × ${dataRows.length} iterations` : args.iterations > 1 ? ` × ${args.iterations} iterations` : "")); - const results = await runCollection({ - collection, - environmentVariables: env, - iterations: args.iterations, - dataRows, - bail: args.bail, - onProgress: (r) => console.log(fmtRow(r)), - }); - const allTests = results.flatMap((r) => r.tests); - const failed = allTests.filter((t) => !t.pass).length; - const errored = results.filter((r) => r.networkError || r.errors.length > 0).length; - const passed = allTests.length - failed; - console.log(""); - console.log(`Requests: ${results.length}`); - console.log(`Assertions: ${allTests.length} (✓ ${passed} / ✗ ${failed})`); - if (errored > 0) - console.log(`Errors: ${errored}`); - for (const r of results) { - const failures = r.tests.filter((t) => !t.pass); - if (failures.length === 0 && !r.networkError) - continue; - console.log(`\n ${r.method} ${r.requestName}`); - if (r.networkError) - console.log(` ! ${r.networkError}`); - for (const t of failures) - console.log(` ✗ ${t.name}\n ${t.error}`); - } - if (args.reporter === "junit" && args.reporterOut) { - await writeFile(args.reporterOut, buildJUnitXml(results, collection.name)); - console.log(`\nWrote JUnit XML to ${args.reporterOut}`); - } - process.exit(failed > 0 || errored > 0 ? 1 : 0); -} -main().catch((err) => { - console.error(`Fatal: ${err.message}`); - process.exit(1); -}); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/apps/api-runner/dist/index.js.map b/apps/api-runner/dist/index.js.map deleted file mode 100644 index 0e4e0e3d..00000000 --- a/apps/api-runner/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAe1C,SAAS,SAAS,CAAC,IAAc;IAC7B,MAAM,GAAG,GAAY,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;IAChE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;QACf,OAAO,GAAG,CAAA;IACd,CAAC;IACD,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAU,CAAA;IAC1B,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;QAAC,CAAC;aACzD,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAAC,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;QAAC,CAAC;aAC5D,IAAI,CAAC,KAAK,cAAc,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAAC,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QAAC,CAAC;aAChG,IAAI,CAAC,KAAK,YAAY,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,CAAwB,CAAA;QAAC,CAAC;aACzF,IAAI,CAAC,KAAK,gBAAgB,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAAC,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;QAAC,CAAC;aACzE,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;YAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;QAAC,CAAC;aACvC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;YAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;QAAC,CAAC;aACrD,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;YAAC,GAAG,CAAC,cAAc,GAAG,CAAC,CAAA;QAAC,CAAC;;YACnD,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAA;QAC9C,CAAC,EAAE,CAAA;IACP,CAAC;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAED,SAAS,UAAU;IACf,OAAO,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;CAiBjB,CAAC,CAAA;AACF,CAAC;AAED,SAAS,MAAM,CAAC,CAAmB;IAC/B,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAC1D,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAA;IACtD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;IACtD,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAA;AAC1F,CAAC;AAED,KAAK,UAAU,IAAI;IACf,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7C,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAAC,UAAU,EAAE,CAAC;QAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAAC,CAAC;IAC7E,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;QAAC,UAAU,EAAE,CAAC;QAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAAC,CAAC;IACjF,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAA;QACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACnB,CAAC;IAED,MAAM,UAAU,GAAG,uBAAuB,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAA;IACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAClG,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAE9F,OAAO,CAAC,GAAG,CAAC,WAAW,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAE3J,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC;QAChC,UAAU;QACV,oBAAoB,EAAE,GAAG;QACzB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,QAAQ;QACR,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;KAC5C,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAA;IACrD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAA;IACnF,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAA;IAEvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACf,OAAO,CAAC,GAAG,CAAC,gBAAgB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAC7C,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,MAAM,QAAQ,MAAM,QAAQ,MAAM,GAAG,CAAC,CAAA;IAC3E,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,OAAO,EAAE,CAAC,CAAA;IAEvD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAC/C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY;YAAE,SAAQ;QACtD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;QAC/C,IAAI,CAAC,CAAC,YAAY;YAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,YAAY,EAAE,CAAC,CAAA;QAC1D,KAAK,MAAM,CAAC,IAAI,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;IAChF,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QAChD,MAAM,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;QAC1E,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;IAC3D,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACnD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,UAAW,GAAa,CAAC,OAAO,EAAE,CAAC,CAAA;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACnB,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/apps/api-runner/dist/junit.d.ts b/apps/api-runner/dist/junit.d.ts deleted file mode 100644 index 3b7dcdb1..00000000 --- a/apps/api-runner/dist/junit.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -/** JUnit XML emitter — port of `apps/web/src/lib/runner/junit.ts`. */ -import type { RequestRunResult } from "./types.js"; -export declare function buildJUnitXml(results: RequestRunResult[], suiteName: string): string; diff --git a/apps/api-runner/dist/junit.js b/apps/api-runner/dist/junit.js deleted file mode 100644 index db15b8ec..00000000 --- a/apps/api-runner/dist/junit.js +++ /dev/null @@ -1,46 +0,0 @@ -/** JUnit XML emitter — port of `apps/web/src/lib/runner/junit.ts`. */ -function escapeXml(s) { - return String(s) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} -export function buildJUnitXml(results, suiteName) { - const allTests = results.flatMap((r) => r.tests.map((t) => ({ run: r, test: t }))); - const totalTests = allTests.length; - const failed = allTests.filter(({ test }) => !test.pass).length; - const errored = results.filter((r) => r.networkError || r.errors.length > 0).length; - const totalTime = results.reduce((s, r) => s + (r.time ?? 0), 0) / 1000; - const cases = allTests.map(({ run, test }) => { - const classname = escapeXml(run.requestName); - const name = escapeXml(test.name); - const time = ((run.time ?? 0) / 1000).toFixed(3); - if (test.pass) { - return ` `; - } - const msg = escapeXml(test.error ?? "assertion failed"); - return ` - ${msg} - `; - }); - const errorCases = results - .filter((r) => r.networkError || r.errors.length > 0) - .map((r) => { - const msg = escapeXml(r.networkError ?? r.errors.join("; ")); - const classname = escapeXml(r.requestName); - const time = ((r.time ?? 0) / 1000).toFixed(3); - return ` - ${msg} - `; - }); - const body = [...cases, ...errorCases].join("\n"); - return ` - - -${body} - -`; -} -//# sourceMappingURL=junit.js.map \ No newline at end of file diff --git a/apps/api-runner/dist/junit.js.map b/apps/api-runner/dist/junit.js.map deleted file mode 100644 index 9eaf2288..00000000 --- a/apps/api-runner/dist/junit.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"junit.js","sourceRoot":"","sources":["../src/junit.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAItE,SAAS,SAAS,CAAC,CAAS;IACxB,OAAO,MAAM,CAAC,CAAC,CAAC;SACX,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AAChC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAA2B,EAAE,SAAiB;IACxE,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAClF,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAA;IAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAA;IAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAA;IACnF,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;IAEvE,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE;QACzC,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC5C,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;QAChD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,OAAO,4BAA4B,SAAS,WAAW,IAAI,WAAW,IAAI,MAAM,CAAA;QACpF,CAAC;QACD,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,kBAAkB,CAAC,CAAA;QACvD,OAAO,4BAA4B,SAAS,WAAW,IAAI,WAAW,IAAI;0BACxD,GAAG,2BAA2B,GAAG;gBAC3C,CAAA;IACZ,CAAC,CAAC,CAAA;IAEF,MAAM,UAAU,GAAG,OAAO;SACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACP,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QAC5D,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;QAC1C,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;QAC9C,OAAO,4BAA4B,SAAS,oCAAoC,IAAI;wBACxE,GAAG,yBAAyB,GAAG;gBACvC,CAAA;IACR,CAAC,CAAC,CAAA;IAEN,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,EAAE,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjD,OAAO;;qBAEU,SAAS,CAAC,SAAS,CAAC,YAAY,UAAU,GAAG,OAAO,eAAe,MAAM,aAAa,OAAO,WAAW,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;EAC/I,IAAI;;cAEQ,CAAA;AACd,CAAC"} \ No newline at end of file diff --git a/apps/api-runner/dist/postman.d.ts b/apps/api-runner/dist/postman.d.ts deleted file mode 100644 index c416a408..00000000 --- a/apps/api-runner/dist/postman.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Lean Postman v2.1 → Collection converter for the CLI. - * Mirrors `apps/web/src/lib/import/postman.ts` but bound to the smaller - * CLI type surface (no OAuth2, no graphql body parsing — those map to text). - */ -import type { Collection } from "./types.js"; -export declare function importPostmanCollection(raw: string | object): Collection; -/** - * Parse a Postman Environment export (v2 schema) into a flat `key → value` map. - * Disabled vars are skipped. - */ -export declare function parsePostmanEnvironment(raw: string | object): Record; diff --git a/apps/api-runner/dist/postman.js b/apps/api-runner/dist/postman.js deleted file mode 100644 index e554ad39..00000000 --- a/apps/api-runner/dist/postman.js +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Lean Postman v2.1 → Collection converter for the CLI. - * Mirrors `apps/web/src/lib/import/postman.ts` but bound to the smaller - * CLI type surface (no OAuth2, no graphql body parsing — those map to text). - */ -import { randomUUID } from "node:crypto"; -const VALID_METHODS = new Set([ - "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", -]); -const id = () => randomUUID(); -function pickScript(events, kind) { - const evt = events?.find((e) => e.listen === kind); - if (!evt?.script?.exec) - return ""; - return Array.isArray(evt.script.exec) ? evt.script.exec.join("\n") : evt.script.exec; -} -function kvList(items) { - return (items ?? []) - .filter((kv) => kv && (kv.key || kv.value)) - .map((kv) => ({ - id: id(), - key: kv.key ?? "", - value: kv.value ?? "", - active: !kv.disabled, - })); -} -function readUrl(u) { - if (!u) - return { url: "", query: [] }; - if (typeof u === "string") - return { url: u, query: [] }; - return { url: u.raw ?? "", query: u.query ?? [] }; -} -function lookup(items, key) { - return items?.find((x) => x.key === key)?.value; -} -function convertAuth(a) { - if (!a || a.type === "noauth" || !a.type) - return { type: "none" }; - if (a.type === "bearer") - return { type: "bearer", token: lookup(a.bearer, "token") }; - if (a.type === "basic") { - return { - type: "basic", - username: lookup(a.basic, "username"), - password: lookup(a.basic, "password"), - }; - } - if (a.type === "apikey") { - return { - type: "api-key", - apiKeyKey: lookup(a.apikey, "key"), - apiKeyValue: lookup(a.apikey, "value"), - apiKeyLocation: lookup(a.apikey, "in") === "query" ? "query" : "header", - }; - } - return { type: "none" }; -} -function convertBody(b) { - if (!b || b.mode === "none") - return { type: "none", content: "" }; - if (b.mode === "raw") { - const lang = b.options?.raw?.language; - return { type: lang === "json" ? "json" : "text", content: b.raw ?? "" }; - } - if (b.mode === "urlencoded") { - return { - type: "x-www-form-urlencoded", - content: "", - urlEncoded: kvList(b.urlencoded), - }; - } - if (b.mode === "formdata") { - return { - type: "form-data", - content: "", - formData: (b.formdata ?? []) - .filter((kv) => kv && (kv.key || kv.value)) - .map((kv) => ({ - id: id(), - key: kv.key ?? "", - value: kv.value ?? "", - active: !kv.disabled, - valueType: kv.type === "file" ? "file" : "text", - })), - }; - } - return { type: "text", content: b.raw ?? "" }; -} -function convertRequest(item) { - const r = item.request ?? {}; - const { url, query } = readUrl(r.url); - const method = (r.method ?? "GET").toUpperCase(); - const safeMethod = VALID_METHODS.has(method) ? method : "GET"; - const preRequestScript = pickScript(item.event, "prerequest"); - const testScript = pickScript(item.event, "test"); - return { - id: id(), - name: item.name ?? safeMethod, - method: safeMethod, - url, - params: kvList(query), - headers: kvList(r.header), - body: convertBody(r.body), - auth: convertAuth(r.auth), - preRequestScript: preRequestScript || undefined, - testScript: testScript || undefined, - }; -} -function convertItems(items) { - const out = []; - for (const item of items ?? []) { - if (item.request) - out.push(convertRequest(item)); - else if (item.item) { - out.push({ - id: id(), - name: item.name ?? "Folder", - type: "folder", - items: convertItems(item.item), - }); - } - } - return out; -} -export function importPostmanCollection(raw) { - const data = typeof raw === "string" ? JSON.parse(raw) : raw; - if (!data || (!data.info?.name && !Array.isArray(data.item))) { - throw new Error("Not a Postman collection (missing info.name + item[])"); - } - return { - id: id(), - name: data.info?.name ?? "Imported Postman collection", - items: convertItems(data.item), - }; -} -/** - * Parse a Postman Environment export (v2 schema) into a flat `key → value` map. - * Disabled vars are skipped. - */ -export function parsePostmanEnvironment(raw) { - const data = typeof raw === "string" ? JSON.parse(raw) : raw; - const vars = data?.values; - if (!Array.isArray(vars)) - return {}; - const out = {}; - for (const v of vars) { - if (!v?.key) - continue; - if (v.enabled === false) - continue; - out[v.key] = String(v.value ?? ""); - } - return out; -} -//# sourceMappingURL=postman.js.map \ No newline at end of file diff --git a/apps/api-runner/dist/postman.js.map b/apps/api-runner/dist/postman.js.map deleted file mode 100644 index 66c611f7..00000000 --- a/apps/api-runner/dist/postman.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"postman.js","sourceRoot":"","sources":["../src/postman.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAWxC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAgB;IACzC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS;CAC7D,CAAC,CAAA;AAkCF,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC,UAAU,EAAE,CAAA;AAE7B,SAAS,UAAU,CAAC,MAA4B,EAAE,IAA2B;IACzE,MAAM,GAAG,GAAG,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAA;IAClD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI;QAAE,OAAO,EAAE,CAAA;IACjC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAA;AACxF,CAAC;AAED,SAAS,MAAM,CAAC,KAAmB;IAC/B,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACf,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;SAC1C,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACV,EAAE,EAAE,EAAE,EAAE;QACR,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE;QACjB,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE;QACrB,MAAM,EAAE,CAAC,EAAE,CAAC,QAAQ;KACvB,CAAC,CAAC,CAAA;AACX,CAAC;AAED,SAAS,OAAO,CAAC,CAAwB;IACrC,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IACrC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IACvD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,CAAA;AACrD,CAAC;AAED,SAAS,MAAM,CAAC,KAA8B,EAAE,GAAW;IACvD,OAAO,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,KAAK,CAAA;AACnD,CAAC;AAED,SAAS,WAAW,CAAC,CAAyB;IAC1C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACjE,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IACpF,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,OAAO;YACH,IAAI,EAAE,OAAO;YACb,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC;YACrC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC;SACxC,CAAA;IACL,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACH,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC;YAClC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC;YACtC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;SAC1E,CAAA;IACL,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AAC3B,CAAC;AAED,SAAS,WAAW,CAAC,CAAyB;IAC1C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IACjE,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAA;QACrC,OAAO,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,CAAA;IAC5E,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,OAAO;YACH,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;SACnC,CAAA;IACL,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,OAAO;YACH,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;iBACvB,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;iBAC1C,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACV,EAAE,EAAE,EAAE,EAAE;gBACR,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE;gBACjB,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE;gBACrB,MAAM,EAAE,CAAC,EAAE,CAAC,QAAQ;gBACpB,SAAS,EAAE,EAAE,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;aAClD,CAAC,CAAC;SACV,CAAA;IACL,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,CAAA;AACjD,CAAC;AAED,SAAS,cAAc,CAAC,IAAiB;IACrC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAA;IAC5B,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAmB,CAAA;IACjE,MAAM,UAAU,GAAkB,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAA;IAC5E,MAAM,gBAAgB,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;IAC7D,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACjD,OAAO;QACH,EAAE,EAAE,EAAE,EAAE;QACR,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,UAAU;QAC7B,MAAM,EAAE,UAAU;QAClB,GAAG;QACH,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QACzB,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;QACzB,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;QACzB,gBAAgB,EAAE,gBAAgB,IAAI,SAAS;QAC/C,UAAU,EAAE,UAAU,IAAI,SAAS;KACtC,CAAA;AACL,CAAC;AAED,SAAS,YAAY,CAAC,KAAqB;IACvC,MAAM,GAAG,GAA6C,EAAE,CAAA;IACxD,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,OAAO;YAAE,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAA;aAC3C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACjB,GAAG,CAAC,IAAI,CAAC;gBACL,EAAE,EAAE,EAAE,EAAE;gBACR,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,QAAQ;gBAC3B,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;aACjC,CAAC,CAAA;QACN,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,GAAoB;IACxD,MAAM,IAAI,GAAsB,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,GAAyB,CAAA;IACtG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAA;IAC5E,CAAC;IACD,OAAO;QACH,EAAE,EAAE,EAAE,EAAE;QACR,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,6BAA6B;QACtD,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;KACjC,CAAA;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAoB;IACxD,MAAM,IAAI,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAC5D,MAAM,IAAI,GAAI,IAAgF,EAAE,MAAM,CAAA;IACtG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAA;IACnC,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACnB,IAAI,CAAC,CAAC,EAAE,GAAG;YAAE,SAAQ;QACrB,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK;YAAE,SAAQ;QACjC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IACtC,CAAC;IACD,OAAO,GAAG,CAAA;AACd,CAAC"} \ No newline at end of file diff --git a/apps/api-runner/dist/runner.d.ts b/apps/api-runner/dist/runner.d.ts deleted file mode 100644 index 30d060cc..00000000 --- a/apps/api-runner/dist/runner.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Headless collection runner. - * - * Mirrors `apps/web/src/lib/runner/runner.ts` semantics: - * - Sequential execution; one iteration per data row, else `iterations`. - * - Pre-request + test scripts run via `node:vm` with `pm.*` API. - * - Env mutations cascade across requests within a run. - * - `{{response.body.x}}` chaining off the previous successful response. - * - Folder-level defaults (headers / scripts) merged before per-request fields. - * - * Deliberate omissions for the CLI v1: - * - No cookie jar (Node native fetch has none; add tough-cookie if needed). - * - No OAuth refresh flow (user supplies bearer token via env). - * - No streaming (proxy-stream lives on the web side). - */ -import type { Collection, RequestRunResult } from "./types.js"; -interface RunOpts { - collection: Collection; - folderId?: string; - iterations: number; - dataRows?: Record[]; - environmentVariables: Record; - bail?: boolean; - onProgress?: (r: RequestRunResult) => void; - abortSignal?: AbortSignal; -} -export declare function runCollection(opts: RunOpts): Promise; -export {}; diff --git a/apps/api-runner/dist/runner.js b/apps/api-runner/dist/runner.js deleted file mode 100644 index 0d1b1e6a..00000000 --- a/apps/api-runner/dist/runner.js +++ /dev/null @@ -1,403 +0,0 @@ -/** - * Headless collection runner. - * - * Mirrors `apps/web/src/lib/runner/runner.ts` semantics: - * - Sequential execution; one iteration per data row, else `iterations`. - * - Pre-request + test scripts run via `node:vm` with `pm.*` API. - * - Env mutations cascade across requests within a run. - * - `{{response.body.x}}` chaining off the previous successful response. - * - Folder-level defaults (headers / scripts) merged before per-request fields. - * - * Deliberate omissions for the CLI v1: - * - No cookie jar (Node native fetch has none; add tough-cookie if needed). - * - No OAuth refresh flow (user supplies bearer token via env). - * - No streaming (proxy-stream lives on the web side). - */ -import { runScript } from "./scripts.js"; -function walkRequests(items, out) { - for (const it of items) { - if ("type" in it && it.type === "folder") - walkRequests(it.items, out); - else - out.push(it); - } -} -function findRequestAncestors(items, requestId, trail = []) { - for (const item of items) { - if (item.id === requestId) - return trail; - if ("type" in item && item.type === "folder") { - const hit = findRequestAncestors(item.items, requestId, [...trail, item]); - if (hit) - return hit; - } - } - return null; -} -function mergeHeaders(chain, requestHeaders) { - const seen = new Set(); - const out = []; - for (const h of requestHeaders) { - if (h.key) - seen.add(h.key.toLowerCase()); - out.push(h); - } - for (const folder of chain) { - for (const h of folder.defaultHeaders ?? []) { - if (h.key && seen.has(h.key.toLowerCase())) - continue; - if (h.key) - seen.add(h.key.toLowerCase()); - out.push(h); - } - } - return out; -} -function inherit(req, ancestors) { - if (ancestors.length === 0) - return req; - const preChain = [...ancestors.map((f) => f.preRequestScript), req.preRequestScript] - .map((s) => (s ?? "").trim()).filter(Boolean).join("\n\n"); - const testChain = [...ancestors.map((f) => f.testScript), req.testScript] - .map((s) => (s ?? "").trim()).filter(Boolean).join("\n\n"); - return { - ...req, - headers: mergeHeaders(ancestors, req.headers), - preRequestScript: preChain || undefined, - testScript: testChain || undefined, - }; -} -function tryParseJson(s) { - try { - return JSON.parse(s); - } - catch { - return undefined; - } -} -function parseResponsePath(s) { - const out = []; - for (const part of s.split(".")) { - const m = part.match(/^([^[]*)((?:\[\d+\])*)$/); - if (!m) { - out.push(part); - continue; - } - if (m[1]) - out.push(m[1]); - for (const idx of m[2].matchAll(/\[(\d+)\]/g)) - out.push(Number(idx[1])); - } - return out; -} -function resolveResponsePath(path, response) { - if (!response) - return undefined; - const parts = parseResponsePath(path); - if (parts.length === 0) - return undefined; - const head = parts[0]; - if (head === "status") - return String(response.status); - if (head === "statusText") - return response.statusText; - if (head === "headers") { - if (parts.length === 1) - return undefined; - const name = String(parts[1]).toLowerCase(); - const hit = Object.entries(response.headers).find(([k]) => k.toLowerCase() === name); - return hit?.[1]; - } - if (head === "body") { - if (parts.length === 1) - return response.body; - const parsed = tryParseJson(response.body); - if (parsed === undefined) - return undefined; - let cur = parsed; - for (const k of parts.slice(1)) { - if (cur == null || typeof cur !== "object") - return undefined; - cur = cur[k]; - } - if (cur == null) - return undefined; - return typeof cur === "object" ? JSON.stringify(cur) : String(cur); - } - return undefined; -} -function makeSubstitute(args) { - return (text) => { - if (!text) - return text; - return text.replace(/\{\{(.+?)\}\}/g, (m, k) => { - const key = k.trim(); - if (key.startsWith("response.")) { - const resolved = resolveResponsePath(key.slice("response.".length), args.previousResponse); - return resolved ?? m; - } - if (key in args.row) - return args.row[key]; - if (args.envUnsets.has(key)) - return m; - if (key in args.envOverlay) - return args.envOverlay[key]; - if (key in args.sessionVars) - return args.sessionVars[key]; - return args.env[key] ?? m; - }); - }; -} -function utf8Btoa(s) { - return Buffer.from(s, "utf-8").toString("base64"); -} -async function executeRequest(args) { - const { req, iteration } = args; - const result = { - iteration, - requestId: req.id, - requestName: req.name, - method: req.method, - url: req.url, - tests: [], - logs: [], - errors: [], - }; - const substitute = makeSubstitute({ - env: args.env, - sessionVars: args.sessionVars, - envOverlay: args.envOverlay, - envUnsets: args.envUnsets, - row: args.row, - previousResponse: args.previousResponse, - }); - let workMethod = req.method; - let workUrl = req.url; - let workHeaders = {}; - req.headers.forEach((h) => { if (h.active && h.key) - workHeaders[h.key] = h.value; }); - // Pre-request script. - if (req.preRequestScript && req.preRequestScript.trim()) { - const r = runScript(req.preRequestScript, { - request: { - url: workUrl, method: workMethod, headers: workHeaders, - body: req.body.type === "json" || req.body.type === "text" || req.body.type === "graphql" ? req.body.content : undefined, - }, - environment: { ...args.env, ...args.envOverlay }, - variables: { ...args.sessionVars, ...args.row }, - }); - result.tests.push(...r.tests); - result.logs.push(...r.logs); - if (!r.ok && r.error) - result.errors.push(`pre-request: ${r.error}`); - for (const k of Object.keys(r.environment)) { - if (r.environment[k] !== args.env[k]) - args.envOverlay[k] = r.environment[k]; - } - for (const k of Object.keys(args.env)) { - if (!(k in r.environment)) - args.envUnsets.add(k); - } - Object.assign(args.sessionVars, r.variables); - workUrl = r.request.url; - workMethod = (r.request.method || workMethod).toUpperCase(); - workHeaders = r.request.headers; - } - let urlObj; - try { - urlObj = new URL(substitute(workUrl)); - } - catch (e) { - result.networkError = `Invalid URL: ${e.message}`; - return { result, response: null }; - } - req.params.forEach((p) => { - if (p.active && p.key) - urlObj.searchParams.append(substitute(p.key), substitute(p.value)); - }); - const headersObj = {}; - for (const [k, v] of Object.entries(workHeaders)) { - headersObj[substitute(k)] = substitute(v); - } - if (req.auth.type === "bearer" && req.auth.token) { - headersObj["Authorization"] = `Bearer ${substitute(req.auth.token).trim()}`; - } - else if (req.auth.type === "basic" && req.auth.username && req.auth.password) { - headersObj["Authorization"] = `Basic ${utf8Btoa(`${substitute(req.auth.username).trim()}:${substitute(req.auth.password)}`)}`; - } - else if (req.auth.type === "api-key" && req.auth.apiKeyKey && req.auth.apiKeyValue) { - const key = substitute(req.auth.apiKeyKey).trim(); - const val = substitute(req.auth.apiKeyValue).trim(); - if (req.auth.apiKeyLocation === "query") - urlObj.searchParams.append(key, val); - else - headersObj[key] = val; - } - let body; - const noBody = workMethod === "GET" || workMethod === "HEAD" || req.body.type === "none"; - if (!noBody) { - if (req.body.type === "json") { - const sub = substitute(req.body.content); - try { - JSON.parse(sub); - } - catch (e) { - result.networkError = `Invalid JSON body: ${e.message}`; - return { result, response: null }; - } - body = sub; - headersObj["Content-Type"] = "application/json"; - } - else if (req.body.type === "x-www-form-urlencoded") { - const sp = new URLSearchParams(); - (req.body.urlEncoded ?? []).forEach((it) => { - if (it.active && it.key) - sp.append(substitute(it.key), substitute(it.value)); - }); - body = sp.toString(); - if (!Object.keys(headersObj).some((k) => k.toLowerCase() === "content-type")) { - headersObj["Content-Type"] = "application/x-www-form-urlencoded"; - } - } - else if (req.body.type === "form-data") { - const form = new FormData(); - for (const it of req.body.formData ?? []) { - if (!it.active || !it.key) - continue; - if (it.valueType === "file") { - if (!it.fileContentBase64) - continue; - const buf = Buffer.from(it.fileContentBase64, "base64"); - form.append(substitute(it.key), new Blob([buf], { type: it.fileType || "application/octet-stream" }), it.fileName || "upload.bin"); - } - else { - form.append(substitute(it.key), substitute(it.value)); - } - } - body = form; - // Let fetch set the multipart Content-Type with its own boundary. - for (const k of Object.keys(headersObj)) { - if (k.toLowerCase() === "content-type") - delete headersObj[k]; - } - } - else if (req.body.type === "graphql") { - const query = substitute(req.body.content); - let variables; - const rawVars = (req.body.graphqlVariables ?? "").trim(); - if (rawVars) { - try { - variables = JSON.parse(substitute(rawVars)); - } - catch (e) { - result.networkError = `Invalid GraphQL variables JSON: ${e.message}`; - return { result, response: null }; - } - } - body = JSON.stringify(variables !== undefined ? { query, variables } : { query }); - headersObj["Content-Type"] = "application/json"; - } - else { - body = substitute(req.body.content); - if (!headersObj["Content-Type"]) - headersObj["Content-Type"] = "text/plain"; - } - } - const start = Date.now(); - let response; - try { - response = await fetch(urlObj.toString(), { - method: workMethod, - headers: headersObj, - body, - signal: args.signal, - }); - } - catch (e) { - result.networkError = e.message; - return { result, response: null }; - } - const elapsed = Date.now() - start; - const bodyText = await response.text().catch(() => ""); - const respHeaders = {}; - response.headers.forEach((v, k) => { respHeaders[k] = v; }); - result.status = response.status; - result.statusText = response.statusText; - result.time = elapsed; - result.size = Buffer.byteLength(bodyText, "utf-8"); - const snapshot = { - status: response.status, - statusText: response.statusText, - headers: respHeaders, - body: bodyText, - time: elapsed, - size: result.size, - }; - // Test script. - if (req.testScript && req.testScript.trim()) { - const r = runScript(req.testScript, { - request: { url: urlObj.toString(), method: workMethod, headers: { ...headersObj }, body: typeof body === "string" ? body : undefined }, - response: { - status: response.status, - statusText: response.statusText, - headers: respHeaders, - body: bodyText, - time: elapsed, - }, - environment: { ...args.env, ...args.envOverlay }, - variables: { ...args.sessionVars, ...args.row }, - }); - result.tests.push(...r.tests); - result.logs.push(...r.logs); - if (!r.ok && r.error) - result.errors.push(`test: ${r.error}`); - for (const k of Object.keys(r.environment)) { - if (r.environment[k] !== args.env[k]) - args.envOverlay[k] = r.environment[k]; - } - Object.assign(args.sessionVars, r.variables); - } - return { result, response: snapshot }; -} -export async function runCollection(opts) { - const rootItems = opts.collection.items; - const requests = []; - walkRequests(rootItems, requests); - const iterations = opts.dataRows && opts.dataRows.length > 0 - ? opts.dataRows.length - : Math.max(1, opts.iterations || 1); - const sessionVars = {}; - const envOverlay = {}; - const envUnsets = new Set(); - const out = []; - let previousResponse = null; - for (let it = 0; it < iterations; it++) { - const row = opts.dataRows?.[it] ?? {}; - for (const req of requests) { - if (opts.abortSignal?.aborted) - return out; - const ancestors = findRequestAncestors(rootItems, req.id) ?? []; - const inherited = ancestors.length > 0 ? inherit(req, ancestors) : req; - const { result, response } = await executeRequest({ - req: inherited, - iteration: it, - row, - env: opts.environmentVariables, - sessionVars, - envOverlay, - envUnsets, - previousResponse, - signal: opts.abortSignal, - }); - out.push(result); - opts.onProgress?.(result); - if (response) - previousResponse = response; - const failedHere = result.networkError || result.tests.some((t) => !t.pass); - if (failedHere && opts.bail) - return out; - } - } - return out; -} -//# sourceMappingURL=runner.js.map \ No newline at end of file diff --git a/apps/api-runner/dist/runner.js.map b/apps/api-runner/dist/runner.js.map deleted file mode 100644 index b31611c7..00000000 --- a/apps/api-runner/dist/runner.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAWH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAsBxC,SAAS,YAAY,CAAC,KAA+C,EAAE,GAAwB;IAC3F,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACrB,IAAI,MAAM,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ;YAAE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;;YAChE,GAAG,CAAC,IAAI,CAAC,EAAuB,CAAC,CAAA;IAC1C,CAAC;AACL,CAAC;AAED,SAAS,oBAAoB,CACzB,KAA+C,EAC/C,SAAiB,EACjB,QAA4B,EAAE;IAE9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QACvC,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3C,MAAM,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;YACzE,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAA;QACvB,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAA;AACf,CAAC;AAED,SAAS,YAAY,CAAC,KAAyB,EAAE,cAA8B;IAC3E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAmB,EAAE,CAAA;IAC9B,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;QAAC,IAAI,CAAC,CAAC,GAAG;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;QAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAAC,CAAC;IACzF,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,cAAc,IAAI,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBAAE,SAAQ;YACpD,IAAI,CAAC,CAAC,GAAG;gBAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAA;YACxC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACf,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAED,SAAS,OAAO,CAAC,GAAsB,EAAE,SAA6B;IAClE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAA;IACtC,MAAM,QAAQ,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAAC;SAC/E,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC9D,MAAM,SAAS,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC;SACpE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC9D,OAAO;QACH,GAAG,GAAG;QACN,OAAO,EAAE,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC;QAC7C,gBAAgB,EAAE,QAAQ,IAAI,SAAS;QACvC,UAAU,EAAE,SAAS,IAAI,SAAS;KACrC,CAAA;AACL,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC3B,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,SAAS,CAAA;IAAC,CAAC;AAC3D,CAAC;AAED,SAAS,iBAAiB,CAAC,CAAS;IAChC,MAAM,GAAG,GAAwB,EAAE,CAAA;IACnC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAA;QAC/C,IAAI,CAAC,CAAC,EAAE,CAAC;YAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAAC,SAAQ;QAAC,CAAC;QACpC,IAAI,CAAC,CAAC,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACxB,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3E,CAAC;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY,EAAE,QAAyC;IAChF,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC/B,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;IACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACrB,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACrD,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,QAAQ,CAAC,UAAU,CAAA;IACrD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;QAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,CAAA;QACpF,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,CAAA;IACnB,CAAC;IACD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAClB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC,IAAI,CAAA;QAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC1C,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QAC1C,IAAI,GAAG,GAAY,MAAM,CAAA;QACzB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7B,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,OAAO,SAAS,CAAA;YAC5D,GAAG,GAAI,GAAwC,CAAC,CAAqB,CAAC,CAAA;QAC1E,CAAC;QACD,IAAI,GAAG,IAAI,IAAI;YAAE,OAAO,SAAS,CAAA;QACjC,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACtE,CAAC;IACD,OAAO,SAAS,CAAA;AACpB,CAAC;AAWD,SAAS,cAAc,CAAC,IAAoB;IACxC,OAAO,CAAC,IAAY,EAAU,EAAE;QAC5B,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC3C,MAAM,GAAG,GAAI,CAAY,CAAC,IAAI,EAAE,CAAA;YAChC,IAAI,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAA;gBAC1F,OAAO,QAAQ,IAAI,CAAC,CAAA;YACxB,CAAC;YACD,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG;gBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACzC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAA;YACrC,IAAI,GAAG,IAAI,IAAI,CAAC,UAAU;gBAAE,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;YACvD,IAAI,GAAG,IAAI,IAAI,CAAC,WAAW;gBAAE,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;YACzD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC7B,CAAC,CAAC,CAAA;IACN,CAAC,CAAA;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AACrD,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,IAU7B;IACG,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;IAC/B,MAAM,MAAM,GAAqB;QAC7B,SAAS;QACT,SAAS,EAAE,GAAG,CAAC,EAAE;QACjB,WAAW,EAAE,GAAG,CAAC,IAAI;QACrB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,KAAK,EAAE,EAAE;QACT,IAAI,EAAE,EAAE;QACR,MAAM,EAAE,EAAE;KACb,CAAA;IAED,MAAM,UAAU,GAAG,cAAc,CAAC;QAC9B,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;KAC1C,CAAC,CAAA;IAEF,IAAI,UAAU,GAAW,GAAG,CAAC,MAAM,CAAA;IACnC,IAAI,OAAO,GAAW,GAAG,CAAC,GAAG,CAAA;IAC7B,IAAI,WAAW,GAA2B,EAAE,CAAA;IAC5C,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG;QAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAA,CAAC,CAAC,CAAC,CAAA;IAEnF,sBAAsB;IACtB,IAAI,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC;QACtD,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,gBAAgB,EAAE;YACtC,OAAO,EAAE;gBACL,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW;gBACtD,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;aAC3H;YACD,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE;YAChD,SAAS,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;SAClD,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAqB,CAAC,CAAA;QAC7C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAmB,CAAC,CAAA;QAC1C,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK;YAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;QACnE,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;QAC/E,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC;gBAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QACpD,CAAC;QACD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,CAAA;QAC5C,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAA;QACvB,UAAU,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC,CAAC,WAAW,EAAE,CAAA;QAC3D,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,CAAA;IACnC,CAAC;IAED,IAAI,MAAW,CAAA;IACf,IAAI,CAAC;QAAC,MAAM,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAA;IAAC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACrD,MAAM,CAAC,YAAY,GAAG,gBAAiB,CAAW,CAAC,OAAO,EAAE,CAAA;QAC5D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;IACrC,CAAC;IACD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACrB,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG;YAAE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;IAC7F,CAAC,CAAC,CAAA;IAEF,MAAM,UAAU,GAA2B,EAAE,CAAA;IAC7C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/C,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;IAC7C,CAAC;IAED,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC/C,UAAU,CAAC,eAAe,CAAC,GAAG,UAAU,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAA;IAC/E,CAAC;SAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7E,UAAU,CAAC,eAAe,CAAC,GAAG,SAAS,QAAQ,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAA;IACjI,CAAC;SAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACnF,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAA;QACjD,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAA;QACnD,IAAI,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,OAAO;YAAE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;;YACxE,UAAU,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;IAC9B,CAAC;IAED,IAAI,IAA0B,CAAA;IAC9B,MAAM,MAAM,GAAG,UAAU,KAAK,KAAK,IAAI,UAAU,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAA;IACxF,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACxC,IAAI,CAAC;gBAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAAC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBAC/B,MAAM,CAAC,YAAY,GAAG,sBAAuB,CAAW,CAAC,OAAO,EAAE,CAAA;gBAClE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;YACrC,CAAC;YACD,IAAI,GAAG,GAAG,CAAA;YACV,UAAU,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAA;QACnD,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;YACnD,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAC/B;YAAA,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;gBACxC,IAAI,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG;oBAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;YAChF,CAAC,CAAC,CAAA;YACF,IAAI,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAA;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,cAAc,CAAC,EAAE,CAAC;gBAC3E,UAAU,CAAC,cAAc,CAAC,GAAG,mCAAmC,CAAA;YACpE,CAAC;QACL,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAA;YAC3B,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;gBACvC,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG;oBAAE,SAAQ;gBACnC,IAAI,EAAE,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;oBAC1B,IAAI,CAAC,EAAE,CAAC,iBAAiB;wBAAE,SAAQ;oBACnC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAA;oBACvD,IAAI,CAAC,MAAM,CACP,UAAU,CAAC,EAAE,CAAC,GAAG,CAAC,EAClB,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,IAAI,0BAA0B,EAAE,CAAC,EACpE,EAAE,CAAC,QAAQ,IAAI,YAAY,CAC9B,CAAA;gBACL,CAAC;qBAAM,CAAC;oBACJ,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;gBACzD,CAAC;YACL,CAAC;YACD,IAAI,GAAG,IAAI,CAAA;YACX,kEAAkE;YAClE,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;gBACtC,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,cAAc;oBAAE,OAAO,UAAU,CAAC,CAAC,CAAC,CAAA;YAChE,CAAC;QACL,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC1C,IAAI,SAAkB,CAAA;YACtB,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YACxD,IAAI,OAAO,EAAE,CAAC;gBACV,IAAI,CAAC;oBAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAA;gBAAC,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBAC3D,MAAM,CAAC,YAAY,GAAG,mCAAoC,CAAW,CAAC,OAAO,EAAE,CAAA;oBAC/E,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;gBACrC,CAAC;YACL,CAAC;YACD,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;YACjF,UAAU,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAA;QACnD,CAAC;aAAM,CAAC;YACJ,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACnC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC;gBAAE,UAAU,CAAC,cAAc,CAAC,GAAG,YAAY,CAAA;QAC9E,CAAC;IACL,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACxB,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACD,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE;YACtC,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,UAAU;YACnB,IAAI;YACJ,MAAM,EAAE,IAAI,CAAC,MAAM;SACtB,CAAC,CAAA;IACN,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,MAAM,CAAC,YAAY,GAAI,CAAW,CAAC,OAAO,CAAA;QAC1C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;IACrC,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAA;IAClC,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACtD,MAAM,WAAW,GAA2B,EAAE,CAAA;IAC9C,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;IAE1D,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAA;IAC/B,MAAM,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAA;IACvC,MAAM,CAAC,IAAI,GAAG,OAAO,CAAA;IACrB,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAElD,MAAM,QAAQ,GAA6B;QACvC,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO,EAAE,WAAW;QACpB,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,MAAM,CAAC,IAAK;KACrB,CAAA;IAED,eAAe;IACf,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1C,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE;YAChC,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,GAAG,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE;YACtI,QAAQ,EAAE;gBACN,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,OAAO,EAAE,WAAW;gBACpB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,OAAO;aAChB;YACD,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE;YAChD,SAAS,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;SAClD,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAqB,CAAC,CAAA;QAC7C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAmB,CAAC,CAAA;QAC1C,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK;YAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;QAC5D,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;QAC/E,CAAC;QACD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,CAAA;IAChD,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAA;AACzC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAa;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAA;IACvC,MAAM,QAAQ,GAAwB,EAAE,CAAA;IACxC,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;IACjC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QACxD,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM;QACtB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,CAAA;IAEvC,MAAM,WAAW,GAA2B,EAAE,CAAA;IAC9C,MAAM,UAAU,GAA2B,EAAE,CAAA;IAC7C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAA;IACnC,MAAM,GAAG,GAAuB,EAAE,CAAA;IAClC,IAAI,gBAAgB,GAAoC,IAAI,CAAA;IAE5D,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,UAAU,EAAE,EAAE,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAA;QACrC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO;gBAAE,OAAO,GAAG,CAAA;YACzC,MAAM,SAAS,GAAG,oBAAoB,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAA;YAC/D,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;YACtE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,cAAc,CAAC;gBAC9C,GAAG,EAAE,SAAS;gBACd,SAAS,EAAE,EAAE;gBACb,GAAG;gBACH,GAAG,EAAE,IAAI,CAAC,oBAAoB;gBAC9B,WAAW;gBACX,UAAU;gBACV,SAAS;gBACT,gBAAgB;gBAChB,MAAM,EAAE,IAAI,CAAC,WAAW;aAC3B,CAAC,CAAA;YACF,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YAChB,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,CAAA;YACzB,IAAI,QAAQ;gBAAE,gBAAgB,GAAG,QAAQ,CAAA;YACzC,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;YAC3E,IAAI,UAAU,IAAI,IAAI,CAAC,IAAI;gBAAE,OAAO,GAAG,CAAA;QAC3C,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAA;AACd,CAAC"} \ No newline at end of file diff --git a/apps/api-runner/dist/runner.test.d.ts b/apps/api-runner/dist/runner.test.d.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/apps/api-runner/dist/runner.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/apps/api-runner/dist/runner.test.js b/apps/api-runner/dist/runner.test.js deleted file mode 100644 index 17b49472..00000000 --- a/apps/api-runner/dist/runner.test.js +++ /dev/null @@ -1,90 +0,0 @@ -import { test } from "node:test"; -import assert from "node:assert/strict"; -import { runCollection } from "./runner.js"; -const calls = []; -const originalFetch = globalThis.fetch; -function installMockFetch(handler) { - globalThis.fetch = (async (input, init) => { - const url = typeof input === "string" ? input : input.toString(); - calls.push({ url, init }); - return handler(url, init); - }); -} -test("substitutes env vars and runs test scripts", async () => { - calls.length = 0; - installMockFetch(async () => new Response('{"id":42}', { status: 200, headers: { "content-type": "application/json" } })); - const col = { - id: "c", name: "T", - items: [{ - id: "r1", name: "fetch", - method: "GET", - url: "https://api.test/items/{{wanted}}", - params: [], headers: [], - body: { type: "none", content: "" }, - auth: { type: "none" }, - testScript: "pm.test('200', () => pm.expect(pm.response.code).toBe(200))", - }], - }; - const results = await runCollection({ - collection: col, - environmentVariables: { wanted: "42" }, - iterations: 1, - }); - assert.equal(calls.length, 1); - assert.equal(calls[0].url, "https://api.test/items/42"); - assert.equal(results[0].tests.length, 1); - assert.equal(results[0].tests[0].pass, true); - globalThis.fetch = originalFetch; -}); -test("chains {{response.body.token}} from previous request", async () => { - calls.length = 0; - const responses = [ - new Response(JSON.stringify({ token: "xyz" }), { status: 200, headers: { "content-type": "application/json" } }), - new Response('{"ok":true}', { status: 200, headers: { "content-type": "application/json" } }), - ]; - installMockFetch(async () => responses.shift()); - const col = { - id: "c", name: "T", - items: [ - { - id: "r1", name: "login", - method: "POST", - url: "https://api.test/login", - params: [], headers: [], - body: { type: "json", content: "{}" }, - auth: { type: "none" }, - }, - { - id: "r2", name: "me", - method: "GET", - url: "https://api.test/me", - params: [], - headers: [{ id: "h", key: "Authorization", value: "Bearer {{response.body.token}}", active: true }], - body: { type: "none", content: "" }, - auth: { type: "none" }, - }, - ], - }; - await runCollection({ collection: col, environmentVariables: {}, iterations: 1 }); - assert.equal(calls.length, 2); - const meReqHeaders = calls[1].init?.headers; - const authHeader = meReqHeaders?.Authorization; - assert.equal(authHeader, "Bearer xyz"); - globalThis.fetch = originalFetch; -}); -test("--bail short-circuits after first failing test", async () => { - calls.length = 0; - installMockFetch(async () => new Response("", { status: 500 })); - const col = { - id: "c", name: "T", - items: [ - { id: "r1", name: "a", method: "GET", url: "https://api.test/a", params: [], headers: [], body: { type: "none", content: "" }, auth: { type: "none" }, testScript: "pm.test('ok', () => pm.expect(pm.response.code).toBe(200))" }, - { id: "r2", name: "b", method: "GET", url: "https://api.test/b", params: [], headers: [], body: { type: "none", content: "" }, auth: { type: "none" } }, - ], - }; - const results = await runCollection({ collection: col, environmentVariables: {}, iterations: 1, bail: true }); - assert.equal(results.length, 1); // second request skipped - assert.equal(calls.length, 1); - globalThis.fetch = originalFetch; -}); -//# sourceMappingURL=runner.test.js.map \ No newline at end of file diff --git a/apps/api-runner/dist/runner.test.js.map b/apps/api-runner/dist/runner.test.js.map deleted file mode 100644 index c13dbd5c..00000000 --- a/apps/api-runner/dist/runner.test.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../src/runner.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAI3C,MAAM,KAAK,GAAgB,EAAE,CAAA;AAC7B,MAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAA;AAEtC,SAAS,gBAAgB,CAAC,OAA+D;IACrF,UAAU,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,KAAwB,EAAE,IAAkB,EAAE,EAAE;QACvE,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;QAChE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAA;QACzB,OAAO,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAC7B,CAAC,CAAiB,CAAA;AACtB,CAAC;AAED,IAAI,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;IAC1D,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAChB,gBAAgB,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC,CAAA;IAEzH,MAAM,GAAG,GAAe;QACpB,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;QAClB,KAAK,EAAE,CAAC;gBACJ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO;gBACvB,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,mCAAmC;gBACxC,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE;gBACvB,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;gBACnC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;gBACtB,UAAU,EAAE,6DAA6D;aAC5E,CAAC;KACL,CAAA;IACD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC;QAChC,UAAU,EAAE,GAAG;QACf,oBAAoB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QACtC,UAAU,EAAE,CAAC;KAChB,CAAC,CAAA;IACF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAC7B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,2BAA2B,CAAC,CAAA;IACvD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IACxC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAE5C,UAAU,CAAC,KAAK,GAAG,aAAa,CAAA;AACpC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;IACpE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAChB,MAAM,SAAS,GAAG;QACd,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC;QAChH,IAAI,QAAQ,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC;KAChG,CAAA;IACD,gBAAgB,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC,KAAK,EAAG,CAAC,CAAA;IAEhD,MAAM,GAAG,GAAe;QACpB,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;QAClB,KAAK,EAAE;YACH;gBACI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO;gBACvB,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,wBAAwB;gBAC7B,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE;gBACvB,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;gBACrC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;aACzB;YACD;gBACI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;gBACpB,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,qBAAqB;gBAC1B,MAAM,EAAE,EAAE;gBACV,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,gCAAgC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;gBACnG,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;gBACnC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;aACzB;SACJ;KACJ,CAAA;IACD,MAAM,aAAa,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,oBAAoB,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAA;IACjF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAC7B,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAA;IAC3C,MAAM,UAAU,GAAI,YAAuC,EAAE,aAAa,CAAA;IAC1E,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IAEtC,UAAU,CAAC,KAAK,GAAG,aAAa,CAAA;AACpC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;IAC9D,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAChB,gBAAgB,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;IAE/D,MAAM,GAAG,GAAe;QACpB,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;QAClB,KAAK,EAAE;YACH,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,4DAA4D,EAAE;YACjO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;SAC1J;KACJ,CAAA;IACD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,oBAAoB,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7G,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA,CAAE,yBAAyB;IAC1D,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAE7B,UAAU,CAAC,KAAK,GAAG,aAAa,CAAA;AACpC,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/apps/api-runner/dist/scripts.d.ts b/apps/api-runner/dist/scripts.d.ts deleted file mode 100644 index 046799b0..00000000 --- a/apps/api-runner/dist/scripts.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Headless pre-request / test script runner. Same `pm.*` surface as the web - * worker, executed inside `node:vm.runInContext` with a wall-clock timeout. - * The sandbox has no fetch, no fs, no process — just `pm` + `console`. - */ -export interface ScriptContext { - request: { - url: string; - method: string; - headers: Record; - body?: string; - }; - response?: { - status: number; - statusText: string; - headers: Record; - body: string; - time: number; - }; - environment: Record; - variables: Record; -} -export interface ScriptResult { - ok: boolean; - error?: string; - tests: { - name: string; - pass: boolean; - error?: string; - }[]; - logs: { - level: "log" | "warn" | "error"; - args: string[]; - }[]; - environment: Record; - variables: Record; - request: ScriptContext["request"]; -} -export declare function runScript(script: string, ctx: ScriptContext): ScriptResult; diff --git a/apps/api-runner/dist/scripts.js b/apps/api-runner/dist/scripts.js deleted file mode 100644 index 76df9746..00000000 --- a/apps/api-runner/dist/scripts.js +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Headless pre-request / test script runner. Same `pm.*` surface as the web - * worker, executed inside `node:vm.runInContext` with a wall-clock timeout. - * The sandbox has no fetch, no fs, no process — just `pm` + `console`. - */ -import { Script, createContext } from "node:vm"; -const SCRIPT_TIMEOUT_MS = 3_000; -function safeStringify(v) { - if (typeof v === "string") - return v; - try { - return JSON.stringify(v); - } - catch { - return String(v); - } -} -function makeExpect(actual) { - const fail = (m) => { throw new Error(m); }; - return { - toBe(e) { if (actual !== e) - fail(`expected ${safeStringify(actual)} to be ${safeStringify(e)}`); }, - toEqual(e) { if (JSON.stringify(actual) !== JSON.stringify(e)) - fail(`expected ${safeStringify(actual)} to equal ${safeStringify(e)}`); }, - toMatch(r) { - const ok = r instanceof RegExp ? r.test(String(actual)) : String(actual).includes(String(r)); - if (!ok) - fail(`expected ${safeStringify(actual)} to match ${safeStringify(r)}`); - }, - toContain(s) { - const ok = Array.isArray(actual) - ? actual.includes(s) - : String(actual).includes(String(s)); - if (!ok) - fail(`expected ${safeStringify(actual)} to contain ${safeStringify(s)}`); - }, - toBeGreaterThan(n) { if (!(Number(actual) > n)) - fail(`expected ${safeStringify(actual)} > ${n}`); }, - toBeLessThan(n) { if (!(Number(actual) < n)) - fail(`expected ${safeStringify(actual)} < ${n}`); }, - toHaveProperty(k) { - if (!actual || typeof actual !== "object" || !(k in actual)) - fail(`expected object to have property "${k}"`); - }, - toBeTruthy() { if (!actual) - fail(`expected ${safeStringify(actual)} to be truthy`); }, - toBeFalsy() { if (actual) - fail(`expected ${safeStringify(actual)} to be falsy`); }, - }; -} -export function runScript(script, ctx) { - if (!script || !script.trim()) { - return { - ok: true, - tests: [], - logs: [], - environment: ctx.environment, - variables: ctx.variables, - request: ctx.request, - }; - } - const env = { ...ctx.environment }; - const vars = { ...ctx.variables }; - const tests = []; - const logs = []; - const request = { - url: ctx.request.url, - method: ctx.request.method, - headers: { ...ctx.request.headers }, - body: ctx.request.body, - }; - const pm = { - environment: { - get(k) { return env[k]; }, - set(k, v) { env[String(k)] = safeStringify(v); }, - unset(k) { delete env[String(k)]; }, - has(k) { return k in env; }, - toObject() { return { ...env }; }, - }, - variables: { - get(k) { return vars[k] ?? env[k]; }, - set(k, v) { vars[String(k)] = safeStringify(v); }, - has(k) { return k in vars || k in env; }, - }, - request: { - get url() { return request.url; }, - set url(v) { request.url = String(v); }, - get method() { return request.method; }, - set method(v) { request.method = String(v).toUpperCase(); }, - headers: { - get(name) { - const k = Object.keys(request.headers).find((h) => h.toLowerCase() === String(name).toLowerCase()); - return k ? request.headers[k] : undefined; - }, - add(name, v) { request.headers[String(name)] = safeStringify(v); }, - remove(name) { - const k = Object.keys(request.headers).find((h) => h.toLowerCase() === String(name).toLowerCase()); - if (k) - delete request.headers[k]; - }, - toObject() { return { ...request.headers }; }, - }, - get body() { return request.body; }, - set body(v) { request.body = v == null ? undefined : String(v); }, - }, - response: ctx.response - ? { - code: ctx.response.status, - status: ctx.response.statusText, - responseTime: ctx.response.time, - headers: { - get(name) { - const lower = String(name).toLowerCase(); - const k = Object.keys(ctx.response.headers).find((h) => h.toLowerCase() === lower); - return k ? ctx.response.headers[k] : undefined; - }, - toObject() { return { ...ctx.response.headers }; }, - }, - text: () => ctx.response.body, - json: () => { - try { - return JSON.parse(ctx.response.body); - } - catch (e) { - throw new Error(`Response body is not valid JSON: ${e.message}`); - } - }, - } - : undefined, - test(name, fn) { - try { - fn(); - tests.push({ name: String(name), pass: true }); - } - catch (e) { - tests.push({ name: String(name), pass: false, error: e.message }); - } - }, - expect: makeExpect, - sendRequest() { throw new Error("pm.sendRequest is not supported in the CLI yet"); }, - }; - const sandboxConsole = { - log: (...args) => { logs.push({ level: "log", args: args.map(safeStringify) }); }, - warn: (...args) => { logs.push({ level: "warn", args: args.map(safeStringify) }); }, - error: (...args) => { logs.push({ level: "error", args: args.map(safeStringify) }); }, - }; - const sandbox = createContext({ pm, console: sandboxConsole }); - try { - new Script(`"use strict";\n${script}`).runInContext(sandbox, { timeout: SCRIPT_TIMEOUT_MS }); - } - catch (e) { - return { ok: false, error: e.message, tests, logs, environment: env, variables: vars, request }; - } - return { ok: true, tests, logs, environment: env, variables: vars, request }; -} -//# sourceMappingURL=scripts.js.map \ No newline at end of file diff --git a/apps/api-runner/dist/scripts.js.map b/apps/api-runner/dist/scripts.js.map deleted file mode 100644 index 45dc8b5a..00000000 --- a/apps/api-runner/dist/scripts.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"scripts.js","sourceRoot":"","sources":["../src/scripts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAyB/C,MAAM,iBAAiB,GAAG,KAAK,CAAA;AAE/B,SAAS,aAAa,CAAC,CAAU;IAC7B,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAA;IACnC,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAA;IAAC,CAAC;AAC/D,CAAC;AAED,SAAS,UAAU,CAAC,MAAe;IAC/B,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC,CAAA;IAClD,OAAO;QACH,IAAI,CAAC,CAAU,IAAI,IAAI,MAAM,KAAK,CAAC;YAAE,IAAI,CAAC,YAAY,aAAa,CAAC,MAAM,CAAC,UAAU,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA,CAAC,CAAC;QAC1G,OAAO,CAAC,CAAU,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,YAAY,aAAa,CAAC,MAAM,CAAC,aAAa,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA,CAAC,CAAC;QAChJ,OAAO,CAAC,CAAU;YACd,MAAM,EAAE,GAAG,CAAC,YAAY,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;YAC5F,IAAI,CAAC,EAAE;gBAAE,IAAI,CAAC,YAAY,aAAa,CAAC,MAAM,CAAC,aAAa,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACnF,CAAC;QACD,SAAS,CAAC,CAAU;YAChB,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAC5B,CAAC,CAAE,MAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACnC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;YACxC,IAAI,CAAC,EAAE;gBAAE,IAAI,CAAC,YAAY,aAAa,CAAC,MAAM,CAAC,eAAe,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACrF,CAAC;QACD,eAAe,CAAC,CAAS,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAAE,IAAI,CAAC,YAAY,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA,CAAC,CAAC;QAC1G,YAAY,CAAC,CAAS,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAAE,IAAI,CAAC,YAAY,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA,CAAC,CAAC;QACvG,cAAc,CAAC,CAAS;YACpB,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,IAAK,MAAiB,CAAC;gBAAE,IAAI,CAAC,qCAAqC,CAAC,GAAG,CAAC,CAAA;QAC5H,CAAC;QACD,UAAU,KAAK,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,YAAY,aAAa,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA,CAAC,CAAC;QACpF,SAAS,KAAK,IAAI,MAAM;YAAE,IAAI,CAAC,YAAY,aAAa,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA,CAAC,CAAC;KACpF,CAAA;AACL,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,GAAkB;IACxD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5B,OAAO;YACH,EAAE,EAAE,IAAI;YACR,KAAK,EAAE,EAAE;YACT,IAAI,EAAE,EAAE;YACR,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,OAAO,EAAE,GAAG,CAAC,OAAO;SACvB,CAAA;IACL,CAAC;IAED,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC,SAAS,EAAE,CAAA;IACjC,MAAM,KAAK,GAA0B,EAAE,CAAA;IACvC,MAAM,IAAI,GAAyB,EAAE,CAAA;IACrC,MAAM,OAAO,GAAG;QACZ,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG;QACpB,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM;QAC1B,OAAO,EAAE,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE;QACnC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI;KACzB,CAAA;IAED,MAAM,EAAE,GAAG;QACP,WAAW,EAAE;YACT,GAAG,CAAC,CAAS,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC;YAChC,GAAG,CAAC,CAAS,EAAE,CAAU,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC;YAChE,KAAK,CAAC,CAAS,IAAI,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC;YAC1C,GAAG,CAAC,CAAS,IAAI,OAAO,CAAC,IAAI,GAAG,CAAA,CAAC,CAAC;YAClC,QAAQ,KAAK,OAAO,EAAE,GAAG,GAAG,EAAE,CAAA,CAAC,CAAC;SACnC;QACD,SAAS,EAAE;YACP,GAAG,CAAC,CAAS,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC;YAC3C,GAAG,CAAC,CAAS,EAAE,CAAU,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC;YACjE,GAAG,CAAC,CAAS,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,GAAG,CAAA,CAAC,CAAC;SAClD;QACD,OAAO,EAAE;YACL,IAAI,GAAG,KAAK,OAAO,OAAO,CAAC,GAAG,CAAA,CAAC,CAAC;YAChC,IAAI,GAAG,CAAC,CAAS,IAAI,OAAO,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC;YAC9C,IAAI,MAAM,KAAK,OAAO,OAAO,CAAC,MAAM,CAAA,CAAC,CAAC;YACtC,IAAI,MAAM,CAAC,CAAS,IAAI,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA,CAAC,CAAC;YAClE,OAAO,EAAE;gBACL,GAAG,CAAC,IAAY;oBACZ,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;oBAClG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;gBAC7C,CAAC;gBACD,GAAG,CAAC,IAAY,EAAE,CAAU,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC;gBAClF,MAAM,CAAC,IAAY;oBACf,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;oBAClG,IAAI,CAAC;wBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;gBACpC,CAAC;gBACD,QAAQ,KAAK,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAA,CAAC,CAAC;aAC/C;YACD,IAAI,IAAI,KAAK,OAAO,OAAO,CAAC,IAAI,CAAA,CAAC,CAAC;YAClC,IAAI,IAAI,CAAC,CAAqB,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC;SACvF;QACD,QAAQ,EAAE,GAAG,CAAC,QAAQ;YAClB,CAAC,CAAC;gBACE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM;gBACzB,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU;gBAC/B,YAAY,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI;gBAC/B,OAAO,EAAE;oBACL,GAAG,CAAC,IAAY;wBACZ,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;wBACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAA;wBACnF,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;oBACnD,CAAC;oBACD,QAAQ,KAAK,OAAO,EAAE,GAAG,GAAG,CAAC,QAAS,CAAC,OAAO,EAAE,CAAA,CAAC,CAAC;iBACrD;gBACD,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,QAAS,CAAC,IAAI;gBAC9B,IAAI,EAAE,GAAG,EAAE;oBACP,IAAI,CAAC;wBAAC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAS,CAAC,IAAI,CAAC,CAAA;oBAAC,CAAC;oBAC7C,OAAO,CAAC,EAAE,CAAC;wBAAC,MAAM,IAAI,KAAK,CAAC,oCAAqC,CAAW,CAAC,OAAO,EAAE,CAAC,CAAA;oBAAC,CAAC;gBAC7F,CAAC;aACJ;YACD,CAAC,CAAC,SAAS;QACf,IAAI,CAAC,IAAY,EAAE,EAAc;YAC7B,IAAI,CAAC;gBAAC,EAAE,EAAE,CAAC;gBAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAAC,CAAC;YAC5D,OAAO,CAAC,EAAE,CAAC;gBAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAG,CAAW,CAAC,OAAO,EAAE,CAAC,CAAA;YAAC,CAAC;QAC9F,CAAC;QACD,MAAM,EAAE,UAAU;QAClB,WAAW,KAAK,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA,CAAC,CAAC;KACtF,CAAA;IAED,MAAM,cAAc,GAAG;QACnB,GAAG,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA,CAAC,CAAC;QAC3F,IAAI,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA,CAAC,CAAC;QAC7F,KAAK,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA,CAAC,CAAC;KAClG,CAAA;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAA;IAC9D,IAAI,CAAC;QACD,IAAI,MAAM,CAAC,kBAAkB,MAAM,EAAE,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAA;IAChG,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAG,CAAW,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;IAC9G,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAChF,CAAC"} \ No newline at end of file diff --git a/apps/api-runner/dist/types.d.ts b/apps/api-runner/dist/types.d.ts deleted file mode 100644 index 6ee1a708..00000000 --- a/apps/api-runner/dist/types.d.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Self-contained types for the CLI — deliberately decoupled from the web app so - * the package can publish standalone. The shape matches what `lib/import/postman.ts` - * emits, so a Postman v2.1 export round-trips into here. - */ -export type RequestMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS"; -export interface KeyValueItem { - id: string; - key: string; - value: string; - active: boolean; -} -export interface FormDataItem { - id: string; - key: string; - value: string; - active: boolean; - valueType: "text" | "file"; - fileName?: string; - fileType?: string; - fileContentBase64?: string; -} -export interface RequestBody { - type: "json" | "text" | "none" | "form-data" | "x-www-form-urlencoded" | "graphql"; - content: string; - formData?: FormDataItem[]; - urlEncoded?: KeyValueItem[]; - graphqlVariables?: string; -} -export interface RequestAuth { - type: "none" | "bearer" | "basic" | "api-key"; - token?: string; - username?: string; - password?: string; - apiKeyKey?: string; - apiKeyValue?: string; - apiKeyLocation?: "header" | "query"; -} -export interface CollectionRequest { - id: string; - name: string; - method: RequestMethod; - url: string; - params: KeyValueItem[]; - headers: KeyValueItem[]; - body: RequestBody; - auth: RequestAuth; - preRequestScript?: string; - testScript?: string; -} -export interface CollectionFolder { - id: string; - name: string; - type: "folder"; - items: (CollectionFolder | CollectionRequest)[]; - defaultHeaders?: KeyValueItem[]; - preRequestScript?: string; - testScript?: string; -} -export interface Collection { - id: string; - name: string; - items: (CollectionFolder | CollectionRequest)[]; -} -export interface TestResult { - name: string; - pass: boolean; - error?: string; -} -export interface ScriptLog { - level: "log" | "warn" | "error"; - args: string[]; -} -export interface RequestRunResult { - iteration: number; - requestId: string; - requestName: string; - method: string; - url: string; - status?: number; - statusText?: string; - time?: number; - size?: number; - tests: TestResult[]; - logs: ScriptLog[]; - errors: string[]; - networkError?: string; -} diff --git a/apps/api-runner/dist/types.js b/apps/api-runner/dist/types.js deleted file mode 100644 index f7c8bdff..00000000 --- a/apps/api-runner/dist/types.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Self-contained types for the CLI — deliberately decoupled from the web app so - * the package can publish standalone. The shape matches what `lib/import/postman.ts` - * emits, so a Postman v2.1 export round-trips into here. - */ -export {}; -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/apps/api-runner/dist/types.js.map b/apps/api-runner/dist/types.js.map deleted file mode 100644 index 902389a9..00000000 --- a/apps/api-runner/dist/types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"} \ No newline at end of file diff --git a/apps/api-runner/node_modules/.bin/tsc b/apps/api-runner/node_modules/.bin/tsc deleted file mode 100755 index 6d8d1011..00000000 --- a/apps/api-runner/node_modules/.bin/tsc +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") -basedir_win="$basedir" -exe="" -msys="" - -case `uname -a` in - *CYGWIN*|*MINGW*|*MSYS*) - if command -v cygpath > /dev/null 2>&1; then - basedir_win=`cygpath -w "$basedir"` - fi - exe=".exe" - msys="true" - ;; - *WSL2*) - if command -v wslpath > /dev/null 2>&1; then - basedir_win="$(wslpath -w "$basedir" 2> /dev/null)" - if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then - basedir_win="$basedir" - else - exe=".exe" - fi - fi - ;; -esac - -if [ -z "$NODE_PATH" ]; then - export NODE_PATH="/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/node_modules" -else - export NODE_PATH="/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/node_modules:$NODE_PATH" -fi -if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then - exec "$basedir/node.exe" "$basedir_win/../typescript/bin/tsc" "$@" -elif [ -x "$basedir/node" ]; then - exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@" -elif command -v node >/dev/null 2>&1; then - exec node "$basedir/../typescript/bin/tsc" "$@" -elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then - exec node.exe "$basedir_win/../typescript/bin/tsc" "$@" -else - exec node "$basedir/../typescript/bin/tsc" "$@" -fi -# cmd-shim-target=/Users/max/Works/Personal/mydevtools/apps/api-runner/node_modules/typescript/bin/tsc diff --git a/apps/api-runner/node_modules/.bin/tsserver b/apps/api-runner/node_modules/.bin/tsserver deleted file mode 100755 index 66a860ca..00000000 --- a/apps/api-runner/node_modules/.bin/tsserver +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") -basedir_win="$basedir" -exe="" -msys="" - -case `uname -a` in - *CYGWIN*|*MINGW*|*MSYS*) - if command -v cygpath > /dev/null 2>&1; then - basedir_win=`cygpath -w "$basedir"` - fi - exe=".exe" - msys="true" - ;; - *WSL2*) - if command -v wslpath > /dev/null 2>&1; then - basedir_win="$(wslpath -w "$basedir" 2> /dev/null)" - if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then - basedir_win="$basedir" - else - exe=".exe" - fi - fi - ;; -esac - -if [ -z "$NODE_PATH" ]; then - export NODE_PATH="/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/node_modules" -else - export NODE_PATH="/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/max/Works/Personal/mydevtools/node_modules/.pnpm/node_modules:$NODE_PATH" -fi -if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then - exec "$basedir/node.exe" "$basedir_win/../typescript/bin/tsserver" "$@" -elif [ -x "$basedir/node" ]; then - exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@" -elif command -v node >/dev/null 2>&1; then - exec node "$basedir/../typescript/bin/tsserver" "$@" -elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then - exec node.exe "$basedir_win/../typescript/bin/tsserver" "$@" -else - exec node "$basedir/../typescript/bin/tsserver" "$@" -fi -# cmd-shim-target=/Users/max/Works/Personal/mydevtools/apps/api-runner/node_modules/typescript/bin/tsserver diff --git a/apps/api-runner/node_modules/@types/node b/apps/api-runner/node_modules/@types/node deleted file mode 120000 index d235c10c..00000000 --- a/apps/api-runner/node_modules/@types/node +++ /dev/null @@ -1 +0,0 @@ -../../../../node_modules/.pnpm/@types+node@20.19.43/node_modules/@types/node \ No newline at end of file diff --git a/apps/api-runner/node_modules/typescript b/apps/api-runner/node_modules/typescript deleted file mode 120000 index 949dba4e..00000000 --- a/apps/api-runner/node_modules/typescript +++ /dev/null @@ -1 +0,0 @@ -../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript \ No newline at end of file diff --git a/apps/api-runner/package.json b/apps/api-runner/package.json deleted file mode 100644 index 3988ea7e..00000000 --- a/apps/api-runner/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@mydevtools/api-runner", - "version": "0.1.0", - "description": "Headless runner for mydevtools API client collections — CI/CD parity with the web UI runner.", - "type": "module", - "bin": { - "mydevtools-api": "./dist/index.js" - }, - "main": "./dist/index.js", - "files": ["dist", "README.md"], - "scripts": { - "build": "tsc -p tsconfig.json", - "test": "node --experimental-vm-modules --test \"dist/**/*.test.js\"", - "start": "node ./dist/index.js", - "prepublishOnly": "pnpm build" - }, - "engines": { - "node": ">=18.17.0" - }, - "license": "MIT", - "devDependencies": { - "typescript": "^5.4.5", - "@types/node": "^20.11.0" - } -} diff --git a/apps/api-runner/src/csv.ts b/apps/api-runner/src/csv.ts deleted file mode 100644 index aa153e58..00000000 --- a/apps/api-runner/src/csv.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** Same CSV/JSON data-file parser as the web app — verbatim port for portability. */ - -export function parseCsv(text: string): Record[] { - const rows = parseRows(text) - if (rows.length === 0) return [] - const [headers, ...body] = rows - return body - .filter((r) => r.length > 1 || (r.length === 1 && r[0] !== "")) - .map((cells) => { - const obj: Record = {} - headers.forEach((h, i) => { obj[h] = cells[i] ?? "" }) - return obj - }) -} - -function parseRows(text: string): string[][] { - const rows: string[][] = [] - let row: string[] = [] - let cell = "" - let inQuote = false - for (let i = 0; i < text.length; i++) { - const c = text[i] - if (inQuote) { - if (c === '"' && text[i + 1] === '"') { cell += '"'; i++ } - else if (c === '"') { inQuote = false } - else cell += c - } else { - if (c === '"') { inQuote = true } - else if (c === ",") { row.push(cell); cell = "" } - else if (c === "\n" || c === "\r") { - row.push(cell); cell = "" - rows.push(row); row = [] - if (c === "\r" && text[i + 1] === "\n") i++ - } else cell += c - } - } - if (cell.length > 0 || row.length > 0) { - row.push(cell) - rows.push(row) - } - return rows -} - -export function parseDataFile(text: string): Record[] { - const t = text.trimStart() - if (t.startsWith("[")) { - const parsed = JSON.parse(t) - if (!Array.isArray(parsed)) throw new Error("JSON data file must be an array") - return parsed.map((row: unknown) => { - if (!row || typeof row !== "object") return {} - const out: Record = {} - for (const [k, v] of Object.entries(row as Record)) { - out[k] = v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v) - } - return out - }) - } - return parseCsv(text) -} diff --git a/apps/api-runner/src/index.ts b/apps/api-runner/src/index.ts deleted file mode 100644 index e9831b08..00000000 --- a/apps/api-runner/src/index.ts +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env node -/** - * mydevtools-api CLI. - * Headless equivalent of the web-side collection runner. Same scripts, same - * substitution layering, same JUnit output — runs over a Postman v2.1 export. - */ - -import { readFile, writeFile } from "node:fs/promises" -import { importPostmanCollection, parsePostmanEnvironment } from "./postman.js" -import { parseDataFile } from "./csv.js" -import { runCollection } from "./runner.js" -import { buildJUnitXml } from "./junit.js" -import type { RequestRunResult } from "./types.js" - -interface CliArgs { - cmd?: "run" - collectionPath?: string - envPath?: string - dataPath?: string - iterations: number - reporter?: "cli" | "junit" - reporterOut?: string - bail: boolean - help: boolean -} - -function parseArgs(argv: string[]): CliArgs { - const out: CliArgs = { iterations: 1, bail: false, help: false } - if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") { - out.help = true - return out - } - out.cmd = argv[0] as "run" - let i = 1 - while (i < argv.length) { - const a = argv[i] - if (a === "--data" || a === "-d") { out.dataPath = argv[++i] } - else if (a === "--env" || a === "-e") { out.envPath = argv[++i] } - else if (a === "--iterations" || a === "-n") { out.iterations = Math.max(1, Number(argv[++i]) || 1) } - else if (a === "--reporter" || a === "-r") { out.reporter = argv[++i] as CliArgs["reporter"] } - else if (a === "--reporter-out" || a === "-o") { out.reporterOut = argv[++i] } - else if (a === "--bail") { out.bail = true } - else if (a === "-h" || a === "--help") { out.help = true } - else if (!out.collectionPath) { out.collectionPath = a } - else throw new Error(`Unknown argument: ${a}`) - i++ - } - return out -} - -function printUsage(): void { - console.error(`Usage: mydevtools-api run [options] - -Options: - -d, --data CSV or JSON array of iteration rows (each row's keys - override env vars for one iteration). - -e, --env Postman Environment export (v2 schema) — enabled - values become the base \`{{var}}\` map. - -n, --iterations Iterations to run when no data file is given. Default 1. - -r, --reporter 'cli' (default) or 'junit'. - -o, --reporter-out Write the JUnit XML here. Required for --reporter junit. - --bail Stop on the first failing request. - -h, --help Show this message. - -Exit codes: - 0 — all tests passed - 1 — one or more tests / requests failed - 2 — usage error -`) -} - -function fmtRow(r: RequestRunResult): string { - const ok = !r.networkError && r.tests.every((t) => t.pass) - const status = r.status !== undefined ? r.status : "—" - const time = r.time !== undefined ? `${r.time}ms` : "" - return ` ${ok ? "✓" : "✗"} ${r.method.padEnd(6)} ${status} ${time} ${r.requestName}` -} - -async function main(): Promise { - const args = parseArgs(process.argv.slice(2)) - if (args.help || !args.cmd) { printUsage(); process.exit(args.help ? 0 : 2) } - if (args.cmd !== "run" || !args.collectionPath) { printUsage(); process.exit(2) } - if (args.reporter === "junit" && !args.reporterOut) { - console.error("--reporter-out is required when --reporter junit is used") - process.exit(2) - } - - const collection = importPostmanCollection(await readFile(args.collectionPath, "utf-8")) - const dataRows = args.dataPath ? parseDataFile(await readFile(args.dataPath, "utf-8")) : undefined - const env = args.envPath ? parsePostmanEnvironment(await readFile(args.envPath, "utf-8")) : {} - - console.log(`Running ${collection.name}` + (dataRows ? ` × ${dataRows.length} iterations` : args.iterations > 1 ? ` × ${args.iterations} iterations` : "")) - - const results = await runCollection({ - collection, - environmentVariables: env, - iterations: args.iterations, - dataRows, - bail: args.bail, - onProgress: (r) => console.log(fmtRow(r)), - }) - - const allTests = results.flatMap((r) => r.tests) - const failed = allTests.filter((t) => !t.pass).length - const errored = results.filter((r) => r.networkError || r.errors.length > 0).length - const passed = allTests.length - failed - - console.log("") - console.log(`Requests: ${results.length}`) - console.log(`Assertions: ${allTests.length} (✓ ${passed} / ✗ ${failed})`) - if (errored > 0) console.log(`Errors: ${errored}`) - - for (const r of results) { - const failures = r.tests.filter((t) => !t.pass) - if (failures.length === 0 && !r.networkError) continue - console.log(`\n ${r.method} ${r.requestName}`) - if (r.networkError) console.log(` ! ${r.networkError}`) - for (const t of failures) console.log(` ✗ ${t.name}\n ${t.error}`) - } - - if (args.reporter === "junit" && args.reporterOut) { - await writeFile(args.reporterOut, buildJUnitXml(results, collection.name)) - console.log(`\nWrote JUnit XML to ${args.reporterOut}`) - } - - process.exit(failed > 0 || errored > 0 ? 1 : 0) -} - -main().catch((err) => { - console.error(`Fatal: ${(err as Error).message}`) - process.exit(1) -}) diff --git a/apps/api-runner/src/junit.ts b/apps/api-runner/src/junit.ts deleted file mode 100644 index a12e4337..00000000 --- a/apps/api-runner/src/junit.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** JUnit XML emitter — port of `apps/web/src/lib/runner/junit.ts`. */ - -import type { RequestRunResult } from "./types.js" - -function escapeXml(s: string): string { - return String(s) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'") -} - -export function buildJUnitXml(results: RequestRunResult[], suiteName: string): string { - const allTests = results.flatMap((r) => r.tests.map((t) => ({ run: r, test: t }))) - const totalTests = allTests.length - const failed = allTests.filter(({ test }) => !test.pass).length - const errored = results.filter((r) => r.networkError || r.errors.length > 0).length - const totalTime = results.reduce((s, r) => s + (r.time ?? 0), 0) / 1000 - - const cases = allTests.map(({ run, test }) => { - const classname = escapeXml(run.requestName) - const name = escapeXml(test.name) - const time = ((run.time ?? 0) / 1000).toFixed(3) - if (test.pass) { - return ` ` - } - const msg = escapeXml(test.error ?? "assertion failed") - return ` - ${msg} - ` - }) - - const errorCases = results - .filter((r) => r.networkError || r.errors.length > 0) - .map((r) => { - const msg = escapeXml(r.networkError ?? r.errors.join("; ")) - const classname = escapeXml(r.requestName) - const time = ((r.time ?? 0) / 1000).toFixed(3) - return ` - ${msg} - ` - }) - - const body = [...cases, ...errorCases].join("\n") - return ` - - -${body} - -` -} diff --git a/apps/api-runner/src/postman.ts b/apps/api-runner/src/postman.ts deleted file mode 100644 index 0edad52f..00000000 --- a/apps/api-runner/src/postman.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Lean Postman v2.1 → Collection converter for the CLI. - * Mirrors `apps/web/src/lib/import/postman.ts` but bound to the smaller - * CLI type surface (no OAuth2, no graphql body parsing — those map to text). - */ - -import { randomUUID } from "node:crypto" -import type { - Collection, - CollectionFolder, - CollectionRequest, - KeyValueItem, - RequestAuth, - RequestBody, - RequestMethod, -} from "./types.js" - -const VALID_METHODS = new Set([ - "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", -]) - -interface PostmanCollection { info?: { name?: string }; item?: PostmanItem[] } -interface PostmanItem { - name?: string - item?: PostmanItem[] - request?: PostmanRequest - event?: Array<{ listen?: string; script?: { exec?: string | string[] } }> -} -interface PostmanRequest { - method?: string - url?: string | { raw?: string; query?: PostmanKv[] } - header?: PostmanKv[] - body?: { - mode?: string - raw?: string - urlencoded?: PostmanKv[] - formdata?: PostmanKv[] - options?: { raw?: { language?: string } } - } - auth?: { - type?: string - bearer?: PostmanKv[] - basic?: PostmanKv[] - apikey?: PostmanKv[] - } -} -interface PostmanKv { - key?: string - value?: string - disabled?: boolean - type?: string -} - -const id = () => randomUUID() - -function pickScript(events: PostmanItem["event"], kind: "prerequest" | "test"): string { - const evt = events?.find((e) => e.listen === kind) - if (!evt?.script?.exec) return "" - return Array.isArray(evt.script.exec) ? evt.script.exec.join("\n") : evt.script.exec -} - -function kvList(items?: PostmanKv[]): KeyValueItem[] { - return (items ?? []) - .filter((kv) => kv && (kv.key || kv.value)) - .map((kv) => ({ - id: id(), - key: kv.key ?? "", - value: kv.value ?? "", - active: !kv.disabled, - })) -} - -function readUrl(u: PostmanRequest["url"]): { url: string; query: PostmanKv[] } { - if (!u) return { url: "", query: [] } - if (typeof u === "string") return { url: u, query: [] } - return { url: u.raw ?? "", query: u.query ?? [] } -} - -function lookup(items: PostmanKv[] | undefined, key: string): string | undefined { - return items?.find((x) => x.key === key)?.value -} - -function convertAuth(a: PostmanRequest["auth"]): RequestAuth { - if (!a || a.type === "noauth" || !a.type) return { type: "none" } - if (a.type === "bearer") return { type: "bearer", token: lookup(a.bearer, "token") } - if (a.type === "basic") { - return { - type: "basic", - username: lookup(a.basic, "username"), - password: lookup(a.basic, "password"), - } - } - if (a.type === "apikey") { - return { - type: "api-key", - apiKeyKey: lookup(a.apikey, "key"), - apiKeyValue: lookup(a.apikey, "value"), - apiKeyLocation: lookup(a.apikey, "in") === "query" ? "query" : "header", - } - } - return { type: "none" } -} - -function convertBody(b: PostmanRequest["body"]): RequestBody { - if (!b || b.mode === "none") return { type: "none", content: "" } - if (b.mode === "raw") { - const lang = b.options?.raw?.language - return { type: lang === "json" ? "json" : "text", content: b.raw ?? "" } - } - if (b.mode === "urlencoded") { - return { - type: "x-www-form-urlencoded", - content: "", - urlEncoded: kvList(b.urlencoded), - } - } - if (b.mode === "formdata") { - return { - type: "form-data", - content: "", - formData: (b.formdata ?? []) - .filter((kv) => kv && (kv.key || kv.value)) - .map((kv) => ({ - id: id(), - key: kv.key ?? "", - value: kv.value ?? "", - active: !kv.disabled, - valueType: kv.type === "file" ? "file" : "text", - })), - } - } - return { type: "text", content: b.raw ?? "" } -} - -function convertRequest(item: PostmanItem): CollectionRequest { - const r = item.request ?? {} - const { url, query } = readUrl(r.url) - const method = (r.method ?? "GET").toUpperCase() as RequestMethod - const safeMethod: RequestMethod = VALID_METHODS.has(method) ? method : "GET" - const preRequestScript = pickScript(item.event, "prerequest") - const testScript = pickScript(item.event, "test") - return { - id: id(), - name: item.name ?? safeMethod, - method: safeMethod, - url, - params: kvList(query), - headers: kvList(r.header), - body: convertBody(r.body), - auth: convertAuth(r.auth), - preRequestScript: preRequestScript || undefined, - testScript: testScript || undefined, - } -} - -function convertItems(items?: PostmanItem[]): (CollectionFolder | CollectionRequest)[] { - const out: (CollectionFolder | CollectionRequest)[] = [] - for (const item of items ?? []) { - if (item.request) out.push(convertRequest(item)) - else if (item.item) { - out.push({ - id: id(), - name: item.name ?? "Folder", - type: "folder", - items: convertItems(item.item), - }) - } - } - return out -} - -export function importPostmanCollection(raw: string | object): Collection { - const data: PostmanCollection = typeof raw === "string" ? JSON.parse(raw) : (raw as PostmanCollection) - if (!data || (!data.info?.name && !Array.isArray(data.item))) { - throw new Error("Not a Postman collection (missing info.name + item[])") - } - return { - id: id(), - name: data.info?.name ?? "Imported Postman collection", - items: convertItems(data.item), - } -} - -/** - * Parse a Postman Environment export (v2 schema) into a flat `key → value` map. - * Disabled vars are skipped. - */ -export function parsePostmanEnvironment(raw: string | object): Record { - const data = typeof raw === "string" ? JSON.parse(raw) : raw - const vars = (data as { values?: Array<{ key?: string; value?: string; enabled?: boolean }> })?.values - if (!Array.isArray(vars)) return {} - const out: Record = {} - for (const v of vars) { - if (!v?.key) continue - if (v.enabled === false) continue - out[v.key] = String(v.value ?? "") - } - return out -} diff --git a/apps/api-runner/src/runner.test.ts b/apps/api-runner/src/runner.test.ts deleted file mode 100644 index 0c456312..00000000 --- a/apps/api-runner/src/runner.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { test } from "node:test" -import assert from "node:assert/strict" -import { runCollection } from "./runner.js" -import type { Collection } from "./types.js" - -interface FetchCall { url: string; init?: RequestInit } -const calls: FetchCall[] = [] -const originalFetch = globalThis.fetch - -function installMockFetch(handler: (url: string, init?: RequestInit) => Promise) { - globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { - const url = typeof input === "string" ? input : input.toString() - calls.push({ url, init }) - return handler(url, init) - }) as typeof fetch -} - -test("substitutes env vars and runs test scripts", async () => { - calls.length = 0 - installMockFetch(async () => new Response('{"id":42}', { status: 200, headers: { "content-type": "application/json" } })) - - const col: Collection = { - id: "c", name: "T", - items: [{ - id: "r1", name: "fetch", - method: "GET", - url: "https://api.test/items/{{wanted}}", - params: [], headers: [], - body: { type: "none", content: "" }, - auth: { type: "none" }, - testScript: "pm.test('200', () => pm.expect(pm.response.code).toBe(200))", - }], - } - const results = await runCollection({ - collection: col, - environmentVariables: { wanted: "42" }, - iterations: 1, - }) - assert.equal(calls.length, 1) - assert.equal(calls[0].url, "https://api.test/items/42") - assert.equal(results[0].tests.length, 1) - assert.equal(results[0].tests[0].pass, true) - - globalThis.fetch = originalFetch -}) - -test("chains {{response.body.token}} from previous request", async () => { - calls.length = 0 - const responses = [ - new Response(JSON.stringify({ token: "xyz" }), { status: 200, headers: { "content-type": "application/json" } }), - new Response('{"ok":true}', { status: 200, headers: { "content-type": "application/json" } }), - ] - installMockFetch(async () => responses.shift()!) - - const col: Collection = { - id: "c", name: "T", - items: [ - { - id: "r1", name: "login", - method: "POST", - url: "https://api.test/login", - params: [], headers: [], - body: { type: "json", content: "{}" }, - auth: { type: "none" }, - }, - { - id: "r2", name: "me", - method: "GET", - url: "https://api.test/me", - params: [], - headers: [{ id: "h", key: "Authorization", value: "Bearer {{response.body.token}}", active: true }], - body: { type: "none", content: "" }, - auth: { type: "none" }, - }, - ], - } - await runCollection({ collection: col, environmentVariables: {}, iterations: 1 }) - assert.equal(calls.length, 2) - const meReqHeaders = calls[1].init?.headers - const authHeader = (meReqHeaders as Record)?.Authorization - assert.equal(authHeader, "Bearer xyz") - - globalThis.fetch = originalFetch -}) - -test("--bail short-circuits after first failing test", async () => { - calls.length = 0 - installMockFetch(async () => new Response("", { status: 500 })) - - const col: Collection = { - id: "c", name: "T", - items: [ - { id: "r1", name: "a", method: "GET", url: "https://api.test/a", params: [], headers: [], body: { type: "none", content: "" }, auth: { type: "none" }, testScript: "pm.test('ok', () => pm.expect(pm.response.code).toBe(200))" }, - { id: "r2", name: "b", method: "GET", url: "https://api.test/b", params: [], headers: [], body: { type: "none", content: "" }, auth: { type: "none" } }, - ], - } - const results = await runCollection({ collection: col, environmentVariables: {}, iterations: 1, bail: true }) - assert.equal(results.length, 1) // second request skipped - assert.equal(calls.length, 1) - - globalThis.fetch = originalFetch -}) diff --git a/apps/api-runner/src/runner.ts b/apps/api-runner/src/runner.ts deleted file mode 100644 index 6f3533e9..00000000 --- a/apps/api-runner/src/runner.ts +++ /dev/null @@ -1,416 +0,0 @@ -/** - * Headless collection runner. - * - * Mirrors `apps/web/src/lib/runner/runner.ts` semantics: - * - Sequential execution; one iteration per data row, else `iterations`. - * - Pre-request + test scripts run via `node:vm` with `pm.*` API. - * - Env mutations cascade across requests within a run. - * - `{{response.body.x}}` chaining off the previous successful response. - * - Folder-level defaults (headers / scripts) merged before per-request fields. - * - * Deliberate omissions for the CLI v1: - * - No cookie jar (Node native fetch has none; add tough-cookie if needed). - * - No OAuth refresh flow (user supplies bearer token via env). - * - No streaming (proxy-stream lives on the web side). - */ - -import type { - Collection, - CollectionFolder, - CollectionRequest, - KeyValueItem, - RequestRunResult, - TestResult, - ScriptLog, -} from "./types.js" -import { runScript } from "./scripts.js" - -interface RunOpts { - collection: Collection - folderId?: string - iterations: number - dataRows?: Record[] - environmentVariables: Record - bail?: boolean - onProgress?: (r: RequestRunResult) => void - abortSignal?: AbortSignal -} - -interface PreviousResponseSnapshot { - status: number - statusText: string - headers: Record - body: string - time: number - size: number -} - -function walkRequests(items: (CollectionFolder | CollectionRequest)[], out: CollectionRequest[]): void { - for (const it of items) { - if ("type" in it && it.type === "folder") walkRequests(it.items, out) - else out.push(it as CollectionRequest) - } -} - -function findRequestAncestors( - items: (CollectionFolder | CollectionRequest)[], - requestId: string, - trail: CollectionFolder[] = [], -): CollectionFolder[] | null { - for (const item of items) { - if (item.id === requestId) return trail - if ("type" in item && item.type === "folder") { - const hit = findRequestAncestors(item.items, requestId, [...trail, item]) - if (hit) return hit - } - } - return null -} - -function mergeHeaders(chain: CollectionFolder[], requestHeaders: KeyValueItem[]): KeyValueItem[] { - const seen = new Set() - const out: KeyValueItem[] = [] - for (const h of requestHeaders) { if (h.key) seen.add(h.key.toLowerCase()); out.push(h) } - for (const folder of chain) { - for (const h of folder.defaultHeaders ?? []) { - if (h.key && seen.has(h.key.toLowerCase())) continue - if (h.key) seen.add(h.key.toLowerCase()) - out.push(h) - } - } - return out -} - -function inherit(req: CollectionRequest, ancestors: CollectionFolder[]): CollectionRequest { - if (ancestors.length === 0) return req - const preChain = [...ancestors.map((f) => f.preRequestScript), req.preRequestScript] - .map((s) => (s ?? "").trim()).filter(Boolean).join("\n\n") - const testChain = [...ancestors.map((f) => f.testScript), req.testScript] - .map((s) => (s ?? "").trim()).filter(Boolean).join("\n\n") - return { - ...req, - headers: mergeHeaders(ancestors, req.headers), - preRequestScript: preChain || undefined, - testScript: testChain || undefined, - } -} - -function tryParseJson(s: string): unknown | undefined { - try { return JSON.parse(s) } catch { return undefined } -} - -function parseResponsePath(s: string): (string | number)[] { - const out: (string | number)[] = [] - for (const part of s.split(".")) { - const m = part.match(/^([^[]*)((?:\[\d+\])*)$/) - if (!m) { out.push(part); continue } - if (m[1]) out.push(m[1]) - for (const idx of m[2].matchAll(/\[(\d+)\]/g)) out.push(Number(idx[1])) - } - return out -} - -function resolveResponsePath(path: string, response: PreviousResponseSnapshot | null): string | undefined { - if (!response) return undefined - const parts = parseResponsePath(path) - if (parts.length === 0) return undefined - const head = parts[0] - if (head === "status") return String(response.status) - if (head === "statusText") return response.statusText - if (head === "headers") { - if (parts.length === 1) return undefined - const name = String(parts[1]).toLowerCase() - const hit = Object.entries(response.headers).find(([k]) => k.toLowerCase() === name) - return hit?.[1] - } - if (head === "body") { - if (parts.length === 1) return response.body - const parsed = tryParseJson(response.body) - if (parsed === undefined) return undefined - let cur: unknown = parsed - for (const k of parts.slice(1)) { - if (cur == null || typeof cur !== "object") return undefined - cur = (cur as Record)[k as keyof typeof cur] - } - if (cur == null) return undefined - return typeof cur === "object" ? JSON.stringify(cur) : String(cur) - } - return undefined -} - -interface SubstituteArgs { - env: Record - sessionVars: Record - envOverlay: Record - envUnsets: Set - row: Record - previousResponse: PreviousResponseSnapshot | null -} - -function makeSubstitute(args: SubstituteArgs) { - return (text: string): string => { - if (!text) return text - return text.replace(/\{\{(.+?)\}\}/g, (m, k) => { - const key = (k as string).trim() - if (key.startsWith("response.")) { - const resolved = resolveResponsePath(key.slice("response.".length), args.previousResponse) - return resolved ?? m - } - if (key in args.row) return args.row[key] - if (args.envUnsets.has(key)) return m - if (key in args.envOverlay) return args.envOverlay[key] - if (key in args.sessionVars) return args.sessionVars[key] - return args.env[key] ?? m - }) - } -} - -function utf8Btoa(s: string): string { - return Buffer.from(s, "utf-8").toString("base64") -} - -async function executeRequest(args: { - req: CollectionRequest - iteration: number - row: Record - env: Record - sessionVars: Record - envOverlay: Record - envUnsets: Set - previousResponse: PreviousResponseSnapshot | null - signal?: AbortSignal -}): Promise<{ result: RequestRunResult; response: PreviousResponseSnapshot | null }> { - const { req, iteration } = args - const result: RequestRunResult = { - iteration, - requestId: req.id, - requestName: req.name, - method: req.method, - url: req.url, - tests: [], - logs: [], - errors: [], - } - - const substitute = makeSubstitute({ - env: args.env, - sessionVars: args.sessionVars, - envOverlay: args.envOverlay, - envUnsets: args.envUnsets, - row: args.row, - previousResponse: args.previousResponse, - }) - - let workMethod: string = req.method - let workUrl: string = req.url - let workHeaders: Record = {} - req.headers.forEach((h) => { if (h.active && h.key) workHeaders[h.key] = h.value }) - - // Pre-request script. - if (req.preRequestScript && req.preRequestScript.trim()) { - const r = runScript(req.preRequestScript, { - request: { - url: workUrl, method: workMethod, headers: workHeaders, - body: req.body.type === "json" || req.body.type === "text" || req.body.type === "graphql" ? req.body.content : undefined, - }, - environment: { ...args.env, ...args.envOverlay }, - variables: { ...args.sessionVars, ...args.row }, - }) - result.tests.push(...r.tests as TestResult[]) - result.logs.push(...r.logs as ScriptLog[]) - if (!r.ok && r.error) result.errors.push(`pre-request: ${r.error}`) - for (const k of Object.keys(r.environment)) { - if (r.environment[k] !== args.env[k]) args.envOverlay[k] = r.environment[k] - } - for (const k of Object.keys(args.env)) { - if (!(k in r.environment)) args.envUnsets.add(k) - } - Object.assign(args.sessionVars, r.variables) - workUrl = r.request.url - workMethod = (r.request.method || workMethod).toUpperCase() - workHeaders = r.request.headers - } - - let urlObj: URL - try { urlObj = new URL(substitute(workUrl)) } catch (e) { - result.networkError = `Invalid URL: ${(e as Error).message}` - return { result, response: null } - } - req.params.forEach((p) => { - if (p.active && p.key) urlObj.searchParams.append(substitute(p.key), substitute(p.value)) - }) - - const headersObj: Record = {} - for (const [k, v] of Object.entries(workHeaders)) { - headersObj[substitute(k)] = substitute(v) - } - - if (req.auth.type === "bearer" && req.auth.token) { - headersObj["Authorization"] = `Bearer ${substitute(req.auth.token).trim()}` - } else if (req.auth.type === "basic" && req.auth.username && req.auth.password) { - headersObj["Authorization"] = `Basic ${utf8Btoa(`${substitute(req.auth.username).trim()}:${substitute(req.auth.password)}`)}` - } else if (req.auth.type === "api-key" && req.auth.apiKeyKey && req.auth.apiKeyValue) { - const key = substitute(req.auth.apiKeyKey).trim() - const val = substitute(req.auth.apiKeyValue).trim() - if (req.auth.apiKeyLocation === "query") urlObj.searchParams.append(key, val) - else headersObj[key] = val - } - - let body: BodyInit | undefined - const noBody = workMethod === "GET" || workMethod === "HEAD" || req.body.type === "none" - if (!noBody) { - if (req.body.type === "json") { - const sub = substitute(req.body.content) - try { JSON.parse(sub) } catch (e) { - result.networkError = `Invalid JSON body: ${(e as Error).message}` - return { result, response: null } - } - body = sub - headersObj["Content-Type"] = "application/json" - } else if (req.body.type === "x-www-form-urlencoded") { - const sp = new URLSearchParams() - ;(req.body.urlEncoded ?? []).forEach((it) => { - if (it.active && it.key) sp.append(substitute(it.key), substitute(it.value)) - }) - body = sp.toString() - if (!Object.keys(headersObj).some((k) => k.toLowerCase() === "content-type")) { - headersObj["Content-Type"] = "application/x-www-form-urlencoded" - } - } else if (req.body.type === "form-data") { - const form = new FormData() - for (const it of req.body.formData ?? []) { - if (!it.active || !it.key) continue - if (it.valueType === "file") { - if (!it.fileContentBase64) continue - const buf = Buffer.from(it.fileContentBase64, "base64") - form.append( - substitute(it.key), - new Blob([buf], { type: it.fileType || "application/octet-stream" }), - it.fileName || "upload.bin", - ) - } else { - form.append(substitute(it.key), substitute(it.value)) - } - } - body = form - // Let fetch set the multipart Content-Type with its own boundary. - for (const k of Object.keys(headersObj)) { - if (k.toLowerCase() === "content-type") delete headersObj[k] - } - } else if (req.body.type === "graphql") { - const query = substitute(req.body.content) - let variables: unknown - const rawVars = (req.body.graphqlVariables ?? "").trim() - if (rawVars) { - try { variables = JSON.parse(substitute(rawVars)) } catch (e) { - result.networkError = `Invalid GraphQL variables JSON: ${(e as Error).message}` - return { result, response: null } - } - } - body = JSON.stringify(variables !== undefined ? { query, variables } : { query }) - headersObj["Content-Type"] = "application/json" - } else { - body = substitute(req.body.content) - if (!headersObj["Content-Type"]) headersObj["Content-Type"] = "text/plain" - } - } - - const start = Date.now() - let response: Response - try { - response = await fetch(urlObj.toString(), { - method: workMethod, - headers: headersObj, - body, - signal: args.signal, - }) - } catch (e) { - result.networkError = (e as Error).message - return { result, response: null } - } - const elapsed = Date.now() - start - const bodyText = await response.text().catch(() => "") - const respHeaders: Record = {} - response.headers.forEach((v, k) => { respHeaders[k] = v }) - - result.status = response.status - result.statusText = response.statusText - result.time = elapsed - result.size = Buffer.byteLength(bodyText, "utf-8") - - const snapshot: PreviousResponseSnapshot = { - status: response.status, - statusText: response.statusText, - headers: respHeaders, - body: bodyText, - time: elapsed, - size: result.size!, - } - - // Test script. - if (req.testScript && req.testScript.trim()) { - const r = runScript(req.testScript, { - request: { url: urlObj.toString(), method: workMethod, headers: { ...headersObj }, body: typeof body === "string" ? body : undefined }, - response: { - status: response.status, - statusText: response.statusText, - headers: respHeaders, - body: bodyText, - time: elapsed, - }, - environment: { ...args.env, ...args.envOverlay }, - variables: { ...args.sessionVars, ...args.row }, - }) - result.tests.push(...r.tests as TestResult[]) - result.logs.push(...r.logs as ScriptLog[]) - if (!r.ok && r.error) result.errors.push(`test: ${r.error}`) - for (const k of Object.keys(r.environment)) { - if (r.environment[k] !== args.env[k]) args.envOverlay[k] = r.environment[k] - } - Object.assign(args.sessionVars, r.variables) - } - - return { result, response: snapshot } -} - -export async function runCollection(opts: RunOpts): Promise { - const rootItems = opts.collection.items - const requests: CollectionRequest[] = [] - walkRequests(rootItems, requests) - const iterations = opts.dataRows && opts.dataRows.length > 0 - ? opts.dataRows.length - : Math.max(1, opts.iterations || 1) - - const sessionVars: Record = {} - const envOverlay: Record = {} - const envUnsets = new Set() - const out: RequestRunResult[] = [] - let previousResponse: PreviousResponseSnapshot | null = null - - for (let it = 0; it < iterations; it++) { - const row = opts.dataRows?.[it] ?? {} - for (const req of requests) { - if (opts.abortSignal?.aborted) return out - const ancestors = findRequestAncestors(rootItems, req.id) ?? [] - const inherited = ancestors.length > 0 ? inherit(req, ancestors) : req - const { result, response } = await executeRequest({ - req: inherited, - iteration: it, - row, - env: opts.environmentVariables, - sessionVars, - envOverlay, - envUnsets, - previousResponse, - signal: opts.abortSignal, - }) - out.push(result) - opts.onProgress?.(result) - if (response) previousResponse = response - const failedHere = result.networkError || result.tests.some((t) => !t.pass) - if (failedHere && opts.bail) return out - } - } - - return out -} diff --git a/apps/api-runner/src/scripts.ts b/apps/api-runner/src/scripts.ts deleted file mode 100644 index 5d3f363b..00000000 --- a/apps/api-runner/src/scripts.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Headless pre-request / test script runner. Same `pm.*` surface as the web - * worker, executed inside `node:vm.runInContext` with a wall-clock timeout. - * The sandbox has no fetch, no fs, no process — just `pm` + `console`. - */ - -import { Script, createContext } from "node:vm" - -export interface ScriptContext { - request: { url: string; method: string; headers: Record; body?: string } - response?: { - status: number - statusText: string - headers: Record - body: string - time: number - } - environment: Record - variables: Record -} - -export interface ScriptResult { - ok: boolean - error?: string - tests: { name: string; pass: boolean; error?: string }[] - logs: { level: "log" | "warn" | "error"; args: string[] }[] - environment: Record - variables: Record - request: ScriptContext["request"] -} - -const SCRIPT_TIMEOUT_MS = 3_000 - -function safeStringify(v: unknown): string { - if (typeof v === "string") return v - try { return JSON.stringify(v) } catch { return String(v) } -} - -function makeExpect(actual: unknown) { - const fail = (m: string) => { throw new Error(m) } - return { - toBe(e: unknown) { if (actual !== e) fail(`expected ${safeStringify(actual)} to be ${safeStringify(e)}`) }, - toEqual(e: unknown) { if (JSON.stringify(actual) !== JSON.stringify(e)) fail(`expected ${safeStringify(actual)} to equal ${safeStringify(e)}`) }, - toMatch(r: unknown) { - const ok = r instanceof RegExp ? r.test(String(actual)) : String(actual).includes(String(r)) - if (!ok) fail(`expected ${safeStringify(actual)} to match ${safeStringify(r)}`) - }, - toContain(s: unknown) { - const ok = Array.isArray(actual) - ? (actual as unknown[]).includes(s) - : String(actual).includes(String(s)) - if (!ok) fail(`expected ${safeStringify(actual)} to contain ${safeStringify(s)}`) - }, - toBeGreaterThan(n: number) { if (!(Number(actual) > n)) fail(`expected ${safeStringify(actual)} > ${n}`) }, - toBeLessThan(n: number) { if (!(Number(actual) < n)) fail(`expected ${safeStringify(actual)} < ${n}`) }, - toHaveProperty(k: string) { - if (!actual || typeof actual !== "object" || !(k in (actual as object))) fail(`expected object to have property "${k}"`) - }, - toBeTruthy() { if (!actual) fail(`expected ${safeStringify(actual)} to be truthy`) }, - toBeFalsy() { if (actual) fail(`expected ${safeStringify(actual)} to be falsy`) }, - } -} - -export function runScript(script: string, ctx: ScriptContext): ScriptResult { - if (!script || !script.trim()) { - return { - ok: true, - tests: [], - logs: [], - environment: ctx.environment, - variables: ctx.variables, - request: ctx.request, - } - } - - const env = { ...ctx.environment } - const vars = { ...ctx.variables } - const tests: ScriptResult["tests"] = [] - const logs: ScriptResult["logs"] = [] - const request = { - url: ctx.request.url, - method: ctx.request.method, - headers: { ...ctx.request.headers }, - body: ctx.request.body, - } - - const pm = { - environment: { - get(k: string) { return env[k] }, - set(k: string, v: unknown) { env[String(k)] = safeStringify(v) }, - unset(k: string) { delete env[String(k)] }, - has(k: string) { return k in env }, - toObject() { return { ...env } }, - }, - variables: { - get(k: string) { return vars[k] ?? env[k] }, - set(k: string, v: unknown) { vars[String(k)] = safeStringify(v) }, - has(k: string) { return k in vars || k in env }, - }, - request: { - get url() { return request.url }, - set url(v: string) { request.url = String(v) }, - get method() { return request.method }, - set method(v: string) { request.method = String(v).toUpperCase() }, - headers: { - get(name: string) { - const k = Object.keys(request.headers).find((h) => h.toLowerCase() === String(name).toLowerCase()) - return k ? request.headers[k] : undefined - }, - add(name: string, v: unknown) { request.headers[String(name)] = safeStringify(v) }, - remove(name: string) { - const k = Object.keys(request.headers).find((h) => h.toLowerCase() === String(name).toLowerCase()) - if (k) delete request.headers[k] - }, - toObject() { return { ...request.headers } }, - }, - get body() { return request.body }, - set body(v: string | undefined) { request.body = v == null ? undefined : String(v) }, - }, - response: ctx.response - ? { - code: ctx.response.status, - status: ctx.response.statusText, - responseTime: ctx.response.time, - headers: { - get(name: string) { - const lower = String(name).toLowerCase() - const k = Object.keys(ctx.response!.headers).find((h) => h.toLowerCase() === lower) - return k ? ctx.response!.headers[k] : undefined - }, - toObject() { return { ...ctx.response!.headers } }, - }, - text: () => ctx.response!.body, - json: () => { - try { return JSON.parse(ctx.response!.body) } - catch (e) { throw new Error(`Response body is not valid JSON: ${(e as Error).message}`) } - }, - } - : undefined, - test(name: string, fn: () => void) { - try { fn(); tests.push({ name: String(name), pass: true }) } - catch (e) { tests.push({ name: String(name), pass: false, error: (e as Error).message }) } - }, - expect: makeExpect, - sendRequest() { throw new Error("pm.sendRequest is not supported in the CLI yet") }, - } - - const sandboxConsole = { - log: (...args: unknown[]) => { logs.push({ level: "log", args: args.map(safeStringify) }) }, - warn: (...args: unknown[]) => { logs.push({ level: "warn", args: args.map(safeStringify) }) }, - error: (...args: unknown[]) => { logs.push({ level: "error", args: args.map(safeStringify) }) }, - } - - const sandbox = createContext({ pm, console: sandboxConsole }) - try { - new Script(`"use strict";\n${script}`).runInContext(sandbox, { timeout: SCRIPT_TIMEOUT_MS }) - } catch (e) { - return { ok: false, error: (e as Error).message, tests, logs, environment: env, variables: vars, request } - } - return { ok: true, tests, logs, environment: env, variables: vars, request } -} diff --git a/apps/api-runner/src/types.ts b/apps/api-runner/src/types.ts deleted file mode 100644 index 3a60fac1..00000000 --- a/apps/api-runner/src/types.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Self-contained types for the CLI — deliberately decoupled from the web app so - * the package can publish standalone. The shape matches what `lib/import/postman.ts` - * emits, so a Postman v2.1 export round-trips into here. - */ - -export type RequestMethod = - | "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS" - -export interface KeyValueItem { - id: string - key: string - value: string - active: boolean -} - -export interface FormDataItem { - id: string - key: string - value: string - active: boolean - valueType: "text" | "file" - fileName?: string - fileType?: string - fileContentBase64?: string -} - -export interface RequestBody { - type: "json" | "text" | "none" | "form-data" | "x-www-form-urlencoded" | "graphql" - content: string - formData?: FormDataItem[] - urlEncoded?: KeyValueItem[] - graphqlVariables?: string -} - -export interface RequestAuth { - type: "none" | "bearer" | "basic" | "api-key" - token?: string - username?: string - password?: string - apiKeyKey?: string - apiKeyValue?: string - apiKeyLocation?: "header" | "query" -} - -export interface CollectionRequest { - id: string - name: string - method: RequestMethod - url: string - params: KeyValueItem[] - headers: KeyValueItem[] - body: RequestBody - auth: RequestAuth - preRequestScript?: string - testScript?: string -} - -export interface CollectionFolder { - id: string - name: string - type: "folder" - items: (CollectionFolder | CollectionRequest)[] - defaultHeaders?: KeyValueItem[] - preRequestScript?: string - testScript?: string -} - -export interface Collection { - id: string - name: string - items: (CollectionFolder | CollectionRequest)[] -} - -export interface TestResult { - name: string - pass: boolean - error?: string -} - -export interface ScriptLog { - level: "log" | "warn" | "error" - args: string[] -} - -export interface RequestRunResult { - iteration: number - requestId: string - requestName: string - method: string - url: string - status?: number - statusText?: string - time?: number - size?: number - tests: TestResult[] - logs: ScriptLog[] - errors: string[] - networkError?: string -} diff --git a/apps/api-runner/tsconfig.json b/apps/api-runner/tsconfig.json deleted file mode 100644 index ee39280f..00000000 --- a/apps/api-runner/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "moduleResolution": "Bundler", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitOverride": true, - "noFallthroughCasesInSwitch": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "declaration": true, - "sourceMap": true, - "outDir": "dist", - "rootDir": "src", - "lib": ["ES2022", "DOM"], - "types": ["node"] - }, - "include": ["src/**/*"], - "exclude": ["dist", "node_modules"] -} diff --git a/apps/backend/app/api/router.py b/apps/backend/app/api/router.py index 47456993..b3e5d713 100644 --- a/apps/backend/app/api/router.py +++ b/apps/backend/app/api/router.py @@ -3,56 +3,8 @@ from app.api.routes.auth.api import router as auth_router from app.api.routes.auth.passkey.api import router as auth_passkey_router from app.api.routes.health import router as health_router -from app.api.routes.bookmarks.api import router as bookmarks_router -from app.api.routes.tasks.api import router as tasks_router -from app.api.routes.passwords.api import router as passwords_router -from app.api.routes.environment_manager.api import router as environment_manager_router -from app.api.routes.api_key_vault.api import router as api_key_vault_router -from app.api.routes.notes.api import router as notes_router -from app.api.routes.nosql.api import router as nosql_router -from app.api.routes.user_preferences.api import router as user_preferences_router -from app.api.routes.api_client.api import router as api_client_router -from app.api.routes.json_formatter.api import router as json_formatter_router -from app.api.routes.analytics.api import router as analytics_router -from app.api.routes.code_snippets.api import router as code_snippets_router -from app.api.routes.sql_client.api import router as sql_client_router -from app.api.routes.game_scores.api import router as game_scores_router -from app.api.routes.users.api import router as users_router -from app.api.routes.s3_drive.api import router as s3_drive_router -from app.api.routes.feedback.api import router as feedback_router -from app.api.routes.redis_commander.api import router as redis_commander_router -from app.api.routes.url_shortener.api import router as url_shortener_router -from app.api.routes.dns_lookup.api import router as dns_lookup_router -from app.api.routes.whois.api import router as whois_router -from app.api.routes.webhook_tester.api import router as webhook_tester_router -from app.api.routes.audit_log.api import router as audit_log_router -from app.api.routes.workspaces.api import router as workspaces_router api_router = APIRouter() api_router.include_router(health_router) api_router.include_router(auth_router) api_router.include_router(auth_passkey_router) -api_router.include_router(tasks_router) -api_router.include_router(bookmarks_router) -api_router.include_router(passwords_router) -api_router.include_router(environment_manager_router) -api_router.include_router(api_key_vault_router) -api_router.include_router(notes_router) -api_router.include_router(nosql_router) -api_router.include_router(user_preferences_router) -api_router.include_router(api_client_router) -api_router.include_router(json_formatter_router) -api_router.include_router(analytics_router) -api_router.include_router(code_snippets_router) -api_router.include_router(sql_client_router) -api_router.include_router(game_scores_router) -api_router.include_router(users_router) -api_router.include_router(s3_drive_router) -api_router.include_router(feedback_router) -api_router.include_router(redis_commander_router) -api_router.include_router(url_shortener_router) -api_router.include_router(dns_lookup_router) -api_router.include_router(whois_router) -api_router.include_router(webhook_tester_router) -api_router.include_router(audit_log_router) -api_router.include_router(workspaces_router) diff --git a/apps/backend/app/api/routes/analytics/__init__.py b/apps/backend/app/api/routes/analytics/__init__.py deleted file mode 100644 index 5c001d4e..00000000 --- a/apps/backend/app/api/routes/analytics/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Analytics routes (dashboard aggregates).""" diff --git a/apps/backend/app/api/routes/analytics/api.py b/apps/backend/app/api/routes/analytics/api.py deleted file mode 100644 index b142a16a..00000000 --- a/apps/backend/app/api/routes/analytics/api.py +++ /dev/null @@ -1,29 +0,0 @@ -from fastapi import APIRouter, Depends, Query - -from app.api.routes.analytics import services as analytics_svc -from app.api.routes.auth.services import get_current_uid -from app.api.routes.workspaces.middleware import WorkspaceContext, get_workspace_ctx - -router = APIRouter(prefix="/analytics", tags=["analytics"]) - - -@router.get("/summary", summary="Dashboard analytics counts") -async def dashboard_summary(ctx: WorkspaceContext = Depends(get_workspace_ctx)): - return await analytics_svc.get_dashboard_analytics(ctx) - - -@router.get("/top-tools", summary="Top tools by usage (global, cached 5 min)") -async def top_tools( - days: int = Query(default=7, ge=1, le=90), - limit: int = Query(default=10, ge=1, le=50), - _uid: str = Depends(get_current_uid), -): - return await analytics_svc.get_top_tools(days=days, limit=limit) - - -@router.get("/activity-buckets", summary="Daily activity counts (global, cached 5 min)") -async def activity_buckets( - days: int = Query(default=7, ge=1, le=90), - _uid: str = Depends(get_current_uid), -): - return await analytics_svc.get_activity_buckets(days=days) diff --git a/apps/backend/app/api/routes/analytics/schema.py b/apps/backend/app/api/routes/analytics/schema.py deleted file mode 100644 index d1439cee..00000000 --- a/apps/backend/app/api/routes/analytics/schema.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Dashboard analytics aggregates — read-only counts per authenticated user.""" - -from pydantic import BaseModel, Field - -from app.api.routes.tasks.schema import TaskStatsOut - - -class DashboardAnalyticsOut(BaseModel): - passwordEntries: int = Field(ge=0) - bookmarks: int = Field(ge=0) - bookmarkFolders: int = Field(ge=0) - tasks: TaskStatsOut - projects: int = Field(ge=0) - nosqlConnections: int = Field(ge=0) - notes: int = Field(ge=0) - apiClientCollections: int = Field(ge=0) - apiClientEnvironments: int = Field(ge=0) - apiClientHistoryEntries: int = Field(ge=0) - jsonFormatterDocuments: int = Field(ge=0) - codeSnippets: int = Field(ge=0) diff --git a/apps/backend/app/api/routes/analytics/services.py b/apps/backend/app/api/routes/analytics/services.py deleted file mode 100644 index 05d502ee..00000000 --- a/apps/backend/app/api/routes/analytics/services.py +++ /dev/null @@ -1,107 +0,0 @@ -import asyncio -import datetime - -from app.api.routes.tasks import services as task_svc -from app.core.cache import cached -from app.utils.collection_name import ( - API_CLIENT_COLLECTIONS, - API_CLIENT_ENVIRONMENTS, - API_CLIENT_HISTORY, - AUDIT_LOG, - BOOKMARK_FOLDERS, - BOOKMARKS, - CODE_SNIPPETS, - JSON_FORMATTER_DOCUMENTS, - NOTES, - NOSQL_CONNECTIONS, - PASSWORD_ENTRIES, - PROJECTS, -) -from app.api.routes.analytics.schema import DashboardAnalyticsOut -from app.api.routes.workspaces.middleware import WorkspaceContext, apply_legacy_or_filter -from app.database import db_manager - - -@cached(ns="analytics_aggregate", ttl=300, scope="global", strategy="xfetch") -async def get_top_tools(*, days: int = 7, limit: int = 10) -> list[dict]: - """Return the top N tools by event count across all users over the last ``days`` days.""" - since = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=days) - pipeline = [ - {"$match": {"module": {"$ne": None}, "ts": {"$gte": since.timestamp() * 1000}}}, - {"$group": {"_id": "$module", "count": {"$sum": 1}}}, - {"$sort": {"count": -1}}, - {"$limit": limit}, - {"$project": {"_id": 0, "tool": "$_id", "count": 1}}, - ] - return await db_manager.aggregate(AUDIT_LOG, pipeline) - - -@cached(ns="analytics_aggregate", ttl=300, scope="global", strategy="xfetch") -async def get_activity_buckets(*, days: int = 7) -> list[dict]: - """Return daily event counts across all users over the last ``days`` days.""" - since = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=days) - pipeline = [ - {"$match": {"ts": {"$gte": since.timestamp() * 1000}}}, - { - "$group": { - "_id": { - "$dateToString": { - "format": "%Y-%m-%d", - "date": {"$toDate": "$ts"}, - "timezone": "UTC", - } - }, - "count": {"$sum": 1}, - } - }, - {"$sort": {"_id": 1}}, - {"$project": {"_id": 0, "date": "$_id", "count": 1}}, - ] - return await db_manager.aggregate(AUDIT_LOG, pipeline) - - -async def get_dashboard_analytics(ctx: WorkspaceContext) -> DashboardAnalyticsOut: - flt = lambda base=None: apply_legacy_or_filter(ctx, base or {}, user_field="created_by") - nosql_filter = flt({"encryptedData": {"$exists": True}, "iv": {"$exists": True}}) - ( - task_stats, - password_entries, - bookmarks, - bookmark_folders, - projects, - nosql_connections, - notes, - api_collections, - api_envs, - api_history, - json_docs, - code_snippets, - ) = await asyncio.gather( - task_svc.get_task_stats(ctx=ctx), - db_manager.count_documents(PASSWORD_ENTRIES, flt()), - db_manager.count_documents(BOOKMARKS, flt()), - db_manager.count_documents(BOOKMARK_FOLDERS, flt()), - db_manager.count_documents(PROJECTS, flt()), - db_manager.count_documents(NOSQL_CONNECTIONS, nosql_filter), - db_manager.count_documents(NOTES, flt()), - db_manager.count_documents(API_CLIENT_COLLECTIONS, flt()), - db_manager.count_documents(API_CLIENT_ENVIRONMENTS, flt()), - db_manager.count_documents(API_CLIENT_HISTORY, flt()), - db_manager.count_documents(JSON_FORMATTER_DOCUMENTS, flt()), - db_manager.count_documents(CODE_SNIPPETS, flt()), - ) - - return DashboardAnalyticsOut( - passwordEntries=password_entries, - bookmarks=bookmarks, - bookmarkFolders=bookmark_folders, - tasks=task_stats, - projects=projects, - nosqlConnections=nosql_connections, - notes=notes, - apiClientCollections=api_collections, - apiClientEnvironments=api_envs, - apiClientHistoryEntries=api_history, - jsonFormatterDocuments=json_docs, - codeSnippets=code_snippets, - ) diff --git a/apps/backend/app/api/routes/api_client/__init__.py b/apps/backend/app/api/routes/api_client/__init__.py deleted file mode 100644 index fb0a2f80..00000000 --- a/apps/backend/app/api/routes/api_client/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API route modules.""" diff --git a/apps/backend/app/api/routes/api_client/api.py b/apps/backend/app/api/routes/api_client/api.py deleted file mode 100644 index a5695a97..00000000 --- a/apps/backend/app/api/routes/api_client/api.py +++ /dev/null @@ -1,212 +0,0 @@ -from fastapi import APIRouter, BackgroundTasks, Depends, Query - -from app.api.routes.api_client import collections_delta -from app.api.routes.api_client import services as api_client_svc -from app.api.routes.api_client.schema import ( - HISTORY_MAX_ITEMS, - ApiClientCollectionCreate, - ApiClientCollectionOut, - ApiClientCollectionUpdate, - ApiClientEnvironmentCreate, - ApiClientEnvironmentOut, - ApiClientEnvironmentUpdate, - ApiClientHistoryCreate, - ApiClientHistoryOut, - ApiClientPublicMockOut, - ApiClientPublicMockPublish, - ApiClientWorkspaceCreate, - ApiClientWorkspaceOut, - ApiClientWorkspaceUpdate, -) -from app.api.routes.auth.services import get_current_uid -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -router = APIRouter(prefix="/api-client", tags=["api-client"]) -router.include_router(collections_delta.router) - - -@router.get("/collections", response_model=list[ApiClientCollectionOut], summary="List API client collections") -async def list_collections(ctx: WorkspaceContext = Depends(require_permission("api-client", "read"))) -> list[ApiClientCollectionOut]: - return await api_client_svc.list_collections(ctx=ctx) - - -@router.post("/collections", response_model=ApiClientCollectionOut, summary="Create API client collection") -async def create_collection( - body: ApiClientCollectionCreate, - ctx: WorkspaceContext = Depends(require_permission("api-client", "write")), -) -> ApiClientCollectionOut: - return await api_client_svc.create_collection(ctx, body) - - -@router.patch( - "/collections/{collection_id}", - response_model=ApiClientCollectionOut, - summary="Update API client collection (partial)", -) -async def patch_collection( - collection_id: str, - body: ApiClientCollectionUpdate, - ctx: WorkspaceContext = Depends(require_permission("api-client", "write")), -) -> ApiClientCollectionOut: - return await api_client_svc.patch_collection(ctx, collection_id, body) - - -@router.delete("/collections/{collection_id}", status_code=204, summary="Delete API client collection") -async def delete_collection(collection_id: str, ctx: WorkspaceContext = Depends(require_permission("api-client", "delete"))) -> None: - await api_client_svc.delete_collection(ctx, collection_id) - - -@router.get("/environments", response_model=list[ApiClientEnvironmentOut], summary="List API client environments") -async def list_environments(ctx: WorkspaceContext = Depends(require_permission("api-client", "read"))) -> list[ApiClientEnvironmentOut]: - return await api_client_svc.list_environments(ctx=ctx) - - -@router.post("/environments", response_model=ApiClientEnvironmentOut, summary="Create API client environment") -async def create_environment( - body: ApiClientEnvironmentCreate, - ctx: WorkspaceContext = Depends(require_permission("api-client", "write")), -) -> ApiClientEnvironmentOut: - return await api_client_svc.create_environment(ctx, body) - - -@router.patch( - "/environments/{environment_id}", - response_model=ApiClientEnvironmentOut, - summary="Update API client environment (partial)", -) -async def patch_environment( - environment_id: str, - body: ApiClientEnvironmentUpdate, - ctx: WorkspaceContext = Depends(require_permission("api-client", "write")), -) -> ApiClientEnvironmentOut: - return await api_client_svc.patch_environment(ctx, environment_id, body) - - -@router.delete("/environments/{environment_id}", status_code=204, summary="Delete API client environment") -async def delete_environment(environment_id: str, ctx: WorkspaceContext = Depends(require_permission("api-client", "delete"))) -> None: - await api_client_svc.delete_environment(ctx, environment_id) - - -@router.get("/history", response_model=list[ApiClientHistoryOut], summary="List API client request history") -async def list_history( - ctx: WorkspaceContext = Depends(require_permission("api-client", "read")), - limit: int = Query(default=HISTORY_MAX_ITEMS, ge=1, le=HISTORY_MAX_ITEMS), -) -> list[ApiClientHistoryOut]: - return await api_client_svc.list_history(ctx=ctx, limit=limit) - - -@router.post("/history", response_model=ApiClientHistoryOut, summary="Append API client history entry") -async def create_history( - body: ApiClientHistoryCreate, - background_tasks: BackgroundTasks, - ctx: WorkspaceContext = Depends(require_permission("api-client", "write")), -) -> ApiClientHistoryOut: - entry = await api_client_svc.create_history(ctx, body) - background_tasks.add_task(api_client_svc.trim_history, ctx) - return entry - - -@router.delete("/history/clear", status_code=204, summary="Clear all API client history") -async def clear_history(ctx: WorkspaceContext = Depends(require_permission("api-client", "admin"))) -> None: - await api_client_svc.clear_history(ctx) - - -@router.delete("/history/{entry_id}", status_code=204, summary="Delete one history entry") -async def delete_history_entry(entry_id: str, ctx: WorkspaceContext = Depends(require_permission("api-client", "delete"))) -> None: - await api_client_svc.delete_history_entry(ctx, entry_id) - - -# ── Public mocks ───────────────────────────────────────────────────────────── -# Public mocks retain uid-based ownership management (not workspace-scoped). -# See services.py for rationale. - - -@router.get( - "/public-mocks", - response_model=list[ApiClientPublicMockOut], - summary="List the caller's published public mocks", -) -async def list_public_mocks(uid: str = Depends(get_current_uid)) -> list[ApiClientPublicMockOut]: - return await api_client_svc.list_public_mocks(uid=uid) - - -@router.post( - "/public-mocks", - response_model=ApiClientPublicMockOut, - summary="Publish a snapshot of a collection as an anonymously-readable mock", -) -async def publish_public_mock( - body: ApiClientPublicMockPublish, - uid: str = Depends(get_current_uid), -) -> ApiClientPublicMockOut: - return await api_client_svc.publish_public_mock(uid, body) - - -@router.delete( - "/public-mocks/{mock_id}", - status_code=204, - summary="Unpublish a public mock", -) -async def delete_public_mock(mock_id: str, uid: str = Depends(get_current_uid)) -> None: - await api_client_svc.delete_public_mock(uid, mock_id) - - -@router.get( - "/public-mock/{mock_id}", - response_model=ApiClientPublicMockOut, - summary="Fetch a public mock by id — no auth required", -) -async def get_public_mock_anonymous(mock_id: str) -> ApiClientPublicMockOut: - mock = await api_client_svc.get_public_mock_anonymous(mock_id) - if not mock: - from fastapi import HTTPException, status - - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Public mock not found.") - return mock - - -# ── Workspaces ─────────────────────────────────────────────────────────────── - - -@router.get( - "/workspaces", - response_model=list[ApiClientWorkspaceOut], - summary="List the caller's workspaces", -) -async def list_workspaces(ctx: WorkspaceContext = Depends(require_permission("api-client", "read"))) -> list[ApiClientWorkspaceOut]: - return await api_client_svc.list_workspaces(ctx=ctx) - - -@router.post( - "/workspaces", - response_model=ApiClientWorkspaceOut, - summary="Create a workspace", -) -async def create_workspace( - body: ApiClientWorkspaceCreate, - ctx: WorkspaceContext = Depends(require_permission("api-client", "write")), -) -> ApiClientWorkspaceOut: - return await api_client_svc.create_workspace(ctx, body) - - -@router.patch( - "/workspaces/{workspace_id}", - response_model=ApiClientWorkspaceOut, - summary="Rename a workspace", -) -async def patch_workspace( - workspace_id: str, - body: ApiClientWorkspaceUpdate, - ctx: WorkspaceContext = Depends(require_permission("api-client", "write")), -) -> ApiClientWorkspaceOut: - return await api_client_svc.patch_workspace(ctx, workspace_id, body) - - -@router.delete( - "/workspaces/{workspace_id}", - status_code=204, - summary="Delete a workspace (collections reset to default)", -) -async def delete_workspace(workspace_id: str, ctx: WorkspaceContext = Depends(require_permission("api-client", "delete"))) -> None: - await api_client_svc.delete_workspace(ctx, workspace_id) diff --git a/apps/backend/app/api/routes/api_client/collections_delta.py b/apps/backend/app/api/routes/api_client/collections_delta.py deleted file mode 100644 index 3df8fd88..00000000 --- a/apps/backend/app/api/routes/api_client/collections_delta.py +++ /dev/null @@ -1,280 +0,0 @@ -"""Service + route for the collection-items delta endpoint. - -POST /api-client/collections/{collection_id}/items:apply-delta - -Applies a list of ops (add / update / delete / move) to a collection's items -tree in-memory, then persists the result in a single atomic update_one call. -Cache is invalidated via bump_version exactly as the existing patch_collection -service does. -""" -from __future__ import annotations - -from typing import Any - -from bson import ObjectId -from bson.errors import InvalidId -from fastapi import APIRouter, Depends, HTTPException, status -from pymongo import ReturnDocument -from pymongo.errors import PyMongoError - -from app.api.routes.api_client.schema import ( - AddItemOp, - ApiClientCollectionOut, - ApplyDeltaRequest, - ApplyDeltaResponse, - DeleteItemOp, - MoveItemOp, - Op, - UpdateItemOp, -) -from app.api.routes.workspaces.middleware import WorkspaceContext, apply_workspace_filter -from app.api.routes.workspaces.rbac import require_permission -from app.database import db_manager -from app.utils.collection_name import API_CLIENT_COLLECTIONS - -router = APIRouter() - - -def _parse_oid(raw: str, *, kind: str) -> ObjectId: - try: - return ObjectId(raw) - except InvalidId as exc: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid {kind} id.", - ) from exc - - -def _collection_to_out(doc: dict[str, Any]) -> ApiClientCollectionOut: - oid = doc.get("_id") - return ApiClientCollectionOut( - id=str(oid) if oid is not None else "", - name=doc.get("name", ""), - items=list(doc.get("items") or []), - ) - - -# ── In-memory tree walkers ──────────────────────────────────────────────────── - -def _apply_add( - items: list[dict[str, Any]], - parent_id: str, - collection_id: str, - new_item: dict[str, Any], - position: int | None, -) -> list[dict[str, Any]]: - """Insert new_item under parent_id. parent_id may be the collection root.""" - if parent_id == collection_id: - # Insert at root level - if position is None or position >= len(items): - return items + [new_item] - result = list(items) - result.insert(position, new_item) - return result - - return _add_in_children(items, parent_id, new_item, position) - - -def _add_in_children( - items: list[dict[str, Any]], - parent_id: str, - new_item: dict[str, Any], - position: int | None, -) -> list[dict[str, Any]]: - result = [] - for item in items: - if item.get("id") == parent_id and item.get("type") == "folder": - children = list(item.get("items") or []) - if position is None or position >= len(children): - children = children + [new_item] - else: - children.insert(position, new_item) - result.append({**item, "items": children}) - elif item.get("type") == "folder": - result.append({**item, "items": _add_in_children(item.get("items") or [], parent_id, new_item, position)}) - else: - result.append(item) - return result - - -def _apply_update( - items: list[dict[str, Any]], - item_id: str, - patch: dict[str, Any], -) -> tuple[list[dict[str, Any]], bool]: - """Recursively apply patch to the item with item_id. Returns (new_items, found).""" - result = [] - found = False - for item in items: - if item.get("id") == item_id: - result.append({**item, **patch}) - found = True - elif item.get("type") == "folder": - new_children, child_found = _apply_update(item.get("items") or [], item_id, patch) - result.append({**item, "items": new_children}) - if child_found: - found = True - else: - result.append(item) - return result, found - - -def _apply_delete( - items: list[dict[str, Any]], - item_id: str, -) -> tuple[list[dict[str, Any]], bool]: - """Recursively remove item_id. Returns (new_items, found).""" - new_items = [] - found = False - for item in items: - if item.get("id") == item_id: - found = True - # skip (delete) - elif item.get("type") == "folder": - new_children, child_found = _apply_delete(item.get("items") or [], item_id) - new_items.append({**item, "items": new_children}) - if child_found: - found = True - else: - new_items.append(item) - return new_items, found - - -def _extract_item( - items: list[dict[str, Any]], - item_id: str, -) -> tuple[list[dict[str, Any]], dict[str, Any] | None]: - """Remove and return item_id from the tree.""" - new_items = [] - extracted: dict[str, Any] | None = None - for item in items: - if item.get("id") == item_id: - extracted = item - elif item.get("type") == "folder": - new_children, child_extracted = _extract_item(item.get("items") or [], item_id) - new_items.append({**item, "items": new_children}) - if child_extracted is not None: - extracted = child_extracted - else: - new_items.append(item) - return new_items, extracted - - -def _id_exists_in_tree(items: list[dict[str, Any]], node_id: str) -> bool: - """Return True if node_id appears anywhere in the tree (any level).""" - for item in items: - if item.get("id") == node_id: - return True - if item.get("type") == "folder": - if _id_exists_in_tree(item.get("items") or [], node_id): - return True - return False - - -def _apply_move( - items: list[dict[str, Any]], - item_id: str, - new_parent_id: str, - new_index: int, - collection_id: str, -) -> tuple[list[dict[str, Any]], bool]: - """Move item_id to new_parent_id at new_index. Returns (new_items, success).""" - # Validate that new_parent_id exists (root or a folder in the tree). - if new_parent_id != collection_id and not _id_exists_in_tree(items, new_parent_id): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="move: new_parent_id not found", - ) - - # Step 1: extract the item - items_without, target = _extract_item(items, item_id) - if target is None: - return items, False - - # Step 2: insert at new parent - new_items = _apply_add(items_without, new_parent_id, collection_id, target, new_index) - return new_items, True - - -# ── Service ─────────────────────────────────────────────────────────────────── - -async def apply_collection_delta( - ctx: WorkspaceContext, - collection_id: str, - ops: list[Op], -) -> ApiClientCollectionOut: - oid = _parse_oid(collection_id, kind="collection") - - # Fetch + ownership check BEFORE any mutation - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await db_manager.find_one(API_CLIENT_COLLECTIONS, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Collection not found.") - - items: list[dict[str, Any]] = list(doc.get("items") or []) - - # Apply all ops in-memory (atomic within a single request) - for op in ops: - if isinstance(op, AddItemOp): - items = _apply_add(items, op.parent_id, collection_id, op.item, op.position) - - elif isinstance(op, UpdateItemOp): - items, found = _apply_update(items, op.item_id, op.patch) - if not found: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Item {op.item_id!r} not found.", - ) - - elif isinstance(op, DeleteItemOp): - items, found = _apply_delete(items, op.item_id) - if not found: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Item {op.item_id!r} not found.", - ) - - elif isinstance(op, MoveItemOp): - items, ok = _apply_move(items, op.item_id, op.new_parent_id, op.new_index, collection_id) - if not ok: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Item {op.item_id!r} not found.", - ) - - # Single atomic update - try: - updated_doc = await db_manager.find_one_and_update( - API_CLIENT_COLLECTIONS, - flt, - {"$set": {"items": items}}, - return_document=ReturnDocument.AFTER, - ) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to update collection.", - ) from exc - - if not updated_doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Collection not found.") - - # ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot - - return _collection_to_out(updated_doc) - - -# ── Route ───────────────────────────────────────────────────────────────────── - -@router.post( - "/collections/{collection_id}/items:apply-delta", - response_model=ApplyDeltaResponse, - summary="Apply delta ops to a collection's items tree", -) -async def apply_delta( - collection_id: str, - body: ApplyDeltaRequest, - ctx: WorkspaceContext = Depends(require_permission("api-client", "write")), -) -> ApplyDeltaResponse: - collection = await apply_collection_delta(ctx, collection_id, body.ops) - return ApplyDeltaResponse(collection=collection) diff --git a/apps/backend/app/api/routes/api_client/schema.py b/apps/backend/app/api/routes/api_client/schema.py deleted file mode 100644 index 2432b7fe..00000000 --- a/apps/backend/app/api/routes/api_client/schema.py +++ /dev/null @@ -1,164 +0,0 @@ -from typing import Annotated, Any, Literal - -from pydantic import BaseModel, ConfigDict, Field - - -class ApiClientCollectionBase(BaseModel): - name: str = Field(min_length=1) - items: list[dict[str, Any]] = Field(default_factory=list) - - -class ApiClientCollectionCreate(BaseModel): - name: str = Field(min_length=1) - - -class ApiClientCollectionUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - name: str | None = Field(default=None, min_length=1) - items: list[dict[str, Any]] | None = None - workspace: str | None = None - - -class ApiClientCollectionOut(ApiClientCollectionBase): - model_config = ConfigDict(extra="allow") - - id: str - workspace: str | None = None - - -class ApiClientEnvironmentBase(BaseModel): - name: str = Field(min_length=1) - variables: list[dict[str, Any]] = Field(default_factory=list) - - -class ApiClientEnvironmentCreate(BaseModel): - name: str = Field(min_length=1) - - -class ApiClientEnvironmentUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - name: str | None = Field(default=None, min_length=1) - variables: list[dict[str, Any]] | None = None - - -class ApiClientEnvironmentOut(ApiClientEnvironmentBase): - model_config = ConfigDict(extra="allow") - - id: str - - -HISTORY_MAX_ITEMS = 100 - - -# ── Delta ops ──────────────────────────────────────────────────────────────── - -class AddItemOp(BaseModel): - type: Literal["add"] - parent_id: str - item: dict[str, Any] - position: int | None = None - - -class UpdateItemOp(BaseModel): - type: Literal["update"] - item_id: str - patch: dict[str, Any] - - -class DeleteItemOp(BaseModel): - type: Literal["delete"] - item_id: str - - -class MoveItemOp(BaseModel): - type: Literal["move"] - item_id: str - new_parent_id: str - new_index: int - - -Op = Annotated[ - AddItemOp | UpdateItemOp | DeleteItemOp | MoveItemOp, - Field(discriminator="type"), -] - - -class ApplyDeltaRequest(BaseModel): - ops: list[Op] - - -class ApplyDeltaResponse(BaseModel): - collection: ApiClientCollectionOut - - -class ApiClientHistoryCreate(BaseModel): - """One sent request snapshot (matches web HistoryRequest minus id).""" - - model_config = ConfigDict(extra="ignore") - - method: str - url: str - params: list[dict[str, Any]] = Field(default_factory=list) - headers: list[dict[str, Any]] = Field(default_factory=list) - body: dict[str, Any] = Field(default_factory=dict) - auth: dict[str, Any] = Field(default_factory=dict) - name: str = Field(min_length=1) - status: int | None = None - timestamp: int | None = None - - -class ApiClientHistoryOut(BaseModel): - model_config = ConfigDict(extra="allow") - - id: str - method: str - url: str - params: list[dict[str, Any]] - headers: list[dict[str, Any]] - body: dict[str, Any] - auth: dict[str, Any] - name: str - timestamp: int - status: int | None = None - - -# ── Public mocks ───────────────────────────────────────────────────────────── - - -class ApiClientPublicMockPublish(BaseModel): - """Publish a snapshot of an existing collection as an anonymously-readable mock.""" - - collection_id: str - name: str = Field(min_length=1) - items: list[dict[str, Any]] = Field(default_factory=list) - - -class ApiClientPublicMockOut(BaseModel): - """Public-facing mock record. `mock_id` is the slug used in the public URL.""" - - mock_id: str - name: str - items: list[dict[str, Any]] - collection_id: str | None = None - created_at: int - - -# ── Workspaces ─────────────────────────────────────────────────────────────── - - -class ApiClientWorkspaceCreate(BaseModel): - name: str = Field(min_length=1, max_length=120) - - -class ApiClientWorkspaceUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - name: str | None = Field(default=None, min_length=1, max_length=120) - - -class ApiClientWorkspaceOut(BaseModel): - id: str - name: str - created_at: int - diff --git a/apps/backend/app/api/routes/api_client/services.py b/apps/backend/app/api/routes/api_client/services.py deleted file mode 100644 index 65001530..00000000 --- a/apps/backend/app/api/routes/api_client/services.py +++ /dev/null @@ -1,380 +0,0 @@ -import time -from typing import Any - -from bson import ObjectId -from bson.errors import InvalidId -from fastapi import HTTPException, status -from pymongo.errors import PyMongoError - -from app.api.routes.api_client.schema import ( - HISTORY_MAX_ITEMS, - ApiClientCollectionCreate, - ApiClientCollectionOut, - ApiClientCollectionUpdate, - ApiClientEnvironmentCreate, - ApiClientEnvironmentOut, - ApiClientEnvironmentUpdate, - ApiClientHistoryCreate, - ApiClientHistoryOut, - ApiClientPublicMockOut, - ApiClientPublicMockPublish, - ApiClientWorkspaceCreate, - ApiClientWorkspaceOut, - ApiClientWorkspaceUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.database import db_manager -from app.utils.collection_name import ( - API_CLIENT_COLLECTIONS, - API_CLIENT_ENVIRONMENTS, - API_CLIENT_HISTORY, - API_CLIENT_PUBLIC_MOCKS, - API_CLIENT_WORKSPACES, -) -from app.utils.crud import safe_delete_one, safe_insert, safe_update_one - -import secrets - -HISTORY_TRIM_BATCH_SIZE = 500 - - -def _parse_oid(raw: str, *, kind: str) -> ObjectId: - try: - return ObjectId(raw) - except InvalidId as exc: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid {kind} id.", - ) from exc - - -def _collection_to_out(doc: dict[str, Any]) -> ApiClientCollectionOut: - oid = doc.get("_id") - return ApiClientCollectionOut( - id=str(oid) if oid is not None else "", - name=doc.get("name", ""), - items=list(doc.get("items") or []), - workspace=doc.get("workspace"), - ) - - -def _env_to_out(doc: dict[str, Any]) -> ApiClientEnvironmentOut: - oid = doc.get("_id") - return ApiClientEnvironmentOut( - id=str(oid) if oid is not None else "", - name=doc.get("name", ""), - variables=list(doc.get("variables") or []), - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_collections(*, ctx: WorkspaceContext) -> list[ApiClientCollectionOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - API_CLIENT_COLLECTIONS, - flt, - {"_id": 1, "name": 1, "items": 1, "workspace": 1}, - sort=[("name", 1), ("_id", 1)], - ) - return [_collection_to_out(d) for d in docs] - - -async def create_collection(ctx: WorkspaceContext, body: ApiClientCollectionCreate) -> ApiClientCollectionOut: - doc: dict[str, Any] = { - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "name": body.name, - "items": [], - } - await safe_insert(API_CLIENT_COLLECTIONS, doc, name="Collection") - return _collection_to_out(doc) - - -async def patch_collection(ctx: WorkspaceContext, collection_id: str, body: ApiClientCollectionUpdate) -> ApiClientCollectionOut: - oid = _parse_oid(collection_id, kind="collection") - patch = body.model_dump(exclude_unset=True) - if not patch: - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await db_manager.find_one(API_CLIENT_COLLECTIONS, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Collection not found.") - return _collection_to_out(doc) - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await safe_update_one( - API_CLIENT_COLLECTIONS, flt, patch, name="Collection" - ) - return _collection_to_out(doc) - - -async def delete_collection(ctx: WorkspaceContext, collection_id: str) -> None: - oid = _parse_oid(collection_id, kind="collection") - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - await safe_delete_one(API_CLIENT_COLLECTIONS, flt, name="Collection") - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_environments(*, ctx: WorkspaceContext) -> list[ApiClientEnvironmentOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - API_CLIENT_ENVIRONMENTS, - flt, - {"_id": 1, "name": 1, "variables": 1}, - sort=[("name", 1), ("_id", 1)], - ) - return [_env_to_out(d) for d in docs] - - -async def create_environment(ctx: WorkspaceContext, body: ApiClientEnvironmentCreate) -> ApiClientEnvironmentOut: - doc: dict[str, Any] = { - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "name": body.name, - "variables": [], - } - await safe_insert(API_CLIENT_ENVIRONMENTS, doc, name="Environment") - return _env_to_out(doc) - - -async def patch_environment(ctx: WorkspaceContext, environment_id: str, body: ApiClientEnvironmentUpdate) -> ApiClientEnvironmentOut: - oid = _parse_oid(environment_id, kind="environment") - patch = body.model_dump(exclude_unset=True) - if not patch: - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await db_manager.find_one(API_CLIENT_ENVIRONMENTS, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Environment not found.") - return _env_to_out(doc) - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await safe_update_one( - API_CLIENT_ENVIRONMENTS, flt, patch, name="Environment" - ) - return _env_to_out(doc) - - -async def delete_environment(ctx: WorkspaceContext, environment_id: str) -> None: - oid = _parse_oid(environment_id, kind="environment") - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - await safe_delete_one(API_CLIENT_ENVIRONMENTS, flt, name="Environment") - - -def _history_doc_to_out(doc: dict[str, Any]) -> ApiClientHistoryOut: - oid = doc.get("_id") - ts = doc.get("timestamp") - if not isinstance(ts, int): - ts = 0 - return ApiClientHistoryOut( - id=str(oid) if oid is not None else "", - method=str(doc.get("method", "GET")), - url=str(doc.get("url", "")), - params=list(doc.get("params") or []), - headers=list(doc.get("headers") or []), - body=dict(doc.get("body") or {}), - auth=dict(doc.get("auth") or {}), - name=str(doc.get("name", "")), - timestamp=ts, - status=doc.get("status") if isinstance(doc.get("status"), int) else None, - ) - - -async def trim_history(ctx: WorkspaceContext) -> None: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - stale_docs = await db_manager.find( - API_CLIENT_HISTORY, - flt, - {"_id": 1}, - sort=[("timestamp", -1), ("_id", -1)], - skip=HISTORY_MAX_ITEMS, - limit=HISTORY_TRIM_BATCH_SIZE, - ) - if not stale_docs: - return - ids = [d["_id"] for d in stale_docs] - del_flt = apply_workspace_filter(ctx, {"_id": {"$in": ids}, "created_by": ctx.uid}) - await db_manager.delete_many(API_CLIENT_HISTORY, del_flt) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_history(*, ctx: WorkspaceContext, limit: int = HISTORY_MAX_ITEMS) -> list[ApiClientHistoryOut]: - lim = max(1, min(limit, HISTORY_MAX_ITEMS)) - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find(API_CLIENT_HISTORY, flt, sort=[("timestamp", -1)], limit=lim) - return [_history_doc_to_out(d) for d in docs] - - -async def create_history(ctx: WorkspaceContext, body: ApiClientHistoryCreate) -> ApiClientHistoryOut: - ts = body.timestamp if body.timestamp is not None else int(time.time() * 1000) - doc: dict[str, Any] = { - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "method": body.method, - "url": body.url, - "params": body.params, - "headers": body.headers, - "body": body.body, - "auth": body.auth, - "name": body.name, - "timestamp": ts, - "status": body.status, - } - await safe_insert(API_CLIENT_HISTORY, doc, name="History entry") - return _history_doc_to_out(doc) - - -async def delete_history_entry(ctx: WorkspaceContext, entry_id: str) -> None: - oid = _parse_oid(entry_id, kind="history") - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - await safe_delete_one(API_CLIENT_HISTORY, flt, name="History entry") - - -async def clear_history(ctx: WorkspaceContext) -> None: - flt = apply_workspace_filter(ctx, {"created_by": ctx.uid}) - try: - await db_manager.delete_many(API_CLIENT_HISTORY, flt) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to clear history." - ) from exc - - -# ── Public mocks ───────────────────────────────────────────────────────────── -# -# API_CLIENT_PUBLIC_MOCKS is NOT scoped to workspace for reads. Each published -# mock is globally readable by its mock_id (the id IS the credential — see -# get_public_mock_anonymous). The owner's management operations (list/publish/ -# delete) ARE user-scoped via created_by, but we do NOT apply workspace filter -# here because: -# 1. Mocks are meant to be shareable across workspace boundaries. -# 2. Scoping by workspace_id would break the anonymous read path. -# 3. The mock_id token (~144 bits of entropy) is the access control boundary. -# Ownership is tracked by `created_by` only; the collection is NOT stamped with -# workspace_id / owner_uid. - - -def _mock_doc_to_out(doc: dict[str, Any]) -> ApiClientPublicMockOut: - return ApiClientPublicMockOut( - mock_id=str(doc.get("mock_id", "")), - name=str(doc.get("name", "")), - items=list(doc.get("items") or []), - collection_id=doc.get("collection_id"), - created_at=int(doc.get("created_at", 0)), - ) - - -def _generate_mock_id() -> str: - """24 chars of url-safe base64 → ~144 bits of entropy. Acts as the public token.""" - - return secrets.token_urlsafe(18) - - -async def list_public_mocks(*, uid: str) -> list[ApiClientPublicMockOut]: - docs = await db_manager.find( - API_CLIENT_PUBLIC_MOCKS, - {"created_by": uid}, - sort=[("created_at", -1)], - ) - return [_mock_doc_to_out(d) for d in docs] - - -async def publish_public_mock(uid: str, body: ApiClientPublicMockPublish) -> ApiClientPublicMockOut: - doc: dict[str, Any] = { - "created_by": uid, - "collection_id": body.collection_id, - "mock_id": _generate_mock_id(), - "name": body.name, - "items": body.items, - "created_at": int(time.time() * 1000), - } - await safe_insert(API_CLIENT_PUBLIC_MOCKS, doc, name="Public mock") - return _mock_doc_to_out(doc) - - -async def delete_public_mock(uid: str, mock_id: str) -> None: - await safe_delete_one( - API_CLIENT_PUBLIC_MOCKS, {"mock_id": mock_id, "created_by": uid}, name="Public mock" - ) - - -async def get_public_mock_anonymous(mock_id: str) -> ApiClientPublicMockOut | None: - """Read a published mock by id — NO auth, by design. The id is the credential.""" - - doc = await db_manager.find_one(API_CLIENT_PUBLIC_MOCKS, {"mock_id": mock_id}) - return _mock_doc_to_out(doc) if doc else None - - -# ── Workspaces (API Client's internal grouping) ─────────────────────────────── -# -# API_CLIENT_WORKSPACES stores the API Client tool's own "workspace" concept -# (grouping of API requests) — completely separate from our global Workspaces -# feature. These are per-user data so they ARE stamped with workspace_id. - - -def _ws_doc_to_out(doc: dict[str, Any]) -> ApiClientWorkspaceOut: - oid = doc.get("_id") - return ApiClientWorkspaceOut( - id=str(oid) if oid is not None else "", - name=str(doc.get("name", "")), - created_at=int(doc.get("created_at", 0)), - ) - - -async def list_workspaces(*, ctx: WorkspaceContext) -> list[ApiClientWorkspaceOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - API_CLIENT_WORKSPACES, - flt, - sort=[("name", 1), ("_id", 1)], - ) - return [_ws_doc_to_out(d) for d in docs] - - -async def create_workspace(ctx: WorkspaceContext, body: ApiClientWorkspaceCreate) -> ApiClientWorkspaceOut: - doc: dict[str, Any] = { - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "name": body.name, - "created_at": int(time.time() * 1000), - } - await safe_insert(API_CLIENT_WORKSPACES, doc, name="Workspace") - return _ws_doc_to_out(doc) - - -async def patch_workspace(ctx: WorkspaceContext, workspace_id: str, body: ApiClientWorkspaceUpdate) -> ApiClientWorkspaceOut: - oid = _parse_oid(workspace_id, kind="workspace") - patch = body.model_dump(exclude_unset=True) - if not patch: - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await db_manager.find_one(API_CLIENT_WORKSPACES, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found.") - return _ws_doc_to_out(doc) - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await safe_update_one( - API_CLIENT_WORKSPACES, flt, patch, name="Workspace" - ) - return _ws_doc_to_out(doc) - - -async def delete_workspace(ctx: WorkspaceContext, workspace_id: str) -> None: - oid = _parse_oid(workspace_id, kind="workspace") - # Clear workspace pointer from any collections that reference it. - # Use legacy-or filter so we don't orphan pre-migration collections. - col_flt = apply_legacy_or_filter( - ctx, {"workspace": workspace_id}, user_field="created_by" - ) - await db_manager.update_many( - API_CLIENT_COLLECTIONS, - col_flt, - {"$set": {"workspace": None}}, - ) - flt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - await safe_delete_one( - API_CLIENT_WORKSPACES, flt, name="Workspace" - ) diff --git a/apps/backend/app/api/routes/api_key_vault/__init__.py b/apps/backend/app/api/routes/api_key_vault/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/api_key_vault/api.py b/apps/backend/app/api/routes/api_key_vault/api.py deleted file mode 100644 index 4cc94268..00000000 --- a/apps/backend/app/api/routes/api_key_vault/api.py +++ /dev/null @@ -1,65 +0,0 @@ -from fastapi import APIRouter, Depends, Query - -from app.api.routes.api_key_vault import services as vault_svc -from app.api.routes.api_key_vault.schema import ( - ApiKeyEntryCreate, - ApiKeyEntryOut, - ApiKeyEntryUpdate, -) -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -router = APIRouter(prefix="/api-keys", tags=["api-keys"]) - - -@router.get( - "/entries", - response_model=list[ApiKeyEntryOut], - summary="List encrypted API key entries", -) -async def list_entries( - ctx: WorkspaceContext = Depends(require_permission("api-key-vault", "read")), - limit: int | None = Query(default=None, ge=1, le=1000), - offset: int = Query(default=0, ge=0), -) -> list[ApiKeyEntryOut]: - return await vault_svc.list_entries(ctx, limit=limit, offset=offset) - - -@router.post( - "/entries", - response_model=ApiKeyEntryOut, - summary="Create API key entry (encrypted blob)", -) -async def create_entry(body: ApiKeyEntryCreate, ctx: WorkspaceContext = Depends(require_permission("api-key-vault", "write"))) -> ApiKeyEntryOut: - return await vault_svc.create_entry(ctx, body) - - -@router.get( - "/entries/{entry_id}", - response_model=ApiKeyEntryOut, - summary="Get one API key entry", -) -async def get_entry(entry_id: str, ctx: WorkspaceContext = Depends(require_permission("api-key-vault", "read"))) -> ApiKeyEntryOut: - return await vault_svc.get_entry(ctx=ctx, entry_id=entry_id) - - -@router.patch( - "/entries/{entry_id}", - response_model=ApiKeyEntryOut, - summary="Update API key entry (encrypted blob)", -) -async def patch_entry( - entry_id: str, - body: ApiKeyEntryUpdate, - ctx: WorkspaceContext = Depends(require_permission("api-key-vault", "write")), -) -> ApiKeyEntryOut: - return await vault_svc.update_entry(ctx, entry_id, body) - - -@router.delete( - "/entries/{entry_id}", - status_code=204, - summary="Delete API key entry", -) -async def delete_entry(entry_id: str, ctx: WorkspaceContext = Depends(require_permission("api-key-vault", "delete"))) -> None: - await vault_svc.delete_entry(ctx, entry_id) diff --git a/apps/backend/app/api/routes/api_key_vault/schema.py b/apps/backend/app/api/routes/api_key_vault/schema.py deleted file mode 100644 index c5369a08..00000000 --- a/apps/backend/app/api/routes/api_key_vault/schema.py +++ /dev/null @@ -1,26 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class ApiKeyEntryCreate(BaseModel): - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - createdAt: int | None = Field(default=None, ge=0) - updatedAt: int | None = Field(default=None, ge=0) - - -class ApiKeyEntryUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - updatedAt: int | None = Field(default=None, ge=0) - - -class ApiKeyEntryOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - encryptedData: str - iv: str - createdAt: int - updatedAt: int diff --git a/apps/backend/app/api/routes/api_key_vault/services.py b/apps/backend/app/api/routes/api_key_vault/services.py deleted file mode 100644 index dfb5db18..00000000 --- a/apps/backend/app/api/routes/api_key_vault/services.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import Any - -from fastapi import HTTPException, status - -from app.api.routes.api_key_vault.schema import ( - ApiKeyEntryCreate, - ApiKeyEntryOut, - ApiKeyEntryUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.database import db_manager -from app.utils.collection_name import API_KEY_VAULT_ENTRIES -from app.utils.crud import safe_delete_one, safe_insert, safe_update_one -from app.utils.utils import create_timestamp, new_id - - -def _entry_doc_to_out(doc: dict[str, Any], *, entry_id: str) -> ApiKeyEntryOut: - created_at = int(doc.get("createdAt", 0)) or create_timestamp() - updated_at = int(doc.get("updatedAt", 0)) or created_at - return ApiKeyEntryOut( - id=entry_id, - encryptedData=str(doc.get("encryptedData", "")), - iv=str(doc.get("iv", "")), - createdAt=created_at, - updatedAt=updated_at, - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_entries(ctx: WorkspaceContext, *, limit: int | None = None, offset: int = 0) -> list[ApiKeyEntryOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - API_KEY_VAULT_ENTRIES, - flt, - sort=[("updatedAt", -1), ("createdAt", -1)], - skip=max(0, offset), - limit=limit or 0, - ) - return [_entry_doc_to_out(d, entry_id=str(d.get("_id", ""))) for d in docs] - - -async def create_entry(ctx: WorkspaceContext, body: ApiKeyEntryCreate) -> ApiKeyEntryOut: - eid = new_id() - ts = create_timestamp() - created_at = int(body.createdAt) if body.createdAt is not None else ts - updated_at = int(body.updatedAt) if body.updatedAt is not None else created_at - - doc: dict[str, Any] = { - "_id": eid, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "encryptedData": body.encryptedData, - "iv": body.iv, - "createdAt": created_at, - "updatedAt": updated_at, - } - await safe_insert(API_KEY_VAULT_ENTRIES, doc, name="ApiKeyEntry") - return _entry_doc_to_out(doc, entry_id=eid) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_entry(*, ctx: WorkspaceContext, entry_id: str) -> ApiKeyEntryOut: - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - doc = await db_manager.find_one(API_KEY_VAULT_ENTRIES, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="API key entry not found.") - return _entry_doc_to_out(doc, entry_id=entry_id) - - -async def update_entry(ctx: WorkspaceContext, entry_id: str, body: ApiKeyEntryUpdate) -> ApiKeyEntryOut: - ts_updated = int(body.updatedAt) if body.updatedAt is not None else create_timestamp() - patch: dict[str, Any] = { - "encryptedData": body.encryptedData, - "iv": body.iv, - "updatedAt": ts_updated, - } - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - result = await safe_update_one( - API_KEY_VAULT_ENTRIES, - flt, - patch, - name="ApiKeyEntry", - ) - return _entry_doc_to_out(result, entry_id=entry_id) - - -async def delete_entry(ctx: WorkspaceContext, entry_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - await safe_delete_one( - API_KEY_VAULT_ENTRIES, flt, name="ApiKeyEntry" - ) diff --git a/apps/backend/app/api/routes/audit_log/__init__.py b/apps/backend/app/api/routes/audit_log/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/audit_log/api.py b/apps/backend/app/api/routes/audit_log/api.py deleted file mode 100644 index 27b305c5..00000000 --- a/apps/backend/app/api/routes/audit_log/api.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Optional - -from fastapi import APIRouter, Depends, Query - -from app.api.routes.auth.services import get_current_uid -from app.api.routes.audit_log import services as svc -from app.api.routes.audit_log.schema import AuditListOut - -router = APIRouter(prefix="/audit-log", tags=["audit-log"]) - - -@router.get("", response_model=AuditListOut, summary="List the current user's audit events") -async def list_events( - uid: str = Depends(get_current_uid), - skip: int = Query(default=0, ge=0), - limit: int = Query(default=50, ge=1, le=100), - module: Optional[str] = Query(default=None), - action: Optional[str] = Query(default=None), - outcome: Optional[str] = Query(default=None), - ts_from: Optional[int] = Query(default=None, alias="from"), - ts_to: Optional[int] = Query(default=None, alias="to"), - search: Optional[str] = Query(default=None), -) -> AuditListOut: - return await svc.list_audit_events( - uid, skip=skip, limit=limit, module=module, action=action, - outcome=outcome, ts_from=ts_from, ts_to=ts_to, search=search, - ) diff --git a/apps/backend/app/api/routes/audit_log/schema.py b/apps/backend/app/api/routes/audit_log/schema.py deleted file mode 100644 index d85d4c3e..00000000 --- a/apps/backend/app/api/routes/audit_log/schema.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Any, Optional - -from pydantic import BaseModel - - -class AuditChange(BaseModel): - field: str - before: Any | None = None - after: Any | None = None - - -class AuditDevice(BaseModel): - browser: str - os: str - device_type: str - - -class AuditEventOut(BaseModel): - id: str - uid: Optional[str] = None - action: str - module: Optional[str] = None - entity_type: Optional[str] = None - entity_id: Optional[str] = None - method: str - path: str - status: int - outcome: str - changes: Optional[list[AuditChange]] = None - summary: Optional[str] = None - ip: Optional[str] = None - ua_raw: Optional[str] = None - device: Optional[AuditDevice] = None - latency_ms: int - ts: int - - -class AuditListOut(BaseModel): - items: list[AuditEventOut] - total: int - skip: int - limit: int diff --git a/apps/backend/app/api/routes/audit_log/services.py b/apps/backend/app/api/routes/audit_log/services.py deleted file mode 100644 index 92396e98..00000000 --- a/apps/backend/app/api/routes/audit_log/services.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Any, Optional - -from app.database import db_manager -from app.utils.collection_name import AUDIT_LOG -from app.api.routes.audit_log.schema import AuditEventOut, AuditListOut - - -def _doc_to_out(doc: dict[str, Any]) -> AuditEventOut: - return AuditEventOut( - id=str(doc.get("_id", "")), - uid=doc.get("uid"), - action=doc.get("action", ""), - module=doc.get("module"), - entity_type=doc.get("entity_type"), - entity_id=doc.get("entity_id"), - method=doc.get("method", ""), - path=doc.get("path", ""), - status=int(doc.get("status", 0)), - outcome=doc.get("outcome", ""), - changes=doc.get("changes"), - summary=doc.get("summary"), - ip=doc.get("ip"), - ua_raw=doc.get("ua_raw"), - device=doc.get("device"), - latency_ms=int(doc.get("latency_ms", 0)), - ts=int(doc.get("ts", 0)), - ) - - -async def list_audit_events( - uid: str, - *, - skip: int = 0, - limit: int = 50, - module: Optional[str] = None, - action: Optional[str] = None, - outcome: Optional[str] = None, - ts_from: Optional[int] = None, - ts_to: Optional[int] = None, - search: Optional[str] = None, -) -> AuditListOut: - query: dict[str, Any] = {"uid": uid} - if module: - query["module"] = module - if action: - query["action"] = action - if outcome: - query["outcome"] = outcome - if ts_from is not None or ts_to is not None: - rng: dict[str, Any] = {} - if ts_from is not None: - rng["$gte"] = ts_from - if ts_to is not None: - rng["$lte"] = ts_to - query["ts"] = rng - if search: - query["summary"] = {"$regex": search, "$options": "i"} - - total = await db_manager.count_documents(AUDIT_LOG, query) - docs = await db_manager.find( - AUDIT_LOG, query, sort=[("ts", -1)], skip=skip, limit=limit - ) - return AuditListOut( - items=[_doc_to_out(d) for d in docs], - total=total, - skip=skip, - limit=limit, - ) diff --git a/apps/backend/app/api/routes/auth/api.py b/apps/backend/app/api/routes/auth/api.py index f0cdc2ae..0c458447 100644 --- a/apps/backend/app/api/routes/auth/api.py +++ b/apps/backend/app/api/routes/auth/api.py @@ -1,10 +1,8 @@ import asyncio -import logging import re -import time from typing import Annotated -from fastapi import APIRouter, BackgroundTasks, Cookie, Depends, Header, HTTPException, Request, Response, status +from fastapi import APIRouter, Cookie, Depends, Header, HTTPException, Request, Response, status from app.core.limiter import limiter from app.core import audit @@ -12,13 +10,8 @@ from app.api.routes.auth.cookie_attach import attach_auth_cookies, clear_auth_cookies from app.api.routes.auth.schema import ( - BackupCodeDataOut, - BackupCodeLookupRequest, - MasterVaultOut, - MasterVaultSetupRequest, OkResponse, SessionRequest, - StoreBackupCodesRequest, UserProfileResponse, UpdateProfileRequest, ) @@ -34,12 +27,7 @@ clear_refresh_token_hash, complete_onboarding, find_uid_by_refresh_hash, - get_backup_code_by_id, - get_master_vault, get_user_doc, - mark_backup_code_used, - set_backup_codes, - set_master_vault, set_refresh_token_hash, upsert_user_from_firebase_claims, update_user_profile, @@ -56,7 +44,6 @@ async def create_session( request: Request, payload: SessionRequest, response: Response, - background_tasks: BackgroundTasks, ) -> UserProfileResponse: decoded = await asyncio.to_thread( verify_id_token, @@ -72,13 +59,6 @@ async def create_session( await upsert_user_from_firebase_claims(decoded) - # Ensure user workspace setup (idempotent first-login hook) - try: - from app.api.routes.workspaces.services import ensure_user_workspace_setup - await ensure_user_workspace_setup(uid, background_tasks=background_tasks) - except Exception as exc: - logging.getLogger(__name__).warning("Workspace setup failed for %s: %s", uid, exc) - access = create_access_token(uid) raw_refresh = new_refresh_token() settings = get_settings() @@ -110,6 +90,8 @@ async def create_session( disabled=bool(doc.get("disabled")), plan=doc.get("plan") or "free", plan_source=doc.get("plan_source"), + plan_granted_at=doc.get("plan_granted_at"), + created_at=doc.get("created_at"), onboarding_completed=bool(doc.get("onboarding_completed", False)), workspace_setup_at=doc.get("workspace_setup_at"), migrated_at=doc.get("migrated_at"), @@ -339,111 +321,6 @@ async def desktop_token(uid: Annotated[str, Depends(get_current_uid)]) -> dict: return {"token": token} -# ── Master-password vault ───────────────────────────────────────────────────── - - -@router.get( - "/master-vault", - response_model=MasterVaultOut, - summary="Retrieve master-vault metadata (salt + verifier) for the current user", -) -async def get_master_vault_endpoint( - uid: Annotated[str, Depends(get_current_uid)], -) -> MasterVaultOut: - vault = await get_master_vault(uid) - if not vault: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Master vault not configured.", - ) - return MasterVaultOut(**vault) - - -@router.post( - "/master-vault", - response_model=MasterVaultOut, - status_code=status.HTTP_201_CREATED, - summary="Set up master vault once (salt + key-verifier blob; raw password never sent)", -) -@limiter.limit("3/minute") -async def setup_master_vault_endpoint( - request: Request, - payload: MasterVaultSetupRequest, - uid: Annotated[str, Depends(get_current_uid)], -) -> MasterVaultOut: - if await get_master_vault(uid): - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="Master vault already configured.", - ) - created_at = int(time.time() * 1000) - vault: dict = { - "salt": payload.salt, - "verifier": payload.verifier.model_dump(), - "createdAt": created_at, - } - await set_master_vault(uid, vault) - return MasterVaultOut(**vault) - - -# ── Backup codes ────────────────────────────────────────────────────────────── - - -@router.post( - "/backup-codes", - response_model=OkResponse, - status_code=status.HTTP_201_CREATED, - summary="Store encrypted backup codes (replaces any existing set)", -) -@limiter.limit("10/minute") -async def store_backup_codes_endpoint( - request: Request, - payload: StoreBackupCodesRequest, - uid: Annotated[str, Depends(get_current_uid)], -) -> OkResponse: - await set_backup_codes(uid, [c.model_dump() for c in payload.codes]) - return OkResponse(ok=True) - - -@router.post( - "/backup-codes/lookup", - response_model=BackupCodeDataOut, - summary="Return encrypted blob for a backup code ID (does not consume the code)", -) -@limiter.limit("20/minute") -async def lookup_backup_code_endpoint( - request: Request, - payload: BackupCodeLookupRequest, - uid: Annotated[str, Depends(get_current_uid)], -) -> BackupCodeDataOut: - code = await get_backup_code_by_id(uid, payload.codeId) - if not code: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Backup code not found or already used.", - ) - return BackupCodeDataOut( - codeSalt=code["codeSalt"], - encrypted=code["encrypted"], - iv=code["iv"], - ) - - -@router.post( - "/backup-codes/use", - response_model=OkResponse, - summary="Mark a backup code as used (one-time consumption)", -) -@limiter.limit("10/minute") -async def use_backup_code_endpoint( - request: Request, - payload: BackupCodeLookupRequest, - uid: Annotated[str, Depends(get_current_uid)], -) -> OkResponse: - await mark_backup_code_used(uid, payload.codeId) - return OkResponse(ok=True) - - # ── Onboarding ──────────────────────────────────────────────────────────────── diff --git a/apps/backend/app/api/routes/auth/schema.py b/apps/backend/app/api/routes/auth/schema.py index 355ed933..b3d96a04 100644 --- a/apps/backend/app/api/routes/auth/schema.py +++ b/apps/backend/app/api/routes/auth/schema.py @@ -115,6 +115,8 @@ class UserProfileResponse(BaseModel): # Billing plan; absent on the Mongo doc means free. plan: str = "free" plan_source: str | None = None + plan_granted_at: int | None = None + created_at: int | None = None onboarding_completed: bool = False # Workspace migration fields (T25) workspace_setup_at: int | None = None diff --git a/apps/backend/app/api/routes/auth/services.py b/apps/backend/app/api/routes/auth/services.py index 4e7f5c77..6eec51bf 100644 --- a/apps/backend/app/api/routes/auth/services.py +++ b/apps/backend/app/api/routes/auth/services.py @@ -131,6 +131,8 @@ async def get_current_user( personal_info=PersonalInfo(**doc["personal_info"]) if doc.get("personal_info") else None, plan=doc.get("plan") or "free", plan_source=doc.get("plan_source"), + plan_granted_at=doc.get("plan_granted_at"), + created_at=doc.get("created_at"), onboarding_completed=bool(doc.get("onboarding_completed", False)), workspace_setup_at=doc.get("workspace_setup_at"), migrated_at=doc.get("migrated_at"), diff --git a/apps/backend/app/api/routes/auth/users_repo.py b/apps/backend/app/api/routes/auth/users_repo.py index 2180aed9..b3ba0c50 100644 --- a/apps/backend/app/api/routes/auth/users_repo.py +++ b/apps/backend/app/api/routes/auth/users_repo.py @@ -117,36 +117,6 @@ async def find_uid_by_refresh_hash(token_hash: str) -> str | None: return str(uid) if uid is not None else None -async def get_master_vault(uid: str) -> dict[str, Any] | None: - doc = await get_user_doc(uid) - if not doc: - return None - return doc.get("master_vault") or None - - -async def set_master_vault(uid: str, vault: dict[str, Any]) -> None: - now = create_timestamp() - await db_manager.update_one(USERS, {"_id": uid}, {"$set": {"master_vault": vault, "updated_at": now}}) - await bump_version(ns="auth_user", uid=uid) - - -async def set_backup_codes(uid: str, codes: list[dict[str, Any]]) -> None: - now = create_timestamp() - await db_manager.update_one(USERS, {"_id": uid}, {"$set": {"backup_codes": codes, "updated_at": now}}) - await bump_version(ns="auth_user", uid=uid) - - -async def get_backup_code_by_id(uid: str, code_id: str) -> dict[str, Any] | None: - doc = await get_user_doc(uid) - if not doc: - return None - codes: list[dict] = doc.get("backup_codes") or [] - for code in codes: - if code.get("codeId") == code_id and not code.get("used"): - return code - return None - - async def complete_onboarding(uid: str) -> None: now = create_timestamp() await db_manager.update_one( @@ -155,16 +125,6 @@ async def complete_onboarding(uid: str) -> None: await bump_version(ns="auth_user", uid=uid) -async def mark_backup_code_used(uid: str, code_id: str) -> None: - now = create_timestamp() - await db_manager.update_one( - USERS, - {"_id": uid, "backup_codes.codeId": code_id}, - {"$set": {"backup_codes.$.used": True, "updated_at": now}}, - ) - await bump_version(ns="auth_user", uid=uid) - - # ── Passkeys / WebAuthn ─────────────────────────────────────────────────────── diff --git a/apps/backend/app/api/routes/bookmarks/__init__.py b/apps/backend/app/api/routes/bookmarks/__init__.py deleted file mode 100644 index fb0a2f80..00000000 --- a/apps/backend/app/api/routes/bookmarks/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API route modules.""" diff --git a/apps/backend/app/api/routes/bookmarks/api.py b/apps/backend/app/api/routes/bookmarks/api.py deleted file mode 100644 index 28b2f5b7..00000000 --- a/apps/backend/app/api/routes/bookmarks/api.py +++ /dev/null @@ -1,166 +0,0 @@ - -from fastapi import APIRouter, Depends, Query - -from app.api.routes.bookmarks import services as bm_svc -from app.api.routes.bookmarks.schema import ( - BookmarkCreate, - BookmarkFolderCreate, - BookmarkFolderExpanded, - BookmarkFolderOut, - BookmarkFolderUpdate, - BookmarkImportBody, - BookmarkMove, - BookmarkOut, - BookmarkSnapshotOut, - BookmarkUpdate, -) -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -bookmarks_router = APIRouter(tags=["bookmarks"]) -folders_router = APIRouter(tags=["bookmark-folders"]) - - -@bookmarks_router.get( - "/snapshot", - response_model=BookmarkSnapshotOut, - summary="Full export (bookmarks + folders), same shape as client JSON export", -) -async def get_snapshot(ctx: WorkspaceContext = Depends(require_permission("bookmarks", "read"))) -> BookmarkSnapshotOut: - return await bm_svc.snapshot(ctx) - - -@bookmarks_router.post("/import", summary="Upsert folders + bookmarks (HTML/JSON import parity)") -async def import_bookmarks( - body: BookmarkImportBody, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "admin")), -) -> dict[str, int]: - return await bm_svc.import_bookmarks(ctx, body) - - -@bookmarks_router.post("/clear-all", summary="Delete all bookmarks and folders for user (clearAll)") -async def clear_all(ctx: WorkspaceContext = Depends(require_permission("bookmarks", "admin"))) -> dict[str, int]: - return await bm_svc.clear_all_bookmarks(ctx) - - -@bookmarks_router.get("", response_model=list[BookmarkOut], summary="List bookmarks") -async def list_bookmarks( - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "read")), - folder_id: str | None = Query( - default=None, - alias="folderId", - ), - skip: int = Query(default=0, ge=0), - limit: int | None = Query(default=None, ge=1, le=500), -) -> list[BookmarkOut]: - return await bm_svc.list_bookmarks(ctx=ctx, folder_id=folder_id, skip=skip, limit=limit) - - -@bookmarks_router.post("", response_model=BookmarkOut, summary="Create bookmark (addBookmark)") -async def create_bookmark( - body: BookmarkCreate, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "write")), -) -> BookmarkOut: - return await bm_svc.create_bookmark(ctx, body) - - -@bookmarks_router.get("/{bookmark_id}", response_model=BookmarkOut, summary="Get one bookmark") -async def get_bookmark( - bookmark_id: str, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "read")), -) -> BookmarkOut: - return await bm_svc.get_bookmark(ctx=ctx, bookmark_id=bookmark_id) - - -@bookmarks_router.patch("/{bookmark_id}", response_model=BookmarkOut, summary="Update bookmark (updateBookmark)") -async def patch_bookmark( - bookmark_id: str, - body: BookmarkUpdate, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "write")), -) -> BookmarkOut: - return await bm_svc.update_bookmark(ctx, bookmark_id, body) - - -@bookmarks_router.patch( - "/{bookmark_id}/move", - response_model=BookmarkOut, - summary="Move bookmark to folder (moveBookmark)", -) -async def move_bookmark( - bookmark_id: str, - body: BookmarkMove, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "write")), -) -> BookmarkOut: - return await bm_svc.move_bookmark(ctx, bookmark_id, body) - - -@bookmarks_router.delete("/{bookmark_id}", status_code=204, summary="Delete bookmark (deleteBookmark)") -async def remove_bookmark( - bookmark_id: str, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "delete")), -) -> None: - await bm_svc.delete_bookmark(ctx, bookmark_id) - - -@folders_router.get("", response_model=list[BookmarkFolderOut], summary="List folders (tree source)") -async def list_folders( - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "read")), - skip: int = Query(default=0, ge=0), - limit: int | None = Query(default=None, ge=1, le=500), -) -> list[BookmarkFolderOut]: - return await bm_svc.list_folders(ctx=ctx, skip=skip, limit=limit) - - -@folders_router.post("", response_model=BookmarkFolderOut, summary="Create folder (addFolder)") -async def create_folder( - body: BookmarkFolderCreate, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "write")), -) -> BookmarkFolderOut: - return await bm_svc.create_folder(ctx, body) - - -@folders_router.get("/{folder_id}", response_model=BookmarkFolderOut, summary="Get one folder") -async def get_folder( - folder_id: str, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "read")), -) -> BookmarkFolderOut: - return await bm_svc.get_folder(ctx, folder_id) - - -@folders_router.patch("/{folder_id}", response_model=BookmarkFolderOut, summary="Update folder (updateFolder)") -async def patch_folder( - folder_id: str, - body: BookmarkFolderUpdate, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "write")), -) -> BookmarkFolderOut: - return await bm_svc.update_folder(ctx, folder_id, body) - - -@folders_router.patch( - "/{folder_id}/expanded", - response_model=BookmarkFolderOut, - summary="Toggle tree expanded (toggleFolderExpanded)", -) -async def patch_folder_expanded( - folder_id: str, - body: BookmarkFolderExpanded, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "write")), -) -> BookmarkFolderOut: - return await bm_svc.set_folder_expanded(ctx, folder_id, body.isExpanded) - - -@folders_router.delete( - "/{folder_id}", - status_code=204, - summary="Delete folder and descendants; uncategorize bookmarks (deleteFolder)", -) -async def remove_folder( - folder_id: str, - ctx: WorkspaceContext = Depends(require_permission("bookmarks", "delete")), -) -> None: - await bm_svc.delete_folder(ctx, folder_id) - - -router = APIRouter() -router.include_router(bookmarks_router, prefix="/bookmarks") -router.include_router(folders_router, prefix="/bookmark-folders") diff --git a/apps/backend/app/api/routes/bookmarks/schema.py b/apps/backend/app/api/routes/bookmarks/schema.py deleted file mode 100644 index 80c4b9c2..00000000 --- a/apps/backend/app/api/routes/bookmarks/schema.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Bookmarks data model — matches ``Bookmark`` / ``BookmarkFolder`` in -``apps/web/src/store/bookmark-store.ts``. - -The web app currently persists via Zustand + localStorage only (no Firestore). -If you add Firestore later, use top-level collections with a ``created_by`` field -(same pattern as ``tasks`` / ``projects``): - -- ``bookmarks`` — documents with string IDs (client-generated, same as today). -- ``bookmarkFolders`` — nested folders via ``parentId`` (null = root). - -Stored fields use the same names as the client (camelCase + ``created_by``). -``createdAt`` / ``updatedAt`` are Unix ms integers, matching the store. - -Recommended indexes:: - - db.bookmarks.create_index([("created_by", 1), ("folderId", 1)]) - db.bookmarks.create_index([("created_by", 1), ("updatedAt", -1)]) - db.bookmarkFolders.create_index([("created_by", 1), ("parentId", 1)]) - db.bookmarkFolders.create_index([("created_by", 1), ("createdAt", 1)]) -""" - - -from pydantic import BaseModel, ConfigDict, Field - -from app.utils.collection_name import BOOKMARK_FOLDERS, BOOKMARKS - -# Re-export for services -COLLECTION_BOOKMARKS = BOOKMARKS -COLLECTION_BOOKMARK_FOLDERS = BOOKMARK_FOLDERS - - -class BookmarkBase(BaseModel): - model_config = ConfigDict(extra="ignore") - - title: str = Field(min_length=1) - url: str = Field(min_length=1) - description: str | None = None - favicon: str | None = None - tags: list[str] = Field(default_factory=list) - folderId: str | None = None - - -class BookmarkCreate(BookmarkBase): - """Optional ``id`` — if omitted, server generates one (client uses timestamp-random).""" - - id: str | None = None - - -class BookmarkUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - title: str | None = Field(default=None, min_length=1) - url: str | None = Field(default=None, min_length=1) - description: str | None = None - favicon: str | None = None - tags: list[str] | None = None - folderId: str | None = None - - -class BookmarkMove(BaseModel): - folderId: str | None = None - - -class BookmarkOut(BookmarkBase): - id: str - createdAt: int - updatedAt: int - - -class BookmarkFolderBase(BaseModel): - model_config = ConfigDict(extra="ignore") - - name: str = Field(min_length=1) - parentId: str | None = None - color: str | None = None - icon: str | None = None - isExpanded: bool | None = None - - -class BookmarkFolderCreate(BookmarkFolderBase): - id: str | None = None - - -class BookmarkFolderUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - name: str | None = Field(default=None, min_length=1) - parentId: str | None = None - color: str | None = None - icon: str | None = None - isExpanded: bool | None = None - - -class BookmarkFolderExpanded(BaseModel): - isExpanded: bool - - -class BookmarkFolderOut(BookmarkFolderBase): - id: str - createdAt: int - - -class BookmarkImportBody(BaseModel): - """Same shape as ``exportBookmarksToJSON`` / ``parseBookmarkJSON`` output.""" - - bookmarks: list[BookmarkCreate] = Field(default_factory=list) - folders: list[BookmarkFolderCreate] = Field(default_factory=list) - - -class BookmarkSnapshotOut(BaseModel): - bookmarks: list[BookmarkOut] - folders: list[BookmarkFolderOut] diff --git a/apps/backend/app/api/routes/bookmarks/services.py b/apps/backend/app/api/routes/bookmarks/services.py deleted file mode 100644 index 9a066659..00000000 --- a/apps/backend/app/api/routes/bookmarks/services.py +++ /dev/null @@ -1,296 +0,0 @@ -from typing import Any - -from fastapi import HTTPException, status -from pymongo import ReplaceOne -from pymongo.errors import PyMongoError - -from app.api.routes.bookmarks.schema import ( - BookmarkCreate, - BookmarkFolderCreate, - BookmarkFolderOut, - BookmarkFolderUpdate, - BookmarkImportBody, - BookmarkMove, - BookmarkOut, - BookmarkSnapshotOut, - BookmarkUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.core import audit -from app.database import db_manager -from app.utils.collection_name import BOOKMARK_FOLDERS as FOLDERS -from app.utils.collection_name import BOOKMARKS -from app.utils.crud import safe_insert, safe_update_one -from app.utils.utils import create_timestamp, new_id - - -def _bookmark_doc_to_out(doc: dict[str, Any]) -> BookmarkOut: - bid = doc.get("_id") - return BookmarkOut( - id=str(bid) if bid is not None else "", - title=doc.get("title", ""), - url=doc.get("url", ""), - description=doc.get("description"), - favicon=doc.get("favicon"), - tags=list(doc.get("tags") or []), - folderId=doc.get("folderId"), - createdAt=int(doc.get("createdAt", 0)), - updatedAt=int(doc.get("updatedAt", 0)), - ) - - -def _folder_doc_to_out(doc: dict[str, Any]) -> BookmarkFolderOut: - fid = doc.get("_id") - return BookmarkFolderOut( - id=str(fid) if fid is not None else "", - name=doc.get("name", ""), - parentId=doc.get("parentId"), - color=doc.get("color"), - icon=doc.get("icon"), - isExpanded=doc.get("isExpanded"), - createdAt=int(doc.get("createdAt", 0)), - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_bookmarks( - *, - ctx: WorkspaceContext, - folder_id: str | None = None, - skip: int = 0, - limit: int | None = None, -) -> list[BookmarkOut]: - base: dict[str, Any] = {} - if folder_id == "uncategorized": - base["$or"] = [{"folderId": None}, {"folderId": {"$exists": False}}] - elif folder_id is not None and folder_id != "": - base["folderId"] = folder_id - - q = apply_legacy_or_filter(ctx, base, user_field="created_by") - - docs = await db_manager.find( - BOOKMARKS, q, sort=[("updatedAt", -1), ("createdAt", -1)], skip=skip, limit=limit or 0 - ) - return [_bookmark_doc_to_out(d) for d in docs] - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_bookmark(*, ctx: WorkspaceContext, bookmark_id: str) -> BookmarkOut: - flt = apply_workspace_filter(ctx, {"_id": bookmark_id, "created_by": ctx.uid}) - doc = await db_manager.find_one(BOOKMARKS, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Bookmark not found.") - return _bookmark_doc_to_out(doc) - - -async def create_bookmark(ctx: WorkspaceContext, body: BookmarkCreate) -> BookmarkOut: - bid = body.id or new_id() - ts = create_timestamp() - doc: dict[str, Any] = { - "_id": bid, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "title": body.title, - "url": body.url, - "description": body.description, - "favicon": body.favicon, - "tags": body.tags, - "folderId": body.folderId, - "createdAt": ts, - "updatedAt": ts, - } - await safe_insert(BOOKMARKS, doc, name="Bookmark") - audit.set_action("bookmark.create") - audit.set_entity("bookmark", bid) - audit.set_summary(f"Created bookmark '{body.title}'") - audit.set_changes(audit.diff(None, doc)) - return _bookmark_doc_to_out(doc) - - -async def update_bookmark(ctx: WorkspaceContext, bookmark_id: str, body: BookmarkUpdate) -> BookmarkOut: - patch = body.model_dump(exclude_unset=True) - if not patch: - return await get_bookmark(ctx=ctx, bookmark_id=bookmark_id) - flt = apply_workspace_filter(ctx, {"_id": bookmark_id, "created_by": ctx.uid}) - before = await db_manager.find_one(BOOKMARKS, flt) - patch["updatedAt"] = create_timestamp() - result = await safe_update_one( - BOOKMARKS, flt, patch, name="Bookmark" - ) - audit.set_action("bookmark.update") - audit.set_entity("bookmark", bookmark_id) - audit.set_summary(f"Updated bookmark '{result.get('title', '')}'") - audit.set_changes(audit.diff(before, result)) - return _bookmark_doc_to_out(result) - - -async def move_bookmark(ctx: WorkspaceContext, bookmark_id: str, body: BookmarkMove) -> BookmarkOut: - return await update_bookmark(ctx, bookmark_id, BookmarkUpdate(folderId=body.folderId)) - - -async def delete_bookmark(ctx: WorkspaceContext, bookmark_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": bookmark_id, "created_by": ctx.uid}) - before = await db_manager.find_one(BOOKMARKS, flt) - result = await db_manager.delete_one(BOOKMARKS, flt) - if result.deleted_count == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Bookmark not found.") - audit.set_action("bookmark.delete") - audit.set_entity("bookmark", bookmark_id) - title = (before or {}).get("title", "") - audit.set_summary(f"Deleted bookmark '{title}'") - - -async def import_bookmarks(ctx: WorkspaceContext, body: BookmarkImportBody) -> dict[str, int]: - folder_ops: list[ReplaceOne] = [] - bookmark_ops: list[ReplaceOne] = [] - try: - for folder in body.folders: - fid = str(folder.id or new_id()) - doc = { - "_id": fid, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "name": folder.name, - "parentId": folder.parentId, - "color": folder.color, - "icon": folder.icon, - "isExpanded": folder.isExpanded or False, - "createdAt": create_timestamp(), - } - flt = apply_legacy_or_filter(ctx, {"_id": fid}, user_field="created_by") - folder_ops.append(ReplaceOne(flt, doc, upsert=True)) - - for bookmark in body.bookmarks: - bid = str(bookmark.id or new_id()) - ts = create_timestamp() - doc = { - "_id": bid, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "title": bookmark.title, - "url": bookmark.url, - "description": bookmark.description, - "favicon": bookmark.favicon, - "tags": list(bookmark.tags), - "folderId": bookmark.folderId, - "createdAt": ts, - "updatedAt": ts, - } - flt = apply_legacy_or_filter(ctx, {"_id": bid}, user_field="created_by") - bookmark_ops.append(ReplaceOne(flt, doc, upsert=True)) - - if folder_ops: - await db_manager.bulk_write(FOLDERS, folder_ops, ordered=False) - if bookmark_ops: - await db_manager.bulk_write(BOOKMARKS, bookmark_ops, ordered=False) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to import bookmarks." - ) from exc - return {"foldersUpserted": len(folder_ops), "bookmarksUpserted": len(bookmark_ops)} - - -async def clear_all_bookmarks(ctx: WorkspaceContext) -> dict[str, int]: - flt = apply_workspace_filter(ctx, {"created_by": ctx.uid}) - br = await db_manager.delete_many(BOOKMARKS, flt) - fr = await db_manager.delete_many(FOLDERS, flt) - return {"bookmarksDeleted": br.deleted_count, "foldersDeleted": fr.deleted_count} - - -async def snapshot(ctx: WorkspaceContext) -> BookmarkSnapshotOut: - return BookmarkSnapshotOut( - bookmarks=await list_bookmarks(ctx=ctx, folder_id=None), - folders=await list_folders(ctx=ctx), - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_folders(*, ctx: WorkspaceContext, skip: int = 0, limit: int | None = None) -> list[BookmarkFolderOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - FOLDERS, flt, sort=[("createdAt", 1)], skip=skip, limit=limit or 0 - ) - return [_folder_doc_to_out(d) for d in docs] - - -async def get_folder(ctx: WorkspaceContext, folder_id: str) -> BookmarkFolderOut: - flt = apply_workspace_filter(ctx, {"_id": folder_id, "created_by": ctx.uid}) - doc = await db_manager.find_one(FOLDERS, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Folder not found.") - return _folder_doc_to_out(doc) - - -async def create_folder(ctx: WorkspaceContext, body: BookmarkFolderCreate) -> BookmarkFolderOut: - fid = body.id or new_id() - ts = create_timestamp() - doc: dict[str, Any] = { - "_id": fid, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "name": body.name, - "parentId": body.parentId, - "color": body.color, - "icon": body.icon, - "isExpanded": body.isExpanded if body.isExpanded is not None else False, - "createdAt": ts, - } - await safe_insert(FOLDERS, doc, name="Folder") - return _folder_doc_to_out(doc) - - -async def update_folder(ctx: WorkspaceContext, folder_id: str, body: BookmarkFolderUpdate) -> BookmarkFolderOut: - patch = body.model_dump(exclude_unset=True) - if not patch: - return await get_folder(ctx, folder_id) - flt = apply_workspace_filter(ctx, {"_id": folder_id, "created_by": ctx.uid}) - result = await safe_update_one( - FOLDERS, flt, patch, name="Folder" - ) - return _folder_doc_to_out(result) - - -async def set_folder_expanded(ctx: WorkspaceContext, folder_id: str, is_expanded: bool) -> BookmarkFolderOut: - return await update_folder(ctx, folder_id, BookmarkFolderUpdate(isExpanded=is_expanded)) - - -async def delete_folder(ctx: WorkspaceContext, folder_id: str) -> None: - pipeline = [ - {"$match": apply_workspace_filter(ctx, {"_id": folder_id, "created_by": ctx.uid})}, - { - "$graphLookup": { - "from": FOLDERS, - "startWith": "$_id", - "connectFromField": "_id", - "connectToField": "parentId", - "as": "descendants", - "restrictSearchWithMatch": apply_workspace_filter(ctx, {"created_by": ctx.uid}), - } - }, - {"$project": {"descendants._id": 1}}, - ] - results = await db_manager.aggregate(FOLDERS, pipeline) - if not results: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Folder not found.") - to_remove = [folder_id, *[str(d["_id"]) for d in results[0].get("descendants", [])]] - try: - flt_folders = apply_workspace_filter(ctx, {"_id": {"$in": to_remove}, "created_by": ctx.uid}) - flt_bookmarks = apply_workspace_filter(ctx, {"created_by": ctx.uid, "folderId": {"$in": to_remove}}) - await db_manager.delete_many(FOLDERS, flt_folders) - await db_manager.update_many( - BOOKMARKS, - flt_bookmarks, - {"$set": {"folderId": None, "updatedAt": create_timestamp()}}, - ) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to delete folder." - ) from exc diff --git a/apps/backend/app/api/routes/code_snippets/__init__.py b/apps/backend/app/api/routes/code_snippets/__init__.py deleted file mode 100644 index c8fb9bc4..00000000 --- a/apps/backend/app/api/routes/code_snippets/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Code snippet REST API.""" diff --git a/apps/backend/app/api/routes/code_snippets/api.py b/apps/backend/app/api/routes/code_snippets/api.py deleted file mode 100644 index 5c6f6a71..00000000 --- a/apps/backend/app/api/routes/code_snippets/api.py +++ /dev/null @@ -1,36 +0,0 @@ -from fastapi import APIRouter, Depends, Query - -from app.api.routes.code_snippets import services as snippet_svc -from app.api.routes.code_snippets.schema import CodeSnippetCreate, CodeSnippetOut, CodeSnippetUpdate -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -router = APIRouter(prefix="/code-snippets", tags=["code-snippets"]) - - -@router.get("", response_model=list[CodeSnippetOut], summary="List code snippets for current user") -async def list_snippets( - ctx: WorkspaceContext = Depends(require_permission("code-snippets", "read")), - skip: int = Query(default=0, ge=0), - limit: int | None = Query(default=None, ge=1, le=500), -) -> list[CodeSnippetOut]: - return await snippet_svc.list_code_snippets(ctx, skip=skip, limit=limit) - - -@router.post("", response_model=CodeSnippetOut, summary="Create a code snippet") -async def create_snippet(body: CodeSnippetCreate, ctx: WorkspaceContext = Depends(require_permission("code-snippets", "write"))) -> CodeSnippetOut: - return await snippet_svc.create_code_snippet(ctx, body) - - -@router.patch("/{snippet_id}", response_model=CodeSnippetOut, summary="Update a code snippet") -async def patch_snippet( - snippet_id: str, - body: CodeSnippetUpdate, - ctx: WorkspaceContext = Depends(require_permission("code-snippets", "write")), -) -> CodeSnippetOut: - return await snippet_svc.update_code_snippet(ctx, snippet_id, body) - - -@router.delete("/{snippet_id}", status_code=204, summary="Delete a code snippet") -async def remove_snippet(snippet_id: str, ctx: WorkspaceContext = Depends(require_permission("code-snippets", "delete"))) -> None: - await snippet_svc.delete_code_snippet(ctx, snippet_id) diff --git a/apps/backend/app/api/routes/code_snippets/schema.py b/apps/backend/app/api/routes/code_snippets/schema.py deleted file mode 100644 index 2928637c..00000000 --- a/apps/backend/app/api/routes/code_snippets/schema.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -Code snippets — matches ``CodeSnippet`` in ``apps/web/src/store/snippet-manager-store.ts``. - -``createdAt`` / ``updatedAt`` are Unix ms integers (same as bookmarks). Documents use -``created_by`` = Firebase UID. - -Recommended indexes:: - - db.code_snippets.create_index([("created_by", 1), ("updatedAt", -1)]) -""" - - -from pydantic import BaseModel, ConfigDict, Field - -from app.utils.collection_name import CODE_SNIPPETS - -COLLECTION_CODE_SNIPPETS = CODE_SNIPPETS - - -class CodeSnippetCreate(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str | None = Field(default=None, description="Client-generated id (UUID); optional") - title: str = Field(default="Untitled snippet", min_length=1) - language: str = Field(default="auto", min_length=1) - code: str = "" - tags: list[str] | None = None - pinned: bool | None = None - createdAt: int | None = Field(default=None, description="Unix ms; server defaults if omitted") - updatedAt: int | None = Field(default=None, description="Unix ms; server defaults if omitted") - - -class CodeSnippetUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - title: str | None = Field(default=None, min_length=1) - language: str | None = Field(default=None, min_length=1) - code: str | None = None - tags: list[str] | None = None - pinned: bool | None = None - - -class CodeSnippetOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - title: str - language: str - code: str - tags: list[str] = Field(default_factory=list) - pinned: bool = False - createdAt: int - updatedAt: int diff --git a/apps/backend/app/api/routes/code_snippets/services.py b/apps/backend/app/api/routes/code_snippets/services.py deleted file mode 100644 index 8ce4a00d..00000000 --- a/apps/backend/app/api/routes/code_snippets/services.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import Any - -from fastapi import HTTPException, status - -from app.api.routes.code_snippets.schema import ( - CodeSnippetCreate, - CodeSnippetOut, - CodeSnippetUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.database import db_manager -from app.utils.collection_name import CODE_SNIPPETS as SNIPPETS -from app.utils.crud import safe_delete_one, safe_insert, safe_update_one -from app.utils.utils import create_timestamp, new_id - - -def _doc_to_out(doc: dict[str, Any]) -> CodeSnippetOut: - sid = doc.get("_id") - return CodeSnippetOut( - id=str(sid) if sid is not None else "", - title=str(doc.get("title", "")), - language=str(doc.get("language", "auto")), - code=str(doc.get("code", "")), - tags=list(doc.get("tags") or []), - pinned=bool(doc.get("pinned", False)), - createdAt=int(doc.get("createdAt", 0)), - updatedAt=int(doc.get("updatedAt", 0)), - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_code_snippets(ctx: WorkspaceContext, *, skip: int = 0, limit: int | None = None) -> list[CodeSnippetOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - SNIPPETS, - flt, - sort=[("updatedAt", -1), ("createdAt", -1)], - skip=skip, - limit=limit or 0, - ) - return [_doc_to_out(d) for d in docs] - - -async def create_code_snippet(ctx: WorkspaceContext, body: CodeSnippetCreate) -> CodeSnippetOut: - sid = body.id or new_id() - ts = create_timestamp() - created = body.createdAt if body.createdAt is not None else ts - updated = body.updatedAt if body.updatedAt is not None else ts - doc: dict[str, Any] = { - "_id": sid, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "title": body.title, - "language": body.language, - "code": body.code, - "tags": body.tags or [], - "pinned": body.pinned or False, - "createdAt": created, - "updatedAt": updated, - } - await safe_insert(SNIPPETS, doc, name="Snippet") - return _doc_to_out(doc) - - -async def update_code_snippet(ctx: WorkspaceContext, snippet_id: str, body: CodeSnippetUpdate) -> CodeSnippetOut: - patch = body.model_dump(exclude_unset=True) - if not patch: - return await get_code_snippet(ctx=ctx, snippet_id=snippet_id) - patch["updatedAt"] = create_timestamp() - flt = apply_workspace_filter(ctx, {"_id": snippet_id, "created_by": ctx.uid}) - result = await safe_update_one( - SNIPPETS, - flt, - patch, - name="Snippet", - ) - return _doc_to_out(result) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_code_snippet(*, ctx: WorkspaceContext, snippet_id: str) -> CodeSnippetOut: - flt = apply_workspace_filter(ctx, {"_id": snippet_id, "created_by": ctx.uid}) - doc = await db_manager.find_one(SNIPPETS, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Snippet not found.") - return _doc_to_out(doc) - - -async def delete_code_snippet(ctx: WorkspaceContext, snippet_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": snippet_id, "created_by": ctx.uid}) - await safe_delete_one(SNIPPETS, flt, name="Snippet") diff --git a/apps/backend/app/api/routes/dns_lookup/__init__.py b/apps/backend/app/api/routes/dns_lookup/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/dns_lookup/api.py b/apps/backend/app/api/routes/dns_lookup/api.py deleted file mode 100644 index e967b940..00000000 --- a/apps/backend/app/api/routes/dns_lookup/api.py +++ /dev/null @@ -1,18 +0,0 @@ -# ponytail: stateless tool — no per-user persistence, no workspace scoping needed -from fastapi import APIRouter, Query - -from app.api.routes.dns_lookup import services as svc -from app.api.routes.dns_lookup.schema import DNSLookupResult - -router = APIRouter(prefix="/dns-lookup", tags=["dns-lookup"]) - - -@router.get("/lookup", response_model=DNSLookupResult, summary="Perform DNS lookup for a domain") -async def lookup( - domain: str = Query(..., description="Domain name to look up"), - record_types: list[str] = Query( - default=["A", "AAAA", "MX", "TXT", "NS", "CNAME"], - description="DNS record types to query", - ), -) -> DNSLookupResult: - return await svc.lookup_domain(domain, record_types) diff --git a/apps/backend/app/api/routes/dns_lookup/schema.py b/apps/backend/app/api/routes/dns_lookup/schema.py deleted file mode 100644 index cfc7a483..00000000 --- a/apps/backend/app/api/routes/dns_lookup/schema.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Any -from pydantic import BaseModel - - -class DNSRecord(BaseModel): - type: str - value: str - ttl: int | None = None - priority: int | None = None - extra: dict[str, Any] | None = None - - -class DNSLookupResult(BaseModel): - domain: str - records: dict[str, list[DNSRecord]] - errors: dict[str, str] = {} diff --git a/apps/backend/app/api/routes/dns_lookup/services.py b/apps/backend/app/api/routes/dns_lookup/services.py deleted file mode 100644 index 68f8fc3e..00000000 --- a/apps/backend/app/api/routes/dns_lookup/services.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import annotations - -import asyncio -import re -from typing import Any - -import dns.asyncresolver -import dns.exception -import dns.rdatatype -import dns.resolver - -from app.api.routes.dns_lookup.schema import DNSLookupResult, DNSRecord -from app.core.cache import cached - -VALID_RECORD_TYPES = {"A", "AAAA", "MX", "TXT", "NS", "CNAME", "SOA", "CAA", "PTR"} -_DOMAIN_RE = re.compile(r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$") - - -def _is_valid_domain(domain: str) -> bool: - return bool(_DOMAIN_RE.match(domain)) and len(domain) <= 253 - - -@cached(ns="dns_lookup", ttl=3600, scope="global") -async def lookup(*, host: str, record_type: str = "A") -> tuple[list[DNSRecord], str | None]: - """Cached DNS lookup for a single record type. - - Pure function of (host, record_type) — safe to cache globally for 1h. - """ - domain = host.strip().lower().rstrip(".") - resolver = dns.asyncresolver.Resolver() - resolver.timeout = 5 - resolver.lifetime = 10 - return await _resolve_type(resolver, domain, record_type) - - -async def _resolve_type(resolver: dns.asyncresolver.Resolver, domain: str, rtype: str) -> tuple[list[DNSRecord], str | None]: - try: - answers = await resolver.resolve(domain, rtype, raise_on_no_answer=False) - if answers.rrset is None: - return [], None - ttl = answers.rrset.ttl - records: list[DNSRecord] = [] - for rdata in answers.rrset: - rec = _parse_rdata(rtype, rdata, ttl) - if rec: - records.append(rec) - return records, None - except dns.resolver.NXDOMAIN: - return [], "NXDOMAIN: Domain does not exist" - except dns.resolver.NoNameservers: - return [], "No nameservers available" - except dns.resolver.Timeout: - return [], "Query timed out" - except dns.exception.DNSException as exc: - return [], str(exc) - - -def _parse_rdata(rtype: str, rdata: Any, ttl: int) -> DNSRecord | None: - if rtype == "A": - return DNSRecord(type=rtype, value=str(rdata.address), ttl=ttl) - if rtype == "AAAA": - return DNSRecord(type=rtype, value=str(rdata.address), ttl=ttl) - if rtype == "MX": - return DNSRecord(type=rtype, value=str(rdata.exchange).rstrip("."), ttl=ttl, priority=rdata.preference) - if rtype == "TXT": - value = " ".join(part.decode("utf-8", errors="replace") for part in rdata.strings) - return DNSRecord(type=rtype, value=value, ttl=ttl) - if rtype == "NS": - return DNSRecord(type=rtype, value=str(rdata.target).rstrip("."), ttl=ttl) - if rtype == "CNAME": - return DNSRecord(type=rtype, value=str(rdata.target).rstrip("."), ttl=ttl) - if rtype == "SOA": - return DNSRecord( - type=rtype, - value=str(rdata.mname).rstrip("."), - ttl=ttl, - extra={ - "rname": str(rdata.rname).rstrip("."), - "serial": rdata.serial, - "refresh": rdata.refresh, - "retry": rdata.retry, - "expire": rdata.expire, - "minimum": rdata.minimum, - }, - ) - if rtype == "CAA": - tag = rdata.tag.decode("utf-8", errors="replace") if isinstance(rdata.tag, bytes) else rdata.tag - value = rdata.value.decode("utf-8", errors="replace") if isinstance(rdata.value, bytes) else rdata.value - return DNSRecord(type=rtype, value=f'{rdata.flags} {tag} "{value}"', ttl=ttl) - if rtype == "PTR": - return DNSRecord(type=rtype, value=str(rdata.target).rstrip("."), ttl=ttl) - return DNSRecord(type=rtype, value=str(rdata), ttl=ttl) - - -async def lookup_domain(domain: str, record_types: list[str]) -> DNSLookupResult: - domain = domain.strip().lower().rstrip(".") - if not _is_valid_domain(domain): - return DNSLookupResult(domain=domain, records={}, errors={"_": "Invalid domain name"}) - - requested = [rt.upper() for rt in record_types if rt.upper() in VALID_RECORD_TYPES] - if not requested: - requested = ["A", "AAAA", "MX", "TXT", "NS", "CNAME"] - - tasks = {rtype: lookup(host=domain, record_type=rtype) for rtype in requested} - results = await asyncio.gather(*tasks.values()) - - records: dict[str, list[DNSRecord]] = {} - errors: dict[str, str] = {} - for rtype, (recs, err) in zip(tasks.keys(), results): - if err and rtype == requested[0] and "NXDOMAIN" in err: - return DNSLookupResult(domain=domain, records={}, errors={"_": err}) - if err: - errors[rtype] = err - else: - records[rtype] = recs - - return DNSLookupResult(domain=domain, records=records, errors=errors) diff --git a/apps/backend/app/api/routes/environment_manager/__init__.py b/apps/backend/app/api/routes/environment_manager/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/environment_manager/api.py b/apps/backend/app/api/routes/environment_manager/api.py deleted file mode 100644 index e5c9b63b..00000000 --- a/apps/backend/app/api/routes/environment_manager/api.py +++ /dev/null @@ -1,66 +0,0 @@ - -from fastapi import APIRouter, Depends, Query - -from app.api.routes.environment_manager import services as env_svc -from app.api.routes.environment_manager.schema import ( - EnvSetEntryCreate, - EnvSetEntryOut, - EnvSetEntryUpdate, -) -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -router = APIRouter(prefix="/environment-manager", tags=["environment-manager"]) - - -@router.get( - "/entries", - response_model=list[EnvSetEntryOut], - summary="List encrypted environment sets (per project / environment)", -) -async def list_entries( - ctx: WorkspaceContext = Depends(require_permission("environment-manager", "read")), - limit: int | None = Query(default=None, ge=1, le=1000), - offset: int = Query(default=0, ge=0), -) -> list[EnvSetEntryOut]: - return await env_svc.list_entries(ctx, limit=limit, offset=offset) - - -@router.post( - "/entries", - response_model=EnvSetEntryOut, - summary="Create environment set (encrypted blob)", -) -async def create_entry(body: EnvSetEntryCreate, ctx: WorkspaceContext = Depends(require_permission("environment-manager", "write"))) -> EnvSetEntryOut: - return await env_svc.create_entry(ctx, body) - - -@router.get( - "/entries/{entry_id}", - response_model=EnvSetEntryOut, - summary="Get one environment set", -) -async def get_entry(entry_id: str, ctx: WorkspaceContext = Depends(require_permission("environment-manager", "read"))) -> EnvSetEntryOut: - return await env_svc.get_entry(ctx=ctx, entry_id=entry_id) - - -@router.patch( - "/entries/{entry_id}", - response_model=EnvSetEntryOut, - summary="Update environment set (encrypted blob)", -) -async def patch_entry( - entry_id: str, - body: EnvSetEntryUpdate, - ctx: WorkspaceContext = Depends(require_permission("environment-manager", "write")), -) -> EnvSetEntryOut: - return await env_svc.update_entry(ctx, entry_id, body) - - -@router.delete( - "/entries/{entry_id}", - status_code=204, - summary="Delete environment set", -) -async def delete_entry(entry_id: str, ctx: WorkspaceContext = Depends(require_permission("environment-manager", "delete"))) -> None: - await env_svc.delete_entry(ctx, entry_id) diff --git a/apps/backend/app/api/routes/environment_manager/schema.py b/apps/backend/app/api/routes/environment_manager/schema.py deleted file mode 100644 index 4ade2ab5..00000000 --- a/apps/backend/app/api/routes/environment_manager/schema.py +++ /dev/null @@ -1,27 +0,0 @@ - -from pydantic import BaseModel, ConfigDict, Field - - -class EnvSetEntryCreate(BaseModel): - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - createdAt: int | None = Field(default=None, ge=0) - updatedAt: int | None = Field(default=None, ge=0) - - -class EnvSetEntryUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - updatedAt: int | None = Field(default=None, ge=0) - - -class EnvSetEntryOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - encryptedData: str - iv: str - createdAt: int - updatedAt: int diff --git a/apps/backend/app/api/routes/environment_manager/services.py b/apps/backend/app/api/routes/environment_manager/services.py deleted file mode 100644 index 84ffc5d9..00000000 --- a/apps/backend/app/api/routes/environment_manager/services.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import Any - -from fastapi import HTTPException, status - -from app.api.routes.environment_manager.schema import ( - EnvSetEntryCreate, - EnvSetEntryOut, - EnvSetEntryUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.database import db_manager -from app.utils.collection_name import ENV_MANAGER_ENTRIES -from app.utils.crud import safe_delete_one, safe_insert, safe_update_one -from app.utils.utils import create_timestamp, new_id - - -def _entry_doc_to_out(doc: dict[str, Any], *, entry_id: str) -> EnvSetEntryOut: - created_at = int(doc.get("createdAt", 0)) or create_timestamp() - updated_at = int(doc.get("updatedAt", 0)) or created_at - return EnvSetEntryOut( - id=entry_id, - encryptedData=str(doc.get("encryptedData", "")), - iv=str(doc.get("iv", "")), - createdAt=created_at, - updatedAt=updated_at, - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_entries(ctx: WorkspaceContext, *, limit: int | None = None, offset: int = 0) -> list[EnvSetEntryOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - ENV_MANAGER_ENTRIES, - flt, - sort=[("updatedAt", -1), ("createdAt", -1)], - skip=max(0, offset), - limit=limit or 0, - ) - return [_entry_doc_to_out(d, entry_id=str(d.get("_id", ""))) for d in docs] - - -async def create_entry(ctx: WorkspaceContext, body: EnvSetEntryCreate) -> EnvSetEntryOut: - eid = new_id() - ts = create_timestamp() - created_at = int(body.createdAt) if body.createdAt is not None else ts - updated_at = int(body.updatedAt) if body.updatedAt is not None else created_at - - doc: dict[str, Any] = { - "_id": eid, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "encryptedData": body.encryptedData, - "iv": body.iv, - "createdAt": created_at, - "updatedAt": updated_at, - } - await safe_insert(ENV_MANAGER_ENTRIES, doc, name="Entry") - return _entry_doc_to_out(doc, entry_id=eid) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_entry(*, ctx: WorkspaceContext, entry_id: str) -> EnvSetEntryOut: - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - doc = await db_manager.find_one(ENV_MANAGER_ENTRIES, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entry not found.") - return _entry_doc_to_out(doc, entry_id=entry_id) - - -async def update_entry(ctx: WorkspaceContext, entry_id: str, body: EnvSetEntryUpdate) -> EnvSetEntryOut: - ts_updated = int(body.updatedAt) if body.updatedAt is not None else create_timestamp() - patch: dict[str, Any] = { - "encryptedData": body.encryptedData, - "iv": body.iv, - "updatedAt": ts_updated, - } - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - result = await safe_update_one( - ENV_MANAGER_ENTRIES, - flt, - patch, - name="Entry", - ) - return _entry_doc_to_out(result, entry_id=entry_id) - - -async def delete_entry(ctx: WorkspaceContext, entry_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - await safe_delete_one( - ENV_MANAGER_ENTRIES, flt, name="Entry" - ) diff --git a/apps/backend/app/api/routes/feedback/__init__.py b/apps/backend/app/api/routes/feedback/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/feedback/api.py b/apps/backend/app/api/routes/feedback/api.py deleted file mode 100644 index 77a71cde..00000000 --- a/apps/backend/app/api/routes/feedback/api.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Annotated, Optional - -from fastapi import APIRouter, Cookie, Depends, Header, Request - -from app.api.routes.auth.tokens import decode_access_token -from app.api.routes.feedback.schema import FeedbackCreate, FeedbackOut -from app.api.routes.feedback import services as feedback_svc -from app.core.auth_cookies import ACCESS_COOKIE_NAME -from app.core.limiter import limiter - - -router = APIRouter(prefix="/feedback", tags=["feedback"]) - - -def get_optional_uid( - authorization: Annotated[str | None, Header(alias="Authorization")] = None, - mdt_at: Annotated[str | None, Cookie(alias=ACCESS_COOKIE_NAME)] = None, -) -> Optional[str]: - token: str | None = None - if authorization: - scheme, _, value = authorization.partition(" ") - if scheme.lower() == "bearer" and value.strip(): - token = value.strip() - if not token and mdt_at and mdt_at.strip(): - token = mdt_at.strip() - if not token: - return None - try: - return decode_access_token(token) - except Exception: - return None - - -@router.post("", response_model=FeedbackOut, summary="Submit feedback") -@limiter.limit("5/hour") -async def submit_feedback( - request: Request, - body: FeedbackCreate, - uid: Optional[str] = Depends(get_optional_uid), -) -> FeedbackOut: - return await feedback_svc.create_feedback(body, uid) diff --git a/apps/backend/app/api/routes/feedback/schema.py b/apps/backend/app/api/routes/feedback/schema.py deleted file mode 100644 index 2dbf7dfc..00000000 --- a/apps/backend/app/api/routes/feedback/schema.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Literal, Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class FeedbackCreate(BaseModel): - model_config = ConfigDict(extra="ignore") - - type: Literal["bug", "feature", "general"] - message: str = Field(min_length=10, max_length=2000) - rating: Optional[int] = Field(default=None, ge=1, le=5) - email: Optional[str] = Field(default=None, max_length=254) - page: Optional[str] = Field(default=None, max_length=500) - - -class FeedbackOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - type: str - message: str - rating: Optional[int] - email: Optional[str] - page: Optional[str] - userId: Optional[str] - createdAt: str diff --git a/apps/backend/app/api/routes/feedback/services.py b/apps/backend/app/api/routes/feedback/services.py deleted file mode 100644 index 90f57275..00000000 --- a/apps/backend/app/api/routes/feedback/services.py +++ /dev/null @@ -1,48 +0,0 @@ -from datetime import datetime, timezone -from typing import Any, Optional - -from app.api.routes.feedback.schema import FeedbackCreate, FeedbackOut -from app.utils.collection_name import FEEDBACK -from app.utils.utils import new_id -from app.database import db_manager - - -def _isoformat_utc(dt: datetime) -> str: - if dt.tzinfo is None: - dt = dt.replace(tzinfo=timezone.utc) - return dt.astimezone(timezone.utc).isoformat() - - -def _doc_to_out(doc: dict[str, Any]) -> FeedbackOut: - created_at = doc.get("createdAt") - if isinstance(created_at, datetime): - created_at = _isoformat_utc(created_at) - elif not isinstance(created_at, str): - created_at = _isoformat_utc(datetime.now(timezone.utc)) - - return FeedbackOut( - id=str(doc.get("_id", "")), - type=str(doc.get("type", "general")), - message=str(doc.get("message", "")), - rating=doc.get("rating"), - email=doc.get("email"), - page=doc.get("page"), - userId=doc.get("userId"), - createdAt=created_at, - ) - - -async def create_feedback(body: FeedbackCreate, uid: Optional[str] = None) -> FeedbackOut: - ts = datetime.now(timezone.utc) - doc = { - "_id": new_id(), - "type": body.type, - "message": body.message, - "rating": body.rating, - "email": body.email, - "page": body.page, - "userId": uid, - "createdAt": ts, - } - await db_manager.insert_one(FEEDBACK, doc) - return _doc_to_out(doc) diff --git a/apps/backend/app/api/routes/game_scores/__init__.py b/apps/backend/app/api/routes/game_scores/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/game_scores/api.py b/apps/backend/app/api/routes/game_scores/api.py deleted file mode 100644 index 3148dd15..00000000 --- a/apps/backend/app/api/routes/game_scores/api.py +++ /dev/null @@ -1,31 +0,0 @@ -from fastapi import APIRouter, Depends, Query - -from app.api.routes.auth.services import get_current_uid -from app.api.routes.game_scores.schema import GameScoreCreate, GameScoreOut -from app.api.routes.game_scores import services as svc - -router = APIRouter(tags=["game-scores"]) - - -@router.get( - "/game-scores", - response_model=list[GameScoreOut], - summary="Get all scores for the current user filtered by game name", -) -async def get_scores( - game: str = Query(..., min_length=1, description="Game name, e.g. 'sudoku'"), - uid: str = Depends(get_current_uid), -) -> list[GameScoreOut]: - return await svc.list_scores(uid, game) - - -@router.post( - "/game-scores", - response_model=GameScoreOut, - summary="Save or update best score for a game stage (upserts if new score is higher)", -) -async def save_score( - body: GameScoreCreate, - uid: str = Depends(get_current_uid), -) -> GameScoreOut: - return await svc.upsert_score(uid, body) diff --git a/apps/backend/app/api/routes/game_scores/schema.py b/apps/backend/app/api/routes/game_scores/schema.py deleted file mode 100644 index 19ea63c4..00000000 --- a/apps/backend/app/api/routes/game_scores/schema.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Literal, Optional -from pydantic import BaseModel, ConfigDict, Field - -GameName = str -Level = Literal["easy", "medium", "hard"] - - -class GameScoreCreate(BaseModel): - model_config = ConfigDict(extra="ignore") - - game: GameName = Field(min_length=1) - level: Level - stage: int = Field(ge=1, le=150) - score: int = Field(ge=0) - time_seconds: int = Field(ge=0) - - -class GameScoreOut(BaseModel): - id: str - game: GameName - level: Level - stage: int - score: int - time_seconds: int - created_by: str - completed_at: int diff --git a/apps/backend/app/api/routes/game_scores/services.py b/apps/backend/app/api/routes/game_scores/services.py deleted file mode 100644 index 3ab4eaef..00000000 --- a/apps/backend/app/api/routes/game_scores/services.py +++ /dev/null @@ -1,59 +0,0 @@ -import time -from app.database import db_manager - -from app.utils.collection_name import GAME_SCORES -from app.api.routes.game_scores.schema import GameScoreCreate, GameScoreOut - - -def _to_out(doc: dict) -> GameScoreOut: - return GameScoreOut( - id=str(doc["_id"]), - game=doc["game"], - level=doc["level"], - stage=doc["stage"], - score=doc["score"], - time_seconds=doc["time_seconds"], - created_by=doc["created_by"], - completed_at=doc["completed_at"], - ) - - -async def list_scores(uid: str, game: str) -> list[GameScoreOut]: - docs = await db_manager.find(GAME_SCORES, {"created_by": uid, "game": game}) - return [_to_out(d) for d in docs] - - -async def upsert_score(uid: str, body: GameScoreCreate) -> GameScoreOut: - existing = await db_manager.find_one(GAME_SCORES, { - "created_by": uid, - "game": body.game, - "level": body.level, - "stage": body.stage, - }) - - now = int(time.time() * 1000) - - if existing and existing.get("score", 0) >= body.score: - return _to_out(existing) - - if existing: - await db_manager.update_one(GAME_SCORES, {"_id": existing["_id"]}, {"$set": { - "score": body.score, - "time_seconds": body.time_seconds, - "completed_at": now, - }}) - updated = await db_manager.find_one(GAME_SCORES, {"_id": existing["_id"]}) - return _to_out(updated) - - doc = { - "created_by": uid, - "game": body.game, - "level": body.level, - "stage": body.stage, - "score": body.score, - "time_seconds": body.time_seconds, - "completed_at": now, - } - result = await db_manager.insert_one(GAME_SCORES, doc) - doc["_id"] = result.inserted_id - return _to_out(doc) diff --git a/apps/backend/app/api/routes/json_formatter/__init__.py b/apps/backend/app/api/routes/json_formatter/__init__.py deleted file mode 100644 index fb0a2f80..00000000 --- a/apps/backend/app/api/routes/json_formatter/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API route modules.""" diff --git a/apps/backend/app/api/routes/json_formatter/api.py b/apps/backend/app/api/routes/json_formatter/api.py deleted file mode 100644 index c8ddbb31..00000000 --- a/apps/backend/app/api/routes/json_formatter/api.py +++ /dev/null @@ -1,48 +0,0 @@ -from fastapi import APIRouter, Depends, Query - -from app.api.routes.json_formatter import services as jf_svc -from app.api.routes.json_formatter.schema import ( - JsonFormatterDocumentCreate, - JsonFormatterDocumentOut, - JsonFormatterDocumentUpdate, -) -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -router = APIRouter(prefix="/json-formatter", tags=["json-formatter"]) - - -@router.get("/documents", response_model=list[JsonFormatterDocumentOut], summary="List saved JSON documents") -async def list_documents( - ctx: WorkspaceContext = Depends(require_permission("json-formatter", "read")), - skip: int = Query(default=0, ge=0), - limit: int = Query(default=200, ge=1, le=1000), -) -> list[JsonFormatterDocumentOut]: - return await jf_svc.list_documents_paginated(ctx, skip=skip, limit=limit) - - -@router.post("/documents", response_model=JsonFormatterDocumentOut, summary="Save new JSON document") -async def create_document( - body: JsonFormatterDocumentCreate, - ctx: WorkspaceContext = Depends(require_permission("json-formatter", "write")), -) -> JsonFormatterDocumentOut: - return await jf_svc.create_document(ctx, body) - - -@router.get("/documents/{document_id}", response_model=JsonFormatterDocumentOut, summary="Get one JSON document") -async def get_document(document_id: str, ctx: WorkspaceContext = Depends(require_permission("json-formatter", "read"))) -> JsonFormatterDocumentOut: - return await jf_svc.get_document(ctx, document_id) - - -@router.patch("/documents/{document_id}", response_model=JsonFormatterDocumentOut, summary="Update JSON document") -async def patch_document( - document_id: str, - body: JsonFormatterDocumentUpdate, - ctx: WorkspaceContext = Depends(require_permission("json-formatter", "write")), -) -> JsonFormatterDocumentOut: - return await jf_svc.update_document(ctx, document_id, body) - - -@router.delete("/documents/{document_id}", status_code=204, summary="Delete JSON document") -async def remove_document(document_id: str, ctx: WorkspaceContext = Depends(require_permission("json-formatter", "delete"))) -> None: - await jf_svc.delete_document(ctx, document_id) diff --git a/apps/backend/app/api/routes/json_formatter/schema.py b/apps/backend/app/api/routes/json_formatter/schema.py deleted file mode 100644 index 61e1fbec..00000000 --- a/apps/backend/app/api/routes/json_formatter/schema.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Literal, Optional - -from pydantic import BaseModel, ConfigDict, Field - -JsonFormatterPane = Literal["left", "right"] - - -class JsonFormatterDocumentCreate(BaseModel): - title: str = Field(min_length=1) - pane: JsonFormatterPane - content: str = "" - - -class JsonFormatterDocumentUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - title: Optional[str] = Field(default=None, min_length=1) - pane: Optional[JsonFormatterPane] = None - content: Optional[str] = None - - -class JsonFormatterDocumentOut(BaseModel): - id: str - title: str - pane: JsonFormatterPane - content: str - createdAt: str - updatedAt: str diff --git a/apps/backend/app/api/routes/json_formatter/services.py b/apps/backend/app/api/routes/json_formatter/services.py deleted file mode 100644 index 74698d33..00000000 --- a/apps/backend/app/api/routes/json_formatter/services.py +++ /dev/null @@ -1,139 +0,0 @@ -from datetime import datetime, timezone -from typing import Any - -from bson import ObjectId -from bson.errors import InvalidId -from fastapi import HTTPException, status -from pymongo.errors import PyMongoError - -from app.api.routes.json_formatter.schema import ( - JsonFormatterDocumentCreate, - JsonFormatterDocumentOut, - JsonFormatterDocumentUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.utils.collection_name import JSON_FORMATTER_DOCUMENTS as JSON -from app.database import db_manager - - -def _parse_oid(doc_id: str) -> ObjectId: - try: - return ObjectId(doc_id) - except InvalidId as exc: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Invalid document id.", - ) from exc - - -def _format_ts(value: Any) -> str: - if value is None: - return "" - if isinstance(value, datetime): - if value.tzinfo is None: - value = value.replace(tzinfo=timezone.utc) - return value.astimezone(timezone.utc).isoformat() - if isinstance(value, str): - return value - return str(value) - - -def _doc_to_out(doc: dict[str, Any]) -> JsonFormatterDocumentOut: - oid = doc.get("_id") - pane = doc.get("pane") - if pane not in ("left", "right"): - pane = "left" - return JsonFormatterDocumentOut( - id=str(oid) if oid is not None else "", - title=doc.get("title", ""), - pane=pane, - content=doc.get("content") if isinstance(doc.get("content"), str) else "", - createdAt=_format_ts(doc.get("createdAt")) or "", - updatedAt=_format_ts(doc.get("updatedAt")) or "", - ) - - -async def list_documents(ctx: WorkspaceContext) -> list[JsonFormatterDocumentOut]: - base = {} - flt = apply_legacy_or_filter(ctx, base, user_field="created_by") - docs = await db_manager.find(JSON, flt, sort=[("updatedAt", -1)]) - return [_doc_to_out(d) for d in docs] - - -async def list_documents_paginated(ctx: WorkspaceContext, *, skip: int = 0, limit: int = 200) -> list[JsonFormatterDocumentOut]: - base = {} - flt = apply_legacy_or_filter(ctx, base, user_field="created_by") - docs = await db_manager.find( - JSON, - flt, - sort=[("updatedAt", -1)], - skip=max(0, skip), - limit=max(1, limit), - ) - return [_doc_to_out(d) for d in docs] - - -async def create_document(ctx: WorkspaceContext, body: JsonFormatterDocumentCreate) -> JsonFormatterDocumentOut: - now = datetime.now(timezone.utc) - doc: dict[str, Any] = { - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "title": body.title, - "pane": body.pane, - "content": body.content, - "createdAt": now, - "updatedAt": now, - } - try: - result = await db_manager.insert_one(JSON, doc) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create JSON formatter document.", - ) from exc - doc["_id"] = result.inserted_id - return _doc_to_out(doc) - - -async def get_document(ctx: WorkspaceContext, doc_id: str) -> JsonFormatterDocumentOut: - oid = _parse_oid(doc_id) - flt = apply_workspace_filter(ctx, {"_id": oid}) - doc = await db_manager.find_one(JSON, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found.") - return _doc_to_out(doc) - - -async def update_document(ctx: WorkspaceContext, doc_id: str, body: JsonFormatterDocumentUpdate) -> JsonFormatterDocumentOut: - oid = _parse_oid(doc_id) - flt = apply_workspace_filter(ctx, {"_id": oid}) - existing = await db_manager.find_one(JSON, flt) - if not existing: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found.") - - patch = body.model_dump(exclude_unset=True) - if not patch: - return _doc_to_out(existing) - - patch["updatedAt"] = datetime.now(timezone.utc) - try: - await db_manager.update_one(JSON, flt, {"$set": patch}) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update document." - ) from exc - updated = await db_manager.find_one(JSON, flt) - return _doc_to_out(updated) # type: ignore[arg-type] - - -async def delete_document(ctx: WorkspaceContext, doc_id: str) -> None: - oid = _parse_oid(doc_id) - flt = apply_workspace_filter(ctx, {"_id": oid}) - result = await db_manager.delete_one(JSON, flt) - if result.deleted_count == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found.") diff --git a/apps/backend/app/api/routes/nosql/__init__.py b/apps/backend/app/api/routes/nosql/__init__.py deleted file mode 100644 index ab799676..00000000 --- a/apps/backend/app/api/routes/nosql/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""NoSQL Explorer backend routes.""" - diff --git a/apps/backend/app/api/routes/nosql/api.py b/apps/backend/app/api/routes/nosql/api.py deleted file mode 100644 index 91319bc6..00000000 --- a/apps/backend/app/api/routes/nosql/api.py +++ /dev/null @@ -1,41 +0,0 @@ -from fastapi import APIRouter, Depends - -from app.api.routes.nosql.schema import ConnectionCreate, ConnectionOut, ConnectionUpdate -from app.api.routes.nosql import services as nosql_svc -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - - -router = APIRouter(prefix="/nosql", tags=["nosql"]) - - -@router.get("/connections", response_model=list[ConnectionOut], summary="List saved MongoDB connections") -async def list_connections(ctx: WorkspaceContext = Depends(require_permission("nosql-explorer", "read"))) -> list[ConnectionOut]: - return await nosql_svc.list_connections(ctx) - - -@router.post("/connections", response_model=ConnectionOut, summary="Save a connection") -async def save_connection(body: ConnectionCreate, ctx: WorkspaceContext = Depends(require_permission("nosql-explorer", "write"))) -> ConnectionOut: - return await nosql_svc.create_connection(ctx, body) - - -@router.patch( - "/connections/{connection_id}", - response_model=ConnectionOut, - summary="Update a saved connection", -) -async def update_connection( - connection_id: str, - body: ConnectionUpdate, - ctx: WorkspaceContext = Depends(require_permission("nosql-explorer", "write")), -) -> ConnectionOut: - return await nosql_svc.update_connection(ctx, connection_id, body) - - -@router.delete( - "/connections/{connection_id}", - status_code=204, - summary="Delete a saved connection", -) -async def delete_connection(connection_id: str, ctx: WorkspaceContext = Depends(require_permission("nosql-explorer", "delete"))) -> None: - await nosql_svc.delete_connection(ctx, connection_id) diff --git a/apps/backend/app/api/routes/nosql/schema.py b/apps/backend/app/api/routes/nosql/schema.py deleted file mode 100644 index ddcfb8b7..00000000 --- a/apps/backend/app/api/routes/nosql/schema.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class ConnectionCreate(BaseModel): - """ - The raw connectionString is encrypted client-side (AES-GCM / master key) - before being sent. The server stores only the ciphertext blob. - """ - model_config = ConfigDict(extra="ignore") - - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - name: Optional[str] = Field(default=None, min_length=1) - - -class ConnectionUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - encryptedData: Optional[str] = Field(default=None, min_length=1) - iv: Optional[str] = Field(default=None, min_length=1) - name: Optional[str] = Field(default=None, min_length=1) - - -class ConnectionOut(BaseModel): - """ - Returned to the client. connectionString is absent — the client decrypts - encryptedData locally using the master key. - """ - model_config = ConfigDict(extra="ignore") - - id: str - userId: str - encryptedData: str - iv: str - name: str - createdAt: int - lastUsedAt: int - updatedAt: int diff --git a/apps/backend/app/api/routes/nosql/services.py b/apps/backend/app/api/routes/nosql/services.py deleted file mode 100644 index af6addd0..00000000 --- a/apps/backend/app/api/routes/nosql/services.py +++ /dev/null @@ -1,103 +0,0 @@ -from typing import Any - -from fastapi import HTTPException, status -from pymongo.errors import PyMongoError - -from app.api.routes.nosql.schema import ConnectionCreate, ConnectionOut, ConnectionUpdate -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.utils.collection_name import NOSQL_CONNECTIONS -from app.utils.utils import create_timestamp, new_id -from app.database import db_manager - - -def _doc_to_out(doc: dict[str, Any], *, connection_id: str) -> ConnectionOut: - created_at = int(doc.get("createdAt", 0)) or create_timestamp() - last_used_at = int(doc.get("lastUsedAt", 0)) or created_at - # Content-edit clock for sync LWW. Falls back to lastUsedAt for legacy docs - # written before updatedAt existed. - updated_at = int(doc.get("updatedAt", 0)) or last_used_at - return ConnectionOut( - id=connection_id, - userId=str(doc.get("created_by", "")), - encryptedData=str(doc.get("encryptedData", "")), - iv=str(doc.get("iv", "")), - name=str(doc.get("name", "")), - createdAt=created_at, - lastUsedAt=last_used_at, - updatedAt=updated_at, - ) - - -async def list_connections(ctx: WorkspaceContext) -> list[ConnectionOut]: - flt = apply_legacy_or_filter(ctx, {"encryptedData": {"$exists": True}, "iv": {"$exists": True}}, user_field="created_by") - docs = await db_manager.find( - NOSQL_CONNECTIONS, - flt, - sort=[("lastUsedAt", -1), ("createdAt", -1)], - ) - return [_doc_to_out(doc, connection_id=str(doc.get("_id", ""))) for doc in docs] - - -async def create_connection(ctx: WorkspaceContext, body: ConnectionCreate) -> ConnectionOut: - ts = create_timestamp() - _id = new_id() - doc: dict[str, Any] = { - "_id": _id, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "encryptedData": body.encryptedData, - "iv": body.iv, - "name": body.name or "My Connection", - "createdAt": ts, - "lastUsedAt": ts, - "updatedAt": ts, - } - try: - await db_manager.insert_one(NOSQL_CONNECTIONS, doc) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create connection." - ) from exc - return _doc_to_out(doc, connection_id=_id) - - -async def update_connection(ctx: WorkspaceContext, connection_id: str, body: ConnectionUpdate) -> ConnectionOut: - flt = apply_workspace_filter(ctx, {"_id": connection_id, "created_by": ctx.uid}) - existing = await db_manager.find_one(NOSQL_CONNECTIONS, flt) - if not existing: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - - ts = create_timestamp() - # updatedAt is the sync LWW clock — it advances on every content edit (unlike - # lastUsedAt, which also moves on a bare connect/touch). - patch: dict[str, Any] = {"lastUsedAt": ts, "updatedAt": ts} - if body.encryptedData is not None: - patch["encryptedData"] = body.encryptedData - if body.iv is not None: - patch["iv"] = body.iv - if body.name is not None: - patch["name"] = body.name - - try: - await db_manager.update_one(NOSQL_CONNECTIONS, flt, {"$set": patch}) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update connection." - ) from exc - - updated = await db_manager.find_one(NOSQL_CONNECTIONS, flt) - if not updated: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - return _doc_to_out(updated, connection_id=connection_id) - - -async def delete_connection(ctx: WorkspaceContext, connection_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": connection_id, "created_by": ctx.uid}) - res = await db_manager.delete_one(NOSQL_CONNECTIONS, flt) - if res.deleted_count == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") diff --git a/apps/backend/app/api/routes/notes/__init__.py b/apps/backend/app/api/routes/notes/__init__.py deleted file mode 100644 index fb0a2f80..00000000 --- a/apps/backend/app/api/routes/notes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API route modules.""" diff --git a/apps/backend/app/api/routes/notes/api.py b/apps/backend/app/api/routes/notes/api.py deleted file mode 100644 index 1ba7333b..00000000 --- a/apps/backend/app/api/routes/notes/api.py +++ /dev/null @@ -1,42 +0,0 @@ - -from fastapi import APIRouter, Depends, Query - -from app.api.routes.notes import services as note_svc -from app.api.routes.notes.schema import NoteCreate, NoteOut, NoteUpdate -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -router = APIRouter(prefix="/notes", tags=["notes"]) - - -@router.get("", response_model=list[NoteOut], summary="List notes for current user") -async def list_notes( - ctx: WorkspaceContext = Depends(require_permission("notes", "read")), - skip: int = Query(default=0, ge=0), - limit: int = Query(default=200, ge=1, le=1000), -) -> list[NoteOut]: - return await note_svc.list_notes_paginated(ctx=ctx, skip=skip, limit=limit) - - -@router.get("/{note_id}", response_model=NoteOut, summary="Get a single note with full content") -async def get_note(note_id: str, ctx: WorkspaceContext = Depends(require_permission("notes", "read"))) -> NoteOut: - return await note_svc.get_note(ctx=ctx, note_id=note_id) - - -@router.post("", response_model=NoteOut, summary="Create a note") -async def create_note(body: NoteCreate, ctx: WorkspaceContext = Depends(require_permission("notes", "write"))) -> NoteOut: - return await note_svc.create_note(ctx, body) - - -@router.patch("/{note_id}", response_model=NoteOut, summary="Update a note (partial)") -async def patch_note(note_id: str, body: NoteUpdate, ctx: WorkspaceContext = Depends(require_permission("notes", "write"))) -> NoteOut: - return await note_svc.update_note(ctx, note_id, body) - - -@router.delete("/{note_id}", status_code=204, summary="Delete a note") -async def delete_note( - note_id: str, - recursive: bool = Query(default=True, description="Delete descendants as well"), - ctx: WorkspaceContext = Depends(require_permission("notes", "delete")), -) -> None: - await note_svc.delete_note(ctx, note_id, recursive=recursive) diff --git a/apps/backend/app/api/routes/notes/schema.py b/apps/backend/app/api/routes/notes/schema.py deleted file mode 100644 index 4b1110ce..00000000 --- a/apps/backend/app/api/routes/notes/schema.py +++ /dev/null @@ -1,92 +0,0 @@ -import json -from typing import Any - -from pydantic import BaseModel, ConfigDict, Field, field_validator - -_CONTENT_MAX_BYTES = 1_000_000 # 1 MB -_TITLE_MAX_LEN = 500 -_TAG_MAX_LEN = 100 -_TAGS_MAX_COUNT = 50 -_ICON_MAX_LEN = 10 - - -def _validate_content(value: Any) -> Any: - if value is None: - return value - try: - size = len(json.dumps(value, ensure_ascii=False).encode("utf-8")) - except (TypeError, ValueError): - raise ValueError("content must be JSON-serialisable") - if size > _CONTENT_MAX_BYTES: - raise ValueError(f"content exceeds maximum size of {_CONTENT_MAX_BYTES // 1000} KB") - return value - - -class NoteCreate(BaseModel): - model_config = ConfigDict(extra="ignore") - - title: str | None = Field(default=None, min_length=1, max_length=_TITLE_MAX_LEN) - content: Any = Field(default_factory=dict) - parentId: str | None = Field(default=None, max_length=128) - icon: str | None = Field(default=None, max_length=_ICON_MAX_LEN) - pinned: bool | None = None - tags: list[str] | None = None - - @field_validator("content", mode="before") - @classmethod - def validate_content_size(cls, v: Any) -> Any: - return _validate_content(v) - - @field_validator("tags", mode="before") - @classmethod - def validate_tags(cls, v: Any) -> Any: - if v is None: - return v - if not isinstance(v, list): - raise ValueError("tags must be a list") - if len(v) > _TAGS_MAX_COUNT: - raise ValueError(f"too many tags (max {_TAGS_MAX_COUNT})") - return [str(t)[:_TAG_MAX_LEN] for t in v] - - -class NoteUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - title: str | None = Field(default=None, min_length=1, max_length=_TITLE_MAX_LEN) - content: Any | None = None - parentId: str | None = Field(default=None, max_length=128) - icon: str | None = Field(default=None, max_length=_ICON_MAX_LEN) - pinned: bool | None = None - tags: list[str] | None = None - - @field_validator("content", mode="before") - @classmethod - def validate_content_size(cls, v: Any) -> Any: - return _validate_content(v) - - @field_validator("tags", mode="before") - @classmethod - def validate_tags(cls, v: Any) -> Any: - if v is None: - return v - if not isinstance(v, list): - raise ValueError("tags must be a list") - if len(v) > _TAGS_MAX_COUNT: - raise ValueError(f"too many tags (max {_TAGS_MAX_COUNT})") - return [str(t)[:_TAG_MAX_LEN] for t in v] - - -class NoteOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - title: str - content: Any - parentId: str | None = None - icon: str | None = None - pinned: bool = False - tags: list[str] = Field(default_factory=list) - userId: str - createdAt: str - updatedAt: str - diff --git a/apps/backend/app/api/routes/notes/services.py b/apps/backend/app/api/routes/notes/services.py deleted file mode 100644 index fcd58d3c..00000000 --- a/apps/backend/app/api/routes/notes/services.py +++ /dev/null @@ -1,163 +0,0 @@ -from datetime import datetime, timezone -from typing import Any - -from fastapi import HTTPException, status - -from app.api.routes.notes.schema import NoteCreate, NoteOut, NoteUpdate -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.database import db_manager -from app.utils.collection_name import NOTES -from app.utils.crud import safe_insert, safe_update_one -from app.utils.utils import new_id - - -def isoformat_utc(dt: datetime) -> str: - if dt.tzinfo is None: - dt = dt.replace(tzinfo=timezone.utc) - return dt.astimezone(timezone.utc).isoformat() - - -def _doc_to_out(doc: dict[str, Any]) -> NoteOut: - nid = str(doc.get("_id", "")) - - def _to_iso(v: Any) -> str: - if v is None: - return isoformat_utc(datetime.now(timezone.utc)) - if isinstance(v, datetime): - return isoformat_utc(v) - if isinstance(v, str): - return v - return isoformat_utc(datetime.now(timezone.utc)) - - return NoteOut( - id=nid, - title=str(doc.get("title", "")), - content=doc.get("content", {}), - parentId=doc.get("parentId"), - icon=doc.get("icon"), - pinned=bool(doc.get("pinned", False)), - tags=list(doc.get("tags") or []), - userId=str(doc.get("created_by", "")), - createdAt=_to_iso(doc.get("createdAt")), - updatedAt=_to_iso(doc.get("updatedAt")), - ) - - -_LIST_PROJECTION = {"content": 0} - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_notes(*, ctx: WorkspaceContext) -> list[NoteOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - NOTES, - flt, - projection=_LIST_PROJECTION, - sort=[("createdAt", 1)], - ) - return [_doc_to_out(d) for d in docs] - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_notes_paginated(*, ctx: WorkspaceContext, skip: int = 0, limit: int = 200) -> list[NoteOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - NOTES, - flt, - projection=_LIST_PROJECTION, - sort=[("createdAt", 1)], - skip=max(0, skip), - limit=max(1, limit), - ) - return [_doc_to_out(d) for d in docs] - - -async def create_note(ctx: WorkspaceContext, body: NoteCreate) -> NoteOut: - ts = datetime.now(timezone.utc) - note_id = new_id() - doc = { - "_id": note_id, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "title": body.title or "Untitled", - "content": body.content if body.content is not None else {}, - "parentId": body.parentId, - "icon": body.icon, - "pinned": body.pinned or False, - "tags": body.tags or [], - "createdAt": ts, - "updatedAt": ts, - } - await safe_insert(NOTES, doc, name="Note") - return _doc_to_out(doc) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_note(*, ctx: WorkspaceContext, note_id: str) -> NoteOut: - flt = apply_workspace_filter(ctx, {"_id": note_id, "created_by": ctx.uid}) - doc = await db_manager.find_one(NOTES, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Note not found.") - return _doc_to_out(doc) - - -async def update_note(ctx: WorkspaceContext, note_id: str, body: NoteUpdate) -> NoteOut: - patch = body.model_dump(exclude_unset=True) - if not patch: - return await get_note(ctx=ctx, note_id=note_id) - - patch["updatedAt"] = datetime.now(timezone.utc) - flt = apply_workspace_filter(ctx, {"_id": note_id, "created_by": ctx.uid}) - doc = await safe_update_one( - NOTES, flt, patch, name="Note" - ) - return _doc_to_out(doc) - - -async def _descendant_ids(ctx: WorkspaceContext, root_id: str) -> list[str]: - """BFS using targeted per-level queries instead of loading all user notes.""" - collected: list[str] = [root_id] - frontier: list[str] = [root_id] - visited: set[str] = {root_id} - - while frontier: - base: dict[str, Any] = {"parentId": {"$in": frontier}} - flt = apply_workspace_filter(ctx, {**base, "created_by": ctx.uid}) - docs = await db_manager.find( - NOTES, - flt, - projection={"_id": 1}, - ) - frontier = [] - for d in docs: - nid = str(d.get("_id")) - if nid not in visited: - visited.add(nid) - collected.append(nid) - frontier.append(nid) - - return collected - - -async def delete_note(ctx: WorkspaceContext, note_id: str, *, recursive: bool = True) -> None: - if recursive: - ids = await _descendant_ids(ctx, note_id) - flt = apply_workspace_filter(ctx, {"created_by": ctx.uid, "_id": {"$in": ids}}) - result = await db_manager.delete_many(NOTES, flt) - if result.deleted_count == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Note not found.") - return - - flt = apply_workspace_filter(ctx, {"_id": note_id, "created_by": ctx.uid}) - result = await db_manager.delete_one(NOTES, flt) - if result.deleted_count == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Note not found.") - - -async def delete_note_non_recursive(ctx: WorkspaceContext, note_id: str) -> None: - await delete_note(ctx, note_id, recursive=False) diff --git a/apps/backend/app/api/routes/passwords/__init__.py b/apps/backend/app/api/routes/passwords/__init__.py deleted file mode 100644 index fb0a2f80..00000000 --- a/apps/backend/app/api/routes/passwords/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API route modules.""" diff --git a/apps/backend/app/api/routes/passwords/api.py b/apps/backend/app/api/routes/passwords/api.py deleted file mode 100644 index 5f15c764..00000000 --- a/apps/backend/app/api/routes/passwords/api.py +++ /dev/null @@ -1,89 +0,0 @@ -from fastapi import APIRouter, Depends, Query, Request - -from app.api.routes.passwords import services as pw_svc -from app.api.routes.passwords.schema import ( - PasswordEntryCreate, - PasswordEntryOut, - PasswordEntryUpdate, - VaultOut, - VaultSetupRequest, -) -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission -from app.core.limiter import limiter - -router = APIRouter(prefix="/password-manager", tags=["password-manager"]) - - -@router.get("/vault", response_model=VaultOut, summary="Get password vault settings") -async def get_vault(ctx: WorkspaceContext = Depends(require_permission("password-manager", "read"))) -> VaultOut: - return await pw_svc.get_vault(ctx=ctx) - - -@router.post("/vault/setup", response_model=VaultOut, summary="Setup/replace password vault") -@limiter.limit("3/minute") -async def setup_vault(request: Request, body: VaultSetupRequest, ctx: WorkspaceContext = Depends(require_permission("password-manager", "write"))) -> VaultOut: - return await pw_svc.setup_vault(ctx, body) - - -@router.post( - "/vault/clear", - summary="Delete vault + all encrypted entries (clearAll equivalent)", -) -@limiter.limit("3/minute") -async def clear_vault(request: Request, ctx: WorkspaceContext = Depends(require_permission("password-manager", "admin"))) -> dict[str, int]: - return await pw_svc.clear_vault(ctx) - - -@router.get( - "/entries", - response_model=list[PasswordEntryOut], - summary="List encrypted password entries", -) -async def list_entries( - ctx: WorkspaceContext = Depends(require_permission("password-manager", "read")), - limit: int = Query(default=200, ge=1, le=1000), - offset: int = Query(default=0, ge=0), -) -> list[PasswordEntryOut]: - return await pw_svc.list_entries(ctx=ctx, limit=limit, offset=offset) - - -@router.post( - "/entries", - response_model=PasswordEntryOut, - summary="Create password entry (encrypted blob)", -) -@limiter.limit("30/minute") -async def create_entry(request: Request, body: PasswordEntryCreate, ctx: WorkspaceContext = Depends(require_permission("password-manager", "write"))) -> PasswordEntryOut: - return await pw_svc.create_entry(ctx, body) - - -@router.get( - "/entries/{entry_id}", - response_model=PasswordEntryOut, - summary="Get one password entry", -) -async def get_entry(entry_id: str, ctx: WorkspaceContext = Depends(require_permission("password-manager", "read"))) -> PasswordEntryOut: - return await pw_svc.get_entry(ctx=ctx, entry_id=entry_id) - - -@router.patch( - "/entries/{entry_id}", - response_model=PasswordEntryOut, - summary="Update password entry (encrypted blob)", -) -async def patch_entry( - entry_id: str, - body: PasswordEntryUpdate, - ctx: WorkspaceContext = Depends(require_permission("password-manager", "write")), -) -> PasswordEntryOut: - return await pw_svc.update_entry(ctx, entry_id, body) - - -@router.delete( - "/entries/{entry_id}", - status_code=204, - summary="Delete password entry", -) -async def delete_entry(entry_id: str, ctx: WorkspaceContext = Depends(require_permission("password-manager", "delete"))) -> None: - await pw_svc.delete_entry(ctx, entry_id) diff --git a/apps/backend/app/api/routes/passwords/schema.py b/apps/backend/app/api/routes/passwords/schema.py deleted file mode 100644 index feebb413..00000000 --- a/apps/backend/app/api/routes/passwords/schema.py +++ /dev/null @@ -1,49 +0,0 @@ - -from pydantic import BaseModel, ConfigDict, Field - - -class KeyVerifier(BaseModel): - model_config = ConfigDict(extra="ignore") - - encrypted: str = Field(min_length=1) - iv: str = Field(min_length=1) - - -class VaultSetupRequest(BaseModel): - salt: str = Field(min_length=1) - verifier: KeyVerifier - createdAt: int | None = Field(default=None, ge=0) - - -class VaultOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - salt: str - verifier: KeyVerifier - createdAt: int - - -class PasswordEntryCreate(BaseModel): - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - createdAt: int | None = Field(default=None, ge=0) - updatedAt: int | None = Field(default=None, ge=0) - - -class PasswordEntryUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - updatedAt: int | None = Field(default=None, ge=0) - - -class PasswordEntryOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - encryptedData: str - iv: str - createdAt: int - updatedAt: int - diff --git a/apps/backend/app/api/routes/passwords/services.py b/apps/backend/app/api/routes/passwords/services.py deleted file mode 100644 index a0482dbb..00000000 --- a/apps/backend/app/api/routes/passwords/services.py +++ /dev/null @@ -1,177 +0,0 @@ -from typing import Any - -from fastapi import HTTPException, status -from pymongo.errors import PyMongoError - -from app.api.routes.passwords.schema import ( - KeyVerifier, - PasswordEntryCreate, - PasswordEntryOut, - PasswordEntryUpdate, - VaultOut, - VaultSetupRequest, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.database import db_manager -from app.utils.collection_name import PASSWORD_ENTRIES, PASSWORD_VAULTS -from app.utils.crud import safe_delete_one, safe_insert, safe_update_one -from app.utils.utils import create_timestamp, new_id - - -def _vault_doc_to_out(doc: dict[str, Any]) -> VaultOut: - verifier_raw = doc.get("verifier") or {} - salt = doc.get("salt") - if not salt: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Vault salt missing.") - - verifier_enc = verifier_raw.get("encrypted") - verifier_iv = verifier_raw.get("iv") - if not verifier_enc or not verifier_iv: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Vault verifier missing.") - - created_at = int(doc.get("createdAt", 0)) - if created_at <= 0: - created_at = create_timestamp() - - return VaultOut( - salt=str(salt), - verifier=KeyVerifier(encrypted=str(verifier_enc), iv=str(verifier_iv)), - createdAt=created_at, - ) - - -def _entry_doc_to_out(doc: dict[str, Any], *, entry_id: str) -> PasswordEntryOut: - created_at = int(doc.get("createdAt", 0)) or create_timestamp() - updated_at = int(doc.get("updatedAt", 0)) or created_at - return PasswordEntryOut( - id=entry_id, - encryptedData=str(doc.get("encryptedData", "")), - iv=str(doc.get("iv", "")), - createdAt=created_at, - updatedAt=updated_at, - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_vault(*, ctx: WorkspaceContext) -> VaultOut: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - doc = await db_manager.find_one(PASSWORD_VAULTS, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vault not found.") - return _vault_doc_to_out(doc) - - -async def setup_vault(ctx: WorkspaceContext, body: VaultSetupRequest) -> VaultOut: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - existing = await db_manager.find_one(PASSWORD_VAULTS, flt) - if existing: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="Vault already exists. Delete the existing vault first.", - ) - - ts_created = int(body.createdAt) if body.createdAt is not None else create_timestamp() - ts_updated = create_timestamp() - - doc: dict[str, Any] = { - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "salt": body.salt, - "verifier": {"encrypted": body.verifier.encrypted, "iv": body.verifier.iv}, - "createdAt": ts_created, - "updatedAt": ts_updated, - } - await safe_insert(PASSWORD_VAULTS, doc, name="Vault") - return await get_vault(ctx=ctx) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_entries(*, ctx: WorkspaceContext, limit: int = 200, offset: int = 0) -> list[PasswordEntryOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find( - PASSWORD_ENTRIES, - flt, - sort=[("updatedAt", -1), ("createdAt", -1)], - skip=max(0, offset), - limit=max(1, limit), - ) - return [_entry_doc_to_out(d, entry_id=str(d.get("_id", ""))) for d in docs] - - -async def create_entry(ctx: WorkspaceContext, body: PasswordEntryCreate) -> PasswordEntryOut: - eid = new_id() - ts = create_timestamp() - created_at = int(body.createdAt) if body.createdAt is not None else ts - updated_at = int(body.updatedAt) if body.updatedAt is not None else created_at - - doc: dict[str, Any] = { - "_id": eid, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "encryptedData": body.encryptedData, - "iv": body.iv, - "createdAt": created_at, - "updatedAt": updated_at, - } - await safe_insert(PASSWORD_ENTRIES, doc, name="Entry") - return _entry_doc_to_out(doc, entry_id=eid) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_entry(*, ctx: WorkspaceContext, entry_id: str) -> PasswordEntryOut: - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - doc = await db_manager.find_one(PASSWORD_ENTRIES, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entry not found.") - return _entry_doc_to_out(doc, entry_id=entry_id) - - -async def update_entry(ctx: WorkspaceContext, entry_id: str, body: PasswordEntryUpdate) -> PasswordEntryOut: - ts_updated = int(body.updatedAt) if body.updatedAt is not None else create_timestamp() - patch: dict[str, Any] = { - "encryptedData": body.encryptedData, - "iv": body.iv, - "updatedAt": ts_updated, - } - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - doc = await safe_update_one( - PASSWORD_ENTRIES, - flt, - patch, - name="Entry", - ) - return _entry_doc_to_out(doc, entry_id=entry_id) - - -async def delete_entry(ctx: WorkspaceContext, entry_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": entry_id, "created_by": ctx.uid}) - await safe_delete_one(PASSWORD_ENTRIES, flt, name="Entry") - - -async def clear_entries(ctx: WorkspaceContext) -> dict[str, int]: - flt = apply_workspace_filter(ctx, {"created_by": ctx.uid}) - try: - res = await db_manager.delete_many(PASSWORD_ENTRIES, flt) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to clear entries." - ) from exc - return {"entriesDeleted": int(res.deleted_count)} - - -async def clear_vault(ctx: WorkspaceContext) -> dict[str, int]: - entries_deleted = (await clear_entries(ctx))["entriesDeleted"] - flt = apply_workspace_filter(ctx, {"created_by": ctx.uid}) - try: - res = await db_manager.delete_many(PASSWORD_VAULTS, flt) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to clear vault." - ) from exc - return {"entriesDeleted": entries_deleted, "vaultDeleted": int(res.deleted_count)} diff --git a/apps/backend/app/api/routes/redis_commander/__init__.py b/apps/backend/app/api/routes/redis_commander/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/redis_commander/api.py b/apps/backend/app/api/routes/redis_commander/api.py deleted file mode 100644 index 44e57e07..00000000 --- a/apps/backend/app/api/routes/redis_commander/api.py +++ /dev/null @@ -1,67 +0,0 @@ -from fastapi import APIRouter, Depends - -from app.api.routes.redis_commander.schema import ( - RedisConnectionCreate, - RedisConnectionOut, - RedisConnectionUpdate, -) -from app.api.routes.redis_commander import services as svc -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -router = APIRouter(prefix="/redis-commander", tags=["redis-commander"]) - - -@router.get( - "/connections", - response_model=list[RedisConnectionOut], - summary="List saved Redis connections", -) -async def list_connections(ctx: WorkspaceContext = Depends(require_permission("redis-commander", "read"))) -> list[RedisConnectionOut]: - return await svc.list_connections(ctx) - - -@router.post( - "/connections", - response_model=RedisConnectionOut, - summary="Save a new Redis connection", -) -async def create_connection( - body: RedisConnectionCreate, ctx: WorkspaceContext = Depends(require_permission("redis-commander", "write")) -) -> RedisConnectionOut: - return await svc.create_connection(ctx, body) - - -@router.patch( - "/connections/{connection_id}", - response_model=RedisConnectionOut, - summary="Update a saved Redis connection", -) -async def update_connection( - connection_id: str, - body: RedisConnectionUpdate, - ctx: WorkspaceContext = Depends(require_permission("redis-commander", "write")), -) -> RedisConnectionOut: - return await svc.update_connection(ctx, connection_id, body) - - -@router.delete( - "/connections/{connection_id}", - status_code=204, - summary="Delete a saved Redis connection", -) -async def delete_connection( - connection_id: str, ctx: WorkspaceContext = Depends(require_permission("redis-commander", "delete")) -) -> None: - await svc.delete_connection(ctx, connection_id) - - -@router.post( - "/connections/{connection_id}/touch", - status_code=204, - summary="Update lastUsedAt for a connection", -) -async def touch_connection( - connection_id: str, ctx: WorkspaceContext = Depends(require_permission("redis-commander", "write")) -) -> None: - await svc.touch_connection(ctx, connection_id) diff --git a/apps/backend/app/api/routes/redis_commander/schema.py b/apps/backend/app/api/routes/redis_commander/schema.py deleted file mode 100644 index 670e8c4e..00000000 --- a/apps/backend/app/api/routes/redis_commander/schema.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class RedisConnectionCreate(BaseModel): - """ - The REDIS_URL (redis:// or rediss://) is encrypted client-side (AES-GCM) - before being sent. The server stores only the ciphertext blob. - """ - - model_config = ConfigDict(extra="ignore") - - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - name: Optional[str] = Field(default=None, min_length=1) - - -class RedisConnectionUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - encryptedData: Optional[str] = Field(default=None, min_length=1) - iv: Optional[str] = Field(default=None, min_length=1) - name: Optional[str] = Field(default=None, min_length=1) - - -class RedisConnectionOut(BaseModel): - """ - Returned to the client. The raw REDIS_URL is absent — the client decrypts - encryptedData locally using the master key. - """ - - model_config = ConfigDict(extra="ignore") - - id: str - userId: str - encryptedData: str - iv: str - name: str - createdAt: int - lastUsedAt: int - updatedAt: int diff --git a/apps/backend/app/api/routes/redis_commander/services.py b/apps/backend/app/api/routes/redis_commander/services.py deleted file mode 100644 index 43347f57..00000000 --- a/apps/backend/app/api/routes/redis_commander/services.py +++ /dev/null @@ -1,122 +0,0 @@ -from typing import Any - -from fastapi import HTTPException, status -from pymongo.errors import PyMongoError - -from app.api.routes.redis_commander.schema import ( - RedisConnectionCreate, - RedisConnectionOut, - RedisConnectionUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.utils.collection_name import REDIS_CONNECTIONS -from app.utils.utils import create_timestamp, new_id -from app.database import db_manager - - -def _doc_to_out(doc: dict[str, Any], *, connection_id: str) -> RedisConnectionOut: - created_at = int(doc.get("createdAt", 0)) or create_timestamp() - last_used_at = int(doc.get("lastUsedAt", 0)) or created_at - # Content-edit clock for sync LWW. Falls back to lastUsedAt for legacy docs - # written before updatedAt existed. - updated_at = int(doc.get("updatedAt", 0)) or last_used_at - return RedisConnectionOut( - id=connection_id, - userId=str(doc.get("created_by", "")), - encryptedData=str(doc.get("encryptedData", "")), - iv=str(doc.get("iv", "")), - name=str(doc.get("name", "")), - createdAt=created_at, - lastUsedAt=last_used_at, - updatedAt=updated_at, - ) - - -async def list_connections(ctx: WorkspaceContext) -> list[RedisConnectionOut]: - flt = apply_legacy_or_filter(ctx, {"encryptedData": {"$exists": True}, "iv": {"$exists": True}}, user_field="created_by") - docs = await db_manager.find( - REDIS_CONNECTIONS, - flt, - sort=[("lastUsedAt", -1), ("createdAt", -1)], - ) - return [_doc_to_out(doc, connection_id=str(doc.get("_id", ""))) for doc in docs] - - -async def create_connection(ctx: WorkspaceContext, body: RedisConnectionCreate) -> RedisConnectionOut: - ts = create_timestamp() - _id = new_id() - doc: dict[str, Any] = { - "_id": _id, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "encryptedData": body.encryptedData, - "iv": body.iv, - "name": body.name or "My Redis Connection", - "createdAt": ts, - "lastUsedAt": ts, - "updatedAt": ts, - } - try: - await db_manager.insert_one(REDIS_CONNECTIONS, doc) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create connection.", - ) from exc - return _doc_to_out(doc, connection_id=_id) - - -async def update_connection(ctx: WorkspaceContext, connection_id: str, body: RedisConnectionUpdate) -> RedisConnectionOut: - flt = apply_workspace_filter(ctx, {"_id": connection_id, "created_by": ctx.uid}) - existing = await db_manager.find_one(REDIS_CONNECTIONS, flt) - if not existing: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - - ts = create_timestamp() - # updatedAt is the sync LWW clock — it advances on every content edit (unlike - # lastUsedAt, which also moves on a bare connect/touch). - patch: dict[str, Any] = {"lastUsedAt": ts, "updatedAt": ts} - if body.encryptedData is not None: - patch["encryptedData"] = body.encryptedData - if body.iv is not None: - patch["iv"] = body.iv - if body.name is not None: - patch["name"] = body.name - - try: - await db_manager.update_one( - REDIS_CONNECTIONS, - flt, - {"$set": patch}, - ) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to update connection.", - ) from exc - - updated = await db_manager.find_one(REDIS_CONNECTIONS, flt) - if not updated: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - return _doc_to_out(updated, connection_id=connection_id) - - -async def delete_connection(ctx: WorkspaceContext, connection_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": connection_id, "created_by": ctx.uid}) - res = await db_manager.delete_one(REDIS_CONNECTIONS, flt) - if res.deleted_count == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - - -async def touch_connection(ctx: WorkspaceContext, connection_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": connection_id, "created_by": ctx.uid}) - await db_manager.update_one( - REDIS_CONNECTIONS, - flt, - {"$set": {"lastUsedAt": create_timestamp()}}, - ) diff --git a/apps/backend/app/api/routes/s3_drive/__init__.py b/apps/backend/app/api/routes/s3_drive/__init__.py deleted file mode 100644 index ebe56cac..00000000 --- a/apps/backend/app/api/routes/s3_drive/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""S3 Drive API route module.""" diff --git a/apps/backend/app/api/routes/s3_drive/api.py b/apps/backend/app/api/routes/s3_drive/api.py deleted file mode 100644 index 5a00636f..00000000 --- a/apps/backend/app/api/routes/s3_drive/api.py +++ /dev/null @@ -1,146 +0,0 @@ -import asyncio -import hashlib - -from fastapi import APIRouter, Depends, Request - -from app.api.routes.s3_drive import services as svc -from app.api.routes.s3_drive.schema import ( - BucketInfo, - ConfigureCorsRequest, - CreateFolderRequest, - DeleteObjectsRequest, - ListBucketsRequest, - ListObjectsRequest, - ListObjectsResponse, - MoveObjectRequest, - PresignedBatchRequest, - PresignedBatchResponse, - PresignedDownloadRequest, - PresignedUploadRequest, - PresignedUrlResponse, - S3ConnectionCreate, - S3ConnectionOut, - S3ConnectionUpdate, -) -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission -from app.core.cache.decorator import bump_version, get_or_set -from app.core.cache.keys import version_key -from app.core.limiter import limiter -from app.core.redis_client import get_redis - -router = APIRouter(prefix="/s3-drive", tags=["s3-drive"]) - - -# ── Saved connections (encrypted credentials) ───────────────────────────────── - -@router.get("/connections", response_model=list[S3ConnectionOut]) -async def list_connections(ctx: WorkspaceContext = Depends(require_permission("s3-drive", "read"))) -> list[S3ConnectionOut]: - return await svc.list_connections(ctx) - - -@router.post("/connections", response_model=S3ConnectionOut, status_code=201) -@limiter.limit("20/minute") -async def create_connection(request: Request, body: S3ConnectionCreate, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "write"))) -> S3ConnectionOut: - return await svc.create_connection(ctx, body) - - -@router.get("/connections/{conn_id}", response_model=S3ConnectionOut) -async def get_connection(conn_id: str, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "read"))) -> S3ConnectionOut: - return await svc.get_connection(ctx, conn_id) - - -@router.patch("/connections/{conn_id}", response_model=S3ConnectionOut) -async def update_connection(conn_id: str, body: S3ConnectionUpdate, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "write"))) -> S3ConnectionOut: - return await svc.update_connection(ctx, conn_id, body) - - -@router.delete("/connections/{conn_id}", status_code=204) -async def delete_connection(conn_id: str, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "delete"))) -> None: - await svc.delete_connection(ctx, conn_id) - - -# ── S3 operations (credentials sent per-request, never stored plaintext) ────── - -@router.post("/operations/buckets", response_model=list[BucketInfo]) -@limiter.limit("30/minute") -async def list_buckets(request: Request, body: ListBucketsRequest, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "read"))) -> list[BucketInfo]: - return await asyncio.to_thread(svc.list_buckets, body) - - -def _list_args_hash(body: ListObjectsRequest) -> str: - c = body.credentials - raw = f"{c.accessKey}|{c.region}|{c.endpoint or ''}|{c.bucket}|{body.prefix}|{body.delimiter}|{body.continuationToken or ''}|{body.maxKeys}" - return hashlib.blake2b(raw.encode(), digest_size=8).hexdigest() - - -async def _list_cache_key(ctx: WorkspaceContext, body: ListObjectsRequest) -> str: - r = get_redis() - ver = 0 - if r is not None: - try: - raw = await r.get(version_key("s3_drive_list", ctx.uid)) - if raw is not None: - ver = int(raw) - except Exception: # noqa: BLE001 - ver = 0 - return f"cache:s3_drive_list:u:{ctx.uid}:v{ver}:list:{_list_args_hash(body)}" - - -@router.post("/operations/list", response_model=ListObjectsResponse) -@limiter.limit("60/minute") -async def list_objects(request: Request, body: ListObjectsRequest, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "read"))) -> ListObjectsResponse: - key = await _list_cache_key(ctx, body) - - async def loader() -> ListObjectsResponse: - return await asyncio.to_thread(svc.list_objects, body) - - return await get_or_set(ns="s3_drive_list", key=key, loader=loader) - - -@router.post("/operations/delete", response_model=dict) -@limiter.limit("20/minute") -async def delete_objects(request: Request, body: DeleteObjectsRequest, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "delete"))) -> dict: - result = await asyncio.to_thread(svc.delete_objects, body) - await bump_version(ns="s3_drive_list", uid=ctx.uid) - return result - - -@router.post("/operations/create-folder", response_model=dict) -@limiter.limit("30/minute") -async def create_folder(request: Request, body: CreateFolderRequest, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "write"))) -> dict: - result = await asyncio.to_thread(svc.create_folder, body) - await bump_version(ns="s3_drive_list", uid=ctx.uid) - return result - - -@router.post("/operations/presigned-download", response_model=PresignedUrlResponse) -@limiter.limit("60/minute") -async def presigned_download(request: Request, body: PresignedDownloadRequest, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "read"))) -> PresignedUrlResponse: - return await asyncio.to_thread(svc.presigned_download, body) - - -@router.post("/operations/presigned-upload", response_model=PresignedUrlResponse) -@limiter.limit("60/minute") -async def presigned_upload(request: Request, body: PresignedUploadRequest, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "write"))) -> PresignedUrlResponse: - return await asyncio.to_thread(svc.presigned_upload, body) - - -@router.post("/operations/presigned-batch", response_model=PresignedBatchResponse) -@limiter.limit("60/minute") -async def presigned_batch(request: Request, body: PresignedBatchRequest, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "read"))) -> PresignedBatchResponse: - return await asyncio.to_thread(svc.presigned_batch, body) - - -@router.post("/operations/move", response_model=dict) -@limiter.limit("20/minute") -async def move_object(request: Request, body: MoveObjectRequest, ctx: WorkspaceContext = Depends(require_permission("s3-drive", "write"))) -> dict: - result = await asyncio.to_thread(svc.move_object, body) - await bump_version(ns="s3_drive_list", uid=ctx.uid) - return result - - -@router.post("/operations/configure-cors", response_model=dict, summary="Set bucket CORS rules to allow browser presigned URL requests") -@limiter.limit("5/minute") -async def configure_cors(request: Request, body: ConfigureCorsRequest, _ctx: WorkspaceContext = Depends(require_permission("s3-drive", "admin"))) -> dict: - return await asyncio.to_thread(svc.configure_bucket_cors, body, body.allowedOrigins) diff --git a/apps/backend/app/api/routes/s3_drive/schema.py b/apps/backend/app/api/routes/s3_drive/schema.py deleted file mode 100644 index 30f2a92f..00000000 --- a/apps/backend/app/api/routes/s3_drive/schema.py +++ /dev/null @@ -1,134 +0,0 @@ - -from pydantic import BaseModel, ConfigDict, Field - -# ── Stored connection (credentials encrypted client-side) ───────────────────── - -class S3ConnectionCreate(BaseModel): - name: str = Field(min_length=1, max_length=100) - provider: str = Field(min_length=1, max_length=50) # "aws" | "digitalocean" | "custom" - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - createdAt: int | None = Field(default=None, ge=0) - - -class S3ConnectionUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - name: str | None = Field(default=None, min_length=1, max_length=100) - provider: str | None = Field(default=None, min_length=1, max_length=50) - encryptedData: str | None = Field(default=None, min_length=1) - iv: str | None = Field(default=None, min_length=1) - updatedAt: int | None = Field(default=None, ge=0) - - -class S3ConnectionOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - name: str - provider: str - encryptedData: str - iv: str - createdAt: int - updatedAt: int - - -# ── Credentials sent per-operation (decrypted client-side, over HTTPS) ──────── - -class S3Credentials(BaseModel): - accessKey: str = Field(min_length=1) - secretKey: str = Field(min_length=1) - region: str = Field(default="us-east-1") - bucket: str = Field(min_length=1) - endpoint: str | None = Field(default=None) # custom endpoint for DO Spaces / self-hosted - - -# ── Operation request bodies ────────────────────────────────────────────────── - -class ListObjectsRequest(BaseModel): - credentials: S3Credentials - prefix: str = Field(default="") - delimiter: str = Field(default="/") - continuationToken: str | None = Field(default=None) - maxKeys: int = Field(default=1000, ge=1, le=1000) - - -class DeleteObjectsRequest(BaseModel): - credentials: S3Credentials - keys: list[str] = Field(min_length=1) - - -class CreateFolderRequest(BaseModel): - credentials: S3Credentials - prefix: str = Field(min_length=1) - - -class PresignedDownloadRequest(BaseModel): - credentials: S3Credentials - key: str = Field(min_length=1) - expiresIn: int = Field(default=3600, ge=60, le=86400) - - -class PresignedUploadRequest(BaseModel): - credentials: S3Credentials - key: str = Field(min_length=1) - contentType: str = Field(default="application/octet-stream") - expiresIn: int = Field(default=3600, ge=60, le=86400) - - -class MoveObjectRequest(BaseModel): - credentials: S3Credentials - sourceKey: str = Field(min_length=1) - destinationKey: str = Field(min_length=1) - - -class ListBucketsRequest(BaseModel): - credentials: S3Credentials - - -class PresignedBatchItem(BaseModel): - key: str = Field(min_length=1) - op: str = Field(default="get", pattern="^(get|put)$") - contentType: str | None = Field(default=None) - - -class PresignedBatchRequest(BaseModel): - credentials: S3Credentials - items: list[PresignedBatchItem] = Field(min_length=1, max_length=100) - expiresIn: int = Field(default=3600, ge=60, le=86400) - - -class ConfigureCorsRequest(BaseModel): - credentials: S3Credentials - allowedOrigins: list[str] = Field(min_length=1, description="Allowed CORS origins for the S3 bucket. Must be explicitly provided.") - - -# ── Operation response models ───────────────────────────────────────────────── - -class S3ObjectItem(BaseModel): - key: str - size: int | None = None - lastModified: str | None = None - etag: str | None = None - isFolder: bool = False - - -class ListObjectsResponse(BaseModel): - objects: list[S3ObjectItem] - prefixes: list[str] - isTruncated: bool - nextContinuationToken: str | None = None - - -class PresignedUrlResponse(BaseModel): - url: str - key: str - - -class PresignedBatchResponse(BaseModel): - urls: list[PresignedUrlResponse] - - -class BucketInfo(BaseModel): - name: str - creationDate: str | None = None diff --git a/apps/backend/app/api/routes/s3_drive/services.py b/apps/backend/app/api/routes/s3_drive/services.py deleted file mode 100644 index 200c1192..00000000 --- a/apps/backend/app/api/routes/s3_drive/services.py +++ /dev/null @@ -1,344 +0,0 @@ -import hashlib -import threading -import time -from typing import Any - -from fastapi import HTTPException, status - -try: - import boto3 - from botocore.config import Config - from botocore.exceptions import BotoCoreError, ClientError -except ImportError: # pragma: no cover - boto3 = None # type: ignore - -from app.api.routes.s3_drive.schema import ( - BucketInfo, - CreateFolderRequest, - DeleteObjectsRequest, - ListBucketsRequest, - ListObjectsRequest, - ListObjectsResponse, - MoveObjectRequest, - PresignedBatchRequest, - PresignedBatchResponse, - PresignedDownloadRequest, - PresignedUploadRequest, - PresignedUrlResponse, - S3ConnectionCreate, - S3ConnectionOut, - S3ConnectionUpdate, - S3Credentials, - S3ObjectItem, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.database import db_manager -from app.utils.collection_name import S3_CONNECTIONS -from app.utils.crud import safe_delete_one, safe_insert, safe_update_one -from app.utils.utils import create_timestamp, new_id - - -def _doc_to_out(doc: dict[str, Any]) -> S3ConnectionOut: - conn_id = str(doc.get("_id", "")) - created_at = int(doc.get("createdAt", 0)) or create_timestamp() - updated_at = int(doc.get("updatedAt", 0)) or created_at - return S3ConnectionOut( - id=conn_id, - name=str(doc.get("name", "")), - provider=str(doc.get("provider", "aws")), - encryptedData=str(doc.get("encryptedData", "")), - iv=str(doc.get("iv", "")), - createdAt=created_at, - updatedAt=updated_at, - ) - - -# ── Connection CRUD (async — uses DB) ───────────────────────────────────────── - -async def list_connections(ctx: WorkspaceContext) -> list[S3ConnectionOut]: - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find(S3_CONNECTIONS, flt, sort=[("updatedAt", -1)]) - return [_doc_to_out(d) for d in docs] - - -async def create_connection(ctx: WorkspaceContext, body: S3ConnectionCreate) -> S3ConnectionOut: - conn_id = new_id() - ts = create_timestamp() - created_at = int(body.createdAt) if body.createdAt is not None else ts - doc: dict[str, Any] = { - "_id": conn_id, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "name": body.name, - "provider": body.provider, - "encryptedData": body.encryptedData, - "iv": body.iv, - "createdAt": created_at, - "updatedAt": ts, - } - await safe_insert(S3_CONNECTIONS, doc, name="Connection") - return _doc_to_out(doc) - - -async def get_connection(ctx: WorkspaceContext, conn_id: str) -> S3ConnectionOut: - flt = apply_workspace_filter(ctx, {"_id": conn_id, "created_by": ctx.uid}) - doc = await db_manager.find_one(S3_CONNECTIONS, flt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - return _doc_to_out(doc) - - -async def update_connection(ctx: WorkspaceContext, conn_id: str, body: S3ConnectionUpdate) -> S3ConnectionOut: - flt = apply_workspace_filter(ctx, {"_id": conn_id, "created_by": ctx.uid}) - patch: dict[str, Any] = {"updatedAt": int(body.updatedAt) if body.updatedAt is not None else create_timestamp()} - if body.name is not None: - patch["name"] = body.name - if body.provider is not None: - patch["provider"] = body.provider - if body.encryptedData is not None: - patch["encryptedData"] = body.encryptedData - if body.iv is not None: - patch["iv"] = body.iv - doc = await safe_update_one( - S3_CONNECTIONS, flt, patch, name="Connection" - ) - return _doc_to_out(doc) - - -async def delete_connection(ctx: WorkspaceContext, conn_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": conn_id, "created_by": ctx.uid}) - await safe_delete_one(S3_CONNECTIONS, flt, name="Connection") - - -# ── S3 client factory (TTL-based cache) ─────────────────────────────────────── - -_CLIENT_CACHE: dict[str, tuple[float, Any]] = {} -_CLIENT_CACHE_TTL = 300 -_CLIENT_CACHE_MAX = 64 -_CLIENT_CACHE_LOCK = threading.Lock() - - -def _s3_client(creds: S3Credentials): - if boto3 is None: - raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="boto3 not installed.") - - raw = f"{creds.accessKey}:{creds.secretKey}:{creds.region}:{creds.endpoint or ''}:{creds.bucket or ''}" - cache_key = hashlib.sha256(raw.encode()).hexdigest() - now = time.monotonic() - - with _CLIENT_CACHE_LOCK: - entry = _CLIENT_CACHE.get(cache_key) - if entry and entry[0] > now: - return entry[1] - - kwargs: dict[str, Any] = { - "aws_access_key_id": creds.accessKey, - "aws_secret_access_key": creds.secretKey, - "region_name": creds.region, - "config": Config( - signature_version="s3v4", - connect_timeout=10, - read_timeout=30, - max_pool_connections=50, - retries={"max_attempts": 3, "mode": "standard"}, - ), - } - if creds.endpoint: - kwargs["endpoint_url"] = creds.endpoint - client = boto3.client("s3", **kwargs) - - with _CLIENT_CACHE_LOCK: - if len(_CLIENT_CACHE) >= _CLIENT_CACHE_MAX: - expired = [k for k, (exp, _) in _CLIENT_CACHE.items() if exp <= now] - for k in expired: - del _CLIENT_CACHE[k] - if len(_CLIENT_CACHE) >= _CLIENT_CACHE_MAX: - oldest_key = min(_CLIENT_CACHE, key=lambda k: _CLIENT_CACHE[k][0]) - del _CLIENT_CACHE[oldest_key] - _CLIENT_CACHE[cache_key] = (now + _CLIENT_CACHE_TTL, client) - - return client - - -def _s3_error(exc: Exception) -> HTTPException: - if isinstance(exc, ClientError): - code = exc.response.get("Error", {}).get("Code", "Unknown") - msg = exc.response.get("Error", {}).get("Message", str(exc)) - if code in ("NoSuchBucket", "NoSuchKey"): - return HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg) - if code in ("AccessDenied", "InvalidAccessKeyId", "SignatureDoesNotMatch"): - return HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=msg) - return HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"S3 error ({code}): {msg}") - return HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) - - -# ── S3 operations (sync — boto3 is blocking) ────────────────────────────────── - -def list_buckets(body: ListBucketsRequest) -> list[BucketInfo]: - client = _s3_client(body.credentials) - try: - resp = client.list_buckets() - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - return [ - BucketInfo( - name=b["Name"], - creationDate=b.get("CreationDate", "").isoformat() if b.get("CreationDate") else None, - ) - for b in resp.get("Buckets", []) - ] - - -def list_objects(body: ListObjectsRequest) -> ListObjectsResponse: - client = _s3_client(body.credentials) - kwargs: dict[str, Any] = { - "Bucket": body.credentials.bucket, - "Prefix": body.prefix, - "Delimiter": body.delimiter, - "MaxKeys": body.maxKeys, - } - if body.continuationToken: - kwargs["ContinuationToken"] = body.continuationToken - try: - resp = client.list_objects_v2(**kwargs) - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - - objects: list[S3ObjectItem] = [] - for obj in resp.get("Contents", []): - key: str = obj["Key"] - if key == body.prefix: - continue - objects.append(S3ObjectItem( - key=key, - size=obj.get("Size"), - lastModified=obj.get("LastModified", "").isoformat() if obj.get("LastModified") else None, - etag=obj.get("ETag", "").strip('"'), - isFolder=key.endswith("/"), - )) - - prefixes = [p.get("Prefix", "") for p in resp.get("CommonPrefixes", [])] - return ListObjectsResponse( - objects=objects, - prefixes=prefixes, - isTruncated=bool(resp.get("IsTruncated", False)), - nextContinuationToken=resp.get("NextContinuationToken"), - ) - - -def delete_objects(body: DeleteObjectsRequest) -> dict[str, int]: - client = _s3_client(body.credentials) - to_delete = [{"Key": k} for k in body.keys] - try: - resp = client.delete_objects( - Bucket=body.credentials.bucket, - Delete={"Objects": to_delete, "Quiet": True}, - ) - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - errors = resp.get("Errors", []) - if errors: - raise HTTPException(status_code=status.HTTP_207_MULTI_STATUS, detail={"errors": errors}) - return {"deleted": len(to_delete)} - - -def create_folder(body: CreateFolderRequest) -> dict[str, str]: - key = body.prefix if body.prefix.endswith("/") else body.prefix + "/" - client = _s3_client(body.credentials) - try: - client.put_object(Bucket=body.credentials.bucket, Key=key, Body=b"") - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - return {"key": key} - - -def presigned_download(body: PresignedDownloadRequest) -> PresignedUrlResponse: - client = _s3_client(body.credentials) - try: - url = client.generate_presigned_url( - "get_object", - Params={"Bucket": body.credentials.bucket, "Key": body.key}, - ExpiresIn=body.expiresIn, - ) - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - return PresignedUrlResponse(url=url, key=body.key) - - -def presigned_upload(body: PresignedUploadRequest) -> PresignedUrlResponse: - client = _s3_client(body.credentials) - try: - url = client.generate_presigned_url( - "put_object", - Params={ - "Bucket": body.credentials.bucket, - "Key": body.key, - "ContentType": body.contentType, - }, - ExpiresIn=body.expiresIn, - ) - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - return PresignedUrlResponse(url=url, key=body.key) - - -def presigned_batch(body: PresignedBatchRequest) -> PresignedBatchResponse: - client = _s3_client(body.credentials) - bucket = body.credentials.bucket - urls: list[PresignedUrlResponse] = [] - try: - for item in body.items: - if item.op == "put": - params: dict[str, Any] = {"Bucket": bucket, "Key": item.key} - if item.contentType: - params["ContentType"] = item.contentType - url = client.generate_presigned_url("put_object", Params=params, ExpiresIn=body.expiresIn) - else: - url = client.generate_presigned_url( - "get_object", - Params={"Bucket": bucket, "Key": item.key}, - ExpiresIn=body.expiresIn, - ) - urls.append(PresignedUrlResponse(url=url, key=item.key)) - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - return PresignedBatchResponse(urls=urls) - - -def configure_bucket_cors(body: ListBucketsRequest, allowed_origins: list[str]) -> dict[str, str]: - client = _s3_client(body.credentials) - cors_config = { - "CORSRules": [ - { - "AllowedHeaders": ["*"], - "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"], - "AllowedOrigins": allowed_origins, - "ExposeHeaders": ["ETag", "Content-Length", "Content-Type"], - "MaxAgeSeconds": 3600, - } - ] - } - try: - client.put_bucket_cors(Bucket=body.credentials.bucket, CORSConfiguration=cors_config) - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - return {"bucket": body.credentials.bucket, "status": "cors_configured"} - - -def move_object(body: MoveObjectRequest) -> dict[str, str]: - client = _s3_client(body.credentials) - try: - client.copy_object( - Bucket=body.credentials.bucket, - CopySource={"Bucket": body.credentials.bucket, "Key": body.sourceKey}, - Key=body.destinationKey, - ) - client.delete_object(Bucket=body.credentials.bucket, Key=body.sourceKey) - except (ClientError, BotoCoreError) as exc: - raise _s3_error(exc) from exc - return {"sourceKey": body.sourceKey, "destinationKey": body.destinationKey} diff --git a/apps/backend/app/api/routes/sql_client/__init__.py b/apps/backend/app/api/routes/sql_client/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/sql_client/api.py b/apps/backend/app/api/routes/sql_client/api.py deleted file mode 100644 index bba69009..00000000 --- a/apps/backend/app/api/routes/sql_client/api.py +++ /dev/null @@ -1,67 +0,0 @@ -from fastapi import APIRouter, Depends - -from app.api.routes.sql_client.schema import ( - SqlConnectionCreate, - SqlConnectionOut, - SqlConnectionUpdate, -) -from app.api.routes.sql_client import services as svc -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -router = APIRouter(prefix="/sql-client", tags=["sql-client"]) - - -@router.get( - "/connections", - response_model=list[SqlConnectionOut], - summary="List saved SQL connections", -) -async def list_connections(ctx: WorkspaceContext = Depends(require_permission("sql-client", "read"))) -> list[SqlConnectionOut]: - return await svc.list_connections(ctx) - - -@router.post( - "/connections", - response_model=SqlConnectionOut, - summary="Save a new SQL connection", -) -async def create_connection( - body: SqlConnectionCreate, ctx: WorkspaceContext = Depends(require_permission("sql-client", "write")) -) -> SqlConnectionOut: - return await svc.create_connection(ctx, body) - - -@router.patch( - "/connections/{connection_id}", - response_model=SqlConnectionOut, - summary="Update a saved SQL connection", -) -async def update_connection( - connection_id: str, - body: SqlConnectionUpdate, - ctx: WorkspaceContext = Depends(require_permission("sql-client", "write")), -) -> SqlConnectionOut: - return await svc.update_connection(ctx, connection_id, body) - - -@router.delete( - "/connections/{connection_id}", - status_code=204, - summary="Delete a saved SQL connection", -) -async def delete_connection( - connection_id: str, ctx: WorkspaceContext = Depends(require_permission("sql-client", "delete")) -) -> None: - await svc.delete_connection(ctx, connection_id) - - -@router.post( - "/connections/{connection_id}/touch", - status_code=204, - summary="Update lastUsedAt for a connection", -) -async def touch_connection( - connection_id: str, ctx: WorkspaceContext = Depends(require_permission("sql-client", "write")) -) -> None: - await svc.touch_connection(ctx, connection_id) diff --git a/apps/backend/app/api/routes/sql_client/schema.py b/apps/backend/app/api/routes/sql_client/schema.py deleted file mode 100644 index 624a8dfe..00000000 --- a/apps/backend/app/api/routes/sql_client/schema.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Literal, Optional - -from pydantic import BaseModel, ConfigDict, Field - -DbType = Literal["postgresql", "mysql", "mariadb"] - - -class SqlConnectionCreate(BaseModel): - """ - The full connection config (host, port, database, username, password) is - encrypted client-side with the master AES-GCM key before being sent. - The server stores only the ciphertext blob. - """ - - model_config = ConfigDict(extra="ignore") - - encryptedData: str = Field(min_length=1) - iv: str = Field(min_length=1) - name: Optional[str] = Field(default=None, min_length=1) - type: DbType - - -class SqlConnectionUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - encryptedData: Optional[str] = Field(default=None, min_length=1) - iv: Optional[str] = Field(default=None, min_length=1) - name: Optional[str] = Field(default=None, min_length=1) - type: Optional[DbType] = None - - -class SqlConnectionOut(BaseModel): - """ - Returned to the client. The raw credentials are absent — the client decrypts - encryptedData locally using the master key. - """ - - model_config = ConfigDict(extra="ignore") - - id: str - userId: str - encryptedData: str - iv: str - name: str - type: DbType - createdAt: int - lastUsedAt: int - updatedAt: int diff --git a/apps/backend/app/api/routes/sql_client/services.py b/apps/backend/app/api/routes/sql_client/services.py deleted file mode 100644 index 1a67c081..00000000 --- a/apps/backend/app/api/routes/sql_client/services.py +++ /dev/null @@ -1,120 +0,0 @@ -from typing import Any - -from fastapi import HTTPException, status -from pymongo.errors import PyMongoError - -from app.api.routes.sql_client.schema import ( - SqlConnectionCreate, - SqlConnectionOut, - SqlConnectionUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.utils.collection_name import SQL_CONNECTIONS -from app.utils.utils import create_timestamp, new_id -from app.database import db_manager - - -def _doc_to_out(doc: dict[str, Any], *, connection_id: str) -> SqlConnectionOut: - created_at = int(doc.get("createdAt", 0)) or create_timestamp() - last_used_at = int(doc.get("lastUsedAt", 0)) or created_at - # Content-edit clock for sync LWW. Falls back to lastUsedAt for legacy docs - # written before updatedAt existed. - updated_at = int(doc.get("updatedAt", 0)) or last_used_at - return SqlConnectionOut( - id=connection_id, - userId=str(doc.get("created_by", "")), - encryptedData=str(doc.get("encryptedData", "")), - iv=str(doc.get("iv", "")), - name=str(doc.get("name", "")), - type=doc.get("type", "postgresql"), - createdAt=created_at, - lastUsedAt=last_used_at, - updatedAt=updated_at, - ) - - -async def list_connections(ctx: WorkspaceContext) -> list[SqlConnectionOut]: - flt = apply_legacy_or_filter(ctx, {"encryptedData": {"$exists": True}, "iv": {"$exists": True}}, user_field="created_by") - docs = await db_manager.find( - SQL_CONNECTIONS, - flt, - sort=[("lastUsedAt", -1), ("createdAt", -1)], - ) - return [_doc_to_out(doc, connection_id=str(doc.get("_id", ""))) for doc in docs] - - -async def create_connection(ctx: WorkspaceContext, body: SqlConnectionCreate) -> SqlConnectionOut: - ts = create_timestamp() - _id = new_id() - doc: dict[str, Any] = { - "_id": _id, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "encryptedData": body.encryptedData, - "iv": body.iv, - "name": body.name or "My Connection", - "type": body.type, - "createdAt": ts, - "lastUsedAt": ts, - "updatedAt": ts, - } - try: - await db_manager.insert_one(SQL_CONNECTIONS, doc) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create connection." - ) from exc - return _doc_to_out(doc, connection_id=_id) - - -async def update_connection(ctx: WorkspaceContext, connection_id: str, body: SqlConnectionUpdate) -> SqlConnectionOut: - flt = apply_workspace_filter(ctx, {"_id": connection_id, "created_by": ctx.uid}) - existing = await db_manager.find_one(SQL_CONNECTIONS, flt) - if not existing: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - - ts = create_timestamp() - # updatedAt is the sync LWW clock — it advances on every content edit (unlike - # lastUsedAt, which also moves on a bare connect/touch). - patch: dict[str, Any] = {"lastUsedAt": ts, "updatedAt": ts} - if body.encryptedData is not None: - patch["encryptedData"] = body.encryptedData - if body.iv is not None: - patch["iv"] = body.iv - if body.name is not None: - patch["name"] = body.name - if body.type is not None: - patch["type"] = body.type - - try: - await db_manager.update_one(SQL_CONNECTIONS, flt, {"$set": patch}) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update connection." - ) from exc - - updated = await db_manager.find_one(SQL_CONNECTIONS, flt) - if not updated: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - return _doc_to_out(updated, connection_id=connection_id) - - -async def delete_connection(ctx: WorkspaceContext, connection_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": connection_id, "created_by": ctx.uid}) - res = await db_manager.delete_one(SQL_CONNECTIONS, flt) - if res.deleted_count == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found.") - - -async def touch_connection(ctx: WorkspaceContext, connection_id: str) -> None: - flt = apply_workspace_filter(ctx, {"_id": connection_id, "created_by": ctx.uid}) - await db_manager.update_one( - SQL_CONNECTIONS, - flt, - {"$set": {"lastUsedAt": create_timestamp()}}, - ) diff --git a/apps/backend/app/api/routes/tasks/__init__.py b/apps/backend/app/api/routes/tasks/__init__.py deleted file mode 100644 index fb0a2f80..00000000 --- a/apps/backend/app/api/routes/tasks/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API route modules.""" diff --git a/apps/backend/app/api/routes/tasks/api.py b/apps/backend/app/api/routes/tasks/api.py deleted file mode 100644 index 30e1fd45..00000000 --- a/apps/backend/app/api/routes/tasks/api.py +++ /dev/null @@ -1,148 +0,0 @@ -from fastapi import APIRouter, Depends, Query - -from app.api.routes.tasks import services as task_svc -from app.api.routes.tasks.schema import ( - ProjectCreate, - ProjectOut, - ProjectUpdate, - TaskCreate, - TaskImportRequest, - TaskListResponse, - TaskOut, - TaskStatsOut, - TaskStatusUpdate, - TaskUpdate, -) -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission - -tasks_router = APIRouter(tags=["tasks"]) -projects_router = APIRouter(tags=["projects"]) - - -@tasks_router.get("/stats", response_model=TaskStatsOut, summary="Task counts (dashboard stats)") -async def task_stats(ctx: WorkspaceContext = Depends(require_permission("tasks", "read"))) -> TaskStatsOut: - return await task_svc.get_task_stats(ctx=ctx) - - -@tasks_router.get("/export", response_model=list[TaskOut], summary="All tasks for export (filtered)") -async def export_tasks( - ctx: WorkspaceContext = Depends(require_permission("tasks", "read")), - status: str = Query(default="all"), - project_id: str = Query(default="all", alias="projectId"), - assignee: str = Query(default="all"), - skip: int = Query(default=0, ge=0), - limit: int = Query(default=2000, ge=1, le=10000), -) -> list[TaskOut]: - return await task_svc.export_tasks( - ctx=ctx, - status_filter=status, - project_filter=project_id, - assignee_filter=assignee, - skip=skip, - limit=limit, - ) - - -@tasks_router.post("/import", summary="Batch import tasks") -async def import_tasks( - body: TaskImportRequest, - ctx: WorkspaceContext = Depends(require_permission("tasks", "admin")), -) -> dict[str, int]: - return await task_svc.import_tasks(ctx, body) - - -@tasks_router.get("", response_model=TaskListResponse, summary="Paginated task list") -async def list_tasks( - ctx: WorkspaceContext = Depends(require_permission("tasks", "read")), - status: str = Query(default="all"), - project_id: str = Query(default="all", alias="projectId"), - assignee: str = Query(default="all"), - page: int = Query(default=1, ge=1), - page_size: int = Query(default=10, ge=1, le=100, alias="pageSize"), -) -> TaskListResponse: - return await task_svc.list_tasks( - ctx=ctx, - status_filter=status, - project_filter=project_id, - assignee_filter=assignee, - page=page, - page_size=page_size, - ) - - -@tasks_router.get("/{task_id}", response_model=TaskOut, summary="Get one task") -async def get_task( - task_id: str, - ctx: WorkspaceContext = Depends(require_permission("tasks", "read")), -) -> TaskOut: - return await task_svc.get_task(ctx=ctx, task_id=task_id) - - -@tasks_router.post("", response_model=TaskOut, summary="Create task") -async def create_task( - body: TaskCreate, - ctx: WorkspaceContext = Depends(require_permission("tasks", "write")), -) -> TaskOut: - return await task_svc.create_task(ctx, body) - - -@tasks_router.patch("/{task_id}", response_model=TaskOut, summary="Partial update (Firestore updateDoc)") -async def patch_task( - task_id: str, - body: TaskUpdate, - ctx: WorkspaceContext = Depends(require_permission("tasks", "write")), -) -> TaskOut: - return await task_svc.update_task(ctx, task_id, body) - - -@tasks_router.patch("/{task_id}/status", response_model=TaskOut, summary="Update status + statusOrder") -async def patch_task_status( - task_id: str, - body: TaskStatusUpdate, - ctx: WorkspaceContext = Depends(require_permission("tasks", "write")), -) -> TaskOut: - return await task_svc.update_task_status(ctx, task_id, body) - - -@tasks_router.delete("/{task_id}", status_code=204, summary="Delete task") -async def remove_task( - task_id: str, - ctx: WorkspaceContext = Depends(require_permission("tasks", "delete")), -) -> None: - await task_svc.delete_task(ctx, task_id) - - -@projects_router.get("", response_model=list[ProjectOut], summary="List projects") -async def list_projects(ctx: WorkspaceContext = Depends(require_permission("tasks", "read"))) -> list[ProjectOut]: - return await task_svc.list_projects(ctx=ctx) - - -@projects_router.post("", response_model=ProjectOut, summary="Create project") -async def create_project( - body: ProjectCreate, - ctx: WorkspaceContext = Depends(require_permission("tasks", "write")), -) -> ProjectOut: - return await task_svc.create_project(ctx, body) - - -@projects_router.patch("/{project_id}", response_model=ProjectOut, summary="Update project") -async def patch_project( - project_id: str, - body: ProjectUpdate, - ctx: WorkspaceContext = Depends(require_permission("tasks", "write")), -) -> ProjectOut: - return await task_svc.update_project(ctx, project_id, body) - - -@projects_router.delete("/{project_id}", status_code=204, summary="Delete project") -async def remove_project( - project_id: str, - ctx: WorkspaceContext = Depends(require_permission("tasks", "delete")), -) -> None: - await task_svc.delete_project(ctx, project_id) - - -router = APIRouter() -router.include_router(tasks_router, prefix="/tasks") -router.include_router(projects_router, prefix="/projects") diff --git a/apps/backend/app/api/routes/tasks/schema.py b/apps/backend/app/api/routes/tasks/schema.py deleted file mode 100644 index 51480e4c..00000000 --- a/apps/backend/app/api/routes/tasks/schema.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -MongoDB document layout mirrors the Firestore collections used by the web todo app. - -Collections ------------ -- ``tasks`` — same logical model as Firestore ``tasks`` (per-user documents). -- ``projects`` — same logical model as Firestore ``projects``. - -Field names use camelCase where the frontend uses camelCase (``statusOrder``, ``projectId``, -``createdAt``, ``completedAt``, etc.) and ``created_by`` for the Firebase UID, matching -``TaskContext`` / ``ProjectContext``. - -Recommended MongoDB indexes (create once in production) -------------------------------------------------------- -Tasks (match query patterns in ``TaskContext``):: - - db.tasks.create_index( - [("created_by", 1), ("status", 1), ("statusOrder", 1), ("createdAt", -1)] - ) - db.tasks.create_index( - [("created_by", 1), ("projectId", 1), ("status", 1), ("statusOrder", 1), ("createdAt", -1)] - ) - -Projects:: - - db.projects.create_index([("created_by", 1), ("createdAt", 1)]) -""" - -from typing import Any, Literal - -from pydantic import BaseModel, ConfigDict, Field - -TaskStatus = Literal["not-started", "ongoing", "completed"] -TaskPriority = Literal["low", "medium", "high"] - - -class SubTask(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - text: str - completed: bool - - -class TaskTag(BaseModel): - model_config = ConfigDict(extra="ignore") - - id: str - name: str - color: str - - -class TaskBase(BaseModel): - """Writable task fields (aligned with ``NewTask`` / Firestore writes).""" - - text: str = Field(min_length=1) - description: str | None = None - status: TaskStatus = "not-started" - statusOrder: int = 2 - priority: TaskPriority | None = None - dueDate: str | None = None - tags: list[TaskTag] | None = None - subTasks: list[SubTask] | None = None - archived: bool | None = None - timeEstimate: int | None = None - timeLogged: int | None = None - isTimerRunning: bool | None = None - timerStartedAt: str | None = None - projectId: str | None = None - # Single assignee (a workspace member uid). None = unassigned. - assigneeUid: str | None = None - - -class TaskCreate(BaseModel): - """Maps to ``addTask``: title + optional project / assignee.""" - - text: str = Field(min_length=1) - projectId: str | None = None - assigneeUid: str | None = None - - -class TaskUpdate(BaseModel): - """ - Partial update; maps to ``updateTask`` (id / created_by / createdAt must not be sent). - Server sets ``completedAt`` when status becomes ``completed`` if omitted. - """ - - model_config = ConfigDict(extra="ignore") - - text: str | None = None - description: str | None = None - status: TaskStatus | None = None - statusOrder: int | None = None - priority: TaskPriority | None = None - dueDate: str | None = None - tags: list[TaskTag] | None = None - subTasks: list[SubTask] | None = None - archived: bool | None = None - timeEstimate: int | None = None - timeLogged: int | None = None - isTimerRunning: bool | None = None - timerStartedAt: str | None = None - projectId: str | None = None - assigneeUid: str | None = None - - -class TaskStatusUpdate(BaseModel): - """Maps to ``updateTaskStatus`` (status + derived ``statusOrder`` / ``completedAt``).""" - - status: TaskStatus - - -class TaskOut(BaseModel): - """API shape aligned with frontend ``Task`` (ISO datetimes as strings).""" - - model_config = ConfigDict(extra="allow") - - id: str - text: str - description: str | None = None - status: TaskStatus - statusOrder: int - priority: TaskPriority | None = None - dueDate: str | None = None - tags: list[dict[str, Any]] | None = None - subTasks: list[dict[str, Any]] | None = None - createdAt: str - completedAt: str | None = None - created_by: str - archived: bool | None = None - timeEstimate: int | None = None - timeLogged: int | None = None - isTimerRunning: bool | None = None - timerStartedAt: str | None = None - projectId: str | None = None - assigneeUid: str | None = None - - -class TaskListResponse(BaseModel): - items: list[TaskOut] - page: int - page_size: int - total: int - total_pages: int - - -class TaskStatsOut(BaseModel): - """Maps to ``allTaskStats`` + total count used for pagination.""" - - total: int - completed: int - ongoing: int - notStarted: int - - -class TaskImportRequest(BaseModel): - """Maps to ``importTasks`` batch insert.""" - - tasks: list[dict[str, Any]] - - -class ProjectBase(BaseModel): - name: str = Field(min_length=1) - color: str = Field(min_length=1) - - -class ProjectCreate(ProjectBase): - pass - - -class ProjectUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - name: str | None = Field(default=None, min_length=1) - color: str | None = Field(default=None, min_length=1) - - -class ProjectOut(BaseModel): - id: str - name: str - color: str - created_by: str - createdAt: str diff --git a/apps/backend/app/api/routes/tasks/services.py b/apps/backend/app/api/routes/tasks/services.py deleted file mode 100644 index d53b6a33..00000000 --- a/apps/backend/app/api/routes/tasks/services.py +++ /dev/null @@ -1,331 +0,0 @@ -from datetime import datetime, timezone -from typing import Any - -from bson import ObjectId -from bson.errors import InvalidId -from fastapi import HTTPException, status -from pymongo.errors import PyMongoError - -from app.api.routes.tasks.schema import ( - ProjectCreate, - ProjectOut, - ProjectUpdate, - TaskCreate, - TaskImportRequest, - TaskListResponse, - TaskOut, - TaskStatsOut, - TaskStatus, - TaskStatusUpdate, - TaskUpdate, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) -from app.database import db_manager -from app.utils.collection_name import PROJECTS, TASKS -from app.utils.crud import safe_delete_one, safe_insert, safe_update_one - -STATUS_ORDER_MAP: dict[TaskStatus, int] = { - "ongoing": 1, - "not-started": 2, - "completed": 3, -} - - -def _parse_object_id(raw: str, label: str = "id") -> ObjectId: - try: - return ObjectId(raw) - except InvalidId as exc: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid {label}.", - ) from exc - - -def _format_timestamp(value: Any) -> str: - if value is None: - return "" - if isinstance(value, datetime): - if value.tzinfo is None: - value = value.replace(tzinfo=timezone.utc) - return value.astimezone(timezone.utc).isoformat() - if isinstance(value, str): - return value - return str(value) - - -def _task_doc_to_out(doc: dict[str, Any]) -> TaskOut: - oid = doc.get("_id") - return TaskOut( - id=str(oid) if oid is not None else "", - text=doc.get("text", ""), - description=doc.get("description"), - status=doc["status"], - statusOrder=int(doc.get("statusOrder", 2)), - priority=doc.get("priority"), - dueDate=doc.get("dueDate"), - tags=doc.get("tags"), - subTasks=doc.get("subTasks"), - createdAt=_format_timestamp(doc.get("createdAt")) or "", - completedAt=_format_timestamp(doc.get("completedAt")) or None, - created_by=doc.get("created_by", ""), - archived=doc.get("archived"), - timeEstimate=doc.get("timeEstimate"), - timeLogged=doc.get("timeLogged"), - isTimerRunning=doc.get("isTimerRunning"), - timerStartedAt=doc.get("timerStartedAt"), - projectId=doc.get("projectId"), - assigneeUid=doc.get("assigneeUid"), - ) - - -def _project_doc_to_out(doc: dict[str, Any]) -> ProjectOut: - oid = doc.get("_id") - return ProjectOut( - id=str(oid) if oid is not None else "", - name=doc.get("name", ""), - color=doc.get("color", ""), - created_by=doc.get("created_by", ""), - createdAt=_format_timestamp(doc.get("createdAt")) or "", - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -def _apply_assignee(base: dict[str, Any], ctx: WorkspaceContext, assignee_filter: str) -> None: - """Mutate ``base`` with an assignee predicate. Supports the literals - ``all`` (no filter), ``me`` (caller), ``unassigned`` (no/None assignee), - or a specific member uid.""" - if not assignee_filter or assignee_filter == "all": - return - if assignee_filter == "unassigned": - base["assigneeUid"] = None - elif assignee_filter == "me": - base["assigneeUid"] = ctx.uid - else: - base["assigneeUid"] = assignee_filter - - -async def list_tasks( - *, - ctx: WorkspaceContext, - status_filter: str = "all", - project_filter: str = "all", - assignee_filter: str = "all", - page: int = 1, - page_size: int = 10, -) -> TaskListResponse: - base: dict[str, Any] = {} - if status_filter and status_filter != "all": - base["status"] = status_filter - if project_filter and project_filter != "all": - base["projectId"] = project_filter - _apply_assignee(base, ctx, assignee_filter) - filt = apply_legacy_or_filter(ctx, base, user_field="created_by") - total = await db_manager.count_documents(TASKS, filt) - total_pages = max(1, (total + page_size - 1) // page_size) if total else 1 - skip = max(0, (page - 1) * page_size) - docs = await db_manager.find(TASKS, filt, sort=[("statusOrder", 1), ("createdAt", -1)], skip=skip, limit=page_size) - return TaskListResponse( - items=[_task_doc_to_out(d) for d in docs], - page=page, - page_size=page_size, - total=total, - total_pages=total_pages, - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_task_stats(*, ctx: WorkspaceContext) -> TaskStatsOut: - filt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - pipeline = [ - {"$match": filt}, - {"$group": {"_id": "$status", "count": {"$sum": 1}}}, - ] - rows = await db_manager.aggregate(TASKS, pipeline) - counts: dict[str, int] = {r["_id"]: r["count"] for r in rows} - return TaskStatsOut( - total=sum(counts.values()), - completed=counts.get("completed", 0), - ongoing=counts.get("ongoing", 0), - notStarted=counts.get("not-started", 0), - ) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def export_tasks( - *, - ctx: WorkspaceContext, - status_filter: str = "all", - project_filter: str = "all", - assignee_filter: str = "all", - skip: int = 0, - limit: int = 2000, -) -> list[TaskOut]: - base: dict[str, Any] = {} - if status_filter and status_filter != "all": - base["status"] = status_filter - if project_filter and project_filter != "all": - base["projectId"] = project_filter - _apply_assignee(base, ctx, assignee_filter) - filt = apply_legacy_or_filter(ctx, base, user_field="created_by") - docs = await db_manager.find( - TASKS, - filt, - sort=[("statusOrder", 1), ("createdAt", -1)], - skip=max(0, skip), - limit=max(1, limit), - ) - return [_task_doc_to_out(d) for d in docs] - - -async def create_task(ctx: WorkspaceContext, body: TaskCreate) -> TaskOut: - now = datetime.now(timezone.utc) - doc: dict[str, Any] = { - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "text": body.text, - "status": "not-started", - "statusOrder": 2, - "createdAt": now, - "projectId": body.projectId, - "assigneeUid": body.assigneeUid, - } - await safe_insert(TASKS, doc, name="Task") - return _task_doc_to_out(doc) - - -def _task_scope(ctx: WorkspaceContext, oid: ObjectId) -> dict[str, Any]: - """Row filter for a single task by id. Scopes to the active workspace (any - member may edit shared-workspace tasks; personal workspaces stay owner-locked - via ``owner_uid``) and tolerates legacy pre-migration docs via ``created_by``. - - NOTE: no ``created_by == uid`` clause — collaboration relies on - ``require_permission("tasks", ...)`` for role gating, not creator ownership.""" - return apply_legacy_or_filter(ctx, {"_id": oid}, user_field="created_by") - - -async def _assert_task_owner(ctx: WorkspaceContext, oid: ObjectId) -> dict[str, Any]: - doc = await db_manager.find_one(TASKS, _task_scope(ctx, oid)) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found.") - return doc - - -async def update_task(ctx: WorkspaceContext, task_id: str, body: TaskUpdate) -> TaskOut: - oid = _parse_object_id(task_id, "task id") - - patch = body.model_dump(exclude_unset=True) - patch.pop("id", None) - patch.pop("completedAt", None) - - if body.status == "completed": - patch["completedAt"] = datetime.now(timezone.utc) - - if not patch: - doc = await db_manager.find_one(TASKS, _task_scope(ctx, oid)) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found.") - return _task_doc_to_out(doc) - - doc = await safe_update_one(TASKS, _task_scope(ctx, oid), patch, name="Task") - return _task_doc_to_out(doc) - - -async def update_task_status(ctx: WorkspaceContext, task_id: str, body: TaskStatusUpdate) -> TaskOut: - oid = _parse_object_id(task_id, "task id") - new_status = body.status - patch: dict[str, Any] = { - "status": new_status, - "statusOrder": STATUS_ORDER_MAP[new_status], - } - if new_status == "completed": - patch["completedAt"] = datetime.now(timezone.utc) - doc = await safe_update_one(TASKS, _task_scope(ctx, oid), patch, name="Task") - return _task_doc_to_out(doc) - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def get_task(*, ctx: WorkspaceContext, task_id: str) -> TaskOut: - oid = _parse_object_id(task_id, "task id") - doc = await _assert_task_owner(ctx, oid) - return _task_doc_to_out(doc) - - -async def delete_task(ctx: WorkspaceContext, task_id: str) -> None: - oid = _parse_object_id(task_id, "task id") - await safe_delete_one(TASKS, _task_scope(ctx, oid), name="Task") - - -async def import_tasks(ctx: WorkspaceContext, body: TaskImportRequest) -> dict[str, int]: - now = datetime.now(timezone.utc) - docs: list[dict[str, Any]] = [] - for raw in body.tasks: - row = dict(raw) - row.pop("id", None) - row.pop("createdAt", None) - row.pop("completedAt", None) - row["created_by"] = ctx.uid - row["workspace_id"] = ctx.workspace_id - row["owner_uid"] = ctx.uid - row["createdAt"] = now - if row.get("status") not in ("not-started", "ongoing", "completed"): - row["status"] = "not-started" - if "statusOrder" not in row: - row["statusOrder"] = STATUS_ORDER_MAP.get(row["status"], 2) - if raw.get("completedAt"): - row["completedAt"] = now - docs.append(row) - if not docs: - return {"inserted": 0} - try: - result = await db_manager.insert_many(TASKS, docs) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to import tasks." - ) from exc - return {"inserted": len(result.inserted_ids)} - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_projects(*, ctx: WorkspaceContext) -> list[ProjectOut]: - filt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - docs = await db_manager.find(PROJECTS, filt, sort=[("createdAt", 1)]) - return [_project_doc_to_out(d) for d in docs] - - -async def create_project(ctx: WorkspaceContext, body: ProjectCreate) -> ProjectOut: - now = datetime.now(timezone.utc) - doc = { - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "name": body.name, - "color": body.color, - "createdAt": now, - } - await safe_insert(PROJECTS, doc, name="Project") - return _project_doc_to_out(doc) - - -async def update_project(ctx: WorkspaceContext, project_id: str, body: ProjectUpdate) -> ProjectOut: - oid = _parse_object_id(project_id, "project id") - patch = body.model_dump(exclude_unset=True) - if not patch: - filt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await db_manager.find_one(PROJECTS, filt) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found.") - return _project_doc_to_out(doc) - filt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - doc = await safe_update_one(PROJECTS, filt, patch, name="Project") - return _project_doc_to_out(doc) - - -async def delete_project(ctx: WorkspaceContext, project_id: str) -> None: - oid = _parse_object_id(project_id, "project id") - filt = apply_workspace_filter(ctx, {"_id": oid, "created_by": ctx.uid}) - await safe_delete_one(PROJECTS, filt, name="Project") diff --git a/apps/backend/app/api/routes/url_shortener/__init__.py b/apps/backend/app/api/routes/url_shortener/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/url_shortener/api.py b/apps/backend/app/api/routes/url_shortener/api.py deleted file mode 100644 index 10ef7db5..00000000 --- a/apps/backend/app/api/routes/url_shortener/api.py +++ /dev/null @@ -1,123 +0,0 @@ -import logging - -from fastapi import APIRouter, BackgroundTasks, Depends, Query, Request -from fastapi.responses import RedirectResponse - -from app.api.routes.url_shortener import services as svc -from app.api.routes.url_shortener.schema import ( - ShortLinkCreate, - ShortLinkOut, - ShortLinkResolve, - ShortLinkUpdate, - LinkAnalytics, -) -from app.api.routes.workspaces.middleware import WorkspaceContext -from app.api.routes.workspaces.rbac import require_permission -from app.core.redis_client import get_redis - -router = APIRouter(prefix="/url-shortener", tags=["url-shortener"]) -log = logging.getLogger("app.url_shortener") - -_CLICK_WINDOW = 60 # seconds -_CLICK_MAX = 5 # per IP per code per window - - -async def _is_click_rate_limited(ip: str, code: str) -> bool: - r = get_redis() - if r is None: - return False # ponytail: fail-open if Redis down - key = f"rl:click:{ip}:{code}".encode() - try: - count = await r.incr(key) - if count == 1: - await r.expire(key, _CLICK_WINDOW) - return count > _CLICK_MAX - except Exception as exc: # noqa: BLE001 - log.warning("click_rate.error err=%s", exc) - return False - - -@router.post("", response_model=ShortLinkOut, summary="Create a short link") -async def create_link( - body: ShortLinkCreate, - ctx: WorkspaceContext = Depends(require_permission("url-shortener", "write")), -) -> ShortLinkOut: - return await svc.create_link(ctx, body) - - -@router.get("", response_model=list[ShortLinkOut], summary="List user's short links") -async def list_links( - ctx: WorkspaceContext = Depends(require_permission("url-shortener", "read")), - skip: int = Query(default=0, ge=0), - limit: int = Query(default=100, ge=1, le=500), -) -> list[ShortLinkOut]: - return await svc.list_my_short_urls(ctx=ctx, skip=skip, limit=limit) - - -@router.get("/resolve/{code}", response_model=ShortLinkResolve, summary="Resolve a short code (public)") -async def resolve_link(code: str) -> ShortLinkResolve: - return await svc.resolve_short_url(slug=code) - - -@router.get("/r/{code}", summary="Resolve + redirect + record click (public; one-hop)") -async def redirect_short( - code: str, - request: Request, - background: BackgroundTasks, -) -> RedirectResponse: - """Single-hop redirect: skips the Next.js round-trip. Reverse-proxy /s/* to this.""" - resolved = await svc.resolve_short_url(slug=code) - if not resolved.active: - target = "/link-disabled" - else: - target = resolved.original_url - - ip = request.headers.get( - "x-forwarded-for", - request.client.host if request.client else "unknown", - ).split(",")[0].strip() - - if not await _is_click_rate_limited(ip, code): - ua = request.headers.get("user-agent", "") - referrer = request.headers.get("referer", "") - background.add_task(svc.record_click, code, ua=ua, referrer=referrer) - - response = RedirectResponse(url=target, status_code=302) - response.headers["Cache-Control"] = "public, max-age=60, s-maxage=300" - return response - - -@router.post("/{code}/click", status_code=204, summary="Record a click (public)") -async def record_click(code: str, request: Request) -> None: - ip = request.headers.get("x-forwarded-for", request.client.host if request.client else "unknown").split(",")[0].strip() - if await _is_click_rate_limited(ip, code): - return # silently ignore, don't error - ua = request.headers.get("user-agent", "") - referrer = request.headers.get("referer", "") - await svc.record_click(code, ua=ua, referrer=referrer) - - -@router.get("/{code}/analytics", response_model=LinkAnalytics, summary="Get click analytics for a link") -async def get_analytics( - code: str, - days: int = Query(default=30, ge=1, le=365), - ctx: WorkspaceContext = Depends(require_permission("url-shortener", "read")), -) -> LinkAnalytics: - return await svc.get_analytics(ctx=ctx, code=code, days=days) - - -@router.patch("/{code}", response_model=ShortLinkOut, summary="Update title or active state") -async def update_link( - code: str, - body: ShortLinkUpdate, - ctx: WorkspaceContext = Depends(require_permission("url-shortener", "write")), -) -> ShortLinkOut: - return await svc.update_link(ctx, code, body) - - -@router.delete("/{code}", status_code=204, summary="Delete a short link") -async def delete_link( - code: str, - ctx: WorkspaceContext = Depends(require_permission("url-shortener", "delete")), -) -> None: - await svc.delete_link(ctx, code) diff --git a/apps/backend/app/api/routes/url_shortener/click_queue.py b/apps/backend/app/api/routes/url_shortener/click_queue.py deleted file mode 100644 index fa161126..00000000 --- a/apps/backend/app/api/routes/url_shortener/click_queue.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Buffered click writes: events RPUSHed to Redis, periodic flush bulk-writes to Mongo.""" -from __future__ import annotations - -import asyncio -import logging -from collections import Counter -from datetime import datetime, timedelta, timezone -from typing import Optional -from uuid import uuid4 - -import orjson -from pymongo import UpdateOne -from pymongo.errors import BulkWriteError -from redis.asyncio import Redis - -from app.api.routes.url_shortener.schema import COLLECTION, CLICKS_COLLECTION -from app.core.config import get_settings -from app.core.redis_client import get_redis -from app.database import db_manager - -log = logging.getLogger("app.url_shortener.click_queue") - -_QUEUE_KEY = b"url_shortener:clicks:queue" -_FLUSH_INTERVAL_S = 5.0 -_MAX_DRAIN = 500 # ponytail: cap per tick to bound Mongo bulk size; raise if traffic warrants -_CLICK_RETENTION_DAYS = 90 -_FLUSH_SOCKET_TIMEOUT_S = 2.0 # generous timeout for pipeline ops; cache layer keeps its own tight one - -_flush_client: Optional[Redis] = None - - -def _get_flush_client() -> Optional[Redis]: - """Dedicated client for the click queue. Longer socket_timeout than the cache client.""" - global _flush_client - if _flush_client is not None: - return _flush_client - settings = get_settings() - if settings.REDIS_URL is None: - return None - _flush_client = Redis.from_url( - settings.REDIS_URL, - encoding="utf-8", - decode_responses=False, - socket_timeout=_FLUSH_SOCKET_TIMEOUT_S, - socket_connect_timeout=2.0, - health_check_interval=30, - ) - return _flush_client - - -async def close_flush_client() -> None: - global _flush_client - if _flush_client is not None: - try: - await _flush_client.aclose() - except Exception as exc: # noqa: BLE001 - log.warning("click_queue.flush_client.close.failed err=%s", exc) - _flush_client = None - - -async def enqueue_click(event: dict) -> bool: - """Push event to Redis; returns True if queued, False if Redis unavailable.""" - r = get_redis() - if r is None: - return False - # _id stamped here so retries after a partial flush dedupe on insert_many - event.setdefault("_id", uuid4().hex) - try: - await r.rpush(_QUEUE_KEY, orjson.dumps(event)) - return True - except Exception as exc: # noqa: BLE001 - log.warning("click_queue.enqueue.failed err=%s", exc) - return False - - -async def _drain_once() -> int: - """Peek-ack drain: LRANGE → Mongo write → LTRIM. Idempotent on retry via event _id.""" - r = _get_flush_client() - if r is None: - return 0 - raw_events = await r.lrange(_QUEUE_KEY, 0, _MAX_DRAIN - 1) - if not raw_events: - return 0 - - events: list[dict] = [] - for raw in raw_events: - try: - events.append(orjson.loads(raw)) - except Exception: # noqa: BLE001 - continue - if not events: - # Bad payloads — ack so we don't loop forever - await r.ltrim(_QUEUE_KEY, len(raw_events), -1) - return 0 - - expire_at = datetime.now(timezone.utc) + timedelta(days=_CLICK_RETENTION_DAYS) - for e in events: - e["expireAt"] = expire_at - - db = db_manager.get_db() - - # Insert clicks. Dup _id (11000) → already inserted on prior tick; skip in the inc step below. - dup_indexes: set[int] = set() - try: - await db[CLICKS_COLLECTION].insert_many(events, ordered=False) - except BulkWriteError as bwe: - write_errors = bwe.details.get("writeErrors", []) if bwe.details else [] - dup_indexes = {we["index"] for we in write_errors if we.get("code") == 11000} - other_errors = [we for we in write_errors if we.get("code") != 11000] - if other_errors: - log.warning( - "click_queue.insert_many.partial dup=%d other=%d sample=%s", - len(dup_indexes), len(other_errors), other_errors[:1], - ) - # Real failures (not just dups) → don't ack; next tick retries the batch - return 0 - except Exception as exc: # noqa: BLE001 - log.warning("click_queue.insert_many.failed err=%s n=%d", exc, len(events)) - return 0 - - # Increment counters only for newly-inserted events to avoid double-counting on retry - new_events = [e for i, e in enumerate(events) if i not in dup_indexes] - counts: Counter[str] = Counter(e["code"] for e in new_events if "code" in e) - if counts: - ops = [UpdateOne({"_id": code}, {"$inc": {"clicks": n}}) for code, n in counts.items()] - try: - await db[COLLECTION].bulk_write(ops, ordered=False) - except Exception as exc: # noqa: BLE001 - # Counters are denormalized; events already written. Log and ack to avoid re-double-count loop. - log.warning("click_queue.bulk_inc.failed err=%s n=%d", exc, len(ops)) - - # Ack: trim the exact range we read. New events arriving after LRANGE stay queued. - try: - await r.ltrim(_QUEUE_KEY, len(raw_events), -1) - except Exception as exc: # noqa: BLE001 - # ponytail: ltrim failed after Mongo write succeeded; next tick will re-insert (dup-key no-op) - # and re-inc only newly-inserted (zero), so no double-count. - log.warning("click_queue.ltrim.failed err=%s n=%d", exc, len(raw_events)) - - return len(events) - - -async def flush_loop() -> None: - log.info("click_queue.flush_loop.start interval=%ss", _FLUSH_INTERVAL_S) - while True: - try: - await asyncio.sleep(_FLUSH_INTERVAL_S) - n = await _drain_once() - if n: - log.info("click_queue.flushed n=%d", n) - except asyncio.CancelledError: - # Final drain on shutdown — don't lose buffered events - try: - while await _drain_once(): - pass - except Exception: # noqa: BLE001 - pass - raise - except Exception as exc: # noqa: BLE001 - log.warning("click_queue.flush.tick.failed err=%s", exc) diff --git a/apps/backend/app/api/routes/url_shortener/schema.py b/apps/backend/app/api/routes/url_shortener/schema.py deleted file mode 100644 index fbd8c789..00000000 --- a/apps/backend/app/api/routes/url_shortener/schema.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - -COLLECTION = "url_links" -CLICKS_COLLECTION = "url_click_events" - - -class ShortLinkCreate(BaseModel): - model_config = ConfigDict(extra="ignore") - - original_url: str = Field(min_length=1, max_length=2048) - title: Optional[str] = Field(default=None, max_length=200) - custom_code: Optional[str] = Field(default=None, min_length=3, max_length=20, pattern=r'^[a-zA-Z0-9][a-zA-Z0-9\-_]*$') - - -class ShortLinkUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - title: Optional[str] = Field(default=None, max_length=200) - active: Optional[bool] = None - - -class ShortLinkOut(BaseModel): - code: str - original_url: str - title: str - created_by: str - created_at: int - clicks: int - active: bool - - -class ShortLinkResolve(BaseModel): - original_url: str - active: bool - - -# ── Analytics ────────────────────────────────────────────────────────────── - - -class DailyClicks(BaseModel): - date: str - clicks: int - - -class StatEntry(BaseModel): - label: str - clicks: int - - -class LinkAnalytics(BaseModel): - total_clicks: int - daily: list[DailyClicks] - referrers: list[StatEntry] - devices: list[StatEntry] - os: list[StatEntry] - browsers: list[StatEntry] diff --git a/apps/backend/app/api/routes/url_shortener/services.py b/apps/backend/app/api/routes/url_shortener/services.py deleted file mode 100644 index c4141d53..00000000 --- a/apps/backend/app/api/routes/url_shortener/services.py +++ /dev/null @@ -1,381 +0,0 @@ -import asyncio -import re -import secrets -import string -from datetime import datetime, timedelta, timezone -from typing import Any -from urllib.parse import urlparse - -from fastapi import HTTPException, status -from pymongo.errors import DuplicateKeyError - -from app.core.cache import cached, cache_invalidate -from app.core.cache.keys import build_key, args_hash as _ah -from app.core.config import get_settings -from app.database import db_manager -from app.utils.utils import create_timestamp -from app.api.routes.url_shortener.schema import ( - COLLECTION, - CLICKS_COLLECTION, - ShortLinkCreate, - ShortLinkOut, - ShortLinkResolve, - ShortLinkUpdate, - LinkAnalytics, - DailyClicks, - StatEntry, -) -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, -) - - -def _resolve_key(slug: str) -> str: - secret = (get_settings().JWT_SECRET_KEY or "default-cache-secret").encode() - return build_key( - ns="url_shortener_resolve", - scope="global", - uid=None, - ver=None, - op="resolve_short_url", - args_hash=_ah({"slug": slug}, secret=secret), - ) - -_FORBIDDEN_SCHEME = re.compile(r"^\s*([a-zA-Z][a-zA-Z0-9+.-]*):") -_BLOCKED_HOSTNAMES = frozenset( - {"javascript", "data", "vbscript", "file", "blob", "about"}, -) - - -def _validate_original_url(raw: str) -> str: - s = str(raw).strip() - if not s: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="URL is required.") - m = _FORBIDDEN_SCHEME.match(s) - if m and m.group(1).lower() not in ("http", "https"): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Only http(s) URLs are allowed.", - ) - url = s if s.startswith(("http://", "https://")) else f"https://{s}" - parsed = urlparse(url) - if parsed.scheme not in ("http", "https"): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Only http(s) URLs are allowed.", - ) - host = (parsed.hostname or "").lower() - if not host: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Invalid URL host.", - ) - if host in _BLOCKED_HOSTNAMES: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="This URL host is not allowed.", - ) - return url - - -def _is_safe_redirect_target(url: str) -> bool: - try: - parsed = urlparse(url) - except Exception: - return False - if parsed.scheme not in ("http", "https"): - return False - host = (parsed.hostname or "").lower() - if not host or host in _BLOCKED_HOSTNAMES: - return False - return True - - -_ALPHABET = string.ascii_letters + string.digits -_CODE_LEN = 7 -_MAX_RETRIES = 5 - - -def _generate_code() -> str: - return "".join(secrets.choice(_ALPHABET) for _ in range(_CODE_LEN)) - - -def _doc_to_out(doc: dict[str, Any]) -> ShortLinkOut: - return ShortLinkOut( - code=str(doc["_id"]), - original_url=doc.get("original_url", ""), - title=doc.get("title", ""), - created_by=doc.get("created_by", ""), - created_at=int(doc.get("created_at", 0)), - clicks=int(doc.get("clicks", 0)), - active=bool(doc.get("active", True)), - ) - - -def _parse_ua(ua: str) -> tuple[str, str, str]: - """Returns (device, os_name, browser) from a User-Agent string.""" - u = ua.lower() - - if "ipad" in u or ("tablet" in u) or ("android" in u and "mobile" not in u): - device = "Tablet" - elif "mobile" in u: - device = "Mobile" - else: - device = "Desktop" - - if "windows nt" in u: - os_name = "Windows" - elif "iphone" in u or "ipad" in u: - os_name = "iOS" - elif "android" in u: - os_name = "Android" - elif "mac os x" in u or "macos" in u: - os_name = "macOS" - elif "cros" in u: - os_name = "ChromeOS" - elif "linux" in u: - os_name = "Linux" - else: - os_name = "Other" - - # Order matters: Edge/Opera check before Chrome/Safari - if "edg/" in u or "edge/" in u: - browser = "Edge" - elif "opr/" in u or "opera" in u: - browser = "Opera" - elif "samsungbrowser" in u: - browser = "Samsung" - elif "chrome/" in u or "chromium" in u or "crios/" in u: - browser = "Chrome" - elif "firefox/" in u or "fxios/" in u: - browser = "Firefox" - elif "safari/" in u: - browser = "Safari" - else: - browser = "Other" - - return device, os_name, browser - - -def _parse_referrer(ref: str) -> str: - if not ref or not ref.strip(): - return "Direct" - try: - hostname = urlparse(ref).hostname or "" - if not hostname: - return "Direct" - return re.sub(r"^www\.", "", hostname.lower()) - except Exception: - return "Direct" - - -async def create_link(ctx: WorkspaceContext, body: ShortLinkCreate) -> ShortLinkOut: - db = db_manager.get_db() - col = db[COLLECTION] - - url = _validate_original_url(body.original_url) - title = body.title or _extract_hostname(url) - base_doc = { - "original_url": url, - "title": title, - "created_by": ctx.uid, - "workspace_id": ctx.workspace_id, - "owner_uid": ctx.uid, - "created_at": create_timestamp(), - "clicks": 0, - "active": True, - } - - if body.custom_code: - code = body.custom_code - try: - await col.insert_one({"_id": code, **base_doc}) - except DuplicateKeyError: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail=f"Code '{code}' is already taken.", - ) - else: - for _ in range(_MAX_RETRIES): - code = _generate_code() - try: - await col.insert_one({"_id": code, **base_doc}) - break - except DuplicateKeyError: - continue - else: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="Could not generate a unique code. Try again.", - ) - - await cache_invalidate(ns="url_shortener_resolve", key=_resolve_key(code)) - return _doc_to_out({"_id": code, **base_doc}) - - -def _extract_hostname(url: str) -> str: - try: - return urlparse(url).hostname or url - except Exception: - return url - - -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid) key if hot -async def list_my_short_urls(*, ctx: WorkspaceContext, skip: int = 0, limit: int = 100) -> list[ShortLinkOut]: - db = db_manager.get_db() - col = db[COLLECTION] - flt = apply_legacy_or_filter(ctx, {}, user_field="created_by") - cursor = col.find(flt).sort("created_at", -1).skip(skip).limit(limit) - return [_doc_to_out(doc) async for doc in cursor] - - -@cached(ns="url_shortener_resolve", ttl=600, scope="global", strategy="xfetch") -async def resolve_short_url(*, slug: str) -> ShortLinkResolve: - db = db_manager.get_db() - col = db[COLLECTION] - doc = await col.find_one({"_id": slug}, {"original_url": 1, "active": 1}) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Short link not found.") - original_url = doc["original_url"] - if not _is_safe_redirect_target(str(original_url)): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Short link not found.") - return ShortLinkResolve(original_url=str(original_url), active=bool(doc.get("active", True))) - - -_CLICK_RETENTION_DAYS = 90 - - -async def record_click(code: str, ua: str = "", referrer: str = "") -> None: - device, os_name, browser = _parse_ua(ua) - ref_label = _parse_referrer(referrer) - - # Fetch the link document to inherit workspace stamps - db = db_manager.get_db() - link_doc = await db[COLLECTION].find_one({"_id": code}, {"workspace_id": 1, "owner_uid": 1}) - - event = { - "code": code, - "ts": create_timestamp(), - "referrer": ref_label, - "device": device, - "os": os_name, - "browser": browser, - } - - # Inherit workspace stamps from the link - if link_doc: - if "workspace_id" in link_doc: - event["workspace_id"] = link_doc["workspace_id"] - if "owner_uid" in link_doc: - event["owner_uid"] = link_doc["owner_uid"] - - # Fast path: enqueue to Redis, flush_loop bulk-writes every few seconds. - from app.api.routes.url_shortener.click_queue import enqueue_click - if await enqueue_click(event): - return - - # Fallback: Redis down — direct write keeps clicks working - event["expireAt"] = datetime.now(timezone.utc) + timedelta(days=_CLICK_RETENTION_DAYS) - await db[CLICKS_COLLECTION].insert_one(event) - await db[COLLECTION].update_one({"_id": code}, {"$inc": {"clicks": 1}}) - - -# ponytail: skipped precomputed daily rollups; relies on 60s cache + 90d TTL + (code,ts) index. -# Add a url_click_daily rollup collection + nightly job when per-code clicks > ~10k/day. -# ponytail: cache removed during workspace refactor; re-add with (workspace_id, uid, code) key if hot -async def get_analytics(*, ctx: WorkspaceContext, code: str, days: int = 30) -> LinkAnalytics: - db = db_manager.get_db() - - flt = apply_workspace_filter(ctx, {"_id": code, "created_by": ctx.uid}) - doc = await db[COLLECTION].find_one(flt, {"clicks": 1}) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Short link not found.") - - events_col = db[CLICKS_COLLECTION] - since = create_timestamp() - days * 86_400_000 - - base_match: dict[str, Any] = {"code": code, "ts": {"$gte": since}} - - # Daily clicks grouped by UTC date string — all 5 queries run concurrently - ( - daily_raw, - referrers_raw, - devices_raw, - os_raw, - browsers_raw, - ) = await asyncio.gather( - events_col.aggregate([ - {"$match": base_match}, - {"$group": { - "_id": {"$dateToString": {"format": "%Y-%m-%d", "date": {"$toDate": "$ts"}}}, - "clicks": {"$sum": 1}, - }}, - {"$sort": {"_id": 1}}, - ]).to_list(None), - events_col.aggregate([ - {"$match": base_match}, - {"$group": {"_id": "$referrer", "clicks": {"$sum": 1}}}, - {"$sort": {"clicks": -1}}, - {"$limit": 10}, - ]).to_list(None), - events_col.aggregate([ - {"$match": base_match}, - {"$group": {"_id": "$device", "clicks": {"$sum": 1}}}, - {"$sort": {"clicks": -1}}, - ]).to_list(None), - events_col.aggregate([ - {"$match": base_match}, - {"$group": {"_id": "$os", "clicks": {"$sum": 1}}}, - {"$sort": {"clicks": -1}}, - {"$limit": 8}, - ]).to_list(None), - events_col.aggregate([ - {"$match": base_match}, - {"$group": {"_id": "$browser", "clicks": {"$sum": 1}}}, - {"$sort": {"clicks": -1}}, - {"$limit": 8}, - ]).to_list(None), - ) - - return LinkAnalytics( - total_clicks=int(doc.get("clicks", 0)), - daily=[DailyClicks(date=d["_id"], clicks=d["clicks"]) for d in daily_raw], - referrers=[StatEntry(label=r["_id"] or "Direct", clicks=r["clicks"]) for r in referrers_raw], - devices=[StatEntry(label=d["_id"] or "Unknown", clicks=d["clicks"]) for d in devices_raw], - os=[StatEntry(label=o["_id"] or "Unknown", clicks=o["clicks"]) for o in os_raw], - browsers=[StatEntry(label=b["_id"] or "Unknown", clicks=b["clicks"]) for b in browsers_raw], - ) - - -async def update_link(ctx: WorkspaceContext, code: str, body: ShortLinkUpdate) -> ShortLinkOut: - db = db_manager.get_db() - col = db[COLLECTION] - patch: dict[str, Any] = {} - if body.title is not None: - patch["title"] = body.title - if body.active is not None: - patch["active"] = body.active - if not patch: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Nothing to update.") - flt = apply_workspace_filter(ctx, {"_id": code, "created_by": ctx.uid}) - doc = await col.find_one_and_update( - flt, - {"$set": patch}, - return_document=True, - ) - if not doc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Short link not found.") - await cache_invalidate(ns="url_shortener_resolve", key=_resolve_key(code)) - return _doc_to_out(doc) - - -async def delete_link(ctx: WorkspaceContext, code: str) -> None: - db = db_manager.get_db() - col = db[COLLECTION] - flt = apply_workspace_filter(ctx, {"_id": code, "created_by": ctx.uid}) - result = await col.delete_one(flt) - if result.deleted_count == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Short link not found.") - await cache_invalidate(ns="url_shortener_resolve", key=_resolve_key(code)) diff --git a/apps/backend/app/api/routes/user_preferences/api.py b/apps/backend/app/api/routes/user_preferences/api.py deleted file mode 100644 index ce7a65ba..00000000 --- a/apps/backend/app/api/routes/user_preferences/api.py +++ /dev/null @@ -1,61 +0,0 @@ -from fastapi import APIRouter, Depends, Query - -from app.api.routes.auth.services import get_current_uid -from app.api.routes.user_preferences.schema import ( - NosqlQueryHistoryOut, - NosqlQueryHistoryPut, - ToolUsageTrackRequest, - UserPreferencesOut, - UserPreferencesUpdate, -) -from app.api.routes.user_preferences import services as pref_svc - - -router = APIRouter(prefix="/user-preferences", tags=["user-preferences"]) - - -@router.get("", response_model=UserPreferencesOut, summary="Get user preferences") -async def get_prefs(uid: str = Depends(get_current_uid)) -> UserPreferencesOut: - return await pref_svc.get_preferences(uid=uid) - - -@router.patch("", response_model=UserPreferencesOut, summary="Update user preferences (partial)") -async def patch_prefs(body: UserPreferencesUpdate, uid: str = Depends(get_current_uid)) -> UserPreferencesOut: - return await pref_svc.patch_preferences(uid, body) - - -@router.post( - "/tool-usage", - summary="Increment per-tool usage count (authenticated analytics)", -) -async def track_tool_usage(body: ToolUsageTrackRequest, uid: str = Depends(get_current_uid)) -> dict[str, bool]: - await pref_svc.track_tool_usage(uid, body.toolId) - return {"ok": True} - - -@router.get( - "/nosql-query-history", - response_model=NosqlQueryHistoryOut, - summary="Load NoSQL explorer query history for a collection context", -) -async def get_nosql_query_history( - connection_name: str = Query(..., alias="connectionName"), - db_name: str = Query(..., alias="dbName"), - collection_name: str = Query(..., alias="collectionName"), - uid: str = Depends(get_current_uid), -) -> NosqlQueryHistoryOut: - return await pref_svc.get_nosql_query_history( - uid, - connection_name=connection_name, - db_name=db_name, - collection_name=collection_name, - ) - - -@router.put( - "/nosql-query-history", - response_model=NosqlQueryHistoryOut, - summary="Replace NoSQL explorer query history (max 10 queries)", -) -async def put_nosql_query_history(body: NosqlQueryHistoryPut, uid: str = Depends(get_current_uid)) -> NosqlQueryHistoryOut: - return await pref_svc.put_nosql_query_history(uid, body) diff --git a/apps/backend/app/api/routes/user_preferences/schema.py b/apps/backend/app/api/routes/user_preferences/schema.py deleted file mode 100644 index ffa96b02..00000000 --- a/apps/backend/app/api/routes/user_preferences/schema.py +++ /dev/null @@ -1,110 +0,0 @@ -from typing import Literal, Optional - -from pydantic import BaseModel, ConfigDict, Field, field_validator - -ThemePreference = Literal["light", "dark", "system"] - -# Keep in sync with apps/web/src/store/tool-visibility-store.ts DEFAULT_ENABLED_TOOLS -DEFAULT_ENABLED_TOOLS = [ - "/app/to-do", - "/app/notes", - "/app/bookmarks", - "/app/snippet-manager", - "/app/password-manager", - "/app/environment-manager", - "/app/email-validator", - "/app/jwt-decoder", - "/app/encryption-playground", - "/app/certificate-pem-decoder", - "/app/json-formatter", - "/app/json-schema-generator", - "/app/sql-formatter", - "/app/graphql-formatter", - "/app/diff-checker", - "/app/regex-tester", - "/app/timestamp-converter", - "/app/cron-builder", - "/app/number-base-converter", - "/app/csv-excel-json", - "/app/api-client", - "/app/http-status-codes", - "/app/nosql-explorer", - "/app/url-encode", - "/app/uuid-generator", - "/app/secret-api-key-generator", - "/app/qr-code-generator", - "/app/ip-subnet-calculator", - "/app/hash-generator", - "/app/hmac-generator", - "/app/totp-generator", - "/app/lorem-ipsum", - "/app/color-picker", - "/app/contrast-checker", - "/app/markdown-preview-html", -] - -MAX_NOSQL_HISTORY_QUERIES = 10 - - -class ToolStatOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - usageCount: int = 0 - lastUsed: str = "" - - -class UserPreferencesOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - theme: ThemePreference = "system" - accentColor: str = "blue" - locale: str = "en" - enabledTools: list[str] = Field(default_factory=lambda: list(DEFAULT_ENABLED_TOOLS)) - toolFavorites: list[str] = Field(default_factory=list) - # New keyed-by-workspace pinned-tools map (T24). - pinnedToolsByWorkspace: dict[str, list[str]] = Field(default_factory=dict) - toolStats: dict[str, ToolStatOut] = Field(default_factory=dict) - createdAt: int - updatedAt: int - - -class UserPreferencesUpdate(BaseModel): - model_config = ConfigDict(extra="ignore") - - theme: Optional[ThemePreference] = None - accentColor: Optional[str] = Field(default=None, min_length=1) - locale: Optional[str] = Field(default=None, min_length=1) - enabledTools: Optional[list[str]] = None - toolFavorites: Optional[list[str]] = None - # New keyed-by-workspace pinned-tools map (T24). The frontend sends this - # instead of toolFavorites going forward. If only toolFavorites is present - # (legacy clients / one-release compat), the service layer will migrate it - # into pinnedToolsByWorkspace[activeWorkspaceId]. - pinnedToolsByWorkspace: Optional[dict[str, list[str]]] = None - toolStats: Optional[dict[str, ToolStatOut]] = None - - -class ToolUsageTrackRequest(BaseModel): - toolId: str = Field(min_length=1) - - -class NosqlQueryHistoryOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - connectionName: str = Field(min_length=1) - dbName: str = Field(min_length=1) - collectionName: str = Field(min_length=1) - queries: list[str] = Field(default_factory=list) - updatedAt: int - - -class NosqlQueryHistoryPut(BaseModel): - connectionName: str = Field(min_length=1) - dbName: str = Field(min_length=1) - collectionName: str = Field(min_length=1) - queries: list[str] = Field(default_factory=list) - - @field_validator("queries") - @classmethod - def cap_queries(cls, v: list[str]) -> list[str]: - return v[:MAX_NOSQL_HISTORY_QUERIES] diff --git a/apps/backend/app/api/routes/user_preferences/services.py b/apps/backend/app/api/routes/user_preferences/services.py deleted file mode 100644 index 6f91c6be..00000000 --- a/apps/backend/app/api/routes/user_preferences/services.py +++ /dev/null @@ -1,255 +0,0 @@ -import time -from datetime import datetime, timezone -from typing import Any - -from fastapi import HTTPException, status -from pymongo.errors import PyMongoError - -from app.api.routes.user_preferences.schema import ( - DEFAULT_ENABLED_TOOLS, - MAX_NOSQL_HISTORY_QUERIES, - NosqlQueryHistoryOut, - NosqlQueryHistoryPut, - ToolStatOut, - UserPreferencesOut, - UserPreferencesUpdate, -) -from app.core.cache import cached, bump_version -from app.database import db_manager -from app.utils.collection_name import NOSQL_QUERY_HISTORY, USER_PREFERENCES -from app.utils.utils import create_timestamp - - -def _iso_now() -> str: - return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def _tool_stats_list_to_dict(raw: Any) -> dict[str, ToolStatOut]: - if not isinstance(raw, list): - return {} - out: dict[str, ToolStatOut] = {} - for item in raw: - if not isinstance(item, dict): - continue - tid = item.get("toolId") - if not tid or not isinstance(tid, str): - continue - out[tid] = ToolStatOut( - usageCount=int(item.get("usageCount", 0) or 0), - lastUsed=str(item.get("lastUsed", "") or ""), - ) - return out - - -def _tool_stats_dict_to_list(d: dict[str, ToolStatOut]) -> list[dict[str, Any]]: - return [ - {"toolId": k, "usageCount": v.usageCount, "lastUsed": v.lastUsed} - for k, v in sorted(d.items(), key=lambda kv: kv[0]) - ] - - -def _default_prefs_doc(uid: str, ts: int) -> dict[str, Any]: - return { - "_id": uid, - "created_by": uid, - "createdAt": ts, - "updatedAt": ts, - "theme": "system", - "accentColor": "blue", - "locale": "en", - "enabledTools": list(DEFAULT_ENABLED_TOOLS), - "toolFavorites": [], - "toolStatsList": [], - } - - -def _doc_to_out(doc: dict[str, Any]) -> UserPreferencesOut: - created_at = int(doc.get("createdAt", 0)) or create_timestamp() - updated_at = int(doc.get("updatedAt", 0)) or created_at - raw_enabled = doc.get("enabledTools") - if raw_enabled is None or not isinstance(raw_enabled, list): - enabled = list(DEFAULT_ENABLED_TOOLS) - else: - enabled = [str(x) for x in raw_enabled] - favs = doc.get("toolFavorites") - if not isinstance(favs, list): - favs = [] - stats = _tool_stats_list_to_dict(doc.get("toolStatsList")) - # Deserialise the new keyed pinned-tools map. - raw_by_ws = doc.get("pinnedToolsByWorkspace") - pinned_by_workspace: dict[str, list[str]] = {} - if isinstance(raw_by_ws, dict): - for ws_id, tools in raw_by_ws.items(): - if isinstance(ws_id, str) and isinstance(tools, list): - pinned_by_workspace[ws_id] = [str(t) for t in tools] - return UserPreferencesOut( - theme=doc.get("theme") or "system", - accentColor=doc.get("accentColor") or "blue", - locale=doc.get("locale") or "en", - enabledTools=enabled, - toolFavorites=[str(x) for x in favs], - pinnedToolsByWorkspace=pinned_by_workspace, - toolStats=stats, - createdAt=created_at, - updatedAt=updated_at, - ) - - -@cached(ns="user_preferences", ttl=600, scope="user") -async def get_preferences(*, uid: str) -> UserPreferencesOut: - doc = await db_manager.find_one(USER_PREFERENCES, {"created_by": uid}) - if not doc: - ts = create_timestamp() - return _doc_to_out(_default_prefs_doc(uid, ts)) - return _doc_to_out(doc) - - -async def patch_preferences(uid: str, body: UserPreferencesUpdate) -> UserPreferencesOut: - patch = body.model_dump(exclude_unset=True) - patch.pop("createdAt", None) - patch.pop("updatedAt", None) - - ts = create_timestamp() - set_fields: dict[str, Any] = {"updatedAt": ts} - - if "toolStats" in patch and patch["toolStats"] is not None: - stats_dict = {k: ToolStatOut.model_validate(v) for k, v in patch["toolStats"].items()} - set_fields["toolStatsList"] = _tool_stats_dict_to_list(stats_dict) - del patch["toolStats"] - - for key in ("enabledTools", "toolFavorites"): - if key in patch and patch[key] is not None: - set_fields[key] = patch[key] - del patch[key] - - # Handle the new keyed pinned-tools map (T24). - if "pinnedToolsByWorkspace" in patch and patch["pinnedToolsByWorkspace"] is not None: - set_fields["pinnedToolsByWorkspace"] = patch.pop("pinnedToolsByWorkspace") - else: - patch.pop("pinnedToolsByWorkspace", None) - - set_fields.update({k: v for k, v in patch.items() if v is not None}) - - try: - existing = await db_manager.find_one(USER_PREFERENCES, {"created_by": uid}) - if not existing: - doc = _default_prefs_doc(uid, ts) - for k, v in set_fields.items(): - if k != "updatedAt": - doc[k] = v - doc["updatedAt"] = ts - await db_manager.insert_one(USER_PREFERENCES, doc) - await bump_version(ns="user_preferences", uid=uid) - return _doc_to_out(doc) - - await db_manager.update_one(USER_PREFERENCES, {"created_by": uid}, {"$set": set_fields}) - updated = await db_manager.find_one(USER_PREFERENCES, {"created_by": uid}) - if not updated: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Preferences missing.") - await bump_version(ns="user_preferences", uid=uid) - return _doc_to_out(updated) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update preferences." - ) from exc - - -async def track_tool_usage(uid: str, tool_id: str) -> None: - ts = create_timestamp() - now_iso = _iso_now() - - try: - doc = await db_manager.find_one(USER_PREFERENCES, {"created_by": uid}) - if not doc: - new_doc = _default_prefs_doc(uid, ts) - new_doc["toolStatsList"] = [{"toolId": tool_id, "usageCount": 1, "lastUsed": now_iso}] - await db_manager.insert_one(USER_PREFERENCES, new_doc) - await bump_version(ns="user_preferences", uid=uid) - return - - stats_list = list(doc.get("toolStatsList") or []) - found = False - for i, item in enumerate(stats_list): - if isinstance(item, dict) and item.get("toolId") == tool_id: - stats_list[i] = { - "toolId": tool_id, - "usageCount": int(item.get("usageCount", 0) or 0) + 1, - "lastUsed": now_iso, - } - found = True - break - if not found: - stats_list.append({"toolId": tool_id, "usageCount": 1, "lastUsed": now_iso}) - - await db_manager.update_one( - USER_PREFERENCES, - {"created_by": uid}, - {"$set": {"toolStatsList": stats_list, "updatedAt": ts}}, - ) - await bump_version(ns="user_preferences", uid=uid) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to track tool usage." - ) from exc - - -def _history_filter(uid: str, connection_name: str, db_name: str, collection_name: str) -> dict[str, Any]: - return { - "created_by": uid, - "connectionName": connection_name, - "dbName": db_name, - "collectionName": collection_name, - } - - -async def get_nosql_query_history( - uid: str, *, connection_name: str, db_name: str, collection_name: str -) -> NosqlQueryHistoryOut: - doc = await db_manager.find_one( - NOSQL_QUERY_HISTORY, _history_filter(uid, connection_name, db_name, collection_name) - ) - if not doc: - return NosqlQueryHistoryOut( - connectionName=connection_name, - dbName=db_name, - collectionName=collection_name, - queries=[], - updatedAt=create_timestamp(), - ) - queries = doc.get("queries") or [] - if not isinstance(queries, list): - queries = [] - return NosqlQueryHistoryOut( - connectionName=connection_name, - dbName=db_name, - collectionName=collection_name, - queries=[str(q) for q in queries][:MAX_NOSQL_HISTORY_QUERIES], - updatedAt=int(doc.get("updatedAt", 0)) or create_timestamp(), - ) - - -async def put_nosql_query_history(uid: str, body: NosqlQueryHistoryPut) -> NosqlQueryHistoryOut: - ts = create_timestamp() - queries = body.queries[:MAX_NOSQL_HISTORY_QUERIES] - flt = _history_filter(uid, body.connectionName, body.dbName, body.collectionName) - doc = {**flt, "queries": queries, "updatedAt": ts} - - try: - existing = await db_manager.find_one(NOSQL_QUERY_HISTORY, flt) - if not existing: - doc["createdAt"] = ts - await db_manager.insert_one(NOSQL_QUERY_HISTORY, doc) - else: - await db_manager.update_one( - NOSQL_QUERY_HISTORY, flt, {"$set": {"queries": queries, "updatedAt": ts}} - ) - return await get_nosql_query_history( - uid, - connection_name=body.connectionName, - db_name=body.dbName, - collection_name=body.collectionName, - ) - except PyMongoError as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to save query history." - ) from exc diff --git a/apps/backend/app/api/routes/users/api.py b/apps/backend/app/api/routes/users/api.py deleted file mode 100644 index 49e270af..00000000 --- a/apps/backend/app/api/routes/users/api.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import APIRouter, HTTPException, Request, status - -from app.api.routes.auth.users_repo import get_user_doc_by_username -from app.api.routes.users.schema import PublicUserProfileResponse -from app.core.limiter import limiter - -router = APIRouter(prefix="/users", tags=["public", "users"]) - - -@router.get( - "/{username}", - response_model=PublicUserProfileResponse, - summary="Get public profile information by username", -) -@limiter.limit("30/minute") -async def get_public_user_profile(request: Request, username: str) -> PublicUserProfileResponse: - if not username: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Username parameter is required." - ) - - doc = await get_user_doc_by_username(username.lower()) - if not doc: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="User not found." - ) - - return PublicUserProfileResponse( - username=doc.get("username"), - display_name=doc.get("display_name"), - photo_url=doc.get("photo_url"), - github_username=doc.get("github_username"), - bio=doc.get("bio"), - social_links=doc.get("social_links"), - tech_stacks=doc.get("tech_stacks") or [], - ) diff --git a/apps/backend/app/api/routes/users/schema.py b/apps/backend/app/api/routes/users/schema.py deleted file mode 100644 index 18240f9d..00000000 --- a/apps/backend/app/api/routes/users/schema.py +++ /dev/null @@ -1,11 +0,0 @@ -from pydantic import BaseModel, Field -from app.api.routes.auth.schema import SocialLinks - -class PublicUserProfileResponse(BaseModel): - username: str - display_name: str | None = None - photo_url: str | None = None - github_username: str | None = None - bio: str | None = None - social_links: SocialLinks | None = None - tech_stacks: list[str] = Field(default_factory=list) diff --git a/apps/backend/app/api/routes/webhook_tester/__init__.py b/apps/backend/app/api/routes/webhook_tester/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/webhook_tester/api.py b/apps/backend/app/api/routes/webhook_tester/api.py deleted file mode 100644 index 06f2cdcd..00000000 --- a/apps/backend/app/api/routes/webhook_tester/api.py +++ /dev/null @@ -1,76 +0,0 @@ -# ponytail: public capability-URL tool — no auth on any endpoint (same as -# dns_lookup). Bin ids are unguessable; the receive endpoint MUST stay open -# because external services (Stripe, GitHub, ...) call it directly. -from fastapi import APIRouter, HTTPException, Query, Request - -from app.api.routes.webhook_tester import services as svc -from app.api.routes.webhook_tester.schema import BinOut, RequestListOut - -router = APIRouter(prefix="/webhook-tester", tags=["webhook-tester"]) - -_ALL_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] - - -@router.post("/bins", response_model=BinOut, summary="Create a webhook bin (expires after 24h)") -async def create_bin() -> BinOut: - return BinOut(**await svc.create_bin()) - - -@router.api_route("/in/{bin_id}", methods=_ALL_METHODS, summary="Receive a webhook (public, any method)") -@router.api_route( - "/in/{bin_id}/{path:path}", - methods=_ALL_METHODS, - summary="Receive a webhook with a path suffix (public, any method)", -) -async def receive(bin_id: str, request: Request, path: str = "") -> dict: - body = await request.body() - - query: dict[str, str] = {} - for key, value in request.query_params.multi_items(): - query[key] = f"{query[key]},{value}" if key in query else value - - forwarded = request.headers.get("x-forwarded-for") - if forwarded: - source_ip: str | None = forwarded.split(",")[0].strip() - else: - source_ip = request.client.host if request.client else None - - record = svc.make_record( - method=request.method, - path=path, - query=query, - headers=dict(request.headers), - body=body, - content_type=request.headers.get("content-type"), - source_ip=source_ip, - ) - if not await svc.add_request(bin_id, record): - raise HTTPException(status_code=404, detail="Bin not found or expired") - return {"ok": True} - - -@router.get( - "/bins/{bin_id}/requests", - response_model=RequestListOut, - summary="List captured requests (newest first)", -) -async def list_requests( - bin_id: str, - after: str | None = Query(default=None, description="Cursor: request id or ISO timestamp"), -) -> RequestListOut: - result = await svc.list_requests(bin_id, after) - if result is None: - raise HTTPException(status_code=404, detail="Bin not found or expired") - meta, items = result - return RequestListOut( - bin_id=bin_id, - expires_at=meta["expires_at"], - count=len(items), - requests=items, - ) - - -@router.delete("/bins/{bin_id}/requests", status_code=204, summary="Clear captured requests") -async def clear_requests(bin_id: str) -> None: - if not await svc.clear_requests(bin_id): - raise HTTPException(status_code=404, detail="Bin not found or expired") diff --git a/apps/backend/app/api/routes/webhook_tester/schema.py b/apps/backend/app/api/routes/webhook_tester/schema.py deleted file mode 100644 index 908c21c7..00000000 --- a/apps/backend/app/api/routes/webhook_tester/schema.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import datetime - -from pydantic import BaseModel - - -class BinOut(BaseModel): - bin_id: str - created_at: datetime - expires_at: datetime - - -class RecordedRequest(BaseModel): - id: str - method: str - path: str = "" - query: dict[str, str] = {} - headers: dict[str, str] = {} - body: str = "" - body_encoding: str = "text" # "text" | "base64" (binary payloads) - body_truncated: bool = False - content_type: str | None = None - size: int = 0 # original body size in bytes (before truncation) - source_ip: str | None = None - received_at: datetime - - -class RequestListOut(BaseModel): - bin_id: str - expires_at: datetime - count: int - requests: list[RecordedRequest] diff --git a/apps/backend/app/api/routes/webhook_tester/services.py b/apps/backend/app/api/routes/webhook_tester/services.py deleted file mode 100644 index 9fddb4f3..00000000 --- a/apps/backend/app/api/routes/webhook_tester/services.py +++ /dev/null @@ -1,222 +0,0 @@ -"""Webhook Tester — short-lived request bins. - -Storage: Redis with a 24h TTL when configured (matches the dominant -short-lived-data pattern, see app/core/redis_client.py). When Redis is -unavailable (dev/tests without REDIS_URL) an in-process fallback store with -the same TTL semantics is used. -""" -from __future__ import annotations - -import base64 -import json -import logging -import secrets -import uuid -from datetime import datetime, timedelta, timezone -from typing import Any - -from app.core.redis_client import get_redis - -log = logging.getLogger("app.webhook_tester") - -BIN_TTL_SECONDS = 24 * 60 * 60 -MAX_REQUESTS_PER_BIN = 100 -MAX_BODY_BYTES = 64 * 1024 - -# bin_id -> {"bin_id", "created_at", "expires_at", "requests": [newest, ...]} -_memory_bins: dict[str, dict[str, Any]] = {} - - -def _now() -> datetime: - return datetime.now(timezone.utc) - - -def _bin_key(bin_id: str) -> str: - return f"webhook_tester:bin:{bin_id}" - - -def _req_key(bin_id: str) -> str: - return f"webhook_tester:req:{bin_id}" - - -def new_bin_id() -> str: - """Random URL-safe id (~11 chars) — unguessable capability id.""" - return secrets.token_urlsafe(8) - - -def _purge_memory() -> None: - now_iso = _now().isoformat() - expired = [bid for bid, entry in _memory_bins.items() if entry["expires_at"] <= now_iso] - for bid in expired: - _memory_bins.pop(bid, None) - - -def make_record( - *, - method: str, - path: str, - query: dict[str, str], - headers: dict[str, str], - body: bytes, - content_type: str | None, - source_ip: str | None, -) -> dict[str, Any]: - """Build a stored request record; truncates large bodies, base64s binary ones.""" - size = len(body) - truncated = False - if size > MAX_BODY_BYTES: - body = body[:MAX_BODY_BYTES] - truncated = True - try: - text = body.decode("utf-8") - encoding = "text" - except UnicodeDecodeError: - text = base64.b64encode(body).decode("ascii") - encoding = "base64" - return { - "id": uuid.uuid4().hex[:12], - "method": method.upper(), - "path": path, - "query": query, - "headers": headers, - "body": text, - "body_encoding": encoding, - "body_truncated": truncated, - "content_type": content_type, - "size": size, - "source_ip": source_ip, - "received_at": _now().isoformat(), - } - - -async def create_bin() -> dict[str, Any]: - created = _now() - meta = { - "bin_id": new_bin_id(), - "created_at": created.isoformat(), - "expires_at": (created + timedelta(seconds=BIN_TTL_SECONDS)).isoformat(), - } - r = get_redis() - if r is not None: - try: - await r.set(_bin_key(meta["bin_id"]), json.dumps(meta), ex=BIN_TTL_SECONDS) - return meta - except Exception as exc: # noqa: BLE001 — fail over to memory store - log.warning("webhook_tester.redis.create failed err=%s", exc) - _purge_memory() - _memory_bins[meta["bin_id"]] = {**meta, "requests": []} - return meta - - -async def get_bin(bin_id: str) -> dict[str, Any] | None: - """Return bin metadata or None if unknown/expired.""" - r = get_redis() - if r is not None: - try: - raw = await r.get(_bin_key(bin_id)) - if raw is not None: - return json.loads(raw) - except Exception as exc: # noqa: BLE001 - log.warning("webhook_tester.redis.get_bin failed err=%s", exc) - _purge_memory() - entry = _memory_bins.get(bin_id) - if entry is None: - return None - return {k: entry[k] for k in ("bin_id", "created_at", "expires_at")} - - -async def add_request(bin_id: str, record: dict[str, Any]) -> bool: - """Store a request (newest first, capped). False if the bin is unknown/expired.""" - r = get_redis() - if r is not None: - try: - ttl = await r.ttl(_bin_key(bin_id)) - if ttl is None or ttl <= 0: - return False - pipe = r.pipeline() - pipe.lpush(_req_key(bin_id), json.dumps(record)) - pipe.ltrim(_req_key(bin_id), 0, MAX_REQUESTS_PER_BIN - 1) - pipe.expire(_req_key(bin_id), ttl) - await pipe.execute() - return True - except Exception as exc: # noqa: BLE001 - log.warning("webhook_tester.redis.add failed err=%s", exc) - _purge_memory() - entry = _memory_bins.get(bin_id) - if entry is None: - return False - entry["requests"].insert(0, record) - del entry["requests"][MAX_REQUESTS_PER_BIN:] - return True - - -def _parse_ts(value: str) -> datetime | None: - try: - dt = datetime.fromisoformat(value.replace("Z", "+00:00")) - except (ValueError, AttributeError): - return None - if dt.tzinfo is None: - dt = dt.replace(tzinfo=timezone.utc) - return dt - - -def filter_after(items: list[dict[str, Any]], after: str) -> list[dict[str, Any]]: - """Return only entries newer than the cursor (request id or ISO timestamp). - - `items` is newest-first. Unknown cursors return everything (safe for polling). - """ - newer: list[dict[str, Any]] = [] - for item in items: - if item.get("id") == after: - return newer - newer.append(item) - ts = _parse_ts(after) - if ts is None: - return items - result = [] - for item in items: - item_ts = _parse_ts(str(item.get("received_at", ""))) - if item_ts is not None and item_ts > ts: - result.append(item) - return result - - -async def list_requests( - bin_id: str, after: str | None = None -) -> tuple[dict[str, Any], list[dict[str, Any]]] | None: - """Return (bin meta, requests newest-first) or None if the bin is unknown/expired.""" - meta = await get_bin(bin_id) - if meta is None: - return None - items: list[dict[str, Any]] | None = None - r = get_redis() - if r is not None: - try: - raw = await r.lrange(_req_key(bin_id), 0, MAX_REQUESTS_PER_BIN - 1) - items = [json.loads(x) for x in raw] - except Exception as exc: # noqa: BLE001 - log.warning("webhook_tester.redis.list failed err=%s", exc) - if items is None: - entry = _memory_bins.get(bin_id) - items = list(entry["requests"]) if entry else [] - if after: - items = filter_after(items, after) - return meta, items - - -async def clear_requests(bin_id: str) -> bool: - """Delete all captured requests. False if the bin is unknown/expired.""" - meta = await get_bin(bin_id) - if meta is None: - return False - r = get_redis() - if r is not None: - try: - await r.delete(_req_key(bin_id)) - return True - except Exception as exc: # noqa: BLE001 - log.warning("webhook_tester.redis.clear failed err=%s", exc) - entry = _memory_bins.get(bin_id) - if entry is not None: - entry["requests"] = [] - return True diff --git a/apps/backend/app/api/routes/whois/__init__.py b/apps/backend/app/api/routes/whois/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/whois/api.py b/apps/backend/app/api/routes/whois/api.py deleted file mode 100644 index b7f2ffaf..00000000 --- a/apps/backend/app/api/routes/whois/api.py +++ /dev/null @@ -1,14 +0,0 @@ -# ponytail: stateless tool — no per-user persistence, no workspace scoping needed -from fastapi import APIRouter, Query - -from app.api.routes.whois import services as svc -from app.api.routes.whois.schema import WhoisResult - -router = APIRouter(prefix="/whois", tags=["whois"]) - - -@router.get("/lookup", response_model=WhoisResult, summary="RDAP/WHOIS lookup for a domain or IP address") -async def lookup( - target: str = Query(..., min_length=1, max_length=300, description="Domain name or IP address to look up"), -) -> WhoisResult: - return await svc.lookup_whois(target) diff --git a/apps/backend/app/api/routes/whois/schema.py b/apps/backend/app/api/routes/whois/schema.py deleted file mode 100644 index 7baac512..00000000 --- a/apps/backend/app/api/routes/whois/schema.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Any, Literal - -from pydantic import BaseModel - - -class WhoisContact(BaseModel): - roles: list[str] = [] - name: str | None = None - organization: str | None = None - handle: str | None = None - - -class WhoisResult(BaseModel): - target: str - type: Literal["domain", "ip"] - registrar: str | None = None - status: list[str] = [] - created: str | None = None - updated: str | None = None - expires: str | None = None - nameservers: list[str] = [] - contacts: list[WhoisContact] = [] - raw: dict[str, Any] = {} diff --git a/apps/backend/app/api/routes/whois/services.py b/apps/backend/app/api/routes/whois/services.py deleted file mode 100644 index e4a7f26c..00000000 --- a/apps/backend/app/api/routes/whois/services.py +++ /dev/null @@ -1,193 +0,0 @@ -from __future__ import annotations - -import ipaddress -import re -from typing import Any - -import httpx -from fastapi import HTTPException - -from app.api.routes.whois.schema import WhoisContact, WhoisResult -from app.core.cache import cached -from app.core.cache.keys import register_namespace - -# Global cache: RDAP data is public and slow-moving. Registered here (not in -# keys.py) so the whois module stays self-contained. -register_namespace("whois", scope="global", default_ttl=3600) - -RDAP_BASE_URL = "https://rdap.org" -_HTTP_TIMEOUT_S = 15.0 - -_DOMAIN_RE = re.compile(r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$") - -# RDAP eventAction -> normalized field -_EVENT_FIELD = { - "registration": "created", - "last changed": "updated", - "last update": "updated", - "expiration": "expires", -} - - -def sanitize_target(raw: str) -> tuple[str, str]: - """Sanitize and classify a lookup target. - - Strips scheme/path/port noise, lowercases, and validates. Returns - ``(target, kind)`` where kind is ``"domain"`` or ``"ip"``. - Raises ``HTTPException(400)`` for anything that is neither. - """ - target = raw.strip() - target = re.sub(r"^[a-zA-Z][a-zA-Z0-9+.\-]*://", "", target) # scheme - target = target.split("/", 1)[0].split("?", 1)[0].split("#", 1)[0] # path/query - target = target.strip().strip(".").lower() - if target.startswith("[") and target.endswith("]"): # bracketed IPv6 - target = target[1:-1] - - if not target or len(target) > 253: - raise HTTPException(status_code=400, detail="Invalid domain or IP address") - - try: - ipaddress.ip_address(target) - return target, "ip" - except ValueError: - pass - - # Not an IP — strip a :port suffix if present, then validate as a domain. - host = target.rsplit(":", 1)[0] if re.search(r":\d+$", target) else target - if _is_valid_domain(host): - return host, "domain" - - raise HTTPException(status_code=400, detail="Invalid domain or IP address") - - -def _is_valid_domain(domain: str) -> bool: - return bool(_DOMAIN_RE.match(domain)) and len(domain) <= 253 - - -@cached(ns="whois", ttl=3600, scope="global") -async def fetch_rdap(*, kind: str, target: str) -> dict[str, Any] | None: - """Fetch the raw RDAP document for a domain or IP. - - Pure function of (kind, target) — cached globally for 1h. - Returns ``None`` when the registry has no record (RDAP 404). - Raises ``HTTPException`` for upstream failures (not cached). - """ - path = "domain" if kind == "domain" else "ip" - url = f"{RDAP_BASE_URL}/{path}/{target}" - try: - async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT_S, follow_redirects=True) as client: - resp = await client.get(url, headers={"accept": "application/rdap+json, application/json"}) - except httpx.HTTPError: - raise HTTPException(status_code=502, detail="RDAP lookup failed: upstream service unreachable") - - if resp.status_code == 404: - return None - if resp.status_code == 429: - raise HTTPException(status_code=503, detail="RDAP service is rate limiting requests; try again shortly") - if resp.status_code >= 400: - raise HTTPException(status_code=502, detail=f"RDAP upstream error (HTTP {resp.status_code})") - - try: - data = resp.json() - except ValueError: - raise HTTPException(status_code=502, detail="RDAP upstream returned an invalid response") - if not isinstance(data, dict): - raise HTTPException(status_code=502, detail="RDAP upstream returned an invalid response") - return data - - -def _vcard_value(entity: dict[str, Any], key: str) -> str | None: - """Extract a text value (fn, org, …) from an RDAP entity's vcardArray.""" - vcard = entity.get("vcardArray") - if not isinstance(vcard, list) or len(vcard) < 2 or not isinstance(vcard[1], list): - return None - for item in vcard[1]: - if isinstance(item, list) and len(item) >= 4 and item[0] == key: - value = item[3] - if isinstance(value, list): - value = " ".join(str(v) for v in value if v) - text = str(value).strip() - if text: - return text - return None - - -def _collect_contacts(entities: Any, out: list[WhoisContact], depth: int = 0) -> None: - if depth > 3 or not isinstance(entities, list): - return - for entity in entities: - if not isinstance(entity, dict): - continue - roles = [str(r) for r in entity.get("roles", []) if r] - name = _vcard_value(entity, "fn") - org = _vcard_value(entity, "org") - handle = entity.get("handle") - handle = str(handle) if handle else None - # Respect redaction: keep whatever the registry exposes; skip entries - # that carry no usable information at all. - if roles or name or org or handle: - out.append(WhoisContact(roles=roles, name=name, organization=org, handle=handle)) - _collect_contacts(entity.get("entities"), out, depth + 1) - - -def _find_registrar(data: dict[str, Any]) -> str | None: - for entity in data.get("entities") or []: - if not isinstance(entity, dict): - continue - if "registrar" in (entity.get("roles") or []): - return _vcard_value(entity, "fn") or _vcard_value(entity, "org") or entity.get("handle") - return None - - -def _extract_events(data: dict[str, Any]) -> dict[str, str | None]: - out: dict[str, str | None] = {"created": None, "updated": None, "expires": None} - for event in data.get("events") or []: - if not isinstance(event, dict): - continue - action = str(event.get("eventAction", "")).lower() - field = _EVENT_FIELD.get(action) - if field and event.get("eventDate") and out[field] is None: - out[field] = str(event["eventDate"]) - return out - - -def normalize_rdap(target: str, kind: str, data: dict[str, Any]) -> WhoisResult: - events = _extract_events(data) - - nameservers: list[str] = [] - for ns in data.get("nameservers") or []: - if isinstance(ns, dict) and ns.get("ldhName"): - name = str(ns["ldhName"]).rstrip(".").lower() - if name and name not in nameservers: - nameservers.append(name) - - contacts: list[WhoisContact] = [] - _collect_contacts(data.get("entities"), contacts) - - registrar = _find_registrar(data) - if registrar is None and kind == "ip": - # For IP networks, surface the network name/handle as the "registrar" line. - registrar = data.get("name") or data.get("handle") - registrar = str(registrar) if registrar else None - - return WhoisResult( - target=target, - type=kind, # type: ignore[arg-type] - registrar=registrar, - status=[str(s) for s in data.get("status") or []], - created=events["created"], - updated=events["updated"], - expires=events["expires"], - nameservers=nameservers, - contacts=contacts, - raw=data, - ) - - -async def lookup_whois(raw_target: str) -> WhoisResult: - target, kind = sanitize_target(raw_target) - data = await fetch_rdap(kind=kind, target=target) - if data is None: - label = "domain" if kind == "domain" else "IP address" - raise HTTPException(status_code=404, detail=f"No RDAP/WHOIS record found for this {label}") - return normalize_rdap(target, kind, data) diff --git a/apps/backend/app/api/routes/workspaces/__init__.py b/apps/backend/app/api/routes/workspaces/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/workspaces/api.py b/apps/backend/app/api/routes/workspaces/api.py deleted file mode 100644 index 0c58a8c7..00000000 --- a/apps/backend/app/api/routes/workspaces/api.py +++ /dev/null @@ -1,105 +0,0 @@ -from typing import Annotated -from fastapi import APIRouter, Depends, HTTPException, Response, status -from app.api.routes.auth.services import get_current_uid -from app.api.routes.workspaces.middleware import ACTIVE_WS_COOKIE -from app.api.routes.workspaces.repo import ( - find_user_workspaces, find_workspace, find_ws_membership, -) -from app.api.routes.workspaces.schema import ( - EncryptionBlob, KeypairOut, KeypairPostRequest, SetActiveWorkspaceRequest, - SetActiveWorkspaceResponse, WorkspaceOut, -) -from app.api.routes.workspaces import crypto_repo -from app.core.config import get_settings - -router = APIRouter(prefix="/workspaces-api", tags=["workspaces"]) - - -def _ws_to_out(ws: dict) -> WorkspaceOut: - return WorkspaceOut( - id=ws["_id"], - name=ws["name"], - slug=ws["slug"], - is_personal=bool(ws.get("is_personal")), - kind=ws.get("kind", "personal"), - ws_role=ws["ws_role"], - settings=ws.get("settings") or {}, - ) - - -@router.get("/workspaces", response_model=list[WorkspaceOut]) -async def list_workspaces( - uid: Annotated[str, Depends(get_current_uid)], -) -> list[WorkspaceOut]: - workspaces = await find_user_workspaces(uid) - return [_ws_to_out(w) for w in workspaces] - - -@router.get("/workspaces/{workspace_id}", response_model=WorkspaceOut) -async def get_workspace( - workspace_id: str, - uid: Annotated[str, Depends(get_current_uid)], -) -> WorkspaceOut: - ws = await find_workspace(workspace_id) - mem = await find_ws_membership(workspace_id, uid) - if not ws or not mem: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - return _ws_to_out({**ws, "ws_role": mem["ws_role"]}) - - -@router.post("/workspaces/active", response_model=SetActiveWorkspaceResponse) -async def set_active_workspace( - body: SetActiveWorkspaceRequest, - response: Response, - uid: Annotated[str, Depends(get_current_uid)], -) -> SetActiveWorkspaceResponse: - mem = await find_ws_membership(body.workspace_id, uid) - if not mem: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Not a member of this workspace.", - ) - settings = get_settings() - response.set_cookie( - key=ACTIVE_WS_COOKIE, - value=body.workspace_id, - httponly=True, - samesite="lax", - secure=settings.APP_ENV == "production", - path="/", - ) - return SetActiveWorkspaceResponse(workspace_id=body.workspace_id) - - -@router.get("/users/me/keypair", response_model=KeypairOut | None) -async def get_my_keypair( - uid: Annotated[str, Depends(get_current_uid)], -) -> KeypairOut | None: - enc = await crypto_repo.get_user_encryption(uid) - if not enc: - return None - return KeypairOut( - publicKey=enc["publicKey"], - privateKeyEncrypted=EncryptionBlob( - encrypted=enc["privateKeyEncrypted"]["encrypted"], - iv=enc["privateKeyEncrypted"]["iv"], - ), - salt=enc["salt"], - createdAt=int(enc.get("createdAt", 0)), - ) - - -@router.post("/users/me/keypair", status_code=204) -async def set_my_keypair( - body: KeypairPostRequest, - uid: Annotated[str, Depends(get_current_uid)], -) -> None: - await crypto_repo.set_user_encryption( - uid, - public_key=body.publicKey, - private_key_encrypted={ - "encrypted": body.privateKeyEncrypted.encrypted, - "iv": body.privateKeyEncrypted.iv, - }, - salt=body.salt, - ) diff --git a/apps/backend/app/api/routes/workspaces/backfill.py b/apps/backend/app/api/routes/workspaces/backfill.py deleted file mode 100644 index 8aaea488..00000000 --- a/apps/backend/app/api/routes/workspaces/backfill.py +++ /dev/null @@ -1,120 +0,0 @@ -from dataclasses import dataclass -from typing import Any - -from fastapi import BackgroundTasks - -from app.api.routes.auth import users_repo -from app.database import db_manager -from app.utils.collection_name import ( - API_CLIENT_COLLECTIONS, - API_CLIENT_ENVIRONMENTS, - API_CLIENT_HISTORY, - API_CLIENT_WORKSPACES, - API_KEY_VAULT_ENTRIES, - BOOKMARKS, - BOOKMARK_FOLDERS, - CODE_SNIPPETS, - ENV_MANAGER_ENTRIES, - JSON_FORMATTER_DOCUMENTS, - NOSQL_CONNECTIONS, - NOSQL_QUERY_HISTORY, - NOTES, - PASSWORD_ENTRIES, - PASSWORD_VAULTS, - PROJECTS, - REDIS_CONNECTIONS, - S3_CONNECTIONS, - SQL_CONNECTIONS, - TASKS, - URL_LINKS, - USER_PREFERENCES, -) - - -@dataclass(frozen=True) -class BackfillSpec: - collection: str - user_field: str - stamp_owner_uid: bool - - -BACKFILL_COLLECTIONS: list[BackfillSpec] = [ - BackfillSpec(PASSWORD_VAULTS, "created_by", True), - BackfillSpec(PASSWORD_ENTRIES, "created_by", True), - BackfillSpec(ENV_MANAGER_ENTRIES, "created_by", True), - BackfillSpec(API_KEY_VAULT_ENTRIES, "created_by", True), - BackfillSpec(NOTES, "created_by", True), - BackfillSpec(TASKS, "created_by", True), - BackfillSpec(PROJECTS, "created_by", True), - BackfillSpec(BOOKMARKS, "created_by", True), - BackfillSpec(BOOKMARK_FOLDERS, "created_by", True), - BackfillSpec(CODE_SNIPPETS, "created_by", True), - BackfillSpec(NOSQL_CONNECTIONS, "created_by", True), - BackfillSpec(NOSQL_QUERY_HISTORY, "created_by", True), - BackfillSpec(API_CLIENT_COLLECTIONS, "created_by", True), - BackfillSpec(API_CLIENT_ENVIRONMENTS, "created_by", True), - BackfillSpec(API_CLIENT_HISTORY, "created_by", True), - BackfillSpec(API_CLIENT_WORKSPACES, "created_by", True), - BackfillSpec(SQL_CONNECTIONS, "created_by", True), - BackfillSpec(S3_CONNECTIONS, "created_by", True), - BackfillSpec(REDIS_CONNECTIONS, "created_by", True), - BackfillSpec(URL_LINKS, "created_by", True), - BackfillSpec(JSON_FORMATTER_DOCUMENTS, "created_by", True), -] - - -async def _stamp_collection(spec: BackfillSpec, uid: str, ws_id: str) -> int: - update: dict[str, Any] = {"workspace_id": ws_id} - if spec.stamp_owner_uid: - update["owner_uid"] = uid - res = await db_manager.update_many( - spec.collection, - {spec.user_field: uid, "workspace_id": {"$exists": False}}, - {"$set": update}, - ) - return getattr(res, "modified_count", 0) - - -async def _rewrite_pinned_tools(uid: str, ws_id: str) -> None: - pref = await db_manager.find_one(USER_PREFERENCES, {"_id": uid}) - if not pref: - return - legacy = pref.get("toolFavorites") - if legacy is None: - return - keyed = pref.get("pinnedToolsByWorkspace") or {} - keyed[ws_id] = list(legacy) - await db_manager.update_one( - USER_PREFERENCES, - {"_id": uid}, - { - "$set": {"pinnedToolsByWorkspace": keyed}, - "$unset": {"toolFavorites": ""}, - }, - ) - - -async def run_user_backfill(uid: str, personal_workspace_id: str) -> None: - # Skip if already migrated - migrated_at = await users_repo.get_migrated_at(uid) - if migrated_at: - return - - progress = await users_repo.get_migration_progress(uid) or {} - for spec in BACKFILL_COLLECTIONS: - if progress.get(spec.collection) == "done": - continue - await _stamp_collection(spec, uid, personal_workspace_id) - progress[spec.collection] = "done" - await users_repo.set_migration_progress(uid, progress) - - await _rewrite_pinned_tools(uid, personal_workspace_id) - await users_repo.mark_migrated(uid) - - -def schedule_backfill( - background_tasks: BackgroundTasks, - uid: str, - personal_workspace_id: str, -) -> None: - background_tasks.add_task(run_user_backfill, uid, personal_workspace_id) diff --git a/apps/backend/app/api/routes/workspaces/crypto_repo.py b/apps/backend/app/api/routes/workspaces/crypto_repo.py deleted file mode 100644 index 5cf241f2..00000000 --- a/apps/backend/app/api/routes/workspaces/crypto_repo.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Any -from app.database import db_manager -from app.utils.collection_name import USERS -from app.utils.utils import create_timestamp - - -async def set_user_encryption( - uid: str, - *, - public_key: str, - private_key_encrypted: dict[str, str], - salt: str, -) -> None: - await db_manager.update_one( - USERS, - {"_id": uid}, - {"$set": { - "encryption": { - "publicKey": public_key, - "privateKeyEncrypted": private_key_encrypted, - "salt": salt, - "createdAt": create_timestamp(), - }, - }}, - upsert=True, - ) - - -async def get_user_encryption(uid: str) -> dict[str, Any] | None: - doc = await db_manager.find_one(USERS, {"_id": uid}) - if not doc: - return None - return doc.get("encryption") diff --git a/apps/backend/app/api/routes/workspaces/middleware.py b/apps/backend/app/api/routes/workspaces/middleware.py deleted file mode 100644 index 55509097..00000000 --- a/apps/backend/app/api/routes/workspaces/middleware.py +++ /dev/null @@ -1,133 +0,0 @@ -from __future__ import annotations - -from typing import Annotated, Any - -from fastapi import Depends, HTTPException, Request -from pydantic import BaseModel - -from app.api.routes.auth.services import get_current_uid -from app.api.routes.workspaces.repo import ( - find_user_workspaces, - find_workspace, - find_ws_membership, -) -from app.api.routes.workspaces.schema import WsRole - -ACTIVE_WS_COOKIE = "active_workspace" - - -class WorkspaceContext(BaseModel): - uid: str - workspace_id: str - ws_role: WsRole - is_personal: bool - owner_uid: str | None = None - # True once `workspaces.settings.encryption` is non-null. Flips the - # encrypted-tool RBAC row from all-empty to plaintext-row. - has_encryption: bool = False - - -async def default_personal_ws_id(uid: str) -> str | None: - workspaces = await find_user_workspaces(uid) - for w in workspaces: - if w.get("is_personal"): - return w["_id"] - return None - - -async def get_workspace_ctx( - request: Request, - uid: Annotated[str, Depends(get_current_uid)], -) -> WorkspaceContext: - cookie_ws_id = request.cookies.get(ACTIVE_WS_COOKIE) - ws_id = cookie_ws_id or await default_personal_ws_id(uid) - if not ws_id: - raise HTTPException(403, "No accessible workspace.") - - ws = await find_workspace(ws_id) - mem = await find_ws_membership(ws_id, uid) if ws else None - - # A stale/inaccessible `active_workspace` cookie (e.g. a workspace the user - # only reached via the old org-admin cascade, or one since removed) must not - # 403 every scoped request — that reads as "all my data vanished". Fall back - # to the user's personal workspace, which the frontend also resolves to. - if (not ws or not mem) and cookie_ws_id: - fallback_id = await default_personal_ws_id(uid) - if fallback_id and fallback_id != ws_id: - ws_id = fallback_id - ws = await find_workspace(ws_id) - mem = await find_ws_membership(ws_id, uid) if ws else None - - if not ws: - raise HTTPException(403, "Workspace not found.") - if not mem: - raise HTTPException(403, "Not a member of this workspace.") - ws_role = mem["ws_role"] - - enc_settings = (ws.get("settings") or {}).get("encryption") - has_encryption = enc_settings is not None - return WorkspaceContext( - uid=uid, - workspace_id=ws_id, - ws_role=ws_role, - is_personal=bool(ws.get("is_personal")), - owner_uid=ws.get("owner_uid"), - has_encryption=has_encryption, - ) - - -def assert_writer(ctx: WorkspaceContext) -> None: - """Raise 403 if the active workspace role can't mutate. Viewers are read-only. - Call at the top of every create/update/delete service path.""" - if ctx.ws_role == "viewer": - raise HTTPException(403, "Read-only role in this workspace.") - - -async def get_workspace_write_ctx( - ctx: Annotated[WorkspaceContext, Depends(get_workspace_ctx)], -) -> WorkspaceContext: - """FastAPI Depends for routes that mutate workspace data.""" - assert_writer(ctx) - return ctx - - -def apply_workspace_filter( - ctx: WorkspaceContext, base_filter: dict[str, Any] -) -> dict[str, Any]: - flt = {**base_filter, "workspace_id": ctx.workspace_id} - if ctx.is_personal: - assert ctx.owner_uid is not None, ( - f"Personal workspace {ctx.workspace_id} has no owner_uid — " - "data invariant violated" - ) - flt["owner_uid"] = ctx.uid - return flt - - -def apply_legacy_or_filter( - ctx: WorkspaceContext, - base_filter: dict[str, Any], - *, - user_field: str, -) -> dict[str, Any]: - """Read-path tolerance during pending backfill. - - ponytail: transitional OR-branch. Remove in a follow-up PR after all - live users have user.migrated_at set. The OR is bounded by `user_field` - so it cannot leak across users. - """ - stamped: dict[str, Any] = { - "workspace_id": ctx.workspace_id, - } - if ctx.is_personal: - stamped["owner_uid"] = ctx.uid - legacy: dict[str, Any] = { - "workspace_id": {"$exists": False}, - user_field: ctx.uid, - } - workspace_or = {"$or": [stamped, legacy]} - # If the base filter already carries its own $or (e.g. an "uncategorized" - # field test), combine via $and so neither side is silently overwritten. - if "$or" in base_filter: - return {"$and": [base_filter, workspace_or]} - return {**base_filter, "$or": [stamped, legacy]} diff --git a/apps/backend/app/api/routes/workspaces/rbac.py b/apps/backend/app/api/routes/workspaces/rbac.py deleted file mode 100644 index bbf78c50..00000000 --- a/apps/backend/app/api/routes/workspaces/rbac.py +++ /dev/null @@ -1,84 +0,0 @@ -from typing import Annotated, Literal -from fastapi import Depends, HTTPException, status -from app.api.routes.workspaces.middleware import WorkspaceContext, get_workspace_ctx -from app.api.routes.workspaces.schema import WsRole - -Permission = Literal["read", "write", "delete", "admin"] - -ENCRYPTED_TOOLS: set[str] = { - "password-manager", - "environment-manager", - "api-key-vault", -} - - -def _full() -> set[Permission]: - return {"read", "write", "delete", "admin"} - - -def _editor() -> set[Permission]: - return {"read", "write", "delete"} - - -def _reader() -> set[Permission]: - return {"read"} - - -def _none() -> set[Permission]: - return set() - - -def _encrypted_row() -> dict[WsRole, set[Permission]]: - # Encrypted tools are gated in B for shared workspaces — no role has access. - # ponytail: in C this row flips per workspace once a DEK exists. - return {"admin": _none(), "developer": _none(), "viewer": _none()} - - -def _plaintext_row() -> dict[WsRole, set[Permission]]: - return {"admin": _full(), "developer": _editor(), "viewer": _reader()} - - -# Hardcoded canonical RBAC matrix. -TOOL_PERMISSIONS: dict[str, dict[WsRole, set[Permission]]] = { - # Encrypted tools — Personal-only in B. - "password-manager": _encrypted_row(), - "environment-manager": _encrypted_row(), - "api-key-vault": _encrypted_row(), - - # Plaintext tools. - "notes": _plaintext_row(), - "bookmarks": _plaintext_row(), - "tasks": _plaintext_row(), - "code-snippets": _plaintext_row(), - "api-client": _plaintext_row(), - "nosql-explorer": _plaintext_row(), - "sql-client": _plaintext_row(), - "redis-commander": _plaintext_row(), - "s3-drive": _plaintext_row(), - "json-formatter": _plaintext_row(), - "url-shortener": _plaintext_row(), - "dns-lookup": _plaintext_row(), -} - - -def has_permission(ctx: WorkspaceContext, tool: str, permission: Permission) -> bool: - if ctx.is_personal: - return True - # Encrypted tools in a shared workspace flip from "no access" to plaintext-row - # permissions once the workspace has an initialized DEK (settings.encryption). - if tool in ENCRYPTED_TOOLS and ctx.has_encryption: - return permission in _plaintext_row().get(ctx.ws_role, set()) - return permission in TOOL_PERMISSIONS.get(tool, {}).get(ctx.ws_role, set()) - - -def require_permission(tool: str, permission: Permission): - async def dep( - ctx: Annotated[WorkspaceContext, Depends(get_workspace_ctx)], - ) -> WorkspaceContext: - if not has_permission(ctx, tool, permission): - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail=f"Role {ctx.ws_role} lacks {permission} on {tool}", - ) - return ctx - return dep diff --git a/apps/backend/app/api/routes/workspaces/repo.py b/apps/backend/app/api/routes/workspaces/repo.py deleted file mode 100644 index 20946e9d..00000000 --- a/apps/backend/app/api/routes/workspaces/repo.py +++ /dev/null @@ -1,107 +0,0 @@ -from typing import Any -from pymongo.errors import DuplicateKeyError -from app.database import db_manager -from app.utils.collection_name import ( - WORKSPACES, - WORKSPACE_MEMBERSHIPS, -) -from app.utils.utils import create_timestamp, new_id - - -async def find_user_workspaces(uid: str) -> list[dict[str, Any]]: - memberships = await db_manager.find( - WORKSPACE_MEMBERSHIPS, {"uid": uid}, limit=500 - ) - if not memberships: - return [] - ws_ids = [m["workspace_id"] for m in memberships] - workspaces = await db_manager.find( - WORKSPACES, {"_id": {"$in": ws_ids}, "deleted_at": None}, limit=500 - ) - by_id = {w["_id"]: w for w in workspaces} - out: list[dict[str, Any]] = [] - for m in memberships: - ws = by_id.get(m["workspace_id"]) - if ws: - out.append({**ws, "ws_role": m["ws_role"]}) - return out - - -async def find_workspace(workspace_id: str) -> dict[str, Any] | None: - return await db_manager.find_one(WORKSPACES, {"_id": workspace_id}) - - -async def find_ws_membership( - workspace_id: str, uid: str -) -> dict[str, Any] | None: - return await db_manager.find_one( - WORKSPACE_MEMBERSHIPS, {"workspace_id": workspace_id, "uid": uid} - ) - - -def _personal_slug(uid: str) -> str: - return f"personal-{uid[:12]}" - - -# ponytail: workspace creation race tolerated — workspace_setup_at short-circuit in T3 -# prevents repeat calls per user. Tighten if a workspace dup is ever observed in prod. -async def upsert_personal_workspace(owner_uid: str) -> str: - existing = await db_manager.find_one( - WORKSPACES, - { - "owner_uid": owner_uid, - "is_personal": True, - }, - ) - if existing: - return existing["_id"] - ts = create_timestamp() - doc = { - "_id": new_id(), - "name": "Personal", - "slug": _personal_slug(owner_uid), - "is_personal": True, - "owner_uid": owner_uid, - "kind": "personal", - "settings": {"encryption": None}, - "createdAt": ts, - "updatedAt": ts, - } - await db_manager.insert_one(WORKSPACES, doc) - return doc["_id"] - - -async def upsert_ws_membership( - workspace_id: str, uid: str, ws_role: str -) -> None: - existing = await db_manager.find_one( - WORKSPACE_MEMBERSHIPS, - {"workspace_id": workspace_id, "uid": uid}, - ) - if existing: - return - try: - await db_manager.insert_one( - WORKSPACE_MEMBERSHIPS, - { - "_id": new_id(), - "workspace_id": workspace_id, - "uid": uid, - "ws_role": ws_role, - "createdAt": create_timestamp(), - }, - ) - except DuplicateKeyError: - # Concurrent caller won the race — membership already exists, nothing to do. - existing = await db_manager.find_one( - WORKSPACE_MEMBERSHIPS, {"workspace_id": workspace_id, "uid": uid} - ) - if not existing: - raise - - -async def set_workspace_deleted(workspace_id: str, deleted_at: int | None) -> None: - await db_manager.update_one( - WORKSPACES, {"_id": workspace_id}, - {"$set": {"deleted_at": deleted_at, "updatedAt": create_timestamp()}}, - ) diff --git a/apps/backend/app/api/routes/workspaces/schema.py b/apps/backend/app/api/routes/workspaces/schema.py deleted file mode 100644 index 4728d97a..00000000 --- a/apps/backend/app/api/routes/workspaces/schema.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Literal -from pydantic import BaseModel, Field - -WsKind = Literal["personal", "shared"] -WsRole = Literal["admin", "developer", "viewer"] - - -class WorkspaceEncryptionInfo(BaseModel): - scheme: str - dekFingerprint: str - createdAt: int - rotatedAt: int | None = None - # Retained for schema compatibility with existing encrypted workspaces. - # No longer set now that member sharing is removed (single-user workspaces). - rotationRequired: bool = False - - -class WorkspaceSettings(BaseModel): - encryption: WorkspaceEncryptionInfo | None = None - - -class WorkspaceOut(BaseModel): - id: str - name: str - slug: str - is_personal: bool - kind: WsKind - ws_role: WsRole - settings: WorkspaceSettings = WorkspaceSettings() - - -class SetActiveWorkspaceRequest(BaseModel): - workspace_id: str = Field(min_length=1) - - -class SetActiveWorkspaceResponse(BaseModel): - workspace_id: str - - -class EncryptionBlob(BaseModel): - encrypted: str - iv: str - - -class KeypairOut(BaseModel): - publicKey: str - privateKeyEncrypted: EncryptionBlob - salt: str - createdAt: int - - -class KeypairPostRequest(BaseModel): - publicKey: str = Field(min_length=1) - privateKeyEncrypted: EncryptionBlob - salt: str = Field(min_length=1) diff --git a/apps/backend/app/api/routes/workspaces/services.py b/apps/backend/app/api/routes/workspaces/services.py deleted file mode 100644 index 3b88ab44..00000000 --- a/apps/backend/app/api/routes/workspaces/services.py +++ /dev/null @@ -1,35 +0,0 @@ -from fastapi import BackgroundTasks - -from app.api.routes.auth import users_repo -from app.api.routes.workspaces.repo import ( - upsert_personal_workspace, - upsert_ws_membership, -) -from app.api.routes.workspaces.backfill import schedule_backfill - - -async def ensure_user_workspace_setup( - uid: str, - background_tasks: BackgroundTasks | None = None, -) -> str: - """Idempotently create the user's Personal workspace. - Returns the user's Personal workspace_id. - """ - setup_at = await users_repo.get_workspace_setup_at(uid) - if setup_at: - ws_id = await users_repo.get_personal_workspace_id(uid) - if ws_id: - return ws_id - - ws_id = await upsert_personal_workspace(uid) - await upsert_ws_membership(ws_id, uid, "admin") - - await users_repo.mark_workspace_setup(uid, ws_id) - - migrated_at = await users_repo.get_migrated_at(uid) - if not migrated_at: - await users_repo.mark_migration_pending(uid) - if background_tasks is not None: - schedule_backfill(background_tasks, uid, ws_id) - - return ws_id diff --git a/apps/backend/app/core/cache/keys.py b/apps/backend/app/core/cache/keys.py index 7971cddd..7bc1427c 100644 --- a/apps/backend/app/core/cache/keys.py +++ b/apps/backend/app/core/cache/keys.py @@ -56,16 +56,3 @@ def args_hash(kwargs: dict, *, secret: bytes) -> str: # --- spec-locked namespaces --- register_namespace("auth_token", scope="global", default_ttl=300) register_namespace("auth_user", scope="user", default_ttl=60) -register_namespace("bookmarks", scope="user", default_ttl=120) -register_namespace("notes", scope="user", default_ttl=120) -register_namespace("code_snippets", scope="user", default_ttl=120) -register_namespace("tasks", scope="user", default_ttl=60) -register_namespace("passwords", scope="user", default_ttl=60) -register_namespace("api_client", scope="user", default_ttl=300) -register_namespace("user_preferences", scope="user", default_ttl=600) -register_namespace("url_shortener_resolve", scope="global", default_ttl=600, default_strategy="xfetch") -register_namespace("url_shortener_owner", scope="user", default_ttl=120) -register_namespace("url_shortener_analytics", scope="user", default_ttl=60) -register_namespace("analytics_aggregate", scope="global", default_ttl=300, default_strategy="xfetch") -register_namespace("dns_lookup", scope="global", default_ttl=3600) -register_namespace("s3_drive_list", scope="user", default_ttl=30) diff --git a/apps/backend/app/core/indexes.py b/apps/backend/app/core/indexes.py index a42a711e..d595a1e0 100644 --- a/apps/backend/app/core/indexes.py +++ b/apps/backend/app/core/indexes.py @@ -1,32 +1,8 @@ from app.database import db_manager from app.utils.collection_name import ( - API_CLIENT_HISTORY, - API_CLIENT_COLLECTIONS, - API_CLIENT_ENVIRONMENTS, AUDIT_LOG, - BOOKMARK_FOLDERS, - BOOKMARKS, - CODE_SNIPPETS, - ENV_MANAGER_ENTRIES, - FEEDBACK, - GAME_SCORES, - JSON_FORMATTER_DOCUMENTS, - NOTES, - NOSQL_CONNECTIONS, - NOSQL_QUERY_HISTORY, - PASSWORD_ENTRIES, - PROJECTS, - REDIS_CONNECTIONS, - S3_CONNECTIONS, - SQL_CONNECTIONS, - TASKS, - URL_CLICK_EVENTS, - URL_LINKS, - USER_PREFERENCES, USERS, WEBAUTHN_CHALLENGES, - WORKSPACES, - WORKSPACE_MEMBERSHIPS, ) from app.core.config import get_settings @@ -42,45 +18,6 @@ async def ensure_indexes() -> None: "created_at", expire_after_seconds=get_settings().WEBAUTHN_CHALLENGE_TTL_SECONDS, ) - await db_manager.create_index(TASKS, [("created_by", 1), ("status", 1), ("statusOrder", 1), ("createdAt", -1)]) - await db_manager.create_index(TASKS, [("created_by", 1), ("projectId", 1), ("status", 1), ("statusOrder", 1)]) - # Assignee filter within a shared workspace (Linear-style "assigned to me"). - await db_manager.create_index(TASKS, [("workspace_id", 1), ("assigneeUid", 1), ("statusOrder", 1)]) - await db_manager.create_index(PROJECTS, [("created_by", 1), ("createdAt", 1)]) - await db_manager.create_index(BOOKMARKS, [("created_by", 1), ("folderId", 1)]) - await db_manager.create_index(BOOKMARKS, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(BOOKMARK_FOLDERS, [("created_by", 1), ("parentId", 1)]) - await db_manager.create_index(BOOKMARK_FOLDERS, [("created_by", 1), ("createdAt", 1)]) - await db_manager.create_index(CODE_SNIPPETS, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(NOTES, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(JSON_FORMATTER_DOCUMENTS, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(API_CLIENT_HISTORY, [("created_by", 1), ("timestamp", -1)]) - await db_manager.create_index(API_CLIENT_COLLECTIONS, [("created_by", 1), ("name", 1), ("_id", 1)]) - await db_manager.create_index(API_CLIENT_ENVIRONMENTS, [("created_by", 1), ("name", 1), ("_id", 1)]) - await db_manager.create_index(PASSWORD_ENTRIES, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(ENV_MANAGER_ENTRIES, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(NOSQL_CONNECTIONS, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(NOSQL_QUERY_HISTORY, [("created_by", 1), ("createdAt", -1)]) - await db_manager.create_index(USER_PREFERENCES, "created_by") - await db_manager.create_index(SQL_CONNECTIONS, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(S3_CONNECTIONS, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(REDIS_CONNECTIONS, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(GAME_SCORES, [("created_by", 1), ("updatedAt", -1)]) - await db_manager.create_index(FEEDBACK, [("created_by", 1), ("createdAt", -1)]) - await db_manager.create_index(URL_LINKS, [("created_by", 1), ("created_at", -1)]) - await db_manager.create_index(URL_CLICK_EVENTS, [("code", 1), ("ts", -1)]) - # ponytail: TTL via expireAt BSON Date (ts is int millis, not Date); 90d retention - await db_manager.create_index(URL_CLICK_EVENTS, "expireAt", expire_after_seconds=0) await db_manager.create_index(AUDIT_LOG, [("uid", 1), ("ts", -1)]) await db_manager.create_index(AUDIT_LOG, [("uid", 1), ("module", 1), ("ts", -1)]) await db_manager.create_index(AUDIT_LOG, "expireAt", expire_after_seconds=0) - await db_manager.create_index( - WORKSPACES, - [("owner_uid", 1), ("is_personal", 1)], - ) - await db_manager.create_index( - WORKSPACE_MEMBERSHIPS, - [("workspace_id", 1), ("uid", 1)], - unique=True, - ) - await db_manager.create_index(WORKSPACE_MEMBERSHIPS, [("uid", 1)]) diff --git a/apps/backend/app/main.py b/apps/backend/app/main.py index 9c24cee5..ebf4bce3 100644 --- a/apps/backend/app/main.py +++ b/apps/backend/app/main.py @@ -1,4 +1,3 @@ -import asyncio import logging from contextlib import asynccontextmanager @@ -28,18 +27,9 @@ async def lifespan(_app: FastAPI): from app.core.redis_client import open_redis, close_redis await open_redis() - from app.api.routes.url_shortener.click_queue import flush_loop, close_flush_client - click_flush_task = asyncio.create_task(flush_loop()) - try: yield finally: - click_flush_task.cancel() - try: - await click_flush_task - except asyncio.CancelledError: - pass - await close_flush_client() await close_redis() diff --git a/apps/backend/app/utils/collection_name.py b/apps/backend/app/utils/collection_name.py index 64a6ee9a..9218523d 100644 --- a/apps/backend/app/utils/collection_name.py +++ b/apps/backend/app/utils/collection_name.py @@ -19,12 +19,9 @@ JSON_FORMATTER_DOCUMENTS = "json_formatter_documents" CODE_SNIPPETS = "code_snippets" SQL_CONNECTIONS = "sql_connections" -GAME_SCORES = "game_scores" S3_CONNECTIONS = "s3_connections" FEEDBACK = "feedback" REDIS_CONNECTIONS = "redis_connections" -URL_LINKS = "url_links" -URL_CLICK_EVENTS = "url_click_events" AUDIT_LOG = "audit_log" WEBAUTHN_CHALLENGES = "webauthn_challenges" WORKSPACES = "workspaces" diff --git a/apps/backend/tests/api/routes/api_client/__init__.py b/apps/backend/tests/api/routes/api_client/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/tests/api/routes/api_client/test_collections_delta.py b/apps/backend/tests/api/routes/api_client/test_collections_delta.py deleted file mode 100644 index 513eb3f8..00000000 --- a/apps/backend/tests/api/routes/api_client/test_collections_delta.py +++ /dev/null @@ -1,264 +0,0 @@ -"""Tests for POST /api-client/collections/{id}/items:apply-delta. - -Auth pattern: override get_workspace_ctx via FastAPI dependency_overrides. -MongoDB is monkeypatched in-process so no real DB is needed. -""" -from __future__ import annotations - -import copy -from typing import Any -from unittest.mock import AsyncMock - -import pytest -from bson import ObjectId -from httpx import ASGITransport, AsyncClient - -from app.api.routes.workspaces.middleware import WorkspaceContext, get_workspace_ctx -from app.main import app - -# Fixed IDs used throughout -OWNER_UID = "test-owner-uid" -OTHER_UID = "other-uid" -COLLECTION_OID = ObjectId() -COLLECTION_ID = str(COLLECTION_OID) - -FOLDER_ID = "folder-1" -REQUEST_ID = "request-1" - -BASE_COLLECTION: dict[str, Any] = { - "_id": COLLECTION_OID, - "created_by": OWNER_UID, - "workspace_id": "ws-owner", - "owner_uid": OWNER_UID, - "name": "My Collection", - "items": [ - { - "id": FOLDER_ID, - "name": "Folder 1", - "type": "folder", - "items": [ - { - "id": REQUEST_ID, - "name": "Existing Request", - "type": "request", - "method": "GET", - "url": "https://existing", - "params": [], - "headers": [], - "body": {"type": "none", "content": "", "formData": [], "urlEncoded": []}, - "auth": {"type": "none"}, - } - ], - "isOpen": True, - } - ], -} - - -def _make_ctx(uid: str, ws_id: str = "ws-owner") -> WorkspaceContext: - return WorkspaceContext( - uid=uid, - workspace_id=ws_id, - ws_role="admin", - is_personal=True, - owner_uid=uid, - ) - - -def _make_client(uid: str, ws_id: str = "ws-owner") -> AsyncClient: - """Return an AsyncClient that authenticates as uid with the given workspace.""" - ctx = _make_ctx(uid, ws_id) - app.dependency_overrides[get_workspace_ctx] = lambda: ctx - return AsyncClient(transport=ASGITransport(app=app), base_url="http://test") - - -def _mock_find_one(collection_doc: dict | None): - """Monkeypatch db_manager.find_one to return collection_doc. - - Checks ownership via created_by (plus workspace_id/owner_uid when present). - """ - async def _find_one(collection_name, query, projection=None): - if collection_doc is None: - return None - # Check created_by - if query.get("created_by") != collection_doc.get("created_by"): - return None - # Check workspace_id (strict filter from apply_workspace_filter) - if "workspace_id" in query and query["workspace_id"] != collection_doc.get("workspace_id"): - return None - # Check owner_uid (personal workspace strict filter) - if "owner_uid" in query and query["owner_uid"] != collection_doc.get("owner_uid"): - return None - return copy.deepcopy(collection_doc) - - return _find_one - - -def _mock_find_one_and_update(updated_doc: dict | None): - """Monkeypatch db_manager.find_one_and_update to return updated_doc. - - The inner function stores the most-recent ``update_query`` on itself so - tests can inspect the ``$set`` payload without resorting to AsyncMock. - """ - async def _find_one_and_update(collection_name, query, update_query, return_document=False): - _find_one_and_update.last_update_query = update_query # type: ignore[attr-defined] - if updated_doc is None: - return None - return copy.deepcopy(updated_doc) - - _find_one_and_update.last_update_query = None # type: ignore[attr-defined] - return _find_one_and_update - - -@pytest.fixture(autouse=True) -def cleanup_overrides(): - yield - app.dependency_overrides.pop(get_workspace_ctx, None) - - -# ── Test: add op inserts a new item under parent_id ────────────────────────── - -@pytest.mark.asyncio -async def test_apply_delta_adds_item(monkeypatch): - new_item = { - "id": "new-req-1", - "name": "New Request", - "type": "request", - "method": "POST", - "url": "https://new", - "params": [], - "headers": [], - "body": {"type": "none", "content": "", "formData": [], "urlEncoded": []}, - "auth": {"type": "none"}, - } - - # After the update, the collection has the new item under the folder - updated_collection = copy.deepcopy(BASE_COLLECTION) - updated_collection["items"][0]["items"].append(new_item) - - mock_update = _mock_find_one_and_update(updated_collection) - monkeypatch.setattr("app.api.routes.api_client.collections_delta.db_manager.find_one", _mock_find_one(BASE_COLLECTION)) - monkeypatch.setattr( - "app.api.routes.api_client.collections_delta.db_manager.find_one_and_update", - mock_update, - ) - - async with _make_client(OWNER_UID) as ac: - resp = await ac.post( - f"/api/v1/api-client/collections/{COLLECTION_ID}/items:apply-delta", - json={"ops": [{"type": "add", "parent_id": FOLDER_ID, "item": new_item}]}, - ) - - assert resp.status_code == 200, resp.text - body = resp.json() - assert "collection" in body - folder = next(i for i in body["collection"]["items"] if i["id"] == FOLDER_ID) - item_ids = {it["id"] for it in folder["items"]} - assert "new-req-1" in item_ids - - # Assert the $set payload sent to MongoDB contains the new item - update_query = mock_update.last_update_query - assert update_query is not None - persisted_items = update_query["$set"]["items"] - persisted_folder = next(i for i in persisted_items if i["id"] == FOLDER_ID) - persisted_ids = {it["id"] for it in persisted_folder["items"]} - assert "new-req-1" in persisted_ids - - -# ── Test: delete op removes an item ────────────────────────────────────────── - -@pytest.mark.asyncio -async def test_apply_delta_deletes_item(monkeypatch): - # After delete, folder has no children - updated_collection = copy.deepcopy(BASE_COLLECTION) - updated_collection["items"][0]["items"] = [] - - mock_update = _mock_find_one_and_update(updated_collection) - monkeypatch.setattr("app.api.routes.api_client.collections_delta.db_manager.find_one", _mock_find_one(BASE_COLLECTION)) - monkeypatch.setattr( - "app.api.routes.api_client.collections_delta.db_manager.find_one_and_update", - mock_update, - ) - - async with _make_client(OWNER_UID) as ac: - resp = await ac.post( - f"/api/v1/api-client/collections/{COLLECTION_ID}/items:apply-delta", - json={"ops": [{"type": "delete", "item_id": REQUEST_ID}]}, - ) - - assert resp.status_code == 200, resp.text - body = resp.json() - folder = next(i for i in body["collection"]["items"] if i["id"] == FOLDER_ID) - assert all(i["id"] != REQUEST_ID for i in folder["items"]) - - # Assert the $set payload sent to MongoDB has the item removed - update_query = mock_update.last_update_query - assert update_query is not None - persisted_items = update_query["$set"]["items"] - persisted_folder = next(i for i in persisted_items if i["id"] == FOLDER_ID) - assert all(it["id"] != REQUEST_ID for it in persisted_folder["items"]) - - -# ── Test: update op patches fields ─────────────────────────────────────────── - -@pytest.mark.asyncio -async def test_apply_delta_updates_item(monkeypatch): - updated_collection = copy.deepcopy(BASE_COLLECTION) - # Patch the nested request's name - updated_collection["items"][0]["items"][0]["name"] = "Renamed Request" - updated_collection["items"][0]["items"][0]["url"] = "https://updated" - - monkeypatch.setattr("app.api.routes.api_client.collections_delta.db_manager.find_one", _mock_find_one(BASE_COLLECTION)) - monkeypatch.setattr( - "app.api.routes.api_client.collections_delta.db_manager.find_one_and_update", - _mock_find_one_and_update(updated_collection), - ) - - async with _make_client(OWNER_UID) as ac: - resp = await ac.post( - f"/api/v1/api-client/collections/{COLLECTION_ID}/items:apply-delta", - json={"ops": [{"type": "update", "item_id": REQUEST_ID, "patch": {"name": "Renamed Request", "url": "https://updated"}}]}, - ) - - assert resp.status_code == 200, resp.text - body = resp.json() - folder = next(i for i in body["collection"]["items"] if i["id"] == FOLDER_ID) - req = next(i for i in folder["items"] if i["id"] == REQUEST_ID) - assert req["name"] == "Renamed Request" - assert req["url"] == "https://updated" - - -# ── Test: unauthorized user (different uid) gets 404 ───────────────────────── - -@pytest.mark.asyncio -async def test_apply_delta_wrong_uid_gets_404(monkeypatch): - # find_one returns None for OTHER_UID (ownership filter fails) - monkeypatch.setattr("app.api.routes.api_client.collections_delta.db_manager.find_one", _mock_find_one(BASE_COLLECTION)) - - # OTHER_UID with a different workspace should be denied by the ownership check - other_ctx = _make_ctx(OTHER_UID, ws_id="ws-other") - app.dependency_overrides[get_workspace_ctx] = lambda: other_ctx - - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - resp = await ac.post( - f"/api/v1/api-client/collections/{COLLECTION_ID}/items:apply-delta", - json={"ops": [{"type": "delete", "item_id": REQUEST_ID}]}, - ) - - assert resp.status_code == 404 - - -# ── Test: move op with unknown new_parent_id returns 400 ───────────────────── - -@pytest.mark.asyncio -async def test_apply_delta_move_unknown_parent_returns_400(monkeypatch): - monkeypatch.setattr("app.api.routes.api_client.collections_delta.db_manager.find_one", _mock_find_one(BASE_COLLECTION)) - - async with _make_client(OWNER_UID) as ac: - resp = await ac.post( - f"/api/v1/api-client/collections/{COLLECTION_ID}/items:apply-delta", - json={"ops": [{"type": "move", "item_id": REQUEST_ID, "new_parent_id": "does-not-exist", "new_index": 0}]}, - ) - - assert resp.status_code == 400, resp.text - assert "new_parent_id" in resp.json().get("detail", "") diff --git a/apps/backend/tests/api/routes/audit_log/__init__.py b/apps/backend/tests/api/routes/audit_log/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/tests/api/routes/audit_log/test_audit_query.py b/apps/backend/tests/api/routes/audit_log/test_audit_query.py deleted file mode 100644 index d73e3ed9..00000000 --- a/apps/backend/tests/api/routes/audit_log/test_audit_query.py +++ /dev/null @@ -1,36 +0,0 @@ -import asyncio - -import pytest - -from app.api.routes.audit_log import services as svc - - -@pytest.fixture -def fake_db(monkeypatch): - rows = [ - {"_id": "1", "uid": "u1", "action": "bookmark.create", "module": "bookmarks", - "entity_type": "bookmark", "entity_id": "b1", "method": "POST", - "path": "/api/v1/bookmarks", "status": 200, "outcome": "success", - "changes": [{"field": "title", "before": None, "after": "GitHub"}], - "summary": "Created bookmark 'GitHub'", "ip": "1.2.3.4", "ua_raw": "UA", - "device": {"browser": "Chrome", "os": "macOS", "device_type": "desktop"}, - "latency_ms": 12, "ts": 1000, "expireAt": "x"}, - ] - - async def fake_find(collection_name, query, projection=None, sort=None, skip=0, limit=0, collation=None): - assert query["uid"] == "u1" - return rows - - async def fake_count(collection_name, query): - return len(rows) - - monkeypatch.setattr("app.api.routes.audit_log.services.db_manager.find", fake_find) - monkeypatch.setattr("app.api.routes.audit_log.services.db_manager.count_documents", fake_count) - return rows - - -def test_list_audit_events_scopes_to_uid_and_excludes_expireAt(fake_db): - out = asyncio.run(svc.list_audit_events("u1", skip=0, limit=50)) - assert out.total == 1 - assert out.items[0].action == "bookmark.create" - assert not hasattr(out.items[0], "expireAt") diff --git a/apps/backend/tests/api/routes/webhook_tester/__init__.py b/apps/backend/tests/api/routes/webhook_tester/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/tests/api/routes/webhook_tester/test_webhook_tester.py b/apps/backend/tests/api/routes/webhook_tester/test_webhook_tester.py deleted file mode 100644 index 4437d351..00000000 --- a/apps/backend/tests/api/routes/webhook_tester/test_webhook_tester.py +++ /dev/null @@ -1,200 +0,0 @@ -"""Webhook Tester route tests. - -The router is not yet wired into app.api.router (the coordinator adds the -include line), so we mount it on the app here. REDIS_URL is unset in tests, -so the service's in-memory fallback store is exercised. -""" -from datetime import datetime, timedelta, timezone - -import pytest -from fastapi.testclient import TestClient - -from app.api.routes.webhook_tester import services as svc -from app.api.routes.webhook_tester.api import router as webhook_tester_router -from app.main import app - -if not any(getattr(r, "path", "").startswith("/api/v1/webhook-tester") for r in app.routes): - app.include_router(webhook_tester_router, prefix="/api/v1") - -client = TestClient(app) - -BASE = "/api/v1/webhook-tester" - - -@pytest.fixture(autouse=True) -def clean_store(): - svc._memory_bins.clear() - yield - svc._memory_bins.clear() - - -def _create_bin() -> dict: - res = client.post(f"{BASE}/bins") - assert res.status_code == 200 - return res.json() - - -def test_create_bin_shape_and_ttl() -> None: - data = _create_bin() - assert set(data) == {"bin_id", "created_at", "expires_at"} - assert 8 <= len(data["bin_id"]) <= 16 - created = datetime.fromisoformat(data["created_at"]) - expires = datetime.fromisoformat(data["expires_at"]) - assert expires - created == timedelta(hours=24) - - -def test_bin_ids_are_unique() -> None: - ids = {_create_bin()["bin_id"] for _ in range(5)} - assert len(ids) == 5 - - -def test_receive_records_request_details() -> None: - bin_id = _create_bin()["bin_id"] - res = client.post( - f"{BASE}/in/{bin_id}?foo=bar&foo=baz&x=1", - json={"hello": "world"}, - headers={"x-custom": "abc"}, - ) - assert res.status_code == 200 - assert res.json() == {"ok": True} - - listed = client.get(f"{BASE}/bins/{bin_id}/requests") - assert listed.status_code == 200 - body = listed.json() - assert body["bin_id"] == bin_id - assert body["count"] == 1 - req = body["requests"][0] - assert req["method"] == "POST" - assert req["path"] == "" - assert req["query"] == {"foo": "bar,baz", "x": "1"} - assert req["headers"]["x-custom"] == "abc" - assert req["body"] == '{"hello":"world"}' - assert req["body_encoding"] == "text" - assert req["body_truncated"] is False - assert req["content_type"] == "application/json" - assert req["size"] == len('{"hello":"world"}') - assert req["id"] - assert req["received_at"] - - -def test_receive_captures_path_suffix() -> None: - bin_id = _create_bin()["bin_id"] - res = client.put(f"{BASE}/in/{bin_id}/github/push/events", content=b"payload") - assert res.status_code == 200 - req = client.get(f"{BASE}/bins/{bin_id}/requests").json()["requests"][0] - assert req["method"] == "PUT" - assert req["path"] == "github/push/events" - assert req["body"] == "payload" - - -@pytest.mark.parametrize("method", ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]) -def test_receive_accepts_all_methods(method: str) -> None: - bin_id = _create_bin()["bin_id"] - res = client.request(method, f"{BASE}/in/{bin_id}") - assert res.status_code == 200 - reqs = client.get(f"{BASE}/bins/{bin_id}/requests").json()["requests"] - assert [r["method"] for r in reqs] == [method] - - -def test_receive_unknown_bin_404() -> None: - assert client.post(f"{BASE}/in/does-not-exist").status_code == 404 - - -def test_list_unknown_bin_404() -> None: - assert client.get(f"{BASE}/bins/does-not-exist/requests").status_code == 404 - - -def test_clear_unknown_bin_404() -> None: - assert client.delete(f"{BASE}/bins/does-not-exist/requests").status_code == 404 - - -def test_expired_bin_rejected() -> None: - bin_id = _create_bin()["bin_id"] - past = (datetime.now(timezone.utc) - timedelta(seconds=1)).isoformat() - svc._memory_bins[bin_id]["expires_at"] = past - assert client.post(f"{BASE}/in/{bin_id}").status_code == 404 - assert client.get(f"{BASE}/bins/{bin_id}/requests").status_code == 404 - - -def test_newest_first_and_capped_at_100() -> None: - bin_id = _create_bin()["bin_id"] - for i in range(105): - client.post(f"{BASE}/in/{bin_id}", content=f"n{i}".encode()) - body = client.get(f"{BASE}/bins/{bin_id}/requests").json() - assert body["count"] == 100 - assert body["requests"][0]["body"] == "n104" - assert body["requests"][-1]["body"] == "n5" # oldest 5 dropped - - -def test_after_cursor_by_request_id() -> None: - bin_id = _create_bin()["bin_id"] - for i in range(3): - client.post(f"{BASE}/in/{bin_id}", content=f"n{i}".encode()) - all_reqs = client.get(f"{BASE}/bins/{bin_id}/requests").json()["requests"] - cursor = all_reqs[1]["id"] # the middle (n1) - newer = client.get(f"{BASE}/bins/{bin_id}/requests", params={"after": cursor}).json() - assert newer["count"] == 1 - assert newer["requests"][0]["body"] == "n2" - - -def test_after_cursor_by_timestamp() -> None: - bin_id = _create_bin()["bin_id"] - client.post(f"{BASE}/in/{bin_id}", content=b"old") - old = client.get(f"{BASE}/bins/{bin_id}/requests").json()["requests"][0] - client.post(f"{BASE}/in/{bin_id}", content=b"new") - newer = client.get( - f"{BASE}/bins/{bin_id}/requests", params={"after": old["received_at"]} - ).json()["requests"] - assert [r["body"] for r in newer] == ["new"] - - -def test_after_unknown_cursor_returns_everything() -> None: - bin_id = _create_bin()["bin_id"] - client.post(f"{BASE}/in/{bin_id}", content=b"a") - body = client.get(f"{BASE}/bins/{bin_id}/requests", params={"after": "bogus"}).json() - assert body["count"] == 1 - - -def test_binary_body_is_base64() -> None: - bin_id = _create_bin()["bin_id"] - client.post(f"{BASE}/in/{bin_id}", content=b"\xff\xfe\x00binary") - req = client.get(f"{BASE}/bins/{bin_id}/requests").json()["requests"][0] - assert req["body_encoding"] == "base64" - import base64 - - assert base64.b64decode(req["body"]) == b"\xff\xfe\x00binary" - - -def test_large_body_truncated_at_64kb() -> None: - bin_id = _create_bin()["bin_id"] - client.post(f"{BASE}/in/{bin_id}", content=b"a" * (svc.MAX_BODY_BYTES + 500)) - req = client.get(f"{BASE}/bins/{bin_id}/requests").json()["requests"][0] - assert req["body_truncated"] is True - assert len(req["body"]) == svc.MAX_BODY_BYTES - assert req["size"] == svc.MAX_BODY_BYTES + 500 - - -def test_clear_requests() -> None: - bin_id = _create_bin()["bin_id"] - client.post(f"{BASE}/in/{bin_id}", content=b"x") - res = client.delete(f"{BASE}/bins/{bin_id}/requests") - assert res.status_code == 204 - body = client.get(f"{BASE}/bins/{bin_id}/requests").json() - assert body["count"] == 0 - # Bin itself still alive after clearing - assert client.post(f"{BASE}/in/{bin_id}", content=b"y").status_code == 200 - - -def test_source_ip_prefers_x_forwarded_for() -> None: - bin_id = _create_bin()["bin_id"] - client.post(f"{BASE}/in/{bin_id}", headers={"x-forwarded-for": "203.0.113.7, 10.0.0.1"}) - req = client.get(f"{BASE}/bins/{bin_id}/requests").json()["requests"][0] - assert req["source_ip"] == "203.0.113.7" - - -def test_endpoints_do_not_require_auth() -> None: - # No cookies / Authorization header anywhere in this file — reaching 200s - # above already proves it, but assert explicitly for the receive endpoint. - bin_id = _create_bin()["bin_id"] - res = client.post(f"{BASE}/in/{bin_id}", json={"ping": 1}) - assert res.status_code == 200 diff --git a/apps/backend/tests/api/routes/whois/__init__.py b/apps/backend/tests/api/routes/whois/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/tests/api/routes/whois/test_whois_lookup.py b/apps/backend/tests/api/routes/whois/test_whois_lookup.py deleted file mode 100644 index c8c695d7..00000000 --- a/apps/backend/tests/api/routes/whois/test_whois_lookup.py +++ /dev/null @@ -1,294 +0,0 @@ -import asyncio - -import httpx -import pytest -from fastapi import HTTPException - -from app.api.routes.whois import services as svc - -RDAP_DOMAIN = { - "objectClassName": "domain", - "ldhName": "EXAMPLE.COM", - "status": ["client delete prohibited", "client transfer prohibited"], - "events": [ - {"eventAction": "registration", "eventDate": "1995-08-14T04:00:00Z"}, - {"eventAction": "expiration", "eventDate": "2026-08-13T04:00:00Z"}, - {"eventAction": "last changed", "eventDate": "2025-08-14T07:01:44Z"}, - {"eventAction": "last update of RDAP database", "eventDate": "2026-01-01T00:00:00Z"}, - ], - "entities": [ - { - "objectClassName": "entity", - "handle": "376", - "roles": ["registrar"], - "vcardArray": [ - "vcard", - [ - ["version", {}, "text", "4.0"], - ["fn", {}, "text", "RESERVED-Internet Assigned Numbers Authority"], - ], - ], - "entities": [ - { - "objectClassName": "entity", - "handle": "ABUSE-1", - "roles": ["abuse"], - "vcardArray": ["vcard", [["version", {}, "text", "4.0"], ["fn", {}, "text", "Abuse Desk"]]], - } - ], - }, - { - "objectClassName": "entity", - "roles": ["registrant"], - "vcardArray": [ - "vcard", - [ - ["version", {}, "text", "4.0"], - ["fn", {}, "text", "REDACTED FOR PRIVACY"], - ["org", {}, "text", "Example Org"], - ], - ], - }, - ], - "nameservers": [ - {"ldhName": "A.IANA-SERVERS.NET."}, - {"ldhName": "B.IANA-SERVERS.NET"}, - {"ldhName": "a.iana-servers.net"}, - ], -} - -RDAP_IP = { - "objectClassName": "ip network", - "handle": "NET-8-8-8-0-1", - "name": "LVLT-GOGL-8-8-8", - "startAddress": "8.8.8.0", - "endAddress": "8.8.8.255", - "status": ["active"], - "events": [ - {"eventAction": "registration", "eventDate": "2014-03-14T00:00:00Z"}, - {"eventAction": "last changed", "eventDate": "2014-03-14T00:00:00Z"}, - ], - "entities": [ - { - "objectClassName": "entity", - "handle": "GOGL", - "roles": ["registrant"], - "vcardArray": ["vcard", [["version", {}, "text", "4.0"], ["fn", {}, "text", "Google LLC"]]], - } - ], -} - - -# --------------------------------------------------------------------------- -# sanitize_target -# --------------------------------------------------------------------------- - -def test_sanitize_target_plain_domain(): - assert svc.sanitize_target("example.com") == ("example.com", "domain") - - -def test_sanitize_target_strips_scheme_path_and_case(): - assert svc.sanitize_target("https://Example.COM/path?q=1#frag") == ("example.com", "domain") - - -def test_sanitize_target_strips_trailing_dot_and_port(): - assert svc.sanitize_target("example.com.") == ("example.com", "domain") - assert svc.sanitize_target("example.com:8080") == ("example.com", "domain") - - -def test_sanitize_target_ipv4(): - assert svc.sanitize_target("8.8.8.8") == ("8.8.8.8", "ip") - - -def test_sanitize_target_ipv6(): - assert svc.sanitize_target("2001:4860:4860::8888") == ("2001:4860:4860::8888", "ip") - assert svc.sanitize_target("[2001:4860:4860::8888]") == ("2001:4860:4860::8888", "ip") - - -@pytest.mark.parametrize("bad", ["", " ", "not a domain", "no-tld", "-bad-.com", "a" * 300 + ".com"]) -def test_sanitize_target_rejects_invalid(bad): - with pytest.raises(HTTPException) as exc: - svc.sanitize_target(bad) - assert exc.value.status_code == 400 - - -# --------------------------------------------------------------------------- -# lookup_whois normalization (fetch mocked) -# --------------------------------------------------------------------------- - -def test_lookup_whois_normalizes_domain(monkeypatch): - async def fake_fetch(*, kind, target): - assert kind == "domain" - assert target == "example.com" - return RDAP_DOMAIN - - monkeypatch.setattr(svc, "fetch_rdap", fake_fetch) - out = asyncio.run(svc.lookup_whois("https://Example.com/whatever")) - - assert out.target == "example.com" - assert out.type == "domain" - assert out.registrar == "RESERVED-Internet Assigned Numbers Authority" - assert out.status == ["client delete prohibited", "client transfer prohibited"] - assert out.created == "1995-08-14T04:00:00Z" - assert out.updated == "2025-08-14T07:01:44Z" - assert out.expires == "2026-08-13T04:00:00Z" - # lowercased, trailing dot stripped, deduped - assert out.nameservers == ["a.iana-servers.net", "b.iana-servers.net"] - assert out.raw == RDAP_DOMAIN - - -def test_lookup_whois_contacts_include_nested_and_redacted(monkeypatch): - async def fake_fetch(*, kind, target): - return RDAP_DOMAIN - - monkeypatch.setattr(svc, "fetch_rdap", fake_fetch) - out = asyncio.run(svc.lookup_whois("example.com")) - - by_role = {tuple(c.roles): c for c in out.contacts} - registrar = by_role[("registrar",)] - assert registrar.name == "RESERVED-Internet Assigned Numbers Authority" - assert registrar.handle == "376" - - registrant = by_role[("registrant",)] - assert registrant.name == "REDACTED FOR PRIVACY" # kept as the registry exposed it - assert registrant.organization == "Example Org" - - abuse = by_role[("abuse",)] # nested inside the registrar entity - assert abuse.name == "Abuse Desk" - assert abuse.handle == "ABUSE-1" - - -def test_lookup_whois_ip(monkeypatch): - async def fake_fetch(*, kind, target): - assert kind == "ip" - assert target == "8.8.8.8" - return RDAP_IP - - monkeypatch.setattr(svc, "fetch_rdap", fake_fetch) - out = asyncio.run(svc.lookup_whois("8.8.8.8")) - - assert out.type == "ip" - assert out.registrar == "LVLT-GOGL-8-8-8" # falls back to network name - assert out.created == "2014-03-14T00:00:00Z" - assert out.nameservers == [] - assert any(c.name == "Google LLC" for c in out.contacts) - - -def test_lookup_whois_unknown_domain_returns_404(monkeypatch): - async def fake_fetch(*, kind, target): - return None - - monkeypatch.setattr(svc, "fetch_rdap", fake_fetch) - with pytest.raises(HTTPException) as exc: - asyncio.run(svc.lookup_whois("definitely-not-registered-zzz.com")) - assert exc.value.status_code == 404 - - -def test_lookup_whois_invalid_target_short_circuits(monkeypatch): - called = False - - async def fake_fetch(*, kind, target): - nonlocal called - called = True - return RDAP_DOMAIN - - monkeypatch.setattr(svc, "fetch_rdap", fake_fetch) - with pytest.raises(HTTPException) as exc: - asyncio.run(svc.lookup_whois("!!not valid!!")) - assert exc.value.status_code == 400 - assert called is False - - -# --------------------------------------------------------------------------- -# fetch_rdap (httpx mocked) -# --------------------------------------------------------------------------- - -class _FakeResponse: - def __init__(self, status_code=200, json_data=None): - self.status_code = status_code - self._json = json_data - - def json(self): - if self._json is None: - raise ValueError("no json") - return self._json - - -class _FakeAsyncClient: - init_kwargs = None - last_url = None - response = None - error = None - - def __init__(self, **kwargs): - _FakeAsyncClient.init_kwargs = kwargs - - async def __aenter__(self): - return self - - async def __aexit__(self, *exc): - return False - - async def get(self, url, **kwargs): - _FakeAsyncClient.last_url = url - if _FakeAsyncClient.error is not None: - raise _FakeAsyncClient.error - return _FakeAsyncClient.response - - -@pytest.fixture -def fake_http(monkeypatch): - _FakeAsyncClient.init_kwargs = None - _FakeAsyncClient.last_url = None - _FakeAsyncClient.response = None - _FakeAsyncClient.error = None - monkeypatch.setattr(svc.httpx, "AsyncClient", _FakeAsyncClient) - return _FakeAsyncClient - - -def test_fetch_rdap_domain_url_and_redirects(fake_http): - fake_http.response = _FakeResponse(200, RDAP_DOMAIN) - out = asyncio.run(svc.fetch_rdap(kind="domain", target="example.com")) - assert out == RDAP_DOMAIN - assert fake_http.last_url == "https://rdap.org/domain/example.com" - assert fake_http.init_kwargs.get("follow_redirects") is True - - -def test_fetch_rdap_ip_url(fake_http): - fake_http.response = _FakeResponse(200, RDAP_IP) - out = asyncio.run(svc.fetch_rdap(kind="ip", target="8.8.8.8")) - assert out == RDAP_IP - assert fake_http.last_url == "https://rdap.org/ip/8.8.8.8" - - -def test_fetch_rdap_404_returns_none(fake_http): - fake_http.response = _FakeResponse(404) - assert asyncio.run(svc.fetch_rdap(kind="domain", target="nope.example")) is None - - -def test_fetch_rdap_upstream_error_maps_to_502(fake_http): - fake_http.response = _FakeResponse(500) - with pytest.raises(HTTPException) as exc: - asyncio.run(svc.fetch_rdap(kind="domain", target="example.com")) - assert exc.value.status_code == 502 - - -def test_fetch_rdap_rate_limit_maps_to_503(fake_http): - fake_http.response = _FakeResponse(429) - with pytest.raises(HTTPException) as exc: - asyncio.run(svc.fetch_rdap(kind="domain", target="example.com")) - assert exc.value.status_code == 503 - - -def test_fetch_rdap_network_error_maps_to_502(fake_http): - fake_http.error = httpx.ConnectError("boom") - with pytest.raises(HTTPException) as exc: - asyncio.run(svc.fetch_rdap(kind="domain", target="example.com")) - assert exc.value.status_code == 502 - - -def test_fetch_rdap_invalid_json_maps_to_502(fake_http): - fake_http.response = _FakeResponse(200, None) - with pytest.raises(HTTPException) as exc: - asyncio.run(svc.fetch_rdap(kind="domain", target="example.com")) - assert exc.value.status_code == 502 diff --git a/apps/backend/tests/conftest.py b/apps/backend/tests/conftest.py index 104437e6..60af7bb9 100644 --- a/apps/backend/tests/conftest.py +++ b/apps/backend/tests/conftest.py @@ -2,7 +2,6 @@ import pytest from httpx import AsyncClient, ASGITransport from starlette.requests import Request -from starlette.datastructures import Headers # Set required environment variables for tests BEFORE any imports os.environ.setdefault("APP_ENV", "test") @@ -18,44 +17,16 @@ os.environ.setdefault("WEBAUTHN_ORIGINS", "http://localhost:3000") os.environ.setdefault("WEBAUTHN_CHALLENGE_TTL_SECONDS", "300") -from app.api.routes.workspaces.services import ensure_user_workspace_setup from app.database import db_manager -from app.utils.collection_name import ( - API_CLIENT_COLLECTIONS, - API_CLIENT_ENVIRONMENTS, - API_CLIENT_HISTORY, - API_CLIENT_WORKSPACES, - API_KEY_VAULT_ENTRIES, - BOOKMARKS, - BOOKMARK_FOLDERS, - CODE_SNIPPETS, - ENV_MANAGER_ENTRIES, - JSON_FORMATTER_DOCUMENTS, - NOSQL_CONNECTIONS, - NOSQL_QUERY_HISTORY, - NOTES, - PASSWORD_ENTRIES, - PASSWORD_VAULTS, - PROJECTS, - REDIS_CONNECTIONS, - S3_CONNECTIONS, - SQL_CONNECTIONS, - TASKS, - USER_PREFERENCES, - USERS, - WORKSPACES, - WORKSPACE_MEMBERSHIPS, - URL_LINKS, - URL_CLICK_EVENTS, -) +from app.utils.collection_name import USERS pytest_plugins = ("pytest_asyncio",) @pytest.fixture async def clean_db(): - """Drop workspace-related collections and USERS before and after test.""" - collections = [API_CLIENT_COLLECTIONS, API_CLIENT_ENVIRONMENTS, API_CLIENT_HISTORY, API_CLIENT_WORKSPACES, API_KEY_VAULT_ENTRIES, WORKSPACES, WORKSPACE_MEMBERSHIPS, USERS, PASSWORD_ENTRIES, PASSWORD_VAULTS, NOTES, USER_PREFERENCES, BOOKMARKS, BOOKMARK_FOLDERS, TASKS, PROJECTS, ENV_MANAGER_ENTRIES, CODE_SNIPPETS, NOSQL_CONNECTIONS, NOSQL_QUERY_HISTORY, SQL_CONNECTIONS, S3_CONNECTIONS, REDIS_CONNECTIONS, URL_LINKS, URL_CLICK_EVENTS, JSON_FORMATTER_DOCUMENTS] + """Drop USERS before and after test.""" + collections = [USERS] # Clean before test for coll in collections: @@ -106,71 +77,6 @@ def get_count(): db_manager.insert_one = original_insert_one -@pytest.fixture -async def seed_legacy_user_data(): - """Insert legacy test data (PASSWORD_ENTRIES + NOTES without workspace_id).""" - # Create user document - await db_manager.insert_one( - USERS, - { - "_id": "u1", - "uid": "u1", - "email": "u1@example.com", - }, - ) - - # Insert legacy password entries - await db_manager.insert_one( - PASSWORD_ENTRIES, - { - "_id": "pe-1", - "created_by": "u1", - "name": "Test Password 1", - "encrypted": "secret", - }, - ) - await db_manager.insert_one( - PASSWORD_ENTRIES, - { - "_id": "pe-2", - "created_by": "u1", - "name": "Test Password 2", - "encrypted": "secret2", - }, - ) - - # Insert legacy notes - await db_manager.insert_one( - NOTES, - { - "_id": "note-1", - "created_by": "u1", - "title": "Test Note 1", - "content": "Some content", - }, - ) - await db_manager.insert_one( - NOTES, - { - "_id": "note-2", - "created_by": "u1", - "title": "Test Note 2", - "content": "More content", - }, - ) - - yield - - -@pytest.fixture -def personal_ws_for(): - """Return an async callable that creates (idempotently) a Personal workspace - for the given uid and returns the workspace_id.""" - async def _ensure(uid: str) -> str: - return await ensure_user_workspace_setup(uid) - return _ensure - - @pytest.fixture async def authed_client(clean_db) -> AsyncClient: """Create an authenticated HTTP client with get_current_uid stubbed to return test-uid.""" @@ -184,9 +90,6 @@ def override_get_current_uid(): app.dependency_overrides[get_current_uid] = override_get_current_uid - # Set up the test user's workspace - await ensure_user_workspace_setup(test_uid) - async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test", @@ -194,11 +97,3 @@ def override_get_current_uid(): yield client app.dependency_overrides.clear() - - -@pytest.fixture -async def personal_ws_id(authed_client: AsyncClient) -> str: - """Set up a test user with a personal workspace and return the workspace_id.""" - test_uid = "test-uid" - ws_id = await ensure_user_workspace_setup(test_uid) - return ws_id diff --git a/apps/backend/tests/test_api_client_workspace_isolation.py b/apps/backend/tests/test_api_client_workspace_isolation.py deleted file mode 100644 index ca4685ce..00000000 --- a/apps/backend/tests/test_api_client_workspace_isolation.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Workspace isolation tests for API Client collections, environments, history, -and internal workspaces. Mirrors the pattern used in test_nosql_workspace_isolation.py. - -Covers the most user-visible data path: COLLECTIONS (T14 primary concern). -""" -import pytest - -from app.api.routes.api_client import services as api_client_svc -from app.api.routes.api_client.schema import ( - ApiClientCollectionCreate, - ApiClientEnvironmentCreate, - ApiClientHistoryCreate, - ApiClientWorkspaceCreate, -) -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, - workspace_id=ws_id, - ws_role="admin", - is_personal=True, - owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_api_client_collections_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - """Collections created by u1 must not appear in u2's list.""" - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await api_client_svc.create_collection(ctx_u1, ApiClientCollectionCreate(name="u1-collection")) - - cols_u1 = await api_client_svc.list_collections(ctx=ctx_u1) - cols_u2 = await api_client_svc.list_collections(ctx=ctx_u2) - - assert len(cols_u1) == 1 - assert len(cols_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_cannot_read_api_client_collections( - clean_db, personal_ws_for, -): - """u2 forging u1's workspace_id still cannot see u1's collections due to owner_uid.""" - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await api_client_svc.create_collection(ctx_u1, ApiClientCollectionCreate(name="secret-collection")) - - forged_ctx = _ctx("u2", ws_u1) # u2 claims u1's workspace_id - cols = await api_client_svc.list_collections(ctx=forged_ctx) - assert cols == [] # owner_uid filter prevents cross-user bleed - - -@pytest.mark.asyncio -async def test_api_client_environments_and_history_isolated( - clean_db, personal_ws_for, -): - """Environments and history created by u1 must not appear for u2.""" - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await api_client_svc.create_environment(ctx_u1, ApiClientEnvironmentCreate(name="prod")) - await api_client_svc.create_history( - ctx_u1, - ApiClientHistoryCreate( - method="GET", - url="https://example.com/api", - name="example-request", - ), - ) - - envs_u2 = await api_client_svc.list_environments(ctx=ctx_u2) - history_u2 = await api_client_svc.list_history(ctx=ctx_u2) - - assert envs_u2 == [] - assert history_u2 == [] diff --git a/apps/backend/tests/test_api_key_vault_workspace_isolation.py b/apps/backend/tests/test_api_key_vault_workspace_isolation.py deleted file mode 100644 index 017807e1..00000000 --- a/apps/backend/tests/test_api_key_vault_workspace_isolation.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest -from app.api.routes.api_key_vault import services as vault_svc -from app.api.routes.api_key_vault.schema import ApiKeyEntryCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=True, owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_api_key_vault_entries_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await vault_svc.create_entry(ctx_u1, ApiKeyEntryCreate(encryptedData="enc-u1", iv="iv-u1")) - - entries_u1 = await vault_svc.list_entries(ctx_u1) - entries_u2 = await vault_svc.list_entries(ctx_u2) - - assert len(entries_u1) == 1 - assert len(entries_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_api_key_vault_data( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await vault_svc.create_entry(ctx_u1, ApiKeyEntryCreate(encryptedData="enc-u1", iv="iv-u1")) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - entries = await vault_svc.list_entries(forged_ctx) - assert entries == [] # owner_uid filter saves us diff --git a/apps/backend/tests/test_audit_middleware.py b/apps/backend/tests/test_audit_middleware.py index fadbd61d..8c7ec953 100644 --- a/apps/backend/tests/test_audit_middleware.py +++ b/apps/backend/tests/test_audit_middleware.py @@ -86,35 +86,3 @@ async def boom(collection_name, data): client = TestClient(build_app()) res = client.post("/api/v1/bookmarks") assert res.status_code == 200 # user response unaffected - - -def test_bookmark_service_sets_audit_detail(monkeypatch): - import asyncio as _asyncio - from app.api.routes.bookmarks import services as bm - from app.api.routes.bookmarks.schema import BookmarkCreate - from app.api.routes.workspaces.middleware import WorkspaceContext - from app.core import audit - - async def fake_insert_one(collection_name, data): - return None - - monkeypatch.setattr("app.api.routes.bookmarks.services.db_manager.insert_one", fake_insert_one) - - _bm_ctx = WorkspaceContext( - uid="uid1", workspace_id="ws1", ws_role="admin", - is_personal=True, owner_uid="uid1", - ) - - async def run(): - tok = audit._audit_ctx.set(audit.AuditContext()) - try: - await bm.create_bookmark(_bm_ctx, BookmarkCreate(title="GitHub", url="https://gh.com")) - ctx = audit.current_context() - assert ctx.action == "bookmark.create" - assert ctx.entity_type == "bookmark" - assert ctx.entity_id # the new id - assert any(c["field"] == "title" and c["after"] == "GitHub" for c in (ctx.changes or [])) - finally: - audit._audit_ctx.reset(tok) - - _asyncio.run(run()) diff --git a/apps/backend/tests/test_backfill.py b/apps/backend/tests/test_backfill.py deleted file mode 100644 index 41e54505..00000000 --- a/apps/backend/tests/test_backfill.py +++ /dev/null @@ -1,47 +0,0 @@ -import pytest -from app.api.routes.auth import users_repo -from app.api.routes.workspaces.backfill import run_user_backfill -from app.database import db_manager -from app.utils.collection_name import PASSWORD_ENTRIES, NOTES, USER_PREFERENCES - - -@pytest.mark.asyncio -async def test_backfill_stamps_legacy_rows(clean_db, seed_legacy_user_data): - # seed_legacy_user_data inserts a few rows in PASSWORD_ENTRIES + NOTES - # for created_by="u1" with no workspace_id - ws_id = "ws-1" - - await run_user_backfill("u1", ws_id) - - for entry in await db_manager.find(PASSWORD_ENTRIES, {"created_by": "u1"}, limit=100): - assert entry["workspace_id"] == ws_id - - for note in await db_manager.find(NOTES, {"created_by": "u1"}, limit=100): - assert note["workspace_id"] == ws_id - - assert await users_repo.get_migrated_at("u1") is not None - - -@pytest.mark.asyncio -async def test_backfill_is_idempotent(clean_db, seed_legacy_user_data): - ws_id = "ws-1" - await run_user_backfill("u1", ws_id) - migrated_first = await users_repo.get_migrated_at("u1") - await run_user_backfill("u1", ws_id) - migrated_second = await users_repo.get_migrated_at("u1") - assert migrated_first == migrated_second - - -@pytest.mark.asyncio -async def test_backfill_rewrites_pinned_tools(clean_db): - ws_id = "ws-1" - await db_manager.insert_one( - USER_PREFERENCES, - {"_id": "u1", "uid": "u1", "toolFavorites": ["/app/passwords", "/app/notes"]}, - ) - - await run_user_backfill("u1", ws_id) - - pref = await db_manager.find_one(USER_PREFERENCES, {"_id": "u1"}) - assert pref["pinnedToolsByWorkspace"] == {ws_id: ["/app/passwords", "/app/notes"]} - assert "toolFavorites" not in pref diff --git a/apps/backend/tests/test_bookmarks_workspace_isolation.py b/apps/backend/tests/test_bookmarks_workspace_isolation.py deleted file mode 100644 index 5437cf4a..00000000 --- a/apps/backend/tests/test_bookmarks_workspace_isolation.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest -from app.api.routes.bookmarks import services as bm_svc -from app.api.routes.bookmarks.schema import BookmarkCreate, BookmarkFolderCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=True, owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_bookmarks_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await bm_svc.create_bookmark(ctx_u1, BookmarkCreate(title="u1 bookmark", url="https://u1.example")) - - bookmarks_u1 = await bm_svc.list_bookmarks(ctx=ctx_u1) - bookmarks_u2 = await bm_svc.list_bookmarks(ctx=ctx_u2) - - assert len(bookmarks_u1) == 1 - assert len(bookmarks_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_bookmark_data( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await bm_svc.create_bookmark(ctx_u1, BookmarkCreate(title="u1 secret", url="https://u1.example")) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - bookmarks = await bm_svc.list_bookmarks(ctx=forged_ctx) - assert bookmarks == [] # owner_uid filter saves us - - -@pytest.mark.asyncio -async def test_folders_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await bm_svc.create_folder(ctx_u1, BookmarkFolderCreate(name="u1 folder")) - - folders_u1 = await bm_svc.list_folders(ctx=ctx_u1) - folders_u2 = await bm_svc.list_folders(ctx=ctx_u2) - - assert len(folders_u1) == 1 - assert len(folders_u2) == 0 diff --git a/apps/backend/tests/test_cache_decorator.py b/apps/backend/tests/test_cache_decorator.py index 4f4de799..df3895ea 100644 --- a/apps/backend/tests/test_cache_decorator.py +++ b/apps/backend/tests/test_cache_decorator.py @@ -66,7 +66,7 @@ def enable_ns(monkeypatch): async def test_decorator_miss_then_hit(fake_redis, enable_ns): calls = {"n": 0} - @cached(ns="bookmarks", ttl=60, scope="user") + @cached(ns="auth_user", ttl=60, scope="user") async def list_bookmarks(*, uid: str): calls["n"] += 1 return [{"id": "a"}] @@ -81,13 +81,13 @@ async def list_bookmarks(*, uid: str): async def test_bump_version_invalidates(fake_redis, enable_ns): calls = {"n": 0} - @cached(ns="bookmarks", ttl=60, scope="user") + @cached(ns="auth_user", ttl=60, scope="user") async def list_bookmarks(*, uid: str): calls["n"] += 1 return [{"id": "a"}] await list_bookmarks(uid="u1") - await bump_version(ns="bookmarks", uid="u1") + await bump_version(ns="auth_user", uid="u1") await list_bookmarks(uid="u1") assert calls["n"] == 2 @@ -96,7 +96,7 @@ async def list_bookmarks(*, uid: str): async def test_fail_open(fake_redis, enable_ns): fake_redis.fail = True - @cached(ns="bookmarks", ttl=60, scope="user") + @cached(ns="auth_user", ttl=60, scope="user") async def list_bookmarks(*, uid: str): return [{"id": "from-mongo"}] @@ -109,7 +109,7 @@ async def test_disabled_namespace_skips_redis(fake_redis, monkeypatch): monkeypatch.setattr("app.core.cache.decorator.is_namespace_enabled", lambda ns: False) monkeypatch.setattr("app.core.cache.decorator._secret", lambda: b"x") - @cached(ns="bookmarks", ttl=60, scope="user") + @cached(ns="auth_user", ttl=60, scope="user") async def list_bookmarks(*, uid: str): return [{"id": "a"}] @@ -120,7 +120,7 @@ async def list_bookmarks(*, uid: str): @pytest.mark.asyncio async def test_user_scope_requires_uid(fake_redis, enable_ns): - @cached(ns="bookmarks", ttl=60, scope="user") + @cached(ns="auth_user", ttl=60, scope="user") async def list_bookmarks(**kw): return [] diff --git a/apps/backend/tests/test_code_snippets_workspace_isolation.py b/apps/backend/tests/test_code_snippets_workspace_isolation.py deleted file mode 100644 index 989138d6..00000000 --- a/apps/backend/tests/test_code_snippets_workspace_isolation.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest -from app.api.routes.code_snippets import services as snippet_svc -from app.api.routes.code_snippets.schema import CodeSnippetCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=True, owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_code_snippets_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await snippet_svc.create_code_snippet(ctx_u1, CodeSnippetCreate(title="snippet-u1", code="code-u1")) - - snippets_u1 = await snippet_svc.list_code_snippets(ctx_u1) - snippets_u2 = await snippet_svc.list_code_snippets(ctx_u2) - - assert len(snippets_u1) == 1 - assert len(snippets_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_code_snippets_data( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await snippet_svc.create_code_snippet(ctx_u1, CodeSnippetCreate(title="snippet-u1", code="code-u1")) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - snippets = await snippet_svc.list_code_snippets(forged_ctx) - assert snippets == [] # owner_uid filter saves us diff --git a/apps/backend/tests/test_environment_manager_workspace_isolation.py b/apps/backend/tests/test_environment_manager_workspace_isolation.py deleted file mode 100644 index f4973f3d..00000000 --- a/apps/backend/tests/test_environment_manager_workspace_isolation.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest -from app.api.routes.environment_manager import services as env_svc -from app.api.routes.environment_manager.schema import EnvSetEntryCreate, EnvSetEntryUpdate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=True, owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_env_entries_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await env_svc.create_entry(ctx_u1, EnvSetEntryCreate(encryptedData="enc-u1", iv="iv-u1")) - - entries_u1 = await env_svc.list_entries(ctx_u1) - entries_u2 = await env_svc.list_entries(ctx_u2) - - assert len(entries_u1) == 1 - assert len(entries_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_env_data( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await env_svc.create_entry(ctx_u1, EnvSetEntryCreate(encryptedData="enc-u1", iv="iv-u1")) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - entries = await env_svc.list_entries(forged_ctx) - assert entries == [] # owner_uid filter saves us diff --git a/apps/backend/tests/test_json_formatter_workspace_isolation.py b/apps/backend/tests/test_json_formatter_workspace_isolation.py deleted file mode 100644 index f486d55f..00000000 --- a/apps/backend/tests/test_json_formatter_workspace_isolation.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Workspace isolation tests for JSON formatter (JSON_FORMATTER_DOCUMENTS). - -Tests ensure that: -1. Documents created by u1 are not visible to u2 across workspace boundaries. -2. Forged workspace_id cannot bypass owner_uid isolation. -""" -import pytest - -from app.api.routes.json_formatter import services as jf_svc -from app.api.routes.json_formatter.schema import JsonFormatterDocumentCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, - workspace_id=ws_id, - ws_role="admin", - is_personal=True, - owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_json_formatter_documents_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - """Documents created by u1 must not appear in u2's list.""" - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - # u1 creates a document - await jf_svc.create_document(ctx_u1, JsonFormatterDocumentCreate(title="test", pane="left", content="{}")) - - # u1 sees their document - docs_u1 = await jf_svc.list_documents(ctx_u1) - assert len(docs_u1) == 1 - assert docs_u1[0].title == "test" - - # u2 does not see u1's document - docs_u2 = await jf_svc.list_documents(ctx_u2) - assert len(docs_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_cannot_read_json_formatter_documents( - clean_db, personal_ws_for, -): - """u2 forging u1's workspace_id still cannot see u1's documents due to owner_uid.""" - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await jf_svc.create_document(ctx_u1, JsonFormatterDocumentCreate(title="secret", pane="left", content="{}")) - - # u2 forges u1's workspace_id - forged_ctx = _ctx("u2", ws_u1) - docs = await jf_svc.list_documents(forged_ctx) - assert docs == [] # owner_uid filter prevents cross-user bleed diff --git a/apps/backend/tests/test_nosql_workspace_isolation.py b/apps/backend/tests/test_nosql_workspace_isolation.py deleted file mode 100644 index ade00d4e..00000000 --- a/apps/backend/tests/test_nosql_workspace_isolation.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest -from app.api.routes.nosql import services as nosql_svc -from app.api.routes.nosql.schema import ConnectionCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=True, owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_nosql_connections_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await nosql_svc.create_connection(ctx_u1, ConnectionCreate(encryptedData="encrypted-u1", iv="iv-u1", name="conn-u1")) - - connections_u1 = await nosql_svc.list_connections(ctx_u1) - connections_u2 = await nosql_svc.list_connections(ctx_u2) - - assert len(connections_u1) == 1 - assert len(connections_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_nosql_connections_data( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await nosql_svc.create_connection(ctx_u1, ConnectionCreate(encryptedData="encrypted-u1", iv="iv-u1", name="conn-u1")) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - connections = await nosql_svc.list_connections(forged_ctx) - assert connections == [] # owner_uid filter saves us diff --git a/apps/backend/tests/test_notes_workspace_isolation.py b/apps/backend/tests/test_notes_workspace_isolation.py deleted file mode 100644 index 55d8d651..00000000 --- a/apps/backend/tests/test_notes_workspace_isolation.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest -from app.api.routes.notes import services as note_svc -from app.api.routes.notes.schema import NoteCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=True, owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_notes_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await note_svc.create_note(ctx_u1, NoteCreate(title="u1 note")) - - notes_u1 = await note_svc.list_notes(ctx=ctx_u1) - notes_u2 = await note_svc.list_notes(ctx=ctx_u2) - - assert len(notes_u1) == 1 - assert len(notes_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_note_data( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await note_svc.create_note(ctx_u1, NoteCreate(title="u1 secret note")) - - # u2 forges u1's workspace_id but has different uid -> owner_uid filter blocks them - forged_ctx = _ctx("u2", ws_u1) - notes = await note_svc.list_notes(ctx=forged_ctx) - assert notes == [] diff --git a/apps/backend/tests/test_rbac_dependency.py b/apps/backend/tests/test_rbac_dependency.py deleted file mode 100644 index 7f87df18..00000000 --- a/apps/backend/tests/test_rbac_dependency.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from fastapi import HTTPException -from app.api.routes.workspaces.rbac import require_permission -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(role: str, is_personal: bool = False) -> WorkspaceContext: - return WorkspaceContext( - uid="u1", workspace_id="w1", - ws_role=role, is_personal=is_personal, - owner_uid="u1" if is_personal else None, - ) - - -@pytest.mark.asyncio -async def test_personal_workspace_bypasses_matrix(): - dep = require_permission("password-manager", "admin") - ctx = _ctx("admin", is_personal=True) - out = await dep(ctx=ctx) - assert out is ctx - - -@pytest.mark.asyncio -async def test_viewer_blocked_from_writing_notes(): - dep = require_permission("notes", "write") - with pytest.raises(HTTPException) as exc: - await dep(ctx=_ctx("viewer")) - assert exc.value.status_code == 403 - - -@pytest.mark.asyncio -async def test_developer_allowed_to_delete_bookmarks(): - dep = require_permission("bookmarks", "delete") - ctx = _ctx("developer") - out = await dep(ctx=ctx) - assert out is ctx - - -@pytest.mark.asyncio -async def test_encrypted_tool_blocked_for_shared_workspace(): - dep = require_permission("password-manager", "read") - with pytest.raises(HTTPException) as exc: - await dep(ctx=_ctx("admin")) - assert exc.value.status_code == 403 diff --git a/apps/backend/tests/test_rbac_matrix.py b/apps/backend/tests/test_rbac_matrix.py deleted file mode 100644 index 0d7e6a6b..00000000 --- a/apps/backend/tests/test_rbac_matrix.py +++ /dev/null @@ -1,30 +0,0 @@ -from app.api.routes.workspaces.rbac import TOOL_PERMISSIONS, ENCRYPTED_TOOLS - - -def test_admin_has_all_permissions_on_every_tool(): - for tool, by_role in TOOL_PERMISSIONS.items(): - if tool not in ENCRYPTED_TOOLS: - assert by_role["admin"] >= {"read", "write", "delete", "admin"}, tool - - -def test_viewer_can_only_read_plaintext_tools(): - for tool, by_role in TOOL_PERMISSIONS.items(): - if tool in ENCRYPTED_TOOLS: - assert by_role["viewer"] == set() - continue - assert by_role["viewer"] == {"read"} - - -def test_developer_cannot_admin_plaintext_tools(): - for tool, by_role in TOOL_PERMISSIONS.items(): - if tool in ENCRYPTED_TOOLS: - assert by_role["developer"] == set() - continue - assert "admin" not in by_role["developer"] - assert by_role["developer"] >= {"read", "write", "delete"} - - -def test_encrypted_tools_are_gated_for_all_shared_roles(): - for tool in ENCRYPTED_TOOLS: - for role in ("admin", "developer", "viewer"): - assert TOOL_PERMISSIONS[tool][role] == set() diff --git a/apps/backend/tests/test_redis_commander_workspace_isolation.py b/apps/backend/tests/test_redis_commander_workspace_isolation.py deleted file mode 100644 index fb71e02a..00000000 --- a/apps/backend/tests/test_redis_commander_workspace_isolation.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Workspace isolation tests for Redis Commander saved connections. -Mirrors the pattern used in test_s3_drive_workspace_isolation.py. -""" -import pytest - -from app.api.routes.redis_commander import services as redis_svc -from app.api.routes.redis_commander.schema import RedisConnectionCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, - workspace_id=ws_id, - ws_role="admin", - is_personal=True, - owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_redis_connections_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - """Connections created by u1 must not appear in u2's list.""" - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await redis_svc.create_connection( - ctx_u1, RedisConnectionCreate(encryptedData="encrypted-u1", iv="iv-u1", name="conn-u1") - ) - - connections_u1 = await redis_svc.list_connections(ctx_u1) - connections_u2 = await redis_svc.list_connections(ctx_u2) - - assert len(connections_u1) == 1 - assert len(connections_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_redis_connections_data( - clean_db, personal_ws_for, -): - """u2 forging u1's workspace_id still cannot see u1's connections due to owner_uid.""" - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await redis_svc.create_connection( - ctx_u1, RedisConnectionCreate(encryptedData="encrypted-u1", iv="iv-u1", name="conn-u1") - ) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - connections = await redis_svc.list_connections(forged_ctx) - assert connections == [] # owner_uid filter saves us diff --git a/apps/backend/tests/test_routes_use_rbac.py b/apps/backend/tests/test_routes_use_rbac.py deleted file mode 100644 index 4eab00d9..00000000 --- a/apps/backend/tests/test_routes_use_rbac.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Contract test: every scoped route endpoint must use require_permission. - -Heuristic: walk each route's dependant.dependencies looking for a callable -whose qualname contains "require_permission" or whose name is "dep" (the inner -closure returned by require_permission). Falls back to scanning the source -file for the literal string "require_permission(" as a belt-and-suspenders -check. -""" -import importlib -import inspect - -import pytest - -from app.api.router import api_router -from app.api.routes.workspaces.rbac import require_permission - - -# Paths that are intentionally not gated (public / uid-only endpoints). -_PUBLIC_PATH_FRAGMENTS = { - "/r/", # url-shortener public redirect - "/resolve/", # url-shortener public resolve - "/click", # url-shortener click recording (public) - "/public-mock/", # api-client anonymous mock read -} - -# These paths use get_current_uid (uid-scoped, not workspace) — not RBAC. -_UID_ONLY_PATHS = { - "/public-mocks", # api-client owned-mock management (uid, not workspace) -} - - -def _is_public(path: str) -> bool: - for frag in _PUBLIC_PATH_FRAGMENTS: - if frag in path: - return True - return False - - -def _is_uid_only(path: str) -> bool: - for frag in _UID_ONLY_PATHS: - if path.endswith(frag) or frag in path: - return True - return False - - -def _dep_uses_require_permission(dep_call) -> bool: - """Return True if dep_call is the inner 'dep' closure from require_permission.""" - qname = getattr(dep_call, "__qualname__", "") - return "require_permission" in qname or qname.endswith(".dep") - - -def _route_uses_require_permission(route) -> bool: - """Inspect dep graph + source fallback.""" - if not hasattr(route, "dependant"): - return False - for dep in route.dependant.dependencies: - if dep.call and _dep_uses_require_permission(dep.call): - return True - # Fallback: inspect nested dependencies one level deep - for dep in route.dependant.dependencies: - if dep.call and hasattr(dep.call, "__wrapped__"): - inner = dep.call.__wrapped__ - if _dep_uses_require_permission(inner): - return True - return False - - -# Prefixes of the 14 scoped route modules in api_router. -# Note: api_key_vault registers under /api-keys (not /api-key-vault). -SCOPED_PREFIXES = { - "/password-manager", - "/environment-manager", - "/api-keys", # api_key_vault - "/notes", - "/tasks", - "/projects", # tasks sub-router - "/bookmarks", - "/bookmark-folders", # bookmarks sub-router - "/code-snippets", - "/api-client", - "/nosql", - "/sql-client", - "/redis-commander", - "/s3-drive", - "/url-shortener", - "/json-formatter", -} - - -def test_each_scoped_route_uses_require_permission(): - bad: list[str] = [] - - for route in api_router.routes: - path: str = getattr(route, "path", "") - methods = getattr(route, "methods", set()) - - # Skip routes not under our 14 scoped modules - if not any(path.startswith(prefix) for prefix in SCOPED_PREFIXES): - continue - - # Skip known public / anonymous endpoints - if _is_public(path): - continue - - # Skip uid-only endpoints (not workspace-scoped) - if _is_uid_only(path): - continue - - if not _route_uses_require_permission(route): - bad.append(f"{methods} {path}") - - assert not bad, ( - f"\n{len(bad)} route(s) are missing require_permission:\n" - + "\n".join(f" {r}" for r in sorted(bad)) - ) diff --git a/apps/backend/tests/test_s3_drive_workspace_isolation.py b/apps/backend/tests/test_s3_drive_workspace_isolation.py deleted file mode 100644 index c7d4bb3a..00000000 --- a/apps/backend/tests/test_s3_drive_workspace_isolation.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Workspace isolation tests for S3 Drive saved connections. -Mirrors the pattern used in test_sql_client_workspace_isolation.py. -""" -import pytest - -from app.api.routes.s3_drive import services as s3_drive_svc -from app.api.routes.s3_drive.schema import S3ConnectionCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, - workspace_id=ws_id, - ws_role="admin", - is_personal=True, - owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_s3_drive_connections_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - """Connections created by u1 must not appear in u2's list.""" - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await s3_drive_svc.create_connection( - ctx_u1, S3ConnectionCreate(encryptedData="encrypted-u1", iv="iv-u1", name="conn-u1", provider="aws") - ) - - connections_u1 = await s3_drive_svc.list_connections(ctx_u1) - connections_u2 = await s3_drive_svc.list_connections(ctx_u2) - - assert len(connections_u1) == 1 - assert len(connections_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_s3_drive_connections_data( - clean_db, personal_ws_for, -): - """u2 forging u1's workspace_id still cannot see u1's connections due to owner_uid.""" - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await s3_drive_svc.create_connection( - ctx_u1, S3ConnectionCreate(encryptedData="encrypted-u1", iv="iv-u1", name="conn-u1", provider="aws") - ) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - connections = await s3_drive_svc.list_connections(forged_ctx) - assert connections == [] # owner_uid filter saves us diff --git a/apps/backend/tests/test_sql_client_workspace_isolation.py b/apps/backend/tests/test_sql_client_workspace_isolation.py deleted file mode 100644 index f4055697..00000000 --- a/apps/backend/tests/test_sql_client_workspace_isolation.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Workspace isolation tests for SQL Client saved connections. -Mirrors the pattern used in test_nosql_workspace_isolation.py. -""" -import pytest - -from app.api.routes.sql_client import services as sql_client_svc -from app.api.routes.sql_client.schema import SqlConnectionCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, - workspace_id=ws_id, - ws_role="admin", - is_personal=True, - owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_sql_client_connections_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - """Connections created by u1 must not appear in u2's list.""" - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await sql_client_svc.create_connection( - ctx_u1, SqlConnectionCreate(encryptedData="encrypted-u1", iv="iv-u1", name="conn-u1", type="postgresql") - ) - - connections_u1 = await sql_client_svc.list_connections(ctx_u1) - connections_u2 = await sql_client_svc.list_connections(ctx_u2) - - assert len(connections_u1) == 1 - assert len(connections_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_sql_client_connections_data( - clean_db, personal_ws_for, -): - """u2 forging u1's workspace_id still cannot see u1's connections due to owner_uid.""" - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await sql_client_svc.create_connection( - ctx_u1, SqlConnectionCreate(encryptedData="encrypted-u1", iv="iv-u1", name="conn-u1", type="postgresql") - ) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - connections = await sql_client_svc.list_connections(forged_ctx) - assert connections == [] # owner_uid filter saves us diff --git a/apps/backend/tests/test_tasks_workspace_isolation.py b/apps/backend/tests/test_tasks_workspace_isolation.py deleted file mode 100644 index ea29c2f4..00000000 --- a/apps/backend/tests/test_tasks_workspace_isolation.py +++ /dev/null @@ -1,142 +0,0 @@ -import pytest -from app.api.routes.tasks import services as task_svc -from app.api.routes.tasks.schema import ProjectCreate, TaskCreate, TaskStatusUpdate, TaskUpdate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=True, owner_uid=uid, - ) - - -def _shared_ctx(uid: str, ws_id: str) -> WorkspaceContext: - """A shared (non-personal) workspace context — no owner_uid lock, so any - member with a writer role can mutate any task in the workspace.""" - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=False, owner_uid=None, - ) - - -@pytest.mark.asyncio -async def test_tasks_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await task_svc.create_task(ctx_u1, TaskCreate(text="u1 task")) - - result_u1 = await task_svc.list_tasks(ctx=ctx_u1) - result_u2 = await task_svc.list_tasks(ctx=ctx_u2) - - assert len(result_u1.items) == 1 - assert len(result_u2.items) == 0 - - -@pytest.mark.asyncio -async def test_projects_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await task_svc.create_project(ctx_u1, ProjectCreate(name="u1 project", color="#ff0000")) - - projects_u1 = await task_svc.list_projects(ctx=ctx_u1) - projects_u2 = await task_svc.list_projects(ctx=ctx_u2) - - assert len(projects_u1) == 1 - assert len(projects_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_task_data( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await task_svc.create_task(ctx_u1, TaskCreate(text="u1 secret task")) - - # u2 forges u1's workspace_id but has different uid → owner_uid filter blocks them - forged_ctx = _ctx("u2", ws_u1) - result = await task_svc.list_tasks(ctx=forged_ctx) - assert result.items == [] - - -@pytest.mark.asyncio -async def test_shared_workspace_member_can_edit_anothers_task( - clean_db, -): - """Collaboration unlock: in a shared workspace, member B can edit / move / - delete a task member A created (was blocked by the old created_by filter).""" - ws = "ws-shared-1" - ctx_a = _shared_ctx("u1", ws) - ctx_b = _shared_ctx("u2", ws) - - created = await task_svc.create_task(ctx_a, TaskCreate(text="shared task")) - - # B sees it - listed = await task_svc.list_tasks(ctx=ctx_b) - assert [t.id for t in listed.items] == [created.id] - - # B assigns it to A and renames it - updated = await task_svc.update_task( - ctx_b, created.id, TaskUpdate(text="renamed by B", assigneeUid="u1") - ) - assert updated.text == "renamed by B" - assert updated.assigneeUid == "u1" - - # B moves it across the board - moved = await task_svc.update_task_status( - ctx_b, created.id, TaskStatusUpdate(status="completed") - ) - assert moved.status == "completed" - - # B deletes it - await task_svc.delete_task(ctx_b, created.id) - assert (await task_svc.list_tasks(ctx=ctx_a)).items == [] - - -@pytest.mark.asyncio -async def test_shared_workspace_isolated_from_other_shared_workspace( - clean_db, -): - """A shared workspace's tasks never leak into a different workspace.""" - ctx_a = _shared_ctx("u1", "ws-shared-A") - ctx_other = _shared_ctx("u1", "ws-shared-B") - - await task_svc.create_task(ctx_a, TaskCreate(text="A-only")) - assert (await task_svc.list_tasks(ctx=ctx_other)).items == [] - - -@pytest.mark.asyncio -async def test_assignee_filter(clean_db): - """list_tasks assignee filter supports me / unassigned / specific uid.""" - ctx = _shared_ctx("u1", "ws-assignee") - - await task_svc.create_task(ctx, TaskCreate(text="assigned to u2", assigneeUid="u2")) - await task_svc.create_task(ctx, TaskCreate(text="unassigned")) - await task_svc.create_task(ctx, TaskCreate(text="mine", assigneeUid="u1")) - - all_tasks = await task_svc.list_tasks(ctx=ctx) - assert all_tasks.total == 3 - - mine = await task_svc.list_tasks(ctx=ctx, assignee_filter="me") - assert [t.text for t in mine.items] == ["mine"] - - u2 = await task_svc.list_tasks(ctx=ctx, assignee_filter="u2") - assert [t.text for t in u2.items] == ["assigned to u2"] - - unassigned = await task_svc.list_tasks(ctx=ctx, assignee_filter="unassigned") - assert [t.text for t in unassigned.items] == ["unassigned"] diff --git a/apps/backend/tests/test_url_shortener_workspace_isolation.py b/apps/backend/tests/test_url_shortener_workspace_isolation.py deleted file mode 100644 index cbcd18b0..00000000 --- a/apps/backend/tests/test_url_shortener_workspace_isolation.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Workspace isolation tests for URL shortener (URL_LINKS and URL_CLICK_EVENTS). - -Tests ensure that: -1. Links created by u1 are not visible to u2 across workspace boundaries. -2. Forged workspace_id cannot bypass owner_uid isolation. -3. Click events inherit the link owner's workspace stamps. -""" -import pytest - -from app.api.routes.url_shortener import services as url_svc -from app.api.routes.url_shortener.schema import ShortLinkCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, - workspace_id=ws_id, - ws_role="admin", - is_personal=True, - owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_url_shortener_links_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - """Links created by u1 must not appear in u2's list.""" - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - # u1 creates a link - await url_svc.create_link(ctx_u1, ShortLinkCreate(original_url="https://example.com")) - - # u1 sees their link - links_u1 = await url_svc.list_my_short_urls(ctx=ctx_u1) - assert len(links_u1) == 1 - assert links_u1[0].original_url == "https://example.com" - - # u2 does not see u1's link - links_u2 = await url_svc.list_my_short_urls(ctx=ctx_u2) - assert len(links_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_cannot_read_url_shortener_links( - clean_db, personal_ws_for, -): - """u2 forging u1's workspace_id still cannot see u1's links due to owner_uid.""" - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - await url_svc.create_link(ctx_u1, ShortLinkCreate(original_url="https://secret.com")) - - # u2 forges u1's workspace_id - forged_ctx = _ctx("u2", ws_u1) - links = await url_svc.list_my_short_urls(ctx=forged_ctx) - assert links == [] # owner_uid filter prevents cross-user bleed - - -@pytest.mark.asyncio -async def test_click_events_inherit_link_workspace_stamps( - clean_db, personal_ws_for, -): - """Click events must inherit the link's workspace_id, owner_uid stamps.""" - ws_u1 = await personal_ws_for("u1") - - ctx_u1 = _ctx("u1", ws_u1) - - # u1 creates a link - link = await url_svc.create_link(ctx_u1, ShortLinkCreate(original_url="https://example.com")) - code = link.code - - # Record a click (without workspace context, as it's public) - await url_svc.record_click(code, ua="Mozilla/5.0", referrer="https://google.com") - - # Query click events by code to verify stamps were inherited - from app.database import db_manager - from app.api.routes.url_shortener.schema import COLLECTION, CLICKS_COLLECTION - - # Verify the link has the correct stamps - link_doc = await db_manager.find_one(COLLECTION, {"_id": code}) - assert link_doc is not None - assert link_doc["workspace_id"] == ws_u1 - assert link_doc["owner_uid"] == "u1" - - # Verify clicks inherited the same stamps - click_events = await db_manager.find(CLICKS_COLLECTION, {"code": code}, limit=10) - assert len(click_events) > 0 - for event in click_events: - assert event.get("workspace_id") == ws_u1 - assert event.get("owner_uid") == "u1" diff --git a/apps/backend/tests/test_user_keypair.py b/apps/backend/tests/test_user_keypair.py deleted file mode 100644 index 2e166db5..00000000 --- a/apps/backend/tests/test_user_keypair.py +++ /dev/null @@ -1,29 +0,0 @@ -import pytest -from httpx import AsyncClient - - -@pytest.mark.asyncio -async def test_get_keypair_returns_null_when_unset(authed_client: AsyncClient): - res = await authed_client.get("/api/v1/workspaces-api/users/me/keypair") - assert res.status_code == 200 - assert res.json() is None - - -@pytest.mark.asyncio -async def test_post_then_get_keypair_roundtrip(authed_client: AsyncClient): - payload = { - "publicKey": "base64-pub-key", - "privateKeyEncrypted": {"encrypted": "enc-data", "iv": "iv-bytes"}, - "salt": "base64-salt", - } - post = await authed_client.post( - "/api/v1/workspaces-api/users/me/keypair", json=payload, - ) - assert post.status_code == 204 - get = await authed_client.get("/api/v1/workspaces-api/users/me/keypair") - assert get.status_code == 200 - body = get.json() - assert body["publicKey"] == "base64-pub-key" - assert body["privateKeyEncrypted"]["encrypted"] == "enc-data" - assert body["salt"] == "base64-salt" - assert body["createdAt"] > 0 diff --git a/apps/backend/tests/test_workspace_ctx.py b/apps/backend/tests/test_workspace_ctx.py deleted file mode 100644 index 951e744b..00000000 --- a/apps/backend/tests/test_workspace_ctx.py +++ /dev/null @@ -1,105 +0,0 @@ -import pytest -from fastapi import HTTPException -from app.api.routes.workspaces.middleware import ( - WorkspaceContext, - apply_legacy_or_filter, - apply_workspace_filter, - default_personal_ws_id, - get_workspace_ctx, -) -from app.api.routes.workspaces.repo import ( - upsert_personal_workspace, - upsert_ws_membership, -) - - -@pytest.mark.asyncio -async def test_get_workspace_ctx_rejects_non_member(make_request, clean_db): - ws_id = await upsert_personal_workspace("owner-uid") - await upsert_ws_membership(ws_id, "owner-uid", "admin") - - req = make_request(cookies={"active_workspace": ws_id}) - with pytest.raises(HTTPException) as exc: - await get_workspace_ctx(req, uid="someone-else") - assert exc.value.status_code == 403 - - -@pytest.mark.asyncio -async def test_default_personal_ws_id_returns_personal_ws(clean_db): - ws_id = await upsert_personal_workspace("u1") - await upsert_ws_membership(ws_id, "u1", "admin") - - assert await default_personal_ws_id("u1") == ws_id - - -def test_apply_workspace_filter_personal_adds_owner_uid(): - ctx = WorkspaceContext( - uid="u1", - workspace_id="w1", - ws_role="admin", - is_personal=True, - owner_uid="u1", - ) - out = apply_workspace_filter(ctx, {"foo": "bar"}) - assert out == { - "foo": "bar", - "workspace_id": "w1", - "owner_uid": "u1", - } - - -def test_apply_legacy_or_filter_bounds_by_user_field(): - ctx = WorkspaceContext( - uid="u1", - workspace_id="w1", - ws_role="admin", - is_personal=True, - owner_uid="u1", - ) - out = apply_legacy_or_filter(ctx, {"foo": "bar"}, user_field="created_by") - assert out["$or"] == [ - {"workspace_id": "w1", "owner_uid": "u1"}, - {"workspace_id": {"$exists": False}, "created_by": "u1"}, - ] - assert out["foo"] == "bar" - - -def test_apply_legacy_or_filter_preserves_caller_or(): - ctx = WorkspaceContext( - uid="u1", - workspace_id="w1", - ws_role="admin", - is_personal=True, - owner_uid="u1", - ) - base = {"$or": [{"folderId": None}, {"folderId": {"$exists": False}}]} - out = apply_legacy_or_filter(ctx, base, user_field="created_by") - # Caller's $or must be preserved — combined via $and with workspace $or. - assert "$and" in out - assert out["$and"][0] == base - assert out["$and"][1]["$or"] == [ - {"workspace_id": "w1", "owner_uid": "u1"}, - {"workspace_id": {"$exists": False}, "created_by": "u1"}, - ] - - -@pytest.mark.asyncio -async def test_admin_member_gets_workspace_ctx(clean_db, make_request): - ws_id = await upsert_personal_workspace("owner-uid") - await upsert_ws_membership(ws_id, "owner-uid", "admin") - - req = make_request(cookies={"active_workspace": ws_id}) - ctx = await get_workspace_ctx(req, uid="owner-uid") - assert ctx.ws_role == "admin" - assert ctx.workspace_id == ws_id - - -@pytest.mark.asyncio -async def test_non_member_without_ws_membership_is_rejected(clean_db, make_request): - ws_id = await upsert_personal_workspace("owner-uid") - await upsert_ws_membership(ws_id, "owner-uid", "admin") - - req = make_request(cookies={"active_workspace": ws_id}) - with pytest.raises(HTTPException) as exc: - await get_workspace_ctx(req, uid="plain-member") - assert exc.value.status_code == 403 diff --git a/apps/backend/tests/test_workspace_invariant.py b/apps/backend/tests/test_workspace_invariant.py deleted file mode 100644 index 37d1a12b..00000000 --- a/apps/backend/tests/test_workspace_invariant.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest -from app.api.routes.passwords import services as pw_svc -from app.api.routes.passwords.schema import PasswordEntryCreate -from app.api.routes.workspaces.middleware import WorkspaceContext - - -def _ctx(uid: str, ws_id: str) -> WorkspaceContext: - return WorkspaceContext( - uid=uid, workspace_id=ws_id, ws_role="admin", - is_personal=True, owner_uid=uid, - ) - - -@pytest.mark.asyncio -async def test_password_entries_are_isolated_across_personal_workspaces( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - ws_u2 = await personal_ws_for("u2") - - ctx_u1 = _ctx("u1", ws_u1) - ctx_u2 = _ctx("u2", ws_u2) - - await pw_svc.create_entry( - ctx_u1, PasswordEntryCreate(encryptedData="enc-u1", iv="iv-u1"), - ) - - entries_u1 = await pw_svc.list_entries(ctx=ctx_u1) - entries_u2 = await pw_svc.list_entries(ctx=ctx_u2) - - assert len(entries_u1) == 1 - assert len(entries_u2) == 0 - - -@pytest.mark.asyncio -async def test_forged_workspace_id_cannot_cross_user_data( - clean_db, personal_ws_for, -): - ws_u1 = await personal_ws_for("u1") - await personal_ws_for("u2") - - await pw_svc.create_entry( - _ctx("u1", ws_u1), - PasswordEntryCreate(encryptedData="enc-u1", iv="iv-u1"), - ) - - forged_ctx = _ctx("u2", ws_u1) # u2 forges u1's workspace_id - entries = await pw_svc.list_entries(ctx=forged_ctx) - assert entries == [] # owner_uid filter saves us diff --git a/apps/backend/tests/test_workspace_routes.py b/apps/backend/tests/test_workspace_routes.py deleted file mode 100644 index 1275534a..00000000 --- a/apps/backend/tests/test_workspace_routes.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest -from httpx import AsyncClient - - -@pytest.mark.asyncio -async def test_list_workspaces_returns_personal(authed_client: AsyncClient): - res = await authed_client.get("/api/v1/workspaces-api/workspaces") - assert res.status_code == 200 - workspaces = res.json() - assert len(workspaces) == 1 - assert workspaces[0]["is_personal"] is True - assert workspaces[0]["name"] == "Personal" - assert workspaces[0]["ws_role"] == "admin" - - -@pytest.mark.asyncio -async def test_set_active_workspace_validates_membership(authed_client: AsyncClient): - res = await authed_client.post( - "/api/v1/workspaces-api/workspaces/active", - json={"workspace_id": "bogus-id"}, - ) - assert res.status_code == 403 - - -@pytest.mark.asyncio -async def test_set_active_workspace_sets_cookie(authed_client: AsyncClient, personal_ws_id: str): - res = await authed_client.post( - "/api/v1/workspaces-api/workspaces/active", - json={"workspace_id": personal_ws_id}, - ) - assert res.status_code == 200 - assert res.cookies.get("active_workspace") == personal_ws_id diff --git a/apps/desktop-ui/components.json b/apps/desktop-ui/components.json new file mode 100644 index 00000000..a5777073 --- /dev/null +++ b/apps/desktop-ui/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/apps/desktop-ui/eslint.config.mjs b/apps/desktop-ui/eslint.config.mjs new file mode 100644 index 00000000..4ba85873 --- /dev/null +++ b/apps/desktop-ui/eslint.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTypeScript from "eslint-config-next/typescript"; + +export default defineConfig([ + ...nextVitals, + ...nextTypeScript, + globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]), +]); diff --git a/apps/web/jest.config.js b/apps/desktop-ui/jest.config.js similarity index 100% rename from apps/web/jest.config.js rename to apps/desktop-ui/jest.config.js diff --git a/apps/web/jest.setup.js b/apps/desktop-ui/jest.setup.js similarity index 100% rename from apps/web/jest.setup.js rename to apps/desktop-ui/jest.setup.js diff --git a/apps/desktop-ui/messages/af.json b/apps/desktop-ui/messages/af.json new file mode 100644 index 00000000..64f9d9ce --- /dev/null +++ b/apps/desktop-ui/messages/af.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Paneelbord", + "settings": "Instellings", + "apiClient": "API-kliënt", + "httpStatusCodes": "HTTP-statuskodes", + "passwordManager": "Wagwoordbestuurder", + "environmentManager": "Omgewingbestuurder", + "bookmarks": "Boekmerke", + "jsonFormatter": "JSON-formateerder", + "emailValidator": "E-posvalidasie", + "nosqlExplorer": "Databasis Verkenner", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Enkripsie-speelgrond", + "jwtDecoder": "JWT-dekodeerder", + "certificatePemDecoder": "Sertifikaat / PEM-dekodeerder", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL-formateerder", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL-ontleder", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Geheim / API-sleutel", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Kontrasttoetser", + "jsonSchemaGenerator": "JSON-skemagenerator", + "imageToBase64": "Beeld na Base64", + "imageCompressor": "Beeldkompressor", + "cssGradientBuilder": "CSS Gradient Builder", + "gitignoreGenerator": ".gitignore Generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Kodesnippette", + "qrCodeGenerator": "QR-kodegenerator", + "dnsLookup": "DNS-soektog", + "ipSubnetCalculator": "IP-/subnetrekenaar", + "hashGenerator": "Hash-generator", + "hmacGenerator": "HMAC-generator", + "totpGenerator": "TOTP / 2FA-kode", + "markdownPreview": "Markdown-voorskou", + "numberBaseConverter": "Basisomskakelaar", + "formatConverter": "Formaat Omskakelaer", + "mockDataGenerator": "Skyn-datagenerator", + "svgOptimizer": "SVG-optimaliseerder", + "unitConverter": "Eenheidsomskakelaar", + "dockerComposeGenerator": "Docker Compose-Generator", + "breakRoom": "Breek Kamer", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Bestuurder", + "yamlFormatter": "YAML-formatteerder", + "sshKeyGenerator": "SSH / RSA-sleutelgenerator", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod-sakrekenaar", + "codeScreenshot": "Kode-skermkiekie", + "cssGenerators": "CSS-genereerders", + "exifViewer": "EXIF-kyker", + "faviconGenerator": "Favicon-genereerder", + "jsonDiff": "JSON-verskil", + "jsonpathPlayground": "JSONPath-speelperk", + "keycodeInspector": "Sleutelkode-inspekteur", + "markdownTableGenerator": "Markdown-tabel", + "timezoneConverter": "Tydsone-omskakelaar", + "tokenCounter": "Token-teller", + "webhookTester": "Webhook-toetser", + "websocketTester": "WebSocket-toetser", + "whoisLookup": "Whois-opsoek" + }, + "Help": { + "title": "Hulp en dokumentasie", + "subtitle": "Hoe MyDevTools saamwerk, wat elke app doen, en hoe sekuriteit en enkripsie jou data beskerm.", + "tabs": { + "overview": "Oorsig", + "apps": "Apps", + "security": "Sekuriteit", + "tips": "Wenke" + }, + "overview": { + "whatIs": { + "title": "Wat is MyDevTools?", + "description": "’n Ontwikkelaar-werkspasie met aanmelding: ’n paneelbord en ’n stel gereedskap vir alledaagse take.", + "p1": "Nadat jy aanmeld, vestig die app ’n sessie met die MyDevTools API sodat gereedskap jou data kan laai en stoor. Die sybalk wys elke app; jy kan gereedskap wat jy nie gebruik nie wegsteek in", + "settingsLink": "Instellings", + "p1After": ".", + "p2": "Sommige funksies gebruik ’n meesterwagwoord (of globale kluis-sleutel) sodat geheime soos wagwoorde en verbindingsstringe op jou toestel geënkripteer word voordat dit die bediener bereik." + }, + "quickLinks": { + "title": "Vinnige skakels", + "dashboard": "Paneelbord", + "settings": "Instellings" + } + }, + "apps": { + "intro": "Maak enige hulpmiddel oop vanaf die sybalk of paneelbord. Hieronder is ’n kort gids per app.", + "open": "Maak oop", + "howItWorks": "Hoe dit werk", + "generic": "Gebruik hierdie hulpmiddel vanaf die sybalk vir sy toegewyde werkvloei." + }, + "security": { + "session": { + "title": "Aanmelding en API-sessie", + "description": "Identiteit en hoe die blaaier met jou backend praat.", + "items": { + "authentication": "Verifikasie gebruik Firebase vir aanmelding. Ná login ruil die app jou Firebase ID-token vir API-koekies.", + "accessTokens": "Toegang-tokens is JWT’s wat op die bediener geteken word (HS256) en bewys watter gebruiker die API oproep.", + "refreshTokens": "Verfris-tokens word in HttpOnly-koekies gestoor; die bediener stoor ’n SHA-256 hash van die token, nie die rou token nie." + } + }, + "encryption": { + "title": "Enkripsie om te ken", + "description": "Kliënt-kant kripto vir die kluis en sensitiewe verbindingsdata.", + "p1": "Vir die wagwoordbestuurder, omgewingbestuurder en gestoorde koppelings in NoSQL Explorer gebruik die webtoepassing die blaaier se Web Crypto API:", + "items": { + "kdf": "PBKDF2 met SHA-256, 100 000 iterasies en ’n ewekansige sout lei ’n 256-bis sleutel uit jou meesterwagwoord af.", + "aes": "AES-256-GCM enkripteer data; ’n ewekansige 12-byte IV word per enkripsie gebruik.", + "scope": "Die afgeleide sleutel bly in die blaaier (nie-uittrekbaar). Die bediener ontvang slegs ciphertext en IV’s, nie jou meesterwagwoord of plaintext geheime nie." + }, + "warning": "As jy die meesterwagwoord vergeet, kan geënkripteerde data nie herstel word nie. Bêre dit veilig." + }, + "logout": { + "title": "Afmeld en plaaslike data", + "body": "Afmeld maak geheime in geheue skoon, kluis-verwante toestand, en meester-sleutelmateriaal uit IndexedDB waar van toepassing, en beëindig die API-sessie. Data wat net in plaaslike berging is kan bly totdat jy dit in die blaaier uitvee." + } + }, + "tips": { + "title": "Gebruik MyDevTools veilig", + "items": { + "masterPassword": "Gebruik ’n sterk, unieke meesterwagwoord vir kluis-funksies.", + "apiClient": "Behandel die API-kliënt soos enige HTTP-kliënt: moenie produksie-geheime in gedeelde omgewings plak as jy nie weet wie dit kan sien nie.", + "nosql": "Databasis Verkenner koppel aan databasisse wat jy konfigureer; gebruik minste-voorreg gebruikers en netwerkreëls waar moontlik.", + "json": "JSON-formateerder werk plaaslik in die blaaier—vermy steeds om baie sensitiewe data op gedeelde masjiene te plak.", + "settings": "Pas sigbare gereedskap in Instellings aan om die sybalk gefokus te hou." + } + } + }, + "Settings": { + "title": "Instellings", + "language": "Taal", + "spanish": "Spaans", + "english": "Engels", + "french": "Frans", + "arabic": "عربى", + "catalan": "Katalaans", + "chineseSimplified": "Chinees (Vereenvoudig)", + "czech": "Čeština", + "greek": "Ελληνικά", + "afrikaans": "Afrikaans", + "german": "Duits", + "danish": "Deens", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Oekraïens" + }, + "SettingsPage": { + "title": "Instellings", + "subtitle": "Bestuur jou rekeninginstellings en voorkeure.", + "userProfile": { + "title": "Gebruikerprofiel", + "description": "Jou persoonlike inligting en rekeningbesonderhede.", + "avatarAlt": "Gebruiker", + "anonymousUser": "Anonieme gebruiker", + "noEmailProvided": "Geen e-pos verskaf nie", + "notLoggedIn": "Nie aangemeld nie." + }, + "tools": { + "title": "Gereedskap en funksies", + "description": "Kies watter gereedskap in jou navigasiesybalk verskyn." + }, + "appearance": { + "title": "Voorkoms", + "description": "Pas aan hoe die toepassing op jou toestel lyk.", + "themePreference": "Temavoorkeur", + "themes": { + "light": "Lig", + "dark": "Donker", + "system": "Stelsel" + } + }, + "language": { + "title": "Taal", + "description": "Stel die taal wat oor die platform gebruik word.", + "selectLanguage": "Kies taal", + "selectPlaceholder": "Kies 'n taal", + "helpText": "As jy jou taal verander, word kieslyste en etikette oor die platform bygewerk.", + "languages": { + "en": "English", + "fr": "Français (Frans)", + "es": "Español (Spaans)", + "ar": "عربى (Arabies)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Grieks)", + "af": "Afrikaans", + "de": "Deutsch (Duits)", + "da": "Dansk (Deens)", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italiaans (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Goeie more", + "afternoon": "Goeie middag", + "evening": "Goeie aand" + }, + "commaName": ", {name}", + "welcomeBack": "Welkom terug", + "welcomeBackNamed": "Welkom terug, {name}", + "tagline": "Wat wil jy vandag bou?", + "brandName": "MyDevTools", + "stats": { + "tools": "Nutsgoed", + "favorites": "Gunstelinge", + "pinned": "Vasgespeld", + "recent": "Onlangs gebruik" + }, + "sections": { + "recentlyUsed": "Onlangs gebruik", + "favorites": "Gunstelinge", + "whatsNew": "Wat is nuut", + "pinned": "Vasgespeld" + }, + "groups": { + "apps": "Toeps" + }, + "toolCard": { + "defaultDescription": "Verken hierdie nutsmiddel vir beter funksionaliteit.", + "launchTool": "Maak nutsmiddel oop" + }, + "tools": { + "toDo": { + "title": "Take", + "description": "Bestuur jou to-do-lyste doeltreffend." + }, + "notes": { + "title": "Notas", + "description": "Vang idees en organiseer jou gedagtes." + }, + "passwordManager": { + "title": "Wagwoordbestuurder", + "description": "Berg en bestuur wagwoorde veilig." + }, + "environmentManager": { + "title": "Omgewingbestuurder", + "description": "Stoor .env-tipe veranderlikes per projek en omgewing met etikette." + }, + "bookmarks": { + "title": "Boekmerke", + "description": "Bestuur en organiseer jou boekmerke." + }, + "apiClient": { + "title": "API-kliënt", + "description": "Toets en ontfout HTTP-versoeke." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Verken MongoDB-databasisse." + }, + "emailValidator": { + "title": "E-posvalidator", + "description": "Verifieer en valideer e-posadresse." + }, + "encryptionPlayground": { + "title": "Enkripsie-speelgrond", + "description": "AES-GCM met rou sleutel of wagfrase; JSON-bondels, Web Crypto net in die blaaier." + }, + "jwtDecoder": { + "title": "JWT-dekodeerder", + "description": "Dekodeer JWT-kop, payload en vervaldatum." + }, + "certificatePemDecoder": { + "title": "Sertifikaat / PEM-dekodeerder", + "description": "Dekodeer X.509-sertifikate en CSRs: onderwerp, uitreiker, geldigheid, SANs." + }, + "sshKeyGenerator": { + "title": "SSH / RSA-sleutelgenerator", + "description": "Genereer Ed25519- of RSA-sleutelpare in die blaaier. Laai af in OpenSSH- en PEM-formaat." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL-formateerder", + "description": "Formateer GraphQL met sintaksuitligting en ’n eenvoudige navrae-bouer." + }, + "yamlFormatter": { + "title": "YAML Formatteerder", + "description": "Formateer, valideer en omskep YAML na JSON in die blaaier." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL-ontleder", + "description": "Breek ’n URL af in protokol, gasheer, pad, navraagparameters en hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Geheim / API-sleutel", + "description": "Kriptografies ewekansige stringe met aanpasbare alfabet, lengte en massa-uitvoer." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Kontrasttoetser", + "description": "WCAG 2.1-kontrastverhouding tussen teks en agtergrond (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "JSON-skemagenerator", + "description": "Lei 'n JSON-skema en getiketteerde modelle uit voorbeeld-JSON af." + }, + "imageToBase64": { + "title": "Beeld na Base64", + "description": "Skakel beelde om na Data URI of rou Base64-stringe." + }, + "imageCompressor": { + "title": "Beeldkompressor", + "description": "Komprimeer JPEG, PNG en WebP plaaslik met ’n kwaliteitsglyer." + }, + "cssGradientBuilder": { + "title": "CSS Gradient Builder", + "description": "Visuele CSS-gradiëntbouer met hoekbeheer en kleurstops." + }, + "gitignoreGenerator": { + "title": ".gitignore Generator", + "description": "Genereer saamgevoegde .gitignore-lêers onmiddellik vanaf 500+ tegnologiese stapels." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Skakel CSV- of Excel-lêers na JSON en voer terug uit na CSV of XLSX." + }, + "snippetManager": { + "title": "Kodesnippette", + "description": "Bewaar snippette met sintaks-beklemtoning, outomatiese taalopsporing en formatering." + }, + "qrCodeGenerator": { + "title": "QR-kodegenerator", + "description": "Skep PNG QR-kodes uit teks of URL's in jou blaaier." + }, + "ipSubnetCalculator": { + "title": "IP-/subnetrekenaar", + "description": "IPv4 en IPv6 CIDR: netmasker, reekse en adresse." + }, + "dnsLookup": { + "title": "DNS-soektog", + "description": "Navraag DNS-rekords vir enige domein — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Hash-generator", + "description": "MD5, SHA-1 en SHA-2 vir teks of lêers." + }, + "hmacGenerator": { + "title": "HMAC-generator", + "description": "Teken webhooks en toets API-integrasies met HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "TOTP / 2FA-kodegenerator", + "description": "Plak 'n Base32-geheim vanaf die verifikasie-app; die sesyfer-kode verfris elke 30 s—nuttig om aanmelding en MFA te toets." + }, + "markdownPreview": { + "title": "Markdown-voorskou", + "description": "Lewendige Markdown-weergawer met HTML-uitvoer en HTML → Markdown-omskakeling." + }, + "numberBaseConverter": { + "title": "Basisomskakelaar", + "description": "Skakel heelgetalle tussen basisse 2–36 (binêr, desimaal, heks, ens.)." + }, + "formatConverter": { + "title": "Formaat Omskakelaar", + "description": "Skakel tussen JSON, YAML, TOML en XML in enige rigting. Alle 12 kombinasies word ondersteun." + }, + "mockDataGenerator": { + "title": "Skyn-datagenerator", + "description": "Definieer kolomme, kies datatipes en voer datastelle uit vir saad, Postman of toetse. Tot 5000 rye, alles in die blaaier — niks word opgelaai nie." + }, + "httpStatusCodes": { + "title": "HTTP-statuskodes", + "description": "Soek HTTP-statuskodes met beskrywings (1xx–5xx)." + }, + "svgOptimizer": { + "title": "SVG-optimaliseerder", + "description": "Plak SVG, verwyder rommel met SVGO en sien UTF-8-groottebesparing." + }, + "unitConverter": { + "title": "Eenheidsomskakelaar", + "description": "Skakel tussen 323 eenhede oor 43 wetenskaplike en ingenieurskategorieë." + }, + "dockerComposeGenerator": { + "title": "Docker Compose-Generator", + "description": "Kies databasisse, kasse, toue, proxy's en waarneembaarheidbeelde; voer docker-compose.yml uit vir plaaslike stapel." + }, + "game2048": { + "title": "2048", + "description": "Skuif en saamsmelt teëls om 2048 te bereik. Klassieke nommer legkaart spel." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klassieke getal legkaart. 3 moeilikheidsgraad, 150 stadiums elk." + }, + "redisCommander": { + "title": "Redis Bestuurder", + "description": "Blaai deur sleutels, inspekteer waardes en voer opdragte op Redis uit." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt-genereerder en -toetser", + "description": "Hash wagwoorde met bcrypt en verifieer bestaande hashe." + }, + "chmodCalculator": { + "title": "Chmod-sakrekenaar", + "description": "Skakel Unix-regte om tussen oktaal, simbolies en merkblokkies." + }, + "codeScreenshot": { + "title": "Kode-skermkiekie", + "description": "Verander bronkode in pragtige, deelbare beelde." + }, + "cssGenerators": { + "title": "CSS-genereerders", + "description": "box-shadow-, border-radius- en vloeibare clamp()-genereerders." + }, + "exifViewer": { + "title": "EXIF-kyker en -verwyderaar", + "description": "Inspekteer en verwyder foto-EXIF-metadata plaaslik." + }, + "faviconGenerator": { + "title": "Favicon-genereerder", + "description": "Genereer favicons, app-ikone en manifest-snippers." + }, + "jsonDiff": { + "title": "JSON-verskil", + "description": "Semantiese, struktuurbewuste verskil van twee JSON-dokumente." + }, + "jsonpathPlayground": { + "title": "JSONPath-speelperk", + "description": "Bevraag JSON met JSONPath of JMESPath en sien lewendige resultate." + }, + "keycodeInspector": { + "title": "Sleutelkode-inspekteur", + "description": "Druk 'n sleutel om sy JavaScript-gebeurteniskodes en wysigers te sien." + }, + "markdownTableGenerator": { + "title": "Markdown-tabelgenereerder", + "description": "Bou Markdown- en HTML-tabelle in 'n sigblad-agtige roosterredigeerder." + }, + "timezoneConverter": { + "title": "Tydsone-omskakelaar", + "description": "Skakel 'n tyd oor baie sones om met dagverskuiwing- en werksure-leidrade." + }, + "tokenCounter": { + "title": "LLM-token-teller", + "description": "Tel LLM-tokens, visualiseer brokke en skat API-koste." + }, + "webhookTester": { + "title": "Webhook-toetser", + "description": "Vang en inspekteer inkomende webhook-versoeke intyds." + }, + "websocketTester": { + "title": "WebSocket-toetser", + "description": "Koppel aan WebSocket-eindpunte en ruil boodskappe intyds uit." + }, + "whoisLookup": { + "title": "Whois-opsoek", + "description": "Soek domein- en IP-registrasiebesonderhede op via RDAP." + } + }, + "tabs": { + "apps": "Apps", + "analytics": "Ontleding", + "ui": "Koppelvlak" + }, + "analytics": { + "title": "Jou data in ’n oogopslag", + "subtitle": "Tellinge gesinkroniseer met jou rekening op die bediener.", + "loadError": "Kon nie ontleding laai nie.", + "retry": "Probeer weer", + "loginHint": "Meld aan om gebruik-ontleding en koppelvlakvoorkeure te sien.", + "passwordEntries": "Wagwoord-inskrywings", + "bookmarks": "Boekmerke", + "bookmarkFolders": "Boekmerkvouers", + "tasksTotal": "Take", + "tasksCompleted": "Voltooi", + "tasksOngoing": "Aan die gang", + "tasksNotStarted": "Nie begin nie", + "projects": "Projekte", + "nosqlConnections": "DB-verbindings", + "notes": "Notas", + "apiClientCollections": "API-versamelings", + "apiClientEnvironments": "API-omgewings", + "apiClientHistory": "API-geskiedenisinskrywings", + "jsonFormatterDocuments": "JSON-formateerderdokumente", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Kodesnippette", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Koppelvlakvoorkeure", + "subtitle": "Tema, taal en sigbare gereedskap in die sybalk — vanaf jou rekeninginstellings.", + "loadError": "Kon nie voorkeure laai nie.", + "loginHint": "Meld aan om gestoorde koppelvlakvoorkeure te sien.", + "theme": "Tema", + "accent": "Aksentkleur", + "locale": "Taal", + "enabledTools": "Sigbare gereedskap", + "favoriteTools": "Gunsteling-gereedskap", + "toolsWithUsage": "Gereedskap met gebruikstatistiek" + }, + "signIn": "Meld aan", + "noResults": "Geen gereedskap stem ooreen met jou soektog nie", + "noResultsHint": "Probeer 'n ander sleutelwoord of maak die filter skoon.", + "filterAll": "Alles", + "loginCta": { + "description": "Meld aan om jou onlangs gebruikte gereedskap te volg." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Besig om te laai...", + "myTasksTitle": "My take", + "statsLine": "{total} take • {percent}% voltooi", + "statsLineDesktop": "{total} totaal • {percent}% voltooi" + }, + "filters": { + "all": "Alles", + "total": "Totaal", + "allProjects": "Alle projekte", + "export": "Uitvoer", + "kanban": "Kanban", + "list": "Lys", + "searchPlaceholder": "Soek take...", + "searchAria": "Soek take", + "clearSearchAria": "Maak soektog skoon", + "foundTasks": "{count} {count, plural, one {taak} other {take}} gevind", + "filterByAllAria": "Filter volgens alles", + "filterByStatusAria": "Filter volgens {status}" + }, + "status": { + "not-started": { + "label": "Nie begin nie", + "description": "Take wat nog nie begin is nie" + }, + "ongoing": { + "label": "Aan die gang", + "description": "Take wat tans aan die gang is" + }, + "completed": { + "label": "Voltooi", + "description": "Take wat klaar is" + } + }, + "priorities": { + "high": "Hoog", + "medium": "Medium", + "low": "Laag" + }, + "form": { + "addNewTaskLabel": "Voeg nuwe taak by", + "multilinePlaceholder": "Voeg taakbesonderhede by... (Shift+Enter vir nuwe reël)", + "singlelinePlaceholder": "Wat moet gedoen word? Druk Enter om by te voeg", + "taskInputAria": "Taakinvoer", + "projectPlaceholder": "Projek", + "selectProject": "Kies projek", + "errorSelectProject": "Kies asseblief 'n projek of skep een om 'n taak by te voeg.", + "hintMultiline": "Shift+Enter vir nuwe reël, Enter om by te voeg", + "hintSingleline": "Enter om by te voeg", + "shortcutsAria": "Sleutelbordkortpaaie", + "shortcutsLabel": "Kortpaaie", + "shortcutAddTask": "Voeg taak by", + "shortcutNewLine": "Nuwe reël", + "shortcutClear": "Maak skoon", + "addButton": "Voeg by" + }, + "list": { + "ariaLabel": "Taaklys", + "loading": "Besig om jou take te laai...", + "emptyTitle": "Geen take gevind nie", + "emptyPrimary": "Begin deur jou eerste taak hierbo by te voeg! Druk Enter om vinnig take by te voeg.", + "emptySecondary": "Probeer jou filters of soektog aanpas." + }, + "mobileDrawer": { + "addTaskSrOnly": "Voeg taak by", + "addNewTaskTitle": "Voeg nuwe taak by", + "addNewTaskDescription": "Skep 'n nuwe taak om jou vordering te volg.", + "cancel": "Kanselleer" + }, + "kanban": { + "ariaLabel": "Kanbanbord", + "loading": "Besig om jou take te laai...", + "dropHere": "Los taak hier", + "noTasksYet": "Nog geen take nie", + "emptyColumnAria": "Leë kolom", + "columnAria": "{title}-kolom met {count} take", + "countInColumnAria": "{count} take in {title}" + }, + "taskItem": { + "confirmDelete": "Is jy seker jy wil hierdie taak uitvee?", + "statusPlaceholder": "Status", + "changeStatusAria": "Verander taakstatus", + "taskOptionsAria": "Taakopsies", + "editTask": "Wysig taak", + "copyTask": "Kopieer taak", + "copied": "Gekopieer!", + "deleteTask": "Vee taak uit", + "statusHeading": "Status", + "editTaskAria": "Wysig taak" + }, + "editDialog": { + "title": "Wysig taak", + "description": "Werk taakbesonderhede by, voeg subtake by, stel prioriteite en meer.", + "taskTitleLabel": "Taaktitel *", + "taskTitlePlaceholder": "Wat moet gedoen word?", + "descriptionLabel": "Beskrywing", + "descriptionPlaceholder": "Voeg meer besonderhede oor hierdie taak by...", + "statusLabel": "Status", + "priorityLabel": "Prioriteit", + "dueDateLabel": "Sperdatum", + "pickDate": "Kies 'n datum", + "clearDate": "Maak datum skoon", + "projectLabel": "Projek", + "selectProjectPlaceholder": "Kies 'n projek", + "noProject": "Geen projek", + "timeEstimateLabel": "Tydskatting (minute)", + "timeEstimatePlaceholder": "Hoe lank sal dit neem?", + "tagsLabel": "Merkers", + "customTagNamePlaceholder": "Pasgemaakte merkernaam", + "subtasksLabel": "Subtake", + "addSubtaskPlaceholder": "Voeg 'n subtaak by...", + "cancel": "Kanselleer", + "saving": "Stoor...", + "saveChanges": "Stoor veranderinge" + }, + "export": { + "title": "Voer take uit", + "description": "Voer jou take uit na JSON-, CSV- of Excel-formate.", + "projectLabel": "Projek", + "statusLabel": "Status", + "tagLabel": "Merker", + "allProjects": "Alle projekte", + "allStatuses": "Alle statusse", + "allTags": "Alle merkers", + "exportAsJsonTitle": "Voer uit as JSON", + "exportAsJsonDescription": "Voer taakdata uit, insluitend subtake, merkers en metadata.", + "exportJson": "Voer JSON uit", + "exportAsCsvExcelTitle": "Voer uit as CSV / Excel", + "exportAsCsvExcelDescription": "Voer take uit vir sigbladtoepassings.", + "exportCsv": "Voer CSV uit", + "exportExcel": "Voer Excel uit", + "close": "Sluit", + "toastNoTasks": "Geen take om met huidige filters uit te voer nie", + "toastExportedJson": "{count} take na JSON uitgevoer!", + "toastExportedCsv": "{count} take na CSV uitgevoer!", + "toastExportedExcel": "{count} take na Excel uitgevoer!", + "toastFailedJson": "Kon nie na JSON uitvoer nie", + "toastFailedCsv": "Kon nie na CSV uitvoer nie", + "toastFailedExcel": "Kon nie na Excel uitvoer nie", + "unknownProject": "Onbekende projek" + }, + "ack": { + "taskAddedTitle": "Taak suksesvol bygevoeg", + "taskAddedFailedTitle": "Kon nie taak byvoeg nie", + "taskUpdatedTitle": "Taak suksesvol bygewerk", + "taskUpdatedFailedTitle": "Kon nie taak bywerk nie", + "taskStatusUpdateFailedTitle": "Kon nie taakstatus bywerk nie", + "taskMovedTitle": "Taak geskuif na {status}", + "tryAgain": "Probeer asseblief weer.", + "taskDeletedTitle": "Taak \"{text}\" uitgevee", + "undo": "Ontdoen", + "taskRestoredTitle": "Taak herstel", + "taskRestoredSuccessTitle": "Taak suksesvol herstel", + "taskRestoreFailedTitle": "Kon nie taak herstel nie. Probeer asseblief weer.", + "taskDeleteFailedTitle": "Kon nie taak uitvee nie. Probeer asseblief weer.", + "tasksImportedTitle": "{count} {count, plural, one {taak} other {take}} suksesvol ingevoer!", + "tasksImportFailedTitle": "Kon nie take invoer nie. Probeer asseblief weer.", + "unknownProject": "Onbekende projek", + "projectCreatedTitle": "Projek suksesvol geskep", + "projectCreateFailedTitle": "Kon nie projek skep nie", + "projectUpdatedTitle": "Projek suksesvol bygewerk", + "projectUpdateFailedTitle": "Kon nie projek bywerk nie", + "projectDeletedTitle": "Projek suksesvol uitgevee", + "projectDeleteFailedTitle": "Kon nie projek uitvee nie" + } + }, + "Notes": { + "page": { + "loading": "Besig om te laai..." + }, + "layout": { + "mobileNavTitle": "Notas-navigasie", + "openMenuAria": "Maak notasmenu oop" + }, + "sidebar": { + "title": "Notas", + "createNoteAria": "Skep nota", + "searchPlaceholder": "Soek notas...", + "loading": "Besig om te laai...", + "noNotesFound": "Geen notas gevind nie.", + "noNotesYet": "Nog geen notas nie. Klik + om een te skep.", + "inParent": "in {parentTitle}", + "untitled": "Sonder titel", + "addSubPage": "Voeg subbladsy by", + "delete": "Vee uit", + "deleteConfirmTitle": "Vee nota uit?", + "deleteConfirmDescription": "Is jy seker jy wil \"{title}\" uitvee? Hierdie aksie kan nie ongedaan gemaak word nie.", + "cancel": "Kanselleer", + "confirmDelete": "Vee uit", + "noteOptionsAria": "Nota-opsies" + }, + "editor": { + "emptyState": "Kies 'n nota of skep 'n nuwe een om te begin.", + "titlePlaceholder": "Sonder titel", + "saving": "Stoor tans…", + "ready": "Gereed", + "saved": "Gestoor {time}", + "stats": "{words} woorde · {read} min lees", + "template": "Sjabloon", + "applyTemplate": "Pas sjabloon toe", + "exportMdLabel": ".md", + "exportMd": "Voer uit as Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Voer uit as HTML", + "focusMode": "Fokusmodus", + "exitFocusMode": "Verlaat fokusmodus", + "save": "Stoor", + "addTag": "Voeg etiket by…", + "removeTag": "Verwyder etiket {tag}", + "loading": "Laai tans…", + "searchHint": "om te soek", + "focusHint": "fokusmodus", + "notePath": "Notapad" + }, + "context": { + "authRequiredError": "Gebruiker nie geverifieer nie", + "defaultTitle": "Sonder titel", + "cloudSyncImageError": "Skakel Wolksinkronisasie aan om notaprente te stoor. Teksnotas bly plaaslik." + } + }, + "PasswordManager": { + "page": { + "loading": "Besig om te laai...", + "title": "Wagwoordbestuurder", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Kon nie wagwoorde ontsyfer nie", + "toastLoadFailed": "Kon nie wagwoorde laai nie", + "toastUnlockedAuto": "Kluis outomaties ontsluit", + "toastAutoUnlockFailed": "Outo-ontsluit het misluk. Ontsluit asseblief handmatig.", + "toastConnectFailed": "Kon nie aan bediener koppel nie", + "toastAutoUnlockWarning": "Kluis ontsluit, maar outo-ontsluit werk dalk nie by herlaai nie", + "errorIncorrectPassword": "Verkeerde meesterwagwoord", + "errorUnlockFailed": "Kon nie kluis ontsluit nie", + "errorPasswordsMismatch": "Wagwoorde stem nie ooreen nie", + "errorPasswordTooShort": "Wagwoord moet minstens 8 karakters wees", + "errorSetupFailed": "Kon nie kluis opstel nie", + "errorConnectFailed": "Kon nie aan bediener koppel nie.", + "mobileTitleCreate": "Skep jou kluis", + "mobileTitleWelcome": "Welkom terug", + "mobileSubtitleCreate": "Stel 'n meesterwagwoord om jou geloofsbriewe te beskerm", + "mobileSubtitleUnlock": "Voer jou meesterwagwoord in om te ontsluit", + "placeholderMasterPassword": "Meesterwagwoord", + "placeholderConfirmPassword": "Bevestig wagwoord", + "labelMasterPassword": "Meesterwagwoord", + "labelConfirmPassword": "Bevestig wagwoord", + "placeholderEnterMaster": "Voer meesterwagwoord in", + "placeholderConfirmMaster": "Bevestig meesterwagwoord", + "buttonCreateVault": "Skep kluis", + "buttonUnlock": "Ontsluit", + "buttonUnlockVault": "Ontsluit kluis", + "footerCreate": "Jou wagwoord word nooit gestoor nie en kan nie herwin word nie", + "footerUnlock": "Beveilig met end-tot-end-enkripsie", + "desktopTitleSetup": "Stel wagwoordkluis op", + "desktopTitleUnlock": "Ontsluit kluis", + "desktopDescriptionSetup": "Skep 'n meesterwagwoord om jou geloofsbriewe te beveilig. Hierdie wagwoord word nooit gestoor nie en kan nie herwin word nie.", + "desktopDescriptionUnlock": "Voer jou meesterwagwoord in om toegang tot jou beveiligde geloofsbriewe te kry." + }, + "list": { + "loading": "Besig om jou wagwoorde te laai...", + "emptyTitle": "Kluis is leeg", + "emptyHintMobile": "Tik die +-knoppie om jou eerste wagwoord by te voeg", + "emptyHintDesktop": "Jy het nog geen wagwoorde gestoor nie. Klik die \"Voeg wagwoord by\"-knoppie hierbo om te begin.", + "addPassword": "Voeg wagwoord by", + "searchPlaceholderMobile": "Soek in jou kluis...", + "searchPlaceholderDesktop": "Soek kluis...", + "importExport": "Invoer / uitvoer", + "lockVault": "Sluit kluis", + "allPasswordsTitle": "Alle wagwoorde", + "passwordCount": "{count, plural, one {# wagwoord} other {# wagwoorde}}", + "vaultHealthDrawerTitle": "Kluisgesondheid", + "noResults": "Geen wagwoorde gevind wat by \"{query}\" pas nie", + "tableService": "Diens", + "tableUsername": "Gebruikersnaam", + "tablePassword": "Wagwoord", + "tableUrl": "URL", + "tableActions": "Aksies", + "gridViewAria": "Roosteraansig", + "listViewAria": "Lysaansig", + "passwordStrengthTitle": "Wagwoordsterkte", + "deleteDialogTitle": "Vee wagwoord uit", + "deleteDialogDescription": "Is jy seker jy wil hierdie wagwoord uitvee? Hierdie aksie kan nie ongedaan gemaak word nie.", + "cancel": "Kanselleer", + "delete": "Vee uit", + "edit": "Wysig" + }, + "toasts": { + "copiedPassword": "Wagwoord na knipbord gekopieer", + "copiedUsername": "Gebruikersnaam na knipbord gekopieer", + "passwordDeleted": "Wagwoord uitgevee", + "deleteFailed": "Kon nie wagwoord uitvee nie", + "vaultLocked": "Kluis gesluit", + "importedCount": "{count, plural, one {# wagwoord} other {# wagwoorde}} suksesvol ingevoer" + }, + "form": { + "addTitle": "Voeg nuwe wagwoord by", + "editTitle": "Wysig wagwoord", + "serviceName": "Diensnaam", + "usernameEmail": "Gebruikersnaam / e-pos", + "password": "Wagwoord", + "urlOptional": "Webwerf-URL (opsioneel)", + "notesOptional": "Notas (opsioneel)", + "tags": "Merkers", + "placeholderService": "bv. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Voer wagwoord in of genereer een", + "placeholderUrl": "https://...", + "placeholderNotes": "Bykomende besonderhede...", + "placeholderTags": "Voeg merkers by (druk Enter of komma)", + "strength": "Sterkte", + "strengthWeak": "Swak", + "strengthMedium": "Medium", + "strengthStrong": "Sterk", + "advancedGenerator": "Gevorderde generator", + "cancel": "Kanselleer", + "saving": "Stoor...", + "savePassword": "Stoor wagwoord", + "updating": "Werk by...", + "updatePassword": "Werk wagwoord by", + "addPasswordSrOnly": "Voeg wagwoord by", + "triggerAdd": "Voeg wagwoord by", + "toastSaved": "Wagwoord suksesvol gestoor", + "toastSaveFailed": "Kon nie wagwoord stoor nie", + "toastUpdated": "Wagwoord suksesvol bygewerk", + "toastUpdateFailed": "Kon nie wagwoord bywerk nie" + }, + "security": { + "vaultHealth": "Kluisgesondheid", + "statusLabel": "Status", + "totalPasswords": "Totale wagwoorde", + "activeEntries": "Aktiewe inskrywings in jou kluis", + "protected": "Beskerm", + "weakPasswords": "Swak wagwoorde", + "needingImprovement": "Wagwoorde wat verbetering nodig het", + "allGood": "Alles goed", + "reusedPasswords": "Hergebruikte wagwoorde", + "usedMultipleTimes": "Wagwoorde wat meer as een keer gebruik is", + "unique": "Uniek", + "total": "Totaal", + "weak": "Swak", + "reused": "Hergebruik", + "totalCountLabel": "{count, plural, one {# wagwoord} other {# wagwoorde}}", + "weakCountLabel": "{count} swak", + "allSecure": "Alles veilig", + "scoreExcellent": "Uitstekend", + "scoreGood": "Goed", + "scoreFair": "Redelik", + "scoreCritical": "Kritiek" + }, + "importExport": { + "title": "Invoer / uitvoer kluis", + "description": "Bestuur jou wagwoorddata. Jy kan jou kluis na JSON uitvoer of van 'n JSON-lêer invoer.", + "tabExport": "Uitvoer", + "tabImport": "Invoer", + "warningTitle": "Waarskuwing", + "warningBody": "Uitgevoerde lêers bevat jou wagwoorde as plat teks. Hou hierdie lêer veilig en vee dit uit na gebruik.", + "exportBlurb": "Laai jou hele kluis as 'n JSON-lêer af. Dit sluit alle dienste, gebruikersname, wagwoorde en notas in.", + "downloadJson": "Laai JSON-uitvoer af", + "pasteLabel": "Plak JSON-data", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Plak die inhoud van 'n voorheen uitgevoerde JSON-lêer hier.", + "importing": "Besig om in te voer...", + "importPasswords": "Voer wagwoorde in", + "trigger": "Invoer / uitvoer", + "toastExported": "Kluis suksesvol uitgevoer", + "toastExportFailed": "Kon nie kluis uitvoer nie", + "toastImportFailed": "Kon nie invoer nie: ongeldige JSON-formaat" + }, + "card": { + "copyUsername": "Kopieer gebruikersnaam", + "openUrl": "Maak URL oop", + "strength": "Sterkte" + }, + "swipeable": { + "copy": "Kopieer", + "delete": "Vee uit", + "copyPassword": "Kopieer wagwoord", + "copyUsername": "Kopieer gebruikersnaam", + "edit": "Wysig", + "deleteAction": "Vee uit" + }, + "floatingMenu": { + "lockVault": "Sluit kluis", + "importExport": "Invoer / uitvoer", + "addPassword": "Voeg wagwoord by" + }, + "generator": { + "tabPassword": "Wagwoord", + "tabPassphrase": "Wagwoordfrase", + "strength": "Sterkte", + "lengthLabel": "Lengte: {length} karakters", + "lengthBadge": "{length} kar.", + "characterSets": "Karakterstelle", + "uppercase": "Hoofletters", + "lowercase": "Kleinletters", + "numbers": "Syfers", + "symbols": "Simbole", + "wordCountLabel": "Woordtelling: {count}", + "wordCountBadge": "{count, plural, one {# woord} other {# woorde}}", + "separator": "Skeidingsteken", + "capitalization": "Hooflettergebruik", + "includeNumber": "Sluit syfer in" + } + }, + "EnvironmentManager": { + "subtitle": "Geënkripteerde omgewingsveranderlikes per projek en omgewing.", + "page": { + "loading": "Laai…", + "title": "Omgewingbestuurder", + "loadFailed": "Kon nie omgewingstelle laai nie" + }, + "list": { + "title": "Omgewings", + "loading": "Laai jou stelle…", + "emptyTitle": "Nog geen omgewingstelle nie", + "emptyHint": "Skep 'n stel vir elke projek en omgewing (bv. produksie API-sleutels). Alles word in jou blaaier geënkripteer voor oplaai.", + "searchPlaceholder": "Soek projek, omgewing, etikette, notas…", + "tagFilterPlaceholder": "Filter volgens etiket", + "variableCount": "{count, plural, one {# veranderlike} other {# veranderlikes}}", + "updatedAgo": "Bygewerk {time}", + "copyDotEnv": "Kopieer .env", + "edit": "Wysig", + "delete": "Vee uit", + "cancel": "Kanselleer", + "confirmDelete": "Vee uit", + "deleteDialogTitle": "Omgewingstel uitvee?", + "deleteDialogDescription": "Dit verwyder die geënkripteerde data uit jou rekening. Dit kan nie ongedaan gemaak word nie.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env na knipbord gekopieer", + "deleted": "Omgewingstel uitgevee", + "deleteFailed": "Kon nie uitvee nie" + }, + "form": { + "addTitle": "Nuwe omgewingstel", + "editTitle": "Wysig omgewingstel", + "triggerAdd": "Voeg omgewingstel by", + "addSrOnly": "Voeg omgewingstel by", + "project": "Projek", + "environment": "Omgewing", + "placeholderProject": "bv. billing-api", + "placeholderEnvironment": "bv. produksie", + "pasteEnvLabel": "Plak .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Voeg geplakte lyne saam", + "variables": "Veranderlikes", + "addRow": "Voeg ry by", + "keyPlaceholder": "SLEUTEL", + "valuePlaceholder": "waarde", + "notesOptional": "Notas (opsioneel)", + "placeholderNotes": "Rotasie, eienaar, skakels…", + "tags": "Etikette", + "placeholderTags": "Voeg etikette by (Enter of komma)", + "cancel": "Kanselleer", + "save": "Stoor", + "saving": "Stoor…", + "update": "Werk by", + "updating": "Werk tans by…", + "toastSaved": "Omgewingstel gestoor", + "toastSaveFailed": "Kon nie stoor nie", + "toastUpdated": "Omgewingstel bygewerk", + "toastUpdateFailed": "Kon nie bywerk nie", + "toastPasteEmpty": "Geen KEY=waarde-lyne gevind nie", + "toastPasteApplied": "{count} veranderlikes van plakking saamgevoeg", + "toastProjectEnvRequired": "Projek en omgewing is verpligtend", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Besig om te laai..." + }, + "manager": { + "foldersHeading": "Vouers", + "tagsHeading": "Merkers", + "tagsMore": "+{count} meer", + "tagsShowLess": "Show less", + "searchPlaceholder": "Soek jou boekmerke...", + "import": "Invoer", + "export": "Uitvoer", + "exportAsHtml": "Voer uit as HTML", + "exportAsJson": "Voer uit as JSON", + "addBookmark": "Voeg boekmerk by", + "matchingQuery": "pas by \"{query}\"", + "bookmarkCount": "{count, plural, one {# boekmerk} other {# boekmerke}}", + "allBookmarks": "Alle boekmerke", + "uncategorized": "Ongekategoriseer", + "unknownFolder": "Onbekend" + }, + "card": { + "urlCopied": "URL na knipbord gekopieer", + "bookmarkDeleted": "Boekmerk uitgevee", + "edit": "Wysig", + "delete": "Vee uit", + "copyUrl": "Kopieer URL", + "deleteConfirmTitle": "Vee boekmerk uit?", + "deleteConfirmDescription": "Dit sal \"{title}\" permanent uitvee.", + "cancel": "Kanselleer" + }, + "grid": { + "emptyTitle": "Nog geen boekmerke nie", + "emptyDescription": "Voeg jou eerste boekmerk by of voer in vanaf jou blaaier om te begin." + }, + "folderTree": { + "allBookmarks": "Alle boekmerke", + "addSubfolder": "Voeg subvouer by", + "rename": "Hernoem", + "deleteFolderTitle": "Vee vouer \"{name}\" uit?", + "deleteFolderDescription": "Dit sal die vouer en al sy subvouers uitvee. Boekmerke in hierdie vouers word na Ongekategoriseer geskuif." + }, + "import": { + "title": "Voer boekmerke in", + "description": "Voer boekmerke in vanaf jou blaaier se uitgevoerde lêer.", + "parseError": "Kon nie boekmerklêer ontleed nie. Maak seker dit is 'n geldige boekmerkuitvoer.", + "successToast": "{bookmarks} boekmerke en {folders} vouers ingevoer", + "errorToast": "Kon nie boekmerke invoer nie", + "chromeInstruction": "Kieslys → Boekmerke → Boekmerkbestuurder → ⋮ → Voer boekmerke uit", + "firefoxInstruction": "Kieslys → Boekmerke → Bestuur boekmerke → Invoer en rugsteun → Uitvoer", + "safariInstruction": "Lêer → Voer boekmerke uit...", + "edgeInstruction": "Kieslys → Gunstelinge → ⋯ → Voer gunstelinge uit", + "dropZoneTitle": "Los jou boekmerklêer hier", + "dropZoneHint": "of klik om te blaai (HTML of JSON)", + "selectedFileHint": "Klik om 'n ander lêer te kies", + "errorTitle": "Fout", + "previewTitle": "Invoervoorskou", + "previewBookmarks": "{count, plural, one {# boekmerk} other {# boekmerke}}", + "previewFolders": "{count, plural, one {# vouer} other {# vouers}}", + "importModeLabel": "Invoermodus", + "modeMerge": "Voeg by bestaande boekmerke", + "modeReplace": "Vervang alle bestaande boekmerke", + "cancel": "Kanselleer", + "importButton": "Invoer", + "importButtonWithCount": "Voer {count, plural, one {# boekmerk} other {# boekmerke}} in" + }, + "addBookmark": { + "titleAdd": "Voeg boekmerk by", + "titleEdit": "Wysig boekmerk", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Voer asseblief 'n geldige URL in", + "titleLabel": "Titel", + "titlePlaceholder": "Boekmerktitel", + "titleRequired": "Voer asseblief 'n titel in", + "descriptionLabel": "Beskrywing (opsioneel)", + "descriptionPlaceholder": "Voeg 'n nota by hierdie boekmerk", + "folderLabel": "Vouer", + "folderPlaceholder": "Kies vouer", + "noFolder": "Geen vouer", + "tagsLabel": "Merkers", + "tagsPlaceholder": "Voeg merkers by (druk Enter)", + "toastUpdated": "Boekmerk bygewerk", + "toastAdded": "Boekmerk bygevoeg", + "cancel": "Kanselleer", + "submitAdd": "Voeg boekmerk by", + "submitUpdate": "Werk boekmerk by" + }, + "addFolder": { + "title": "Skep vouer", + "nameLabel": "Vouernaam", + "namePlaceholder": "My vouer", + "nameRequired": "Voer asseblief 'n vouernaam in", + "parentLabel": "Ouer-vouer", + "parentPlaceholder": "Kies ouer-vouer", + "rootParent": "Wortel (geen ouer)", + "toastCreated": "Vouer geskep", + "cancel": "Kanselleer", + "submit": "Skep vouer" + }, + "editFolder": { + "title": "Hernoem vouer", + "nameLabel": "Vouernaam", + "namePlaceholder": "Vouernaam", + "nameRequired": "Voer asseblief 'n vouernaam in", + "toastRenamed": "Vouer hernoem", + "cancel": "Kanselleer", + "save": "Stoor" + } + }, + "EmailValidator": { + "header": { + "title": "E-posvalidasie", + "description": "Verifieer en valideer e-posadresse vir sintaksis, domein en aflewerbaarheid." + }, + "tabs": { + "single": "Enkel verifikasie", + "bulk": "Grootmaat oplaai" + }, + "single": { + "cardTitle": "Enkel e-posverifikasie", + "cardDescription": "Voer 'n e-posadres in om aflewerbaarheid en reputasie te kontroleer.", + "placeholder": "Voer e-posadres in...", + "clearAria": "Maak e-pos skoon", + "validate": "Valideer", + "validating": "Besig om te valideer...", + "formatError": "Voer asseblief 'n geldige e-posadres in" + }, + "toasts": { + "errorTitle": "Fout", + "enterEmail": "Voer asseblief 'n e-posadres in", + "invalidFormatTitle": "Ongeldige e-posformaat", + "invalidFormatDescription": "Voer asseblief 'n geldige e-posadres in", + "validateFailedDescription": "'n Fout het voorgekom tydens validasie van die e-pos", + "noEmailsTitle": "Geen e-posse gevind nie", + "noEmailsDescription": "Maak seker jou Excel het 'n 'email'-kolom.", + "fileTooLargeTitle": "Lêer te groot", + "fileTooLargeDescription": "{total} e-posse gevind. Slegs die eerste 4000 sal gevalideer word.", + "successTitle": "Sukses", + "successValidated": "{count} e-posse suksesvol gevalideer.", + "batchFailedTitle": "Grootmaatvalidasie het misluk", + "batchFailedDescription": "'n Fout het voorgekom tydens grootmaatvalidasie.", + "noExportTitle": "Geen data om uit te voer nie", + "noExportValidOnly": "Geen geldige e-posse om uit te voer nie.", + "noExportAll": "Geen resultate om uit te voer nie.", + "exportSuccessTitle": "Uitvoer suksesvol", + "exportSuccessExcelAll": "{count} e-posse na Excel uitgevoer.", + "exportSuccessExcelValid": "{count} geldige e-posse na Excel uitgevoer.", + "exportSuccessCsvAll": "{count} e-posse na CSV uitgevoer.", + "exportSuccessCsvValid": "{count} geldige e-posse na CSV uitgevoer." + }, + "disposableAlert": { + "title": "Wegwerp-e-pos bespeur", + "description": "Hierdie e-posadres behoort aan 'n wegwerp-e-posverskaffer. Dit kan tydelik of onveilig wees." + }, + "score": { + "reliabilityTitle": "Betroubaarheidtelling", + "scoreLabel": "Telling" + }, + "details": { + "sectionTitle": "Valideringsbesonderhede" + }, + "validation": { + "syntaxLabel": "Sintaksis geldig", + "syntaxTooltip": "Kontroleer of die e-posadres die korrekte formaat volg (bv. user@domain.com)", + "domainLabel": "Domein bestaan", + "domainTooltip": "Verifieer dat die domeinnaam in die e-posadres werklik bestaan", + "mxLabel": "MX-rekords gevind", + "mxTooltip": "Kontroleer of die domein posuitruil (MX)-rekords het om e-pos te ontvang", + "mailboxLabel": "Posbus bestaan", + "mailboxTooltip": "Verifieer dat die spesifieke posbus/rekening op die posbediener bestaan", + "disposableLabel": "Wegwerp-e-pos", + "disposableTooltip": "Dui aan of hierdie e-pos by 'n tydelike/wegwerpdienste behoort (kan riskant wees)", + "roleBasedLabel": "Rolgebaseerde e-pos", + "roleBasedTooltip": "Identifiseer of dit 'n rolgebaseerde e-pos is (bv. support@, info@) eerder as 'n persoonlike adres" + }, + "copy": { + "emailCopied": "E-pos gekopieer!", + "copyTooltip": "Kopieer e-pos" + }, + "bulk": { + "dropHere": "Los jou lêer hier", + "dragHere": "Sleep jou lêer hierheen", + "uploadHint": "Laai 'n .xlsx-, .xls- of .csv-lêer met 'n 'email'-kolom op om tot 4000 e-posse te valideer.", + "filePrefix": "Lêer:", + "selectFile": "Kies lêer", + "downloadTemplate": "Laai sjabloon af" + }, + "bulkLoading": { + "title": "Besig om e-posse te valideer...", + "batchProgress": "Verwerk bondel {current} van {total}..." + }, + "stats": { + "totalProcessed": "Totaal verwerk", + "valid": "Geldig", + "risky": "Riskant", + "invalid": "Ongeldig" + }, + "results": { + "detailedTitle": "Gedetailleerde resultate", + "searchPlaceholder": "Soek e-posse...", + "newVerification": "Nuwe verifikasie", + "exportAll": "Voer alles uit", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Voer geldiges uit ({count})" + }, + "showingResults": "Wys {shown} van {total} resultate", + "table": { + "email": "E-pos", + "status": "Status", + "score": "Telling", + "checks": "Kontroles" + }, + "noSearchResults": "Geen resultate gevind wat by \"{query}\" pas nie", + "checksTooltip": { + "syntax": "Sintaksis", + "domain": "Domein", + "mx": "MX", + "mailbox": "Posbus" + }, + "exportColumns": { + "email": "E-pos", + "status": "Status", + "score": "Telling", + "syntax": "Sintaksis", + "domain": "Domein", + "mx": "MX", + "mailbox": "Posbus", + "disposable": "Wegwerp", + "roleBased": "Rolgebaseer", + "valid": "Geldig", + "invalid": "Ongeldig", + "yes": "Ja", + "no": "Nee" + }, + "sheets": { + "validEmails": "Geldige e-posse", + "results": "Resultate", + "template": "Sjabloon" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL-verkenner", + "heroSubtitle": "Koppel aan jou databasisse om versamelings te bestuur, navrae uit te voer en jou data te visualiseer.", + "featureMultiTitle": "Veelvuldige koppeling", + "featureMultiDesc": "Bestuur terselfdertyd verskeie databasiskoppelings.", + "featureMongoTitle": "MongoDB-ondersteuning", + "featureMongoDesc": "Inheemse ondersteuning vir MongoDB met gevorderde filtering.", + "featureQueryTitle": "Slimme navraag", + "featureQueryDesc": "Kragtige navraagbouer met sintaksisklem.", + "connectCta": "Koppel aan MongoDB", + "footerNote": "Ondersteun tans {db}. Meer databasisse kom binnekort.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Kies 'n versameling", + "selectCollectionDesc": "Kies 'n versameling in die sybalk om dokumente te bekyk en jou data te verken.", + "dialogAddConnection": "Voeg koppeling by", + "toastConnectionAdded": "Koppeling bygevoeg", + "toastFetchFailed": "Kon nie dokumente vir {collection} laai nie", + "toastDocDeleted": "Dokument uitgevee", + "deleteDocTitle": "Is jy absoluut seker?", + "deleteDocDescription": "Hierdie aksie kan nie ongedaan gemaak word nie. Dit sal die dokument permanent uit jou versameling uitvee.", + "closeAllTitle": "Sluit alle oortjies?", + "closeAllDescription": "Is jy seker jy wil alle oortjies sluit? Ongestoorde veranderinge in navraagfilters gaan verlore.", + "closeAllConfirm": "Sluit alles", + "cancel": "Kanselleer", + "delete": "Vee uit" + }, + "connection": { + "titleEdit": "Wysig koppeling", + "titleConnect": "Koppel aan MongoDB", + "subtitleEdit": "Werk jou koppelingsbesonderhede hieronder by.", + "subtitleConnect": "Voer jou MongoDB-koppelingsstring in om jou data te begin verken.", + "labelName": "Naam", + "placeholderName": "bv. Produksie-DB", + "labelConnectionString": "Koppelingsstring", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Formaat: mongodb://username:password@host:port/database", + "testConnection": "Toets koppeling", + "connecting": "Besig om te koppel...", + "updateConnect": "Werk by en koppel", + "connect": "Koppel", + "cancelEdit": "Kanselleer wysiging", + "savedConnections": "Gestoorde koppelings", + "emptySaved": "Nog geen gestoorde koppelings nie.", + "lastUsed": "Laas gebruik {time}", + "toastTestOk": "Koppeling suksesvol!", + "toastTestFail": "Koppeling het misluk: {message}", + "toastUpdated": "Koppeling bygewerk", + "confirmDelete": "Is jy seker jy wil hierdie gestoorde koppeling uitvee?", + "toastDeletedConn": "Koppeling uitgevee", + "toastDeleteFail": "Kon nie koppeling uitvee nie", + "copyStringTitle": "Kopieer koppelingsstring", + "toastStringCopied": "Koppelingsstring gekopieer!", + "editTitle": "Wysig", + "deleteTitle": "Vee uit" + }, + "sidebar": { + "explorer": "Verkenner", + "add": "Voeg by", + "tooltipAdd": "Voeg nuwe koppeling by", + "tooltipRefresh": "Verfris koppelings", + "searchPlaceholder": "Soek...", + "loadingConnections": "Besig om koppelings te laai...", + "noConnectionsFound": "Geen koppelings gevind nie", + "toastLoadFail": "Kon nie koppelings laai nie", + "toastConnectFail": "Kon nie aan {name} koppel nie", + "toastCollectionsFail": "Kon nie versamelings vir {name} laai nie", + "toastRenamed": "Koppeling hernoem", + "toastRenameFail": "Kon nie koppeling hernoem nie", + "confirmDeleteConnection": "Is jy seker jy wil koppeling \"{name}\" uitvee?", + "toastDeleted": "Koppeling uitgevee", + "toastDeleteConnFail": "Kon nie koppeling uitvee nie", + "confirmDropDb": "Is jy seker jy wil databasis \"{name}\" laat val? Hierdie aksie kan nie ongedaan gemaak word nie.", + "toastDbDropped": "Databasis {name} laat val", + "confirmDropCollection": "Is jy seker jy wil versameling \"{name}\" laat val? Hierdie aksie kan nie ongedaan gemaak word nie.", + "toastCollectionDropped": "Versameling {name} laat val", + "toastCollectionRenamed": "Versameling hernoem na {name}", + "toastDatabaseRenamed": "Databasis hernoem na {name}", + "connecting": "Besig om te koppel...", + "connectionFailed": "Koppeling het misluk", + "noDatabases": "Geen databasisse", + "loading": "Besig om te laai...", + "noCollections": "Geen versamelings", + "renameCollectionTitle": "Hernoem versameling", + "renameDatabaseTitle": "Hernoem databasis", + "placeholderNewCollection": "Nuwe versamelingnaam", + "placeholderNewDatabase": "Nuwe databasisnaam", + "rename": "Hernoem", + "refresh": "Verfris", + "copyDbName": "Kopieer DB-naam", + "toastDbNameCopied": "Databasisnaam na knipbord gekopieer", + "dropDatabase": "Laat databasis val", + "dropCollection": "Laat versameling val", + "menuRename": "Hernoem", + "menuCopyConnectionString": "Kopieer koppelingsstring", + "menuDeleteConnection": "Vee koppeling uit", + "toastSidebarStringCopied": "Koppelingsstring gekopieer!", + "cancel": "Kanselleer", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} dokumente", + "table": "Tabel", + "json": "JSON", + "tree": "Boom", + "tooltipTable": "Tabelaansig", + "tooltipJson": "JSON-aansig", + "tooltipTree": "Boomaansig", + "itemsPerPage": "Items per bladsy", + "perPage": "{n} / bladsy", + "prevPage": "Vorige bladsy", + "nextPage": "Volgende bladsy", + "expandAll": "Vou alles oop", + "collapseAll": "Vou alles toe", + "refreshData": "Verfris data", + "insert": "Voeg in", + "export": "Uitvoer", + "actions": "Aksies", + "invalidJson": "Ongeldige JSON-inhoud", + "copiedClipboard": "Na knipbord gekopieer", + "docUpdated": "Dokument suksesvol bygewerk", + "docInserted": "Dokument suksesvol ingevoeg", + "invalidJsonShort": "Ongeldige JSON", + "docCopied": "Dokument na knipbord gekopieer", + "openFullEditor": "Maak in volledige redigeerder oop", + "cellArray": "Skikking({length})", + "cellObject": "Objek", + "cellArrayBracket": "Skikking[{length}]", + "emptyFilterTitle": "Geen dokumente pas by jou navraag nie", + "emptyFilterHint": "Probeer jou filter aanpas of maak die navraag skoon.", + "clearFilter": "Maak filter skoon", + "emptyCollectionTitle": "Hierdie versameling is leeg", + "emptyCollectionHint": "Voeg 'n dokument in om te begin.", + "insertDocument": "Voeg dokument in", + "formatJson": "Formateer JSON", + "compactJson": "Kompakteer JSON", + "copyClipboard": "Kopieer na knipbord", + "copyJson": "Kopieer JSON", + "duplicate": "Dupliseer", + "editDoc": "Wysig dokument", + "deleteDoc": "Vee dokument uit", + "documentLabel": "Dokument {n}", + "editDialogTitle": "Wysig dokument", + "insertDialogTitle": "Voeg dokument in", + "viewValueTitle": "Bekyk waarde", + "saveChanges": "Stoor veranderinge", + "close": "Sluit", + "cancel": "Kanselleer", + "insertButton": "Voeg in", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Aktiewe filter toegepas", + "filterActiveTooltip": "Filter aktief", + "closeAll": "Sluit alles" + }, + "query": { + "migrateHistory": "Navraaggeskiedenis na wolk gemigreer", + "saveHistoryFail": "Kon nie navraaggeskiedenis stoor nie", + "deleteQueryFail": "Kon nie navraag uitvee nie", + "invalidJsonQuery": "Ongeldige JSON-navraag", + "placeholder": "bv. { \"status\": \"active\" }", + "clearQuery": "Maak navraag skoon", + "openAdvanced": "Maak gevorderde redigeerder oop", + "visualBuilder": "Visuele navraagbouer", + "filterRules": "Filterreëls", + "addRule": "Voeg reël by", + "noFilters": "Geen filters toegepas nie. Voeg 'n reël by om dokumente te filter.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "Veld", + "opPlaceholder": "Operateur", + "typePlaceholder": "Tipe", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Nie in", + "opExists": "Bestaan", + "typeAuto": "Outo", + "typeString": "String", + "typeNumber": "Nommer", + "typeBoolean": "Booleaans", + "typeDate": "Datum", + "typeObjectId": "ObjectId", + "true": "Waar", + "false": "Vals", + "valuePlaceholder": "Waarde", + "valueListPlaceholder": "waarde1, waarde2", + "clearFilters": "Maak filters skoon", + "applyFilters": "Pas filters toe", + "queryHistory": "Navraaggeskiedenis", + "noSavedQueries": "Nog geen gestoorde navrae nie", + "advancedEditorTitle": "Gevorderde navraagredigeerder", + "templatesPlaceholder": "Sjablone", + "templateEmptyAgg": "Leë aggregasie", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Join)", + "templateGroup": "$group (Tel/Som)", + "format": "Formaat", + "formattedJson": "Geformateerde JSON", + "runQuery": "Voer navraag uit" + }, + "export": { + "selectFieldsError": "Kies asseblief minstens een veld om uit te voer", + "success": "Suksesvol uitgevoer na {filename}", + "fail": "Kon nie data uitvoer nie", + "title": "Voer data uit", + "formatLabel": "Uitvoerformaat", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Kies velde", + "selectAll": "Kies alles", + "fieldsSelected": "{count, plural, one {# veld gekies} other {# velde gekies}}", + "cancel": "Kanselleer", + "export": "Uitvoer", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Tipe: {type}", + "itemSummary": "{count, plural, one {# item} other {# items}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Nuwe versoek", + "importedRequest": "Ingevoerde versoek", + "untitledRequest": "Naamlose versoek" + }, + "toasts": { + "curlImported": "cURL suksesvol ingevoer", + "curlParseFailed": "Kon nie cURL-opdrag ontleed nie", + "invalidJsonBody": "Ongeldige JSON-liggaam", + "requestFailed": "Versoek het misluk: {message}", + "curlPasted": "cURL geplak en suksesvol ontleed", + "responseCopied": "Antwoord na knipbord gekopieer", + "codeCopied": "Kode na knipbord gekopieer", + "copyFailed": "Failed to copy to clipboard" + }, + "layout": { + "collections": "Versamelings", + "closeSidebar": "Sluit sybalk", + "openSidebar": "Maak sybalk oop", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Metode", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Besig om te stuur...", + "send": "Stuur", + "invalidJsonBodyHelp": "Cannot send: the JSON body is invalid" + }, + "requestTabs": { + "params": "Params", + "headers": "Koptekste", + "body": "Liggaam", + "auth": "Magtiging", + "bodyType": "Liggaamtipe", + "selectType": "Kies tipe", + "bodyNone": "Geen", + "bodyJson": "JSON", + "bodyText": "Teks", + "noBodyHint": "Geen liggaaminhoud vir hierdie versoek nie", + "switchToJson": "Skakel na JSON-liggaam", + "authType": "Magtigingtipe", + "noAuth": "Geen magtiging", + "bearerToken": "Bearer-token", + "basicAuth": "Basiese magtiging", + "apiKey": "API-sleutel", + "labelToken": "Token", + "placeholderBearerToken": "Bearer-token", + "labelUsername": "Gebruikersnaam", + "placeholderUsername": "Gebruikersnaam", + "labelPassword": "Wagwoord", + "placeholderPassword": "Wagwoord", + "labelKey": "Sleutel", + "placeholderApiKeyName": "Sleutelnaam (bv. X-Api-Key)", + "labelValue": "Waarde", + "placeholderValue": "Waarde", + "addTo": "Voeg by aan", + "header": "Koptekst", + "queryParams": "Navraagparameters", + "noAuthHint": "Hierdie versoek gebruik geen magtiging nie." + }, + "responsePanel": { + "emptyTitle": "Gereed om te verken?", + "emptyHint": "Voer 'n URL in en klik Stuur om die antwoorddata hier te sien.", + "responsePreviewAlt": "Antwoordvoorskou", + "previewUnavailable": "Voorskou nie beskikbaar vir hierdie inhoudtipe nie", + "status": "Status", + "time": "Tyd", + "size": "Grootte", + "copyBody": "Kopieer liggaam", + "downloadResponse": "Laai antwoord af", + "bodyTab": "Liggaam", + "previewTab": "Voorskou", + "headersTab": "Koptekste", + "binaryRawView": "Binêre data kan nie in rouaansig vertoon word nie.", + "errorStatusLabel": "Fout", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Nuwe oortjie" + }, + "collectionsSidebar": { + "title": "API-hulpbronne", + "newCollection": "Nuwe versameling", + "tabCollections": "Versamelings", + "tabHistory": "Geskiedenis", + "noCollectionsTitle": "Geen versamelings", + "noCollectionsHint": "Skep 'n versameling om jou API-versoeke te organiseer.", + "createCollection": "Skep versameling", + "newFolder": "Nuwe vouer", + "rename": "Hernoem", + "delete": "Vee uit", + "emptyCollection": "Leë versameling", + "recentRequests": "Onlangse versoeke", + "clearAll": "Maak alles skoon", + "noHistoryTitle": "Nog geen geskiedenis nie", + "noHistoryHint": "Stuur 'n versoek om dit hier te sien.", + "dialogNewFolderTitle": "Nuwe vouer", + "dialogNewFolderDescription": "Skep 'n nuwe vouer om jou versoeke te organiseer.", + "labelFolderName": "Vouernaam", + "placeholderFolderName": "My vouer", + "createFolder": "Skep vouer", + "dialogNewCollectionTitle": "Nuwe versameling", + "dialogNewCollectionDescription": "Skep 'n nuwe topvlak-versameling.", + "labelCollectionName": "Versamelingnaam", + "placeholderCollectionName": "My versameling", + "dialogRenameCollectionTitle": "Hernoem versameling", + "dialogRenameCollectionDescription": "Voer 'n nuwe naam vir die versameling in.", + "placeholderRenameCollection": "Versamelingnaam", + "renameAction": "Hernoem", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Nuwe vouer", + "delete": "Vee uit", + "emptyFolder": "Leë vouer" + }, + "environmentManager": { + "noEnvironment": "Geen omgewing", + "manageDialogTitle": "Bestuur omgewings", + "placeholderNewEnv": "Nuwe omgewing", + "variablesHeading": "{name}-veranderlikes", + "addVariable": "Voeg veranderlike by", + "columnVariable": "Veranderlike", + "columnValue": "Waarde", + "placeholderKey": "Sleutel", + "placeholderValue": "Waarde", + "selectEnvToEdit": "Kies 'n omgewing om te wysig" + }, + "codeGenerator": { + "triggerTitle": "Genereer kode", + "dialogTitle": "Genereer kode", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Kopieer", + "errorGenerating": "Fout tydens kodegenerering" + }, + "keyValueEditor": { + "columnKey": "Sleutel", + "columnValue": "Waarde", + "placeholderKey": "Sleutel", + "placeholderValue": "Waarde", + "addItem": "Voeg item by" + }, + "importCurl": { + "trigger": "Voer cURL in", + "dialogTitle": "Voer cURL in", + "dialogDescription": "Plak jou cURL-opdrag hieronder om dit as 'n nuwe versoek in te voer.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Invoer" + }, + "saveRequest": { + "trigger": "Stoor", + "dialogTitle": "Stoor versoek", + "dialogDescription": "Stoor hierdie versoek in 'n versamelingvouer.", + "labelName": "Versoeknaam", + "placeholderName": "My versoek", + "labelFolder": "Vouer", + "placeholderFolder": "Kies 'n vouer", + "save": "Stoor" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Wysigmodus", + "viewOnlyMode": "Slegs-lees-modus", + "insertComponent": "Voeg komponent in", + "addUnorderedList": "Voeg ongenommerde lys by", + "addOrderedList": "Voeg genommerde lys by", + "addTable": "Voeg tabel by" + }, + "addBlock": "Voeg blok by", + "media": { + "popoverTitle": "Voeg media by", + "singleImage": "Enkele beeld", + "singleImageDesc": "Laai een beeld op", + "multipleImages": "Veelvuldige beelde", + "multipleImagesDesc": "Laai beeldrooster op", + "video": "Video", + "videoDesc": "Laai videolêer op", + "uploadingAlt": "Besig om op te laai...", + "uploadFailedAlt": "Oplaai het misluk" + }, + "export": { + "fabAria": "Voer kode uit", + "title": "Voer kode uit", + "description": "Kopieer die HTML- of JSON-uitset van jou redigeerderinhoud", + "preview": "Voorskou", + "html": "HTML", + "json": "JSON", + "previewOptions": "Voorskou-opsies", + "enhanceSpaces": "Verbeter spasies", + "livePreview": "Lewendige voorskou van weergegee HTML", + "htmlTailwind": "HTML met Tailwind CSS-klasse", + "editorJson": "Redigeerderstatus as JSON", + "copyHtml": "Kopieer HTML", + "copyJson": "Kopieer JSON", + "copied": "Gekopieer!" + }, + "template": { + "fabTitle": "Wissel sjabloon", + "title": "Kies 'n sjabloon", + "description": "Kies 'n sjabloon om vinnig met voorafgeboude uitlegte en inhoud te begin", + "catAll": "Alle sjablone", + "catProductivity": "Produktiwiteit", + "catCreative": "Kreatief", + "catBusiness": "Besigheid", + "catPersonal": "Persoonlik", + "noTemplates": "Geen sjablone in hierdie kategorie gevind nie", + "confirmTitle": "Vervang bestaande inhoud?", + "confirmDescription": "Jy het reeds inhoud in die redigeerder. Om hierdie sjabloon toe te pas sal alle huidige inhoud vervang.", + "cannotUndo": "Hierdie aksie kan nie ongedaan gemaak word nie.", + "cancel": "Kanselleer", + "replaceContent": "Vervang inhoud" + }, + "command": { + "searchPlaceholder": "Soek opdragte...", + "noResults": "Geen opdragte gevind nie.", + "turnInto": "Verander na", + "h1": { + "label": "Opskrif 1", + "description": "Groot afdelingsopskrif", + "keywords": "h1,heading,title,large" + }, + "h2": { + "label": "Opskrif 2", + "description": "Medium afdelingsopskrif", + "keywords": "h2,heading,subtitle" + }, + "h3": { + "label": "Opskrif 3", + "description": "Klein afdelingsopskrif", + "keywords": "h3,heading,subheading" + }, + "h4": { + "label": "Opskrif 4", + "description": "Minuskule afdelingsopskrif", + "keywords": "h4,heading" + }, + "h5": { + "label": "Opskrif 5", + "description": "Kleiner opskrif", + "keywords": "h5,heading" + }, + "h6": { + "label": "Opskrif 6", + "description": "Kleinste opskrif", + "keywords": "h6,heading" + }, + "p": { + "label": "Paragraaf", + "description": "Gewone teksparagraaf", + "keywords": "p,paragraph,text,normal" + }, + "code": { + "label": "Kodeblok", + "description": "Kodefragment", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "Aanhaling", + "description": "Blokaanhaling", + "keywords": "quote,blockquote,citation" + }, + "li": { + "label": "Opsomminglys", + "description": "Eenvoudige lysitem met koeël", + "keywords": "list,bullet,unordered,ul,li" + }, + "ol": { + "label": "Genommerde lys", + "description": "Genommerde lysitem", + "keywords": "list,numbered,ordered,ol,li" + }, + "img": { + "label": "Beeld", + "description": "Laai beeld op of bed dit in", + "keywords": "image,img,picture,photo,upload" + }, + "video": { + "label": "Video", + "description": "Laai video op of bed dit in", + "keywords": "video,vid,movie,mp4,upload" + }, + "table": { + "label": "Tabel", + "description": "Skep 'n tabel", + "keywords": "table,grid,rows,columns,cells" + } + }, + "toasts": { + "tableImported": "Tabel ingevoer", + "tableImportedDesc": "Markdown-tabel is suksesvol ingevoer", + "mediaPasted": "Media geplak", + "pasteMixed": "{imageCount} beeld(s) en {videoCount} video(s) suksesvol geplak.", + "pasteImages": "{count} beeld(s) suksesvol geplak.", + "pasteVideos": "{count} video(s) suksesvol geplak.", + "pasteFailed": "Plak het misluk", + "unexpectedError": "'n Onverwagte fout het voorgekom", + "uploadFailed": "Oplaai het misluk" + }, + "blockBackground": { + "menuItem": "Verander agtergrondkleur", + "dialogTitle": "Blok-agtergrondkleur", + "dialogDescription": "Kies 'n agtergrondkleur vir hierdie blok", + "tabPreset": "Voorafgestelde kleure", + "tabCustom": "Pasgemaakte kleur", + "sectionHeading": "Agtergrondkleure", + "apply": "Pas toe", + "colors": { + "none": "Geen", + "red": "Rooi", + "orange": "Oranje", + "yellow": "Geel", + "green": "Groen", + "blue": "Blou", + "indigo": "Indigo", + "purple": "Pers", + "pink": "Pienk", + "teal": "Teal", + "cyan": "Siaan", + "gray": "Grys" + } + }, + "tableDialog": { + "title": "Skep tabel", + "description": "Skep 'n nuwe tabel of voer in vanaf markdown", + "markdownCheckbox": "Ek het 'n markdown-tabel", + "pasteMarkdownLabel": "Plak markdown-tabel", + "markdownPlaceholder": "| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "Plak jou markdown-tabel hierbo. Moet kop- en skeidingsrye insluit.", + "rows": "Rye", + "columns": "Kolomme", + "maxDimensions": "Maksimum: 20 rye × 10 kolomme", + "cancel": "Kanselleer", + "importTable": "Voer tabel in", + "createTable": "Skep tabel" + }, + "tableParse": { + "errors": { + "minRows": "Tabel moet minstens 'n kopry en skeidingsry hê", + "pipeFormat": "Tabelrye moet met | begin en eindig", + "headerMinCol": "Kopry moet minstens een kolom hê", + "separatorRow": "Tweede ry moet 'n skeiding wees (bv. |---|---|)", + "rowColumnMismatch": "Ry {row} het {got} kolomme, {expected} verwag", + "parseFailed": "Kon nie markdown-tabel ontleed nie" + } + }, + "link": { + "editLink": "Wysig skakel", + "addLink": "Voeg skakel by", + "selectedText": "Gekose teks: \"{text}\"", + "linkUrl": "Skakel-URL", + "urlPlaceholder": "https://example.com", + "update": "Werk by", + "addLinkButton": "Voeg skakel by", + "toastApplied": "Skakel toegepas", + "toastLinkedTo": "Geskakel na: {url}", + "toastRemoved": "Skakel verwyder", + "toastRemovedDesc": "Skakel is uit seleksie verwyder" + }, + "customClass": { + "devMode": "Ontwikkelaarmodus", + "toggleDevModeAria": "Wissel ontwikkelaarmodus", + "searchPlaceholderDev": "Soek klasse... (bv. 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Soek style... (bv. 'red', 'bold', 'shadow')", + "appliesTitle": "Geld vir: {className}", + "noMatches": "Geen klasse gevind wat by \"{query}\" pas nie", + "toastClassReplaced": "Klas vervang", + "toastClassReplacedDesc": "\"{replaced}\" vervang met \"{className}\"", + "toastCustomClassApplied": "Pasgemaakte klas toegepas", + "toastAppliedClass": "Klas toegepas: {className}" + }, + "insertComponents": { + "title": "Voeg komponent in", + "description": "Kies 'n komponent om in jou dokument in te voeg", + "noComponents": "Geen komponente beskikbaar nie", + "categories": { + "media": "Media", + "layout": "Uitleg", + "interactive": "Interaktief", + "decoration": "Versiering" + }, + "freeImage": { + "name": "Beeld met vrye beweging", + "description": "Voeg 'n beeld by wat oral geposisioneer en vrylik herskaal kan word" + } + } + }, + "JsonFormatter": { + "title": "JSON-formateerder", + "description": "Formateer, valideer en wysig JSON in teks- en boom-aansig.", + "documentNameText": "Teksdokument {n}", + "documentNameTree": "Boomdokument {n}", + "newDocument": "Nuut", + "copy": "Kopieer", + "save": "Stoor", + "load": "Laai", + "saving": "Stoor tans...", + "leftDocNameLabel": "Linkerdokumentnaam", + "rightDocNameLabel": "Regterdokumentnaam", + "toastCopiedText": "Tekspaneel gekopieer", + "toastCopiedTree": "Boompaneel gekopieer", + "toastCopyFailed": "Kon nie JSON kopieer nie", + "toastLoginRequired": "Meld asseblief aan om dokumente te stoor", + "toastSaved": "Dokument gestoor", + "toastSaveFailed": "Kon nie dokument stoor nie", + "format": "Formateer", + "compact": "Kompakteer", + "sortKeys": "Sorteer sleutels", + "repair": "Herstel", + "query": "Navraag", + "toastInvalidJson": "Ongeldige JSON", + "toastAlreadyValid": "JSON is reeds geldig", + "toastRepaired": "JSON herstel", + "toastRepairFailed": "Kon nie JSON herstel nie" + }, + "JwtDecoder": { + "title": "JWT-dekodeerder", + "subtitle": "Inspekteer header en payload. Vervaldatum gebruik die exp-eis. Niks word na ’n bediener gestuur nie; die handtekening word nie geverifieer nie.", + "tokenLabel": "Token", + "clear": "Vee uit", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopieer", + "expiryLabel": "Verval (exp)", + "signaturePresent": "Handtekening teenwoordig (nie geverifieer nie)", + "noExp": "Geen exp-eis in die payload nie.", + "status": { + "expired": "Verval", + "valid": "Geldig" + }, + "fields": { + "unix": "Unix", + "local": "Plaaslik", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Uitgereik (iat)", + "notBefore": "Nie voor (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Plak ’n JWT om te dekodeer.", + "segments": "’n JWT benodig minstens ’n header en payload (twee segmente).", + "base64": "Kon nie header of payload base64url-dekodeer nie." + }, + "relative": { + "future": "oor {n} {unit}", + "past": "{n} {unit} gelede" + }, + "units": { + "second": "{count, plural, one {sekonde} other {sekondes}}", + "minute": "{count, plural, one {minuut} other {minute}}", + "hour": "{count, plural, one {uur} other {ure}}", + "day": "{count, plural, one {dag} other {dae}}" + }, + "tabs": { + "decode": "Dekodeer", + "sign": "Generator / ondertekenaar" + }, + "signer": { + "algLabel": "Algoritme (alg)", + "tabs": { + "payload": "Lading", + "header": "Kop" + }, + "payloadLabel": "Lading-JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Kop-JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Geheim (HS*)", + "secretPlaceholder": "Plak jou HMAC-geheim…", + "privateKeyLabel": "Privaat sleutel PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Die \"none\"-algoritme skep ’n ongetekende token. Dit is onveilig en is slegs vir toetsing.", + "sign": "Onderteken", + "verify": "Verifieer handtekening", + "outputLabel": "Token (kompakte JWS)", + "outputPlaceholder": "Klik \"Onderteken\"…", + "copyToken": "Kopieer token", + "copied": "Gekopieer", + "verifyKeyLabel": "Verifikasie-sleutel (opsioneel)", + "verifyKeyPlaceholder": "Vir RS*: plak die publieke sleutel (SPKI) PEM. Vir HS*: los leeg om die geheim te hergebruik.", + "verifyKeyHint": "Indien leeg, gebruik verifikasie die onderteken-sleutel (nuttig vir HS*).", + "verifyStatus": { + "valid": "Handtekening geldig", + "invalid": "Handtekening ongeldig" + }, + "securityNote": "Alles loop plaaslik in jou blaaier; sleutels en tokens word nie opgelaai nie." + }, + "signErrors": { + "invalidJson": "Kop en lading moet geldige JSON wees.", + "unsupportedAlg": "Onondersteunde algoritme.", + "missingKey": "’n Onderteken-sleutel is nodig vir hierdie algoritme.", + "cryptoFailed": "Ondertekening het misluk. Kontroleer jou sleutel-formaat en probeer weer." + }, + "verifyErrors": { + "empty": "Voer ’n JWT in om te verifieer.", + "segments": "’n JWT moet presies 3 segmente hê om ’n handtekening te verifieer.", + "unsupportedAlg": "Onondersteunde of ontbrekende alg in die kop.", + "missingKey": "’n Verifikasie-sleutel is nodig vir hierdie algoritme.", + "base64": "Kon nie die JWT-kop of handtekening dekodeer nie.", + "cryptoFailed": "Verifikasie het misluk. Kontroleer jou sleutel-formaat en probeer weer." + } + }, + "CertificatePemDecoder": { + "title": "Sertifikaat / PEM-dekodeerder", + "subtitle": "Plak 'n X.509-sertifikaat (-----BEGIN CERTIFICATE-----) of 'n CSR (-----BEGIN CERTIFICATE REQUEST-----). Onderwerp, uitreiker, datums en SANs word lokaal ontleed; niks word opgelaai nie.", + "pemLabel": "PEM-invoer", + "clear": "Maak skoon", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Ontleed…", + "copyHex": "Kopieer vingerafdruk (heks, sonder dubbelpunte)", + "errors": { + "empty": "Plak 'n PEM-sertifikaat of CSR om te dekodeer.", + "noBlock": "Geen PEM-sertifikaat- of CSR-blok gevind nie.", + "parseFailed": "Kon nie hierdie PEM-data ontleed nie." + }, + "block": { + "title": "PEM-blok {index}", + "certificate": "X.509-sertifikaat", + "csr": "Sertifikaatondertekeningsversoek" + }, + "fields": { + "subject": "Onderwerp", + "issuer": "Uitreiker", + "notBefore": "Nie geldig voor", + "notAfter": "Nie geldig na", + "serial": "Serienommer (heks)", + "sha256": "SHA-256-vingerafdruk", + "sans": "Onderwerp alternatiewe name", + "sansNone": "Geen SAN-uitbreiding nie.", + "signatureAlgorithm": "Handtekening-algoritme", + "csrIssuerNote": "CSR's het geen uitreiker nie (dit verskyn op die uitgereikte sertifikaat).", + "csrValidityNote": "CSR's het geen geldigheidsdatums nie (dit verskyn op die uitgereikte sertifikaat)." + }, + "sanType": { + "dns": "DNS", + "dn": "Gidsnaam", + "email": "Email", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Registered ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL-formateerder", + "subtitle": "Formateer SQL vir MySQL, PostgreSQL of SQLite. Loop in jou blaaier; niks word opgelaai nie.", + "dialectLabel": "Dialek", + "keywordsLabel": "Sleutelwoorde", + "indentLabel": "Inkeep", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "HOOFLETTERS", + "lower": "kleinletters", + "preserve": "Behou" + }, + "spaces": "{n} spasies", + "format": "Formateer", + "clear": "Vee uit", + "counter": "{current} / {max} karakters", + "inputPanel": "Invoer", + "formattedPanel": "Geformateer", + "copy": "Kopieer", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klik \"Formateer\"…", + "errors": { + "tooLong": "Invoer is beperk tot {max} karakters.", + "couldNotFormat": "Kon nie hierdie SQL formateer nie." + } + }, + "YamlFormatter": { + "title": "YAML Formatteerder", + "subtitle": "Formateer, valideer en omskep YAML in jou blaaier. Niks word opgelaai nie.", + "outputLabel": "Uitvoer", + "outputModes": { + "yaml": "YAML (geformateer)", + "json": "JSON" + }, + "indentLabel": "Inkeping", + "spaces": "{n} spasies", + "format": "Formateer", + "clear": "Vee uit", + "copy": "Kopieer", + "counter": "{current} / {max} karakters", + "inputPanel": "Invoer", + "outputPanel": "Uitvoer", + "errors": { + "tooLong": "Invoer is beperk tot {max} karakters.", + "couldNotFormat": "Kon hierdie YAML nie ontleed nie." + } + }, + "GraphqlFormatter": { + "title": "GraphQL-formateerder", + "subtitle": "Formateer GraphQL-navrae, mutasies en intekeninge in jou blaaier. Niks word opgelaai nie.", + "tabs": { + "format": "Formateer", + "build": "Navrae-bouer" + }, + "outputModeLabel": "Afvoer", + "outputModes": { + "pretty": "Leesbare druk", + "minify": "Minifiseer" + }, + "format": "Formateer", + "clear": "Maak skoon", + "counter": "{current} / {max} karakters", + "inputPanel": "Invoer", + "outputPanel": "Afvoer", + "copy": "Kopieer", + "builderHint": "Stel 'n raamwerk saam en pas dit op die redigeerder toe. Veranderlike-definisies gebruik GraphQL-sintaks (bv. $id: ID!) — hakies is opsioneel.", + "builder": { + "operation": "Operasie", + "operationName": "Operasienaam", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Veranderlike-definisies", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Keuseset", + "apply": "Pas toe op invoer", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Invoer is beperk tot {max} karakters.", + "couldNotFormat": "Kon nie hierdie GraphQL ontleed of druk nie.", + "emptySelection": "Voeg eers 'n keuseset by voordat jy toepas." + } + }, + "RegexTester": { + "title": "Regex-toetser", + "subtitle": "JavaScript-styl regex teen jou teks, lewendig. Treffer word gemerk; niks verlaat jou blaaier nie.", + "patternLabel": "Patroon", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g globaal", + "i": "i ignoreer hoof-/kleinletters", + "m": "m multireël", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(leeg)", + "noFlags": "—", + "matches": "{count, plural, one {treffer} other {treffers}}", + "testTextLabel": "Toetstekst", + "counter": "{current} / {max}", + "clear": "Vee uit", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Teks is beperk tot {max} karakters.", + "invalid": "Ongeldige regex.", + "runaway": "Gestop ná {max} treffers (moontlike wegholpatroon)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Tydstempel-omsetter", + "subtitle": "Unix-sekondes (≤10 syfers) of millisekondes (langer heelgetalle), ISO-8601 of stringe wat jou blaaier kan ontleed. Alles loop plaaslik.", + "inputLabel": "Invoer", + "placeholder": "1744200000 of 2025-04-09T12:00:00Z", + "now": "Nou", + "copy": "Kopieer", + "hint": "Plak ’n waarde of klik Nou.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relatief", + "human": "Leesbaar" + }, + "fields": { + "seconds": "Sekondes", + "milliseconds": "Millisekondes", + "utc": "UTC", + "local": "Plaaslik", + "distance": "Afstand" + }, + "errors": { + "empty": "Voer ’n Unix-tydstempel, ISO-string of datumteks in.", + "invalidNumber": "Ongeldige numeriese tydstempel.", + "outOfRange": "Buite omvang vir JavaScript Date.", + "couldNotParse": "Kon nie as Unix, ISO-8601 of blaaier-datumstring ontleed nie." + } + }, + "HttpStatusCodes": { + "title": "HTTP-statuskodes", + "subtitle": "Soek en blaai deur HTTP-responsstatuskodes (1xx5xx) met kort beskrywings en RFC-verwysings. Loop plaaslik.", + "header": { + "title": "HTTP-statuskodes", + "description": "Deursoekbare verwysing van HTTP-responskodes met beskrywings en RFC-verwysings." + }, + "searchPlaceholder": "Soek: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filter klas…", + "classFilterAll": "Alle klasse", + "resultsCount": "{count} van {total}", + "table": { + "code": "Code", + "phrase": "Frase", + "description": "Beskrywing", + "copy": "Kopieer" + }, + "noResults": "Geen resultate vir “{query}”.", + "specPrefix": "Spes.:", + "copied": "Kode gekopieer" + }, + "CronBuilder": { + "title": "Cron-uitdrukbouer", + "subtitle": "Standaard 5-veld cron: minute hour day month weekday. Die ontleder gebruik seconds=0 intern. Opsionele 6-veld (sec min hour dom mon dow) werk in die uitdrukredigeerder.", + "tabs": { + "builder": "Visuele bouer", + "expression": "Uitdrukking" + }, + "presetsLabel": "Voorafinstellings", + "fieldsLabel": "Velde", + "rawExpressionLabel": "Rou uitdrukking", + "expressionHint": "Verlaat die veld om te ontleed en die bouer te sinkroniseer. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Huidige uitdrukking", + "copy": "Kopieer", + "quickPick": "Vinnige keuse", + "emptyExpression": "—", + "plainLanguageLabel": "Gewone taal", + "nextRunsLabel": "Volgende lopies (plaaslike tyd)", + "fields": { + "minute": "Minuut", + "hour": "Uur", + "dayOfMonth": "Dag van maand", + "month": "Maand", + "dayOfWeek": "Dag van week" + }, + "presets": { + "everyMinute": "Elke minuut", + "every5Minutes": "Elke 5 minute", + "every15Minutes": "Elke 15 minute", + "everyHour": "Elke uur (by :00)", + "every6Hours": "Elke 6 uur", + "dailyMidnight": "Daagliks om middernag", + "daily0900": "Daagliks om 09:00", + "weekdays0900": "Weekdae om 09:00", + "weeklySunday": "Weekliks (Sondag 00:00)", + "monthlyFirst": "Maandeliks (1ste 00:00)", + "yearlyJan1": "Jaarliks (1 Jan 00:00)" + }, + "picks": { + "minute": { + "every": "elke minuut" + }, + "hour": { + "every": "elke uur" + }, + "dom": { + "any": "enige dag", + "last": "laaste dag" + }, + "month": { + "every": "elke maand" + }, + "dow": { + "any": "enige weekdag", + "weekdays": "weekdae", + "weekend": "naweek" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mrt", + "apr": "Apr", + "may": "Mei", + "jun": "Jun", + "jul": "Jul", + "aug": "Aug", + "sep": "Sep", + "oct": "Okt", + "nov": "Nov", + "dec": "Des" + }, + "weekdays": { + "sun": "So", + "mon": "Ma", + "tue": "Di", + "wed": "Wo", + "thu": "Do", + "fri": "Vr", + "sat": "Sa" + }, + "errors": { + "fieldCount": "Verwag 5 velde (minute hour day month weekday) of 6 insluitend sekondes.", + "invalid": "Ongeldige cron-uitdrukking." + } + }, + "Base64": { + "title": "Base64-enkodeerder / -dekodeerder", + "subtitle": "Enkodeer teks na Base64 of dekodeer Base64-stringe", + "upload": "Laai op", + "clear": "Vee uit", + "modes": { + "encode": "Enkodeer", + "decode": "Dekodeer" + }, + "swapTitle": "Ruil invoer en uitvoer om", + "panels": { + "plainText": "Gewone teks", + "base64Input": "Base64-invoer", + "base64Output": "Base64-uitvoer", + "decodedText": "Gedekodeerde teks" + }, + "placeholders": { + "encode": "Voer teks in om na Base64 te enkodeer…", + "decode": "Plak Base64-string om te dekodeer…" + }, + "dropHere": "Laat lêer hier val", + "charCount": "{count} karakters", + "download": { + "title": "Laai uitvoer af", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Kopieer na knipbord", + "outputPlaceholder": "Uitvoer sal hier verskyn…", + "errors": { + "invalidBase64": "Ongeldige Base64-string. Kontroleer invoer.", + "encodeFailed": "Kon nie invoer enkodeer nie." + } + }, + "UrlEncode": { + "title": "URL-enkodeerder / -dekodeerder", + "subtitle": "Persentasie-enkodeer/-dekodeer met encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Laai op", + "clear": "Vee uit", + "modes": { + "encode": "Enkodeer", + "decode": "Dekodeer" + }, + "swapTitle": "Ruil invoer en uitvoer om", + "panels": { + "plainText": "Gewone teks", + "encodedInput": "Gekodeerde invoer", + "encodedOutput": "Gekodeerde uitvoer", + "decodedText": "Gedekodeerde teks" + }, + "placeholders": { + "encode": "Voer teks in om persentasie te enkodeer…", + "decode": "Plak persentasie-gekodeerde teks om te dekodeer…" + }, + "dropHere": "Laat lêer hier val", + "charCount": "{count} karakters", + "download": { + "title": "Laai uitvoer af", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Kopieer na knipbord", + "outputPlaceholder": "Uitvoer sal hier verskyn…", + "errors": { + "invalidEncoding": "Ongeldige persentasie-enkodering. Kontroleer onvolledige %‑pare of escapes.", + "encodeFailed": "Kon nie enkodeer nie (ongeldige Unicode in invoer)." + } + }, + "UrlParser": { + "title": "URL-ontleder", + "subtitle": "Breek ’n URL af in protokol, gasheer, pad, navraagparameters en hash (fragment).", + "upload": "Laai op", + "clear": "Maak skoon", + "panels": { + "input": "URL-invoer", + "output": "Ontleding" + }, + "placeholders": { + "input": "Plak ’n URL (bv. https://example.com/path?foo=bar#section) of gasheer/pad (bv. example.com/path)…" + }, + "dropHere": "Laat lêer hier val", + "charCount": "{count} karakters", + "outputPlaceholder": "Ontlede komponente sal hier verskyn…", + "fields": { + "protocol": "Protokol", + "host": "Gasheer", + "path": "Pad", + "hash": "Hash", + "queryParams": "Navraagparameters" + }, + "paramCount": "{count} parameters", + "noParams": "Geen navraagparameters nie.", + "paramKey": "Sleutel", + "paramValue": "Waarde", + "copyTitle": "Kopieer na knipbord", + "copyParamTitle": "Kopieer as key=value", + "download": { + "title": "Laai JSON af", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Ongeldige URL. Sluit ’n gasheernaam in (example.com) en kontroleer karakters." + } + }, + "UuidGenerator": { + "title": "UUID / ULID-generator", + "subtitle": "Genereer RFC 9562-UUID’s of ULID’s in die blaaier. Grootmaat-uitvoer tot {max} per lopie.", + "format": "Formaat", + "kindHints": { + "ulid": "Sorteerbare, URL-veilige 26-karakter identifiseerder", + "uuid4": "Ewekansig (RFC 9562)", + "uuid7": "Tyd-georden, Unix ms + ewekansig", + "uuid1": "MAC/tyd-gebaseer (RFC 9562)", + "uuid6": "Herordende Gregoriaanse tyd-gebaseer", + "uuid3": "MD5-hash van naam + naamruimte", + "uuid5": "SHA‑1-hash van naam + naamruimte", + "nil": "Nul-UUID (alles nulle, RFC 9562)" + }, + "countLabel": "Aantal (1–{max})", + "nameLabel": "Naam (invoerstring)", + "namePlaceholder": "bv. my.app/resource", + "bulkHint": "Grootmaat voeg #0, #1, … by sodat elke ID uniek is (v3/v5 is deterministies).", + "namespace": "Naamruimte", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Pasgemaakte UUID" + }, + "customNamespaceLabel": "Naamruimte-UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Genereer", + "output": "Uitvoer", + "lines": "{count} lyne", + "download": { + "title": "Laai .txt af", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Kopieer alles", + "uppercaseToggle": "Hoofletters-uitset", + "hyphensToggle": "Wys koppeltekens", + "copyJsonTitle": "Kopieer as JSON-skikking", + "outputPlaceholder": "Klik \"Genereer\"…", + "errors": { + "invalidCustomNamespace": "Pasgemaakte naamruimte moet ’n geldige UUID wees.", + "missingName": "Naam is vereis vir UUID v3 en v5.", + "unknown": "Kon nie ID’s genereer nie." + } + }, + "SecretApiKeyGenerator": { + "title": "Geheim- / API-sleutelgenerator", + "subtitle": "Genereer kriptografies ewekansige stringe met aanpasbare lengte en alfabet. Massa-uitvoer tot {max} per keer. Werk saam met die UUID-/ULID-generator.", + "alphabetLabel": "Alfabet", + "alphabetHint": "Duplikaatkarakters word verwyder. Elke simbool word eweredig getrek met crypto.getRandomValues en verwerpingsteekproef (geen modulo-skeefheid).", + "alphabetCount": "{count} unieke simbole", + "presetsLabel": "Voorafinstellings", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumeries", + "hex": "Heks (kleinletters)", + "asciiPrintable": "ASCII drukbaar" + }, + "lengthLabel": "Lengte per geheim (1–{maxLen})", + "countLabel": "Aantal (1–{maxBulk})", + "generate": "Genereer", + "output": "Uitset", + "lines": "{count} reëls", + "download": { + "title": "Laai .txt af", + "filename": "geheime.txt" + }, + "copyTitle": "Kopieer alles", + "outputPlaceholder": "Klik \"Genereer\"…", + "entropy": "{bits} bisse entropie", + "strength": { + "weak": "Swak", + "fair": "Redelik", + "strong": "Sterk" + }, + "excludeAmbiguous": "Sluit dubbelsinnige karakters uit", + "excludeAmbiguousHint": "Verwyder {chars} — karakters wat maklik verkeerd gelees word of die shell vyandiggesind is.", + "prefixLabel": "Sleutelvoorvoegsel (opsioneel)", + "errors": { + "emptyAlphabet": "Voeg minstens een karakter by die alfabet.", + "alphabetTooLong": "Na duplikate verwyder is, mag die alfabet hoogstens {maxAlpha} karakters hê.", + "lengthOutOfRange": "Lengte moet tussen 1 en {maxLen} wees.", + "countOutOfRange": "Aantal moet tussen 1 en {maxBulk} wees.", + "unknown": "Kon nie geheime genereer nie." + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum Generator", + "subtitle": "Genereer plaasvervanger Latynse teks vir jou uitlegte en prototipes.", + "paragraphs": "Paragrawe", + "sentences": "Sinne", + "words": "Woorde", + "count": "Aantal", + "generate": "Genereer", + "copy": "Kopieer", + "startWith": "Begin met 'Lorem ipsum...'", + "outputPlaceholder": "Uitset sal hier verskyn...", + "outputType": "Uitsettype", + "bulletList": "Kolletjielys", + "htmlMarkup": "HTML-opmaak", + "htmlMarkupHint": "Omhul uitset in paragrawe of gebruik 'n lys vir kolletjiemodus", + "output": "Uitset", + "download": "Laai af" + }, + "ColorPicker": { + "title": "Kleurkeuser en omskakelaar", + "subtitle": "Skakel HEX, RGB en HSL om; bou harmoniese palette. Alle verwerking bly in jou blaaier.", + "nativePicker": "Inheemse keuser", + "chooseColor": "Kies kleur", + "eyeDropper": "Oogdruppelaar", + "invalidHex": "Ongeldige HEX-kleur.", + "cssValues": "CSS-waardes", + "palettes": "Palette", + "copied": "Gekopieer", + "clickToCopy": "Klik om te kopieer", + "copyAllHex": "Kopieer alle HEX", + "copy": "Kopieer", + "copyColor": "Kopieer {color}", + "paletteShades": "Skakerings", + "paletteComplementary": "Komplementêr", + "paletteTriadic": "Triadies", + "paletteAnalogous": "Analoog", + "paletteSplit": "Gesplete komp.", + "paletteSquare": "Vierkantig" + }, + "ContrastChecker": { + "title": "Kontrasttoetser", + "subtitle": "Bereken die WCAG 2.1-kontrastverhouding tussen voorgrond en agtergrond. Wys AA en AAA vir normale en groot teks. Alles in jou blaaier.", + "foreground": "Teks / voorgrond", + "background": "Agtergrond", + "hexPlaceholder": "#RRGGBB", + "swap": "Ruil kleure", + "chooseForeground": "Kies voorgrondkleur", + "chooseBackground": "Kies agtergrondkleur", + "eyeDropper": "Druppelaar", + "ratio": "Kontrastverhouding", + "copyRatio": "Kopieer verhouding", + "copied": "Gekopieer", + "previewHeading": "Voorskou", + "previewBody": "Die quick brown fox jumps over the lazy dog. My vrou se skoene het ses sole en haar duim is groen.", + "previewCaption": "Voorbeeldteks by verstekgrootte.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — normale teks (4,5:1)", + "levelAALarge": "AA — groot teks (3:1)", + "levelAAANormal": "AAA — normale teks (7:1)", + "levelAAALarge": "AAA — groot teks (4,5:1)", + "pass": "Voldoen", + "fail": "Voldoen nie", + "invalidHex": "Ongeldige HEX-kleur.", + "largeTextHint": "Groot teks: 18 pt+ gewone of 14 pt+ vet (WCAG-definisies)." + }, + "DiffChecker": { + "title": "Verskilvindeling", + "subtitle": "Reël-vir-reël-vergelyking van twee tekste langs mekaar. Alles bly in jou blaaier.", + "rowsCount": "{count} rye", + "truncatedWarning": "Wys eerste {max} rye.", + "limitExceeded": "Elke kant is beperk tot {max} karakters.", + "clearBoth": "Verwyder albei", + "original": "Oorspronklik", + "modified": "Gewysig", + "originalPlaceholder": "Plak oorspronklike teks…", + "modifiedPlaceholder": "Plak gewysigde teks…", + "comparisonLabel": "Sy-aan-sy vergelyking", + "emptyState": "Voer teks in beide panele in om te vergelyk." + }, + "JsonSchemaGenerator": { + "title": "JSON-skemagenerator", + "subtitle": "Plak geldige JSON om 'n JSON-skema of getiketteerde modelle te genereer. Alles loop in jou blaaier.", + "languageLabel": "Uittaal", + "inputLabel": "JSON-invoer", + "inputPlaceholder": "Plak JSON hier…", + "outputLabel": "Gegenereerde uitset", + "outputPlaceholder": "Die skema of gegenereerde tipes verskyn hier…", + "parseError": "Ongeldige JSON", + "clear": "Maak skoon", + "copy": "Kopieer", + "copied": "Na knipbord gekopieer", + "copyFailed": "Kon nie kopieer nie", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-skema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Laai redigeerder…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Beeld na Base64", + "subtitle": "Los 'n prent, kry 'n data-URI of rou Base64-string. Alles bly in jou blaaier.", + "upload": "Laai prent op", + "clear": "Duidelik", + "modes": { + "dataUri": "Data URI", + "rawString": "Rou basis64" + }, + "panels": { + "imageInput": "Beeld-invoer", + "output": "Basis64 Uitset" + }, + "dropHere": "Los prent hier", + "charCount": "{tel} tekens", + "placeholders": { + "uploadImage": "Sleep en los 'n prent of klik oplaai om te begin." + }, + "fileTooLarge": "Lêer is te groot. Verwerking kan jou blaaier vries.", + "copyTitle": "Kopieer na knipbord", + "download": { + "title": "Laai teks af", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Beeldkompressor", + "subtitle": "Verklein JPEG-, PNG- of WebP-uitset plaaslik met kwaliteitbeheer — niks word opgelaai nie.", + "upload": "Laai beeld op", + "clear": "Maak skoon", + "dropHere": "Los beeld hier", + "placeholders": { + "uploadImage": "Sleep en los ’n beeld, of klik om ’n lêer te kies." + }, + "fileTooLarge": "Lêer is te groot (maks {maxMb} MB).", + "unsupportedFormat": "Hierdie lêertipe word nie ondersteun nie. Gebruik JPEG, PNG, WebP, GIF, AVIF of BMP.", + "compressFailed": "Kon nie hierdie beeld komprimeer nie. Probeer ’n ander lêer of formaat.", + "panels": { + "original": "Oorspronklik", + "compressed": "Gekompakteerde voorskou" + }, + "outputFormat": "Uitsetformaat", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kwaliteit", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG is verliesvry in die blaaier; die kwaliteitsglyer geld slegs vir JPEG en WebP.", + "sizes": { + "original": "Oorspronklik: {size}", + "compressed": "Gekompakteer: {size}", + "saved": "{percent}% kleiner" + }, + "largerWarning": "Uitset is groter as die invoer — probeer ’n ander formaat of kwaliteit.", + "download": "Laai af", + "downloadFilename": "gekomprimeerde-beeld", + "processing": "Komprimeer…", + "emptyCompressed": "Gekompakteerde voorskou verskyn hier." + }, + "CssGradientBuilder": { + "title": "CSS Gradient Builder", + "subtitle": "Visuele CSS-gradiëntbouer met hoekbeheer, kleurstops en een-klik CSS-kopie.", + "preview": "Gradiëntvoorskou", + "settings": "Instellings", + "type": "Tik", + "linear": "Lineêr", + "radial": "Radiaal", + "angle": "Hoek", + "stops": "Kleur stop", + "color": "Kleur", + "position": "Posisie (%)", + "addStop": "Voeg Stop by", + "deleteStop": "Vee Stop uit", + "output": "CSS Uitset", + "copy": "Kopieer CSS", + "copied": "Gekopieer!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Generator", + "subtitle": "Genereer saamgevoegde .gitignore-lêers onmiddellik vir 'n spesifieke tegnologiestapel of kombinasie.", + "searchPlaceholder": "Soek stapels (bv. Node, Python, macOS)...", + "noResults": "Geen stapels gevind nie.", + "selectedStacks": "Geselekteerde stapels", + "output": "Voorskou Uitset", + "copy": "Kopieer na knipbord", + "copied": "Gekopieer!", + "download": "Aflaai .gitignore", + "downloading": "Laai tans af...", + "errorFetching": "Kon nie toplys haal nie.", + "errorGenerating": "Kon nie .gitignore vanaf geselekteerde stapels genereer nie.", + "generating": "Genereer tans …", + "emptyState": "Kies een of meer tegnologiese stapels om jou .gitignore te genereer." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Laai CSV of Excel op, wysig JSON, laai dan CSV of XLSX af. Slegs tabeldata; Excel-datums word ISO-tydstempels in JSON.", + "dropTitle": "Los ’n CSV- of Excel-lêer hier", + "dropHint": "Eerste werkblad word vir .xlsx / .xls gebruik. CSV moet ’n opskrifry hê.", + "chooseFile": "Kies lêer", + "jsonLabel": "JSON (skikking van voorwerpe)", + "jsonPlaceholder": "Plak ’n JSON-skikking van voorwerpe of voer CSV / Excel in…", + "loadSample": "Laai voorbeeld", + "actions": { + "format": "Formateer JSON", + "clear": "Maak skoon", + "copy": "Kopieer", + "copied": "Gekopieer", + "downloadCsv": "Laai CSV af", + "downloadXlsx": "Laai Excel af" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Data", + "toastLoadedCsv": "CSV in redigeerder gelaai", + "toastLoadedExcel": "Excel in redigeerder gelaai", + "toastFormatted": "JSON geformateer", + "toastExportedCsv": "CSV afgelaai", + "toastExportedXlsx": "Excel afgelaai", + "toastCopied": "Na knipbord gekopieer", + "footnote": "Alles loop in jou blaaier. JSON moet ’n nie-leë skikking van voorwerpe wees. Geneste waardes word as JSON in CSV-selle gestoor.", + "errors": { + "unsupportedFile": "Gebruik ’n .csv-, .xlsx- of .xls-lêer.", + "parseFailed": "Kon nie hierdie lêer ontleed nie.", + "invalidJson": "Ongeldige JSON", + "exportFailed": "Uitvoer het misluk", + "copyFailed": "Kon nie kopieer nie" + } + }, + "SnippetManager": { + "title": "Kodesnippet-bestuurder", + "subtitle": "Bewaar snippette in jou blaaier of meld aan om dit met jou rekening te sinkroniseer. Wissel tussen bekyk en wysig, kopieer met een klik, kies taal of outo-opsporing, en formatteer JSON, SQL of Monaco-ondersteunde kode.", + "snippets": "Snippette", + "newSnippet": "Nuwe snippet", + "newSnippetTitle": "Nuwe snippet", + "defaultSnippetTitle": "Welkom", + "defaultSnippetCode": "// Welkom by kodesnippette\n// Probeer bekyk vs wysig, kopieer, formateer en outo-taal\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Soek snippette…", + "searchAria": "Soek snippette", + "emptySearch": "Geen snippette pas by jou soektog nie.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Snippett-naam", + "language": "Taal", + "detectedHint": "Beklemtoning as {lang}", + "badgeAuto": "Outo · {lang}", + "modeView": "Bekyk", + "modeEdit": "Wysig", + "copy": "Kopieer", + "copied": "Gekopieer", + "format": "Formateer", + "cancel": "Kanselleer", + "deleteTitle": "Snippett uitvee?", + "deleteDescription": "“{title}” sal uit hierdie blaaier verwyder word.", + "deleteConfirm": "Vee uit", + "editorAria": "Snippett-kode-redigeerder", + "editorLoading": "Laai redigeerder…", + "toastCopied": "Gekopieer na knipbord", + "toastCopyFailed": "Kon nie kopieer nie", + "toastNothingToCopy": "Niks om te kopieer nie", + "toastFormatted": "Geformatteer", + "toastFormatFailed": "Kon nie formateer nie", + "toastFormatJsonInvalid": "Ongeldige JSON — herstel eers sintaksis", + "toastFormatUnsupported": "Geen formateerder vir hierdie taal nie — probeer JSON of SQL of wysig-modus", + "toastDeleted": "Snippett uitgevee", + "toastLoadFailed": "Kon nie snippette van die bediener laai nie.", + "toastSyncFailed": "Kon nie met die bediener sinkroniseer nie.", + "languages": { + "auto": "Outomatiese opsporing", + "plaintext": "Gewone teks", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL-kliënt", + "dialogTitle": "SQL-verbindings", + "mobileSheetTitle": "SQL-verkenner", + "newTabLabel": "Nuwe navraag-oortjie", + "vaultLockedTitle": "Kluis gesluit", + "vaultLockedDesc": "Ontsluit die kluis om toegang te kry tot gestoorde verbindings.", + "heroTitle": "SQL-kliënt", + "heroDesc": "Koppel aan PostgreSQL, MySQL of MariaDB. Geloofsbriewe word in die blaaier geënkripteer.", + "heroAddConnection": "Voeg verbinding by", + "selectTableTitle": "Kies 'n tabel of maak 'n navraag-oortjie oop", + "selectTableDesc": "Klik op 'n tabel in die sybalk of gebruik ⌘↩.", + "toastLoadFail": "Verbindings kon nie gelaai word nie." + }, + "connection": { + "titleNew": "Nuwe SQL-verbinding", + "titleEdit": "Wysig verbinding", + "labelDbType": "Databasistype", + "labelName": "Verbindingsnaam", + "placeholderName": "My produksiedatabasis", + "labelHostPort": "Gasheer en poort", + "labelDatabase": "Databasis", + "placeholderDatabase": "mydb", + "labelUsername": "Gebruikersnaam", + "labelPassword": "Wagwoord", + "labelSsl": "Aktiveer SSL / TLS", + "sslDesc": "Enkripteer die verbinding na die bediener", + "securityNote": "Geloofsbriewe word AES-256 in die blaaier geënkripteer voor stoor", + "btnTest": "Toets verbinding", + "btnSave": "Stoor en verbind", + "btnUpdate": "Opdateer", + "btnCancelEdit": "Kanselleer wysiging", + "savedConnections": "Gestoorde verbindings", + "emptySaved": "Gestoorde verbindings verskyn hier", + "confirmDelete": "Hierdie verbinding verwyder?", + "toastFillFields": "Vul gasheer en databasis in voor toetsing.", + "toastConnectOk": "Verbind! {version}", + "toastConnectFail": "Verbinding misluk: {message}", + "toastUnlockVault": "Ontsluit eers die kluis.", + "toastFieldsRequired": "Gasheer en databasis is vereis.", + "toastUpdated": "Verbinding opgedateer.", + "toastSaved": "Verbinding gestoor.", + "toastSaveFail": "Stoor misluk: {message}", + "toastDeleted": "Verbinding verwyder.", + "toastDeleteFail": "Kon nie verbinding verwyder nie." + }, + "sidebar": { + "title": "SQL-verkenner", + "tooltipAddConnection": "Voeg verbinding by", + "tooltipRefreshSchema": "Verfris skema", + "noConnections": "Nog geen gestoorde verbindings nie", + "toastSchemaFail": "Skema kon nie gelaai word nie: {message}" + }, + "editor": { + "btnRun": "Voer uit", + "executing": "Navraag word uitgevoer…", + "emptyTitle": "Voer 'n navraag uit om resultate te sien", + "emptyHint": "Druk ⌘↩ of klik Voer uit", + "toastNoConnection": "Geen aktiewe verbinding nie." + }, + "results": { + "filterPlaceholder": "Filtreer resultate…", + "rowCount": "{count} ry(e)", + "rowCountFiltered": "{filtered} / {total} rye", + "exportCsv": "CSV", + "noRows": "Geen rye teruggekeer nie" + } + }, + "QrCodeGenerator": { + "title": "QR-kodegenerator", + "subtitle": "Maak enige teks of URL 'n PNG QR-kode. Alles word plaaslik in jou blaaier weergegee.", + "contentLabel": "Inhoud", + "contentPlaceholder": "https://voorbeeld.com, plat teks, Wi‑Fi-string, vCard …", + "styleLabel": "Styl", + "dotTypeLabel": "Kolletjievorm", + "dotTypeSquare": "Vierkant", + "dotTypeDots": "Kolletjies", + "dotTypeRounded": "Gerond", + "dotTypeExtraRounded": "Ekstra gerond", + "dotTypeClassy": "Klassiek", + "dotTypeClassyRounded": "Klassiek gerond", + "cornerSquareLabel": "Hoekvierkante", + "cornerSquareDot": "Kolletjie", + "cornerSquareSquare": "Vierkant", + "cornerSquareExtraRounded": "Ekstra gerond", + "cornerDotLabel": "Hoekkolletjies", + "cornerDotDot": "Kolletjie", + "cornerDotSquare": "Vierkant", + "eccLabel": "Foutkorreksie", + "eccL": "Laag (~7%)", + "eccM": "Medium (~15%)", + "eccQ": "Kwartiel (~25%)", + "eccH": "Hoog (~30%)", + "marginLabel": "Kantlyn (modules)", + "widthLabel": "Beeldbreedte", + "widthEffective": "Word weergegee teen {px} px", + "darkColor": "Donker modules", + "lightColor": "Lig modules", + "resetColors": "Stel kleure terug", + "logoLabel": "Middelste logo", + "logoMarginLabel": "Marge", + "uploadLogo": "Laai prent op", + "removeLogo": "Verwyder logo", + "downloadPng": "Laai PNG af", + "downloadSvg": "Laai SVG af", + "copyImage": "Kopieer beeld", + "copied": "Gekopieer", + "clear": "Maak skoon", + "previewAlt": "QR-kode voorskou", + "emptyHint": "Tik of plak inhoud om jou QR-kode te sien.", + "filename": "qr-kode.png", + "errors": { + "generationFailed": "Kon nie hierdie QR-kode skep nie. Probeer korter teks of laer foutkorreksie.", + "copyUnsupported": "Jou blaaier ondersteun nie die kopieer van beelde na die knipbord nie.", + "copyFailed": "Kon nie na die knipbord kopieer nie." + } + }, + "IpSubnetCalculator": { + "title": "IP-/subnetrekenaar", + "subtitle": "IPv4- en IPv6-CIDR in jou blaaier—geen netwerkversoeke nie.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Netwerk", + "placeholderIpv4": "10.0.0.0/24 of 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 of 2001:db8::1/64", + "hintIpv4": "Gebruik CIDR (bv. /24) of ’n IPv4-adres plus ’n puntgedeelte-netmasker.", + "hintIpv6": "Sluit 'n prefikslengte in (bv. /64). Sonder dit word /128 veronderstel.", + "copyValue": "Kopieer waarde", + "rows": { + "cidr": "CIDR", + "network": "Netwerkadres", + "netmask": "Subnetmasker", + "wildcard": "Wildcard-masker", + "broadcast": "Uitsaai", + "firstHost": "Eerste gasheer", + "lastHost": "Laaste gasheer", + "totalAddresses": "Totale adresse", + "usableHosts": "Bruikbare gasheer-adresse", + "prefixLength": "Prefikslengte (grepe)", + "networkIpv6": "Netwerkadres", + "lastIpv6": "Laaste adres in reeks", + "totalAddressesIpv6": "Adresse in subnet" + }, + "usableNote": { + "pointToPoint": "/31 punt-tot-punt skakels gebruik beide adresse as gasheer (RFC 3021).", + "singleHost": "/32 is vir ’n enkele gasheer." + }, + "ipv6RangeHint": { + "normal": "Die wiskundige reeks loop van die netwerk tot die laaste adres; roeter- en anycast-reëls wissel per netwerk.", + "single": "/128 is ’n enkele adres.", + "full": "/0 dek alle IPv6-adresse." + }, + "errors": { + "invalidIpv4": "Ongeldige IPv4-netwerk. Probeer CIDR (10.0.0.0/24) of adres + netmasker.", + "invalidIpv6": "Ongeldige IPv6-netwerk. Gebruik adres/prefiks, bv. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Hash-generator", + "subtitle": "MD5-, SHA-1- en SHA-2-verslae vir teks of lêers; bcrypt vir wagwoorde (teksinvoer). Alles loop plaaslik in jou blaaier.", + "algorithmLabel": "Algoritme", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Teks", + "modeFile": "Lêer", + "inputLabelText": "Invoer", + "placeholderText": "Tik of plak teks om te hash…", + "chooseFile": "Kies lêer", + "clearFile": "Vee uit", + "fileHint": "Lêers word net in geheue gelees; maks {maxMb} MB per lêer. Niks word opgelaai nie.", + "outputLabel": "Versnit (hex)", + "copyHash": "Kopieer versnit", + "copied": "Gekopieer", + "byteCount": "{count, plural, one {# greep} other {# grepe}}", + "charCount": "{count, plural, one {# karakter} other {# karakters}}", + "emptyHint": "Voer teks in of kies ’n lêer om die hash te sien.", + "securityNote": "MD5 en SHA-1 is nie meer vir nuwe veiligheidsgebruik aanbeveel nie; verkies SHA-256 of SHA-512.", + "errors": { + "fileTooLarge": "Lêer is groter as {maxMb} MB.", + "fileReadFailed": "Kon nie hierdie lêer lees nie." + }, + "bcryptRoundsLabel": "Koste (ronde)", + "bcryptRoundsHint": "Hoër waardes is stadiger en sterker teen brute krag. Algemene verstek is 10.", + "fileTabDisabledTitle": "Bcrypt hashes slegs wagwoorde van teks, nie lukrake lêers nie.", + "placeholderBcrypt": "Tik of plak ’n wagwoord om te hash…", + "outputLabelBcrypt": "Bcrypt-hash", + "copyBcrypt": "Kopieer hash", + "emptyHintBcrypt": "Voer ’n wagwoord in om ’n bcrypt-hash te bereken.", + "securityNoteBcrypt": "Elke bcrypt-uitset sluit ’n lukrake sout in; dieselfde wagwoord gee elke keer ’n ander string. Dit is verwag. Gebruik sterk koste in produksie.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Enkripsie-speelgrond", + "subtitle": "AES-GCM in die blaaier: enkripteer na ’n JSON-bondel met wagfrase of rou sleutel en dekripteer plaaslik. Opvoedkundig en soms prakties — niks word opgelaai nie.", + "tabEncrypt": "Enkripteer", + "tabDecrypt": "Dekripteer", + "keyModeLabel": "Sleutelmateriaal", + "modePassphrase": "Wagfrase (PBKDF2 + AES-GCM)", + "modeRawKey": "Rou AES-sleutel (hex of Base64)", + "aesBitsLabel": "AES-sleutellengte", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Kodering van rou sleutel", + "encodingAuto": "Outo (hex as dit soos hex lyk, anders Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Rou sleutel", + "rawKeyPlaceholder": "32-greep sleutel as 64 hex-karakters of Base64 vir AES-256 (lengte hang van AES-grootte af).", + "rawKeyPlaceholderDecrypt": "Slegs nodig as die bondel keyDerivation \"none\" het.", + "passphraseLabel": "Wagfrase", + "passphrasePlaceholder": "Wagfrase word net in die blaaier gebruik om die sleutel af te lei.", + "passphrasePlaceholderDecrypt": "Slegs nodig as die bondel PBKDF2-SHA256 gebruik.", + "pbkdf2IterLabel": "PBKDF2-iterasies", + "pbkdf2Hint": "Tussen 10 000 en 2 000 000. Verstek stem ooreen met gewone kluis-instellings.", + "plaintextLabel": "Duidelike teks", + "plaintextPlaceholder": "Tik of plak die boodskap om te enkripteer…", + "bundleInputLabel": "Geënkripteerde bondel (JSON)", + "bundlePlaceholder": "Plak die JSON van Enkripteer (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Geënkripteerde bondel", + "plaintextOutputLabel": "Duidelike teks", + "encryptButton": "Enkripteer", + "decryptButton": "Dekripteer", + "copyBundle": "Kopieer JSON", + "copyPlaintext": "Kopieer duidelike teks", + "copied": "Gekopieer", + "emptyBundleHint": "Geënkripteerde JSON verskyn hier na enkripsie.", + "emptyDecryptHint": "Gedekripteerde teks verskyn hier.", + "decryptKeyHint": "Gebruik rou sleutel as keyDerivation \"none\" is; wagfrase by PBKDF2-SHA256. Die bondel stoor sout, iterasies, IV en AES-lengte.", + "securityNote": "Sleutels en duidelike teks verlaat nie jou blaaier nie. Gebruik toegewyde gereedskap vir produksiegeheime.", + "errors": { + "emptyKey": "Voer ’n rou sleutel in wat by die AES-grootte pas.", + "emptyPassphrase": "Voer ’n wagfrase in.", + "badKeyMaterial": "Sleutel kon nie as hex of Base64 gelees word nie.", + "wrongKeyLength": "Sleutellengte pas nie by AES (16 / 24 / 32 grepe) nie.", + "invalidJson": "Ongeldige JSON.", + "invalidBundle": "Hierdie JSON is nie ’n ondersteunde enkripsiebondel nie.", + "decryptFailed": "Dekripsie het misluk — verkeerde geheim, beskadigde bondel of verkeerde parameters.", + "encryptFailed": "Enkripsie het misluk. Kontroleer Web Crypto en invoer." + } + }, + "HmacGenerator": { + "title": "HMAC-generator", + "subtitle": "Vir die ondertekening van webhooks en die toets van API-integrasies. HMAC-SHA1/256/384/512 in heks of Base64—plaaslik in jou blaaier bereken.", + "secretLabel": "Geheime sleutel", + "messageLabel": "Boodskap / nutlading", + "placeholderSecret": "Plak jou ondertekeningsgeheim…", + "placeholderMessage": "Plak die rou lyf of string om te verifieer (dikwels presiese webhook-bytes).", + "algorithmLabel": "HMAC-algoritme", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Uitvoerformaat", + "formatHex": "Heks (kleinletters)", + "formatBase64": "Base64", + "outputLabel": "Handtekening", + "copyOutput": "Kopieer handtekening", + "copied": "Gekopieer", + "byteCountHint": "{count, plural, one {# greep} other {# grepe}} (UTF-8)", + "emptyHint": "Voer 'n geheime sleutel in om HMAC te bereken.", + "securityNote": "Deel nooit jou ondertekeningsgeheim nie. Hierdie bladsy laai niks op nie—alles loop in jou blaaier.", + "errors": { + "cryptoFailed": "Kon nie HMAC bereken nie. Kontroleer Web Crypto-steun en probeer weer.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA-kodegenerator", + "subtitle": "Plak die Base32-geheim en kry die huidige sesyfer RFC 6238-kode (SHA-1, 30 s). Loop plaaslik in die blaaier—nuttig om aanmelding of MFA te toets.", + "secretLabel": "Geheim (Base32)", + "placeholderSecret": "Plak die geheime sleutel (letters A–Z en syfers 2–7, soos by die opstel van die verifikasie-app)…", + "codeLabel": "Huidige kode", + "copyCode": "Kopieer kode", + "copied": "Gekopieer", + "nextIn": "Verfris oor {seconds} s", + "standardHint": "30 s tydstap, 6 syfers, HMAC-SHA-1 (versoenbaar met Google Authenticator).", + "digitsLabel": "Syfers", + "periodLabel": "Tydperk", + "algorithmLabel": "Algoritme", + "periodSeconds": "{seconds} s", + "emptyHint": "Plak 'n geheim om 'n sesyfer-kode te genereer.", + "invalidHint": "Regstel die geheim om die kode te sien.", + "securityNote": "Hanteer soos 'n wagwoord. Die geheim verlaat nie die blaaier nie; niks word opgelaai nie.", + "importLabel": "Voer in vanaf 'n otpauth://-URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Voer in", + "qrSectionTitle": "QR-kode vir voorsiening", + "qrHint": "Skandeer met 'n verifikasie-app (Google Authenticator, 1Password, Aegis…) om hierdie geheim by te voeg. Die QR-kode enkodeer die geheim — behandel dit soos 'n wagwoord.", + "accountLabel": "Rekening", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Uitreiker (opsioneel)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Voer 'n geldige geheim in om 'n QR-kode te genereer.", + "errors": { + "invalidSecret": "Ongeldige Base32-geheim.", + "cryptoFailed": "Kon nie TOTP bereken nie. Kontroleer Web Crypto en probeer weer.", + "invalidUri": "Dit is nie 'n geldige otpauth:// TOTP-URI nie.", + "unsupportedUri": "Hierdie URI gebruik waardes wat nie deur die hulpmiddel ondersteun word nie (syfers moet 6 of 8 wees; tydperk 15, 30 of 60 s)." + } + }, + "MarkdownPreview": { + "title": "Markdown-voorskou & HTML", + "subtitle": "Lewendige Markdown-weergawer met HTML-uitvoer en HTML → Markdown-omskakeling. Alles loop in jou blaaier.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown-invoer", + "htmlInputLabel": "HTML-invoer", + "previewLabel": "Voorskou", + "htmlOutputLabel": "HTML-bron", + "mdOutputLabel": "Markdown-uitvoer", + "inputPlaceholder": "# Hallo\\n\\nTik jou **markdown** hier…", + "htmlInputPlaceholder": "Plak jou HTML hier…", + "copyHtml": "Kopieer HTML", + "copyMarkdown": "Kopieer Markdown", + "exportHtml": "Voer HTML uit", + "copied": "Gekopieer", + "charCount": "{count, plural, one {# karakter} other {# karakters}}", + "emptyHint": "Begin Markdown tik om die lewendige voorskou en HTML-uitvoer te sien.", + "emptyHintHtmlToMd": "Plak HTML om dit na Markdown om te skakel.", + "securityNote": "Alles loop plaaslik in jou blaaier. Niks word opgelaai nie." + }, + "NumberBaseConverter": { + "title": "Basisomskakelaar", + "subtitle": "Skakel heelgetalle tussen basisse 2–36. Opsioneel 0x / 0b / 0o voorvoegsels. Alles plaaslik in jou blaaier.", + "inputBaseLabel": "Van basis", + "outputBaseLabel": "Na basis", + "inputLabel": "Invoernommer", + "placeholderInput": "Voer ’n heelgetal in die gekose invoerbasis in…", + "outputLabel": "Omgeskakelde waarde", + "copyOutput": "Kopieer resultaat", + "copied": "Gekopieer", + "swap": "Ruil basisse", + "byteCountHint": "{count, plural, one {# greep} other {# grepe}} (UTF-8 invoerlengte)", + "emptyHint": "Voer ’n nommer in om om te skakel.", + "note": "Syfers na 9 gebruik letters A–Z (nie saak sensitief nie). Spasies en onderstrepe word geïgnoreer.", + "errors": { + "invalidNumber": "Die waarde is nie geldig vir die gekose invoerbasis nie." + } + }, + "FormatConverter": { + "title": "Formaat Omskakelaer", + "subtitle": "Skakel tussen JSON, YAML, TOML en XML in enige rigting. Al 12 kombinasies. Alles loop in jou blaaier.", + "fromLabel": "Van", + "toLabel": "Na", + "swap": "Wissel", + "swapTitle": "Wissel formate en gebruik uitset as invoer", + "copy": "Kopieer", + "copied": "Gekopieer", + "charCount": "{count, plural, one {# karakter} other {# karakters}}", + "emptyHint": "Plak of tik jou invoer aan die linkerkant om die omgeskakelede uitset te sien.", + "localNote": "Alles loop plaaslik in jou blaaier. Niks word opgelaai nie." + }, + "MimeTypeLookup": { + "title": "MIME-tipe opsoek", + "subtitle": "Soek die MIME-tipe (media-tipe) vir 'n lêeruitbreiding of volledige lêernaam. Alles loop plaaslik in jou blaaier.", + "inputLabel": "Uitbreiding of lêernaam", + "placeholder": "bv. .png, package.json, photo.jpeg", + "hint": "Wenk: plak óf 'n uitbreiding (met of sonder 'n punt) óf 'n volledige lêernaam.", + "clear": "Maak skoon", + "exampleFilename": "Gebruik voorbeeld-lêernaam", + "exampleExtension": "Gebruik voorbeeld-uitbreiding", + "resultLabel": "Resultaat", + "normalizedLabel": "Genormaliseerde invoer:", + "mimeTypeLabel": "MIME-tipe", + "contentTypeLabel": "Content-Type-kop", + "charsetLabel": "Karakterstel", + "copyMimeType": "Kopieer MIME-tipe", + "copy": "Kopieer", + "copied": "Gekopieer", + "emptyHint": "Voer 'n uitbreiding of lêernaam in om die MIME-tipe op te soek.", + "notFound": "Geen MIME-tipe vir daardie invoer gevind nie.", + "localNote": "Alles loop plaaslik in jou blaaier. Niks word opgelaai nie." + }, + "UserAgentParser": { + "title": "User-Agent-ontleder", + "subtitle": "Plak ’n User-Agent (UA)-string en kry ’n uiteensetting van blaaier, OS, enjin, toestel en CPU. Alles loop plaaslik in jou blaaier.", + "inputLabel": "User-Agent-string", + "placeholder": "Plak jou User-Agent hier…", + "hint": "Wenk: jy kan ’n UA uit versoekkoppe, logs of jou blaaier se devtools kopieer.", + "clear": "Maak skoon", + "exampleDesktop": "Gebruik desktop-voorbeeld", + "exampleMobile": "Gebruik mobiele voorbeeld", + "resultLabel": "Uiteensetting", + "browserLabel": "Blaaier", + "osLabel": "Bedryfstelsel", + "engineLabel": "Enjin", + "deviceTypeLabel": "Toesteltipe", + "deviceVendorLabel": "Vervaardiger", + "deviceModelLabel": "Model", + "cpuArchLabel": "CPU-argitektuur", + "copyJson": "Kopieer JSON", + "copy": "Kopieer", + "copied": "Gekopieer", + "emptyHint": "Plak ’n User-Agent-string om die uiteensetting te sien.", + "localNote": "Alles loop plaaslik in jou blaaier. Niks word opgelaai nie." + }, + "MockDataGenerator": { + "title": "Skyn-datagenerator", + "subtitle": "Definieer kolomme, kies datatipes en voer datastelle uit vir saad, Postman of toetse. Tot {max} rye, alles in die blaaier — niks word opgelaai nie.", + "mockarooLink": "Mockaroo-agtige werkstroom", + "rowsLabel": "Rye", + "rowsHint": "Maksimum {max} rye per generasie om die oortjie vinnig te hou.", + "formatLabel": "Formaat", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "SQL INSERT", + "xml": "XML" + }, + "tableNameLabel": "SQL-tabelnaam", + "presetsLabel": "Sjablone", + "presetPlaceholder": "Laai sjabloon…", + "presetDefault": "Begin (gebruiker)", + "presetUsers": "Gebruikers en rolle", + "presetProducts": "E-handels-SKU's", + "presetApiLog": "HTTP-versoeklog", + "generate": "Genereer", + "warningDuplicateNames": "Duplikaat kolomname: later kolomme oorskryf vroeëre in JSON-voorwerpe. Hernoem.", + "schemaHeading": "Skema", + "addField": "Voeg kolom by", + "removeField": "Verwyder kolom", + "duplicateField": "Dupliseer kolom", + "colName": "Naam", + "colType": "Tipe", + "colOptions": "Opsies", + "colBlank": "Leeg %", + "optMin": "min", + "optMax": "maks", + "optDecimals": "des", + "optDateFrom": "van datum", + "optDateTo": "tot datum", + "optValues": "a, b, c", + "optStep": "stap", + "optRowBase": "basis", + "output": "Uitset", + "outputPlaceholder": "Klik Genereer om data te skep…", + "copy": "Kopieer", + "download": "Aflaai", + "copied": "Gekopieer", + "groups": { + "personal": "Persoonlik", + "location": "Ligging", + "internetApi": "Internet en API", + "numbersIds": "Syfers en ID’s", + "dateTime": "Datum en tyd", + "textDesign": "Teks en ontwerp", + "custom": "Aangepas" + }, + "fieldTypes": { + "first_name": "Voornaam", + "last_name": "Van", + "full_name": "Volle naam", + "email": "Email", + "username": "Gebruikersnaam", + "password": "Wagwoord", + "phone": "Foon", + "company": "Maatskappy", + "job_title": "Pos", + "avatar_url": "Avatar-URL", + "city": "Stad", + "country": "Land", + "country_code": "Landkode (ISO)", + "street_address": "Adres", + "zip_code": "Poskode", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "url": "URL", + "domain": "Domein", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Heeltal", + "float": "Desimaal", + "boolean": "Booleaans", + "uuid": "UUID", + "credit_card": "Toetskaart", + "semver": "SemVer", + "row_index": "Ry-indeks", + "sequence": "Reeks", + "date": "Datum", + "datetime": "Datum en tyd", + "unix_timestamp": "Unix-tydstempel", + "timezone": "Tydsone", + "word": "Woord", + "sentence": "Sin", + "paragraph": "Paragraaf", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Geldkode", + "language_code": "Taalkode", + "custom_list": "Pasgemaakte lys" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG-optimaliseerder", + "subtitle": "Plak SVG, minifiseer met SVGO in jou blaaier en vergelyk UTF-8-grootte voor en na.", + "inputLabel": "Inset", + "outputLabel": "Geoptimaliseer", + "placeholder": "Plak 'n -fragment of die hele lêer…", + "outputPlaceholder": "Geoptimaliseerde SVG verskyn hier…", + "chars": "{count} karakters • {bytes} grepe (UTF-8)", + "preview": "Voorskou", + "localNote": "Verwerking plaaslik in jou blaaier. Niks word opgelaai nie.", + "stats": { + "title": "Grootte (UTF-8)", + "before": "Voor", + "after": "Na", + "savedBytes": "{count} grepe bespaar ({percent}%)", + "noChange": "Geen vermindering — reeds minimaal of leë uitset." + }, + "actions": { + "clear": "Maak skoon", + "sample": "Laai voorbeeld", + "copy": "Kopieer", + "copied": "Gekopieer", + "download": "Aflaai" + }, + "errors": { + "optimizeFailed": "Kon nie hierdie SVG optimaliseer nie." + } + }, + "UnitConverter": { + "title": "Eenheidsomskakelaar", + "subtitle": "Skakel tussen 323 eenhede oor 43 kategorieë", + "category": "Kategorie", + "from": "Van", + "to": "Na", + "value": "Waarde", + "result": "Resultaat", + "swapUnits": "Ruil eenhede", + "placeholder": "Voer waarde in", + "copyResult": "Kopieer resultaat", + "searchCategory": "Soek kategorie...", + "selectCategory": "Kies 'n kategorie", + "noResults": "Geen kategorie gevind nie." + }, + "DockerComposeGenerator": { + "title": "Docker Compose-Generator", + "subtitle": "Kies gewilde databasisse, kasse, boodskaptoue, omgekeerde proxy's en waarneembaarheid — kopieer 'n docker-compose.yml vir plaaslike ontwikkeling.", + "searchPlaceholder": "Soek (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Dienste", + "selectedCount": "{count, plural, one {# diens} other {# dienste}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopieer", + "copied": "Gekopieer!", + "download": "Aflaai", + "emptyState": "Kies een of meer dienste om Compose-YAML te genereer.", + "hintPorts": "Verstek gasheerpoorte verminder botsings; verander as Docker 'n bindingfout meld. Vervang demo-wagwoorde voor gedeelde omgewings.", + "categories": { + "databases": "Databasisse en berging", + "cache": "Kas en in-geheue", + "queues": "Toue en boodskappe", + "search": "Soek en logs", + "storage": "Voorwerpherging", + "proxy": "Omgekeerde proxy's en ingress", + "observability": "Metrieke en nasporing", + "mail": "E-pos (plaaslike ontwikkeling)", + "admin": "Admin- en BI-koppelvlakke" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Maak skoon" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (Redis-versoenbaar)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (Kafka-versoenbaar)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (S3-versoenbaar)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (SMTP-vangs)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (ontwikkelaarbediener)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Skuif teëls en smelt hulle saam om 2048 te bereik", + "score": "Telling", + "best": "Best", + "newGame": "Nuwe Spel", + "howToPlay": "Gebruik pyltjie sleutels (of krap op mobiele) teëls te beweeg. Teëls met dieselfde nommer smelt saam. Bereik 2048 om te winnen!", + "youWin": "Jy het 2048 bereik!", + "keepGoing": "Gaan voort", + "gameOver": "Speletjie Verby", + "tryAgain": " Probeer weer", + "gameOverMessage": "Geen beweeg meer nie.", + "undo": "Ontdoen" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "fases voltooi", + "time": "Tyd", + "best": "Best", + "difficulty": { + "easy": "Maklik", + "medium": "Gemiddeld", + "hard": "" + }, + "stage": "Stadium", + "solved": "opgelos", + "score": "Telling", + "nextStage": "Volgende fase", + "replay": "Speel Weer", + "erase": "Uitvee", + "howToPlay": "Klik op 'n sel, tik dan op 'n nommer of druk 1–9. Agterruimte of Uitvee maak die sel skoon. Vet getalle is leidrade. Vul die rooster sodat elke ry, kolom, en 3x3 boks bevat 1–9 met geen herhalings.", + "pencil": "Potlood", + "undo": "Ontdoen", + "hint": "Wenk", + "hintsUsed": "leidrade gebruik", + "mistakes": "Foute", + "failed": "Misluk!", + "mistakeMode": "3-fout modus" + }, + "Snake": { + "title": "Snake", + "subtitle": "Eet kos, groei, vermy mure en jouself", + "score": "Telling", + "best": "Beste", + "startPrompt": "Druk enige pyltjietoets", + "startButton": "Begin Spel", + "gameOver": "Spel Verby", + "playAgain": "Speel Weer", + "howToPlay": "Gebruik pyltjietoetse of WASD om te beweeg. Eet kos om te groei. Spoed neem toe elke 50 punte.", + "diffEasy": "Maklik", + "diffNormal": "Normaal", + "diffHard": "Moeilik", + "diffInfinite": "Oneindig", + "orTap": "of tik hieronder", + "howToPlayInfinite": "Mure draai om — geen muursterfte. Eet kos om te groei.", + "shieldActive": "Skild Aktief", + "speedBoost": "Spoedverhoging", + "powerSpeed": "Spoed x2 (10 beurte)", + "powerShield": "Een dood oorleef", + "powerShrink": "Stert verkort" + }, + "RedisCommander": { + "page": { + "title": "Redis Bestuurder", + "dialogTitle": "Redis Verbindings", + "heroTitle": "Redis Bestuurder", + "heroDesc": "Verbind met Redis-instansies. Blaai deur sleutels, redigeer waardes, voer opdragte uit. Geloofsbriewe word in u blaaier versleutel.", + "heroAddConnection": "Voeg Verbinding By", + "vaultLockedTitle": "Kluis Gesluit", + "vaultLockedDesc": "Ontsluit u kluis om toegang tot gestoorde verbindings te kry." + } + }, + "Minesweeper": { + "title": "Mynveer", + "subtitle": "Vind al die veilige selle sonder om 'n myn te ontplof", + "mines": "Myne", + "newGame": "Nuwe spel", + "win": "Jy het gewen!", + "loss": "Spel verby!", + "playAgain": "Speel weer", + "tryAgain": "Probeer weer", + "startPrompt": "Klik op enige sel om te begin", + "difficulty": { + "beginner": "Beginner", + "intermediate": "Intermediêr", + "expert": "Kenner" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Rangskik die vallende blokke om rye te voltooi", + "next": "Volgende", + "score": "Telling", + "best": "Beste", + "lines": "Lyne", + "level": "Vlak", + "pause": "Pouse", + "resume": "Hervat", + "start": "Begin", + "restart": "Herbegin", + "paused": "Gepouseer", + "gameOver": "Spel verby" + }, + "SshKeyGenerator": { + "title": "SSH / RSA-sleutelgenerator", + "subtitle": "Genereer Ed25519 of RSA-sleutelpare in jou blaaier. Laai privaat- en publieksleutels af in OpenSSH- en PEM-formate. Niks word opgelaai nie.", + "keyTypeLabel": "Sleuteltipe", + "recommended": "aanbeveel", + "ed25519Desc": "Vinnig, klein, modern. Ondersteun deur GitHub, GitLab en alle groot SSH-bedieners.", + "rsa2048Desc": "Wyd versoenbaar. Veilig vir die meeste gebruike.", + "rsa4096Desc": "Maksimum verenigbaarheid en sterkte vir ou stelsels.", + "commentLabel": "Kommentaar (opsioneel)", + "commentHint": "Aangeheg aan die OpenSSH-publieksleutelreël.", + "generateBtn": "Genereer sleutelpaar", + "generating": "Genereer…", + "generatingLong": "Genereer (kan 'n oomblik neem)…", + "copy": "Kopieer", + "download": "Aflaai", + "privateKeyLabel": "Privaatsleutel (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Publieksleutel (OpenSSH-formaat)", + "publicKeySpkiLabel": "Publieksleutel (SPKI PEM)", + "browserNote": "Alle sleutels gegenereer in jou blaaier. Niks word na 'n bediener gestuur nie.", + "errors": { + "generationFailed": "Sleutelgenerering het misluk" + } + }, + "DnsLookup": { + "title": "DNS-soektog", + "subtitle": "Navraag DNS-rekords vir enige domein — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Voer domein in (bv. github.com)", + "lookupBtn": "Soek", + "looking": "Soek tans…", + "refreshBtn": "Verfris", + "recordTypesLabel": "Rekordtipes", + "allBtn": "Alles", + "tryLabel": "Probeer:", + "errors": { + "lookupFailed": "DNS-soektog het misluk" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt-genereerder en -toetser", + "subtitle": "Hash wagwoorde met bcrypt en verifieer hashe — alles bly in jou blaaier", + "tabs": { + "hash": "Hash", + "verify": "Verifieer" + }, + "showPassword": "Wys wagwoord", + "hidePassword": "Versteek wagwoord", + "copy": "Kopieer", + "copied": "Gekopieer", + "hash": { + "passwordLabel": "Wagwoord", + "passwordPlaceholder": "Voer 'n wagwoord in om te hash…", + "costLabel": "Kostefaktor", + "costHint": "Hoër koste verdubbel die werk per stap. 10-12 is 'n algemene keuse vir produksie.", + "slowWarning": "Kostefaktore van 13 en hoër kan 'n paar sekondes neem om te bereken.", + "generate": "Genereer hash", + "generating": "Besig om te hash…", + "resultLabel": "Bcrypt-hash", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Hash-anatomie", + "algorithm": "algoritme", + "cost": "koste", + "salt": "salt (22 karakters)", + "hash": "hash (31 karakters)" + } + }, + "verify": { + "passwordLabel": "Wagwoord", + "passwordPlaceholder": "Voer die wagwoord in om te kontroleer…", + "hashLabel": "Bcrypt-hash", + "hashPlaceholder": "$2b$10$…", + "idle": "Voer 'n wagwoord en 'n bcrypt-hash in om hulle te vergelyk.", + "invalidHash": "Dit lyk nie na 'n geldige bcrypt-hash nie.", + "checking": "Besig om te vergelyk…", + "match": "Pas — die wagwoord lewer hierdie hash.", + "noMatch": "Pas nie — die wagwoord lewer nie hierdie hash nie." + } + }, + "ChmodCalculator": { + "title": "Chmod-sakrekenaar", + "subtitle": "Skakel Unix-lêerregte om tussen merkblokkies, oktaal en simboliese notasie", + "grid": { + "title": "Regte", + "read": "Lees", + "write": "Skryf", + "execute": "Voer uit", + "owner": "Eienaar", + "group": "Groep", + "others": "Ander" + }, + "special": { + "title": "Spesiale bisse", + "setuid": "setuid", + "setgid": "setgid", + "sticky": "sticky" + }, + "octal": { + "label": "Oktaal", + "placeholder": "755 of 4755", + "invalid": "Voer 3 of 4 oktale syfers in (0-7)." + }, + "symbolic": { + "label": "Simbolies", + "placeholder": "rwxr-xr-x", + "invalid": "Voer 9 karakters in soos rwxr-xr-x (s/S, t/T toegelaat)." + }, + "output": { + "title": "Opdrag", + "copy": "Kopieer", + "copied": "Gekopieer" + }, + "explain": { + "line": "{cls} kan {perms}.", + "none": "{cls} het geen regte nie.", + "setuid": "setuid: die lêer loop met sy eienaar se voorregte.", + "setgid": "setgid: die lêer loop met sy groep se voorregte (nuwe lêers in 'n gids erf sy groep).", + "sticky": "sticky: net 'n lêer se eienaar kan dit binne hierdie gids uitvee of hernoem." + }, + "presets": { + "title": "Algemene voorafinstellings", + "octal": "Oktaal", + "permissions": "Regte", + "description": "Tipiese gebruik", + "p644": "Gewone lêers: eienaar skryf, almal lees.", + "p755": "Uitvoerbares en gidse: almal kan lees en ingaan.", + "p600": "Privaat lêers: net die eienaar lees en skryf.", + "p700": "Privaat uitvoerbares en gidse: net eienaar.", + "p664": "Groep-skryfbare lêers.", + "p775": "Groep-skryfbare gidse en uitvoerbares.", + "p400": "Leesalleen vir die eienaar (bv. SSH-privaatsleutels)." + } + }, + "CodeScreenshot": { + "title": "Kode-skermkiekie", + "subtitle": "Verander kode in pragtige, deelbare beelde", + "languageLabel": "Taal", + "autoDetect": "Outo-bespeur", + "backgroundLabel": "Agtergrond", + "paddingLabel": "Opvulling", + "filenameLabel": "Lêernaam", + "filenamePlaceholder": "example.ts", + "windowLabel": "Vensterkolletjies", + "themeLabel": "Venstertema", + "themeDark": "Donker", + "themeLight": "Lig", + "scaleLabel": "Uitvoerskaal", + "copyImage": "Kopieer beeld", + "copied": "Gekopieer", + "exportPng": "Voer PNG uit", + "inputLabel": "Kode", + "detected": "Bespeur: {language}", + "clear": "Vee uit", + "placeholder": "Plak jou kode hier…", + "previewLabel": "Voorskou", + "emptyPreview": "Jou kodevoorskou sal hier verskyn", + "exportFailed": "Kon nie beeld uitvoer nie", + "copyUnsupported": "Die kopiëring van beelde word nie in hierdie blaaier ondersteun nie", + "copyFailed": "Kon nie beeld kopieer nie", + "backgrounds": { + "ocean": "Oseaan", + "sunset": "Sonsondergang", + "candy": "Lekkers", + "forest": "Woud", + "midnight": "Middernag", + "gold": "Goud", + "aurora": "Aurora", + "slate": "Leiklip", + "transparent": "Deursigtig" + } + }, + "CssGenerators": { + "title": "CSS-genereerders", + "subtitle": "box-shadow, border-radius en vloeibare clamp() — kopieer die CSS.", + "preview": "Voorskou", + "output": "CSS", + "copy": "Kopieer", + "copied": "Gekopieer", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Vloeibare clamp()" + }, + "boxShadow": { + "presets": "Voorafinstellings", + "layer": "Laag", + "addLayer": "Voeg laag by", + "removeLayer": "Verwyder laag", + "x": "X", + "y": "Y", + "blur": "Vervaag", + "spread": "Verspreiding", + "color": "Kleur", + "alpha": "Alfa", + "inset": "Inset", + "presetNames": { + "subtleCard": "Subtiele kaart", + "elevated": "Verhewe", + "hardOffset": "Harde verskuiwing", + "neonGlow": "Neon-gloed", + "inner": "Binne", + "layeredSmooth": "Gelaagd glad" + } + }, + "borderRadius": { + "modeLabel": "Modus", + "modes": { + "simple": "Per hoek", + "blob": "Organiese klont" + }, + "unit": "Eenheid", + "linkCorners": "Koppel hoeke", + "randomize": "Verewekansig", + "horizontal": "Horisontale radiusse", + "vertical": "Vertikale radiusse", + "corners": { + "tl": "Bo links", + "tr": "Bo regs", + "br": "Onder regs", + "bl": "Onder links" + } + }, + "clamp": { + "minFont": "Min lettertipe (px)", + "maxFont": "Maks lettertipe (px)", + "minViewport": "Min viewport (px)", + "maxViewport": "Maks viewport (px)", + "rootFont": "Wortel-lettertipe (px)", + "viewportWidth": "Gesimuleerde viewport", + "computedSize": "Berekende grootte", + "slope": "Helling", + "intercept": "Afsnit", + "previewText": "Voorbeeldteks vir voorskou", + "invalid": "Voer geldige, verskillende viewport-grense in." + } + }, + "ExifViewer": { + "title": "EXIF-kyker en -verwyderaar", + "subtitle": "Inspekteer foto-metadata en verwyder dit in jou blaaier", + "dropTitle": "Los 'n beeld hier", + "dropHint": "of klik om te blaai — jou beeld verlaat nooit jou toestel nie", + "browse": "Kies beeld", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Besig om metadata te lees…", + "parseFailed": "Kon nie hierdie beeld lees nie", + "clear": "Verwyder beeld", + "gpsWarning": "Bevat GPS-ligging", + "openMap": "Bekyk op OpenStreetMap", + "noMetadata": "Geen ingebedde metadata is in hierdie beeld gevind nie.", + "facts": { + "name": "Naam", + "type": "Tipe", + "dimensions": "Afmetings", + "size": "Grootte" + }, + "groups": { + "camera": "Kamera", + "exposure": "Blootstelling", + "timestamps": "Tydstempels", + "gps": "GPS-ligging", + "software": "Sagteware en Ander" + }, + "gpsCard": { + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "altitude": "Hoogte" + }, + "raw": { + "title": "Alle metadata", + "copyJson": "Kopieer JSON", + "copied": "Gekopieer" + }, + "remove": { + "title": "Verwyder metadata", + "jpegDescription": "Verwyder EXIF, XMP, IPTC en kommentaar sonder verlies en sonder om die beeld te herkomprimeer.", + "reencodeDescription": "Herkodeer die beeld om ingebedde metadata te verwyder. Dit herkomprimeer die pixels.", + "keepIcc": "Behou kleurprofiel (ICC)", + "stripButton": "Verwyder metadata", + "reencodeButton": "Herkodeer en verwyder", + "unsupported": "Metadata-verwydering word nie vir hierdie lêertipe ondersteun nie.", + "failed": "Kon nie metadata verwyder nie", + "before": "Voor", + "after": "Na", + "download": "Laai skoongemaakte beeld af" + } + }, + "FaviconGenerator": { + "title": "Favicon-genereerder", + "subtitle": "Skep favicons en app-ikone uit een beeld", + "dropTitle": "Los 'n beeld hier", + "dropHint": "PNG, JPEG of SVG — klik om te blaai", + "browse": "Kies beeld", + "clear": "Verwyder beeld", + "backgroundLabel": "Agtergrond", + "transparent": "Deursigtig", + "solidColor": "Soliede kleur", + "cornerLabel": "Hoekvorm", + "cornerNone": "Vierkant", + "cornerRounded": "Afgerond", + "cornerCircle": "Sirkel", + "maskPaddingLabel": "Maskeerbare opvulling", + "maskable": "maskable", + "previewLabel": "Voorskou", + "copy": "Kopieer", + "headSnippet": "HTML head-snipper", + "downloadIco": "Laai favicon.ico af", + "downloadZip": "Laai alles af (.zip)" + }, + "JsonDiff": { + "title": "JSON-verskil", + "subtitle": "Semantiese, struktuurbewuste vergelyking van twee JSON-dokumente — sleutelvolgorde en formatering word geïgnoreer", + "options": { + "ignoreArrayOrder": "Ignoreer skikkingsvolgorde" + }, + "loadBothSamples": "Laai voorbeelde", + "sample": "Voorbeeld", + "format": "Formaat", + "clear": "Vee uit", + "formatError": "Kan nie formateer nie: invoer is nie geldige JSON nie", + "placeholder": "Plak of tik JSON hier…", + "panels": { + "left": "Oorspronklik (links)", + "right": "Gewysig (regs)", + "results": "Verskille" + }, + "summary": { + "added": "{count} bygevoeg", + "removed": "{count} verwyder", + "changed": "{count} verander" + }, + "types": { + "added": "Bygevoeg", + "removed": "Verwyder", + "changed": "Verander" + }, + "copyChanges": "Kopieer as JSON", + "copied": "Gekopieer", + "empty": "Plak JSON in albei panele om hul verskille te sien.", + "identical": "Die twee dokumente is semanties identies." + }, + "JsonpathPlayground": { + "title": "JSONPath-speelperk", + "subtitle": "Bevraag JSON-dokumente met JSONPath of JMESPath en sien lewendige resultate", + "engineLabel": "Enjin", + "queryLabel": "Navraag", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Kits-verwysing", + "cheats": { + "jpAllTitles": "Alle boektitels", + "jpAllAuthors": "Elke outeur, enige plek in die dokument", + "jpFirstBook": "Eerste boek", + "jpLastBook": "Laaste boek", + "jpSlice": "Eerste twee boeke (snit)", + "jpFilterPrice": "Boeke goedkoper as 40", + "jpFilterInStock": "Titels van boeke in voorraad", + "jpRecursive": "Alle waardes, rekursief", + "jmAllTitles": "Alle boektitels", + "jmFirstAuthor": "Outeur van die eerste boek", + "jmLastBook": "Laaste boek", + "jmSlice": "Eerste twee boeke (snit)", + "jmFilterPrice": "Titels van boeke goedkoper as 40", + "jmCount": "Tel boeke in voorraad", + "jmProjection": "Hervorm elke boek na 'n nuwe voorwerp", + "jmSort": "Titels gesorteer volgens prys" + }, + "panels": { + "input": "JSON-invoer", + "results": "Resultate" + }, + "sample": "Voorbeeld", + "format": "Formaat", + "clear": "Vee uit", + "formatError": "Kan nie formateer nie: invoer is nie geldige JSON nie", + "placeholder": "Plak of tik JSON hier…", + "outputPlaceholder": "Passings sal hier verskyn…", + "matchCount": "{count, plural, one {# passing} other {# passings}}", + "copy": "Kopieer", + "copied": "Gekopieer" + }, + "KeycodeInspector": { + "title": "Sleutelkode-inspekteur", + "subtitle": "Druk enige sleutel om sy JavaScript-sleutelbordgebeurtenis te inspekteer", + "capture": { + "prompt": "Druk enige sleutel…", + "ready": "Luister — druk enige sleutel op jou sleutelbord", + "clickToFocus": "Klik hier om te fokus, druk dan enige sleutel" + }, + "hero": { + "spaceKey": "(Spasie)", + "keyLabel": "event.key", + "repeat": "herhaal" + }, + "copyJson": "Kopieer JSON", + "copiedJson": "Gebeurtenis-JSON na knipbord gekopieer", + "deprecated": "verouderd", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standaard", + "left": "Links", + "right": "Regs", + "numpad": "Syferblok", + "unknown": "Onbekend" + }, + "history": { + "title": "Geskiedenis", + "clear": "Vee geskiedenis uit", + "empty": "Sleuteldrukke sal hier verskyn." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown-tabelgenereerder", + "subtitle": "Redigeer 'n rooster, plak uit 'n sigblad en kopieer 'n netjies uitgelynde Markdown- of HTML-tabel", + "addRow": "Voeg ry by", + "addColumn": "Voeg kolom by", + "importLabel": "Voer in", + "importPlaceholder": "Plak TSV (uit Excel / Google Sheets) of CSV hier…", + "importButton": "Voer in rooster in", + "pasteClipboard": "Plak uit knipbord", + "importEmpty": "Niks om in te voer nie — plak eers 'n paar rye", + "clipboardError": "Kon nie uit die knipbord lees nie", + "clear": "Vee uit", + "panels": { + "editor": "Tabelredigeerder", + "output": "Uitvoer" + }, + "align": { + "left": "Lyn links", + "center": "Lyn middel", + "right": "Lyn regs" + }, + "headerPlaceholder": "Opskrif", + "cellPlaceholder": "Sel", + "removeRow": "Verwyder ry", + "removeColumn": "Verwyder kolom", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopieer", + "copied": "Gekopieer" + }, + "TimezoneConverter": { + "title": "Tydsone-omskakelaar", + "subtitle": "Skakel een oomblik oor die wêreld om vir vergaderingbeplanning.", + "dateTimeLabel": "Datum en tyd", + "now": "Nou", + "sourceZoneLabel": "Brontydsone", + "selectZone": "Kies sone", + "searchZone": "Soek tydsones...", + "noZone": "Geen tydsone gevind nie.", + "scrubLabel": "Verskuif tyd", + "reset": "Herstel", + "targetsLabel": "Teikensones", + "addZone": "Voeg sone by", + "remove": "Verwyder", + "localBadge": "Plaaslik", + "invalidDate": "Voer 'n geldige datum en tyd in.", + "bands": { + "work": "Werksure (09:00–18:00)", + "edge": "Randure (vroeg / aand)", + "night": "Nag" + } + }, + "TokenCounter": { + "title": "LLM-token-teller", + "subtitle": "Tel tokens, visualiseer brokke en skat API-koste.", + "inputLabel": "Teks", + "sample": "Voorbeeld", + "clear": "Vee uit", + "placeholder": "Plak teks, kode of JSON om tokens te tel...", + "encoding": "Enkodering", + "loading": "Besig om tokeniseerder te laai...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-series)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Karakters", + "words": "Woorde", + "ratio": "Tokens/woord", + "ratioSub": "ongeveer" + }, + "claudeRow": "Claude (ongeveer)", + "estimate": "skatting", + "viz": { + "title": "Token-visualisering", + "toggle": "Wys", + "hint": "Wissel om elke token as 'n brok te kleur.", + "empty": "Voer teks in om tokens te visualiseer.", + "truncated": "Wys die eerste {limit} van {total} tokens." + }, + "cost": { + "title": "Kosteberekenaar", + "disclaimer": "redigeerbare verstekwaardes — verifieer huidige pryse", + "model": "Model", + "expectedOutput": "Verwagte uitvoer-tokens", + "customInput": "Invoer $/MTok", + "customOutput": "Uitvoer $/MTok", + "inputCost": "Invoerkoste", + "outputCost": "Uitvoerkoste", + "total": "Totaal", + "per1000": "Per 1000 oproepe" + } + }, + "WebhookTester": { + "title": "Webhook-toetser", + "subtitle": "Skep 'n tydelike URL en inspekteer inkomende webhook-versoeke lewendig.", + "intro": { + "title": "Skep 'n webhook-URL om versoeke te begin vang", + "hint": "Jy sal 'n onraaibare URL kry wat elke inkomende versoek opneem. Dit verval na 24 uur." + }, + "createBtn": "Skep webhook-URL", + "creating": "Besig om te skep…", + "urlLabel": "Jou webhook-URL", + "expiresIn": "Verval oor {countdown}", + "copyUrlBtn": "Kopieer URL", + "copied": "Gekopieer", + "clearBtn": "Vee uit", + "newBinBtn": "Nuwe URL", + "requestsTitle": "Versoeke", + "selectPrompt": "Kies 'n versoek om sy besonderhede te inspekteer.", + "expiredNotice": "Hierdie webhook-URL het verval. Skep 'n nuwe een om aan te hou toets.", + "base64Badge": "base64", + "truncatedBadge": "afgekap", + "empty": { + "title": "Wag vir versoeke…", + "hint": "Stuur 'n versoek na jou URL en dit sal onmiddellik hier verskyn. Probeer dit:" + }, + "detail": { + "receivedAt": "Ontvang", + "source": "Bron-IP", + "contentType": "Content-Type", + "query": "Navraagparameters", + "headers": "Kopstukke", + "body": "Liggaam", + "copyBody": "Kopieer liggaam", + "emptyBody": "Geen liggaam" + }, + "errors": { + "createFailed": "Kon nie webhook-URL skep nie. Probeer asseblief weer.", + "clearFailed": "Kon nie versoeke uitvee nie. Probeer asseblief weer." + } + }, + "WebsocketTester": { + "title": "WebSocket-toetser", + "subtitle": "Koppel aan 'n WebSocket-eindpunt en stuur, ontvang en inspekteer boodskappe lewendig", + "urlLabel": "Eindpunt-URL", + "protocolsLabel": "Subprotokolle", + "protocolsPlaceholder": "opsioneel, met kommas geskei", + "connect": "Koppel", + "disconnect": "Ontkoppel", + "status": { + "disconnected": "Ontkoppel", + "connecting": "Besig om te koppel", + "connected": "Gekoppel", + "error": "Fout" + }, + "messages": { + "connecting": "Besig om aan {url} te koppel…", + "connected": "Verbinding gevestig", + "connectedWithProtocol": "Verbinding gevestig (protokol: {protocol})", + "invalidUrl": "Ongeldige URL — moet met ws:// of wss:// begin", + "connectFailed": "Kon nie verbinding oopmaak nie", + "socketError": "'n Verbindingsfout het voorgekom", + "notConnected": "Nie gekoppel nie — koppel eers", + "binaryMessage": "'n Binêre boodskap ontvang" + }, + "log": { + "title": "Boodskaplog", + "empty": "Nog geen boodskappe nie. Koppel en begin stuur.", + "copyAll": "Kopieer alles", + "download": "Laai af as .txt", + "clear": "Vee log uit", + "prettyPrint": "Formateer JSON", + "showRaw": "Wys rou teks" + }, + "composer": { + "placeholder": "Tik 'n boodskap om te stuur…", + "send": "Stuur", + "formatJson": "Formateer as JSON", + "invalidJson": "Boodskap is nie geldige JSON nie", + "hint": "Cmd/Ctrl+Enter om te stuur" + }, + "ping": { + "label": "Outo-ping", + "seconds": "sek", + "intervalTitle": "Ping-interval in sekondes", + "payloadPlaceholder": "ping-loonvrag" + } + }, + "WhoisLookup": { + "title": "Whois-opsoek", + "subtitle": "Soek registrasiebesonderhede vir enige domein of IP-adres op via RDAP", + "placeholder": "example.com of 8.8.8.8", + "lookupBtn": "Soek op", + "looking": "Besig om op te soek…", + "refreshBtn": "Verfris", + "tryLabel": "Probeer:", + "copy": "Kopieer", + "copied": "Gekopieer", + "notAvailable": "Nie beskikbaar nie", + "summary": { + "registrar": "Registrateur", + "network": "Netwerk" + }, + "dates": { + "created": "Geskep", + "updated": "Bygewerk", + "expires": "Verval" + }, + "sections": { + "status": "Status", + "nameservers": "Naambedieners", + "contacts": "Kontakte", + "rawRdap": "Rou RDAP" + }, + "contacts": { + "roles": "Rolle", + "name": "Naam", + "organization": "Organisasie" + }, + "errors": { + "invalidTarget": "Voer 'n geldige domeinnaam of IP-adres in", + "lookupFailed": "Opsoek het misluk (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/ar.json b/apps/desktop-ui/messages/ar.json new file mode 100644 index 00000000..409c69a1 --- /dev/null +++ b/apps/desktop-ui/messages/ar.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "لوحة التحكم", + "settings": "الإعدادات", + "apiClient": "عميل API", + "httpStatusCodes": "رموز حالة HTTP", + "passwordManager": "مدير كلمات المرور", + "environmentManager": "مدير البيئة", + "bookmarks": "الإشارات المرجعية", + "jsonFormatter": "منسّق JSON", + "emailValidator": "التحقق من البريد", + "nosqlExplorer": "مستكشف قاعدة البيانات", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "ملعب التشفير", + "jwtDecoder": "محلّل JWT", + "certificatePemDecoder": "فك ترميز الشهادة / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "منسّق GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "محلّل URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "سر / مفتاح API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "فاحص التباين", + "jsonSchemaGenerator": "مولّد مخطط JSON", + "imageToBase64": "الصورة إلى Base64", + "imageCompressor": "ضغط الصور", + "cssGradientBuilder": "CSS منشئ التدرج", + "gitignoreGenerator": ".gitignore مولد", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "مقاطع الشيفرة", + "qrCodeGenerator": "مُولّد رمز QR", + "dnsLookup": "بحث DNS", + "ipSubnetCalculator": "حاسبة IP / الشبكة الفرعية", + "hashGenerator": "مُولّد الهاش", + "hmacGenerator": "مُولّد HMAC", + "totpGenerator": "رمز TOTP / 2FA", + "markdownPreview": "معاينة Markdown", + "numberBaseConverter": "محوّل الأساس الرقمي", + "formatConverter": "محوّل الصيغ", + "mockDataGenerator": "مولّد بيانات وهمية", + "svgOptimizer": "محسّن SVG", + "unitConverter": "محوّل الوحدات", + "dockerComposeGenerator": "مولّد Docker Compose", + "breakRoom": "غرفة الاستراحة", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "منفّذ Redis", + "yamlFormatter": "منسق YAML", + "sshKeyGenerator": "مولد مفاتيح SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "حاسبة Chmod", + "codeScreenshot": "لقطة شاشة للكود", + "cssGenerators": "مولّدات CSS", + "exifViewer": "عارض EXIF", + "faviconGenerator": "مولّد Favicon", + "jsonDiff": "مقارنة JSON", + "jsonpathPlayground": "ساحة JSONPath", + "keycodeInspector": "فاحص أكواد المفاتيح", + "markdownTableGenerator": "جدول Markdown", + "timezoneConverter": "محوّل المناطق الزمنية", + "tokenCounter": "عدّاد الرموز", + "webhookTester": "مختبِر Webhook", + "websocketTester": "مختبِر WebSocket", + "whoisLookup": "بحث Whois" + }, + "Help": { + "title": "المساعدة والتوثيق", + "subtitle": "كيف يعمل MyDevTools معًا، وما الذي تفعله كل أداة، وكيف تحميك الأمان والتشفير.", + "tabs": { + "overview": "نظرة عامة", + "apps": "التطبيقات", + "security": "الأمان", + "tips": "نصائح" + }, + "overview": { + "whatIs": { + "title": "ما هو MyDevTools؟", + "description": "مساحة عمل للمطورين مع تسجيل الدخول: لوحة تحكم ومجموعة أدوات للمهام اليومية.", + "p1": "بعد تسجيل الدخول، ينشئ التطبيق جلسة مع واجهة MyDevTools API حتى تتمكن الأدوات من تحميل بياناتك وحفظها. تعرض القائمة الجانبية جميع التطبيقات؛ ويمكنك إخفاء الأدوات التي لا تستخدمها من", + "settingsLink": "الإعدادات", + "p1After": ".", + "p2": "تستخدم بعض الميزات كلمة مرور رئيسية (أو مفتاح خزنة عام) بحيث يتم تشفير الأسرار مثل كلمات المرور وسلاسل الاتصال على جهازك قبل وصولها إلى الخادم." + }, + "quickLinks": { + "title": "روابط سريعة", + "dashboard": "لوحة التحكم", + "settings": "الإعدادات" + } + }, + "apps": { + "intro": "افتح أي أداة من الشريط الجانبي أو لوحة التحكم. فيما يلي دليل مختصر لكل تطبيق.", + "open": "فتح", + "howItWorks": "كيف يعمل", + "generic": "استخدم هذه الأداة من الشريط الجانبي لسير عملها المخصص." + }, + "security": { + "session": { + "title": "تسجيل الدخول وجلسة API", + "description": "الهوية وكيف يتواصل المتصفح مع الخادم الخلفي.", + "items": { + "authentication": "يستخدم التوثيق Firebase لتسجيل الدخول. بعد ذلك يتم استبدال رمز Firebase ID بملفات تعريف ارتباط للـ API.", + "accessTokens": "رموز الوصول هي JWT موقعة على الخادم (HS256) وتحدد المستخدم الذي يستدعي الـ API.", + "refreshTokens": "رموز التحديث محفوظة في Cookies من نوع HttpOnly؛ ويخزن الخادم قيمة SHA-256 للرمز وليس الرمز الخام." + } + }, + "encryption": { + "title": "التشفير المهم معرفته", + "description": "تشفير من جهة العميل لخزنة البيانات وبيانات الاتصال الحساسة.", + "p1": "بالنسبة إلى مدير كلمات المرور ومدير البيئة واتصالات مستكشف NoSQL المحفوظة، يستخدم التطبيق Web Crypto API في المتصفح:", + "items": { + "kdf": "تقوم PBKDF2 مع SHA-256 و100,000 تكرار وملح عشوائي باشتقاق مفتاح 256-بت من كلمة المرور الرئيسية.", + "aes": "تقوم AES-256-GCM بتشفير البيانات؛ ويتم استخدام IV بطول 12 بايت عشوائي لكل عملية تشفير.", + "scope": "يبقى المفتاح المشتق داخل المتصفح (غير قابل للاستخراج). يستلم الخادم نصًا مُشفّرًا وIV وليس كلمة المرور الرئيسية أو نصًا صريحًا." + }, + "warning": "إذا نسيت كلمة المرور الرئيسية فلن يمكن استعادة البيانات المشفرة. احتفظ بها بأمان." + }, + "logout": { + "title": "تسجيل الخروج والبيانات المحلية", + "body": "عند تسجيل الخروج يتم مسح الأسرار من الذاكرة وحالة الخزنة ومادة المفتاح من IndexedDB عند توفرها، وإنهاء جلسة الـ API. وقد تبقى البيانات المخزنة محليًا على الجهاز حتى تقوم بمسحها من المتصفح." + } + }, + "tips": { + "title": "استخدام MyDevTools بأمان", + "items": { + "masterPassword": "استخدم كلمة مرور رئيسية قوية وفريدة لميزات الخزنة.", + "apiClient": "تعامل مع عميل API مثل أي عميل HTTP: تجنب لصق أسرار الإنتاج في بيئات مشتركة إذا لم تكن متأكدًا من من يمكنه رؤيتها.", + "nosql": "يتصل مستكشف قاعدة البيانات بقواعد بيانات تقوم بإعدادها؛ استخدم حسابات أقل صلاحيات وقواعد شبكة قدر الإمكان.", + "json": "منسّق JSON يعمل محليًا داخل المتصفح—ومع ذلك تجنب لصق بيانات حساسة جدًا على أجهزة مشتركة.", + "settings": "خصص الأدوات الظاهرة من الإعدادات للحفاظ على تركيز الشريط الجانبي." + } + } + }, + "Settings": { + "title": "الإعدادات", + "language": "اللغة", + "spanish": "الإسبانية", + "english": "الإنجليزية", + "french": "الفرنسية", + "arabic": "عربى", + "catalan": "الكاتالونية", + "chineseSimplified": "الصينية المبسطة", + "czech": "التشيكية", + "greek": "Ελληνικά", + "german": "الألمانية", + "danish": "الدنماركية", + "afrikaans": "الأفريكانية", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "الأوكرانية" + }, + "SettingsPage": { + "title": "الإعدادات", + "subtitle": "إدارة إعدادات حسابك وتفضيلاتك.", + "userProfile": { + "title": "الملف الشخصي", + "description": "معلوماتك الشخصية وتفاصيل الحساب.", + "avatarAlt": "المستخدم", + "anonymousUser": "مستخدم مجهول", + "noEmailProvided": "لم يُقدَّم بريد إلكتروني", + "notLoggedIn": "لم يتم تسجيل الدخول." + }, + "tools": { + "title": "الأدوات والميزات", + "description": "اختر الأدوات التي تظهر في شريط التنقل الجانبي." + }, + "appearance": { + "title": "المظهر", + "description": "خصّص مظهر التطبيق على جهازك.", + "themePreference": "تفضيل السمة", + "themes": { + "light": "فاتح", + "dark": "داكن", + "system": "النظام" + } + }, + "language": { + "title": "اللغة", + "description": "حدد اللغة المستخدمة في المنصة.", + "selectLanguage": "اختر اللغة", + "selectPlaceholder": "اختر لغة", + "helpText": "تغيير اللغة يحدّث القوائم والتسميات في جميع أنحاء المنصة.", + "languages": { + "en": "English", + "fr": "Français (الفرنسية)", + "es": "Español (الإسبانية)", + "ar": "عربى", + "ca": "Català (الكاتالونية)", + "zh": "简体中文", + "cs": "Čeština (التشيكية)", + "el": "Ελληνικά (اليونانية)", + "de": "Deutsch (الألمانية)", + "da": "Dansk (الدنماركية)", + "af": "Afrikaans (الأفريكانية)", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "الإيطالية (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "صباح الخير", + "afternoon": "مساء الخير", + "evening": "مساء الخير" + }, + "commaName": "، {name}", + "welcomeBack": "مرحبًا بعودتك", + "welcomeBackNamed": "مرحبًا بعودتك، {name}", + "tagline": "ماذا تريد أن تنشئ اليوم؟", + "brandName": "MyDevTools", + "stats": { + "tools": "الأدوات", + "favorites": "المفضلة", + "pinned": "مثبت", + "recent": "المستخدمة مؤخرًا" + }, + "sections": { + "recentlyUsed": "المستخدمة مؤخرًا", + "favorites": "المفضلة", + "whatsNew": "ما الجديد", + "pinned": "مثبت" + }, + "groups": { + "apps": "التطبيقات" + }, + "toolCard": { + "defaultDescription": "استكشف هذه الأداة لمزيد من الإمكانيات.", + "launchTool": "تشغيل الأداة" + }, + "tools": { + "toDo": { + "title": "المهام", + "description": "أدر قوائم المهام بكفاءة." + }, + "notes": { + "title": "الملاحظات", + "description": "دوّن الأفكار ونظّم أفكارك." + }, + "passwordManager": { + "title": "مدير كلمات المرور", + "description": "خزّن كلمات المرور وأدرها بأمان." + }, + "environmentManager": { + "title": "مدير البيئة", + "description": "خزن متغيرات بصيغة .env حسب المشروع والبيئة مع وسوم." + }, + "bookmarks": { + "title": "الإشارات المرجعية", + "description": "أدر ونظّم إشاراتك المرجعية." + }, + "apiClient": { + "title": "عميل API", + "description": "اختبر طلبات HTTP وصححها." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "استكشف قواعد بيانات MongoDB." + }, + "emailValidator": { + "title": "التحقق من البريد", + "description": "تحقق من عناوين البريد الإلكتروني." + }, + "encryptionPlayground": { + "title": "ملعب التشفير", + "description": "AES-GCM بمفتاح خام أو عبارة سر؛ حزم JSON، Web Crypto فقط في المتصفح." + }, + "jwtDecoder": { + "title": "محلّل JWT", + "description": "فك ترميز ترويسة وبيانات ووقت انتهاء JWT." + }, + "certificatePemDecoder": { + "title": "فك ترميز الشهادة / PEM", + "description": "فك ترميز شهادات X.509 وطلبات CSR: الموضوع، المصدر، الصلاحية، وأسماء SAN." + }, + "sshKeyGenerator": { + "title": "مولد مفاتيح SSH / RSA", + "description": "توليد أزواج مفاتيح Ed25519 أو RSA في المتصفح. التنزيل بتنسيقات OpenSSH و PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "منسّق GraphQL", + "description": "نسّق GraphQL مع تمييز الصيغة ومُنشئ استعلامات بسيط." + }, + "yamlFormatter": { + "title": "منسّق YAML", + "description": "نسّق YAML وتحقق منه وحوّله إلى JSON في المتصفح." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "محلّل URL", + "description": "تقسيم عنوان URL إلى بروتوكول ومضيف ومسار ومعاملات استعلام وتجزئة (hash)." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "سر / مفتاح API", + "description": "سلاسل عشوائية تشفيرية مع أبجدية وطول قابلان للضبط وتصدير بالجملة." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "فاحص التباين", + "description": "نسبة تباين WCAG 2.1 بين النص والخلفية (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "مولّد مخطط JSON", + "description": "استنتج مخطط JSON ونماذج مُنمّطة من عيّنة JSON." + }, + "imageToBase64": { + "title": "الصورة إلى Base64", + "description": "تحويل الصور إلى Data URI أو سلاسل Base64 الأولية." + }, + "imageCompressor": { + "title": "ضغط الصور", + "description": "ضغط JPEG وPNG وWebP محليًا باستخدام شريط جودة." + }, + "cssGradientBuilder": { + "title": "CSS منشئ التدرج", + "description": "منشئ التدرج المرئي لـ CSS مع التحكم في الزاوية وتوقف الألوان." + }, + "gitignoreGenerator": { + "title": ".gitignore مولد", + "description": "قم بإنشاء ملفات .gitignore مدمجة على الفور من أكثر من 500 حزمة تقنية." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "حوّل ملفات CSV أو Excel إلى JSON وصدّر مرة أخرى إلى CSV أو XLSX." + }, + "snippetManager": { + "title": "مقاطع الشيفرة", + "description": "احفظ المقاطع مع تمييز بناء الجملة وكشف اللغة التلقائي والتنسيق." + }, + "qrCodeGenerator": { + "title": "مُولّد رمز QR", + "description": "أنشئ صور PNG لرمز QR من نص أو روابط في المتصفح." + }, + "ipSubnetCalculator": { + "title": "حاسبة IP / الشبكة الفرعية", + "description": "IPv4 وIPv6 CIDR: قناع الشبكة، النطاقات، وعدد العناوين." + }, + "dnsLookup": { + "title": "بحث DNS", + "description": "استعلام عن سجلات DNS لأي نطاق — A، AAAA، MX، TXT، NS، CNAME." + }, + "hashGenerator": { + "title": "مُولّد الهاش", + "description": "MD5 وSHA-1 وSHA-2 لنص أو ملفات." + }, + "hmacGenerator": { + "title": "مُولّد HMAC", + "description": "توقيع خطافات الويب واختبار تكاملات API باستخدام HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "مُولّد رمز TOTP / 2FA", + "description": "الصق سرًا Base32 من تطبيق المصادقة؛ يتجدد الرمز المكوّن من ستة أرقام كل 30 ثانية—مفيد لاختبار تسجيل الدخول وMFA." + }, + "markdownPreview": { + "title": "معاينة Markdown", + "description": "معرض Markdown مباشر مع تصدير HTML وتحويل HTML → Markdown." + }, + "numberBaseConverter": { + "title": "محوّل الأساس الرقمي", + "description": "تحويل الأعداد الصحيحة بين الأسس من 2 إلى 36." + }, + "formatConverter": { + "title": "محوّل الصيغ", + "description": "حوّل بين JSON وYAML وTOML وXML في أي اتجاه. يدعم جميع التوليفات الاثنتي عشرة." + }, + "mockDataGenerator": { + "title": "مولّد بيانات وهمية", + "description": "عرّف الأعمدة، اختر أنواع البيانات، وصدّر مجموعات للبذور أو Postman أو الاختبار. حتى 5000 صفًا، كل شيء في المتصفح — بلا رفع." + }, + "httpStatusCodes": { + "title": "رموز حالة HTTP", + "description": "ابحث عن رموز حالة HTTP مع الأوصاف (1xx–5xx)." + }, + "svgOptimizer": { + "title": "محسّن SVG", + "description": "الصق SVG، أزل الفوضى باستخدام SVGO وشاهد توفير حجم UTF-8." + }, + "unitConverter": { + "title": "محوّل الوحدات", + "description": "تحويل بين 323 وحدة عبر 43 فئة علمية وهندسية." + }, + "dockerComposeGenerator": { + "title": "مولّد Docker Compose", + "description": "اختر قواعد البيانات والتخزين المؤقت والطوابير والوكلاء وصور المراقبة؛ صدّر docker-compose.yml للمكدسات المحلية." + }, + "game2048": { + "title": "2048", + "description": "قم بتحريك ودمج البلاط للوصول إلى عام 2048. لعبة أحجية الأرقام الكلاسيكية." + }, + "sudoku": { + "title": "سودوكو", + "description": "لغز الأرقام الكلاسيكية. 3 مستويات صعوبة، 150 مرحلة لكل منها." + }, + "redisCommander": { + "title": "منفّذ Redis", + "description": "تصفح المفاتيح وافحص القيم ونفّذ الأوامر على أي مثيل Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "مولّد ومدقّق Bcrypt", + "description": "شفّر كلمات المرور باستخدام bcrypt وتحقّق من التجزئات الموجودة." + }, + "chmodCalculator": { + "title": "حاسبة Chmod", + "description": "حوّل أذونات يونكس بين الثماني والرمزي ومربّعات الاختيار." + }, + "codeScreenshot": { + "title": "لقطة شاشة للكود", + "description": "حوّل الكود المصدري إلى صور جميلة قابلة للمشاركة." + }, + "cssGenerators": { + "title": "مولّدات CSS", + "description": "مولّدات box-shadow وborder-radius وclamp() المرن." + }, + "exifViewer": { + "title": "عارض ومزيل EXIF", + "description": "افحص بيانات EXIF الوصفية للصور واحذفها محليًا." + }, + "faviconGenerator": { + "title": "مولّد Favicon", + "description": "أنشئ أيقونات المفضلة وأيقونات التطبيق ومقتطفات manifest." + }, + "jsonDiff": { + "title": "مقارنة JSON", + "description": "مقارنة دلالية واعية بالبنية بين مستندَي JSON." + }, + "jsonpathPlayground": { + "title": "ساحة JSONPath", + "description": "استعلم عن JSON باستخدام JSONPath أو JMESPath وشاهد النتائج مباشرة." + }, + "keycodeInspector": { + "title": "فاحص أكواد المفاتيح", + "description": "اضغط مفتاحًا لرؤية أكواد أحداث JavaScript ومفاتيح التعديل." + }, + "markdownTableGenerator": { + "title": "مولّد جداول Markdown", + "description": "أنشئ جداول Markdown وHTML في محرّر شبكي يشبه جداول البيانات." + }, + "timezoneConverter": { + "title": "محوّل المناطق الزمنية", + "description": "حوّل وقتًا عبر مناطق عديدة مع مؤشرات إزاحة اليوم وساعات العمل." + }, + "tokenCounter": { + "title": "عدّاد رموز LLM", + "description": "احسب رموز LLM، وتصوّر الأجزاء، وقدّر تكلفة الـ API." + }, + "webhookTester": { + "title": "مختبِر Webhook", + "description": "التقط وافحص طلبات webhook الواردة في الوقت الفعلي." + }, + "websocketTester": { + "title": "مختبِر WebSocket", + "description": "اتصل بنقاط نهاية WebSocket وتبادل الرسائل في الوقت الفعلي." + }, + "whoisLookup": { + "title": "بحث Whois", + "description": "ابحث عن تفاصيل تسجيل النطاق وعنوان IP عبر RDAP." + } + }, + "tabs": { + "apps": "التطبيقات", + "analytics": "التحليلات", + "ui": "الواجهة" + }, + "analytics": { + "title": "بياناتك في لمحة", + "subtitle": "أعداد متزامنة مع حسابك على الخادم.", + "loadError": "تعذّر تحميل التحليلات.", + "retry": "إعادة المحاولة", + "loginHint": "سجّل الدخول لعرض تحليلات الاستخدام وتفضيلات الواجهة.", + "passwordEntries": "إدخالات كلمات المرور", + "bookmarks": "الإشارات المرجعية", + "bookmarkFolders": "مجلدات الإشارات", + "tasksTotal": "المهام", + "tasksCompleted": "مكتملة", + "tasksOngoing": "قيد التنفيذ", + "tasksNotStarted": "لم تبدأ", + "projects": "المشاريع", + "nosqlConnections": "اتصالات قاعدة البيانات", + "notes": "الملاحظات", + "apiClientCollections": "مجموعات API", + "apiClientEnvironments": "بيئات API", + "apiClientHistory": "سجلات سجل API", + "jsonFormatterDocuments": "مستندات منسّق JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "مقاطع برمجية", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "تفضيلات الواجهة", + "subtitle": "المظهر واللغة والأدوات الظاهرة في الشريط الجانبي — من إعدادات الحساب.", + "loadError": "تعذّر تحميل التفضيلات.", + "loginHint": "سجّل الدخول لعرض تفضيلات الواجهة المحفوظة.", + "theme": "المظهر", + "accent": "لون التمييز", + "locale": "اللغة", + "enabledTools": "الأدوات الظاهرة", + "favoriteTools": "الأدوات المفضلة", + "toolsWithUsage": "أدوات مع تتبع الاستخدام" + }, + "signIn": "تسجيل الدخول", + "noResults": "لا توجد أدوات تطابق بحثك", + "noResultsHint": "جرّب كلمة مفتاحية مختلفة أو امسح الفلتر.", + "filterAll": "الكل", + "loginCta": { + "description": "سجّل الدخول لتتبع أدواتك المستخدمة مؤخرًا." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "جاري التحميل...", + "myTasksTitle": "مهامي", + "statsLine": "{total} مهام • {percent}% مكتمل", + "statsLineDesktop": "{total} إجمالي • {percent}% مكتمل" + }, + "filters": { + "all": "الكل", + "total": "الإجمالي", + "allProjects": "كل المشاريع", + "export": "تصدير", + "kanban": "كانبان", + "list": "قائمة", + "searchPlaceholder": "ابحث في المهام...", + "searchAria": "بحث في المهام", + "clearSearchAria": "مسح البحث", + "foundTasks": "تم العثور على {count} {count, plural, one {مهمة} other {مهام}}", + "filterByAllAria": "تصفية بالكل", + "filterByStatusAria": "تصفية حسب {status}" + }, + "status": { + "not-started": { + "label": "لم تبدأ", + "description": "مهام لم تُبدأ بعد" + }, + "ongoing": { + "label": "قيد التنفيذ", + "description": "مهام قيد العمل حالياً" + }, + "completed": { + "label": "مكتملة", + "description": "مهام انتهت" + } + }, + "priorities": { + "high": "عالية", + "medium": "متوسطة", + "low": "منخفضة" + }, + "form": { + "addNewTaskLabel": "إضافة مهمة جديدة", + "multilinePlaceholder": "تفاصيل المهمة... (Shift+Enter سطر جديد)", + "singlelinePlaceholder": "ما المطلوب؟ اضغط Enter للإضافة", + "taskInputAria": "إدخال المهمة", + "projectPlaceholder": "مشروع", + "selectProject": "اختر مشروعاً", + "errorSelectProject": "يرجى اختيار مشروع أو إنشاء مشروع جديد لإضافة مهمة.", + "hintMultiline": "Shift+Enter سطر جديد، Enter للإضافة", + "hintSingleline": "Enter للإضافة", + "shortcutsAria": "اختصارات لوحة المفاتيح", + "shortcutsLabel": "اختصارات", + "shortcutAddTask": "إضافة مهمة", + "shortcutNewLine": "سطر جديد", + "shortcutClear": "مسح", + "addButton": "إضافة" + }, + "list": { + "ariaLabel": "قائمة المهام", + "loading": "جاري تحميل مهامك...", + "emptyTitle": "لا توجد مهام", + "emptyPrimary": "ابدأ بإضافة أول مهمة أعلاه! اضغط Enter لإضافة مهام بسرعة.", + "emptySecondary": "جرّب تعديل المرشحات أو استعلام البحث." + }, + "mobileDrawer": { + "addTaskSrOnly": "إضافة مهمة", + "addNewTaskTitle": "مهمة جديدة", + "addNewTaskDescription": "أنشئ مهمة جديدة لتتبع تقدمك.", + "cancel": "إلغاء" + }, + "kanban": { + "ariaLabel": "لوحة كانبان", + "loading": "جاري تحميل مهامك...", + "dropHere": "أسقط المهمة هنا", + "noTasksYet": "لا مهام بعد", + "emptyColumnAria": "عمود فارغ", + "columnAria": "عمود {title} يحتوي {count} مهام", + "countInColumnAria": "{count} مهام في {title}" + }, + "taskItem": { + "confirmDelete": "هل أنت متأكد أنك تريد حذف هذه المهمة؟", + "statusPlaceholder": "الحالة", + "changeStatusAria": "تغيير حالة المهمة", + "taskOptionsAria": "خيارات المهمة", + "editTask": "تعديل المهمة", + "copyTask": "نسخ المهمة", + "copied": "تم النسخ!", + "deleteTask": "حذف المهمة", + "statusHeading": "الحالة", + "editTaskAria": "تعديل المهمة" + }, + "editDialog": { + "title": "تعديل المهمة", + "description": "حدّث تفاصيل المهمة، أضف مهام فرعية، حدد الأولويات والمزيد.", + "taskTitleLabel": "عنوان المهمة *", + "taskTitlePlaceholder": "ما المطلوب؟", + "descriptionLabel": "الوصف", + "descriptionPlaceholder": "أضف تفاصيل أكثر عن هذه المهمة...", + "statusLabel": "الحالة", + "priorityLabel": "الأولوية", + "dueDateLabel": "تاريخ الاستحقاق", + "pickDate": "اختر تاريخاً", + "clearDate": "مسح التاريخ", + "projectLabel": "المشروع", + "selectProjectPlaceholder": "اختر مشروعاً", + "noProject": "بدون مشروع", + "timeEstimateLabel": "تقدير الوقت (دقائق)", + "timeEstimatePlaceholder": "كم يستغرق؟", + "tagsLabel": "الوسوم", + "customTagNamePlaceholder": "اسم وسم مخصص", + "subtasksLabel": "مهام فرعية", + "addSubtaskPlaceholder": "أضف مهمة فرعية...", + "cancel": "إلغاء", + "saving": "جاري الحفظ...", + "saveChanges": "حفظ التغييرات" + }, + "export": { + "title": "تصدير المهام", + "description": "صدّر مهامك إلى JSON أو CSV أو Excel.", + "projectLabel": "المشروع", + "statusLabel": "الحالة", + "tagLabel": "الوسم", + "allProjects": "كل المشاريع", + "allStatuses": "كل الحالات", + "allTags": "كل الوسوم", + "exportAsJsonTitle": "تصدير JSON", + "exportAsJsonDescription": "تصدير بيانات المهام بما في ذلك المهام الفرعية والوسوم والبيانات الوصفية.", + "exportJson": "تصدير JSON", + "exportAsCsvExcelTitle": "تصدير CSV / Excel", + "exportAsCsvExcelDescription": "تصدير المهام لتطبيقات الجداول.", + "exportCsv": "تصدير CSV", + "exportExcel": "تصدير Excel", + "close": "إغلاق", + "toastNoTasks": "لا مهام للتصدير مع المرشحات الحالية", + "toastExportedJson": "تم تصدير {count} مهمة إلى JSON!", + "toastExportedCsv": "تم تصدير {count} مهمة إلى CSV!", + "toastExportedExcel": "تم تصدير {count} مهمة إلى Excel!", + "toastFailedJson": "فشل التصدير إلى JSON", + "toastFailedCsv": "فشل التصدير إلى CSV", + "toastFailedExcel": "فشل التصدير إلى Excel", + "unknownProject": "مشروع غير معروف" + }, + "ack": { + "taskAddedTitle": "تمت إضافة المهمة بنجاح", + "taskAddedFailedTitle": "فشل إضافة المهمة", + "taskUpdatedTitle": "تم تحديث المهمة بنجاح", + "taskUpdatedFailedTitle": "فشل تحديث المهمة", + "taskStatusUpdateFailedTitle": "فشل تحديث حالة المهمة", + "taskMovedTitle": "تم نقل المهمة إلى {status}", + "tryAgain": "يرجى المحاولة مرة أخرى.", + "taskDeletedTitle": "تم حذف المهمة \"{text}\"", + "undo": "تراجع", + "taskRestoredTitle": "تمت استعادة المهمة", + "taskRestoredSuccessTitle": "تمت استعادة المهمة بنجاح", + "taskRestoreFailedTitle": "فشلت استعادة المهمة. يرجى المحاولة مرة أخرى.", + "taskDeleteFailedTitle": "فشل حذف المهمة. يرجى المحاولة مرة أخرى.", + "tasksImportedTitle": "تم استيراد {count} {count, plural, one {مهمة} other {مهام}} بنجاح!", + "tasksImportFailedTitle": "فشل استيراد المهام. يرجى المحاولة مرة أخرى.", + "unknownProject": "مشروع غير معروف", + "projectCreatedTitle": "تم إنشاء المشروع بنجاح", + "projectCreateFailedTitle": "فشل إنشاء المشروع", + "projectUpdatedTitle": "تم تحديث المشروع بنجاح", + "projectUpdateFailedTitle": "فشل تحديث المشروع", + "projectDeletedTitle": "تم حذف المشروع بنجاح", + "projectDeleteFailedTitle": "فشل حذف المشروع" + } + }, + "Notes": { + "page": { + "loading": "جاري التحميل..." + }, + "layout": { + "mobileNavTitle": "تنقل الملاحظات", + "openMenuAria": "فتح قائمة الملاحظات" + }, + "sidebar": { + "title": "الملاحظات", + "createNoteAria": "إنشاء ملاحظة", + "searchPlaceholder": "ابحث في الملاحظات...", + "loading": "جاري التحميل...", + "noNotesFound": "لم يُعثر على ملاحظات.", + "noNotesYet": "لا ملاحظات بعد. انقر + لإنشاء واحدة.", + "inParent": "في {parentTitle}", + "untitled": "بدون عنوان", + "addSubPage": "إضافة صفحة فرعية", + "delete": "حذف", + "deleteConfirmTitle": "حذف الملاحظة؟", + "deleteConfirmDescription": "هل تريد حذف \"{title}\"؟ لا يمكن التراجع عن هذا الإجراء.", + "cancel": "إلغاء", + "confirmDelete": "حذف", + "noteOptionsAria": "خيارات الملاحظة" + }, + "editor": { + "emptyState": "اختر ملاحظة أو أنشئ ملاحظة جديدة للبدء.", + "titlePlaceholder": "بدون عنوان", + "saving": "جارٍ الحفظ…", + "ready": "جاهز", + "saved": "تم الحفظ {time}", + "stats": "{words} كلمة · {read} دقيقة قراءة", + "template": "قالب", + "applyTemplate": "تطبيق القالب", + "exportMdLabel": ".md", + "exportMd": "تصدير كـ Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "تصدير كـ HTML", + "focusMode": "وضع التركيز", + "exitFocusMode": "إنهاء وضع التركيز", + "save": "حفظ", + "addTag": "إضافة وسم…", + "removeTag": "إزالة الوسم {tag}", + "loading": "جارٍ التحميل…", + "searchHint": "للبحث", + "focusHint": "وضع التركيز", + "notePath": "مسار الملاحظة" + }, + "context": { + "authRequiredError": "المستخدم غير مصادق", + "defaultTitle": "بدون عنوان", + "cloudSyncImageError": "فعّل المزامنة السحابية لتخزين صور الملاحظات. تبقى الملاحظات النصية محلية." + } + }, + "PasswordManager": { + "page": { + "loading": "جاري التحميل...", + "title": "مدير كلمات المرور", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "فشل فك تشفير كلمات المرور", + "toastLoadFailed": "فشل تحميل كلمات المرور", + "toastUnlockedAuto": "تم فتح الخزنة تلقائياً", + "toastAutoUnlockFailed": "فشل الفتح التلقائي. يرجى الفتح يدوياً.", + "toastConnectFailed": "فشل الاتصال بالخادم", + "toastAutoUnlockWarning": "تم فتح الخزنة، لكن الفتح التلقائي قد لا يعمل عند إعادة التحميل", + "errorIncorrectPassword": "كلمة المرور الرئيسية غير صحيحة", + "errorUnlockFailed": "فشل فتح الخزنة", + "errorPasswordsMismatch": "كلمات المرور غير متطابقة", + "errorPasswordTooShort": "يجب أن تكون كلمة المرور 8 أحرف على الأقل", + "errorSetupFailed": "فشل إعداد الخزنة", + "errorConnectFailed": "فشل الاتصال بالخادم.", + "mobileTitleCreate": "أنشئ خزنتك", + "mobileTitleWelcome": "مرحباً بعودتك", + "mobileSubtitleCreate": "عيّن كلمة مرور رئيسية لحماية بياناتك", + "mobileSubtitleUnlock": "أدخل كلمة المرور الرئيسية للفتح", + "placeholderMasterPassword": "كلمة المرور الرئيسية", + "placeholderConfirmPassword": "تأكيد كلمة المرور", + "labelMasterPassword": "كلمة المرور الرئيسية", + "labelConfirmPassword": "تأكيد كلمة المرور", + "placeholderEnterMaster": "أدخل كلمة المرور الرئيسية", + "placeholderConfirmMaster": "أكد كلمة المرور الرئيسية", + "buttonCreateVault": "إنشاء الخزنة", + "buttonUnlock": "فتح", + "buttonUnlockVault": "فتح الخزنة", + "footerCreate": "لا تُخزَّن كلمة مرورك ولا يمكن استعادتها", + "footerUnlock": "محمي بتشفير من طرف إلى طرف", + "desktopTitleSetup": "إعداد خزنة كلمات المرور", + "desktopTitleUnlock": "فتح الخزنة", + "desktopDescriptionSetup": "أنشئ كلمة مرور رئيسية لتأمين بياناتك. لا تُخزَّن هذه الكلمة ولا يمكن استعادتها.", + "desktopDescriptionUnlock": "أدخل كلمة المرور الرئيسية للوصول إلى بياناتك المحمية." + }, + "list": { + "loading": "جاري تحميل كلمات المرور...", + "emptyTitle": "الخزنة فارغة", + "emptyHintMobile": "اضغط زر + لإضافة أول كلمة مرور", + "emptyHintDesktop": "لم تخزّن كلمات مرور بعد. انقر \"إضافة كلمة مرور\" أعلاه للبدء.", + "addPassword": "إضافة كلمة مرور", + "searchPlaceholderMobile": "ابحث في خزنتك...", + "searchPlaceholderDesktop": "ابحث في الخزنة...", + "importExport": "استيراد / تصدير", + "lockVault": "قفل الخزنة", + "allPasswordsTitle": "كل كلمات المرور", + "passwordCount": "{count, plural, one {# كلمة مرور} other {# كلمات مرور}}", + "vaultHealthDrawerTitle": "صحة الخزنة", + "noResults": "لا توجد كلمات مرور تطابق \"{query}\"", + "tableService": "الخدمة", + "tableUsername": "اسم المستخدم", + "tablePassword": "كلمة المرور", + "tableUrl": "الرابط", + "tableActions": "إجراءات", + "gridViewAria": "عرض شبكي", + "listViewAria": "عرض قائمة", + "passwordStrengthTitle": "قوة كلمة المرور", + "deleteDialogTitle": "حذف كلمة المرور", + "deleteDialogDescription": "هل تريد حذف كلمة المرور هذه؟ لا يمكن التراجع.", + "cancel": "إلغاء", + "delete": "حذف", + "edit": "تعديل" + }, + "toasts": { + "copiedPassword": "تم نسخ كلمة المرور", + "copiedUsername": "تم نسخ اسم المستخدم", + "passwordDeleted": "تم حذف كلمة المرور", + "deleteFailed": "فشل حذف كلمة المرور", + "vaultLocked": "تم قفل الخزنة", + "importedCount": "تم استيراد {count, plural, one {# كلمة مرور} other {# كلمات مرور}} بنجاح" + }, + "form": { + "addTitle": "إضافة كلمة مرور جديدة", + "editTitle": "تعديل كلمة المرور", + "serviceName": "اسم الخدمة", + "usernameEmail": "اسم المستخدم / البريد", + "password": "كلمة المرور", + "urlOptional": "رابط الموقع (اختياري)", + "notesOptional": "ملاحظات (اختياري)", + "tags": "الوسوم", + "placeholderService": "مثال: Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "أدخل أو أنشئ كلمة مرور", + "placeholderUrl": "https://...", + "placeholderNotes": "تفاصيل إضافية...", + "placeholderTags": "أضف وسوماً (Enter أو فاصلة)", + "strength": "القوة", + "strengthWeak": "ضعيفة", + "strengthMedium": "متوسطة", + "strengthStrong": "قوية", + "advancedGenerator": "مولّد متقدم", + "cancel": "إلغاء", + "saving": "جاري الحفظ...", + "savePassword": "حفظ كلمة المرور", + "updating": "جاري التحديث...", + "updatePassword": "تحديث كلمة المرور", + "addPasswordSrOnly": "إضافة كلمة مرور", + "triggerAdd": "إضافة كلمة مرور", + "toastSaved": "تم حفظ كلمة المرور بنجاح", + "toastSaveFailed": "فشل حفظ كلمة المرور", + "toastUpdated": "تم تحديث كلمة المرور بنجاح", + "toastUpdateFailed": "فشل تحديث كلمة المرور" + }, + "security": { + "vaultHealth": "صحة الخزنة", + "statusLabel": "الحالة", + "totalPasswords": "إجمالي كلمات المرور", + "activeEntries": "إدخالات نشطة في خزنتك", + "protected": "محمية", + "weakPasswords": "كلمات مرور ضعيفة", + "needingImprovement": "كلمات مرور تحتاج تحسيناً", + "allGood": "كل شيء جيد", + "reusedPasswords": "كلمات مرور مُعاد استخدامها", + "usedMultipleTimes": "كلمات مرور استُخدمت أكثر من مرة", + "unique": "فريدة", + "total": "الإجمالي", + "weak": "ضعيفة", + "reused": "مُعاد استخدامها", + "totalCountLabel": "{count, plural, one {# كلمة مرور} other {# كلمات مرور}}", + "weakCountLabel": "{count} ضعيفة", + "allSecure": "كلها آمنة", + "scoreExcellent": "ممتاز", + "scoreGood": "جيد", + "scoreFair": "مقبول", + "scoreCritical": "حرج" + }, + "importExport": { + "title": "استيراد / تصدير الخزنة", + "description": "إدارة بيانات كلمات المرور. يمكنك تصدير الخزنة إلى JSON أو الاستيراد من ملف JSON.", + "tabExport": "تصدير", + "tabImport": "استيراد", + "warningTitle": "تحذير", + "warningBody": "ملفات التصدير تحتوي كلمات مرورك كنص عادي. احفظ الملف بأمان واحذفه بعد الاستخدام.", + "exportBlurb": "حمّل خزنتك كاملة كملف JSON. يشمل الخدمات وأسماء المستخدمين وكلمات المرور والملاحظات.", + "downloadJson": "تنزيل تصدير JSON", + "pasteLabel": "لصق بيانات JSON", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "الصق محتوى ملف JSON مُصدَّر سابقاً هنا.", + "importing": "جاري الاستيراد...", + "importPasswords": "استيراد كلمات المرور", + "trigger": "استيراد / تصدير", + "toastExported": "تم تصدير الخزنة بنجاح", + "toastExportFailed": "فشل تصدير الخزنة", + "toastImportFailed": "فشل الاستيراد: تنسيق JSON غير صالح" + }, + "card": { + "copyUsername": "نسخ اسم المستخدم", + "openUrl": "فتح الرابط", + "strength": "القوة" + }, + "swipeable": { + "copy": "نسخ", + "delete": "حذف", + "copyPassword": "نسخ كلمة المرور", + "copyUsername": "نسخ اسم المستخدم", + "edit": "تعديل", + "deleteAction": "حذف" + }, + "floatingMenu": { + "lockVault": "قفل الخزنة", + "importExport": "استيراد / تصدير", + "addPassword": "إضافة كلمة مرور" + }, + "generator": { + "tabPassword": "كلمة مرور", + "tabPassphrase": "عبارة سرية", + "strength": "القوة", + "lengthLabel": "الطول: {length} حرفاً", + "lengthBadge": "{length} حرف", + "characterSets": "مجموعات الأحرف", + "uppercase": "أحرف كبيرة", + "lowercase": "أحرف صغيرة", + "numbers": "أرقام", + "symbols": "رموز", + "wordCountLabel": "عدد الكلمات: {count}", + "wordCountBadge": "{count, plural, one {# كلمة} other {# كلمات}}", + "separator": "الفاصل", + "capitalization": "الأحرف الكبيرة", + "includeNumber": "تضمين رقم" + } + }, + "EnvironmentManager": { + "subtitle": "متغيرات بيئة مشفّرة حسب المشروع والبيئة.", + "page": { + "loading": "جارٍ التحميل…", + "title": "مدير البيئة", + "loadFailed": "تعذّر تحميل مجموعات البيئة" + }, + "list": { + "title": "البيئات", + "loading": "جارٍ تحميل مجموعاتك…", + "emptyTitle": "لا توجد مجموعات بيئة بعد", + "emptyHint": "أنشئ مجموعة لكل مشروع وبيئة (مثل مفاتيح واجهة الإنتاج). يُشفّر كل شيء في متصفحك قبل الرفع.", + "searchPlaceholder": "ابحث في المشروع والبيئة والوسوم والملاحظات…", + "tagFilterPlaceholder": "تصفية حسب الوسم", + "variableCount": "{count, plural, one {# متغير} other {# متغيرات}}", + "updatedAgo": "عُدّل {time}", + "copyDotEnv": "نسخ .env", + "edit": "تعديل", + "delete": "حذف", + "cancel": "إلغاء", + "confirmDelete": "حذف", + "deleteDialogTitle": "حذف مجموعة البيئة؟", + "deleteDialogDescription": "يُزال النص المشفّر من حسابك. لا يمكن التراجع.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": "تم نسخ .env إلى الحافظة", + "deleted": "تم حذف مجموعة البيئة", + "deleteFailed": "فشل الحذف" + }, + "form": { + "addTitle": "مجموعة بيئة جديدة", + "editTitle": "تعديل مجموعة البيئة", + "triggerAdd": "إضافة مجموعة بيئة", + "addSrOnly": "إضافة مجموعة بيئة", + "project": "المشروع", + "environment": "البيئة", + "placeholderProject": "مثال: billing-api", + "placeholderEnvironment": "مثال: production", + "pasteEnvLabel": "لصق .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "دمج الأسطر الملصوقة", + "variables": "المتغيرات", + "addRow": "إضافة صف", + "keyPlaceholder": "المفتاح", + "valuePlaceholder": "القيمة", + "notesOptional": "ملاحظات (اختياري)", + "placeholderNotes": "سياسة التدوير، المالك، روابط…", + "tags": "الوسوم", + "placeholderTags": "أضف وسومًا (Enter أو فاصلة)", + "cancel": "إلغاء", + "save": "حفظ", + "saving": "جارٍ الحفظ…", + "update": "تحديث", + "updating": "جارٍ التحديث…", + "toastSaved": "تم حفظ مجموعة البيئة", + "toastSaveFailed": "فشل الحفظ", + "toastUpdated": "تم تحديث مجموعة البيئة", + "toastUpdateFailed": "فشل التحديث", + "toastPasteEmpty": "لم يُعثر على أسطر KEY=value", + "toastPasteApplied": "تم دمج {count} متغيرًا من اللصق", + "toastProjectEnvRequired": "المشروع والبيئة مطلوبان", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "جاري التحميل..." + }, + "manager": { + "foldersHeading": "المجلدات", + "tagsHeading": "الوسوم", + "tagsMore": "+{count} أخرى", + "tagsShowLess": "Show less", + "searchPlaceholder": "ابحث في الإشارات المرجعية...", + "import": "استيراد", + "export": "تصدير", + "exportAsHtml": "تصدير HTML", + "exportAsJson": "تصدير JSON", + "addBookmark": "إضافة إشارة", + "matchingQuery": "يطابق \"{query}\"", + "bookmarkCount": "{count, plural, one {# إشارة} other {# إشارات}}", + "allBookmarks": "كل الإشارات", + "uncategorized": "غير مصنّف", + "unknownFolder": "غير معروف" + }, + "card": { + "urlCopied": "تم نسخ الرابط", + "bookmarkDeleted": "تم حذف الإشارة", + "edit": "تعديل", + "delete": "حذف", + "copyUrl": "نسخ الرابط", + "deleteConfirmTitle": "حذف الإشارة؟", + "deleteConfirmDescription": "سيُحذف \"{title}\" نهائياً.", + "cancel": "إلغاء" + }, + "grid": { + "emptyTitle": "لا إشارات بعد", + "emptyDescription": "أضف أول إشارة أو استورد من المتصفح للبدء." + }, + "folderTree": { + "allBookmarks": "كل الإشارات", + "addSubfolder": "إضافة مجلد فرعي", + "rename": "إعادة تسمية", + "deleteFolderTitle": "حذف المجلد \"{name}\"؟", + "deleteFolderDescription": "سيُحذف المجلد وكل المجلدات الفرعية. تُنقل الإشارات إلى غير مصنّف." + }, + "import": { + "title": "استيراد الإشارات", + "description": "استورد الإشارات من ملف مُصدَّر من المتصفح.", + "parseError": "فشل تحليل ملف الإشارات. تأكد أنه تصدير صالح.", + "successToast": "تم استيراد {bookmarks} إشارة و{folders} مجلد", + "errorToast": "فشل استيراد الإشارات", + "chromeInstruction": "القائمة → الإشارات → مدير الإشارات → ⋮ → تصدير الإشارات", + "firefoxInstruction": "القائمة → الإشارات → إدارة الإشارات → استيراد ونسخ احتياطي → تصدير", + "safariInstruction": "ملف → تصدير الإشارات...", + "edgeInstruction": "القائمة → المفضلة → ⋯ → تصدير المفضلة", + "dropZoneTitle": "أسقط ملف الإشارات هنا", + "dropZoneHint": "أو انقر للتصفح (HTML أو JSON)", + "selectedFileHint": "انقر لاختيار ملف آخر", + "errorTitle": "خطأ", + "previewTitle": "معاينة الاستيراد", + "previewBookmarks": "{count, plural, one {# إشارة} other {# إشارات}}", + "previewFolders": "{count, plural, one {# مجلد} other {# مجلدات}}", + "importModeLabel": "وضع الاستيراد", + "modeMerge": "دمج مع الإشارات الحالية", + "modeReplace": "استبدال كل الإشارات الحالية", + "cancel": "إلغاء", + "importButton": "استيراد", + "importButtonWithCount": "استيراد {count, plural, one {# إشارة} other {# إشارات}}" + }, + "addBookmark": { + "titleAdd": "إضافة إشارة", + "titleEdit": "تعديل الإشارة", + "urlLabel": "الرابط", + "urlPlaceholder": "https://example.com", + "urlInvalid": "يرجى إدخال رابط صالح", + "titleLabel": "العنوان", + "titlePlaceholder": "عنوان الإشارة", + "titleRequired": "يرجى إدخال عنوان", + "descriptionLabel": "الوصف (اختياري)", + "descriptionPlaceholder": "ملاحظة عن هذه الإشارة", + "folderLabel": "المجلد", + "folderPlaceholder": "اختر مجلداً", + "noFolder": "بدون مجلد", + "tagsLabel": "الوسوم", + "tagsPlaceholder": "أضف وسوماً (Enter)", + "toastUpdated": "تم تحديث الإشارة", + "toastAdded": "تمت إضافة الإشارة", + "cancel": "إلغاء", + "submitAdd": "إضافة إشارة", + "submitUpdate": "تحديث الإشارة" + }, + "addFolder": { + "title": "إنشاء مجلد", + "nameLabel": "اسم المجلد", + "namePlaceholder": "مجلدي", + "nameRequired": "يرجى إدخال اسم المجلد", + "parentLabel": "المجلد الأب", + "parentPlaceholder": "اختر المجلد الأب", + "rootParent": "الجذر (بدون أب)", + "toastCreated": "تم إنشاء المجلد", + "cancel": "إلغاء", + "submit": "إنشاء مجلد" + }, + "editFolder": { + "title": "إعادة تسمية المجلد", + "nameLabel": "اسم المجلد", + "namePlaceholder": "اسم المجلد", + "nameRequired": "يرجى إدخال اسم المجلد", + "toastRenamed": "تمت إعادة تسمية المجلد", + "cancel": "إلغاء", + "save": "حفظ" + } + }, + "EmailValidator": { + "header": { + "title": "التحقق من البريد", + "description": "تحقق من عناوين البريد: الصيغة، النطاق، وإمكانية التسليم." + }, + "tabs": { + "single": "تحقق فردي", + "bulk": "رفع جماعي" + }, + "single": { + "cardTitle": "تحقق من بريد واحد", + "cardDescription": "أدخل عنوان بريد للتحقق من إمكانية التسليم والسمعة.", + "placeholder": "أدخل عنوان البريد...", + "clearAria": "مسح البريد", + "validate": "تحقق", + "validating": "جاري التحقق...", + "formatError": "يرجى إدخال بريد إلكتروني صالح" + }, + "toasts": { + "errorTitle": "خطأ", + "enterEmail": "يرجى إدخال بريد إلكتروني", + "invalidFormatTitle": "صيغة بريد غير صالحة", + "invalidFormatDescription": "يرجى إدخال بريد إلكتروني صالح", + "validateFailedDescription": "حدث خطأ أثناء التحقق من البريد", + "noEmailsTitle": "لم يُعثر على رسائل", + "noEmailsDescription": "تأكد أن Excel يحتوي عموداً باسم 'email'.", + "fileTooLargeTitle": "الملف كبير جداً", + "fileTooLargeDescription": "وُجد {total} بريد. سيُتحقق من أول 4000 فقط.", + "successTitle": "نجاح", + "successValidated": "تم التحقق من {count} بريد بنجاح.", + "batchFailedTitle": "فشل التحقق الجماعي", + "batchFailedDescription": "حدث خطأ أثناء معالجة التحقق الجماعي.", + "noExportTitle": "لا بيانات للتصدير", + "noExportValidOnly": "لا توجد عناوين صالحة للتصدير.", + "noExportAll": "لا نتائج للتصدير.", + "exportSuccessTitle": "تم التصدير بنجاح", + "exportSuccessExcelAll": "تم تصدير {count} بريد إلى Excel.", + "exportSuccessExcelValid": "تم تصدير {count} بريد صالح إلى Excel.", + "exportSuccessCsvAll": "تم تصدير {count} بريد إلى CSV.", + "exportSuccessCsvValid": "تم تصدير {count} بريد صالح إلى CSV." + }, + "disposableAlert": { + "title": "بريد مؤقت مكتشف", + "description": "هذا العنوان من مزود بريد مؤقت. قد يكون مؤقتاً أو غير آمِن." + }, + "score": { + "reliabilityTitle": "درجة الموثوقية", + "scoreLabel": "الدرجة" + }, + "details": { + "sectionTitle": "تفاصيل التحقق" + }, + "validation": { + "syntaxLabel": "الصيغة صالحة", + "syntaxTooltip": "يتحقق من أن البريد يتبع الصيغة الصحيحة (مثل user@domain.com)", + "domainLabel": "النطاق موجود", + "domainTooltip": "يتحقق من وجود اسم النطاق في البريد", + "mxLabel": "سجلات MX موجودة", + "mxTooltip": "يتحقق من تكوين سجلات تبادل البريد لاستقبال الرسائل", + "mailboxLabel": "صندوق البريد موجود", + "mailboxTooltip": "يتحقق من وجود صندوق البريد على الخادم", + "disposableLabel": "بريد مؤقت", + "disposableTooltip": "يُظهر إن كان البريد من خدمة مؤقتة (قد يكون خطيراً)", + "roleBasedLabel": "بريد وظيفي", + "roleBasedTooltip": "يحدد إن كان البريد وظيفياً (مثل support@) وليس شخصياً" + }, + "copy": { + "emailCopied": "تم نسخ البريد!", + "copyTooltip": "نسخ البريد" + }, + "bulk": { + "dropHere": "أسقط الملف هنا", + "dragHere": "اسحب وأفلت الملف هنا", + "uploadHint": "ارفع ملف .xlsx أو .xls أو .csv يحتوي عمود 'email' للتحقق حتى 4000 بريد.", + "filePrefix": "الملف:", + "selectFile": "اختر ملفاً", + "downloadTemplate": "تنزيل القالب" + }, + "bulkLoading": { + "title": "جاري التحقق من البريد...", + "batchProgress": "معالجة الدفعة {current} من {total}..." + }, + "stats": { + "totalProcessed": "إجمالي المعالج", + "valid": "صالح", + "risky": "محفوف بالمخاطر", + "invalid": "غير صالح" + }, + "results": { + "detailedTitle": "نتائج مفصلة", + "searchPlaceholder": "ابحث في البريد...", + "newVerification": "تحقق جديد", + "exportAll": "تصدير الكل", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "تصدير الصالح ({count})" + }, + "showingResults": "عرض {shown} من {total} نتيجة", + "table": { + "email": "البريد", + "status": "الحالة", + "score": "الدرجة", + "checks": "الفحوصات" + }, + "noSearchResults": "لا نتائج تطابق \"{query}\"", + "checksTooltip": { + "syntax": "الصيغة", + "domain": "النطاق", + "mx": "MX", + "mailbox": "صندوق البريد" + }, + "exportColumns": { + "email": "البريد", + "status": "الحالة", + "score": "الدرجة", + "syntax": "الصيغة", + "domain": "النطاق", + "mx": "MX", + "mailbox": "صندوق البريد", + "disposable": "مؤقت", + "roleBased": "وظيفي", + "valid": "صالح", + "invalid": "غير صالح", + "yes": "نعم", + "no": "لا" + }, + "sheets": { + "validEmails": "بريد صالح", + "results": "النتائج", + "template": "القالب" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "مستكشف NoSQL", + "heroSubtitle": "اتصل بقواعد البيانات لإدارة المجموعات وتنفيذ الاستعلامات وعرض بياناتك.", + "featureMultiTitle": "اتصالات متعددة", + "featureMultiDesc": "إدارة عدة اتصالات بقواعد البيانات في آن واحد.", + "featureMongoTitle": "دعم MongoDB", + "featureMongoDesc": "دعم أصلي لـ MongoDB مع تصفية متقدمة.", + "featureQueryTitle": "استعلام ذكي", + "featureQueryDesc": "منشئ استعلامات قوي مع تمييز الصياغة.", + "connectCta": "الاتصال بـ MongoDB", + "footerNote": "يدعم حالياً {db}. قواعد بيانات أخرى قريباً.", + "footerDb": "MongoDB", + "selectCollectionTitle": "اختر مجموعة", + "selectCollectionDesc": "اختر مجموعة من الشريط الجانبي لعرض المستندات واستكشاف بياناتك.", + "dialogAddConnection": "إضافة اتصال", + "toastConnectionAdded": "تمت إضافة الاتصال", + "toastFetchFailed": "فشل جلب مستندات {collection}", + "toastDocDeleted": "تم حذف المستند", + "deleteDocTitle": "هل أنت متأكد تماماً؟", + "deleteDocDescription": "لا يمكن التراجع. سيُحذف المستند نهائياً من المجموعة.", + "closeAllTitle": "إغلاق كل التبويبات؟", + "closeAllDescription": "سيُفقد أي تغيير غير محفوظ في مرشحات الاستعلام.", + "closeAllConfirm": "إغلاق الكل", + "cancel": "إلغاء", + "delete": "حذف" + }, + "connection": { + "titleEdit": "تعديل الاتصال", + "titleConnect": "الاتصال بـ MongoDB", + "subtitleEdit": "حدّث تفاصيل الاتصال أدناه.", + "subtitleConnect": "أدخل سلسلة اتصال MongoDB لبدء استكشاف بياناتك.", + "labelName": "الاسم", + "placeholderName": "مثال: قاعدة الإنتاج", + "labelConnectionString": "سلسلة الاتصال", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "الصيغة: mongodb://username:password@host:port/database", + "testConnection": "اختبار الاتصال", + "connecting": "جاري الاتصال...", + "updateConnect": "تحديث والاتصال", + "connect": "اتصال", + "cancelEdit": "إلغاء التعديل", + "savedConnections": "اتصالات محفوظة", + "emptySaved": "لا اتصالات محفوظة بعد.", + "lastUsed": "آخر استخدام {time}", + "toastTestOk": "نجح الاتصال!", + "toastTestFail": "فشل الاتصال: {message}", + "toastUpdated": "تم تحديث الاتصال", + "confirmDelete": "هل تريد حذف هذا الاتصال المحفوظ؟", + "toastDeletedConn": "تم حذف الاتصال", + "toastDeleteFail": "فشل حذف الاتصال", + "copyStringTitle": "نسخ سلسلة الاتصال", + "toastStringCopied": "تم نسخ سلسلة الاتصال!", + "editTitle": "تعديل", + "deleteTitle": "حذف" + }, + "sidebar": { + "explorer": "المستكشف", + "add": "إضافة", + "tooltipAdd": "إضافة اتصال جديد", + "tooltipRefresh": "تحديث الاتصالات", + "searchPlaceholder": "بحث...", + "loadingConnections": "جاري تحميل الاتصالات...", + "noConnectionsFound": "لا اتصالات", + "toastLoadFail": "فشل تحميل الاتصالات", + "toastConnectFail": "فشل الاتصال بـ {name}", + "toastCollectionsFail": "فشل جلب مجموعات {name}", + "toastRenamed": "تمت إعادة تسمية الاتصال", + "toastRenameFail": "فشلت إعادة تسمية الاتصال", + "confirmDeleteConnection": "هل تريد حذف الاتصال \"{name}\"؟", + "toastDeleted": "تم حذف الاتصال", + "toastDeleteConnFail": "فشل حذف الاتصال", + "confirmDropDb": "هل تريد حذف قاعدة البيانات \"{name}\"؟ لا يمكن التراجع.", + "toastDbDropped": "تم حذف قاعدة البيانات {name}", + "confirmDropCollection": "هل تريد حذف المجموعة \"{name}\"؟ لا يمكن التراجع.", + "toastCollectionDropped": "تم حذف المجموعة {name}", + "toastCollectionRenamed": "تمت إعادة تسمية المجموعة إلى {name}", + "toastDatabaseRenamed": "تمت إعادة تسمية قاعدة البيانات إلى {name}", + "connecting": "جاري الاتصال...", + "connectionFailed": "فشل الاتصال", + "noDatabases": "لا قواعد بيانات", + "loading": "جاري التحميل...", + "noCollections": "لا مجموعات", + "renameCollectionTitle": "إعادة تسمية المجموعة", + "renameDatabaseTitle": "إعادة تسمية قاعدة البيانات", + "placeholderNewCollection": "اسم المجموعة الجديد", + "placeholderNewDatabase": "اسم قاعدة البيانات الجديد", + "rename": "إعادة تسمية", + "refresh": "تحديث", + "copyDbName": "نسخ اسم قاعدة البيانات", + "toastDbNameCopied": "تم نسخ اسم قاعدة البيانات", + "dropDatabase": "حذف قاعدة البيانات", + "dropCollection": "حذف المجموعة", + "menuRename": "إعادة تسمية", + "menuCopyConnectionString": "نسخ سلسلة الاتصال", + "menuDeleteConnection": "حذف الاتصال", + "toastSidebarStringCopied": "تم نسخ سلسلة الاتصال!", + "cancel": "إلغاء", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} مستندات", + "table": "جدول", + "json": "JSON", + "tree": "شجرة", + "tooltipTable": "عرض جدولي", + "tooltipJson": "عرض JSON", + "tooltipTree": "عرض شجري", + "itemsPerPage": "عناصر لكل صفحة", + "perPage": "{n} / صفحة", + "prevPage": "الصفحة السابقة", + "nextPage": "الصفحة التالية", + "expandAll": "توسيع الكل", + "collapseAll": "طي الكل", + "refreshData": "تحديث البيانات", + "insert": "إدراج", + "export": "تصدير", + "actions": "إجراءات", + "invalidJson": "محتوى JSON غير صالح", + "copiedClipboard": "تم النسخ إلى الحافظة", + "docUpdated": "تم تحديث المستند بنجاح", + "docInserted": "تم إدراج المستند بنجاح", + "invalidJsonShort": "JSON غير صالح", + "docCopied": "تم نسخ المستند", + "openFullEditor": "فتح في المحرر الكامل", + "cellArray": "مصفوفة({length})", + "cellObject": "كائن", + "cellArrayBracket": "مصفوفة[{length}]", + "emptyFilterTitle": "لا مستندات تطابق الاستعلام", + "emptyFilterHint": "جرّب تعديل المرشح أو مسح الاستعلام.", + "clearFilter": "مسح المرشح", + "emptyCollectionTitle": "هذه المجموعة فارغة", + "emptyCollectionHint": "أدرج مستنداً للبدء.", + "insertDocument": "إدراج مستند", + "formatJson": "تنسيق JSON", + "compactJson": "ضغط JSON", + "copyClipboard": "نسخ إلى الحافظة", + "copyJson": "نسخ JSON", + "duplicate": "تكرار", + "editDoc": "تعديل المستند", + "deleteDoc": "حذف المستند", + "documentLabel": "مستند {n}", + "editDialogTitle": "تعديل المستند", + "insertDialogTitle": "إدراج مستند", + "viewValueTitle": "عرض القيمة", + "saveChanges": "حفظ التغييرات", + "close": "إغلاق", + "cancel": "إلغاء", + "insertButton": "إدراج", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "مرشح نشط مطبّق", + "filterActiveTooltip": "المرشح نشط", + "closeAll": "إغلاق الكل" + }, + "query": { + "migrateHistory": "تم نقل سجل الاستعلامات إلى السحابة", + "saveHistoryFail": "فشل حفظ سجل الاستعلامات", + "deleteQueryFail": "فشل حذف الاستعلام", + "invalidJsonQuery": "استعلام JSON غير صالح", + "placeholder": "مثال: { \"status\": \"active\" }", + "clearQuery": "مسح الاستعلام", + "openAdvanced": "فتح المحرر المتقدم", + "visualBuilder": "منشئ استعلام بصري", + "filterRules": "قواعد التصفية", + "addRule": "إضافة قاعدة", + "noFilters": "لا مرشحات. أضف قاعدة لتصفية المستندات.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "حقل", + "opPlaceholder": "عملية", + "typePlaceholder": "نوع", + "opRegex": "Regex", + "opIn": "ضمن", + "opNin": "ليس ضمن", + "opExists": "موجود", + "typeAuto": "تلقائي", + "typeString": "نص", + "typeNumber": "رقم", + "typeBoolean": "منطقي", + "typeDate": "تاريخ", + "typeObjectId": "ObjectId", + "true": "صحيح", + "false": "خطأ", + "valuePlaceholder": "قيمة", + "valueListPlaceholder": "قيمة1، قيمة2", + "clearFilters": "مسح المرشحات", + "applyFilters": "تطبيق المرشحات", + "queryHistory": "سجل الاستعلامات", + "noSavedQueries": "لا استعلامات محفوظة بعد", + "advancedEditorTitle": "محرر استعلام متقدم", + "templatesPlaceholder": "قوالب", + "templateEmptyAgg": "تجميع فارغ", + "templateMatch": "$match (تصفية)", + "templateLookup": "$lookup (ربط)", + "templateGroup": "$group (عدد/مجموع)", + "format": "تنسيق", + "formattedJson": "JSON مُنسَّق", + "runQuery": "تشغيل الاستعلام" + }, + "export": { + "selectFieldsError": "اختر حقلاً واحداً على الأقل للتصدير", + "success": "تم التصدير بنجاح إلى {filename}", + "fail": "فشل تصدير البيانات", + "title": "تصدير البيانات", + "formatLabel": "صيغة التصدير", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "اختر الحقول", + "selectAll": "تحديد الكل", + "fieldsSelected": "{count, plural, one {# حقل محدد} other {# حقول محددة}}", + "cancel": "إلغاء", + "export": "تصدير", + "sheetName": "بيانات" + }, + "jsonTree": { + "typeLabel": "النوع: {type}", + "itemSummary": "{count, plural, one {# عنصر} other {# عناصر}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "طلب جديد", + "importedRequest": "طلب مستورد", + "untitledRequest": "طلب بدون عنوان" + }, + "toasts": { + "curlImported": "تم استيراد cURL بنجاح", + "curlParseFailed": "فشل تحليل أمر cURL", + "invalidJsonBody": "جسم JSON غير صالح", + "requestFailed": "فشل الطلب: {message}", + "curlPasted": "تم لصق cURL وتحليله بنجاح", + "responseCopied": "تم نسخ الاستجابة", + "codeCopied": "تم نسخ الكود", + "copyFailed": "فشل نسخ المحتوى إلى الحافظة" + }, + "layout": { + "collections": "المجموعات", + "closeSidebar": "إغلاق الشريط الجانبي", + "openSidebar": "فتح الشريط الجانبي", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "الطريقة", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "جاري الإرسال...", + "send": "إرسال", + "invalidJsonBodyHelp": "لا يمكن الإرسال: نص JSON غير صالح" + }, + "requestTabs": { + "params": "معاملات", + "headers": "رؤوس", + "body": "الجسم", + "auth": "مصادقة", + "bodyType": "نوع الجسم", + "selectType": "اختر النوع", + "bodyNone": "لا شيء", + "bodyJson": "JSON", + "bodyText": "نص", + "noBodyHint": "لا محتوى جسم لهذا الطلب", + "switchToJson": "التبديل إلى جسم JSON", + "authType": "نوع المصادقة", + "noAuth": "بدون مصادقة", + "bearerToken": "رمز Bearer", + "basicAuth": "مصادقة أساسية", + "apiKey": "مفتاح API", + "labelToken": "الرمز", + "placeholderBearerToken": "رمز Bearer", + "labelUsername": "اسم المستخدم", + "placeholderUsername": "اسم المستخدم", + "labelPassword": "كلمة المرور", + "placeholderPassword": "كلمة المرور", + "labelKey": "المفتاح", + "placeholderApiKeyName": "اسم المفتاح (مثل X-Api-Key)", + "labelValue": "القيمة", + "placeholderValue": "القيمة", + "addTo": "إضافة إلى", + "header": "رأس", + "queryParams": "معاملات الاستعلام", + "noAuthHint": "هذا الطلب لا يستخدم مصادقة." + }, + "responsePanel": { + "emptyTitle": "جاهز للاستكشاف؟", + "emptyHint": "أدخل رابطاً واضغط إرسال لعرض الاستجابة هنا.", + "responsePreviewAlt": "معاينة الاستجابة", + "previewUnavailable": "المعاينة غير متاحة لهذا النوع من المحتوى", + "status": "الحالة", + "time": "الوقت", + "size": "الحجم", + "copyBody": "نسخ الجسم", + "downloadResponse": "تنزيل الاستجابة", + "bodyTab": "الجسم", + "previewTab": "معاينة", + "headersTab": "الرؤوس", + "binaryRawView": "لا يمكن عرض البيانات الثنائية في العرض الخام.", + "errorStatusLabel": "خطأ", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "تبويب جديد" + }, + "collectionsSidebar": { + "title": "موارد API", + "newCollection": "مجموعة جديدة", + "tabCollections": "المجموعات", + "tabHistory": "السجل", + "noCollectionsTitle": "لا مجموعات", + "noCollectionsHint": "أنشئ مجموعة لتنظيم طلبات API.", + "createCollection": "إنشاء مجموعة", + "newFolder": "مجلد جديد", + "rename": "إعادة تسمية", + "delete": "حذف", + "emptyCollection": "مجموعة فارغة", + "recentRequests": "طلبات حديثة", + "clearAll": "مسح الكل", + "noHistoryTitle": "لا سجل بعد", + "noHistoryHint": "أرسل طلباً ليظهر هنا.", + "dialogNewFolderTitle": "مجلد جديد", + "dialogNewFolderDescription": "أنشئ مجلداً جديداً لتنظيم الطلبات.", + "labelFolderName": "اسم المجلد", + "placeholderFolderName": "مجلدي", + "createFolder": "إنشاء مجلد", + "dialogNewCollectionTitle": "مجموعة جديدة", + "dialogNewCollectionDescription": "أنشئ مجموعة جديدة في المستوى الأعلى.", + "labelCollectionName": "اسم المجموعة", + "placeholderCollectionName": "مجموعتي", + "dialogRenameCollectionTitle": "إعادة تسمية المجموعة", + "dialogRenameCollectionDescription": "أدخل اسماً جديداً للمجموعة.", + "placeholderRenameCollection": "اسم المجموعة", + "renameAction": "إعادة تسمية", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "مجلد جديد", + "delete": "حذف", + "emptyFolder": "مجلد فارغ" + }, + "environmentManager": { + "noEnvironment": "لا بيئة", + "manageDialogTitle": "إدارة البيئات", + "placeholderNewEnv": "بيئة جديدة", + "variablesHeading": "متغيرات {name}", + "addVariable": "إضافة متغير", + "columnVariable": "متغير", + "columnValue": "قيمة", + "placeholderKey": "مفتاح", + "placeholderValue": "قيمة", + "selectEnvToEdit": "اختر بيئة للتعديل" + }, + "codeGenerator": { + "triggerTitle": "توليد الكود", + "dialogTitle": "توليد الكود", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "نسخ", + "errorGenerating": "خطأ في توليد الكود" + }, + "keyValueEditor": { + "columnKey": "مفتاح", + "columnValue": "قيمة", + "placeholderKey": "مفتاح", + "placeholderValue": "قيمة", + "addItem": "إضافة عنصر" + }, + "importCurl": { + "trigger": "استيراد cURL", + "dialogTitle": "استيراد cURL", + "dialogDescription": "الصق أمر cURL أدناه لاستيراده كطلب جديد.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "استيراد" + }, + "saveRequest": { + "trigger": "حفظ", + "dialogTitle": "حفظ الطلب", + "dialogDescription": "احفظ هذا الطلب في مجلد مجموعة.", + "labelName": "اسم الطلب", + "placeholderName": "طلبي", + "labelFolder": "المجلد", + "placeholderFolder": "اختر مجلداً", + "save": "حفظ" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "وضع التحرير", + "viewOnlyMode": "وضع العرض فقط", + "insertComponent": "إدراج مكوّن", + "addUnorderedList": "إضافة قائمة نقطية", + "addOrderedList": "إضافة قائمة مرقمة", + "addTable": "إضافة جدول" + }, + "addBlock": "إضافة كتلة", + "media": { + "popoverTitle": "إضافة وسائط", + "singleImage": "صورة واحدة", + "singleImageDesc": "رفع صورة واحدة", + "multipleImages": "صور متعددة", + "multipleImagesDesc": "رفع شبكة صور", + "video": "فيديو", + "videoDesc": "رفع ملف فيديو", + "uploadingAlt": "جاري الرفع...", + "uploadFailedAlt": "فشل الرفع" + }, + "export": { + "fabAria": "تصدير الكود", + "title": "تصدير الكود", + "description": "انسخ مخرجات HTML أو JSON من محتوى المحرر", + "preview": "معاينة", + "html": "HTML", + "json": "JSON", + "previewOptions": "خيارات المعاينة", + "enhanceSpaces": "تحسين المسافات", + "livePreview": "معاينة مباشرة لـ HTML المُصَيَّر", + "htmlTailwind": "HTML مع فئات Tailwind", + "editorJson": "حالة المحرر كـ JSON", + "copyHtml": "نسخ HTML", + "copyJson": "نسخ JSON", + "copied": "تم النسخ!" + }, + "template": { + "fabTitle": "تبديل القالب", + "title": "اختر قالباً", + "description": "اختر قالباً للبدء بسرعة بتخطيطات ومحتوى جاهز", + "catAll": "كل القوالب", + "catProductivity": "إنتاجية", + "catCreative": "إبداعي", + "catBusiness": "أعمال", + "catPersonal": "شخصي", + "noTemplates": "لا قوالب في هذه الفئة", + "confirmTitle": "استبدال المحتوى الحالي؟", + "confirmDescription": "يوجد محتوى في المحرر. تطبيق القالب سيستبدل كل المحتوى الحالي.", + "cannotUndo": "لا يمكن التراجع عن هذا الإجراء.", + "cancel": "إلغاء", + "replaceContent": "استبدال المحتوى" + }, + "command": { + "searchPlaceholder": "بحث في الأوامر...", + "noResults": "لا أوامر.", + "turnInto": "تحويل إلى", + "h1": { + "label": "عنوان 1", + "description": "عنوان قسم كبير", + "keywords": "h1,عنوان,عنوان كبير" + }, + "h2": { + "label": "عنوان 2", + "description": "عنوان قسم متوسط", + "keywords": "h2,عنوان,عنوان فرعي" + }, + "h3": { + "label": "عنوان 3", + "description": "عنوان قسم صغير", + "keywords": "h3,عنوان" + }, + "h4": { + "label": "عنوان 4", + "description": "عنوان صغير جداً", + "keywords": "h4,عنوان" + }, + "h5": { + "label": "عنوان 5", + "description": "عنوان أصغر", + "keywords": "h5,عنوان" + }, + "h6": { + "label": "عنوان 6", + "description": "أصغر عنوان", + "keywords": "h6,عنوان" + }, + "p": { + "label": "فقرة", + "description": "فقرة نص عادي", + "keywords": "p,فقرة,نص" + }, + "code": { + "label": "كتلة كود", + "description": "مقتطف كود", + "keywords": "code,كود,مقتطف" + }, + "blockquote": { + "label": "اقتباس", + "description": "اقتباس كتلة", + "keywords": "quote,اقتباس" + }, + "li": { + "label": "قائمة نقطية", + "description": "عنصر قائمة بنقطة", + "keywords": "list,نقطة,قائمة" + }, + "ol": { + "label": "قائمة مرقمة", + "description": "عنصر قائمة مرقم", + "keywords": "list,مرقم,قائمة" + }, + "img": { + "label": "صورة", + "description": "رفع أو تضمين صورة", + "keywords": "image,صورة,رفع" + }, + "video": { + "label": "فيديو", + "description": "رفع أو تضمين فيديو", + "keywords": "video,فيديو,رفع" + }, + "table": { + "label": "جدول", + "description": "إنشاء جدول", + "keywords": "table,جدول,صفوف,أعمدة" + } + }, + "toasts": { + "tableImported": "تم استيراد الجدول", + "tableImportedDesc": "تم استيراد جدول Markdown بنجاح", + "mediaPasted": "تم لصق الوسائط", + "pasteMixed": "تم لصق {imageCount} صورة و{videoCount} فيديو بنجاح.", + "pasteImages": "تم لصق {count} صورة بنجاح.", + "pasteVideos": "تم لصق {count} فيديو بنجاح.", + "pasteFailed": "فشل اللصق", + "unexpectedError": "حدث خطأ غير متوقع", + "uploadFailed": "فشل الرفع" + }, + "blockBackground": { + "menuItem": "تغيير لون الخلفية", + "dialogTitle": "لون خلفية الكتلة", + "dialogDescription": "اختر لون خلفية لهذه الكتلة", + "tabPreset": "ألوان جاهزة", + "tabCustom": "لون مخصص", + "sectionHeading": "ألوان الخلفية", + "apply": "تطبيق", + "colors": { + "none": "بدون", + "red": "أحمر", + "orange": "برتقالي", + "yellow": "أصفر", + "green": "أخضر", + "blue": "أزرق", + "indigo": "نيلي", + "purple": "بنفسجي", + "pink": "وردي", + "teal": "تركوازي", + "cyan": "سماوي", + "gray": "رمادي" + } + }, + "tableDialog": { + "title": "إنشاء جدول", + "description": "أنشئ جدولاً جديداً أو استورد من Markdown", + "markdownCheckbox": "لدي جدول Markdown", + "pasteMarkdownLabel": "الصق جدول Markdown", + "markdownPlaceholder": "| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "الصق جدول Markdown أعلاه. يجب أن يتضمن صف رأس وفاصل.", + "rows": "صفوف", + "columns": "أعمدة", + "maxDimensions": "الحد الأقصى: 20 صفاً × 10 أعمدة", + "cancel": "إلغاء", + "importTable": "استيراد الجدول", + "createTable": "إنشاء الجدول" + }, + "tableParse": { + "errors": { + "minRows": "يجب أن يحتوي الجدول على صف رأس وصف فاصل على الأقل", + "pipeFormat": "يجب أن تبدأ وتنتهي صفوف الجدول بـ |", + "headerMinCol": "صف الرأس يجب أن يحتوي عموداً واحداً على الأقل", + "separatorRow": "الصف الثاني يجب أن يكون فاصلاً (مثل |---|---|)", + "rowColumnMismatch": "الصف {row} يحتوي {got} أعمدة، المتوقع {expected}", + "parseFailed": "فشل تحليل جدول Markdown" + } + }, + "link": { + "editLink": "تعديل الرابط", + "addLink": "إضافة رابط", + "selectedText": "النص المحدد: \"{text}\"", + "linkUrl": "رابط URL", + "urlPlaceholder": "https://example.com", + "update": "تحديث", + "addLinkButton": "إضافة رابط", + "toastApplied": "تم تطبيق الرابط", + "toastLinkedTo": "مربوط بـ: {url}", + "toastRemoved": "تمت إزالة الرابط", + "toastRemovedDesc": "تمت إزالة الرابط من التحديد" + }, + "customClass": { + "devMode": "وضع المطور", + "toggleDevModeAria": "تبديل وضع المطور", + "searchPlaceholderDev": "بحث في الفئات... (مثل text، bg، flex)", + "searchPlaceholderUser": "بحث في الأنماط... (مثل أحمر، عريض، ظل)", + "appliesTitle": "يطبّق: {className}", + "noMatches": "لا فئات تطابق \"{query}\"", + "toastClassReplaced": "تم استبدال الفئة", + "toastClassReplacedDesc": "استُبدل \"{replaced}\" بـ \"{className}\"", + "toastCustomClassApplied": "تم تطبيق فئة مخصصة", + "toastAppliedClass": "الفئة المطبقة: {className}" + }, + "insertComponents": { + "title": "إدراج مكوّن", + "description": "اختر مكوّناً لإدراجه في المستند", + "noComponents": "لا مكوّنات متاحة", + "categories": { + "media": "وسائط", + "layout": "تخطيط", + "interactive": "تفاعلي", + "decoration": "زخرفة" + }, + "freeImage": { + "name": "صورة حرة الحركة", + "description": "أضف صورة يمكن وضعها في أي مكان وتغيير حجمها بحرية" + } + } + }, + "JsonFormatter": { + "title": "منسّق JSON", + "description": "نسّق JSON وتحقق منه وحرّره في عرض النص والشجرة.", + "documentNameText": "مستند نصي {n}", + "documentNameTree": "مستند شجري {n}", + "newDocument": "جديد", + "copy": "نسخ", + "save": "حفظ", + "load": "تحميل", + "saving": "جارٍ الحفظ...", + "leftDocNameLabel": "اسم المستند الأيسر", + "rightDocNameLabel": "اسم المستند الأيمن", + "toastCopiedText": "تم نسخ لوحة النص", + "toastCopiedTree": "تم نسخ لوحة الشجرة", + "toastCopyFailed": "فشل نسخ JSON", + "toastLoginRequired": "يرجى تسجيل الدخول لحفظ المستندات", + "toastSaved": "تم حفظ المستند", + "toastSaveFailed": "فشل حفظ المستند", + "format": "تنسيق", + "compact": "ضغط", + "sortKeys": "فرز المفاتيح", + "repair": "إصلاح", + "query": "استعلام", + "toastInvalidJson": "JSON غير صالح", + "toastAlreadyValid": "JSON صالح بالفعل", + "toastRepaired": "تم إصلاح JSON", + "toastRepairFailed": "تعذر إصلاح JSON" + }, + "JwtDecoder": { + "title": "محلّل JWT", + "subtitle": "اعرض الترويسة والحمولة. يعتمد الانتهاء على مطالبة exp. لا يتم إرسال أي شيء إلى الخادم؛ ولا يتم التحقق من التوقيع.", + "tokenLabel": "الرمز", + "clear": "مسح", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "نسخ", + "expiryLabel": "الانتهاء (exp)", + "signaturePresent": "التوقيع موجود (غير مُتحقَّق منه)", + "noExp": "لا توجد مطالبة exp في الحمولة.", + "status": { + "expired": "منتهي الصلاحية", + "valid": "صالح" + }, + "fields": { + "unix": "Unix", + "local": "محلي", + "utc": "UTC" + }, + "claims": { + "issuedAt": "وقت الإصدار (iat)", + "notBefore": "ليس قبل (nbf)" + }, + "panels": { + "header": "الترويسة", + "payload": "الحمولة" + }, + "errors": { + "empty": "الصق JWT لفك الترميز.", + "segments": "يحتاج JWT إلى ترويسة وحمولة على الأقل (جزآن).", + "base64": "تعذّر فك ترميز الترويسة أو الحمولة بصيغة base64url." + }, + "relative": { + "future": "بعد {n} {unit}", + "past": "قبل {n} {unit}" + }, + "units": { + "second": "{count, plural, one {ثانية} other {ثوانٍ}}", + "minute": "{count, plural, one {دقيقة} other {دقائق}}", + "hour": "{count, plural, one {ساعة} other {ساعات}}", + "day": "{count, plural, one {يوم} other {أيام}}" + }, + "tabs": { + "decode": "فك الترميز", + "sign": "مولّد / موقّع" + }, + "signer": { + "algLabel": "الخوارزمية (alg)", + "tabs": { + "payload": "الحمولة", + "header": "الترويسة" + }, + "payloadLabel": "JSON الحمولة", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON الترويسة", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "سر (HS*)", + "secretPlaceholder": "الصق سر HMAC…", + "privateKeyLabel": "مفتاح خاص PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "خوارزمية \"none\" تُنشئ رمزًا غير موقّع. هذا غير آمن ويجب استخدامه للاختبار فقط.", + "sign": "وقّع", + "verify": "تحقق من التوقيع", + "outputLabel": "الرمز (JWS مضغوط)", + "outputPlaceholder": "انقر \"وقّع\"…", + "copyToken": "نسخ الرمز", + "copied": "تم النسخ", + "verifyKeyLabel": "مفتاح التحقق (اختياري)", + "verifyKeyPlaceholder": "لـ RS*: الصق المفتاح العام (SPKI) بصيغة PEM. لـ HS*: اتركه فارغًا لإعادة استخدام السر.", + "verifyKeyHint": "إذا كان فارغًا، سيستخدم التحقق مفتاح التوقيع (مفيد لـ HS*).", + "verifyStatus": { + "valid": "التوقيع صالح", + "invalid": "التوقيع غير صالح" + }, + "securityNote": "كل شيء يعمل محليًا في المتصفح؛ لا يتم رفع المفاتيح أو الرموز." + }, + "signErrors": { + "invalidJson": "يجب أن تكون الترويسة والحمولة JSON صالحًا.", + "unsupportedAlg": "خوارزمية غير مدعومة.", + "missingKey": "مفتاح التوقيع مطلوب لهذه الخوارزمية.", + "cryptoFailed": "فشل التوقيع. تحقق من تنسيق المفتاح وحاول مرة أخرى." + }, + "verifyErrors": { + "empty": "أدخل JWT للتحقق.", + "segments": "يجب أن يحتوي JWT على 3 أجزاء بالضبط للتحقق من التوقيع.", + "unsupportedAlg": "خوارزمية alg غير مدعومة أو مفقودة في الترويسة.", + "missingKey": "مفتاح التحقق مطلوب لهذه الخوارزمية.", + "base64": "تعذر فك ترميز ترويسة JWT أو التوقيع.", + "cryptoFailed": "فشل التحقق. تحقق من تنسيق المفتاح وحاول مرة أخرى." + } + }, + "CertificatePemDecoder": { + "title": "فك ترميز الشهادة / PEM", + "subtitle": "الصق شهادة X.509 (-----BEGIN CERTIFICATE-----) أو طلب CSR (-----BEGIN CERTIFICATE REQUEST-----). يتم تحليل الموضوع والمصدر والتواريخ وأسماء SAN محليًا؛ لا يتم رفع أي شيء.", + "pemLabel": "إدخال PEM", + "clear": "مسح", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "جارٍ التحليل…", + "copyHex": "نسخ البصمة (سداسي بدون نقطتين)", + "errors": { + "empty": "الصق شهادة PEM أو CSR لفك الترميز.", + "noBlock": "لم يُعثر على كتلة PEM لشهادة أو CSR.", + "parseFailed": "تعذّر تحليل بيانات PEM هذه." + }, + "block": { + "title": "كتلة PEM {index}", + "certificate": "شهادة X.509", + "csr": "طلب توقيع شهادة" + }, + "fields": { + "subject": "الموضوع", + "issuer": "المُصْدِر", + "notBefore": "غير صالح قبل", + "notAfter": "غير صالح بعد", + "serial": "الرقم التسلسلي (سداسي)", + "sha256": "بصمة SHA-256", + "sans": "أسماء بديلة للموضوع", + "sansNone": "لا يوجد امتداد SAN.", + "signatureAlgorithm": "خوارزمية التوقيع", + "csrIssuerNote": "لا يتضمّن طلب CSR مُصدِرًا (يظهر ذلك في الشهادة الصادرة).", + "csrValidityNote": "لا يتضمّن طلب CSR تواريخ صلاحية (تظهر في الشهادة الصادرة)." + }, + "sanType": { + "dns": "DNS", + "dn": "اسم الدليل", + "email": "بريد", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "معرّف مسجّل", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "منسّق SQL", + "subtitle": "نسّق SQL لـ MySQL أو PostgreSQL أو SQLite. يعمل داخل المتصفح؛ لا يتم رفع أي شيء.", + "dialectLabel": "اللهجة", + "keywordsLabel": "الكلمات المفتاحية", + "indentLabel": "الإزاحة", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "أحرف كبيرة", + "lower": "أحرف صغيرة", + "preserve": "الإبقاء" + }, + "spaces": "{n} مسافات", + "format": "تنسيق", + "clear": "مسح", + "counter": "{current} / {max} حرفًا", + "inputPanel": "المدخل", + "formattedPanel": "المخرجات", + "copy": "نسخ", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "انقر على \"تنسيق\"…", + "errors": { + "tooLong": "المدخلات محدودة إلى {max} حرفًا.", + "couldNotFormat": "تعذر تنسيق SQL." + } + }, + "YamlFormatter": { + "title": "منسّق YAML", + "subtitle": "نسّق YAML وتحقق منه وحوّله في متصفحك. لا يتم رفع أي شيء.", + "outputLabel": "الإخراج", + "outputModes": { + "yaml": "YAML (منسّق)", + "json": "JSON" + }, + "indentLabel": "الإزاحة", + "spaces": "{n} مسافات", + "format": "تنسيق", + "clear": "مسح", + "copy": "نسخ", + "counter": "{current} / {max} حرف", + "inputPanel": "الإدخال", + "outputPanel": "الإخراج", + "errors": { + "tooLong": "الإدخال محدود بـ {max} حرف.", + "couldNotFormat": "تعذّر تحليل هذا YAML." + } + }, + "GraphqlFormatter": { + "title": "منسّق GraphQL", + "subtitle": "نسّق استعلامات GraphQL والطفرات والاشتراكات داخل المتصفح. لا يتم رفع أي شيء.", + "tabs": { + "format": "تنسيق", + "build": "مُنشئ الاستعلام" + }, + "outputModeLabel": "المخرجات", + "outputModes": { + "pretty": "طباعة مقروءة", + "minify": "تصغير" + }, + "format": "تنسيق", + "clear": "مسح", + "counter": "{current} / {max} حرفًا", + "inputPanel": "الإدخال", + "outputPanel": "المخرجات", + "copy": "نسخ", + "builderHint": "ألِف هيكلًا ثم طبِّقه على المحرر. تعريفات المتغيرات بصياغة GraphQL (مثل $id: ID!) — الأقواس اختيارية.", + "builder": { + "operation": "العملية", + "operationName": "اسم العملية", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "تعريفات المتغيرات", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "مجموعة الاختيار", + "apply": "تطبيق على الإدخال", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "الإدخال محدود بـ {max} حرفًا.", + "couldNotFormat": "تعذّر تحليل أو طباعة هذا GraphQL.", + "emptySelection": "أضِف مجموعة اختيار قبل التطبيق." + } + }, + "RegexTester": { + "title": "مختبر Regex", + "subtitle": "Regex بنمط JavaScript مباشرةً على نصك. يتم تمييز التطابقات؛ ولا يغادر شيء متصفحك.", + "patternLabel": "النمط", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g عام", + "i": "i تجاهل حالة الأحرف", + "m": "m متعدد الأسطر", + "s": "s dotAll", + "u": "u يونيكود", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(فارغ)", + "noFlags": "—", + "matches": "{count, plural, one {تطابق} other {تطابقات}}", + "testTextLabel": "نص الاختبار", + "counter": "{current} / {max}", + "clear": "مسح", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "النص محدود إلى {max} حرفًا.", + "invalid": "تعبير نمطي غير صالح.", + "runaway": "تم الإيقاف بعد {max} تطابقًا (قد يكون النمط جامحًا)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "محول الطابع الزمني", + "subtitle": "ثواني Unix (≤10 أرقام) أو ميلي ثانية (أعداد أطول)، ISO-8601 أو سلاسل يمكن للمتصفح تحليلها. كل شيء يعمل محليًا.", + "inputLabel": "الإدخال", + "placeholder": "1744200000 أو 2025-04-09T12:00:00Z", + "now": "الآن", + "copy": "نسخ", + "hint": "الصق قيمة أو انقر الآن.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "نسبي", + "human": "مقروء" + }, + "fields": { + "seconds": "ثوانٍ", + "milliseconds": "ميلي ثانية", + "utc": "UTC", + "local": "محلي", + "distance": "المسافة" + }, + "errors": { + "empty": "أدخل طابعًا زمنيًا Unix أو سلسلة ISO أو نص تاريخ.", + "invalidNumber": "طابع زمني رقمي غير صالح.", + "outOfRange": "خارج النطاق لـ JavaScript Date.", + "couldNotParse": "تعذر التحليل كـ Unix أو ISO-8601 أو تاريخ قابل للتحليل من المتصفح." + } + }, + "HttpStatusCodes": { + "title": "رموز حالة HTTP", + "subtitle": "ابحث وتصفّح رموز حالة استجابات HTTP (1xx5xx) مع أوصاف مختصرة ومراجع RFC. يعمل محليًا في المتصفح.", + "header": { + "title": "رموز حالة HTTP", + "description": "مرجع قابل للبحث لرموز استجابة HTTP مع أوصاف ومراجع RFC." + }, + "searchPlaceholder": "بحث: 404، not found، rate limit، gateway…", + "classFilterPlaceholder": "تصفية الفئة…", + "classFilterAll": "كل الفئات", + "resultsCount": "{count} من {total}", + "table": { + "code": "Code", + "phrase": "العبارة", + "description": "الوصف", + "copy": "نسخ" + }, + "noResults": "لا نتائج لعبارة «{query}».", + "specPrefix": "المعيار:", + "copied": "تم نسخ الرمز" + }, + "CronBuilder": { + "title": "منشئ تعبيرات Cron", + "subtitle": "Cron قياسي بخمسة حقول: minute hour day month weekday. يستخدم المحلّل seconds=0 داخليًا. يعمل أيضًا التنسيق الاختياري ذو 6 حقول (sec min hour dom mon dow) في محرّر التعبير.", + "tabs": { + "builder": "منشئ مرئي", + "expression": "تعبير" + }, + "presetsLabel": "الإعدادات المسبقة", + "fieldsLabel": "الحقول", + "rawExpressionLabel": "التعبير الخام", + "expressionHint": "اخرج من الحقل (فقدان التركيز) للتحليل ومزامنة المنشئ. {count} {count, plural, one {رمز} other {رموز}}.", + "currentExpressionLabel": "التعبير الحالي", + "copy": "نسخ", + "quickPick": "اختيار سريع", + "emptyExpression": "—", + "plainLanguageLabel": "وصف نصي", + "nextRunsLabel": "التشغيلات التالية (الوقت المحلي)", + "fields": { + "minute": "الدقيقة", + "hour": "الساعة", + "dayOfMonth": "يوم الشهر", + "month": "الشهر", + "dayOfWeek": "يوم الأسبوع" + }, + "presets": { + "everyMinute": "كل دقيقة", + "every5Minutes": "كل 5 دقائق", + "every15Minutes": "كل 15 دقيقة", + "everyHour": "كل ساعة (عند :00)", + "every6Hours": "كل 6 ساعات", + "dailyMidnight": "يوميًا عند منتصف الليل", + "daily0900": "يوميًا عند 09:00", + "weekdays0900": "أيام العمل عند 09:00", + "weeklySunday": "أسبوعيًا (الأحد 00:00)", + "monthlyFirst": "شهريًا (اليوم الأول 00:00)", + "yearlyJan1": "سنويًا (1 يناير 00:00)" + }, + "picks": { + "minute": { + "every": "كل دقيقة" + }, + "hour": { + "every": "كل ساعة" + }, + "dom": { + "any": "أي يوم", + "last": "آخر يوم" + }, + "month": { + "every": "كل شهر" + }, + "dow": { + "any": "أي يوم", + "weekdays": "أيام العمل", + "weekend": "عطلة نهاية الأسبوع" + } + }, + "months": { + "jan": "ينا", + "feb": "فبر", + "mar": "مار", + "apr": "أبر", + "may": "ماي", + "jun": "يون", + "jul": "يول", + "aug": "أغس", + "sep": "سبت", + "oct": "أكت", + "nov": "نوف", + "dec": "ديس" + }, + "weekdays": { + "sun": "أحد", + "mon": "اثن", + "tue": "ثلث", + "wed": "أرب", + "thu": "خمس", + "fri": "جمع", + "sat": "سبت" + }, + "errors": { + "fieldCount": "المتوقع 5 حقول (minute hour day month weekday) أو 6 مع الثواني.", + "invalid": "تعبير cron غير صالح." + } + }, + "Base64": { + "title": "ترميز / فك ترميز Base64", + "subtitle": "حوّل النص إلى Base64 أو فك ترميز سلاسل Base64", + "upload": "رفع", + "clear": "مسح", + "modes": { + "encode": "ترميز", + "decode": "فك الترميز" + }, + "swapTitle": "تبديل الإدخال والإخراج", + "panels": { + "plainText": "نص عادي", + "base64Input": "إدخال Base64", + "base64Output": "إخراج Base64", + "decodedText": "نص مفكوك" + }, + "placeholders": { + "encode": "أدخل نصًا لترميزه إلى Base64…", + "decode": "الصق سلسلة Base64 لفك ترميزها…" + }, + "dropHere": "أفلت الملف هنا", + "charCount": "{count} حرف", + "download": { + "title": "تنزيل الإخراج", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "نسخ إلى الحافظة", + "outputPlaceholder": "سيظهر الإخراج هنا…", + "errors": { + "invalidBase64": "سلسلة Base64 غير صالحة. تحقق من الإدخال.", + "encodeFailed": "فشل ترميز الإدخال." + } + }, + "UrlEncode": { + "title": "ترميز / فك ترميز URL", + "subtitle": "ترميز/فك ترميز بالنسبة المئوية باستخدام encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "رفع", + "clear": "مسح", + "modes": { + "encode": "ترميز", + "decode": "فك الترميز" + }, + "swapTitle": "تبديل الإدخال والإخراج", + "panels": { + "plainText": "نص عادي", + "encodedInput": "إدخال مُرمَّز", + "encodedOutput": "إخراج مُرمَّز", + "decodedText": "نص مفكوك الترميز" + }, + "placeholders": { + "encode": "أدخل نصًا لترميزه بالنسبة المئوية…", + "decode": "الصق نصًا مُرمَّزًا بالنسبة المئوية لفك ترميزه…" + }, + "dropHere": "أفلت الملف هنا", + "charCount": "{count} حرفًا", + "download": { + "title": "تنزيل الإخراج", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "نسخ إلى الحافظة", + "outputPlaceholder": "سيظهر الإخراج هنا…", + "errors": { + "invalidEncoding": "ترميز النسبة المئوية غير صالح. تحقّق من أزواج % غير المكتملة أو تسلسلات الهروب غير الصحيحة.", + "encodeFailed": "تعذّر الترميز (Unicode غير صالح في الإدخال)." + } + }, + "UrlParser": { + "title": "محلّل URL", + "subtitle": "تقسيم عنوان URL إلى بروتوكول ومضيف ومسار ومعاملات استعلام وتجزئة (الجزء بعد #).", + "upload": "رفع", + "clear": "مسح", + "panels": { + "input": "إدخال URL", + "output": "النتيجة" + }, + "placeholders": { + "input": "الصق عنوان URL (مثل https://example.com/path?foo=bar#section) أو مضيف/مسار (مثل example.com/path)…" + }, + "dropHere": "أسقط الملف هنا", + "charCount": "{count} حرفًا", + "outputPlaceholder": "ستظهر المكوّنات المحلّلة هنا…", + "fields": { + "protocol": "البروتوكول", + "host": "المضيف", + "path": "المسار", + "hash": "التجزئة", + "queryParams": "معاملات الاستعلام" + }, + "paramCount": "{count} معاملات", + "noParams": "لا توجد معاملات استعلام.", + "paramKey": "المفتاح", + "paramValue": "القيمة", + "copyTitle": "نسخ إلى الحافظة", + "copyParamTitle": "نسخ كـ key=value", + "download": { + "title": "تنزيل JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "عنوان URL غير صالح. تأكد من وجود اسم مضيف (example.com) ومن صحة الأحرف." + } + }, + "UuidGenerator": { + "title": "مولّد UUID / ULID", + "subtitle": "أنشئ UUID (RFC 9562) أو ULID في المتصفح. تصدير دفعي حتى {max} لكل تشغيل.", + "format": "التنسيق", + "kindHints": { + "ulid": "معرّف قابل للفرز وآمن للروابط بطول 26 حرفًا", + "uuid4": "عشوائي (RFC 9562)", + "uuid7": "مرتّب زمنيًا، ملّي ثانية يونكس + عشوائي", + "uuid1": "مبني على MAC/الوقت (RFC 9562)", + "uuid6": "وقت غريغوري معاد ترتيبه", + "uuid3": "تجزئة MD5 للاسم + مساحة الاسم", + "uuid5": "تجزئة SHA‑1 للاسم + مساحة الاسم", + "nil": "معرف UUID الصفري (كل البتات صفر، RFC 9562)" + }, + "countLabel": "العدد (1–{max})", + "nameLabel": "الاسم (سلسلة الإدخال)", + "namePlaceholder": "مثال: my.app/resource", + "bulkHint": "في التشغيل الدفعي تُضاف #0 و#1… ليكون كل معرّف فريدًا (v3/v5 حتمية).", + "namespace": "مساحة الاسم", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID مخصص" + }, + "customNamespaceLabel": "UUID لمساحة الاسم", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "إنشاء", + "output": "المخرجات", + "lines": "{count} سطر", + "download": { + "title": "تنزيل .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "نسخ الكل", + "uppercaseToggle": "إخراج بأحرف كبيرة", + "hyphensToggle": "إظهار الواصلات", + "copyJsonTitle": "نسخ كمصفوفة JSON", + "outputPlaceholder": "انقر على \"إنشاء\"…", + "errors": { + "invalidCustomNamespace": "يجب أن تكون مساحة الاسم المخصصة UUID صالحًا.", + "missingName": "الاسم مطلوب لـ UUID v3 و v5.", + "unknown": "تعذّر إنشاء المعرّفات." + } + }, + "SecretApiKeyGenerator": { + "title": "مولّد سر / مفتاح API", + "subtitle": "أنشئ سلاسل عشوائية تشفيرية بطول وأبجدية قابلة للضبط. تصدير بالجملة حتى {max} في كل مرة. يكمّل مولّد UUID / ULID.", + "alphabetLabel": "الأبجدية", + "alphabetHint": "تُزال الأحرف المكررة. يُختار كل رمز بشكل موحّد باستخدام crypto.getRandomValues مع اختيار بالرفض (دون انحياز المعيار).", + "alphabetCount": "{count} رمزًا مميزًا", + "presetsLabel": "إعدادات مسبقة", + "preset": { + "base64url": "Base64url", + "alphanumeric": "أبجدية رقمية", + "hex": "سداسي عشري (صغير)", + "asciiPrintable": "ASCII قابل للطباعة" + }, + "lengthLabel": "طول كل سر (1–{maxLen})", + "countLabel": "العدد (1–{maxBulk})", + "generate": "إنشاء", + "output": "المخرجات", + "lines": "{count} أسطر", + "download": { + "title": "تنزيل .txt", + "filename": "أسرار.txt" + }, + "copyTitle": "نسخ الكل", + "outputPlaceholder": "انقر \"إنشاء\"…", + "entropy": "{bits} بت من الإنتروبيا", + "strength": { + "weak": "ضعيف", + "fair": "متوسط", + "strong": "قوي" + }, + "excludeAmbiguous": "استبعاد الأحرف الغامضة", + "excludeAmbiguousHint": "يزيل {chars} — أحرف يسهل قراءتها بشكل خاطئ أو معادية لسطر الأوامر.", + "prefixLabel": "بادئة المفتاح (اختياري)", + "errors": { + "emptyAlphabet": "أضف حرفًا واحدًا على الأقل إلى الأبجدية.", + "alphabetTooLong": "بعد إزالة التكرارات، يمكن أن تحتوي الأبجدية على {maxAlpha} حرفًا كحد أقصى.", + "lengthOutOfRange": "يجب أن يكون الطول بين 1 و{maxLen}.", + "countOutOfRange": "يجب أن يكون العدد بين 1 و{maxBulk}.", + "unknown": "تعذّر إنشاء الأسرار." + } + }, + "LoremIpsum": { + "title": "مولد نصوص اللورم إيبسوم", + "subtitle": "أنشئ نصوصاً لاتينية بديلة لتخطيطاتك ونماذجك.", + "paragraphs": "فقرات", + "sentences": "جمل", + "words": "كلمات", + "count": "العدد", + "generate": "توليد", + "copy": "نسخ", + "startWith": "ابدأ بـ 'Lorem ipsum...'", + "outputPlaceholder": "سيظهر الناتج هنا...", + "outputType": "نوع الإخراج", + "bulletList": "قائمة نقطية", + "htmlMarkup": "ترميز HTML", + "htmlMarkupHint": "التفاف الإخراج في فقرات، أو استخدام قائمة لوضع النقاط", + "output": "الناتج", + "download": "تنزيل" + }, + "ColorPicker": { + "title": "منتقي الألوان ومحوّلها", + "subtitle": "حوّل HEX وRGB وHSL؛ وابنِ لوحات ألوان منسجمة. تتم المعالجة بالكامل في متصفحك.", + "nativePicker": "منتقي الألوان الأصلي", + "chooseColor": "اختر لوناً", + "eyeDropper": "قطارة العين", + "invalidHex": "لون HEX غير صالح.", + "cssValues": "قيم CSS", + "palettes": "لوحات الألوان", + "copied": "تم النسخ", + "clickToCopy": "انقر للنسخ", + "copyAllHex": "نسخ جميع قيم HEX", + "copy": "نسخ", + "copyColor": "نسخ {color}", + "paletteShades": "درجات", + "paletteComplementary": "تكميلي", + "paletteTriadic": "ثلاثي", + "paletteAnalogous": "تناظري", + "paletteSplit": "تكميلي منقسم", + "paletteSquare": "مربع" + }, + "ContrastChecker": { + "title": "فاحص التباين", + "subtitle": "يحسب نسبة التباين WCAG 2.1 بين المقدّم والخلفية. يعرض مطابقة AA وAAA للنص العادي والكبير. يعمل بالكامل في المتصفح.", + "foreground": "النص / المقدّم", + "background": "الخلفية", + "hexPlaceholder": "#RRGGBB", + "swap": "تبديل الألوان", + "chooseForeground": "اختر لون المقدّم", + "chooseBackground": "اختر لون الخلفية", + "eyeDropper": "قطّارة", + "ratio": "نسبة التباين", + "copyRatio": "نسخ النسبة", + "copied": "تم النسخ", + "previewHeading": "معاينة", + "previewBody": "صِف خَلقَ خَودٍ كَمِثْلِ الشَّمْسِ إِذْ بَزَغَتْ يَحْظَى الضَّجِيجُ بِسَوْدَانٍ مِنْ بَعِيدٍ. أبجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ.", + "previewCaption": "نص تجريبي بالحجم الافتراضي.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — نص عادي (4.5:1)", + "levelAALarge": "AA — نص كبير (3:1)", + "levelAAANormal": "AAA — نص عادي (7:1)", + "levelAAALarge": "AAA — نص كبير (4.5:1)", + "pass": "مطابق", + "fail": "غير مطابق", + "invalidHex": "لون HEX غير صالح.", + "largeTextHint": "النص الكبير: 18 نقطة فأكثر عاديًا، أو 14 نقطة فأكثر غامقًا (تعريفات WCAG)." + }, + "DiffChecker": { + "title": "مدقق الاختلافات", + "subtitle": "مقارنة الأسطر جنباً إلى جنب بين نصين. كل شيء يبقى في متصفحك.", + "rowsCount": "{count} صفوف", + "truncatedWarning": "عرض أول {max} صف.", + "limitExceeded": "كل جانب محدود بـ {max} حرف.", + "clearBoth": "مسح الكل", + "original": "الأصلي", + "modified": "المعدّل", + "originalPlaceholder": "الصق النص الأصلي…", + "modifiedPlaceholder": "الصق النص المعدّل…", + "comparisonLabel": "مقارنة جنباً إلى جنب", + "emptyState": "أدخل نصاً في كلا اللوحتين للمقارنة." + }, + "JsonSchemaGenerator": { + "title": "مولّد مخطط JSON", + "subtitle": "الصق JSON صالحًا لتوليد مخطط JSON أو نماذج مُنمّطة. كل شيء يعمل في المتصفح.", + "languageLabel": "لغة المخرجات", + "inputLabel": "إدخال JSON", + "inputPlaceholder": "الصق JSON هنا…", + "outputLabel": "المخرجات المُولَّدة", + "outputPlaceholder": "يظهر المخطط أو الأنواع المُولَّدة هنا…", + "parseError": "JSON غير صالح", + "clear": "مسح", + "copy": "نسخ", + "copied": "تم النسخ إلى الحافظة", + "copyFailed": "تعذّر النسخ", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "مخطط JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "جاري تحميل المحرر…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "الصورة إلى Base64", + "subtitle": "قم بإسقاط صورة، واحصل على URI للبيانات أو سلسلة Base64 الأولية. كل شيء يبقى في متصفحك.", + "upload": "تحميل الصورة", + "clear": "واضح", + "modes": { + "dataUri": "معرف الموارد المنتظم (URI) للبيانات", + "rawString": "قاعدة خام64" + }, + "panels": { + "imageInput": "إدخال الصورة", + "output": "إخراج Base64" + }, + "dropHere": "أسقط الصورة هنا", + "charCount": "{عدد} حرف", + "placeholders": { + "uploadImage": "قم بسحب وإسقاط صورة أو انقر فوق تحميل للبدء." + }, + "fileTooLarge": "الملف كبير جدًا. قد تؤدي المعالجة إلى تجميد متصفحك.", + "copyTitle": "نسخ إلى الحافظة", + "download": { + "title": "تنزيل النص", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "ضغط الصور", + "subtitle": "صغّر مخرجات JPEG أو PNG أو WebP محليًا مع التحكم بالجودة — لا يتم رفع أي شيء.", + "upload": "رفع صورة", + "clear": "مسح", + "dropHere": "أسقط الصورة هنا", + "placeholders": { + "uploadImage": "اسحب وأفلت صورة، أو انقر لاختيار ملف." + }, + "fileTooLarge": "الملف كبير جدًا (الحد الأقصى {maxMb} ميجابايت).", + "unsupportedFormat": "نوع الملف غير مدعوم. استخدم JPEG أو PNG أو WebP أو GIF أو AVIF أو BMP.", + "compressFailed": "تعذر ضغط هذه الصورة. جرّب ملفًا أو تنسيقًا آخر.", + "panels": { + "original": "الأصل", + "compressed": "معاينة مضغوطة" + }, + "outputFormat": "تنسيق الإخراج", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "الجودة", + "qualityPercent": "{value}٪", + "qualityPngHint": "PNG بلا فقدان في المتصفح؛ شريط الجودة يؤثر على JPEG وWebP فقط.", + "sizes": { + "original": "الأصل: {size}", + "compressed": "بعد الضغط: {size}", + "saved": "أصغر بنسبة {percent}٪" + }, + "largerWarning": "حجم المخرجات أكبر من المدخلات — جرّب تنسيقًا أو جودة مختلفة.", + "download": "تنزيل", + "downloadFilename": "صورة-mضغوطة", + "processing": "جاري الضغط…", + "emptyCompressed": "تظهر المعاينة المضغوطة هنا." + }, + "CssGradientBuilder": { + "title": "CSS منشئ التدرج", + "subtitle": "منشئ التدرج المرئي لـ CSS مع التحكم في الزاوية وتوقف الألوان ونسخ CSS بنقرة واحدة.", + "preview": "معاينة التدرج", + "settings": "إعدادات", + "type": "يكتب", + "linear": "خطي", + "radial": "شعاعي", + "angle": "زاوية", + "stops": "توقف اللون", + "color": "لون", + "position": "موضع (٪)", + "addStop": "أضف توقف", + "deleteStop": "حذف التوقف", + "output": "مخرجات CSS", + "copy": "انسخ CSS", + "copied": "منسوخ!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore مولد", + "subtitle": "قم بإنشاء ملفات .gitignore مدمجة على الفور لمجموعة أو مجموعة تقنية محددة.", + "searchPlaceholder": "مكدسات البحث (مثل Node وPython وmacOS)...", + "noResults": "لم يتم العثور على أكوام.", + "selectedStacks": "الأكوام المختارة", + "output": "معاينة الإخراج", + "copy": "نسخ إلى الحافظة", + "copied": "منسوخ!", + "download": "تحميل .gitignore", + "downloading": "جارٍ التنزيل...", + "errorFetching": "فشل جلب قائمة أعلى التل.", + "errorGenerating": "فشل في إنشاء .gitignore من الأكوام المحددة.", + "generating": "جارٍ الإنشاء...", + "emptyState": "حدد واحدًا أو أكثر من مجموعات التكنولوجيا لإنشاء .gitignore الخاص بك." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "ارفع CSV أو Excel، عدّل JSON، ثم نزّل CSV أو XLSX. بيانات جدولية فقط؛ خلايا التاريخ في Excel تصبح طوابع ISO في JSON.", + "dropTitle": "أسقط ملف CSV أو Excel", + "dropHint": "يُستخدم الورقة الأولى لملفات .xlsx / .xls. يجب أن يحتوي CSV على صف عناوين.", + "chooseFile": "اختر ملفًا", + "jsonLabel": "JSON (مصفوفة كائنات)", + "jsonPlaceholder": "الصق مصفوفة JSON من الكائنات أو استورد CSV / Excel…", + "loadSample": "تحميل مثال", + "actions": { + "format": "تنسيق JSON", + "clear": "مسح", + "copy": "نسخ", + "copied": "تم النسخ", + "downloadCsv": "تنزيل CSV", + "downloadXlsx": "تنزيل Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "بيانات", + "toastLoadedCsv": "تم تحميل CSV في المحرر", + "toastLoadedExcel": "تم تحميل Excel في المحرر", + "toastFormatted": "تم تنسيق JSON", + "toastExportedCsv": "تم تنزيل CSV", + "toastExportedXlsx": "تم تنزيل Excel", + "toastCopied": "تم النسخ إلى الحافظة", + "footnote": "كل شيء يعمل في المتصفح. يجب أن يكون JSON مصفوفة غير فارغة من الكائنات. القيم المتداخلة تُسلسل كـ JSON داخل خلايا CSV.", + "errors": { + "unsupportedFile": "استخدم ملف .csv أو .xlsx أو .xls.", + "parseFailed": "تعذّر تحليل هذا الملف.", + "invalidJson": "JSON غير صالح", + "exportFailed": "فشل التصدير", + "copyFailed": "تعذّر النسخ" + } + }, + "SnippetManager": { + "title": "مدير مقاطع الشيفرة", + "subtitle": "خزّن المقاطع في المتصفح أو سجّل الدخول لمزامنتها مع حسابك. بدّل بين العرض والتحرير، انسخ بنقرة واحدة، اختر لغة أو كشفًا تلقائيًا، ونسّق JSON أو SQL أو أكواد يدعمها Monaco.", + "snippets": "المقاطع", + "newSnippet": "مقطع جديد", + "newSnippetTitle": "مقطع جديد", + "defaultSnippetTitle": "مرحبًا", + "defaultSnippetCode": "// مرحبًا بك في مقاطع الشيفرة\n// جرّب العرض مقابل التحرير، النسخ، التنسيق واللغة التلقائية\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "البحث في المقاطع…", + "searchAria": "البحث في المقاطع", + "emptySearch": "لا توجد مقاطع تطابق بحثك.", + "snippetTitle": "العنوان", + "snippetTitlePlaceholder": "اسم المقطع", + "language": "اللغة", + "detectedHint": "تمييز بناء الجملة: {lang}", + "badgeAuto": "تلقائي · {lang}", + "modeView": "عرض", + "modeEdit": "تحرير", + "copy": "نسخ", + "copied": "تم النسخ", + "format": "تنسيق", + "cancel": "إلغاء", + "deleteTitle": "حذف المقطع؟", + "deleteDescription": "سيتم إزالة «{title}» من هذا المتصفح.", + "deleteConfirm": "حذف", + "editorAria": "محرر شيفرة المقطع", + "editorLoading": "جاري تحميل المحرر…", + "toastCopied": "تم النسخ إلى الحافظة", + "toastCopyFailed": "تعذّر النسخ", + "toastNothingToCopy": "لا يوجد شيء للنسخ", + "toastFormatted": "تم التنسيق", + "toastFormatFailed": "تعذّر التنسيق", + "toastFormatJsonInvalid": "JSON غير صالح — صحّح الصياغة أولًا", + "toastFormatUnsupported": "لا يوجد مُنسّق لهذه اللغة — جرّب JSON أو SQL أو وضع التحرير", + "toastDeleted": "تم حذف المقطع", + "toastLoadFailed": "تعذّر تحميل المقاطع من الخادم.", + "toastSyncFailed": "تعذّرت المزامنة مع الخادم.", + "languages": { + "auto": "اكتشاف تلقائي", + "plaintext": "نص عادي", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "عميل SQL", + "dialogTitle": "اتصالات SQL", + "mobileSheetTitle": "مستكشف SQL", + "newTabLabel": "علامة تبويب استعلام جديدة", + "vaultLockedTitle": "الخزنة مقفلة", + "vaultLockedDesc": "افتح الخزنة للوصول إلى الاتصالات المحفوظة.", + "heroTitle": "عميل SQL", + "heroDesc": "اتصل بـ PostgreSQL أو MySQL أو MariaDB. يتم تشفير بيانات الاعتماد في متصفحك.", + "heroAddConnection": "إضافة اتصال", + "selectTableTitle": "اختر جدولاً أو افتح علامة تبويب استعلام", + "selectTableDesc": "انقر على جدول في الشريط الجانبي أو استخدم ⌘↩.", + "toastLoadFail": "فشل تحميل الاتصالات." + }, + "connection": { + "titleNew": "اتصال SQL جديد", + "titleEdit": "تعديل الاتصال", + "labelDbType": "نوع قاعدة البيانات", + "labelName": "اسم الاتصال", + "placeholderName": "قاعدة بيانات الإنتاج", + "labelHostPort": "المضيف والمنفذ", + "labelDatabase": "قاعدة البيانات", + "placeholderDatabase": "mydb", + "labelUsername": "اسم المستخدم", + "labelPassword": "كلمة المرور", + "labelSsl": "تفعيل SSL / TLS", + "sslDesc": "تشفير الاتصال بالخادم", + "securityNote": "يتم تشفير بيانات الاعتماد بـ AES-256 في المتصفح قبل التخزين", + "btnTest": "اختبار الاتصال", + "btnSave": "حفظ والاتصال", + "btnUpdate": "تحديث", + "btnCancelEdit": "إلغاء التعديل", + "savedConnections": "الاتصالات المحفوظة", + "emptySaved": "ستظهر الاتصالات المحفوظة هنا", + "confirmDelete": "حذف هذا الاتصال؟", + "toastFillFields": "املأ المضيف وقاعدة البيانات قبل الاختبار.", + "toastConnectOk": "تم الاتصال! {version}", + "toastConnectFail": "فشل الاتصال: {message}", + "toastUnlockVault": "افتح الخزنة أولاً.", + "toastFieldsRequired": "المضيف وقاعدة البيانات مطلوبان.", + "toastUpdated": "تم تحديث الاتصال.", + "toastSaved": "تم حفظ الاتصال.", + "toastSaveFail": "فشل الحفظ: {message}", + "toastDeleted": "تم حذف الاتصال.", + "toastDeleteFail": "فشل حذف الاتصال." + }, + "sidebar": { + "title": "مستكشف SQL", + "tooltipAddConnection": "إضافة اتصال", + "tooltipRefreshSchema": "تحديث المخطط", + "noConnections": "لا توجد اتصالات محفوظة بعد", + "toastSchemaFail": "فشل تحميل المخطط: {message}" + }, + "editor": { + "btnRun": "تشغيل", + "executing": "جارٍ تنفيذ الاستعلام…", + "emptyTitle": "شغّل استعلاماً لرؤية النتائج", + "emptyHint": "اضغط ⌘↩ أو انقر على تشغيل", + "toastNoConnection": "لا يوجد اتصال نشط." + }, + "results": { + "filterPlaceholder": "تصفية النتائج…", + "rowCount": "{count} صف", + "rowCountFiltered": "{filtered} / {total} صف", + "exportCsv": "CSV", + "noRows": "لم يتم إرجاع أي صفوف" + } + }, + "QrCodeGenerator": { + "title": "مُولّد رمز QR", + "subtitle": "حوّل أي نص أو رابط إلى صورة PNG لرمز QR. كل المعالجة تتم محليًا في المتصفح.", + "contentLabel": "المحتوى", + "contentPlaceholder": "https://example.com، نص عادي، سلسلة Wi‑Fi، vCard…", + "styleLabel": "النمط", + "dotTypeLabel": "شكل النقطة", + "dotTypeSquare": "مربع", + "dotTypeDots": "نقاط", + "dotTypeRounded": "مدوّر", + "dotTypeExtraRounded": "مدوّر جداً", + "dotTypeClassy": "أنيق", + "dotTypeClassyRounded": "أنيق مدوّر", + "cornerSquareLabel": "مربعات الزوايا", + "cornerSquareDot": "نقطة", + "cornerSquareSquare": "مربع", + "cornerSquareExtraRounded": "مدوّر جداً", + "cornerDotLabel": "نقاط الزوايا", + "cornerDotDot": "نقطة", + "cornerDotSquare": "مربع", + "eccLabel": "تصحيح الخطأ", + "eccL": "منخفض (~7%)", + "eccM": "متوسط (~15%)", + "eccQ": "ربعي (~25%)", + "eccH": "عالٍ (~30%)", + "marginLabel": "الهامش (وحدات)", + "widthLabel": "عرض الصورة", + "widthEffective": "يُعرض بحجم {px} بكسل", + "darkColor": "الوحدات الداكنة", + "lightColor": "الوحدات الفاتحة", + "resetColors": "إعادة تعيين الألوان", + "logoLabel": "شعار مركزي", + "logoMarginLabel": "هامش", + "uploadLogo": "رفع صورة", + "removeLogo": "إزالة الشعار", + "downloadPng": "تنزيل PNG", + "downloadSvg": "تنزيل SVG", + "copyImage": "نسخ الصورة", + "copied": "تم النسخ", + "clear": "مسح", + "previewAlt": "معاينة رمز QR", + "emptyHint": "اكتب أو الصق المحتوى لمعاينة رمز QR.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "تعذر إنشاء رمز QR. جرّب نصًا أقصر أو مستوى تصحيح أخطاء أقل.", + "copyUnsupported": "المتصفح لا يدعم نسخ الصور إلى الحافظة.", + "copyFailed": "تعذر النسخ إلى الحافظة." + } + }, + "IpSubnetCalculator": { + "title": "حاسبة IP / الشبكة الفرعية", + "subtitle": "تفاصيل CIDR لـ IPv4 وIPv6 في المتصفح—دون طلبات خارجية.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "الشبكة", + "placeholderIpv4": "10.0.0.0/24 أو 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 أو 2001:db8::1/64", + "hintIpv4": "استخدم CIDR (مثل /24) أو عنوان IPv4 مع قناع شبكة منقّط.", + "hintIpv6": "ضمّن طول البادئة (مثل /64). إن حُذف، يُفترض /128.", + "copyValue": "نسخ القيمة", + "rows": { + "cidr": "CIDR", + "network": "عنوان الشبكة", + "netmask": "قناع الشبكة الفرعية", + "wildcard": "قناع بدل", + "broadcast": "البث", + "firstHost": "أول مضيف", + "lastHost": "آخر مضيف", + "totalAddresses": "إجمالي العناوين", + "usableHosts": "المضيفون القابلون للاستخدام", + "prefixLength": "طول البادئة (بت)", + "networkIpv6": "عنوان الشبكة", + "lastIpv6": "آخر عنوان في النطاق", + "totalAddressesIpv6": "عناوين في الشبكة الفرعية" + }, + "usableNote": { + "pointToPoint": "روابط /31 نقطية تستخدم كلا العنوانين كمضيفين (RFC 3021).", + "singleHost": "/32 لعنوان مضيف واحد." + }, + "ipv6RangeHint": { + "normal": "يُظهر الأداة النطاق الرياضي الكامل؛ قواعد الموجّه قد تختلف.", + "single": "/128 يعني عنوانًا واحدًا.", + "full": "/0 يغطي كل عناوين IPv6." + }, + "errors": { + "invalidIpv4": "شبكة IPv4 غير صالحة. جرّب CIDR أو عنوانًا مع قناع.", + "invalidIpv6": "شبكة IPv6 غير صالحة. استخدم عنوان/بادئة مثل 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "مُولّد الهاش", + "subtitle": "ملخصات MD5 وSHA-1 وSHA-2 للنص أو الملفات؛ bcrypt لكلمات المرور (إدخال نصي). كل شيء محليًا في المتصفح.", + "algorithmLabel": "الخوارزمية", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "نص", + "modeFile": "ملف", + "inputLabelText": "المدخلات", + "placeholderText": "اكتب أو الصق النص…", + "chooseFile": "اختر ملفًا", + "clearFile": "مسح", + "fileHint": "تُقرأ الملفات في الذاكرة فقط؛ الحد الأقصى {maxMb} ميغابايت لكل ملف. لا يتم الرفع.", + "outputLabel": "المختزلة (سِداسي عشري)", + "copyHash": "نسخ المختزلة", + "copied": "تم النسخ", + "byteCount": "{count, plural, zero {# بايت} one {# بايت} two {# بايتان} few {# بايتات} many {# بايتًا} other {# بايت}}", + "charCount": "{count, plural, zero {# حرف} one {# حرف} two {# حرفان} few {# أحرف} many {# حرفًا} other {# حرف}}", + "emptyHint": "أدخل نصًا أو اختر ملفًا لحساب الهاش.", + "securityNote": "لا يُنصح بـ MD5 وSHA-1 لاستخدامات أمنية جديدة؛ يُفضّل SHA-256 أو SHA-512.", + "errors": { + "fileTooLarge": "الملف أكبر من {maxMb} ميغابايت.", + "fileReadFailed": "تعذر قراءة هذا الملف." + }, + "bcryptRoundsLabel": "التكلفة (جولات)", + "bcryptRoundsHint": "القيم الأعلى أبطأ وأقوى ضد التخمين. القيمة الشائعة 10.", + "fileTabDisabledTitle": "bcrypt يجزّئ كلمات المرور من النص فقط، وليس الملفات العشوائية.", + "placeholderBcrypt": "اكتب أو الصق كلمة مرور للتجزئة…", + "outputLabelBcrypt": "تجزئة bcrypt", + "copyBcrypt": "نسخ التجزئة", + "emptyHintBcrypt": "أدخل كلمة مرور لحساب تجزئة bcrypt.", + "securityNoteBcrypt": "كل مخرجات bcrypt تتضمن ملحًا عشوائيًا؛ نفس كلمة المرور تعطي سلسلة مختلفة في كل مرة. هذا متوقع. استخدم تكلفة عالية في الإنتاج.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "ملعب التشفير", + "subtitle": "AES-GCM في المتصفح: شفّر إلى حزمة JSON بعبارة سر أو مفتاح خام، ثم فك التشفير محليًا. تعليمي وأحيانًا عملي — لا يُرفع أي شيء.", + "tabEncrypt": "تشفير", + "tabDecrypt": "فك التشفير", + "keyModeLabel": "مادة المفتاح", + "modePassphrase": "عبارة سر (PBKDF2 + AES-GCM)", + "modeRawKey": "مفتاح AES خام (hex أو Base64)", + "aesBitsLabel": "طول مفتاح AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "ترميز المفتاح الخام", + "encodingAuto": "تلقائي (hex إذا بدا كhex، وإلا Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "المفتاح الخام", + "rawKeyPlaceholder": "مفتاح 32 بايت كـ 64 محرف hex أو Base64 لـ AES-256 (الطول يعتمد على حجم AES أعلاه).", + "rawKeyPlaceholderDecrypt": "مطلوب فقط عندما تحتوي الحزمة على keyDerivation \"none\".", + "passphraseLabel": "عبارة السر", + "passphrasePlaceholder": "تُستخدم عبارة السر فقط في المتصفح لاشتقاق المفتاح.", + "passphrasePlaceholderDecrypt": "مطلوب فقط عندما تستخدم الحزمة PBKDF2-SHA256.", + "pbkdf2IterLabel": "تكرارات PBKDF2", + "pbkdf2Hint": "بين 10,000 و 2,000,000. الافتراضي يطابق إعدادات الخزن الشائعة.", + "plaintextLabel": "نص صريح", + "plaintextPlaceholder": "اكتب أو الصق الرسالة المراد تشفيرها…", + "bundleInputLabel": "حزمة مشفرة (JSON)", + "bundlePlaceholder": "الصق JSON من تشفير (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "حزمة مشفرة", + "plaintextOutputLabel": "نص صريح", + "encryptButton": "تشفير", + "decryptButton": "فك التشفير", + "copyBundle": "نسخ JSON", + "copyPlaintext": "نسخ النص الصريح", + "copied": "تم النسخ", + "emptyBundleHint": "يظهر JSON المشفر هنا بعد التشفير.", + "emptyDecryptHint": "يظهر النص المفكوك هنا.", + "decryptKeyHint": "استخدم المفتاح الخام إذا كان keyDerivation \"none\"؛ وعبارة السر لـ PBKDF2-SHA256. تحتفظ الحزمة بالملح والتكرارات وIV وطول AES.", + "securityNote": "المفاتيح والنص الصريح لا يغادران متصفحك. لأسرار الإنتاج استخدم أدوات مخصصة.", + "errors": { + "emptyKey": "أدخل مفتاحًا خامًا يطابق حجم AES.", + "emptyPassphrase": "أدخل عبارة سر.", + "badKeyMaterial": "تعذر قراءة المفتاح كـ hex أو Base64.", + "wrongKeyLength": "طول المفتاح لا يطابق AES (16 / 24 / 32 بايت).", + "invalidJson": "JSON غير صالح.", + "invalidBundle": "هذا JSON ليس حزمة تشفير مدعومة.", + "decryptFailed": "فشل فك التشفير — سر خاطئ أو حزمة تالفة أو معاملات غير متطابقة.", + "encryptFailed": "فشل التشفير. تحقق من Web Crypto والمدخلات." + } + }, + "HmacGenerator": { + "title": "مُولّد HMAC", + "subtitle": "لتوقيع خطافات الويب واختبار تكاملات واجهات API. HMAC-SHA1/256/384/512 بصيغة hex أو Base64—يُحسب محليًا في المتصفح.", + "secretLabel": "المفتاح السري", + "messageLabel": "الرسالة / الحمولة", + "placeholderSecret": "الصق سر التوقيع…", + "placeholderMessage": "الصق النص الخام أو السلسلة للمصادقة (غالبًا نفس بايتات جسم الخطاف).", + "algorithmLabel": "خوارزمية HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "صيغة الإخراج", + "formatHex": "Hex (أحرف صغيرة)", + "formatBase64": "Base64", + "outputLabel": "التوقيع", + "copyOutput": "نسخ التوقيع", + "copied": "تم النسخ", + "byteCountHint": "{count, plural, one {# بايت} other {# بايت}} (UTF-8)", + "emptyHint": "أدخل مفتاحًا سريًا لحساب HMAC.", + "securityNote": "لا تشارك سر التوقيع أبدًا. لا تُرفع البيانات من هذه الصفحة—كل شيء يعمل في المتصفح.", + "errors": { + "cryptoFailed": "تعذّر حساب HMAC. تأكد من دعم Web Crypto وحاول مجددًا.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "مُولّد رمز TOTP / 2FA", + "subtitle": "الصق سر Base32 من تطبيق المصادقة لعرض رمز RFC 6238 المكوّن من ستة أرقام الحالي (SHA-1، 30 ث). يعمل محليًا في المتصفح—مفيد لاختبار تسجيل الدخول أو MFA.", + "secretLabel": "السر (Base32)", + "placeholderSecret": "الصق المفتاح السري (أحرف A–Z وأرقام 2–7، كما يظهر عند إعداد تطبيق المصادقة)…", + "codeLabel": "الرمز الحالي", + "copyCode": "نسخ الرمز", + "copied": "تم النسخ", + "nextIn": "يتجدد خلال {seconds} ث", + "standardHint": "خطوة زمنية 30 ث، 6 أرقام، HMAC-SHA-1 (متوافق مع Google Authenticator).", + "digitsLabel": "الأرقام", + "periodLabel": "الفترة", + "algorithmLabel": "الخوارزمية", + "periodSeconds": "{seconds} ث", + "emptyHint": "الصق سرًا لإنشاء رمز مكوّن من ستة أرقام.", + "invalidHint": "صحّح السر لعرض الرمز.", + "securityNote": "عامل السر ككلمة مرور. لا يغادر المتصفح؛ لا يتم رفع أي بيانات.", + "importLabel": "استيراد من عنوان otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "استيراد", + "qrSectionTitle": "رمز QR للتزويد", + "qrHint": "امسح باستخدام تطبيق مصادقة (Google Authenticator، 1Password، Aegis…) لإضافة هذا السر. يشفّر رمز QR السر — تعامل معه مثل كلمة المرور.", + "accountLabel": "الحساب", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "المُصدر (اختياري)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "أدخل سرًا صالحًا لإنشاء رمز QR.", + "errors": { + "invalidSecret": "هذا ليس سر Base32 صالحًا.", + "cryptoFailed": "تعذّر حساب TOTP. تحقق من Web Crypto وحاول مجددًا.", + "invalidUri": "هذا ليس عنوان otpauth:// TOTP صالحًا.", + "unsupportedUri": "يستخدم هذا العنوان قيمًا لا تدعمها الأداة (يجب أن تكون الأرقام 6 أو 8؛ والفترة 15 أو 30 أو 60 ثانية)." + } + }, + "MarkdownPreview": { + "title": "معاينة Markdown وHTML", + "subtitle": "عارض Markdown مباشر مع تصدير HTML وتحويل HTML → Markdown. كل شيء يعمل في متصفحك.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "إدخال Markdown", + "htmlInputLabel": "إدخال HTML", + "previewLabel": "معاينة", + "htmlOutputLabel": "مصدر HTML", + "mdOutputLabel": "إخراج Markdown", + "inputPlaceholder": "# مرحبا\\n\\naكتب **markdown** هنا…", + "htmlInputPlaceholder": "الصق HTML هنا…", + "copyHtml": "نسخ HTML", + "copyMarkdown": "نسخ Markdown", + "exportHtml": "تصدير HTML", + "copied": "تم النسخ", + "charCount": "{count, plural, one {# حرف} other {# أحرف}}", + "emptyHint": "ابدأ بكتابة Markdown لرؤية المعاينة المباشرة وإخراج HTML.", + "emptyHintHtmlToMd": "الصق HTML لتحويله إلى Markdown.", + "securityNote": "كل شيء يعمل محليًا في متصفحك. لا يتم تحميل أي بيانات." + }, + "NumberBaseConverter": { + "title": "محوّل الأساس الرقمي", + "subtitle": "تحويل الأعداد الصحيحة بين الأسس 2–36. يدعم البادئات الاختيارية 0x / 0b / 0o. كل شيء محليًا في المتصفح.", + "inputBaseLabel": "من الأساس", + "outputBaseLabel": "إلى الأساس", + "inputLabel": "الرقم المدخل", + "placeholderInput": "أدخل عددًا صحيحًا بالأساس المختار…", + "outputLabel": "القيمة المحوّلة", + "copyOutput": "نسخ النتيجة", + "copied": "تم النسخ", + "swap": "تبديل الأساسين", + "byteCountHint": "{count, plural, one {# بايت} other {# بايت}} (طول الإدخال UTF-8)", + "emptyHint": "أدخل رقمًا للتحويل.", + "note": "الأرقام بعد 9 تستخدم الحروف A–Z (غير حساسة لحالة الأحرف). يتم تجاهل المسافات والشرطات السفلية.", + "errors": { + "invalidNumber": "القيمة غير صالحة للأساس المختار." + } + }, + "FormatConverter": { + "title": "محوّل الصيغ", + "subtitle": "حوّل بين JSON و YAML و TOML و XML في أي اتجاه. جميع 12 تركيبة. يعمل كل شيء في متصفحك.", + "fromLabel": "من", + "toLabel": "إلى", + "swap": "تبديل", + "swapTitle": "تبديل الصيغ واستخدام المخرجات كمدخلات", + "copy": "نسخ", + "copied": "تم النسخ", + "charCount": "{count, plural, one {# حرف} other {# أحرف}}", + "emptyHint": "الصق أو اكتب مدخلاتك على اليسار لرؤية المخرجات المحوّلة.", + "localNote": "كل شيء يعمل محليًا في متصفحك. لا يتم رفع أي شيء." + }, + "MimeTypeLookup": { + "title": "البحث عن نوع MIME", + "subtitle": "اعثر على نوع MIME (نوع الوسائط) حسب الامتداد أو اسم الملف الكامل. كل شيء يعمل محليًا في متصفحك.", + "inputLabel": "الامتداد أو اسم الملف", + "placeholder": "مثل .png أو package.json أو photo.jpeg", + "hint": "نصيحة: الصق امتدادًا (مع أو بدون نقطة) أو اسم ملف كامل.", + "clear": "مسح", + "exampleFilename": "استخدم اسم ملف مثال", + "exampleExtension": "استخدم امتدادًا مثال", + "resultLabel": "النتيجة", + "normalizedLabel": "الإدخال المُوحّد:", + "mimeTypeLabel": "نوع MIME", + "contentTypeLabel": "ترويسة Content-Type", + "charsetLabel": "مجموعة الأحرف", + "copyMimeType": "نسخ نوع MIME", + "copy": "نسخ", + "copied": "تم النسخ", + "emptyHint": "أدخل امتدادًا أو اسم ملف للبحث عن نوع MIME.", + "notFound": "لم يتم العثور على نوع MIME لهذه القيمة.", + "localNote": "كل شيء يعمل محليًا في متصفحك. لا يتم رفع أي شيء." + }, + "UserAgentParser": { + "title": "محلل User-Agent", + "subtitle": "الصق سلسلة User-Agent (UA) للحصول على تفاصيل المتصفح ونظام التشغيل والمحرك والجهاز والمعالج. كل شيء يعمل محليًا في متصفحك.", + "inputLabel": "سلسلة User-Agent", + "placeholder": "الصق User-Agent هنا…", + "hint": "نصيحة: يمكنك نسخ UA من ترويسات الطلب أو السجلات أو أدوات المطور في المتصفح.", + "clear": "مسح", + "exampleDesktop": "استخدم مثال سطح المكتب", + "exampleMobile": "استخدم مثال الهاتف", + "resultLabel": "التفاصيل", + "browserLabel": "المتصفح", + "osLabel": "نظام التشغيل", + "engineLabel": "المحرك", + "deviceTypeLabel": "نوع الجهاز", + "deviceVendorLabel": "الشركة المصنعة", + "deviceModelLabel": "الطراز", + "cpuArchLabel": "معمارية CPU", + "copyJson": "نسخ JSON", + "copy": "نسخ", + "copied": "تم النسخ", + "emptyHint": "الصق سلسلة User-Agent لعرض التفاصيل.", + "localNote": "كل شيء يعمل محليًا في متصفحك. لا يتم رفع أي شيء." + }, + "MockDataGenerator": { + "title": "مولّد بيانات وهمية", + "subtitle": "عرّف الأعمدة، اختر أنواع البيانات، وصدّر مجموعات للبذور أو Postman أو الاختبار. حتى {max} صفًا، كل شيء في المتصفح — بلا رفع.", + "mockarooLink": "مسار عمل يشبه Mockaroo", + "rowsLabel": "الصفوف", + "rowsHint": "بحد أقصى {max} صفًا لكل توليد ليبقى التبويب سريعًا.", + "formatLabel": "التنسيق", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "اسم جدول SQL", + "presetsLabel": "قوالب", + "presetPlaceholder": "تحميل قالب…", + "presetDefault": "بداية (مستخدم)", + "presetUsers": "المستخدمون والأدوار", + "presetProducts": "وحدات SKU للتجارة الإلكترونية", + "presetApiLog": "سجل طلبات HTTP", + "generate": "توليد", + "warningDuplicateNames": "أسماء أعمدة مكررة: الأعمدة اللاحقة تستبدل السابقة في كائنات JSON. أعد التسمية.", + "schemaHeading": "المخطط", + "addField": "إضافة عمود", + "removeField": "حذف عمود", + "duplicateField": "نسخ العمود", + "colName": "الاسم", + "colType": "النوع", + "colOptions": "خيارات", + "colBlank": "فراغ %", + "optMin": "الحد الأدنى", + "optMax": "الحد الأقصى", + "optDecimals": "عشري", + "optDateFrom": "من تاريخ", + "optDateTo": "إلى تاريخ", + "optValues": "a, b, c", + "optStep": "خطوة", + "optRowBase": "أساس", + "output": "المخرجات", + "outputPlaceholder": "انقر «توليد» لإنشاء البيانات…", + "copy": "نسخ", + "download": "تنزيل", + "copied": "تم النسخ", + "groups": { + "personal": "شخصي", + "location": "الموقع", + "internetApi": "الإنترنت وواجهة API", + "numbersIds": "أرقام والمعرّفات", + "dateTime": "التاريخ والوقت", + "textDesign": "النص والتصميم", + "custom": "مخصّص" + }, + "fieldTypes": { + "first_name": "الاسم الأول", + "last_name": "اسم العائلة", + "full_name": "الاسم الكامل", + "email": "Email", + "username": "اسم المستخدم", + "password": "كلمة المرور", + "phone": "الهاتف", + "company": "الشركة", + "job_title": "المسمى الوظيفي", + "avatar_url": "رابط الصورة الرمزية", + "city": "المدينة", + "country": "الدولة", + "country_code": "رمز الدولة (ISO)", + "street_address": "العنوان", + "zip_code": "الرمز البريدي", + "latitude": "خط العرض", + "longitude": "خط الطول", + "url": "URL", + "domain": "النطاق", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "عدد صحيح", + "float": "عشري", + "boolean": "منطقي", + "uuid": "UUID", + "credit_card": "بطاقة (اختبار)", + "semver": "SemVer", + "row_index": "فهرس الصف", + "sequence": "تسلسل", + "date": "تاريخ", + "datetime": "تاريخ ووقت", + "unix_timestamp": "طابع يونكس", + "timezone": "المنطقة الزمنية", + "word": "كلمة", + "sentence": "جملة", + "paragraph": "فقرة", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "رمز العملة", + "language_code": "وسم اللغة", + "custom_list": "قائمة مخصصة" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "محسّن SVG", + "subtitle": "الصق SVG، صغّر باستخدام SVGO في المتصفح وقارن حجم UTF-8 قبل وبعد.", + "inputLabel": "الإدخال", + "outputLabel": "مُحسَّن", + "placeholder": "الصق جزء أو الملف كاملاً…", + "outputPlaceholder": "يظهر SVG المحسّن هنا…", + "chars": "{count} حرفًا • {bytes} بايت (UTF-8)", + "preview": "معاينة", + "localNote": "كل المعالجة محليًا في المتصفح. لا يتم رفع أي شيء.", + "stats": { + "title": "الحجم (UTF-8)", + "before": "قبل", + "after": "بعد", + "savedBytes": "تم توفير {count} بايت ({percent}%)", + "noChange": "لا يوجد تقليل — بالفعل أدنى حجم أو مخرجات فارغة." + }, + "actions": { + "clear": "مسح", + "sample": "تحميل مثال", + "copy": "نسخ", + "copied": "تم النسخ", + "download": "تنزيل" + }, + "errors": { + "optimizeFailed": "تعذّر تحسين هذا SVG." + } + }, + "UnitConverter": { + "title": "محوّل الوحدات", + "subtitle": "تحويل بين 323 وحدة عبر 43 فئة", + "category": "الفئة", + "from": "من", + "to": "إلى", + "value": "القيمة", + "result": "النتيجة", + "swapUnits": "تبديل الوحدات", + "placeholder": "أدخل قيمة", + "copyResult": "نسخ النتيجة", + "searchCategory": "ابحث عن فئة...", + "selectCategory": "اختر فئة", + "noResults": "لم يتم العثور على فئة." + }, + "DockerComposeGenerator": { + "title": "مولّد Docker Compose", + "subtitle": "اختر قواعد بيانات شائعة وتخزينًا مؤقتًا وطوابير رسائل ووكلاء عكسيين ومراقبة — انسخ docker-compose.yml جاهزًا للتطوير المحلي.", + "searchPlaceholder": "بحث (postgres، redis، kafka، nginx، grafana…)", + "selectedLabel": "الخدمات", + "selectedCount": "{count, plural, one {خدمة واحدة} other {# خدمات}}", + "outputTitle": "docker-compose.yml", + "copy": "نسخ", + "copied": "تم النسخ!", + "download": "تنزيل", + "emptyState": "اختر خدمة واحدة أو أكثر لإنشاء YAML لـ Compose.", + "hintPorts": "تم تباعد منافذ المضيف الافتراضية لتقليل التعارض؛ غيّرها إذا أبلغ Docker عن تعارض. استبدل كلمات مرور العرض قبل البيئات المشتركة.", + "categories": { + "databases": "قواعد البيانات والتخزين", + "cache": "التخزين المؤقت والذاكرة", + "queues": "الطوابير والمراسلة", + "search": "البحث والسجلات", + "storage": "تخزين الكائنات", + "proxy": "الوكلاء العكسيون والدخول", + "observability": "المقاييس والتتبع", + "mail": "البريد (تطوير محلي)", + "admin": "واجهات الإدارة وذكاء الأعمال" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "مسح" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (متوافق مع Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (متوافق مع Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (متوافق مع S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (التقاط SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (خادم تطوير)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "حرك البلاط ودمجها للوصول إلى عام 2048", + "score": "الدرجة", + "best": "أفضل", + "newGame": "لعبة جديدة", + "howToPlay": "استخدم مفاتيح الأسهم (أو اسحب على الهاتف المحمول) لتحريك المربعات. يتم دمج البلاطات التي تحتوي على نفس الرقم. الوصول إلى 2048 للفوز!", + "youWin": "لقد وصلت إلى 2048!", + "keepGoing": "واصل التقدم", + "gameOver": "انتهت اللعبة", + "tryAgain": "جرب مرة أخرى", + "gameOverMessage": "لا مزيد من التحركات المتبقية.", + "undo": "تراجع" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "المراحل المكتملة", + "time": "الوقت", + "best": "أفضل", + "difficulty": { + "easy": "سهلة", + "medium": "متوسط", + "hard": "صعب" + }, + "stage": "المرحلة", + "solved": "أجل!", + "score": "الدرجة", + "nextStage": "المرحلة التالية", + "replay": "إعادة التشغيل ", + "erase": "مسح", + "howToPlay": "انقر على خلية، ثم انقر على رقم أو اضغط على 1–9. مسافة للخلف أو حذف لمسح الخلية. الأرقام الغامقة هي أدلة. املأ الشبكة بحيث يحتوي كل صف وعمود ومربع 3×3 على 1–9 بدون تكرار.", + "pencil": "قلم رصاص", + "undo": "تراجع", + "hint": "تلميح", + "hintsUsed": "تلميحات مستخدمة", + "mistakes": "أخطاء", + "failed": "فشلت!", + "mistakeMode": "وضع 3 أخطاء" + }, + "Snake": { + "title": "سنيك", + "subtitle": "كل الطعام، انمو، تجنب الجدران ونفسك", + "score": "النتيجة", + "best": "الأفضل", + "startPrompt": "اضغط أي مفتاح سهم", + "startButton": "ابدأ اللعبة", + "gameOver": "انتهت اللعبة", + "playAgain": "العب مجدداً", + "howToPlay": "استخدم مفاتيح الأسهم أو WASD للتحرك. كل الطعام لتنمو. تزداد السرعة كل 50 نقطة.", + "diffEasy": "سهل", + "diffNormal": "عادي", + "diffHard": "صعب", + "diffInfinite": "لانهائي", + "orTap": "أو انقر أدناه", + "howToPlayInfinite": "الجدران تلتف — لا تموت بالجدران. كل الطعام لتنمو.", + "shieldActive": "الدرع نشط", + "speedBoost": "تعزيز السرعة", + "powerSpeed": "سرعة x2", + "powerShield": "تجنب موت واحد", + "powerShrink": "اختصار الذيل" + }, + "RedisCommander": { + "page": { + "title": "منفّذ Redis", + "dialogTitle": "اتصالات Redis", + "heroTitle": "منفّذ Redis", + "heroDesc": "اتصل بمثيلات Redis. تصفح المفاتيح، وعدّل القيم، ونفّذ الأوامر. بيانات الاعتماد مشفرة في متصفحك.", + "heroAddConnection": "إضافة اتصال", + "vaultLockedTitle": "الخزنة مقفلة", + "vaultLockedDesc": "افتح الخزنة للوصول إلى الاتصالات المحفوظة." + } + }, + "Minesweeper": { + "title": "كاسحة الألغام", + "subtitle": "اعثر على جميع الخلايا الآمنة دون تفجير لغم", + "mines": "ألغام", + "newGame": "لعبة جديدة", + "win": "لقد فزت!", + "loss": "انتهت اللعبة!", + "playAgain": "العب مرة أخرى", + "tryAgain": "حاول مرة أخرى", + "startPrompt": "انقر على أي خلية للبدء", + "difficulty": { + "beginner": "مبتدئ", + "intermediate": "متوسط", + "expert": "خبير" + } + }, + "Tetris": { + "title": "تتريس", + "subtitle": "رتّب الكتل المتساقطة لإكمال الصفوف", + "next": "التالي", + "score": "النقاط", + "best": "الأفضل", + "lines": "الخطوط", + "level": "المستوى", + "pause": "إيقاف مؤقت", + "resume": "استئناف", + "start": "ابدأ", + "restart": "إعادة", + "paused": "متوقف مؤقتًا", + "gameOver": "انتهت اللعبة" + }, + "SshKeyGenerator": { + "title": "مولد مفاتيح SSH / RSA", + "subtitle": "أنشئ أزواج مفاتيح Ed25519 أو RSA في متصفحك. نزّل المفاتيح الخاصة والعامة بصيغتي OpenSSH وPEM. لا يُرفع أي شيء.", + "keyTypeLabel": "نوع المفتاح", + "recommended": "موصى به", + "ed25519Desc": "سريع وصغير وحديث. مدعوم من GitHub وGitLab وجميع خوادم SSH الرئيسية.", + "rsa2048Desc": "توافق واسع. آمن لمعظم الاستخدامات.", + "rsa4096Desc": "أقصى توافق وقوة للأنظمة القديمة.", + "commentLabel": "تعليق (اختياري)", + "commentHint": "يُضاف إلى سطر المفتاح العام في OpenSSH.", + "generateBtn": "إنشاء زوج المفاتيح", + "generating": "جارٍ الإنشاء…", + "generatingLong": "جارٍ الإنشاء (قد يستغرق لحظة)…", + "copy": "نسخ", + "download": "تنزيل", + "privateKeyLabel": "المفتاح الخاص (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "المفتاح العام (صيغة OpenSSH)", + "publicKeySpkiLabel": "المفتاح العام (SPKI PEM)", + "browserNote": "جميع المفاتيح تُنشأ في متصفحك. لا يُرسل شيء إلى أي خادم.", + "errors": { + "generationFailed": "فشل إنشاء المفتاح" + } + }, + "DnsLookup": { + "title": "بحث DNS", + "subtitle": "استعلام عن سجلات DNS لأي نطاق — A، AAAA، MX، TXT، NS، CNAME.", + "placeholder": "أدخل النطاق (مثل github.com)", + "lookupBtn": "بحث", + "looking": "جارٍ البحث…", + "refreshBtn": "تحديث", + "recordTypesLabel": "أنواع السجلات", + "allBtn": "الكل", + "tryLabel": "جرّب:", + "errors": { + "lookupFailed": "فشل بحث DNS" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "مولّد ومدقّق Bcrypt", + "subtitle": "شفّر كلمات المرور باستخدام bcrypt وتحقّق من التجزئات — كل شيء يبقى في متصفّحك", + "tabs": { + "hash": "تجزئة", + "verify": "تحقّق" + }, + "showPassword": "إظهار كلمة المرور", + "hidePassword": "إخفاء كلمة المرور", + "copy": "نسخ", + "copied": "تم النسخ", + "hash": { + "passwordLabel": "كلمة المرور", + "passwordPlaceholder": "أدخل كلمة مرور لتجزئتها…", + "costLabel": "عامل التكلفة", + "costHint": "التكلفة الأعلى تضاعف العمل في كل خطوة. عادةً يُستخدم 10-12 في الإنتاج.", + "slowWarning": "عوامل التكلفة 13 فأعلى قد تستغرق عدة ثوانٍ للحساب.", + "generate": "توليد التجزئة", + "generating": "جارٍ التجزئة…", + "resultLabel": "تجزئة Bcrypt", + "timeTaken": "{ms} مللي ثانية", + "anatomy": { + "title": "بنية التجزئة", + "algorithm": "الخوارزمية", + "cost": "التكلفة", + "salt": "الملح (22 حرفًا)", + "hash": "التجزئة (31 حرفًا)" + } + }, + "verify": { + "passwordLabel": "كلمة المرور", + "passwordPlaceholder": "أدخل كلمة المرور للتحقّق…", + "hashLabel": "تجزئة Bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "أدخل كلمة مرور وتجزئة bcrypt لمقارنتهما.", + "invalidHash": "لا يبدو هذا تجزئة bcrypt صالحة.", + "checking": "جارٍ المقارنة…", + "match": "تطابق — كلمة المرور تنتج هذه التجزئة.", + "noMatch": "لا تطابق — كلمة المرور لا تنتج هذه التجزئة." + } + }, + "ChmodCalculator": { + "title": "حاسبة Chmod", + "subtitle": "حوّل أذونات ملفات يونكس بين مربّعات الاختيار والترميز الثماني والرمزي", + "grid": { + "title": "الأذونات", + "read": "قراءة", + "write": "كتابة", + "execute": "تنفيذ", + "owner": "المالك", + "group": "المجموعة", + "others": "الآخرون" + }, + "special": { + "title": "البتات الخاصة", + "setuid": "setuid", + "setgid": "setgid", + "sticky": "sticky" + }, + "octal": { + "label": "ثماني", + "placeholder": "755 أو 4755", + "invalid": "أدخل 3 أو 4 أرقام ثمانية (0-7)." + }, + "symbolic": { + "label": "رمزي", + "placeholder": "rwxr-xr-x", + "invalid": "أدخل 9 أحرف مثل rwxr-xr-x (يُسمح بـ s/S وt/T)." + }, + "output": { + "title": "الأمر", + "copy": "نسخ", + "copied": "تم النسخ" + }, + "explain": { + "line": "{cls} يمكنه {perms}.", + "none": "{cls} ليس لديه أي أذونات.", + "setuid": "setuid: يعمل الملف بصلاحيات مالكه.", + "setgid": "setgid: يعمل الملف بصلاحيات مجموعته (ترث الملفات الجديدة في مجلد مجموعته).", + "sticky": "sticky: يمكن فقط لمالك الملف حذفه أو إعادة تسميته داخل هذا المجلد." + }, + "presets": { + "title": "الإعدادات الشائعة", + "octal": "ثماني", + "permissions": "الأذونات", + "description": "الاستخدام النموذجي", + "p644": "الملفات العادية: المالك يكتب، والجميع يقرأ.", + "p755": "الملفات القابلة للتنفيذ والمجلدات: يمكن للجميع القراءة والدخول.", + "p600": "الملفات الخاصة: المالك فقط يقرأ ويكتب.", + "p700": "الملفات القابلة للتنفيذ والمجلدات الخاصة: المالك فقط.", + "p664": "ملفات قابلة للكتابة من المجموعة.", + "p775": "مجلدات وملفات قابلة للتنفيذ وقابلة للكتابة من المجموعة.", + "p400": "قراءة فقط للمالك (مثل مفاتيح SSH الخاصة)." + } + }, + "CodeScreenshot": { + "title": "لقطة شاشة للكود", + "subtitle": "حوّل الكود إلى صور جميلة قابلة للمشاركة", + "languageLabel": "اللغة", + "autoDetect": "كشف تلقائي", + "backgroundLabel": "الخلفية", + "paddingLabel": "الحشوة", + "filenameLabel": "اسم الملف", + "filenamePlaceholder": "example.ts", + "windowLabel": "نقاط النافذة", + "themeLabel": "سمة النافذة", + "themeDark": "داكن", + "themeLight": "فاتح", + "scaleLabel": "مقياس التصدير", + "copyImage": "نسخ الصورة", + "copied": "تم النسخ", + "exportPng": "تصدير PNG", + "inputLabel": "الكود", + "detected": "المكتشَف: {language}", + "clear": "مسح", + "placeholder": "الصق الكود هنا…", + "previewLabel": "معاينة", + "emptyPreview": "ستظهر معاينة الكود هنا", + "exportFailed": "فشل تصدير الصورة", + "copyUnsupported": "نسخ الصور غير مدعوم في هذا المتصفّح", + "copyFailed": "فشل نسخ الصورة", + "backgrounds": { + "ocean": "محيط", + "sunset": "غروب", + "candy": "حلوى", + "forest": "غابة", + "midnight": "منتصف الليل", + "gold": "ذهبي", + "aurora": "شفق", + "slate": "رمادي", + "transparent": "شفاف" + } + }, + "CssGenerators": { + "title": "مولّدات CSS", + "subtitle": "box-shadow وborder-radius وclamp() المرن — انسخ الـ CSS.", + "preview": "معاينة", + "output": "CSS", + "copy": "نسخ", + "copied": "تم النسخ", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() المرن" + }, + "boxShadow": { + "presets": "الإعدادات المسبقة", + "layer": "طبقة", + "addLayer": "إضافة طبقة", + "removeLayer": "إزالة الطبقة", + "x": "X", + "y": "Y", + "blur": "تمويه", + "spread": "امتداد", + "color": "اللون", + "alpha": "ألفا", + "inset": "داخلي", + "presetNames": { + "subtleCard": "بطاقة خفيفة", + "elevated": "مرتفع", + "hardOffset": "إزاحة حادة", + "neonGlow": "توهّج نيون", + "inner": "داخلي", + "layeredSmooth": "متعدّد الطبقات ناعم" + } + }, + "borderRadius": { + "modeLabel": "الوضع", + "modes": { + "simple": "لكل زاوية", + "blob": "شكل عضوي" + }, + "unit": "الوحدة", + "linkCorners": "ربط الزوايا", + "randomize": "عشوائي", + "horizontal": "أنصاف الأقطار الأفقية", + "vertical": "أنصاف الأقطار الرأسية", + "corners": { + "tl": "أعلى اليسار", + "tr": "أعلى اليمين", + "br": "أسفل اليمين", + "bl": "أسفل اليسار" + } + }, + "clamp": { + "minFont": "أدنى خط (px)", + "maxFont": "أقصى خط (px)", + "minViewport": "أدنى منفذ عرض (px)", + "maxViewport": "أقصى منفذ عرض (px)", + "rootFont": "خط الجذر (px)", + "viewportWidth": "منفذ عرض محاكى", + "computedSize": "الحجم المحسوب", + "slope": "الميل", + "intercept": "التقاطع", + "previewText": "نصّ معاينة للخط", + "invalid": "أدخل حدود منفذ عرض صالحة ومختلفة." + } + }, + "ExifViewer": { + "title": "عارض ومزيل EXIF", + "subtitle": "افحص بيانات الصور الوصفية واحذفها في متصفّحك", + "dropTitle": "أفلِت صورة هنا", + "dropHint": "أو انقر للتصفّح — صورتك لا تغادر جهازك أبدًا", + "browse": "اختر صورة", + "formats": "JPEG وPNG وWebP وHEIC وTIFF", + "parsing": "جارٍ قراءة البيانات الوصفية…", + "parseFailed": "فشل قراءة هذه الصورة", + "clear": "إزالة الصورة", + "gpsWarning": "يحتوي على موقع GPS", + "openMap": "عرض على OpenStreetMap", + "noMetadata": "لم يُعثر على بيانات وصفية مضمّنة في هذه الصورة.", + "facts": { + "name": "الاسم", + "type": "النوع", + "dimensions": "الأبعاد", + "size": "الحجم" + }, + "groups": { + "camera": "الكاميرا", + "exposure": "التعريض", + "timestamps": "الطوابع الزمنية", + "gps": "موقع GPS", + "software": "البرامج وأخرى" + }, + "gpsCard": { + "latitude": "خط العرض", + "longitude": "خط الطول", + "altitude": "الارتفاع" + }, + "raw": { + "title": "كل البيانات الوصفية", + "copyJson": "نسخ JSON", + "copied": "تم النسخ" + }, + "remove": { + "title": "إزالة البيانات الوصفية", + "jpegDescription": "أزِل EXIF وXMP وIPTC والتعليقات بلا فقدان دون إعادة ضغط الصورة.", + "reencodeDescription": "أعِد ترميز الصورة لإزالة البيانات الوصفية المضمّنة. هذا يعيد ضغط البكسلات.", + "keepIcc": "الاحتفاظ بملف الألوان (ICC)", + "stripButton": "إزالة البيانات الوصفية", + "reencodeButton": "إعادة الترميز والإزالة", + "unsupported": "إزالة البيانات الوصفية غير مدعومة لهذا النوع من الملفات.", + "failed": "فشلت إزالة البيانات الوصفية", + "before": "قبل", + "after": "بعد", + "download": "تنزيل الصورة المنظّفة" + } + }, + "FaviconGenerator": { + "title": "مولّد Favicon", + "subtitle": "أنشئ أيقونات المفضلة وأيقونات التطبيق من صورة واحدة", + "dropTitle": "أفلِت صورة هنا", + "dropHint": "PNG أو JPEG أو SVG — انقر للتصفّح", + "browse": "اختر صورة", + "clear": "إزالة الصورة", + "backgroundLabel": "الخلفية", + "transparent": "شفاف", + "solidColor": "لون خالص", + "cornerLabel": "شكل الزاوية", + "cornerNone": "مربّع", + "cornerRounded": "مستدير", + "cornerCircle": "دائرة", + "maskPaddingLabel": "حشوة قابلة للإخفاء", + "maskable": "قابلة للإخفاء", + "previewLabel": "معاينة", + "copy": "نسخ", + "headSnippet": "مقتطف HTML head", + "downloadIco": "تنزيل favicon.ico", + "downloadZip": "تنزيل الكل (.zip)" + }, + "JsonDiff": { + "title": "مقارنة JSON", + "subtitle": "مقارنة دلالية واعية بالبنية بين مستندَي JSON — يُتجاهل ترتيب المفاتيح والتنسيق", + "options": { + "ignoreArrayOrder": "تجاهل ترتيب المصفوفة" + }, + "loadBothSamples": "تحميل الأمثلة", + "sample": "مثال", + "format": "تنسيق", + "clear": "مسح", + "formatError": "تعذّر التنسيق: الإدخال ليس JSON صالحًا", + "placeholder": "الصق أو اكتب JSON هنا…", + "panels": { + "left": "الأصلي (يسار)", + "right": "المعدّل (يمين)", + "results": "الاختلافات" + }, + "summary": { + "added": "{count} مُضاف", + "removed": "{count} محذوف", + "changed": "{count} متغيّر" + }, + "types": { + "added": "مُضاف", + "removed": "محذوف", + "changed": "متغيّر" + }, + "copyChanges": "نسخ كـ JSON", + "copied": "تم النسخ", + "empty": "الصق JSON في كلا اللوحين لرؤية اختلافاتهما.", + "identical": "المستندان متطابقان دلاليًا." + }, + "JsonpathPlayground": { + "title": "ساحة JSONPath", + "subtitle": "استعلم عن مستندات JSON باستخدام JSONPath أو JMESPath وشاهد النتائج مباشرة", + "engineLabel": "المحرّك", + "queryLabel": "الاستعلام", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "ورقة مرجعية", + "cheats": { + "jpAllTitles": "كل عناوين الكتب", + "jpAllAuthors": "كل مؤلّف في أي مكان بالمستند", + "jpFirstBook": "أول كتاب", + "jpLastBook": "آخر كتاب", + "jpSlice": "أول كتابين (شريحة)", + "jpFilterPrice": "الكتب الأرخص من 40", + "jpFilterInStock": "عناوين الكتب المتوفّرة", + "jpRecursive": "كل القيم، تعاوديًا", + "jmAllTitles": "كل عناوين الكتب", + "jmFirstAuthor": "مؤلّف أول كتاب", + "jmLastBook": "آخر كتاب", + "jmSlice": "أول كتابين (شريحة)", + "jmFilterPrice": "عناوين الكتب الأرخص من 40", + "jmCount": "عدّ الكتب المتوفّرة", + "jmProjection": "أعِد تشكيل كل كتاب إلى كائن جديد", + "jmSort": "العناوين مرتّبة حسب السعر" + }, + "panels": { + "input": "إدخال JSON", + "results": "النتائج" + }, + "sample": "مثال", + "format": "تنسيق", + "clear": "مسح", + "formatError": "تعذّر التنسيق: الإدخال ليس JSON صالحًا", + "placeholder": "الصق أو اكتب JSON هنا…", + "outputPlaceholder": "ستظهر التطابقات هنا…", + "matchCount": "{count, plural, zero {# تطابق} one {# تطابق} two {# تطابقان} few {# تطابقات} many {# تطابقًا} other {# تطابق}}", + "copy": "نسخ", + "copied": "تم النسخ" + }, + "KeycodeInspector": { + "title": "فاحص أكواد المفاتيح", + "subtitle": "اضغط أي مفتاح لفحص حدث لوحة مفاتيح JavaScript الخاص به", + "capture": { + "prompt": "اضغط أي مفتاح…", + "ready": "يستمع — اضغط أي مفتاح على لوحة مفاتيحك", + "clickToFocus": "انقر هنا للتركيز، ثم اضغط أي مفتاح" + }, + "hero": { + "spaceKey": "(مسافة)", + "keyLabel": "event.key", + "repeat": "تكرار" + }, + "copyJson": "نسخ JSON", + "copiedJson": "تم نسخ JSON الحدث إلى الحافظة", + "deprecated": "مهمَل", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "قياسي", + "left": "يسار", + "right": "يمين", + "numpad": "لوحة أرقام", + "unknown": "غير معروف" + }, + "history": { + "title": "السجل", + "clear": "مسح السجل", + "empty": "ستظهر ضغطات المفاتيح هنا." + } + }, + "MarkdownTableGenerator": { + "title": "مولّد جداول Markdown", + "subtitle": "حرّر شبكة، والصق من جدول بيانات، وانسخ جدول Markdown أو HTML بمحاذاة أنيقة", + "addRow": "إضافة صف", + "addColumn": "إضافة عمود", + "importLabel": "استيراد", + "importPlaceholder": "الصق TSV (من Excel / Google Sheets) أو CSV هنا…", + "importButton": "استيراد إلى الشبكة", + "pasteClipboard": "لصق من الحافظة", + "importEmpty": "لا شيء للاستيراد — الصق بعض الصفوف أولًا", + "clipboardError": "تعذّرت القراءة من الحافظة", + "clear": "مسح", + "panels": { + "editor": "محرّر الجدول", + "output": "الناتج" + }, + "align": { + "left": "محاذاة لليسار", + "center": "محاذاة للوسط", + "right": "محاذاة لليمين" + }, + "headerPlaceholder": "رأس", + "cellPlaceholder": "خلية", + "removeRow": "إزالة الصف", + "removeColumn": "إزالة العمود", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "نسخ", + "copied": "تم النسخ" + }, + "TimezoneConverter": { + "title": "محوّل المناطق الزمنية", + "subtitle": "حوّل لحظة واحدة عبر العالم لتخطيط الاجتماعات.", + "dateTimeLabel": "التاريخ والوقت", + "now": "الآن", + "sourceZoneLabel": "المنطقة الزمنية المصدر", + "selectZone": "اختر منطقة", + "searchZone": "ابحث في المناطق الزمنية...", + "noZone": "لم يُعثر على منطقة زمنية.", + "scrubLabel": "إزاحة الوقت", + "reset": "إعادة تعيين", + "targetsLabel": "المناطق الهدف", + "addZone": "إضافة منطقة", + "remove": "إزالة", + "localBadge": "محلي", + "invalidDate": "أدخل تاريخًا ووقتًا صالحين.", + "bands": { + "work": "ساعات العمل (09:00–18:00)", + "edge": "الساعات الهامشية (الصباح الباكر / المساء)", + "night": "الليل" + } + }, + "TokenCounter": { + "title": "عدّاد رموز LLM", + "subtitle": "احسب الرموز، وتصوّر الأجزاء، وقدّر تكلفة الـ API.", + "inputLabel": "النص", + "sample": "مثال", + "clear": "مسح", + "placeholder": "الصق نصًا أو كودًا أو JSON لعدّ الرموز...", + "encoding": "الترميز", + "loading": "جارٍ تحميل المُرمِّز...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-series)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "الرموز", + "characters": "الأحرف", + "words": "الكلمات", + "ratio": "رموز/كلمة", + "ratioSub": "تقريبي" + }, + "claudeRow": "Claude (تقريبي)", + "estimate": "تقدير", + "viz": { + "title": "تصوّر الرموز", + "toggle": "إظهار", + "hint": "بدّل لتلوين كل رمز كجزء.", + "empty": "أدخل نصًا لتصوّر الرموز.", + "truncated": "عرض أول {limit} من {total} رمزًا." + }, + "cost": { + "title": "حاسبة التكلفة", + "disclaimer": "قيم افتراضية قابلة للتعديل — تحقّق من الأسعار الحالية", + "model": "النموذج", + "expectedOutput": "رموز الإخراج المتوقّعة", + "customInput": "إدخال $/MTok", + "customOutput": "إخراج $/MTok", + "inputCost": "تكلفة الإدخال", + "outputCost": "تكلفة الإخراج", + "total": "الإجمالي", + "per1000": "لكل 1000 طلب" + } + }, + "WebhookTester": { + "title": "مختبِر Webhook", + "subtitle": "أنشئ عنوان URL مؤقّتًا وافحص طلبات webhook الواردة مباشرة.", + "intro": { + "title": "أنشئ عنوان webhook لبدء التقاط الطلبات", + "hint": "ستحصل على عنوان URL يصعب تخمينه يسجّل كل طلب وارد. تنتهي صلاحيته بعد 24 ساعة." + }, + "createBtn": "إنشاء عنوان webhook", + "creating": "جارٍ الإنشاء…", + "urlLabel": "عنوان webhook الخاص بك", + "expiresIn": "تنتهي الصلاحية خلال {countdown}", + "copyUrlBtn": "نسخ العنوان", + "copied": "تم النسخ", + "clearBtn": "مسح", + "newBinBtn": "عنوان جديد", + "requestsTitle": "الطلبات", + "selectPrompt": "اختر طلبًا لفحص تفاصيله.", + "expiredNotice": "انتهت صلاحية عنوان webhook هذا. أنشئ عنوانًا جديدًا لمواصلة الاختبار.", + "base64Badge": "base64", + "truncatedBadge": "مقتطع", + "empty": { + "title": "بانتظار الطلبات…", + "hint": "أرسل طلبًا إلى عنوانك وسيظهر هنا فورًا. جرّب هذا:" + }, + "detail": { + "receivedAt": "استُلم", + "source": "IP المصدر", + "contentType": "Content-Type", + "query": "معاملات الاستعلام", + "headers": "الترويسات", + "body": "المحتوى", + "copyBody": "نسخ المحتوى", + "emptyBody": "لا يوجد محتوى" + }, + "errors": { + "createFailed": "فشل إنشاء عنوان webhook. يُرجى المحاولة مجددًا.", + "clearFailed": "فشل مسح الطلبات. يُرجى المحاولة مجددًا." + } + }, + "WebsocketTester": { + "title": "مختبِر WebSocket", + "subtitle": "اتصل بنقطة نهاية WebSocket وأرسِل واستقبِل وافحص الرسائل مباشرة", + "urlLabel": "عنوان نقطة النهاية", + "protocolsLabel": "البروتوكولات الفرعية", + "protocolsPlaceholder": "اختياري، مفصولة بفواصل", + "connect": "اتصال", + "disconnect": "قطع الاتصال", + "status": { + "disconnected": "غير متصل", + "connecting": "جارٍ الاتصال", + "connected": "متصل", + "error": "خطأ" + }, + "messages": { + "connecting": "جارٍ الاتصال بـ {url}…", + "connected": "تم تأسيس الاتصال", + "connectedWithProtocol": "تم تأسيس الاتصال (البروتوكول: {protocol})", + "invalidUrl": "عنوان URL غير صالح — يجب أن يبدأ بـ ws:// أو wss://", + "connectFailed": "فشل فتح الاتصال", + "socketError": "حدث خطأ في الاتصال", + "notConnected": "غير متصل — اتصل أولًا", + "binaryMessage": "تم استلام رسالة ثنائية" + }, + "log": { + "title": "سجل الرسائل", + "empty": "لا رسائل بعد. اتصل وابدأ الإرسال.", + "copyAll": "نسخ الكل", + "download": "تنزيل كـ .txt", + "clear": "مسح السجل", + "prettyPrint": "تنسيق JSON", + "showRaw": "إظهار النص الخام" + }, + "composer": { + "placeholder": "اكتب رسالة للإرسال…", + "send": "إرسال", + "formatJson": "تنسيق كـ JSON", + "invalidJson": "الرسالة ليست JSON صالحًا", + "hint": "Cmd/Ctrl+Enter للإرسال" + }, + "ping": { + "label": "ping تلقائي", + "seconds": "ثانية", + "intervalTitle": "فترة ping بالثواني", + "payloadPlaceholder": "حمولة ping" + } + }, + "WhoisLookup": { + "title": "بحث Whois", + "subtitle": "ابحث عن تفاصيل التسجيل لأي نطاق أو عنوان IP عبر RDAP", + "placeholder": "example.com أو 8.8.8.8", + "lookupBtn": "بحث", + "looking": "جارٍ البحث…", + "refreshBtn": "تحديث", + "tryLabel": "جرّب:", + "copy": "نسخ", + "copied": "تم النسخ", + "notAvailable": "غير متوفّر", + "summary": { + "registrar": "المسجِّل", + "network": "الشبكة" + }, + "dates": { + "created": "أُنشئ", + "updated": "حُدّث", + "expires": "ينتهي" + }, + "sections": { + "status": "الحالة", + "nameservers": "خوادم الأسماء", + "contacts": "جهات الاتصال", + "rawRdap": "RDAP الخام" + }, + "contacts": { + "roles": "الأدوار", + "name": "الاسم", + "organization": "المؤسسة" + }, + "errors": { + "invalidTarget": "أدخل اسم نطاق أو عنوان IP صالحًا", + "lookupFailed": "فشل البحث (الحالة {status})" + } + } +} diff --git a/apps/desktop-ui/messages/ca.json b/apps/desktop-ui/messages/ca.json new file mode 100644 index 00000000..a92b6a0b --- /dev/null +++ b/apps/desktop-ui/messages/ca.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Tauler", + "settings": "Configuració", + "apiClient": "Client API", + "httpStatusCodes": "Codis d’estat HTTP", + "passwordManager": "Gestor de contrasenyes", + "environmentManager": "Gestor d'entorns", + "bookmarks": "Marcadors", + "jsonFormatter": "Formatador JSON", + "emailValidator": "Validador de correu", + "nosqlExplorer": "Explorador de base de dades", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Terreny de joc del xifratge", + "jwtDecoder": "Descodificador JWT", + "certificatePemDecoder": "Descodificador de certificats / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Formatador GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Analitzador d’URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Secret / clau API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Verificador de contrast", + "jsonSchemaGenerator": "Generador d'esquema JSON", + "imageToBase64": "Imatge a Base64", + "imageCompressor": "Compressor d’imatges", + "cssGradientBuilder": "Creador de degradats CSS", + "gitignoreGenerator": "Generador .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Fragments de codi", + "qrCodeGenerator": "Generador de codis QR", + "dnsLookup": "Cerca DNS", + "ipSubnetCalculator": "Calculadora IP / subxarxa", + "hashGenerator": "Generador de hash", + "hmacGenerator": "Generador HMAC", + "totpGenerator": "Codi TOTP / 2FA", + "markdownPreview": "Previsualització Markdown", + "numberBaseConverter": "Conversor de base", + "formatConverter": "Conversor de formats", + "mockDataGenerator": "Generador de dades de prova", + "svgOptimizer": "Optimitzador SVG", + "unitConverter": "Convertidor d'unitats", + "dockerComposeGenerator": "Generador Docker Compose", + "breakRoom": "Sala de descans", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Formatador YAML", + "sshKeyGenerator": "Generador de claus SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Calculadora Chmod", + "codeScreenshot": "Captura de codi", + "cssGenerators": "Generadors CSS", + "exifViewer": "Visualitzador EXIF", + "faviconGenerator": "Generador de favicon", + "jsonDiff": "Diff JSON", + "jsonpathPlayground": "Playground JSONPath", + "keycodeInspector": "Inspector de keycode", + "markdownTableGenerator": "Taula Markdown", + "timezoneConverter": "Convertidor de zona horària", + "tokenCounter": "Comptador de tokens", + "webhookTester": "Provador de webhooks", + "websocketTester": "Provador de WebSocket", + "whoisLookup": "Consulta Whois" + }, + "Help": { + "title": "Ajuda i documentació", + "subtitle": "Com encaixa MyDevTools, què fa cada aplicació i com la seguretat i el xifrat protegeixen les teves dades.", + "tabs": { + "overview": "Visió general", + "apps": "Aplicacions", + "security": "Seguretat", + "tips": "Consells" + }, + "overview": { + "whatIs": { + "title": "Què és MyDevTools?", + "description": "Un espai de treball per a desenvolupadors amb inici de sessió: un tauler i un conjunt d’eines per al dia a dia.", + "p1": "Després d’iniciar sessió, l’app estableix una sessió amb l’API de MyDevTools perquè les eines puguin carregar i desar les teves dades. La barra lateral llista totes les apps; pots amagar les eines que no fas servir a", + "settingsLink": "Configuració", + "p1After": ".", + "p2": "Algunes funcions utilitzen una contrasenya mestra (o clau global del vault) perquè secrets com contrasenyes i cadenes de connexió es xifrin al teu dispositiu abans d’arribar al servidor." + }, + "quickLinks": { + "title": "Enllaços ràpids", + "dashboard": "Tauler", + "settings": "Configuració" + } + }, + "apps": { + "intro": "Obre qualsevol eina des de la barra lateral o el tauler. A continuació tens una guia breu per aplicació.", + "open": "Obrir", + "howItWorks": "Com funciona", + "generic": "Fes servir aquesta eina des de la barra lateral per al seu flux de treball dedicat." + }, + "security": { + "session": { + "title": "Inici de sessió i sessió d’API", + "description": "Identitat i com el navegador es comunica amb el backend.", + "items": { + "authentication": "Autenticació mitjançant Firebase. Després del login, l’app intercanvia el teu Firebase ID token per cookies de l’API.", + "accessTokens": "Tokens d’accés són JWT signats al servidor (HS256) que proven quin usuari crida l’API.", + "refreshTokens": "Tokens de refresc es guarden en cookies HttpOnly; el servidor desa un hash SHA-256 del token, no el token en brut." + } + }, + "encryption": { + "title": "Xifrat que has de conèixer", + "description": "Criptografia al client per al vault i dades sensibles de connexió.", + "p1": "Per al gestor de contrasenyes, el gestor d'entorns i les connexions desades del explorador NoSQL, l'aplicació web utilitza la Web Crypto API del navegador:", + "items": { + "kdf": "PBKDF2 amb SHA-256, 100.000 iteracions i una sal aleatòria deriva una clau de 256 bits a partir de la contrasenya mestra.", + "aes": "AES-256-GCM xifra dades; s’utilitza un IV de 12 bytes aleatori per cada xifrat.", + "scope": "La clau derivada es manté al navegador (no exportable). El servidor només rep xifrat i IV, no la contrasenya mestra ni secrets en clar." + }, + "warning": "Si oblides la contrasenya mestra, no es poden recuperar les dades xifrades. Desa-la de manera segura." + }, + "logout": { + "title": "Tancar sessió i dades locals", + "body": "En tancar sessió s’esborren secrets en memòria, estat del vault i, si escau, material de la clau mestra d’IndexedDB, i es finalitza la sessió de l’API. Les dades que només estiguin a l’emmagatzematge local poden romandre fins que les esborris al navegador." + } + }, + "tips": { + "title": "Fer servir MyDevTools de manera segura", + "items": { + "masterPassword": "Fes servir una contrasenya mestra forta i única per a les funcions del vault.", + "apiClient": "Tracta el Client API com qualsevol client HTTP: evita enganxar secrets de producció en entorns compartits si no saps qui els pot veure.", + "nosql": "L’Explorador de base de dades es connecta a bases de dades que configures; fes servir usuaris amb mínims privilegis i regles de xarxa quan sigui possible.", + "json": "El formatador JSON funciona localment al navegador—tot i així evita enganxar dades molt sensibles en equips compartits.", + "settings": "Personalitza les eines visibles a Configuració per mantenir la barra lateral enfocada." + } + } + }, + "Settings": { + "title": "Configuració", + "language": "Idioma", + "spanish": "Castellà", + "english": "Anglès", + "french": "Francès", + "catalan": "Català", + "arabic": "Àrab", + "chineseSimplified": "Xinès simplificat", + "czech": "Txec", + "greek": "Ελληνικά", + "german": "Alemany", + "danish": "Danès", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ucraïnès" + }, + "SettingsPage": { + "title": "Configuració", + "subtitle": "Gestiona la configuració i les preferències del teu compte.", + "userProfile": { + "title": "Perfil d'usuari", + "description": "La teva informació personal i els detalls del compte.", + "avatarAlt": "Usuari", + "anonymousUser": "Usuari anònim", + "noEmailProvided": "No s'ha proporcionat cap correu", + "notLoggedIn": "No has iniciat sessió." + }, + "tools": { + "title": "Eines i funcions", + "description": "Tria quines eines apareixen a la barra lateral de navegació." + }, + "appearance": { + "title": "Aparença", + "description": "Personalitza com es veu l'aplicació al teu dispositiu.", + "themePreference": "Preferència de tema", + "themes": { + "light": "Clar", + "dark": "Fosc", + "system": "Sistema" + } + }, + "language": { + "title": "Idioma", + "description": "Defineix l'idioma emprat a tota la plataforma.", + "selectLanguage": "Selecciona l'idioma", + "selectPlaceholder": "Selecciona un idioma", + "helpText": "Canviar l'idioma actualitzarà els menús i les etiquetes a tota la plataforma.", + "languages": { + "en": "Anglès (English)", + "fr": "Français (Francès)", + "es": "Castellà (Español)", + "ca": "Català", + "ar": "Àrab (عربى)", + "zh": "简体中文", + "cs": "Čeština (txec)", + "el": "Ελληνικά (Grec)", + "de": "Deutsch (Alemany)", + "da": "Dansk (Danès)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italià (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Bon dia", + "afternoon": "Bona tarda", + "evening": "Bona nit" + }, + "commaName": ", {name}", + "welcomeBack": "Benvingut de nou", + "welcomeBackNamed": "Benvingut de nou, {name}", + "tagline": "Què t’agradaria crear avui?", + "brandName": "MyDevTools", + "stats": { + "tools": "Eines", + "favorites": "Preferits", + "pinned": "Fixat", + "recent": "Usades recentment" + }, + "sections": { + "recentlyUsed": "Usades recentment", + "favorites": "Preferits", + "whatsNew": "Novetats", + "pinned": "Fixat" + }, + "groups": { + "apps": "Aplicacions" + }, + "toolCard": { + "defaultDescription": "Explora aquesta eina per treure’n més profit.", + "launchTool": "Obrir eina" + }, + "tools": { + "toDo": { + "title": "Tasques", + "description": "Gestiona les teves llistes de tasques amb eficàcia." + }, + "notes": { + "title": "Notes", + "description": "Captura idees i organitza els teus pensaments." + }, + "passwordManager": { + "title": "Gestor de contrasenyes", + "description": "Emmagatzema i gestiona les contrasenyes amb seguretat." + }, + "environmentManager": { + "title": "Gestor d'entorns", + "description": "Desa variables tipus .env per projecte i entorn amb etiquetes." + }, + "bookmarks": { + "title": "Marcadors", + "description": "Gestiona i organitza els teus marcadors." + }, + "apiClient": { + "title": "Client API", + "description": "Prova i depura sol·licituds HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Explora bases de dades MongoDB." + }, + "emailValidator": { + "title": "Validador de correu", + "description": "Verifica i valida adreces de correu." + }, + "encryptionPlayground": { + "title": "Terreny de joc del xifratge", + "description": "AES-GCM amb clau en brut o contrasenya; paquets JSON, Web Crypto només al navegador." + }, + "jwtDecoder": { + "title": "Descodificador JWT", + "description": "Descodifica la capçalera, el payload i la caducitat del JWT." + }, + "certificatePemDecoder": { + "title": "Descodificador de certificats / PEM", + "description": "Descodifica certificats X.509 i CSR: subjecte, emissor, validesa i SAN." + }, + "sshKeyGenerator": { + "title": "Generador de claus SSH / RSA", + "description": "Genereu parells de claus Ed25519 o RSA al navegador. Descarregueu en formats OpenSSH i PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Formatador GraphQL", + "description": "Formata GraphQL amb ressaltat de sintaxi i un editor de consultes senzill." + }, + "yamlFormatter": { + "title": "Formatador YAML", + "description": "Formata, valida i converteix YAML a JSON al navegador." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Analitzador d’URL", + "description": "Desglossa un URL en protocol, amfitrió, camí, paràmetres de consulta i hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Secret / clau API", + "description": "Cad aleatòries criptogràfiques amb alfabet, longitud i exportació massiva configurables." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Verificador de contrast", + "description": "Ràtio de contrast WCAG 2.1 entre text i fons (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Generador d'esquema JSON", + "description": "Extreu un esquema JSON i models tipats a partir d'una mostra JSON." + }, + "imageToBase64": { + "title": "Imatge a Base64", + "description": "Converteix imatges a URI de dades o cadenes en brut Base64." + }, + "imageCompressor": { + "title": "Compressor d’imatges", + "description": "Comprimeix JPEG, PNG i WebP localment amb un control de qualitat." + }, + "cssGradientBuilder": { + "title": "Creador de degradats CSS", + "description": "Creador de degradat CSS visual amb control d'angle i parades de color." + }, + "gitignoreGenerator": { + "title": "Generador .gitignore", + "description": "Genereu fitxers .gitignore combinats a l'instant a partir de més de 500 piles tecnològiques." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Converteix fitxers CSV o Excel a JSON i exporta de nou a CSV o XLSX." + }, + "snippetManager": { + "title": "Fragments de codi", + "description": "Desa fragments amb ressaltat de sintaxi, detecció automàtica d’idioma i format." + }, + "qrCodeGenerator": { + "title": "Generador de codis QR", + "description": "Crea codis QR en PNG a partir de text o URL al navegador." + }, + "ipSubnetCalculator": { + "title": "Calculadora IP / subxarxa", + "description": "IPv4 i IPv6 CIDR: màscara, intervals i recompte d’adreces." + }, + "dnsLookup": { + "title": "Cerca DNS", + "description": "Consulta els registres DNS de qualsevol domini — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Generador de hash", + "description": "MD5, SHA-1 i SHA-2 per a text o fitxers." + }, + "hmacGenerator": { + "title": "Generador HMAC", + "description": "Signa webhooks i prova integracions API amb HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Generador de codi TOTP / 2FA", + "description": "Enganxa un secret Base32 de l’autenticador; el codi de sis xifres es renova cada 30 s—útil per provar inici de sessió i MFA." + }, + "markdownPreview": { + "title": "Previsualització Markdown", + "description": "Renderitzador Markdown en viu amb exportació HTML i conversió HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Conversor de base numèrica", + "description": "Converteix enters entre bases del 2 al 36." + }, + "formatConverter": { + "title": "Conversor de formats", + "description": "Converteix entre JSON, YAML, TOML i XML en qualsevol direcció. Les 12 combinacions estan admeses." + }, + "mockDataGenerator": { + "title": "Generador de dades de prova", + "description": "Definiu columnes, trieu tipus i exporteu conjunts de dades per a llavors, Postman o proves. Fins a 5000 files, tot al navegador: no es puja res." + }, + "httpStatusCodes": { + "title": "Codis d'estat HTTP", + "description": "Cerca codis d'estat HTTP amb descripcions (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Optimitzador SVG", + "description": "Enganxa SVG, elimina soroll amb SVGO i veu la reducció de mida UTF-8." + }, + "unitConverter": { + "title": "Convertidor d'unitats", + "description": "Converteix entre 323 unitats en 43 categories científiques i d'enginyeria." + }, + "dockerComposeGenerator": { + "title": "Generador Docker Compose", + "description": "Trieu bases de dades, memòries cau, cues, proxies i imatges d’observabilitat; exporteu un docker-compose.yml per a piles locals." + }, + "game2048": { + "title": "2048", + "description": "Llisca i fusiona les rajoles per arribar al 2048. Joc clàssic de trencaclosques de nombres." + }, + "sudoku": { + "title": "Sudoku", + "description": "Trencaclosques de números clàssics. 3 nivells de dificultat, 150 etapes cadascun." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Navega per claus, inspecciona valors i executa ordres en qualsevol instància Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Generador i verificador Bcrypt", + "description": "Genera hash de contrasenyes amb bcrypt i verifica hashes existents." + }, + "chmodCalculator": { + "title": "Calculadora Chmod", + "description": "Converteix permisos Unix entre octal, simbòlic i caselles de selecció." + }, + "codeScreenshot": { + "title": "Captura de codi", + "description": "Converteix codi font en imatges boniques i compartibles." + }, + "cssGenerators": { + "title": "Generadors CSS", + "description": "Generadors de box-shadow, border-radius i clamp() fluid." + }, + "exifViewer": { + "title": "Visualitzador i eliminador EXIF", + "description": "Inspecciona i elimina les metadades EXIF de les fotos localment." + }, + "faviconGenerator": { + "title": "Generador de favicon", + "description": "Genera favicons, icones d'app i fragments de manifest." + }, + "jsonDiff": { + "title": "Diff JSON", + "description": "Diff semàntic i conscient de l'estructura de dos documents JSON." + }, + "jsonpathPlayground": { + "title": "Playground JSONPath", + "description": "Consulta JSON amb JSONPath o JMESPath i mira els resultats en directe." + }, + "keycodeInspector": { + "title": "Inspector de keycode", + "description": "Prem una tecla per veure els seus codis d'esdeveniment JavaScript i modificadors." + }, + "markdownTableGenerator": { + "title": "Generador de taules Markdown", + "description": "Crea taules Markdown i HTML en un editor de graella tipus full de càlcul." + }, + "timezoneConverter": { + "title": "Convertidor de zona horària", + "description": "Converteix una hora entre moltes zones amb indicacions de canvi de dia i horari laboral." + }, + "tokenCounter": { + "title": "Comptador de tokens LLM", + "description": "Compta tokens LLM, visualitza chunks i estima el cost de l'API." + }, + "webhookTester": { + "title": "Provador de webhooks", + "description": "Captura i inspecciona les peticions de webhook entrants en temps real." + }, + "websocketTester": { + "title": "Provador de WebSocket", + "description": "Connecta't a endpoints WebSocket i intercanvia missatges en temps real." + }, + "whoisLookup": { + "title": "Consulta Whois", + "description": "Consulta les dades de registre de dominis i IP mitjançant RDAP." + } + }, + "tabs": { + "apps": "Aplicacions", + "analytics": "Anàlisi", + "ui": "Interfície" + }, + "analytics": { + "title": "Les teves dades d’un cop d’ull", + "subtitle": "Recomptes sincronitzats amb el teu compte al servidor.", + "loadError": "No s’han pogut carregar les estadístiques.", + "retry": "Torna-ho a provar", + "loginHint": "Inicia la sessió per veure l’anàlisi d’ús i les preferències d’interfície.", + "passwordEntries": "Entrades de contrasenyes", + "bookmarks": "Marcadors", + "bookmarkFolders": "Carpetes de marcadors", + "tasksTotal": "Tasques", + "tasksCompleted": "Completades", + "tasksOngoing": "En curs", + "tasksNotStarted": "No iniciades", + "projects": "Projectes", + "nosqlConnections": "Connexions a BD", + "notes": "Notes", + "apiClientCollections": "Col·leccions API", + "apiClientEnvironments": "Entorns API", + "apiClientHistory": "Entrades de l’historial API", + "jsonFormatterDocuments": "Documents del formatador JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Fragments de codi", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Preferències d’interfície", + "subtitle": "Tema, idioma i eines visibles a la barra lateral — des de la configuració del compte.", + "loadError": "No s’han pogut carregar les preferències.", + "loginHint": "Inicia la sessió per veure les preferències d’interfície desades.", + "theme": "Tema", + "accent": "Color d’accent", + "locale": "Idioma", + "enabledTools": "Eines visibles", + "favoriteTools": "Eines favorites", + "toolsWithUsage": "Eines amb ús registrat" + }, + "signIn": "Inicia la sessió", + "noResults": "Cap eina coincideix amb la vostra cerca", + "noResultsHint": "Proveu una paraula clau diferent o elimineu el filtre.", + "filterAll": "Tot", + "loginCta": { + "description": "Inicieu sessió per fer un seguiment de les eines utilitzades recentment." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "S'està carregant...", + "myTasksTitle": "Les meves tasques", + "statsLine": "{total} tasques • {percent}% completades", + "statsLineDesktop": "{total} en total • {percent}% completades" + }, + "filters": { + "all": "Totes", + "total": "Total", + "allProjects": "Tots els projectes", + "export": "Exporta", + "kanban": "Kanban", + "list": "Llista", + "searchPlaceholder": "Cerca tasques...", + "searchAria": "Cerca tasques", + "clearSearchAria": "Neteja la cerca", + "foundTasks": "S'han trobat {count} {count, plural, one {tasca} other {tasques}}", + "filterByAllAria": "Filtra per totes", + "filterByStatusAria": "Filtra per {status}" + }, + "status": { + "not-started": { + "label": "No iniciada", + "description": "Tasques que encara no s'han començat" + }, + "ongoing": { + "label": "En curs", + "description": "Tasques en progrés" + }, + "completed": { + "label": "Completada", + "description": "Tasques finalitzades" + } + }, + "priorities": { + "high": "Alta", + "medium": "Mitjana", + "low": "Baixa" + }, + "form": { + "addNewTaskLabel": "Afegeix una tasca nova", + "multilinePlaceholder": "Afegeix detalls de la tasca... (Maj+Retorn per a línia nova)", + "singlelinePlaceholder": "Què cal fer? Prem Retorn per afegir", + "taskInputAria": "Entrada de tasca", + "projectPlaceholder": "Projecte", + "selectProject": "Selecciona un projecte", + "errorSelectProject": "Selecciona un projecte o crea'n un de nou per afegir una tasca.", + "hintMultiline": "Maj+Retorn per a línia nova, Retorn per afegir", + "hintSingleline": "Retorn per afegir", + "shortcutsAria": "Dreceres de teclat", + "shortcutsLabel": "Dreceres", + "shortcutAddTask": "Afegeix tasca", + "shortcutNewLine": "Línia nova", + "shortcutClear": "Neteja", + "addButton": "Afegeix" + }, + "list": { + "ariaLabel": "Llista de tasques", + "loading": "S'estan carregant les teves tasques...", + "emptyTitle": "No s'ha trobat cap tasca", + "emptyPrimary": "Comença afegint la teva primera tasca a dalt! Prem Retorn per afegir tasques ràpidament.", + "emptySecondary": "Prova d'ajustar els filtres o la cerca." + }, + "mobileDrawer": { + "addTaskSrOnly": "Afegeix tasca", + "addNewTaskTitle": "Tasca nova", + "addNewTaskDescription": "Crea una tasca nova per fer seguiment del teu progrés.", + "cancel": "Cancel·la" + }, + "kanban": { + "ariaLabel": "Tauler Kanban", + "loading": "S'estan carregant les teves tasques...", + "dropHere": "Deixa anar la tasca aquí", + "noTasksYet": "Encara no hi ha tasques", + "emptyColumnAria": "Columna buida", + "columnAria": "Columna {title} amb {count} tasques", + "countInColumnAria": "{count} tasques a {title}" + }, + "taskItem": { + "confirmDelete": "Segur que vols eliminar aquesta tasca?", + "statusPlaceholder": "Estat", + "changeStatusAria": "Canvia l'estat de la tasca", + "taskOptionsAria": "Opcions de la tasca", + "editTask": "Edita la tasca", + "copyTask": "Copia la tasca", + "copied": "Copiat!", + "deleteTask": "Elimina la tasca", + "statusHeading": "Estat", + "editTaskAria": "Edita la tasca" + }, + "editDialog": { + "title": "Edita la tasca", + "description": "Actualitza els detalls, afegeix subtasques, defineix prioritats i més.", + "taskTitleLabel": "Títol de la tasca *", + "taskTitlePlaceholder": "Què cal fer?", + "descriptionLabel": "Descripció", + "descriptionPlaceholder": "Afegeix més detalls sobre aquesta tasca...", + "statusLabel": "Estat", + "priorityLabel": "Prioritat", + "dueDateLabel": "Data límit", + "pickDate": "Trieu una data", + "clearDate": "Esborra la data", + "projectLabel": "Projecte", + "selectProjectPlaceholder": "Selecciona un projecte", + "noProject": "Sense projecte", + "timeEstimateLabel": "Estimació de temps (minuts)", + "timeEstimatePlaceholder": "Quant durarà?", + "tagsLabel": "Etiquetes", + "customTagNamePlaceholder": "Nom d'etiqueta personalitzada", + "subtasksLabel": "Subtasques", + "addSubtaskPlaceholder": "Afegeix una subtasca...", + "cancel": "Cancel·la", + "saving": "S'està desant...", + "saveChanges": "Desa els canvis" + }, + "export": { + "title": "Exporta tasques", + "description": "Exporta les teves tasques a JSON, CSV o Excel.", + "projectLabel": "Projecte", + "statusLabel": "Estat", + "tagLabel": "Etiqueta", + "allProjects": "Tots els projectes", + "allStatuses": "Tots els estats", + "allTags": "Totes les etiquetes", + "exportAsJsonTitle": "Exporta com a JSON", + "exportAsJsonDescription": "Exporta les dades de les tasques, subtasques, etiquetes i metadades.", + "exportJson": "Exporta JSON", + "exportAsCsvExcelTitle": "Exporta com a CSV / Excel", + "exportAsCsvExcelDescription": "Exporta tasques per a fulls de càlcul.", + "exportCsv": "Exporta CSV", + "exportExcel": "Exporta Excel", + "close": "Tanca", + "toastNoTasks": "No hi ha tasques per exportar amb els filtres actuals", + "toastExportedJson": "S'han exportat {count} tasques a JSON!", + "toastExportedCsv": "S'han exportat {count} tasques a CSV!", + "toastExportedExcel": "S'han exportat {count} tasques a Excel!", + "toastFailedJson": "No s'ha pogut exportar a JSON", + "toastFailedCsv": "No s'ha pogut exportar a CSV", + "toastFailedExcel": "No s'ha pogut exportar a Excel", + "unknownProject": "Projecte desconegut" + }, + "ack": { + "taskAddedTitle": "Tasca afegida correctament", + "taskAddedFailedTitle": "No s'ha pogut afegir la tasca", + "taskUpdatedTitle": "Tasca actualitzada correctament", + "taskUpdatedFailedTitle": "No s'ha pogut actualitzar la tasca", + "taskStatusUpdateFailedTitle": "No s'ha pogut actualitzar l'estat de la tasca", + "taskMovedTitle": "Tasca moguda a {status}", + "tryAgain": "Torna-ho a provar.", + "taskDeletedTitle": "S'ha eliminat la tasca «{text}»", + "undo": "Desfés", + "taskRestoredTitle": "Tasca restaurada", + "taskRestoredSuccessTitle": "Tasca restaurada correctament", + "taskRestoreFailedTitle": "No s'ha pogut restaurar la tasca. Torna-ho a provar.", + "taskDeleteFailedTitle": "No s'ha pogut eliminar la tasca. Torna-ho a provar.", + "tasksImportedTitle": "S'han importat {count} {count, plural, one {tasca} other {tasques}} correctament!", + "tasksImportFailedTitle": "No s'han pogut importar les tasques. Torna-ho a provar.", + "unknownProject": "Projecte desconegut", + "projectCreatedTitle": "Projecte creat correctament", + "projectCreateFailedTitle": "No s'ha pogut crear el projecte", + "projectUpdatedTitle": "Projecte actualitzat correctament", + "projectUpdateFailedTitle": "No s'ha pogut actualitzar el projecte", + "projectDeletedTitle": "Projecte eliminat correctament", + "projectDeleteFailedTitle": "No s'ha pogut eliminar el projecte" + } + }, + "Notes": { + "page": { + "loading": "S'està carregant..." + }, + "layout": { + "mobileNavTitle": "Navegació de notes", + "openMenuAria": "Obre el menú de notes" + }, + "sidebar": { + "title": "Notes", + "createNoteAria": "Crea una nota", + "searchPlaceholder": "Cerca notes...", + "loading": "S'està carregant...", + "noNotesFound": "No s'ha trobat cap nota.", + "noNotesYet": "Encara no hi ha notes. Fes clic a + per crear-ne una.", + "inParent": "a {parentTitle}", + "untitled": "Sense títol", + "addSubPage": "Afegeix subpàgina", + "delete": "Elimina", + "deleteConfirmTitle": "Vols eliminar la nota?", + "deleteConfirmDescription": "Segur que vols eliminar «{title}»? Aquesta acció no es pot desfer.", + "cancel": "Cancel·la", + "confirmDelete": "Elimina", + "noteOptionsAria": "Opcions de la nota" + }, + "editor": { + "emptyState": "Selecciona una nota o crea'n una de nova per començar.", + "titlePlaceholder": "Sense títol", + "saving": "Desant…", + "ready": "A punt", + "saved": "Desat {time}", + "stats": "{words} paraules · {read} min de lectura", + "template": "Plantilla", + "applyTemplate": "Aplica la plantilla", + "exportMdLabel": ".md", + "exportMd": "Exporta com a Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Exporta com a HTML", + "focusMode": "Mode concentració", + "exitFocusMode": "Surt del mode concentració", + "save": "Desa", + "addTag": "Afegeix etiqueta…", + "removeTag": "Elimina l'etiqueta {tag}", + "loading": "Carregant…", + "searchHint": "per cercar", + "focusHint": "mode concentració", + "notePath": "Camí de la nota" + }, + "context": { + "authRequiredError": "Usuari no autenticat", + "defaultTitle": "Sense títol", + "cloudSyncImageError": "Activa la sincronització al núvol per desar imatges de les notes. Les notes de text es queden localment." + } + }, + "PasswordManager": { + "page": { + "loading": "S'està carregant...", + "title": "Gestor de contrasenyes", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "No s'han pogut desxifrar les contrasenyes", + "toastLoadFailed": "No s'han pogut carregar les contrasenyes", + "toastUnlockedAuto": "Coffre desblocat automàticament", + "toastAutoUnlockFailed": "El desblocatge automàtic ha fallat. Desbloca manualment.", + "toastConnectFailed": "No s'ha pogut connectar al servidor", + "toastAutoUnlockWarning": "Coffre desblocat, però el desblocatge automàtic potser no funciona en recarregar", + "errorIncorrectPassword": "Contrasenya mestra incorrecta", + "errorUnlockFailed": "No s'ha pogut desbloquejar el coffre", + "errorPasswordsMismatch": "Les contrasenyes no coincideixen", + "errorPasswordTooShort": "La contrasenya ha de tenir com a mínim 8 caràcters", + "errorSetupFailed": "No s'ha pogut configurar el coffre", + "errorConnectFailed": "No s'ha pogut connectar al servidor.", + "mobileTitleCreate": "Crea el teu coffre", + "mobileTitleWelcome": "Benvingut de nou", + "mobileSubtitleCreate": "Defineix una contrasenya mestra per protegir les teves credencials", + "mobileSubtitleUnlock": "Introdueix la contrasenya mestra per desbloquejar", + "placeholderMasterPassword": "Contrasenya mestra", + "placeholderConfirmPassword": "Confirma la contrasenya", + "labelMasterPassword": "Contrasenya mestra", + "labelConfirmPassword": "Confirma la contrasenya", + "placeholderEnterMaster": "Introdueix la contrasenya mestra", + "placeholderConfirmMaster": "Confirma la contrasenya mestra", + "buttonCreateVault": "Crea el coffre", + "buttonUnlock": "Desbloqueja", + "buttonUnlockVault": "Desbloqueja el coffre", + "footerCreate": "La teva contrasenya no es desa mai i no es pot recuperar", + "footerUnlock": "Protegit amb xifratge extrem a extrem", + "desktopTitleSetup": "Configura el coffre de contrasenyes", + "desktopTitleUnlock": "Desbloqueja el coffre", + "desktopDescriptionSetup": "Crea una contrasenya mestra per protegir les teves credencials. Aquesta contrasenya no es desa mai i no es pot recuperar.", + "desktopDescriptionUnlock": "Introdueix la contrasenya mestra per accedir a les credencials protegides." + }, + "list": { + "loading": "S'estan carregant les contrasenyes...", + "emptyTitle": "El coffre és buit", + "emptyHintMobile": "Toca el botó + per afegir la primera contrasenya", + "emptyHintDesktop": "Encara no has desat cap contrasenya. Fes clic a «Afegeix contrasenya» a dalt per començar.", + "addPassword": "Afegeix contrasenya", + "searchPlaceholderMobile": "Cerca al coffre...", + "searchPlaceholderDesktop": "Cerca al coffre...", + "importExport": "Importa / exporta", + "lockVault": "Bloqueja el coffre", + "allPasswordsTitle": "Totes les contrasenyes", + "passwordCount": "{count, plural, one {# contrasenya} other {# contrasenyes}}", + "vaultHealthDrawerTitle": "Salut del coffre", + "noResults": "No s'ha trobat cap contrasenya que coincideixi amb «{query}»", + "tableService": "Servei", + "tableUsername": "Usuari", + "tablePassword": "Contrasenya", + "tableUrl": "URL", + "tableActions": "Accions", + "gridViewAria": "Vista de graella", + "listViewAria": "Vista de llista", + "passwordStrengthTitle": "Fortalesa de la contrasenya", + "deleteDialogTitle": "Elimina la contrasenya", + "deleteDialogDescription": "Segur que vols eliminar aquesta contrasenya? Aquesta acció no es pot desfer.", + "cancel": "Cancel·la", + "delete": "Elimina", + "edit": "Edita" + }, + "toasts": { + "copiedPassword": "Contrasenya copiada al porta-retalls", + "copiedUsername": "Usuari copiat al porta-retalls", + "passwordDeleted": "Contrasenya eliminada", + "deleteFailed": "No s'ha pogut eliminar la contrasenya", + "vaultLocked": "Coffre bloquejat", + "importedCount": "S'han importat {count, plural, one {# contrasenya} other {# contrasenyes}} correctament" + }, + "form": { + "addTitle": "Contrasenya nova", + "editTitle": "Edita la contrasenya", + "serviceName": "Nom del servei", + "usernameEmail": "Usuari / correu", + "password": "Contrasenya", + "urlOptional": "URL del lloc web (opcional)", + "notesOptional": "Notes (opcional)", + "tags": "Etiquetes", + "placeholderService": "p. ex. Netflix", + "placeholderUsername": "usuari@exemple.com", + "placeholderPassword": "Introdueix o genera una contrasenya", + "placeholderUrl": "https://...", + "placeholderNotes": "Detalls addicionals...", + "placeholderTags": "Afegeix etiquetes (Retorn o coma)", + "strength": "Fortalesa", + "strengthWeak": "Feble", + "strengthMedium": "Mitjana", + "strengthStrong": "Forta", + "advancedGenerator": "Generador avançat", + "cancel": "Cancel·la", + "saving": "S'està desant...", + "savePassword": "Desa la contrasenya", + "updating": "S'està actualitzant...", + "updatePassword": "Actualitza la contrasenya", + "addPasswordSrOnly": "Afegeix contrasenya", + "triggerAdd": "Afegeix contrasenya", + "toastSaved": "Contrasenya desada correctament", + "toastSaveFailed": "No s'ha pogut desar la contrasenya", + "toastUpdated": "Contrasenya actualitzada correctament", + "toastUpdateFailed": "No s'ha pogut actualitzar la contrasenya" + }, + "security": { + "vaultHealth": "Salut del coffre", + "statusLabel": "Estat", + "totalPasswords": "Contrasenyes totals", + "activeEntries": "Entrades actives al coffre", + "protected": "Protegit", + "weakPasswords": "Contrasenyes febles", + "needingImprovement": "Contrasenyes a millorar", + "allGood": "Tot correcte", + "reusedPasswords": "Contrasenyes reutilitzades", + "usedMultipleTimes": "Contrasenyes usades diverses vegades", + "unique": "Úniques", + "total": "Total", + "weak": "Feble", + "reused": "Reutilitzada", + "totalCountLabel": "{count, plural, one {# contrasenya} other {# contrasenyes}}", + "weakCountLabel": "{count} febles", + "allSecure": "Tot segur", + "scoreExcellent": "Excel·lent", + "scoreGood": "Bo", + "scoreFair": "Acceptable", + "scoreCritical": "Crític" + }, + "importExport": { + "title": "Importa / exporta el coffre", + "description": "Gestiona les dades de contrasenyes. Pots exportar el coffre a JSON o importar des d'un fitxer JSON.", + "tabExport": "Exporta", + "tabImport": "Importa", + "warningTitle": "Avís", + "warningBody": "Els fitxers exportats contenen les contrasenyes en text pla. Mantén el fitxer segur i elimina'l després d'usar-lo.", + "exportBlurb": "Baixa tot el coffre com a JSON. Inclou serveis, usuaris, contrasenyes i notes.", + "downloadJson": "Baixa exportació JSON", + "pasteLabel": "Enganxa dades JSON", + "pastePlaceholder": "[{\"service\": \"Exemple\", \"username\": \"usuari\", \"password\": \"123\", ...}]", + "pasteHint": "Enganxa aquí el contingut d'un fitxer JSON exportat anteriorment.", + "importing": "S'està important...", + "importPasswords": "Importa contrasenyes", + "trigger": "Importa / exporta", + "toastExported": "Coffre exportat correctament", + "toastExportFailed": "No s'ha pogut exportar el coffre", + "toastImportFailed": "No s'ha pogut importar: format JSON no vàlid" + }, + "card": { + "copyUsername": "Copia l'usuari", + "openUrl": "Obre l'URL", + "strength": "Fortalesa" + }, + "swipeable": { + "copy": "Copia", + "delete": "Elimina", + "copyPassword": "Copia la contrasenya", + "copyUsername": "Copia l'usuari", + "edit": "Edita", + "deleteAction": "Elimina" + }, + "floatingMenu": { + "lockVault": "Bloqueja el coffre", + "importExport": "Importa / exporta", + "addPassword": "Afegeix contrasenya" + }, + "generator": { + "tabPassword": "Contrasenya", + "tabPassphrase": "Frase secreta", + "strength": "Fortalesa", + "lengthLabel": "Longitud: {length} caràcters", + "lengthBadge": "{length} car.", + "characterSets": "Jocs de caràcters", + "uppercase": "Majúscules", + "lowercase": "Minúscules", + "numbers": "Números", + "symbols": "Símbols", + "wordCountLabel": "Nombre de paraules: {count}", + "wordCountBadge": "{count, plural, one {# paraula} other {# paraules}}", + "separator": "Separador", + "capitalization": "Majúscules inicials", + "includeNumber": "Inclou un número" + } + }, + "EnvironmentManager": { + "subtitle": "Variables d'entorn xifrades per projecte i entorn.", + "page": { + "loading": "S'està carregant…", + "title": "Gestor d'entorns", + "loadFailed": "No s'han pogut carregar els conjunts d'entorn" + }, + "list": { + "title": "Entorns", + "loading": "S'estan carregant els conjunts…", + "emptyTitle": "Encara no hi ha conjunts d'entorn", + "emptyHint": "Crea un conjunt per a cada projecte i entorn (p. ex. claus d'API de producció). Tot es xifra al navegador abans de pujar-lo.", + "searchPlaceholder": "Cerca projecte, entorn, etiquetes, notes…", + "tagFilterPlaceholder": "Filtra per etiqueta", + "variableCount": "{count, plural, one {# variable} other {# variables}}", + "updatedAgo": "Actualitzat {time}", + "copyDotEnv": "Copia .env", + "edit": "Edita", + "delete": "Suprimeix", + "cancel": "Cancel·la", + "confirmDelete": "Suprimeix", + "deleteDialogTitle": "Vols suprimir el conjunt d'entorn?", + "deleteDialogDescription": "Això elimina el blob xifrat del teu compte. No es pot desfer.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env copiat al porta-retalls", + "deleted": "Conjunt d'entorn suprimit", + "deleteFailed": "No s'ha pogut suprimir" + }, + "form": { + "addTitle": "Nou conjunt d'entorn", + "editTitle": "Edita el conjunt d'entorn", + "triggerAdd": "Afegeix conjunt d'entorn", + "addSrOnly": "Afegeix conjunt d'entorn", + "project": "Projecte", + "environment": "Entorn", + "placeholderProject": "p. ex. billing-api", + "placeholderEnvironment": "p. ex. production", + "pasteEnvLabel": "Enganxa .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Fusiona les línies enganxades", + "variables": "Variables", + "addRow": "Afegeix fila", + "keyPlaceholder": "CLAU", + "valuePlaceholder": "valor", + "notesOptional": "Notes (opcional)", + "placeholderNotes": "Política de rotació, propietari, enllaços…", + "tags": "Etiquetes", + "placeholderTags": "Afegeix etiquetes (Retorn o coma)", + "cancel": "Cancel·la", + "save": "Desa", + "saving": "S'està desant…", + "update": "Actualitza", + "updating": "S'està actualitzant…", + "toastSaved": "Conjunt d'entorn desat", + "toastSaveFailed": "No s'ha pogut desar", + "toastUpdated": "Conjunt d'entorn actualitzat", + "toastUpdateFailed": "No s'ha pogut actualitzar", + "toastPasteEmpty": "No s'han trobat línies CLAU=valor", + "toastPasteApplied": "S'han fusionat {count} variables de l'enganxat", + "toastProjectEnvRequired": "El projecte i l'entorn són obligatoris", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "S'està carregant..." + }, + "manager": { + "foldersHeading": "Carpetes", + "tagsHeading": "Etiquetes", + "tagsMore": "+{count} més", + "tagsShowLess": "Show less", + "searchPlaceholder": "Cerca marcadors...", + "import": "Importa", + "export": "Exporta", + "exportAsHtml": "Exporta com a HTML", + "exportAsJson": "Exporta com a JSON", + "addBookmark": "Afegeix marcador", + "matchingQuery": "coincidències amb «{query}»", + "bookmarkCount": "{count, plural, one {# marcador} other {# marcadors}}", + "allBookmarks": "Tots els marcadors", + "uncategorized": "Sense categoria", + "unknownFolder": "Desconeguda" + }, + "card": { + "urlCopied": "URL copiada al porta-retalls", + "bookmarkDeleted": "Marcador eliminat", + "edit": "Edita", + "delete": "Elimina", + "copyUrl": "Copia l'URL", + "deleteConfirmTitle": "Vols eliminar el marcador?", + "deleteConfirmDescription": "S'eliminarà permanentment «{title}».", + "cancel": "Cancel·la" + }, + "grid": { + "emptyTitle": "Encara no hi ha marcadors", + "emptyDescription": "Afegeix el primer marcador o importa des del navegador per començar." + }, + "folderTree": { + "allBookmarks": "Tots els marcadors", + "addSubfolder": "Afegeix subcarpeta", + "rename": "Canvia el nom", + "deleteFolderTitle": "Vols eliminar la carpeta «{name}»?", + "deleteFolderDescription": "S'eliminarà la carpeta i totes les subcarpetes. Els marcadors aniran a Sense categoria." + }, + "import": { + "title": "Importa marcadors", + "description": "Importa marcadors des del fitxer exportat del navegador.", + "parseError": "No s'ha pogut analitzar el fitxer. Assegura't que sigui una exportació vàlida.", + "successToast": "S'han importat {bookmarks} marcadors i {folders} carpetes", + "errorToast": "No s'han pogut importar els marcadors", + "chromeInstruction": "Menú → Marcadors → Gestor de marcadors → ⋮ → Exporta marcadors", + "firefoxInstruction": "Menú → Marcadors → Gestiona marcadors → Importa i còpia de seguretat → Exporta", + "safariInstruction": "Fitxer → Exporta marcadors...", + "edgeInstruction": "Menú → Preferits → ⋯ → Exporta preferits", + "dropZoneTitle": "Deixa anar el fitxer de marcadors aquí", + "dropZoneHint": "o fes clic per triar (HTML o JSON)", + "selectedFileHint": "Fes clic per triar un altre fitxer", + "errorTitle": "Error", + "previewTitle": "Vista prèvia de la importació", + "previewBookmarks": "{count, plural, one {# marcador} other {# marcadors}}", + "previewFolders": "{count, plural, one {# carpeta} other {# carpetes}}", + "importModeLabel": "Mode d'importació", + "modeMerge": "Fusiona amb els marcadors existents", + "modeReplace": "Substitueix tots els marcadors existents", + "cancel": "Cancel·la", + "importButton": "Importa", + "importButtonWithCount": "Importa {count, plural, one {# marcador} other {# marcadors}}" + }, + "addBookmark": { + "titleAdd": "Afegeix marcador", + "titleEdit": "Edita el marcador", + "urlLabel": "URL", + "urlPlaceholder": "https://exemple.com", + "urlInvalid": "Introdueix una URL vàlida", + "titleLabel": "Títol", + "titlePlaceholder": "Títol del marcador", + "titleRequired": "Introdueix un títol", + "descriptionLabel": "Descripció (opcional)", + "descriptionPlaceholder": "Afegeix una nota sobre aquest marcador", + "folderLabel": "Carpeta", + "folderPlaceholder": "Selecciona carpeta", + "noFolder": "Sense carpeta", + "tagsLabel": "Etiquetes", + "tagsPlaceholder": "Afegeix etiquetes (Retorn)", + "toastUpdated": "Marcador actualitzat", + "toastAdded": "Marcador afegit", + "cancel": "Cancel·la", + "submitAdd": "Afegeix marcador", + "submitUpdate": "Actualitza el marcador" + }, + "addFolder": { + "title": "Crea carpeta", + "nameLabel": "Nom de la carpeta", + "namePlaceholder": "La meva carpeta", + "nameRequired": "Introdueix un nom de carpeta", + "parentLabel": "Carpeta pare", + "parentPlaceholder": "Selecciona carpeta pare", + "rootParent": "Arrel (sense pare)", + "toastCreated": "Carpeta creada", + "cancel": "Cancel·la", + "submit": "Crea la carpeta" + }, + "editFolder": { + "title": "Canvia el nom de la carpeta", + "nameLabel": "Nom de la carpeta", + "namePlaceholder": "Nom de la carpeta", + "nameRequired": "Introdueix un nom de carpeta", + "toastRenamed": "S'ha canviat el nom de la carpeta", + "cancel": "Cancel·la", + "save": "Desa" + } + }, + "EmailValidator": { + "header": { + "title": "Validador de correu", + "description": "Verifica i valida adreces de correu per sintaxi, domini i lliurabilitat." + }, + "tabs": { + "single": "Verificació individual", + "bulk": "Pujada massiva" + }, + "single": { + "cardTitle": "Verificació d'un sol correu", + "cardDescription": "Introdueix una adreça per comprovar la lliurabilitat i la reputació.", + "placeholder": "Introdueix l'adreça de correu...", + "clearAria": "Neteja el correu", + "validate": "Valida", + "validating": "S'està validant...", + "formatError": "Introdueix una adreça de correu vàlida" + }, + "toasts": { + "errorTitle": "Error", + "enterEmail": "Introdueix una adreça de correu", + "invalidFormatTitle": "Format de correu no vàlid", + "invalidFormatDescription": "Introdueix una adreça de correu vàlida", + "validateFailedDescription": "S'ha produït un error en validar el correu", + "noEmailsTitle": "No s'han trobat correus", + "noEmailsDescription": "Assegura't que l'Excel tingui una columna «email».", + "fileTooLargeTitle": "Fitxer massa gran", + "fileTooLargeDescription": "S'han trobat {total} correus. Només es validaran els primers 4000.", + "successTitle": "Correcte", + "successValidated": "S'han validat {count} correus correctament.", + "batchFailedTitle": "Validació massiva fallida", + "batchFailedDescription": "S'ha produït un error en processar la validació massiva.", + "noExportTitle": "No hi ha dades per exportar", + "noExportValidOnly": "No s'han trobat correus vàlids per exportar.", + "noExportAll": "No hi ha resultats per exportar.", + "exportSuccessTitle": "Exportació correcta", + "exportSuccessExcelAll": "S'han exportat {count} correus a Excel.", + "exportSuccessExcelValid": "S'han exportat {count} correus vàlids a Excel.", + "exportSuccessCsvAll": "S'han exportat {count} correus a CSV.", + "exportSuccessCsvValid": "S'han exportat {count} correus vàlids a CSV." + }, + "disposableAlert": { + "title": "Correu temporal detectat", + "description": "Aquesta adreça pertany a un proveïdor de correu d'un sol ús. Pot ser temporal o poc fiable." + }, + "score": { + "reliabilityTitle": "Puntuació de fiabilitat", + "scoreLabel": "Puntuació" + }, + "details": { + "sectionTitle": "Detalls de la validació" + }, + "validation": { + "syntaxLabel": "Sintaxi vàlida", + "syntaxTooltip": "Comprova si l'adreça segueix el format correcte (p. ex. usuari@domini.com)", + "domainLabel": "El domini existeix", + "domainTooltip": "Verifica que el domini de l'adreça existeix realment", + "mxLabel": "Registres MX trobats", + "mxTooltip": "Comprova si el domini té registres d'intercanvi de correu (MX) per rebre correus", + "mailboxLabel": "La bústia existeix", + "mailboxTooltip": "Verifica que la bústia o compte existeix al servidor de correu", + "disposableLabel": "Correu d'un sol ús", + "disposableTooltip": "Indica si el correu pertany a un servei temporal o d'un sol ús (pot ser arriscat)", + "roleBasedLabel": "Correu de rol", + "roleBasedTooltip": "Identifica si és un correu de rol (p. ex. suport@, info@) en lloc d'un compte personal" + }, + "copy": { + "emailCopied": "Correu copiat!", + "copyTooltip": "Copia el correu" + }, + "bulk": { + "dropHere": "Deixa anar el fitxer aquí", + "dragHere": "Arrossega i deixa anar el fitxer aquí", + "uploadHint": "Puja un fitxer .xlsx, .xls o .csv amb una columna «email» per validar fins a 4000 correus.", + "filePrefix": "Fitxer:", + "selectFile": "Selecciona fitxer", + "downloadTemplate": "Baixa la plantilla" + }, + "bulkLoading": { + "title": "S'estan validant els correus...", + "batchProgress": "Processant el lot {current} de {total}..." + }, + "stats": { + "totalProcessed": "Total processats", + "valid": "Vàlids", + "risky": "Arriscats", + "invalid": "No vàlids" + }, + "results": { + "detailedTitle": "Resultats detallats", + "searchPlaceholder": "Cerca correus...", + "newVerification": "Nova verificació", + "exportAll": "Exporta-ho tot", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Exporta vàlids ({count})" + }, + "showingResults": "Es mostren {shown} de {total} resultats", + "table": { + "email": "Correu", + "status": "Estat", + "score": "Puntuació", + "checks": "Comprovacions" + }, + "noSearchResults": "No hi ha resultats que coincideixin amb «{query}»", + "checksTooltip": { + "syntax": "Sintaxi", + "domain": "Domini", + "mx": "MX", + "mailbox": "Bústia" + }, + "exportColumns": { + "email": "Correu", + "status": "Estat", + "score": "Puntuació", + "syntax": "Sintaxi", + "domain": "Domini", + "mx": "MX", + "mailbox": "Bústia", + "disposable": "D'un sol ús", + "roleBased": "Rol", + "valid": "Vàlid", + "invalid": "No vàlid", + "yes": "Sí", + "no": "No" + }, + "sheets": { + "validEmails": "Correus vàlids", + "results": "Resultats", + "template": "Plantilla" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "Explorador NoSQL", + "heroSubtitle": "Connecta't a les teves bases de dades per gestionar col·leccions, executar consultes i visualitzar les dades.", + "featureMultiTitle": "Múltiples connexions", + "featureMultiDesc": "Gestiona diverses connexions a la base de dades alhora.", + "featureMongoTitle": "Compatibilitat amb MongoDB", + "featureMongoDesc": "Suport natiu per a MongoDB amb filtres avançats.", + "featureQueryTitle": "Consulta intel·ligent", + "featureQueryDesc": "Constructor de consultes potent amb ressaltat de sintaxi.", + "connectCta": "Connecta a MongoDB", + "footerNote": "Actualment compatible amb {db}. Aviat hi haurà més bases de dades.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Selecciona una col·lecció", + "selectCollectionDesc": "Tria una col·lecció de la barra lateral per veure documents i explorar les dades.", + "dialogAddConnection": "Afegeix connexió", + "toastConnectionAdded": "Connexió afegida", + "toastFetchFailed": "No s'han pogut obtenir documents de {collection}", + "toastDocDeleted": "Document eliminat", + "deleteDocTitle": "N'estàs completament segur?", + "deleteDocDescription": "Aquesta acció no es pot desfer. El document s'eliminarà permanentment de la col·lecció.", + "closeAllTitle": "Vols tancar totes les pestanyes?", + "closeAllDescription": "Segur que vols tancar totes les pestanyes? Es perdran els canvis no desats als filtres de consulta.", + "closeAllConfirm": "Tanca-ho tot", + "cancel": "Cancel·la", + "delete": "Elimina" + }, + "connection": { + "titleEdit": "Edita la connexió", + "titleConnect": "Connecta a MongoDB", + "subtitleEdit": "Actualitza els detalls de connexió a continuació.", + "subtitleConnect": "Introdueix la cadena de connexió de MongoDB per començar a explorar les dades.", + "labelName": "Nom", + "placeholderName": "p. ex. BD de producció", + "labelConnectionString": "Cadena de connexió", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://usuari:contrasenya@host:port/base_de_dades", + "testConnection": "Prova la connexió", + "connecting": "S'està connectant...", + "updateConnect": "Actualitza i connecta", + "connect": "Connecta", + "cancelEdit": "Cancel·la l'edició", + "savedConnections": "Connexions desades", + "emptySaved": "Encara no hi ha connexions desades.", + "lastUsed": "Últim ús {time}", + "toastTestOk": "Connexió correcta!", + "toastTestFail": "Connexió fallida: {message}", + "toastUpdated": "Connexió actualitzada", + "confirmDelete": "Segur que vols eliminar aquesta connexió desada?", + "toastDeletedConn": "Connexió eliminada", + "toastDeleteFail": "No s'ha pogut eliminar la connexió", + "copyStringTitle": "Copia la cadena de connexió", + "toastStringCopied": "Cadena de connexió copiada!", + "editTitle": "Edita", + "deleteTitle": "Elimina" + }, + "sidebar": { + "explorer": "Explorador", + "add": "Afegeix", + "tooltipAdd": "Afegeix una connexió nova", + "tooltipRefresh": "Actualitza les connexions", + "searchPlaceholder": "Cerca...", + "loadingConnections": "S'estan carregant les connexions...", + "noConnectionsFound": "No s'ha trobat cap connexió", + "toastLoadFail": "No s'han pogut carregar les connexions", + "toastConnectFail": "No s'ha pogut connectar a {name}", + "toastCollectionsFail": "No s'han pogut obtenir les col·leccions de {name}", + "toastRenamed": "S'ha canviat el nom de la connexió", + "toastRenameFail": "No s'ha pogut canviar el nom de la connexió", + "confirmDeleteConnection": "Segur que vols eliminar la connexió «{name}»?", + "toastDeleted": "Connexió eliminada", + "toastDeleteConnFail": "No s'ha pogut eliminar la connexió", + "confirmDropDb": "Segur que vols eliminar la base de dades «{name}»? Aquesta acció no es pot desfer.", + "toastDbDropped": "S'ha eliminat la base de dades {name}", + "confirmDropCollection": "Segur que vols eliminar la col·lecció «{name}»? Aquesta acció no es pot desfer.", + "toastCollectionDropped": "S'ha eliminat la col·lecció {name}", + "toastCollectionRenamed": "Col·lecció reanomenada a {name}", + "toastDatabaseRenamed": "Base de dades reanomenada a {name}", + "connecting": "S'està connectant...", + "connectionFailed": "Connexió fallida", + "noDatabases": "Sense bases de dades", + "loading": "S'està carregant...", + "noCollections": "Sense col·leccions", + "renameCollectionTitle": "Reanomena la col·lecció", + "renameDatabaseTitle": "Reanomena la base de dades", + "placeholderNewCollection": "Nom nou de la col·lecció", + "placeholderNewDatabase": "Nom nou de la base de dades", + "rename": "Reanomena", + "refresh": "Actualitza", + "copyDbName": "Copia el nom de la BD", + "toastDbNameCopied": "Nom de la base de dades copiat al porta-retalls", + "dropDatabase": "Elimina la base de dades", + "dropCollection": "Elimina la col·lecció", + "menuRename": "Reanomena", + "menuCopyConnectionString": "Copia la cadena de connexió", + "menuDeleteConnection": "Elimina la connexió", + "toastSidebarStringCopied": "Cadena de connexió copiada!", + "cancel": "Cancel·la", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} docs", + "table": "Taula", + "json": "JSON", + "tree": "Arbre", + "tooltipTable": "Vista de taula", + "tooltipJson": "Vista JSON", + "tooltipTree": "Vista d'arbre", + "itemsPerPage": "Elements per pàgina", + "perPage": "{n} / pàgina", + "prevPage": "Pàgina anterior", + "nextPage": "Pàgina següent", + "expandAll": "Expandeix-ho tot", + "collapseAll": "Col·lapsa-ho tot", + "refreshData": "Actualitza les dades", + "insert": "Insereix", + "export": "Exporta", + "actions": "Accions", + "invalidJson": "Contingut JSON no vàlid", + "copiedClipboard": "Copiat al porta-retalls", + "docUpdated": "Document actualitzat correctament", + "docInserted": "Document inserit correctament", + "invalidJsonShort": "JSON no vàlid", + "docCopied": "Document copiat al porta-retalls", + "openFullEditor": "Obre a l'editor complet", + "cellArray": "Array({length})", + "cellObject": "Objecte", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Cap document coincideix amb la consulta", + "emptyFilterHint": "Prova d'ajustar el filtre o neteja la consulta.", + "clearFilter": "Neteja el filtre", + "emptyCollectionTitle": "Aquesta col·lecció és buida", + "emptyCollectionHint": "Insereix un document per començar.", + "insertDocument": "Insereix document", + "formatJson": "Formata JSON", + "compactJson": "Compacta JSON", + "copyClipboard": "Copia al porta-retalls", + "copyJson": "Copia JSON", + "duplicate": "Duplica", + "editDoc": "Edita el document", + "deleteDoc": "Elimina el document", + "documentLabel": "Document {n}", + "editDialogTitle": "Edita el document", + "insertDialogTitle": "Insereix document", + "viewValueTitle": "Mostra el valor", + "saveChanges": "Desa els canvis", + "close": "Tanca", + "cancel": "Cancel·la", + "insertButton": "Insereix", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Filtre actiu aplicat", + "filterActiveTooltip": "Filtre actiu", + "closeAll": "Tanca-ho tot" + }, + "query": { + "migrateHistory": "Historial de consultes migrat al núvol", + "saveHistoryFail": "No s'ha pogut desar l'historial de consultes", + "deleteQueryFail": "No s'ha pogut eliminar la consulta", + "invalidJsonQuery": "Consulta JSON no vàlida", + "placeholder": "p. ex. { \"status\": \"active\" }", + "clearQuery": "Neteja la consulta", + "openAdvanced": "Obre l'editor avançat", + "visualBuilder": "Constructor visual de consultes", + "filterRules": "Regles de filtre", + "addRule": "Afegeix regla", + "noFilters": "No hi ha filtres. Afegeix una regla per filtrar documents.", + "where": "ON", + "and": "I", + "fieldPlaceholder": "Camp", + "opPlaceholder": "Op", + "typePlaceholder": "Tipus", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Not In", + "opExists": "Exists", + "typeAuto": "Auto", + "typeString": "Cadena", + "typeNumber": "Nombre", + "typeBoolean": "Booleà", + "typeDate": "Data", + "typeObjectId": "ObjectId", + "true": "Cert", + "false": "Fals", + "valuePlaceholder": "Valor", + "valueListPlaceholder": "valor1, valor2", + "clearFilters": "Neteja els filtres", + "applyFilters": "Aplica els filtres", + "queryHistory": "Historial de consultes", + "noSavedQueries": "Encara no hi ha consultes desades", + "advancedEditorTitle": "Editor avançat de consultes", + "templatesPlaceholder": "Plantilles", + "templateEmptyAgg": "Agregació buida", + "templateMatch": "$match (Filtre)", + "templateLookup": "$lookup (Unió)", + "templateGroup": "$group (Comptar/Suma)", + "format": "Formata", + "formattedJson": "JSON formatat", + "runQuery": "Executa la consulta" + }, + "export": { + "selectFieldsError": "Selecciona com a mínim un camp per exportar", + "success": "Exportat correctament a {filename}", + "fail": "No s'han pogut exportar les dades", + "title": "Exporta dades", + "formatLabel": "Format d'exportació", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Selecciona camps", + "selectAll": "Selecciona-ho tot", + "fieldsSelected": "{count, plural, one {# camp seleccionat} other {# camps seleccionats}}", + "cancel": "Cancel·la", + "export": "Exporta", + "sheetName": "Dades" + }, + "jsonTree": { + "typeLabel": "Tipus: {type}", + "itemSummary": "{count, plural, one {# element} other {# elements}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Petició nova", + "importedRequest": "Petició importada", + "untitledRequest": "Petició sense títol" + }, + "toasts": { + "curlImported": "cURL importat correctament", + "curlParseFailed": "No s'ha pogut analitzar l'ordre cURL", + "invalidJsonBody": "Cos JSON no vàlid", + "requestFailed": "Petició fallida: {message}", + "curlPasted": "cURL enganxat i analitzat correctament", + "responseCopied": "Resposta copiada al porta-retalls", + "codeCopied": "Codi copiat al porta-retalls", + "copyFailed": "No s'ha pogut copiar al porta-retalls" + }, + "layout": { + "collections": "Col·leccions", + "closeSidebar": "Tanca la barra lateral", + "openSidebar": "Obre la barra lateral", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Mètode", + "urlPlaceholder": "https://api.exemple.com/v1/...", + "sending": "S'està enviant...", + "send": "Envia", + "invalidJsonBodyHelp": "No es pot enviar: el cos JSON no és vàlid" + }, + "requestTabs": { + "params": "Params", + "headers": "Capçaleres", + "body": "Cos", + "auth": "Autenticació", + "bodyType": "Tipus de cos", + "selectType": "Selecciona el tipus", + "bodyNone": "Cap", + "bodyJson": "JSON", + "bodyText": "Text", + "noBodyHint": "Aquesta petició no té cos", + "switchToJson": "Canvia a cos JSON", + "authType": "Tipus d'autenticació", + "noAuth": "Sense autenticació", + "bearerToken": "Token Bearer", + "basicAuth": "Autenticació bàsica", + "apiKey": "Clau API", + "labelToken": "Token", + "placeholderBearerToken": "Token Bearer", + "labelUsername": "Usuari", + "placeholderUsername": "Usuari", + "labelPassword": "Contrasenya", + "placeholderPassword": "Contrasenya", + "labelKey": "Clau", + "placeholderApiKeyName": "Nom de la clau (p. ex. X-Api-Key)", + "labelValue": "Valor", + "placeholderValue": "Valor", + "addTo": "Afegeix a", + "header": "Capçalera", + "queryParams": "Paràmetres de consulta", + "noAuthHint": "Aquesta petició no usa autenticació." + }, + "responsePanel": { + "emptyTitle": "A punt per explorar?", + "emptyHint": "Introdueix una URL i fes clic a Envia per veure la resposta aquí.", + "responsePreviewAlt": "Vista prèvia de la resposta", + "previewUnavailable": "Vista prèvia no disponible per a aquest tipus de contingut", + "status": "Estat", + "time": "Temps", + "size": "Mida", + "copyBody": "Copia el cos", + "downloadResponse": "Baixa la resposta", + "bodyTab": "Cos", + "previewTab": "Vista prèvia", + "headersTab": "Capçaleres", + "binaryRawView": "Les dades binàries no es poden mostrar en vista en brut.", + "errorStatusLabel": "Error", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Pestanya nova" + }, + "collectionsSidebar": { + "title": "Recursos API", + "newCollection": "Col·lecció nova", + "tabCollections": "Col·leccions", + "tabHistory": "Historial", + "noCollectionsTitle": "Sense col·leccions", + "noCollectionsHint": "Crea una col·lecció per organitzar les peticions API.", + "createCollection": "Crea col·lecció", + "newFolder": "Carpeta nova", + "rename": "Reanomena", + "delete": "Elimina", + "emptyCollection": "Col·lecció buida", + "recentRequests": "Peticions recents", + "clearAll": "Neteja-ho tot", + "noHistoryTitle": "Encara no hi ha historial", + "noHistoryHint": "Envia una petició per veure-la aquí.", + "dialogNewFolderTitle": "Carpeta nova", + "dialogNewFolderDescription": "Crea una carpeta nova per organitzar les peticions.", + "labelFolderName": "Nom de la carpeta", + "placeholderFolderName": "La meva carpeta", + "createFolder": "Crea la carpeta", + "dialogNewCollectionTitle": "Col·lecció nova", + "dialogNewCollectionDescription": "Crea una col·lecció de nivell superior.", + "labelCollectionName": "Nom de la col·lecció", + "placeholderCollectionName": "La meva col·lecció", + "dialogRenameCollectionTitle": "Reanomena la col·lecció", + "dialogRenameCollectionDescription": "Introdueix un nom nou per a la col·lecció.", + "placeholderRenameCollection": "Nom de la col·lecció", + "renameAction": "Reanomena", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Carpeta nova", + "delete": "Elimina", + "emptyFolder": "Buida la carpeta" + }, + "environmentManager": { + "noEnvironment": "Sense entorn", + "manageDialogTitle": "Gestiona entorns", + "placeholderNewEnv": "Entorn nou", + "variablesHeading": "Variables de {name}", + "addVariable": "Afegeix variable", + "columnVariable": "Variable", + "columnValue": "Valor", + "placeholderKey": "Clau", + "placeholderValue": "Valor", + "selectEnvToEdit": "Selecciona un entorn per editar" + }, + "codeGenerator": { + "triggerTitle": "Genera codi", + "dialogTitle": "Genera codi", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Copia", + "errorGenerating": "Error en generar el codi" + }, + "keyValueEditor": { + "columnKey": "Clau", + "columnValue": "Valor", + "placeholderKey": "Clau", + "placeholderValue": "Valor", + "addItem": "Afegeix element" + }, + "importCurl": { + "trigger": "Importa cURL", + "dialogTitle": "Importa cURL", + "dialogDescription": "Enganxa l'ordre cURL a continuació per importar-la com a petició nova.", + "placeholder": "curl -X POST https://api.exemple.com/data -d '...'", + "import": "Importa" + }, + "saveRequest": { + "trigger": "Desa", + "dialogTitle": "Desa la petició", + "dialogDescription": "Desa aquesta petició a una carpeta de la col·lecció.", + "labelName": "Nom de la petició", + "placeholderName": "La meva petició", + "labelFolder": "Carpeta", + "placeholderFolder": "Selecciona una carpeta", + "save": "Desa" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Mode edició", + "viewOnlyMode": "Només lectura", + "insertComponent": "Insereix component", + "addUnorderedList": "Afegeix llista sense ordre", + "addOrderedList": "Afegeix llista ordenada", + "addTable": "Afegeix taula" + }, + "addBlock": "Afegeix bloc", + "media": { + "popoverTitle": "Afegeix multimèdia", + "singleImage": "Imatge única", + "singleImageDesc": "Puja una imatge", + "multipleImages": "Diverses imatges", + "multipleImagesDesc": "Puja graella d'imatges", + "video": "Vídeo", + "videoDesc": "Puja un fitxer de vídeo", + "uploadingAlt": "S'està pujant...", + "uploadFailedAlt": "La pujada ha fallat" + }, + "export": { + "fabAria": "Exporta codi", + "title": "Exporta codi", + "description": "Copia la sortida HTML o JSON del contingut de l'editor", + "preview": "Vista prèvia", + "html": "HTML", + "json": "JSON", + "previewOptions": "Opcions de vista prèvia", + "enhanceSpaces": "Millora els espais", + "livePreview": "Vista prèvia en viu de l'HTML renderitzat", + "htmlTailwind": "HTML amb classes Tailwind CSS", + "editorJson": "Estat de l'editor com a JSON", + "copyHtml": "Copia HTML", + "copyJson": "Copia JSON", + "copied": "Copiat!" + }, + "template": { + "fabTitle": "Canvia plantilla", + "title": "Trieu una plantilla", + "description": "Selecciona una plantilla per començar ràpidament amb dissenys i contingut predefinits", + "catAll": "Totes les plantilles", + "catProductivity": "Productivitat", + "catCreative": "Creatiu", + "catBusiness": "Negocis", + "catPersonal": "Personal", + "noTemplates": "No hi ha plantilles en aquesta categoria", + "confirmTitle": "Substituir el contingut existent?", + "confirmDescription": "Ja hi ha contingut a l'editor. Aplicar aquesta plantilla substituirà tot el contingut actual.", + "cannotUndo": "Aquesta acció no es pot desfer.", + "cancel": "Cancel·la", + "replaceContent": "Substitueix el contingut" + }, + "command": { + "searchPlaceholder": "Cerca ordres...", + "noResults": "No s'han trobat ordres.", + "turnInto": "Converteix en", + "h1": { + "label": "Títol 1", + "description": "Títol de secció gran", + "keywords": "h1,titol,heading,gran" + }, + "h2": { + "label": "Títol 2", + "description": "Títol de secció mitjà", + "keywords": "h2,titol,subtitol" + }, + "h3": { + "label": "Títol 3", + "description": "Títol de secció petit", + "keywords": "h3,titol,subtitol" + }, + "h4": { + "label": "Títol 4", + "description": "Títol molt petit", + "keywords": "h4,titol" + }, + "h5": { + "label": "Títol 5", + "description": "Títol més petit", + "keywords": "h5,titol" + }, + "h6": { + "label": "Títol 6", + "description": "El títol més petit", + "keywords": "h6,titol" + }, + "p": { + "label": "Paràgraf", + "description": "Paràgraf de text normal", + "keywords": "p,paragraf,text,normal" + }, + "code": { + "label": "Bloc de codi", + "description": "Fragment de codi", + "keywords": "code,codi,snippet,pre" + }, + "blockquote": { + "label": "Cita", + "description": "Cita en bloc", + "keywords": "cita,blockquote" + }, + "li": { + "label": "Llista amb pics", + "description": "Element de llista amb pic", + "keywords": "llista,pic,ul,li" + }, + "ol": { + "label": "Llista numerada", + "description": "Element de llista numerada", + "keywords": "llista,numerada,ol,li" + }, + "img": { + "label": "Imatge", + "description": "Puja o incrusta una imatge", + "keywords": "imatge,img,foto,pujar" + }, + "video": { + "label": "Vídeo", + "description": "Puja o incrusta un vídeo", + "keywords": "video,vid,mp4,pujar" + }, + "table": { + "label": "Taula", + "description": "Crea una taula", + "keywords": "taula,files,cel·les" + } + }, + "toasts": { + "tableImported": "Taula importada", + "tableImportedDesc": "La taula Markdown s'ha importat correctament", + "mediaPasted": "Multimèdia enganxada", + "pasteMixed": "S'han enganxat {imageCount} imatge(s) i {videoCount} vídeo(s) correctament.", + "pasteImages": "S'han enganxat {count} imatge(s) correctament.", + "pasteVideos": "S'han enganxat {count} vídeo(s) correctament.", + "pasteFailed": "L'enganxat ha fallat", + "unexpectedError": "S'ha produït un error inesperat", + "uploadFailed": "La pujada ha fallat" + }, + "blockBackground": { + "menuItem": "Canvia el color de fons", + "dialogTitle": "Color de fons del bloc", + "dialogDescription": "Trieu un color de fons per a aquest bloc", + "tabPreset": "Colors predefinits", + "tabCustom": "Color personalitzat", + "sectionHeading": "Colors de fons", + "apply": "Aplica", + "colors": { + "none": "Cap", + "red": "Vermell", + "orange": "Taronja", + "yellow": "Groc", + "green": "Verd", + "blue": "Blau", + "indigo": "Indi", + "purple": "Porpra", + "pink": "Rosa", + "teal": "Turquesa fosc", + "cyan": "Cian", + "gray": "Gris" + } + }, + "tableDialog": { + "title": "Crea taula", + "description": "Crea una taula nova o importa des de markdown", + "markdownCheckbox": "Tinc una taula en markdown", + "pasteMarkdownLabel": "Enganxa la taula Markdown", + "markdownPlaceholder": "| Capçalera 1 | Capçalera 2 | Capçalera 3 |\n|----------|----------|----------|\n| Cel·la 1 | Cel·la 2 | Cel·la 3 |\n| Cel·la 4 | Cel·la 5 | Cel·la 6 |", + "markdownHint": "Enganxa la taula markdown a dalt. Ha d'incloure fila de capçalera i separador.", + "rows": "Files", + "columns": "Columnes", + "maxDimensions": "Màxim: 20 files × 10 columnes", + "cancel": "Cancel·la", + "importTable": "Importa taula", + "createTable": "Crea taula" + }, + "tableParse": { + "errors": { + "minRows": "La taula ha de tenir com a mínim fila de capçalera i fila separadora", + "pipeFormat": "Les files han de començar i acabar amb |", + "headerMinCol": "La fila de capçalera ha de tenir com a mínim una columna", + "separatorRow": "La segona fila ha de ser un separador (p. ex. |---|---|)", + "rowColumnMismatch": "La fila {row} té {got} columnes, s'esperaven {expected}", + "parseFailed": "No s'ha pogut analitzar la taula markdown" + } + }, + "link": { + "editLink": "Edita l'enllaç", + "addLink": "Afegeix enllaç", + "selectedText": "Text seleccionat: «{text}»", + "linkUrl": "URL de l'enllaç", + "urlPlaceholder": "https://exemple.com", + "update": "Actualitza", + "addLinkButton": "Afegeix enllaç", + "toastApplied": "Enllaç aplicat", + "toastLinkedTo": "Enllaçat a: {url}", + "toastRemoved": "Enllaç eliminat", + "toastRemovedDesc": "S'ha eliminat l'enllaç de la selecció" + }, + "customClass": { + "devMode": "Mode desenvolupador", + "toggleDevModeAria": "Commuta el mode desenvolupador", + "searchPlaceholderDev": "Cerca classes... (p. ex. 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Cerca estils... (p. ex. 'vermell', 'negreta', 'ombra')", + "appliesTitle": "S'aplica: {className}", + "noMatches": "No s'han trobat classes que coincideixin amb «{query}»", + "toastClassReplaced": "Classe substituïda", + "toastClassReplacedDesc": "S'ha substituït «{replaced}» per «{className}»", + "toastCustomClassApplied": "Classe personalitzada aplicada", + "toastAppliedClass": "Classe aplicada: {className}" + }, + "insertComponents": { + "title": "Insereix component", + "description": "Trieu un component per inserir al document", + "noComponents": "No hi ha components disponibles", + "categories": { + "media": "Multimèdia", + "layout": "Disseny", + "interactive": "Interactiu", + "decoration": "Decoració" + }, + "freeImage": { + "name": "Imatge lliure", + "description": "Afegeix una imatge que es pot col·locar i redimensionar lliurement" + } + } + }, + "JsonFormatter": { + "title": "Formatador JSON", + "description": "Formata, valida i edita JSON en vista de text i arbre.", + "documentNameText": "Document de text {n}", + "documentNameTree": "Document en arbre {n}", + "newDocument": "Nou", + "copy": "Copiar", + "save": "Desar", + "load": "Carregar", + "saving": "Desant...", + "leftDocNameLabel": "Nom del document esquerre", + "rightDocNameLabel": "Nom del document dret", + "toastCopiedText": "Panell de text copiat", + "toastCopiedTree": "Panell d'arbre copiat", + "toastCopyFailed": "No s'ha pogut copiar el JSON", + "toastLoginRequired": "Inicia sessió per desar documents", + "toastSaved": "Document desat", + "toastSaveFailed": "No s'ha pogut desar el document", + "format": "Formata", + "compact": "Compacta", + "sortKeys": "Ordena les claus", + "repair": "Repara", + "query": "Consulta", + "toastInvalidJson": "JSON no vàlid", + "toastAlreadyValid": "El JSON ja és vàlid", + "toastRepaired": "JSON reparat", + "toastRepairFailed": "No s'ha pogut reparar el JSON" + }, + "JwtDecoder": { + "title": "Descodificador JWT", + "subtitle": "Inspecciona la capçalera i el payload. La caducitat usa la reclamació exp. No s’envia res a un servidor; la signatura no es verifica.", + "tokenLabel": "Token", + "clear": "Netejar", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Copia", + "expiryLabel": "Caducitat (exp)", + "signaturePresent": "Signatura present (no verificada)", + "noExp": "No hi ha la reclamació exp al payload.", + "status": { + "expired": "Caducat", + "valid": "Vàlid" + }, + "fields": { + "unix": "Unix", + "local": "Local", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Emès a (iat)", + "notBefore": "No abans de (nbf)" + }, + "panels": { + "header": "Capçalera", + "payload": "Payload" + }, + "errors": { + "empty": "Enganxa un JWT per descodificar.", + "segments": "Un JWT necessita com a mínim capçalera i payload (dos segments).", + "base64": "No s’ha pogut descodificar en base64url la capçalera o el payload." + }, + "relative": { + "future": "d’aquí a {n} {unit}", + "past": "fa {n} {unit}" + }, + "units": { + "second": "{count, plural, one {segon} other {segons}}", + "minute": "{count, plural, one {minut} other {minuts}}", + "hour": "{count, plural, one {hora} other {hores}}", + "day": "{count, plural, one {dia} other {dies}}" + }, + "tabs": { + "decode": "Decodifica", + "sign": "Generador / signador" + }, + "signer": { + "algLabel": "Algorisme (alg)", + "tabs": { + "payload": "Càrrega", + "header": "Capçalera" + }, + "payloadLabel": "JSON de la càrrega", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON de la capçalera", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Secret (HS*)", + "secretPlaceholder": "Enganxa el teu secret HMAC…", + "privateKeyLabel": "Clau privada PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "L’algorisme \"none\" crea un token sense signar. És insegur i només s’ha d’usar per a proves.", + "sign": "Signa", + "verify": "Verifica la signatura", + "outputLabel": "Token (JWS compacte)", + "outputPlaceholder": "Fes clic a \"Signa\"…", + "copyToken": "Copia el token", + "copied": "Copiat", + "verifyKeyLabel": "Clau de verificació (opcional)", + "verifyKeyPlaceholder": "Per a RS*: enganxa la clau pública (SPKI) PEM. Per a HS*: deixa-ho buit per reutilitzar el secret.", + "verifyKeyHint": "Si és buit, la verificació usa la clau de signatura (útil per a HS*).", + "verifyStatus": { + "valid": "Signatura vàlida", + "invalid": "Signatura invàlida" + }, + "securityNote": "Tot s’executa localment al navegador; no es pugen claus ni tokens." + }, + "signErrors": { + "invalidJson": "La capçalera i la càrrega han de ser JSON vàlid.", + "unsupportedAlg": "Algorisme no compatible.", + "missingKey": "Cal una clau de signatura per a aquest algorisme.", + "cryptoFailed": "Ha fallat la signatura. Comprova el format de la clau i torna-ho a provar." + }, + "verifyErrors": { + "empty": "Introdueix un JWT per verificar.", + "segments": "Un JWT ha de tenir exactament 3 segments per verificar una signatura.", + "unsupportedAlg": "Algorisme no compatible o falta alg a la capçalera.", + "missingKey": "Cal una clau de verificació per a aquest algorisme.", + "base64": "No s’ha pogut decodificar la capçalera o la signatura del JWT.", + "cryptoFailed": "Ha fallat la verificació. Comprova el format de la clau i torna-ho a provar." + } + }, + "CertificatePemDecoder": { + "title": "Descodificador de certificats / PEM", + "subtitle": "Enganxa un certificat X.509 (-----BEGIN CERTIFICATE-----) o una CSR (-----BEGIN CERTIFICATE REQUEST-----). Subjecte, emissor, dates i SAN s'analitzen localment; no es puja res.", + "pemLabel": "Entrada PEM", + "clear": "Netejar", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "S'està analitzant…", + "copyHex": "Copia l'empremta (hex sense dos punts)", + "errors": { + "empty": "Enganxa un certificat PEM o una CSR per descodificar.", + "noBlock": "No s'ha trobat cap bloc PEM de certificat o CSR.", + "parseFailed": "No s'ha pogut analitzar aquestes dades PEM." + }, + "block": { + "title": "Bloc PEM {index}", + "certificate": "Certificat X.509", + "csr": "Sol·licitud de signatura de certificat" + }, + "fields": { + "subject": "Subjecte", + "issuer": "Emissor", + "notBefore": "No vàlid abans de", + "notAfter": "No vàlid després de", + "serial": "Número de sèrie (hex)", + "sha256": "Empremta SHA-256", + "sans": "Noms alternatius del subjecte", + "signatureAlgorithm": "Algorisme de signatura", + "csrIssuerNote": "Les CSR no inclouen emissor (apareix al certificat emès).", + "csrValidityNote": "Les CSR no inclouen dates de validesa (apareixen al certificat emès).", + "sansNone": "Sense extensió SAN." + }, + "sanType": { + "dns": "DNS", + "dn": "Nom de directori", + "email": "Correu", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID registrat", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Formatador SQL", + "subtitle": "Dona format a SQL per MySQL, PostgreSQL o SQLite. S’executa al navegador; no es puja res.", + "dialectLabel": "Dialecte", + "keywordsLabel": "Paraules clau", + "indentLabel": "Sagnat", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "MAJÚSCULES", + "lower": "minúscules", + "preserve": "Conserva" + }, + "spaces": "{n} espais", + "format": "Formata", + "clear": "Neteja", + "counter": "{current} / {max} caràcters", + "inputPanel": "Entrada", + "formattedPanel": "Formatat", + "copy": "Copia", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Fes clic a \"Formata\"…", + "errors": { + "tooLong": "L’entrada està limitada a {max} caràcters.", + "couldNotFormat": "No s’ha pogut formatar aquest SQL." + } + }, + "YamlFormatter": { + "title": "Formatador YAML", + "subtitle": "Formata, valida i converteix YAML al teu navegador. Res no es puja.", + "outputLabel": "Sortida", + "outputModes": { + "yaml": "YAML (formatat)", + "json": "JSON" + }, + "indentLabel": "Sagnat", + "spaces": "{n} espais", + "format": "Formata", + "clear": "Esborra", + "copy": "Copia", + "counter": "{current} / {max} caràcters", + "inputPanel": "Entrada", + "outputPanel": "Sortida", + "errors": { + "tooLong": "L'entrada està limitada a {max} caràcters.", + "couldNotFormat": "No s'ha pogut analitzar aquest YAML." + } + }, + "GraphqlFormatter": { + "title": "Formatador GraphQL", + "subtitle": "Formata consultes, mutacions i subscripcions GraphQL al navegador. No es puja res.", + "tabs": { + "format": "Formata", + "build": "Editor de consulta" + }, + "outputModeLabel": "Sortida", + "outputModes": { + "pretty": "Impressió llegible", + "minify": "Minifica" + }, + "format": "Formata", + "clear": "Esborra", + "counter": "{current} / {max} caràcters", + "inputPanel": "Entrada", + "outputPanel": "Sortida", + "copy": "Copia", + "builderHint": "Compon un esquelet i aplica’l a l’editor. Les definicions de variables usen sintaxi GraphQL (p. ex. $id: ID!) — els parèntesis són opcionals.", + "builder": { + "operation": "Operació", + "operationName": "Nom de l’operació", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definicions de variables", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Conjunt de selecció", + "apply": "Aplica a l’entrada", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "L’entrada està limitada a {max} caràcters.", + "couldNotFormat": "No s’ha pogut analitzar ni imprimir aquest GraphQL.", + "emptySelection": "Afegeix un conjunt de selecció abans d’aplicar." + } + }, + "RegexTester": { + "title": "Testador de regex", + "subtitle": "Regex d’estil JavaScript en directe sobre el teu text. Les coincidències es ressalten; res no surt del navegador.", + "patternLabel": "Patró", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i ignora maj./min.", + "m": "m multilínia", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(buit)", + "noFlags": "—", + "matches": "{count, plural, one {coincidència} other {coincidències}}", + "testTextLabel": "Text de prova", + "counter": "{current} / {max}", + "clear": "Neteja", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "El text està limitat a {max} caràcters.", + "invalid": "Expressió regular no vàlida.", + "runaway": "Aturat després de {max} coincidències (possible patró descontrolat)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Convertidor de marques de temps", + "subtitle": "Segons Unix (≤10 dígits) o mil·lisegons (enters més llargs), ISO-8601 o cadenes que el navegador pugui interpretar. Tot s’executa localment.", + "inputLabel": "Entrada", + "placeholder": "1744200000 o 2025-04-09T12:00:00Z", + "now": "Ara", + "copy": "Copia", + "hint": "Enganxa un valor o fes clic a Ara.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relatiu", + "human": "Llegible" + }, + "fields": { + "seconds": "Segons", + "milliseconds": "Mil·lisegons", + "utc": "UTC", + "local": "Local", + "distance": "Distància" + }, + "errors": { + "empty": "Introdueix un timestamp Unix, una cadena ISO o un text de data.", + "invalidNumber": "Timestamp numèric no vàlid.", + "outOfRange": "Fora de rang per a JavaScript Date.", + "couldNotParse": "No s’ha pogut interpretar com Unix, ISO-8601 o data del navegador." + } + }, + "HttpStatusCodes": { + "title": "Codis d’estat HTTP", + "subtitle": "Cerca i explora codis d’estat de respostes HTTP (1xx5xx) amb descripcions breus i referències RFC. S’executa localment.", + "header": { + "title": "Codis d’estat HTTP", + "description": "Referència amb cerca de codis de resposta HTTP amb descripcions i referències RFC." + }, + "searchPlaceholder": "Cerca: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtra classe…", + "classFilterAll": "Totes les classes", + "resultsCount": "{count} de {total}", + "table": { + "code": "Code", + "phrase": "Frase", + "description": "Description", + "copy": "Copia" + }, + "noResults": "Sense resultats per a “{query}”.", + "specPrefix": "Espec.:", + "copied": "Codi copiat" + }, + "CronBuilder": { + "title": "Constructor d’expressions Cron", + "subtitle": "Cron estàndard de 5 camps: minute hour day month weekday. El parser usa seconds=0 internament. El format opcional de 6 camps (sec min hour dom mon dow) funciona a l’editor.", + "tabs": { + "builder": "Constructor visual", + "expression": "Expressió" + }, + "presetsLabel": "Predefinits", + "fieldsLabel": "Camps", + "rawExpressionLabel": "Expressió en brut", + "expressionHint": "Surt del camp per analitzar i sincronitzar el constructor. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Expressió actual", + "copy": "Copia", + "quickPick": "Tria ràpida", + "emptyExpression": "—", + "plainLanguageLabel": "Llenguatge natural", + "nextRunsLabel": "Properes execucions (hora local)", + "fields": { + "minute": "Minut", + "hour": "Hora", + "dayOfMonth": "Dia del mes", + "month": "Mes", + "dayOfWeek": "Dia de la setmana" + }, + "presets": { + "everyMinute": "Cada minut", + "every5Minutes": "Cada 5 minuts", + "every15Minutes": "Cada 15 minuts", + "everyHour": "Cada hora (a :00)", + "every6Hours": "Cada 6 hores", + "dailyMidnight": "Diari a mitjanit", + "daily0900": "Diari a les 09:00", + "weekdays0900": "Feiners a les 09:00", + "weeklySunday": "Setmanal (diumenge 00:00)", + "monthlyFirst": "Mensual (dia 1 00:00)", + "yearlyJan1": "Anual (1 gen 00:00)" + }, + "picks": { + "minute": { + "every": "cada minut" + }, + "hour": { + "every": "cada hora" + }, + "dom": { + "any": "qualsevol dia", + "last": "últim dia" + }, + "month": { + "every": "cada mes" + }, + "dow": { + "any": "qualsevol dia", + "weekdays": "feiners", + "weekend": "cap de setmana" + } + }, + "months": { + "jan": "Gen", + "feb": "Feb", + "mar": "Mar", + "apr": "Abr", + "may": "Mai", + "jun": "Jun", + "jul": "Jul", + "aug": "Ago", + "sep": "Set", + "oct": "Oct", + "nov": "Nov", + "dec": "Des" + }, + "weekdays": { + "sun": "Dg", + "mon": "Dl", + "tue": "Dt", + "wed": "Dc", + "thu": "Dj", + "fri": "Dv", + "sat": "Ds" + }, + "errors": { + "fieldCount": "S’esperaven 5 camps (minute hour day month weekday) o 6 incloent segons.", + "invalid": "Expressió cron no vàlida." + } + }, + "Base64": { + "title": "Codificador / descodificador Base64", + "subtitle": "Codifica text a Base64 o descodifica cadenes Base64", + "upload": "Puja", + "clear": "Neteja", + "modes": { + "encode": "Codifica", + "decode": "Descodifica" + }, + "swapTitle": "Intercanvia entrada i sortida", + "panels": { + "plainText": "Text pla", + "base64Input": "Entrada Base64", + "base64Output": "Sortida Base64", + "decodedText": "Text descodificat" + }, + "placeholders": { + "encode": "Escriu text per codificar a Base64…", + "decode": "Enganxa una cadena Base64 per descodificar…" + }, + "dropHere": "Deixa el fitxer aquí", + "charCount": "{count} caràcters", + "download": { + "title": "Descarrega la sortida", + "encodedFilename": "codificat.b64", + "decodedFilename": "descodificat.txt" + }, + "copyTitle": "Copia al porta-retalls", + "outputPlaceholder": "La sortida apareixerà aquí…", + "errors": { + "invalidBase64": "Cadena Base64 no vàlida. Revisa l’entrada.", + "encodeFailed": "No s’ha pogut codificar l’entrada." + } + }, + "UrlEncode": { + "title": "Codificador / descodificador d’URL", + "subtitle": "Codifica/descodifica en percentatge amb encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Puja", + "clear": "Neteja", + "modes": { + "encode": "Codifica", + "decode": "Descodifica" + }, + "swapTitle": "Intercanvia entrada i sortida", + "panels": { + "plainText": "Text pla", + "encodedInput": "Entrada codificada", + "encodedOutput": "Sortida codificada", + "decodedText": "Text descodificat" + }, + "placeholders": { + "encode": "Introdueix text per codificar en percentatge…", + "decode": "Enganxa text percent-encoded per descodificar…" + }, + "dropHere": "Deixa el fitxer aquí", + "charCount": "{count} caràcters", + "download": { + "title": "Baixa la sortida", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Copia al porta-retalls", + "outputPlaceholder": "La sortida apareixerà aquí…", + "errors": { + "invalidEncoding": "Codificació de percentatge no vàlida. Revisa parells % incomplets o seqüències d’escapament.", + "encodeFailed": "No s’ha pogut codificar (Unicode no vàlid a l’entrada)." + } + }, + "UrlParser": { + "title": "Analitzador d’URL", + "subtitle": "Desglossa un URL en protocol, amfitrió, camí, paràmetres de consulta i hash (fragment).", + "upload": "Puja", + "clear": "Neteja", + "panels": { + "input": "Entrada d’URL", + "output": "Resultat" + }, + "placeholders": { + "input": "Enganxa un URL (p. ex. https://example.com/path?foo=bar#section) o amfitrió/camí (p. ex. example.com/path)…" + }, + "dropHere": "Deixa el fitxer aquí", + "charCount": "{count} caràcters", + "outputPlaceholder": "Els components analitzats apareixeran aquí…", + "fields": { + "protocol": "Protocol", + "host": "Amfitrió", + "path": "Camí", + "hash": "Hash", + "queryParams": "Paràmetres de consulta" + }, + "paramCount": "{count} paràmetres", + "noParams": "Sense paràmetres de consulta.", + "paramKey": "Clau", + "paramValue": "Valor", + "copyTitle": "Copia al porta-retalls", + "copyParamTitle": "Copia com a key=value", + "download": { + "title": "Baixa el JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL no vàlid. Inclou un nom d’amfitrió (example.com) i revisa els caràcters." + } + }, + "UuidGenerator": { + "title": "Generador UUID / ULID", + "subtitle": "Genera UUID (RFC 9562) o ULID al navegador. Exportació en bloc fins a {max} per execució.", + "format": "Format", + "kindHints": { + "ulid": "Identificador de 26 caràcters ordenable i segur per a URL", + "uuid4": "Aleatori (RFC 9562)", + "uuid7": "Ordenat en el temps, ms Unix + aleatori", + "uuid1": "Basat en MAC/temps (RFC 9562)", + "uuid6": "Temps gregorià reordenat", + "uuid3": "Hash MD5 de nom + espai de noms", + "uuid5": "Hash SHA‑1 de nom + espai de noms", + "nil": "UUID nul (tot zeros, RFC 9562)" + }, + "countLabel": "Quantitat (1–{max})", + "nameLabel": "Nom (cadena d’entrada)", + "namePlaceholder": "p. ex. my.app/resource", + "bulkHint": "En lots s’afegeix #0, #1, … perquè cada ID sigui diferent (v3/v5 són deterministes).", + "namespace": "Espai de noms", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID personalitzat" + }, + "customNamespaceLabel": "UUID d’espai de noms", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Genera", + "output": "Sortida", + "lines": "{count} línies", + "download": { + "title": "Descarrega .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Copia-ho tot", + "uppercaseToggle": "Sortida en majúscules", + "hyphensToggle": "Mostra guionets", + "copyJsonTitle": "Copia com a matriu JSON", + "outputPlaceholder": "Feu clic a \"Genera\"…", + "errors": { + "invalidCustomNamespace": "L’espai de noms personalitzat ha de ser un UUID vàlid.", + "missingName": "Cal un nom per a UUID v3 i v5.", + "unknown": "No s’han pogut generar els ID." + } + }, + "SecretApiKeyGenerator": { + "title": "Generador de secret / clau API", + "subtitle": "Genera cad aleatòries criptogràfiques amb longitud i alfabet configurables. Exportació massiva fins a {max} per execució. Complementa el generador UUID / ULID.", + "alphabetLabel": "Alfabet", + "alphabetHint": "Se suprimeixen els caràcters duplicats. Cada símbol es tria de manera uniforme amb crypto.getRandomValues i mostreig per rebuig (sense biaix de mòdul).", + "alphabetCount": "{count} símbols únics", + "presetsLabel": "Predefinits", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumèric", + "hex": "Hexadecimal (minúscules)", + "asciiPrintable": "ASCII imprimible" + }, + "lengthLabel": "Longitud per secret (1–{maxLen})", + "countLabel": "Recompte (1–{maxBulk})", + "generate": "Genera", + "output": "Sortida", + "lines": "{count} línies", + "download": { + "title": "Baixa .txt", + "filename": "secrets.txt" + }, + "copyTitle": "Copia-ho tot", + "outputPlaceholder": "Fes clic a \"Genera\"…", + "entropy": "{bits} bits d'entropia", + "strength": { + "weak": "Feble", + "fair": "Acceptable", + "strong": "Fort" + }, + "excludeAmbiguous": "Exclou els caràcters ambigus", + "excludeAmbiguousHint": "Elimina {chars} — caràcters que es llegeixen fàcilment malament o hostils per a l'intèrpret d'ordres.", + "prefixLabel": "Prefix de la clau (opcional)", + "errors": { + "emptyAlphabet": "Afegeix almenys un caràcter a l'alfabet.", + "alphabetTooLong": "Després d'eliminar duplicats, l'alfabet pot tenir com a màxim {maxAlpha} caràcters.", + "lengthOutOfRange": "La longitud ha d'estar entre 1 i {maxLen}.", + "countOutOfRange": "El recompte ha d'estar entre 1 i {maxBulk}.", + "unknown": "No s'han pogut generar els secrets." + } + }, + "LoremIpsum": { + "title": "Generador de Lorem Ipsum", + "subtitle": "Genereu text llatí de substitució per als vostres dissenys i maquetes.", + "paragraphs": "Paràgrafs", + "sentences": "Frases", + "words": "Paraules", + "count": "Nombre", + "generate": "Genera", + "copy": "Copia", + "startWith": "Comença amb 'Lorem ipsum...'", + "outputPlaceholder": "El resultat apareixerà aquí...", + "outputType": "Tipus de sortida", + "bulletList": "Llista de punts", + "htmlMarkup": "Marcatge HTML", + "htmlMarkupHint": "Embolica la sortida en paràgrafs, o utilitza una llista per al mode de punts", + "output": "Sortida", + "download": "Descarregar" + }, + "ColorPicker": { + "title": "Selector i convertidor de color", + "subtitle": "Converteix HEX, RGB i HSL; crea paletes harmòniques. Tot el processament es fa al teu navegador.", + "nativePicker": "Selector natiu", + "chooseColor": "Tria un color", + "eyeDropper": "Comptagotes", + "invalidHex": "Color HEX no vàlid.", + "cssValues": "Valors CSS", + "palettes": "Paletes", + "copied": "Copiat", + "clickToCopy": "Fes clic per copiar", + "copyAllHex": "Copia tots els HEX", + "copy": "Copia", + "copyColor": "Copia {color}", + "paletteShades": "Tons", + "paletteComplementary": "Complementari", + "paletteTriadic": "Triàdic", + "paletteAnalogous": "Anàleg", + "paletteSplit": "Comp. dividida", + "paletteSquare": "Quadrat" + }, + "ContrastChecker": { + "title": "Verificador de contrast", + "subtitle": "Calcula la ràtio de contrast WCAG 2.1 entre primer pla i fons. Comprova AA i AAA per a text normal i gran. Tot al navegador.", + "foreground": "Text / primer pla", + "background": "Fons", + "hexPlaceholder": "#RRGGBB", + "swap": "Intercanviar colors", + "chooseForeground": "Trieu el color de primer pla", + "chooseBackground": "Trieu el color de fons", + "eyeDropper": "Comptagotes", + "ratio": "Ràtio de contrast", + "copyRatio": "Copia la ràtio", + "copied": "Copiat", + "previewHeading": "Vista prèvia", + "previewBody": "Jove xef, porti whisky amb quinze glaçons d’hidrogen, coi! Aquest zebra al mig del fòrum es banyava en oli de codony.", + "previewCaption": "Text de mostra a mida per defecte.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — text normal (4,5:1)", + "levelAALarge": "AA — text gran (3:1)", + "levelAAANormal": "AAA — text normal (7:1)", + "levelAAALarge": "AAA — text gran (4,5:1)", + "pass": "Aprova", + "fail": "No aprova", + "invalidHex": "Color HEX no vàlid.", + "largeTextHint": "Text gran: 18 pt+ normal o 14 pt+ negreta (definicions WCAG)." + }, + "DiffChecker": { + "title": "Comparador de diferències", + "subtitle": "Comparació de línies entre dos textos costat a costat. Tot es queda al vostre navegador.", + "rowsCount": "{count} files", + "truncatedWarning": "Mostrant les primeres {max} files.", + "limitExceeded": "Cada costat està limitat a {max} caràcters.", + "clearBoth": "Esborrar tots dos", + "original": "Original", + "modified": "Modificat", + "originalPlaceholder": "Enganxeu el text original…", + "modifiedPlaceholder": "Enganxeu el text modificat…", + "comparisonLabel": "Comparació costat a costat", + "emptyState": "Introduïu text als dos panells per comparar." + }, + "JsonSchemaGenerator": { + "title": "Generador d'esquema JSON", + "subtitle": "Enganxa JSON vàlid per generar un esquema JSON o models tipats. Tot s'executa al navegador.", + "languageLabel": "Idioma de sortida", + "inputLabel": "Entrada JSON", + "inputPlaceholder": "Enganxa el JSON aquí…", + "outputLabel": "Sortida generada", + "outputPlaceholder": "L'esquema o els tipus generats apareixeran aquí…", + "parseError": "JSON no vàlid", + "clear": "Neteja", + "copy": "Copia", + "copied": "Copiat al porta-retalls", + "copyFailed": "No s'ha pogut copiar", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Esquema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "S'està carregant l'editor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Imatge a Base64", + "subtitle": "Deixeu anar una imatge, obteniu un URI de dades o una cadena Base64 en brut. Tot es queda al vostre navegador.", + "upload": "Carrega la imatge", + "clear": "Clar", + "modes": { + "dataUri": "URI de dades", + "rawString": "Base bruta 64" + }, + "panels": { + "imageInput": "Entrada d'imatge", + "output": "Sortida Base64" + }, + "dropHere": "Deixa la imatge aquí", + "charCount": "{count} caràcters", + "placeholders": { + "uploadImage": "Arrossegueu i deixeu anar una imatge o feu clic a carrega per començar." + }, + "fileTooLarge": "El fitxer és massa gran. El processament pot congelar el vostre navegador.", + "copyTitle": "Copia al porta-retalls", + "download": { + "title": "Descarregar text", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Compressor d’imatges", + "subtitle": "Redueix la sortida JPEG, PNG o WebP localment amb control de qualitat — res es puja al servidor.", + "upload": "Puja una imatge", + "clear": "Neteja", + "dropHere": "Deixa anar la imatge aquí", + "placeholders": { + "uploadImage": "Arrossega i deixa anar una imatge, o fes clic per triar un fitxer." + }, + "fileTooLarge": "El fitxer és massa gran (màx. {maxMb} MB).", + "unsupportedFormat": "Aquest tipus de fitxer no és compatible. Usa JPEG, PNG, WebP, GIF, AVIF o BMP.", + "compressFailed": "No s’ha pogut comprimir la imatge. Prova un altre fitxer o format.", + "panels": { + "original": "Original", + "compressed": "Vista prèvia comprimida" + }, + "outputFormat": "Format de sortida", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Qualitat", + "qualityPercent": "{value}%", + "qualityPngHint": "El PNG és sense pèrdua al navegador; el control de qualitat només afecta JPEG i WebP.", + "sizes": { + "original": "Original: {size}", + "compressed": "Comprimida: {size}", + "saved": "{percent}% més petita" + }, + "largerWarning": "La sortida és més gran que l’entrada — prova un altre format o qualitat.", + "download": "Baixa", + "downloadFilename": "imatge-comprimida", + "processing": "S’està comprimint…", + "emptyCompressed": "La vista prèvia comprimida apareix aquí." + }, + "CssGradientBuilder": { + "title": "Creador de degradats CSS", + "subtitle": "Creador de degradat CSS visual amb control d'angle, parades de color i còpia CSS d'un sol clic.", + "preview": "Vista prèvia del degradat", + "settings": "Configuració", + "type": "Tipus", + "linear": "Lineal", + "radial": "Radial", + "angle": "Angle", + "stops": "Color Stops", + "color": "Color", + "position": "Posició (%)", + "addStop": "Afegeix Stop", + "deleteStop": "Elimina Stop", + "output": "Sortida CSS", + "copy": "Copia CSS", + "copied": "Copiat!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Generador .gitignore", + "subtitle": "Genereu fitxers .gitignore combinats a l'instant per a una pila o combinació tecnològica específica.", + "searchPlaceholder": "Cerca piles (per exemple, Node, Python, macOS)...", + "noResults": "No s'han trobat piles.", + "selectedStacks": "Piles seleccionades", + "output": "Vista prèvia de la sortida", + "copy": "Copia al porta-retalls", + "copied": "Copiat!", + "download": "Descarrega .gitignore", + "downloading": "S'està baixant...", + "errorFetching": "No s'ha pogut obtenir la llista de primers llocs.", + "errorGenerating": "No s'ha pogut generar .gitignore a partir de les piles seleccionades.", + "generating": "Generant...", + "emptyState": "Seleccioneu una o més piles de tecnologia per generar el vostre .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Puja CSV o Excel, edita el JSON i descarrega CSV o XLSX. Només dades tabulars; les dates d’Excel passen a ISO al JSON.", + "dropTitle": "Deixa anar un CSV o Excel", + "dropHint": "Per a .xlsx / .xls s’usa el primer full. El CSV ha de tenir fila de capçalera.", + "chooseFile": "Trieu un fitxer", + "jsonLabel": "JSON (matriu d’objectes)", + "jsonPlaceholder": "Enganxa una matriu JSON d’objectes o importa CSV / Excel…", + "loadSample": "Carregar exemple", + "actions": { + "format": "Formatejar JSON", + "clear": "Netejar", + "copy": "Copiar", + "copied": "Copiat", + "downloadCsv": "Descarregar CSV", + "downloadXlsx": "Descarregar Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Dades", + "toastLoadedCsv": "CSV carregat a l’editor", + "toastLoadedExcel": "Excel carregat a l’editor", + "toastFormatted": "JSON formatat", + "toastExportedCsv": "CSV descarregat", + "toastExportedXlsx": "Excel descarregat", + "toastCopied": "Copiat al porta-retalls", + "footnote": "Tot s’executa al navegador. El JSON ha de ser una matriu no buida d’objectes. Els valors imbricats es serialitzen com a JSON a les cel·les CSV.", + "errors": { + "unsupportedFile": "Utilitza un fitxer .csv, .xlsx o .xls.", + "parseFailed": "No s’ha pogut analitzar el fitxer.", + "invalidJson": "JSON no vàlid", + "exportFailed": "Ha fallat l’exportació", + "copyFailed": "No s’ha pogut copiar" + } + }, + "SnippetManager": { + "title": "Gestor de fragments de codi", + "subtitle": "Emmagatzema fragments al navegador o inicia sessió per sincronitzar-los amb el teu compte. Canvia entre veure i editar, copia amb un clic, tria idioma o detecció automàtica i formata JSON, SQL o codi compatible amb Monaco.", + "snippets": "Fragments", + "newSnippet": "Fragment nou", + "newSnippetTitle": "Fragment nou", + "defaultSnippetTitle": "Benvingut", + "defaultSnippetCode": "// Benvingut als fragments de codi\n// Prova veure vs editar, copia, formata i idioma automàtic\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Cerca fragments…", + "searchAria": "Cerca fragments", + "emptySearch": "Cap fragment no coincideix amb la cerca.", + "snippetTitle": "Títol", + "snippetTitlePlaceholder": "Nom del fragment", + "language": "Llenguatge", + "detectedHint": "Ressaltat com a {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Veure", + "modeEdit": "Editar", + "copy": "Copiar", + "copied": "Copiat", + "format": "Formatejar", + "cancel": "Cancel·la", + "deleteTitle": "Eliminar el fragment?", + "deleteDescription": "«{title}» se suprimirà d'aquest navegador.", + "deleteConfirm": "Eliminar", + "editorAria": "Editor de codi del fragment", + "editorLoading": "Carregant l'editor…", + "toastCopied": "Copiat al porta-retalls", + "toastCopyFailed": "No s'ha pogut copiar", + "toastNothingToCopy": "Res a copiar", + "toastFormatted": "Formatejat", + "toastFormatFailed": "No s'ha pogut formatejar", + "toastFormatJsonInvalid": "JSON no vàlid — corregeix la sintaxi primer", + "toastFormatUnsupported": "Sense formatador per a aquest llenguatge — prova JSON o SQL o mode edició", + "toastDeleted": "Fragment eliminat", + "toastLoadFailed": "No s'han pogut carregar els fragments des del servidor.", + "toastSyncFailed": "No s'ha pogut sincronitzar amb el servidor.", + "languages": { + "auto": "Detecció automàtica", + "plaintext": "Text sense format", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Client SQL", + "dialogTitle": "Connexions SQL", + "mobileSheetTitle": "Explorador SQL", + "newTabLabel": "Nova pestanya de consulta", + "vaultLockedTitle": "Caixa forta bloquejada", + "vaultLockedDesc": "Desbloqueja la caixa forta per accedir a les connexions desades.", + "heroTitle": "Client SQL", + "heroDesc": "Connecta't a PostgreSQL, MySQL o MariaDB. Les credencials es xifren al navegador.", + "heroAddConnection": "Afegeix connexió", + "selectTableTitle": "Selecciona una taula o obre una pestanya", + "selectTableDesc": "Fes clic en una taula de la barra lateral o utilitza ⌘↩.", + "toastLoadFail": "No s'han pogut carregar les connexions." + }, + "connection": { + "titleNew": "Nova connexió SQL", + "titleEdit": "Edita la connexió", + "labelDbType": "Tipus de base de dades", + "labelName": "Nom de la connexió", + "placeholderName": "La meva BD de producció", + "labelHostPort": "Amfitrió i port", + "labelDatabase": "Base de dades", + "placeholderDatabase": "mevabd", + "labelUsername": "Nom d'usuari", + "labelPassword": "Contrasenya", + "labelSsl": "Activa SSL / TLS", + "sslDesc": "Xifra la connexió al servidor", + "securityNote": "Les credencials es xifren AES-256 al navegador abans de l'emmagatzemament", + "btnTest": "Prova la connexió", + "btnSave": "Desa i connecta", + "btnUpdate": "Actualitza", + "btnCancelEdit": "Cancel·la l'edició", + "savedConnections": "Connexions desades", + "emptySaved": "Les connexions desades apareixeran aquí", + "confirmDelete": "Voleu suprimir aquesta connexió?", + "toastFillFields": "Empleneu l'amfitrió i la base de dades abans de provar.", + "toastConnectOk": "Connectat! {version}", + "toastConnectFail": "Error de connexió: {message}", + "toastUnlockVault": "Primer desbloqueu la caixa forta.", + "toastFieldsRequired": "L'amfitrió i la base de dades són obligatoris.", + "toastUpdated": "Connexió actualitzada.", + "toastSaved": "Connexió desada.", + "toastSaveFail": "Error en desar: {message}", + "toastDeleted": "Connexió suprimida.", + "toastDeleteFail": "No s'ha pogut suprimir la connexió." + }, + "sidebar": { + "title": "Explorador SQL", + "tooltipAddConnection": "Afegeix connexió", + "tooltipRefreshSchema": "Actualitza l'esquema", + "noConnections": "No hi ha connexions desades", + "toastSchemaFail": "No s'ha pogut carregar l'esquema: {message}" + }, + "editor": { + "btnRun": "Executa", + "executing": "Executant consulta…", + "emptyTitle": "Executa una consulta per veure els resultats", + "emptyHint": "Prem ⌘↩ o fes clic a Executa", + "toastNoConnection": "No hi ha connexió activa." + }, + "results": { + "filterPlaceholder": "Filtra els resultats…", + "rowCount": "{count} {count, plural, one {fila} other {files}}", + "rowCountFiltered": "{filtered} / {total} files", + "exportCsv": "CSV", + "noRows": "No s'han retornat files" + } + }, + "QrCodeGenerator": { + "title": "Generador de codis QR", + "subtitle": "Converteix qualsevol text o URL en un codi QR PNG. Tot es renderitza localment al navegador.", + "contentLabel": "Contingut", + "contentPlaceholder": "https://exemple.cat, text pla, cadena Wi‑Fi, vCard…", + "styleLabel": "Estil", + "dotTypeLabel": "Forma del punt", + "dotTypeSquare": "Quadrat", + "dotTypeDots": "Punts", + "dotTypeRounded": "Arrodonit", + "dotTypeExtraRounded": "Extra arrodonit", + "dotTypeClassy": "Elegant", + "dotTypeClassyRounded": "Elegant arrodonit", + "cornerSquareLabel": "Quadrats de cantonada", + "cornerSquareDot": "Punt", + "cornerSquareSquare": "Quadrat", + "cornerSquareExtraRounded": "Extra arrodonit", + "cornerDotLabel": "Punts de cantonada", + "cornerDotDot": "Punt", + "cornerDotSquare": "Quadrat", + "eccLabel": "Correcció d’errors", + "eccL": "Baixa (~7%)", + "eccM": "Mitjana (~15%)", + "eccQ": "Quartil (~25%)", + "eccH": "Alta (~30%)", + "marginLabel": "Marge (mòduls)", + "widthLabel": "Amplada de la imatge", + "widthEffective": "Es renderitza a {px} px", + "darkColor": "Mòduls foscos", + "lightColor": "Mòduls clars", + "resetColors": "Restableix colors", + "logoLabel": "Logotip central", + "logoMarginLabel": "Marge", + "uploadLogo": "Pujar imatge", + "removeLogo": "Eliminar logotip", + "downloadPng": "Baixa PNG", + "downloadSvg": "Descarregar SVG", + "copyImage": "Copia la imatge", + "copied": "Copiat", + "clear": "Neteja", + "previewAlt": "Vista prèvia del codi QR", + "emptyHint": "Escriu o enganxa contingut per veure el codi QR.", + "filename": "codi-qr.png", + "errors": { + "generationFailed": "No s’ha pogut generar el codi QR. Prova un text més curt o menys correcció.", + "copyUnsupported": "El navegador no permet copiar imatges al porta-retalls.", + "copyFailed": "No s’ha pogut copiar al porta-retalls." + } + }, + "IpSubnetCalculator": { + "title": "Calculadora IP / subxarxa", + "subtitle": "Detalls CIDR IPv4 i IPv6 al navegador—sense sol·licituds externs.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Xarxa", + "placeholderIpv4": "10.0.0.0/24 o 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 o 2001:db8::1/64", + "hintIpv4": "Useu CIDR (p. ex. /24) o adreça IPv4 + màscara en punt decimal.", + "hintIpv6": "Incloeu el prefix (p. ex. /64). Si falta, es suposa /128.", + "copyValue": "Copia el valor", + "rows": { + "cidr": "CIDR", + "network": "Adreça de xarxa", + "netmask": "Màscara de subxarxa", + "wildcard": "Màscara comodí", + "broadcast": "Difusió", + "firstHost": "Primer amfitrió", + "lastHost": "Últim amfitrió", + "totalAddresses": "Adreces totals", + "usableHosts": "Amfitrions usables", + "prefixLength": "Longitud del prefix (bits)", + "networkIpv6": "Adreça de xarxa", + "lastIpv6": "Última adreça del rang", + "totalAddressesIpv6": "Adreces a la subxarxa" + }, + "usableNote": { + "pointToPoint": "Enllaços /31 punt a punt usen les dues adreces com amfitrions (RFC 3021).", + "singleHost": "/32 és per a un sol amfitrió." + }, + "ipv6RangeHint": { + "normal": "Es mostra el rang matemàtic complet; les regles del encaminador poden variar.", + "single": "/128 és una sola adreça.", + "full": "/0 cobreix tot IPv6." + }, + "errors": { + "invalidIpv4": "Xarxa IPv4 no vàlida. Proveu CIDR o adreça + màscara.", + "invalidIpv6": "Xarxa IPv6 no vàlida. Useu adreça/prefix, p. ex. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Generador de hash", + "subtitle": "Resums MD5, SHA-1 i SHA-2 per a text o fitxers; bcrypt per a contrasenyes (entrada de text). Tot es calcula localment al navegador.", + "algorithmLabel": "Algorisme", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Text", + "modeFile": "Fitxer", + "inputLabelText": "Entrada", + "placeholderText": "Escriviu o enganxeu text…", + "chooseFile": "Trieu fitxer", + "clearFile": "Neteja", + "fileHint": "Els fitxers es llegeixen només a la memòria; màx. {maxMb} MB per fitxer. No es puja res.", + "outputLabel": "Resum (hex)", + "copyHash": "Copia el resum", + "copied": "Copiat", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# caràcter} other {# caràcters}}", + "emptyHint": "Introduïu text o trieu un fitxer per calcular el hash.", + "securityNote": "MD5 i SHA-1 no es recomanen per a usos de seguretat nous; preferiu SHA-256 o SHA-512.", + "errors": { + "fileTooLarge": "El fitxer supera {maxMb} MB.", + "fileReadFailed": "No s’ha pogut llegir el fitxer." + }, + "bcryptRoundsLabel": "Cost (voltes)", + "bcryptRoundsHint": "Valors més alts són més lents i més resistents a força bruta. Per defecte sol ser 10.", + "fileTabDisabledTitle": "bcrypt només resum contrasenyes des de text, no fitxers arbitraris.", + "placeholderBcrypt": "Escriviu o enganxeu una contrasenya per resumir…", + "outputLabelBcrypt": "Resum bcrypt", + "copyBcrypt": "Copia el resum", + "emptyHintBcrypt": "Introduïu una contrasenya per calcular el resum bcrypt.", + "securityNoteBcrypt": "Cada sortida bcrypt inclou una sal aleatòria; la mateixa contrasenya dona una cadena diferent cada vegada. És esperat. Useu costos alts en producció.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Terreny de joc del xifratge", + "subtitle": "AES-GCM al navegador: xifra en un paquet JSON amb contrasenya o clau en brut i desxifra localment. Educatiu i de vegades pràctic — no es puja res.", + "tabEncrypt": "Xifrar", + "tabDecrypt": "Desxifrar", + "keyModeLabel": "Material de clau", + "modePassphrase": "Contrasenya (PBKDF2 + AES-GCM)", + "modeRawKey": "Clau AES en brut (hex o Base64)", + "aesBitsLabel": "Longitud de clau AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Codificació de la clau en brut", + "encodingAuto": "Auto (hex si sembla hex, si no Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Clau en brut", + "rawKeyPlaceholder": "Clau de 32 bytes com a 64 caràcters hex o Base64 per AES-256 (la longitud depèn de la mida AES de dalt).", + "rawKeyPlaceholderDecrypt": "Només cal si el paquet té keyDerivation \"none\".", + "passphraseLabel": "Contrasenya", + "passphrasePlaceholder": "La contrasenya només s’usa al navegador per derivar la clau.", + "passphrasePlaceholderDecrypt": "Només cal si el paquet usa PBKDF2-SHA256.", + "pbkdf2IterLabel": "Iteracions PBKDF2", + "pbkdf2Hint": "Entre 10.000 i 2.000.000. El valor per defecte segueix configuracions habituals de cofres.", + "plaintextLabel": "Text en clar", + "plaintextPlaceholder": "Escriviu o enganxeu el missatge a xifrar…", + "bundleInputLabel": "Paquet xifrat (JSON)", + "bundlePlaceholder": "Enganxeu el JSON de Xifrar (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Paquet xifrat", + "plaintextOutputLabel": "Text en clar", + "encryptButton": "Xifrar", + "decryptButton": "Desxifrar", + "copyBundle": "Copiar JSON", + "copyPlaintext": "Copiar text en clar", + "copied": "Copiat", + "emptyBundleHint": "El JSON xifrat apareix aquí després de xifrar.", + "emptyDecryptHint": "El text desxifrat apareix aquí.", + "decryptKeyHint": "Useu la clau en brut si keyDerivation és \"none\"; la contrasenya amb PBKDF2-SHA256. El paquet emmagatzema sal, iteracions, IV i longitud AES.", + "securityNote": "Les claus i el text en clar no surten del navegador. Per a secrets de producció preferiu eines dedicades.", + "errors": { + "emptyKey": "Introduïu una clau en brut que coincideixi amb la mida AES.", + "emptyPassphrase": "Introduïu una contrasenya.", + "badKeyMaterial": "No s’ha pogut llegir la clau com a hex o Base64.", + "wrongKeyLength": "La longitud de clau no coincideix amb l’AES (16 / 24 / 32 bytes).", + "invalidJson": "JSON no vàlid.", + "invalidBundle": "Aquest JSON no és un paquet de xifratge compatible.", + "decryptFailed": "Ha fallat el desxifratge — secret incorrecte, paquet malmès o paràmetres incorrectes.", + "encryptFailed": "Ha fallat el xifratge. Comproveu Web Crypto i les entrades." + } + }, + "HmacGenerator": { + "title": "Generador HMAC", + "subtitle": "Per signar webhooks i provar integracions d’API. HMAC-SHA1/256/384/512 en hex o Base64—calculat localment al navegador.", + "secretLabel": "Clau secreta", + "messageLabel": "Missatge / càrrega útil", + "placeholderSecret": "Enganxeu el secret de signatura…", + "placeholderMessage": "Enganxeu el cos en brut o la cadena a autenticar (sovint els mateixos bytes del webhook).", + "algorithmLabel": "Algorisme HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Format de sortida", + "formatHex": "Hex (minúscules)", + "formatBase64": "Base64", + "outputLabel": "Signatura", + "copyOutput": "Copia la signatura", + "copied": "Copiat", + "byteCountHint": "{count, plural, one {# octet} other {# octets}} (UTF-8)", + "emptyHint": "Introduïu una clau secreta per calcular l’HMAC.", + "securityNote": "No compartiu mai el secret de signatura. Aquesta pàgina no puja dades; tot s’executa al navegador.", + "errors": { + "cryptoFailed": "No s’ha pogut calcular l’HMAC. Comproveu el suport de Web Crypto i torneu-ho a provar.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Generador de codi TOTP / 2FA", + "subtitle": "Enganxa el secret Base32 i obtén el codi actual de sis xifres RFC 6238 (SHA-1, 30 s). Tot al navegador—útil per provar inici de sessió o MFA.", + "secretLabel": "Secret (Base32)", + "placeholderSecret": "Enganxa la clau secreta (lletres A–Z i dígits 2–7, com en configurar l’autenticador)…", + "codeLabel": "Codi actual", + "copyCode": "Copia el codi", + "copied": "Copiat", + "nextIn": "Es renova en {seconds} s", + "standardHint": "Pas de temps de 30 s, 6 xifres, HMAC-SHA-1 (compatible amb Google Authenticator).", + "digitsLabel": "Xifres", + "periodLabel": "Període", + "algorithmLabel": "Algorisme", + "periodSeconds": "{seconds} s", + "emptyHint": "Enganxa un secret per generar un codi de sis xifres.", + "invalidHint": "Corregeix el secret per veure el codi.", + "securityNote": "Tracta’l com una contrasenya. El secret no surt del navegador; no es puja res.", + "importLabel": "Importa des d'un URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importa", + "qrSectionTitle": "Codi QR d'aprovisionament", + "qrHint": "Escaneja amb una aplicació d'autenticació (Google Authenticator, 1Password, Aegis…) per afegir aquest secret. El QR codifica el secret — tracta'l com una contrasenya.", + "accountLabel": "Compte", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Emissor (opcional)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Introdueix un secret vàlid per generar un codi QR.", + "errors": { + "invalidSecret": "Aquest no és un secret Base32 vàlid.", + "cryptoFailed": "No s’ha pogut calcular el TOTP. Comproveu Web Crypto i torneu-ho a provar.", + "invalidUri": "Això no és un URI otpauth:// TOTP vàlid.", + "unsupportedUri": "Aquest URI utilitza valors que l'eina no admet (els dígits han de ser 6 o 8; el període 15, 30 o 60 s)." + } + }, + "MarkdownPreview": { + "title": "Previsualització Markdown & HTML", + "subtitle": "Renderitzador Markdown en viu amb exportació HTML i conversió HTML → Markdown. Tot s'executa al vostre navegador.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Entrada Markdown", + "htmlInputLabel": "Entrada HTML", + "previewLabel": "Previsualització", + "htmlOutputLabel": "Font HTML", + "mdOutputLabel": "Sortida Markdown", + "inputPlaceholder": "# Hola\\n\\nEscriviu el vostre **markdown** aquí…", + "htmlInputPlaceholder": "Enganxeu el vostre HTML aquí…", + "copyHtml": "Copia HTML", + "copyMarkdown": "Copia Markdown", + "exportHtml": "Exporta HTML", + "copied": "Copiat", + "charCount": "{count, plural, one {# caràcter} other {# caràcters}}", + "emptyHint": "Comenceu a escriure Markdown per veure la previsualització i la sortida HTML.", + "emptyHintHtmlToMd": "Enganxeu HTML per convertir-lo en Markdown.", + "securityNote": "Tot s'executa localment al vostre navegador. No es carrega res." + }, + "NumberBaseConverter": { + "title": "Conversor de base numèrica", + "subtitle": "Converteix enters entre bases 2–36. Prefixos opcionals 0x / 0b / 0o. Tot es calcula localment al navegador.", + "inputBaseLabel": "Des de la base", + "outputBaseLabel": "A la base", + "inputLabel": "Nombre d’entrada", + "placeholderInput": "Introduïu un nombre enter en la base d’entrada seleccionada…", + "outputLabel": "Valor convertit", + "copyOutput": "Copia el resultat", + "copied": "Copiat", + "swap": "Intercanvia bases", + "byteCountHint": "{count, plural, one {# octet} other {# octets}} (longitud UTF-8)", + "emptyHint": "Introduïu un nombre per convertir.", + "note": "Els dígits superiors a 9 usen A–Z (sense distingir majúscules). S’ignoren espais i guions baixos.", + "errors": { + "invalidNumber": "Aquest valor no és vàlid per a la base d’entrada seleccionada." + } + }, + "FormatConverter": { + "title": "Conversor de formats", + "subtitle": "Converteix entre JSON, YAML, TOML i XML en qualsevol direcció. Les 12 combinacions. Tot funciona al teu navegador.", + "fromLabel": "De", + "toLabel": "A", + "swap": "Intercanviar", + "swapTitle": "Intercanvia els formats i utilitza la sortida com a entrada", + "copy": "Copiar", + "copied": "Copiat", + "charCount": "{count, plural, one {# caràcter} other {# caràcters}}", + "emptyHint": "Enganxa o escriu l'entrada a l'esquerra per veure la sortida convertida.", + "localNote": "Tot funciona localment al teu navegador. No es puja res." + }, + "MimeTypeLookup": { + "title": "Cerca de tipus MIME", + "subtitle": "Cerca el tipus MIME (tipus de mitjà) per una extensió o un nom de fitxer complet. Tot funciona localment al teu navegador.", + "inputLabel": "Extensió o nom de fitxer", + "placeholder": "p. ex. .png, package.json, photo.jpeg", + "hint": "Consell: enganxa una extensió (amb o sense punt) o un nom de fitxer complet.", + "clear": "Netejar", + "exampleFilename": "Utilitza un nom de fitxer d'exemple", + "exampleExtension": "Utilitza una extensió d'exemple", + "resultLabel": "Resultat", + "normalizedLabel": "Entrada normalitzada:", + "mimeTypeLabel": "Tipus MIME", + "contentTypeLabel": "Capçalera Content-Type", + "charsetLabel": "Joc de caràcters", + "copyMimeType": "Copia el tipus MIME", + "copy": "Copiar", + "copied": "Copiat", + "emptyHint": "Introdueix una extensió o un nom de fitxer per cercar el tipus MIME.", + "notFound": "No s'ha trobat cap tipus MIME per a aquesta entrada.", + "localNote": "Tot funciona localment al teu navegador. No es puja res." + }, + "UserAgentParser": { + "title": "Analitzador de User-Agent", + "subtitle": "Enganxa una cadena User-Agent (UA) i obtén el desglossament de navegador, SO, motor, dispositiu i CPU. Tot funciona localment al teu navegador.", + "inputLabel": "Cadena User-Agent", + "placeholder": "Enganxa el teu User-Agent aquí…", + "hint": "Consell: pots copiar un UA des dels encapçalaments de la sol·licitud, els logs o les devtools del navegador.", + "clear": "Netejar", + "exampleDesktop": "Utilitza un exemple d'escriptori", + "exampleMobile": "Utilitza un exemple mòbil", + "resultLabel": "Desglossament", + "browserLabel": "Navegador", + "osLabel": "Sistema operatiu", + "engineLabel": "Motor", + "deviceTypeLabel": "Tipus de dispositiu", + "deviceVendorLabel": "Fabricant", + "deviceModelLabel": "Model", + "cpuArchLabel": "Arquitectura de la CPU", + "copyJson": "Copia JSON", + "copy": "Copiar", + "copied": "Copiat", + "emptyHint": "Enganxa una cadena User-Agent per veure el desglossament.", + "localNote": "Tot funciona localment al teu navegador. No es puja res." + }, + "MockDataGenerator": { + "title": "Generador de dades de prova", + "subtitle": "Definiu columnes, trieu tipus i exporteu conjunts de dades per a llavors, Postman o proves. Fins a {max} files, tot al navegador: no es puja res.", + "mockarooLink": "Flux com a Mockaroo", + "rowsLabel": "Files", + "rowsHint": "Com a màxim {max} files per generació per mantenir la pestanya fluida.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nom de la taula SQL", + "presetsLabel": "Plantilles", + "presetPlaceholder": "Carregar plantilla…", + "presetDefault": "Inici (usuari)", + "presetUsers": "Usuaris i rols", + "presetProducts": "SKU de comerç electrònic", + "presetApiLog": "Registre de sol·licituds HTTP", + "generate": "Genera", + "warningDuplicateNames": "Noms de columna duplicats: les columnes posteriors sobreescriuen les anteriors en objectes JSON. Canvieu el nom.", + "schemaHeading": "Esquema", + "addField": "Afegeix columna", + "removeField": "Elimina columna", + "duplicateField": "Duplica columna", + "colName": "Nom", + "colType": "Tipus", + "colOptions": "Opcions", + "colBlank": "Buit %", + "optMin": "mín", + "optMax": "màx", + "optDecimals": "dec", + "optDateFrom": "des de", + "optDateTo": "fins a", + "optValues": "a, b, c", + "optStep": "pas", + "optRowBase": "base", + "output": "Sortida", + "outputPlaceholder": "Feu clic a Genera per crear les dades…", + "copy": "Copia", + "download": "Baixa", + "copied": "Copiat", + "groups": { + "personal": "Personal", + "location": "Ubicació", + "internetApi": "Internet i API", + "numbersIds": "Nombres i IDs", + "dateTime": "Data i hora", + "textDesign": "Text i disseny", + "custom": "Personalitzat" + }, + "fieldTypes": { + "first_name": "Nom", + "last_name": "Cognoms", + "full_name": "Nom complet", + "email": "Email", + "username": "Nom d'usuari", + "password": "Contrasenya", + "phone": "Telèfon", + "company": "Empresa", + "job_title": "Càrrec", + "avatar_url": "URL de l'avatar", + "city": "Ciutat", + "country": "País", + "country_code": "Codi de país (ISO)", + "street_address": "Adreça", + "zip_code": "Codi postal", + "latitude": "Latitud", + "longitude": "Longitud", + "url": "URL", + "domain": "Domini", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Enter", + "float": "Decimal", + "boolean": "Booleà", + "uuid": "UUID", + "credit_card": "Targeta (prova)", + "semver": "SemVer", + "row_index": "Índex de fila", + "sequence": "Seqüència", + "date": "Data", + "datetime": "Data i hora", + "unix_timestamp": "Marca de temps Unix", + "timezone": "Zona horària", + "word": "Paraula", + "sentence": "Frase", + "paragraph": "Paràgraf", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Codi de moneda", + "language_code": "Etiqueta d’idioma", + "custom_list": "Llista personalitzada" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Optimitzador SVG", + "subtitle": "Enganxa SVG, minifica amb SVGO al navegador i compara la mida UTF-8 abans i després.", + "inputLabel": "Entrada", + "outputLabel": "Optimitzat", + "placeholder": "Enganxa un fragment o el fitxer sencer…", + "outputPlaceholder": "El SVG optimitzat apareix aquí…", + "chars": "{count} caràcters • {bytes} bytes (UTF-8)", + "preview": "Vista prèvia", + "localNote": "Tot es processa localment al navegador. No es puja res.", + "stats": { + "title": "Mida (UTF-8)", + "before": "Abans", + "after": "Després", + "savedBytes": "{count} bytes estalviats ({percent}%)", + "noChange": "Sense reducció — ja és mínim o la sortida és buida." + }, + "actions": { + "clear": "Netejar", + "sample": "Carregar exemple", + "copy": "Copiar", + "copied": "Copiat", + "download": "Baixar" + }, + "errors": { + "optimizeFailed": "No s’ha pogut optimitzar aquest SVG." + } + }, + "UnitConverter": { + "title": "Convertidor d'unitats", + "subtitle": "Converteix entre 323 unitats en 43 categories", + "category": "Categoria", + "from": "De", + "to": "A", + "value": "Valor", + "result": "Resultat", + "swapUnits": "Intercanviar unitats", + "placeholder": "Introduïu un valor", + "copyResult": "Copiar resultat", + "searchCategory": "Cerca categoria...", + "selectCategory": "Seleccioneu una categoria", + "noResults": "No s'ha trobat cap categoria." + }, + "DockerComposeGenerator": { + "title": "Generador Docker Compose", + "subtitle": "Trieu bases de dades populars, memòries cau, cues de missatges, proxies inversos i observabilitat — copieu un docker-compose.yml llest per al desenvolupament local.", + "searchPlaceholder": "Cerca (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Serveis", + "selectedCount": "{count, plural, one {# servei} other {# serveis}}", + "outputTitle": "docker-compose.yml", + "copy": "Copia", + "copied": "Copiat!", + "download": "Baixa", + "emptyState": "Seleccioneu un o més serveis per generar el YAML de Compose.", + "hintPorts": "Els ports per defecte redueixen conflictes; canvieu-los si Docker informa d’un port en ús. Substituïu les contrasenyes de demostració abans d’entorns compartits.", + "categories": { + "databases": "Bases de dades i emmagatzematge", + "cache": "Memòria cau i en memòria", + "queues": "Cues i missatgeria", + "search": "Cerca i registres", + "storage": "Emmagatzematge d’objectes", + "proxy": "Proxies inversos i ingress", + "observability": "Mètriques i traçat", + "mail": "Correu (desenvolupament local)", + "admin": "Interfícies d’administració i BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Neteja" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (compatible amb Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (compatible amb Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (compatible amb S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (captura SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (servidor de desenvolupament)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Llisca les rajoles i fusiona-les per arribar al 2048", + "score": "Puntuació", + "best": "Òptim", + "newGame": "Nova partida", + "howToPlay": "Utilitza les tecles de fletxa (o llisca el mòbil) per moure les rajoles. Les rajoles amb el mateix nombre es fusionen. Fes el 2048 per guanyar!", + "youWin": "Has arribat al 2048!", + "keepGoing": "- Segueix.", + "gameOver": "La partida ha acabat", + "tryAgain": "Torna a intentar-ho", + "gameOverMessage": "No queden més moviments.", + "undo": "Desfer" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "etapes completades", + "time": "Hora", + "best": "Òptim", + "difficulty": { + "easy": "Fàcil", + "medium": "Suport", + "hard": "Dur" + }, + "stage": "Fase", + "solved": "Solucionat", + "score": "Puntuació", + "nextStage": "Següent etapa", + "replay": "& Repetició", + "erase": "Esborrar", + "howToPlay": "Fes clic a una cel·la i, a continuació, toca un número o prem 1–9. Backspace o Suprimeix neteja la cel·la. Els números en negreta són pistes. Omple la graella perquè cada fila, columna i quadre de 3×3 contingui de l'1 al 9 sense repeticions.", + "pencil": "Llapis", + "undo": "Desfer", + "hint": "Pista", + "hintsUsed": "pistes usades", + "mistakes": "Errors", + "failed": "Fracassat!", + "mistakeMode": "Mode 3 errors" + }, + "Snake": { + "title": "Snake", + "subtitle": "Menja, creix, evita les parets i tu mateix", + "score": "Punts", + "best": "Millor", + "startPrompt": "Prem qualsevol tecla de fletxa", + "startButton": "Iniciar Joc", + "gameOver": "Fi del Joc", + "playAgain": "Jugar de nou", + "howToPlay": "Usa les tecles de fletxa o WASD per moure't. Menja per créixer. La velocitat augmenta cada 50 punts.", + "diffEasy": "Fàcil", + "diffNormal": "Normal", + "diffHard": "Difícil", + "diffInfinite": "Infinit", + "orTap": "o toca a baix", + "howToPlayInfinite": "Les parets s'emboliquen — sense morts per parets. Menja per créixer.", + "shieldActive": "Escut actiu", + "speedBoost": "Augment velocitat", + "powerSpeed": "Velocitat x2", + "powerShield": "Sobreviu una mort", + "powerShrink": "Escurça la cua" + }, + "Minesweeper": { + "title": "Buscamines", + "subtitle": "Troba totes les cel·les segures sense detonar una mina", + "mines": "Mines", + "newGame": "Nova partida", + "win": "Has guanyat!", + "loss": "Has perdut!", + "playAgain": "Torna a jugar", + "tryAgain": "Torna-ho a provar", + "startPrompt": "Fes clic a qualsevol cel·la per començar", + "difficulty": { + "beginner": "Principiant", + "intermediate": "Intermedi", + "expert": "Expert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Organitza els blocs que cauen per completar files", + "next": "Següent", + "score": "Puntuació", + "best": "Millor", + "lines": "Línies", + "level": "Nivell", + "pause": "Pausa", + "resume": "Reprèn", + "start": "Comença", + "restart": "Reinicia", + "paused": "En pausa", + "gameOver": "Fi de partida" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Connexions Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Connecteu-vos a instàncies Redis. Navegueu per claus, editeu valors i executeu ordres. Les credencials es xifren al vostre navegador.", + "heroAddConnection": "Afegeix connexió", + "vaultLockedTitle": "Caixa forta bloquejada", + "vaultLockedDesc": "Desbloquegeu la caixa forta per accedir a les connexions desades." + } + }, + "SshKeyGenerator": { + "title": "Generador de claus SSH / RSA", + "subtitle": "Genera parells de claus Ed25519 o RSA al teu navegador. Descarrega les claus privada i pública en formats OpenSSH i PEM. Res no es puja.", + "keyTypeLabel": "Tipus de clau", + "recommended": "recomanat", + "ed25519Desc": "Ràpid, petit i modern. Compatible amb GitHub, GitLab i tots els servidors SSH principals.", + "rsa2048Desc": "Àmpliament compatible. Segur per a la majoria d'usos.", + "rsa4096Desc": "Màxima compatibilitat i fortalesa per a sistemes antics.", + "commentLabel": "Comentari (opcional)", + "commentHint": "S'afegeix a la línia de clau pública d'OpenSSH.", + "generateBtn": "Genera el parell de claus", + "generating": "Generant…", + "generatingLong": "Generant (pot trigar un moment)…", + "copy": "Copia", + "download": "Descarrega", + "privateKeyLabel": "Clau privada (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Clau pública (format OpenSSH)", + "publicKeySpkiLabel": "Clau pública (SPKI PEM)", + "browserNote": "Totes les claus es generen al teu navegador. Res no s'envia a cap servidor.", + "errors": { + "generationFailed": "La generació de la clau ha fallat" + } + }, + "DnsLookup": { + "title": "Cerca DNS", + "subtitle": "Consulta els registres DNS de qualsevol domini — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Introdueix un domini (p. ex. github.com)", + "lookupBtn": "Cerca", + "looking": "Cercant…", + "refreshBtn": "Actualitza", + "recordTypesLabel": "Tipus de registres", + "allBtn": "Tots", + "tryLabel": "Prova:", + "errors": { + "lookupFailed": "Ha fallat la consulta DNS" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Generador i verificador Bcrypt", + "subtitle": "Genera hash de contrasenyes amb bcrypt i verifica hashes — tot es queda al teu navegador", + "tabs": { + "hash": "Hash", + "verify": "Verifica" + }, + "showPassword": "Mostra la contrasenya", + "hidePassword": "Amaga la contrasenya", + "copy": "Copia", + "copied": "Copiat", + "hash": { + "passwordLabel": "Contrasenya", + "passwordPlaceholder": "Introdueix una contrasenya per generar el hash…", + "costLabel": "Factor de cost", + "costHint": "Un cost més alt duplica la feina per pas. 10-12 és una opció habitual per a producció.", + "slowWarning": "Els factors de cost de 13 o més poden trigar diversos segons a calcular-se.", + "generate": "Genera el hash", + "generating": "Generant el hash…", + "resultLabel": "Hash bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomia del hash", + "algorithm": "algoritme", + "cost": "cost", + "salt": "salt (22 caràcters)", + "hash": "hash (31 caràcters)" + } + }, + "verify": { + "passwordLabel": "Contrasenya", + "passwordPlaceholder": "Introdueix la contrasenya per comprovar-la…", + "hashLabel": "Hash bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Introdueix una contrasenya i un hash bcrypt per comparar-los.", + "invalidHash": "Això no sembla un hash bcrypt vàlid.", + "checking": "Comparant…", + "match": "Coincideix — la contrasenya produeix aquest hash.", + "noMatch": "No coincideix — la contrasenya no produeix aquest hash." + } + }, + "ChmodCalculator": { + "title": "Calculadora Chmod", + "subtitle": "Converteix permisos de fitxer Unix entre caselles de selecció, octal i notació simbòlica", + "grid": { + "title": "Permisos", + "read": "Lectura", + "write": "Escriptura", + "execute": "Execució", + "owner": "Propietari", + "group": "Grup", + "others": "Altres" + }, + "special": { + "title": "Bits especials", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Octal", + "placeholder": "755 o 4755", + "invalid": "Introdueix 3 o 4 dígits octals (0-7)." + }, + "symbolic": { + "label": "Simbòlic", + "placeholder": "rwxr-xr-x", + "invalid": "Introdueix 9 caràcters com rwxr-xr-x (s/S, t/T permesos)." + }, + "output": { + "title": "Ordre", + "copy": "Copia", + "copied": "Copiat" + }, + "explain": { + "line": "{cls} pot {perms}.", + "none": "{cls} no té permisos.", + "setuid": "Setuid: el fitxer s'executa amb els privilegis del seu propietari.", + "setgid": "Setgid: el fitxer s'executa amb els privilegis del seu grup (els fitxers nous d'un directori n'hereten el grup).", + "sticky": "Sticky: només el propietari d'un fitxer pot eliminar-lo o canviar-ne el nom dins d'aquest directori." + }, + "presets": { + "title": "Predefinits habituals", + "octal": "Octal", + "permissions": "Permisos", + "description": "Ús típic", + "p644": "Fitxers normals: el propietari escriu, tothom llegeix.", + "p755": "Executables i directoris: tothom pot llegir i entrar-hi.", + "p600": "Fitxers privats: només el propietari llegeix i escriu.", + "p700": "Executables i directoris privats: només el propietari.", + "p664": "Fitxers on el grup pot escriure.", + "p775": "Directoris i executables on el grup pot escriure.", + "p400": "Només de lectura per al propietari (p. ex. claus privades SSH)." + } + }, + "CodeScreenshot": { + "title": "Captura de codi", + "subtitle": "Converteix codi en imatges boniques i compartibles", + "languageLabel": "Llenguatge", + "autoDetect": "Detecció automàtica", + "backgroundLabel": "Fons", + "paddingLabel": "Farciment", + "filenameLabel": "Nom del fitxer", + "filenamePlaceholder": "exemple.ts", + "windowLabel": "Punts de la finestra", + "themeLabel": "Tema de la finestra", + "themeDark": "Fosc", + "themeLight": "Clar", + "scaleLabel": "Escala d'exportació", + "copyImage": "Copia la imatge", + "copied": "Copiat", + "exportPng": "Exporta PNG", + "inputLabel": "Codi", + "detected": "Detectat: {language}", + "clear": "Netejar", + "placeholder": "Enganxa el teu codi aquí…", + "previewLabel": "Vista prèvia", + "emptyPreview": "La vista prèvia del teu codi apareixerà aquí", + "exportFailed": "No s'ha pogut exportar la imatge", + "copyUnsupported": "La còpia d'imatges no és compatible amb aquest navegador", + "copyFailed": "No s'ha pogut copiar la imatge", + "backgrounds": { + "ocean": "Oceà", + "sunset": "Posta de sol", + "candy": "Caramel", + "forest": "Bosc", + "midnight": "Mitjanit", + "gold": "Or", + "aurora": "Aurora", + "slate": "Pissarra", + "transparent": "Transparent" + } + }, + "CssGenerators": { + "title": "Generadors CSS", + "subtitle": "Box-shadow, border-radius i clamp() fluid — copia el CSS.", + "preview": "Vista prèvia", + "output": "CSS", + "copy": "Copia", + "copied": "Copiat", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() fluid" + }, + "boxShadow": { + "presets": "Predefinits", + "layer": "Capa", + "addLayer": "Afegeix una capa", + "removeLayer": "Elimina la capa", + "x": "X", + "y": "Y", + "blur": "Difuminat", + "spread": "Extensió", + "color": "Color", + "alpha": "Alfa", + "inset": "Interior", + "presetNames": { + "subtleCard": "Targeta subtil", + "elevated": "Elevat", + "hardOffset": "Desplaçament dur", + "neonGlow": "Resplendor neó", + "inner": "Interior", + "layeredSmooth": "Capes suaus" + } + }, + "borderRadius": { + "modeLabel": "Mode", + "modes": { + "simple": "Per cantonada", + "blob": "Bombolla orgànica" + }, + "unit": "Unitat", + "linkCorners": "Enllaça les cantonades", + "randomize": "Aleatoritza", + "horizontal": "Radis horitzontals", + "vertical": "Radis verticals", + "corners": { + "tl": "Superior esquerra", + "tr": "Superior dreta", + "br": "Inferior dreta", + "bl": "Inferior esquerra" + } + }, + "clamp": { + "minFont": "Font mínima (px)", + "maxFont": "Font màxima (px)", + "minViewport": "Viewport mínim (px)", + "maxViewport": "Viewport màxim (px)", + "rootFont": "Font arrel (px)", + "viewportWidth": "Viewport simulat", + "computedSize": "Mida calculada", + "slope": "Pendent", + "intercept": "Intercepció", + "previewText": "La ràpida guineu marró", + "invalid": "Introdueix límits de viewport vàlids i diferents." + } + }, + "ExifViewer": { + "title": "Visualitzador i eliminador EXIF", + "subtitle": "Inspecciona les metadades de les fotos i elimina-les al teu navegador", + "dropTitle": "Deixa anar una imatge aquí", + "dropHint": "o fes clic per navegar — la teva imatge no surt mai del teu dispositiu", + "browse": "Tria una imatge", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Llegint metadades…", + "parseFailed": "No s'ha pogut llegir aquesta imatge", + "clear": "Elimina la imatge", + "gpsWarning": "Conté ubicació GPS", + "openMap": "Mostra a OpenStreetMap", + "noMetadata": "No s'han trobat metadades incrustades en aquesta imatge.", + "facts": { + "name": "Nom", + "type": "Tipus", + "dimensions": "Dimensions", + "size": "Mida" + }, + "groups": { + "camera": "Càmera", + "exposure": "Exposició", + "timestamps": "Marques de temps", + "gps": "Ubicació GPS", + "software": "Programari i altres" + }, + "gpsCard": { + "latitude": "Latitud", + "longitude": "Longitud", + "altitude": "Altitud" + }, + "raw": { + "title": "Totes les metadades", + "copyJson": "Copia el JSON", + "copied": "Copiat" + }, + "remove": { + "title": "Elimina les metadades", + "jpegDescription": "Elimina sense pèrdua EXIF, XMP, IPTC i comentaris sense recomprimir la imatge.", + "reencodeDescription": "Recodifica la imatge per eliminar les metadades incrustades. Això recomprimeix els píxels.", + "keepIcc": "Conserva el perfil de color (ICC)", + "stripButton": "Elimina les metadades", + "reencodeButton": "Recodifica i elimina", + "unsupported": "L'eliminació de metadades no és compatible amb aquest tipus de fitxer.", + "failed": "No s'han pogut eliminar les metadades", + "before": "Abans", + "after": "Després", + "download": "Descarrega la imatge neta" + } + }, + "FaviconGenerator": { + "title": "Generador de favicon", + "subtitle": "Crea favicons i icones d'app a partir d'una imatge", + "dropTitle": "Deixa anar una imatge aquí", + "dropHint": "PNG, JPEG o SVG — fes clic per navegar", + "browse": "Tria una imatge", + "clear": "Elimina la imatge", + "backgroundLabel": "Fons", + "transparent": "Transparent", + "solidColor": "Sòlid", + "cornerLabel": "Forma de la cantonada", + "cornerNone": "Quadrat", + "cornerRounded": "Arrodonit", + "cornerCircle": "Cercle", + "maskPaddingLabel": "Farciment maskable", + "maskable": "maskable", + "previewLabel": "Vista prèvia", + "copy": "Copia", + "headSnippet": "Fragment del head HTML", + "downloadIco": "Descarrega favicon.ico", + "downloadZip": "Descarrega-ho tot (.zip)" + }, + "JsonDiff": { + "title": "Diff JSON", + "subtitle": "Comparació semàntica i conscient de l'estructura de dos documents JSON — s'ignoren l'ordre de les claus i el format", + "options": { + "ignoreArrayOrder": "Ignora l'ordre dels arrays" + }, + "loadBothSamples": "Carrega exemples", + "sample": "Exemple", + "format": "Formata", + "clear": "Netejar", + "formatError": "No es pot formatar: l'entrada no és un JSON vàlid", + "placeholder": "Enganxa o escriu JSON aquí…", + "panels": { + "left": "Original (esquerra)", + "right": "Modificat (dreta)", + "results": "Diferències" + }, + "summary": { + "added": "{count} afegit(s)", + "removed": "{count} eliminat(s)", + "changed": "{count} canviat(s)" + }, + "types": { + "added": "Afegit", + "removed": "Eliminat", + "changed": "Canviat" + }, + "copyChanges": "Copia com a JSON", + "copied": "Copiat", + "empty": "Enganxa JSON als dos plafons per veure'n les diferències.", + "identical": "Els dos documents són semànticament idèntics." + }, + "JsonpathPlayground": { + "title": "Playground JSONPath", + "subtitle": "Consulta documents JSON amb JSONPath o JMESPath i mira els resultats en directe", + "engineLabel": "Motor", + "queryLabel": "Consulta", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Referència ràpida", + "cheats": { + "jpAllTitles": "Tots els títols de llibres", + "jpAllAuthors": "Tots els autors, a qualsevol lloc del document", + "jpFirstBook": "Primer llibre", + "jpLastBook": "Últim llibre", + "jpSlice": "Els dos primers llibres (slice)", + "jpFilterPrice": "Llibres més barats que 40", + "jpFilterInStock": "Títols dels llibres en estoc", + "jpRecursive": "Tots els valors, recursivament", + "jmAllTitles": "Tots els títols de llibres", + "jmFirstAuthor": "Autor del primer llibre", + "jmLastBook": "Últim llibre", + "jmSlice": "Els dos primers llibres (slice)", + "jmFilterPrice": "Títols dels llibres més barats que 40", + "jmCount": "Compta els llibres en estoc", + "jmProjection": "Remodela cada llibre en un objecte nou", + "jmSort": "Títols ordenats per preu" + }, + "panels": { + "input": "Entrada JSON", + "results": "Resultats" + }, + "sample": "Exemple", + "format": "Formata", + "clear": "Netejar", + "formatError": "No es pot formatar: l'entrada no és un JSON vàlid", + "placeholder": "Enganxa o escriu JSON aquí…", + "outputPlaceholder": "Les coincidències apareixeran aquí…", + "matchCount": "{count, plural, one {# coincidència} other {# coincidències}}", + "copy": "Copia", + "copied": "Copiat" + }, + "KeycodeInspector": { + "title": "Inspector de keycode", + "subtitle": "Prem qualsevol tecla per inspeccionar el seu esdeveniment de teclat JavaScript", + "capture": { + "prompt": "Prem qualsevol tecla…", + "ready": "Escoltant — prem qualsevol tecla del teu teclat", + "clickToFocus": "Fes clic aquí per enfocar i després prem qualsevol tecla" + }, + "hero": { + "spaceKey": "(Espai)", + "keyLabel": "event.key", + "repeat": "repetició" + }, + "copyJson": "Copia el JSON", + "copiedJson": "JSON de l'esdeveniment copiat al porta-retalls", + "deprecated": "obsolet", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Estàndard", + "left": "Esquerra", + "right": "Dreta", + "numpad": "Teclat numèric", + "unknown": "Desconegut" + }, + "history": { + "title": "Historial", + "clear": "Neteja l'historial", + "empty": "Les tecles premudes apareixeran aquí." + } + }, + "MarkdownTableGenerator": { + "title": "Generador de taules Markdown", + "subtitle": "Edita una graella, enganxa des d'un full de càlcul i copia una taula Markdown o HTML ben alineada", + "addRow": "Afegeix una fila", + "addColumn": "Afegeix una columna", + "importLabel": "Importa", + "importPlaceholder": "Enganxa TSV (d'Excel / Google Sheets) o CSV aquí…", + "importButton": "Importa a la graella", + "pasteClipboard": "Enganxa des del porta-retalls", + "importEmpty": "Res per importar — enganxa primer algunes files", + "clipboardError": "No s'ha pogut llegir del porta-retalls", + "clear": "Netejar", + "panels": { + "editor": "Editor de taules", + "output": "Resultat" + }, + "align": { + "left": "Alinea a l'esquerra", + "center": "Centra", + "right": "Alinea a la dreta" + }, + "headerPlaceholder": "Capçalera", + "cellPlaceholder": "Cel·la", + "removeRow": "Elimina la fila", + "removeColumn": "Elimina la columna", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Copia", + "copied": "Copiat" + }, + "TimezoneConverter": { + "title": "Convertidor de zona horària", + "subtitle": "Converteix un moment arreu del món per planificar reunions.", + "dateTimeLabel": "Data i hora", + "now": "Ara", + "sourceZoneLabel": "Zona horària d'origen", + "selectZone": "Selecciona una zona", + "searchZone": "Cerca zones horàries...", + "noZone": "No s'ha trobat cap zona horària.", + "scrubLabel": "Desplaça l'hora", + "reset": "Restableix", + "targetsLabel": "Zones de destinació", + "addZone": "Afegeix una zona", + "remove": "Elimina", + "localBadge": "Local", + "invalidDate": "Introdueix una data i hora vàlides.", + "bands": { + "work": "Horari laboral (09:00–18:00)", + "edge": "Hores límit (matí / vespre)", + "night": "Nit" + } + }, + "TokenCounter": { + "title": "Comptador de tokens LLM", + "subtitle": "Compta tokens, visualitza chunks i estima el cost de l'API.", + "inputLabel": "Text", + "sample": "Exemple", + "clear": "Netejar", + "placeholder": "Enganxa text, codi o JSON per comptar tokens...", + "encoding": "Codificació", + "loading": "Carregant el tokenitzador...", + "encodings": { + "o200k": "o200k_base (GPT-4o / sèrie o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Caràcters", + "words": "Paraules", + "ratio": "Tokens/paraula", + "ratioSub": "aprox." + }, + "claudeRow": "Claude (aproximat)", + "estimate": "estimació", + "viz": { + "title": "Visualització de tokens", + "toggle": "Mostra", + "hint": "Activa-ho per acolorir cada token com un chunk.", + "empty": "Introdueix text per visualitzar els tokens.", + "truncated": "Mostrant els primers {limit} de {total} tokens." + }, + "cost": { + "title": "Calculadora de cost", + "disclaimer": "valors per defecte editables — verifica els preus actuals", + "model": "Model", + "expectedOutput": "Tokens de sortida esperats", + "customInput": "Entrada $/MTok", + "customOutput": "Sortida $/MTok", + "inputCost": "Cost d'entrada", + "outputCost": "Cost de sortida", + "total": "Total", + "per1000": "Per 1000 crides" + } + }, + "WebhookTester": { + "title": "Provador de webhooks", + "subtitle": "Crea una URL temporal i inspecciona les peticions de webhook entrants en directe.", + "intro": { + "title": "Crea una URL de webhook per començar a capturar peticions", + "hint": "Rebràs una URL impossible d'endevinar que registra cada petició entrant. Caduca al cap de 24 hores." + }, + "createBtn": "Crea una URL de webhook", + "creating": "Creant…", + "urlLabel": "La teva URL de webhook", + "expiresIn": "Caduca en {countdown}", + "copyUrlBtn": "Copia la URL", + "copied": "Copiat", + "clearBtn": "Netejar", + "newBinBtn": "URL nova", + "requestsTitle": "Peticions", + "selectPrompt": "Selecciona una petició per inspeccionar-ne els detalls.", + "expiredNotice": "Aquesta URL de webhook ha caducat. Crea'n una de nova per continuar provant.", + "base64Badge": "base64", + "truncatedBadge": "truncat", + "empty": { + "title": "Esperant peticions…", + "hint": "Envia una petició a la teva URL i apareixerà aquí a l'instant. Prova això:" + }, + "detail": { + "receivedAt": "Rebut", + "source": "IP d'origen", + "contentType": "Content-Type", + "query": "Paràmetres de query", + "headers": "Capçaleres", + "body": "Cos", + "copyBody": "Copia el cos", + "emptyBody": "Sense cos" + }, + "errors": { + "createFailed": "No s'ha pogut crear la URL de webhook. Torna-ho a provar.", + "clearFailed": "No s'han pogut netejar les peticions. Torna-ho a provar." + } + }, + "WebsocketTester": { + "title": "Provador de WebSocket", + "subtitle": "Connecta't a un endpoint WebSocket i envia, rep i inspecciona missatges en directe", + "urlLabel": "URL de l'endpoint", + "protocolsLabel": "Subprotocols", + "protocolsPlaceholder": "opcional, separats per comes", + "connect": "Connecta", + "disconnect": "Desconnecta", + "status": { + "disconnected": "Desconnectat", + "connecting": "Connectant", + "connected": "Connectat", + "error": "Error" + }, + "messages": { + "connecting": "Connectant a {url}…", + "connected": "Connexió establerta", + "connectedWithProtocol": "Connexió establerta (protocol: {protocol})", + "invalidUrl": "URL no vàlida — ha de començar amb ws:// o wss://", + "connectFailed": "No s'ha pogut obrir la connexió", + "socketError": "S'ha produït un error de connexió", + "notConnected": "No connectat — connecta't primer", + "binaryMessage": "S'ha rebut un missatge binari" + }, + "log": { + "title": "Registre de missatges", + "empty": "Encara no hi ha missatges. Connecta't i comença a enviar.", + "copyAll": "Copia-ho tot", + "download": "Descarrega com a .txt", + "clear": "Neteja el registre", + "prettyPrint": "Formata el JSON", + "showRaw": "Mostra el text en brut" + }, + "composer": { + "placeholder": "Escriu un missatge per enviar…", + "send": "Envia", + "formatJson": "Formata com a JSON", + "invalidJson": "El missatge no és un JSON vàlid", + "hint": "Cmd/Ctrl+Enter per enviar" + }, + "ping": { + "label": "Auto-ping", + "seconds": "s", + "intervalTitle": "Interval de ping en segons", + "payloadPlaceholder": "payload del ping" + } + }, + "WhoisLookup": { + "title": "Consulta Whois", + "subtitle": "Consulta les dades de registre de qualsevol domini o adreça IP mitjançant RDAP", + "placeholder": "example.com o 8.8.8.8", + "lookupBtn": "Consulta", + "looking": "Consultant…", + "refreshBtn": "Actualitza", + "tryLabel": "Prova:", + "copy": "Copia", + "copied": "Copiat", + "notAvailable": "No disponible", + "summary": { + "registrar": "Registrador", + "network": "Xarxa" + }, + "dates": { + "created": "Creat", + "updated": "Actualitzat", + "expires": "Caduca" + }, + "sections": { + "status": "Estat", + "nameservers": "Servidors de noms", + "contacts": "Contactes", + "rawRdap": "RDAP en brut" + }, + "contacts": { + "roles": "Rols", + "name": "Nom", + "organization": "Organització" + }, + "errors": { + "invalidTarget": "Introdueix un nom de domini o adreça IP vàlids", + "lookupFailed": "La consulta ha fallat (estat {status})" + } + } +} diff --git a/apps/desktop-ui/messages/cs.json b/apps/desktop-ui/messages/cs.json new file mode 100644 index 00000000..2dc2b553 --- /dev/null +++ b/apps/desktop-ui/messages/cs.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Přehled", + "settings": "Nastavení", + "apiClient": "API klient", + "httpStatusCodes": "Stavové kódy HTTP", + "passwordManager": "Správce hesel", + "environmentManager": "Správce prostředí", + "bookmarks": "Záložky", + "jsonFormatter": "Formátovač JSON", + "emailValidator": "Validátor e-mailů", + "nosqlExplorer": "Průzkumník databází", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Hraviště šifrování", + "jwtDecoder": "Dekodér JWT", + "certificatePemDecoder": "Dekodér certifikátů / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Formátovač GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Parser URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Tajemství / API klíč", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Kontrola kontrastu", + "jsonSchemaGenerator": "Generátor JSON schématu", + "imageToBase64": "Obrázek do Base64", + "imageCompressor": "Komprese obrázků", + "cssGradientBuilder": "CSS Gradient Builder", + "gitignoreGenerator": "Generátor .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Úryvky kódu", + "qrCodeGenerator": "Generátor QR kódů", + "dnsLookup": "Vyhledávání DNS", + "ipSubnetCalculator": "Kalkulačka IP / podsítě", + "hashGenerator": "Generátor hashů", + "hmacGenerator": "Generátor HMAC", + "totpGenerator": "Kód TOTP / 2FA", + "markdownPreview": "Náhled Markdown", + "numberBaseConverter": "Převodník soustav", + "formatConverter": "Převodník formátů", + "mockDataGenerator": "Generátor testovacích dat", + "svgOptimizer": "Optimalizátor SVG", + "unitConverter": "Převodník jednotek", + "dockerComposeGenerator": "Generátor Docker Compose", + "breakRoom": "PŘESTÁVKOVÁ MÍSTNOST", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Formátovač YAML", + "sshKeyGenerator": "Generátor klíčů SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Kalkulačka chmod", + "codeScreenshot": "Snímek kódu", + "cssGenerators": "CSS generátory", + "exifViewer": "EXIF prohlížeč", + "faviconGenerator": "Generátor favicon", + "jsonDiff": "JSON diff", + "jsonpathPlayground": "JSONPath hřiště", + "keycodeInspector": "Inspektor kláves", + "markdownTableGenerator": "Markdown tabulka", + "timezoneConverter": "Převodník časových pásem", + "tokenCounter": "Počítadlo tokenů", + "webhookTester": "Tester webhooků", + "websocketTester": "Tester WebSocket", + "whoisLookup": "Whois vyhledávání" + }, + "Help": { + "title": "Nápověda a dokumentace", + "subtitle": "Jak MyDevTools funguje, co dělá každá aplikace a jak bezpečnost a šifrování chrání vaše data.", + "tabs": { + "overview": "Přehled", + "apps": "Aplikace", + "security": "Bezpečnost", + "tips": "Tipy" + }, + "overview": { + "whatIs": { + "title": "Co je MyDevTools?", + "description": "Vývojářský workspace s přihlášením: přehled a sada nástrojů pro každodenní práci.", + "p1": "Po přihlášení aplikace naváže relaci s MyDevTools API, aby nástroje mohly načítat a ukládat vaše data. Postranní panel zobrazuje všechny aplikace; nástroje, které nepoužíváte, můžete skrýt v", + "settingsLink": "Nastavení", + "p1After": ".", + "p2": "Některé funkce používají hlavní heslo (nebo globální klíč trezoru), aby se tajné údaje jako hesla a connection stringy šifrovaly na vašem zařízení dříve, než dorazí na server." + }, + "quickLinks": { + "title": "Rychlé odkazy", + "dashboard": "Přehled", + "settings": "Nastavení" + } + }, + "apps": { + "intro": "Otevřete libovolný nástroj z postranního panelu nebo přehledu. Níže je stručný průvodce pro každou aplikaci.", + "open": "Otevřít", + "howItWorks": "Jak to funguje", + "generic": "Používejte tento nástroj z postranního panelu v jeho vyhrazeném pracovním postupu." + }, + "security": { + "session": { + "title": "Přihlášení a API relace", + "description": "Identita a jak prohlížeč komunikuje s backendem.", + "items": { + "authentication": "Ověřování používá Firebase. Po přihlášení aplikace vymění váš Firebase ID token za API cookies.", + "accessTokens": "Přístupové tokeny jsou JWT podepsané na serveru (HS256) a dokazují, který uživatel volá API.", + "refreshTokens": "Obnovovací tokeny jsou uloženy v HttpOnly cookies; server ukládá SHA-256 hash tokenu, ne jeho surovou hodnotu." + } + }, + "encryption": { + "title": "Šifrování, které byste měli znát", + "description": "Kryptografie na straně klienta pro trezor a citlivé údaje o připojení.", + "p1": "Pro správce hesel, správce prostředí a uložená připojení v NoSQL Exploreru webová aplikace používá v prohlížeči Web Crypto API:", + "items": { + "kdf": "PBKDF2 s SHA-256, 100 000 iteracemi a náhodnou solí odvodí 256bitový klíč z hlavního hesla.", + "aes": "AES-256-GCM šifruje data; pro každé šifrování se používá náhodný 12bajtový IV.", + "scope": "Odvozený klíč zůstává v prohlížeči (nelze exportovat). Server dostává pouze šifrotext a IV, nikoliv hlavní heslo ani tajná data v otevřené podobě." + }, + "warning": "Pokud zapomenete hlavní heslo, šifrovaná data nelze obnovit. Bezpečně si ho uložte." + }, + "logout": { + "title": "Odhlášení a lokální data", + "body": "Odhlášení vymaže tajné údaje v paměti, stav trezoru a (pokud je to relevantní) materiál hlavního klíče z IndexedDB a ukončí API relaci. Data uložená pouze v lokálním úložišti zařízení mohou zůstat, dokud je nevymažete v prohlížeči." + } + }, + "tips": { + "title": "Bezpečné používání MyDevTools", + "items": { + "masterPassword": "Používejte silné, jedinečné hlavní heslo pro funkce trezoru.", + "apiClient": "S API klientem zacházejte jako s běžným HTTP klientem: nevkládejte produkční tajemství do sdílených prostředí, pokud nevíte, kdo je může vidět.", + "nosql": "Průzkumník databází se připojuje k databázím, které nastavíte; používejte účty s minimálními právy a síťová pravidla, pokud je to možné.", + "json": "Formátovač JSON běží lokálně v prohlížeči—i tak se vyhněte vkládání velmi citlivých dat na sdílených počítačích.", + "settings": "Přizpůsobte viditelné nástroje v Nastavení, aby postranní panel zůstal přehledný." + } + } + }, + "Settings": { + "title": "Nastavení", + "language": "Jazyk", + "spanish": "Španělština", + "english": "Angličtina", + "french": "Francouzština", + "arabic": "Arabština", + "catalan": "Katalánština", + "chineseSimplified": "Čínština (zjednodušená)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Němčina", + "danish": "Dánština", + "afrikaans": "Afrikánština", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukrajinština" + }, + "SettingsPage": { + "title": "Nastavení", + "subtitle": "Spravujte nastavení účtu a předvolby.", + "userProfile": { + "title": "Uživatelský profil", + "description": "Vaše osobní údaje a informace o účtu.", + "avatarAlt": "Uživatel", + "anonymousUser": "Anonymní uživatel", + "noEmailProvided": "E-mail není uveden", + "notLoggedIn": "Nejste přihlášeni." + }, + "tools": { + "title": "Nástroje a funkce", + "description": "Vyberte, které nástroje se zobrazí v postranní navigaci." + }, + "appearance": { + "title": "Vzhled", + "description": "Přizpůsobte vzhled aplikace na vašem zařízení.", + "themePreference": "Motiv", + "themes": { + "light": "Světlý", + "dark": "Tmavý", + "system": "Podle systému" + } + }, + "language": { + "title": "Jazyk", + "description": "Nastavte jazyk používaný v celé aplikaci.", + "selectLanguage": "Vyberte jazyk", + "selectPlaceholder": "Vyberte jazyk", + "helpText": "Změna jazyka aktualizuje nabídky a popisky v celé aplikaci.", + "languages": { + "en": "English", + "fr": "Français (francouzština)", + "es": "Español", + "ar": "عربى (arabština)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (řečtina)", + "de": "Deutsch (němčina)", + "da": "Dansk (dánština)", + "af": "Afrikaans (afrikánština)", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italština (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Dobré ráno", + "afternoon": "Dobré odpoledne", + "evening": "Dobrý večer" + }, + "commaName": ", {name}", + "welcomeBack": "Vítejte zpět", + "welcomeBackNamed": "Vítejte zpět, {name}", + "tagline": "Co byste dnes chtěli vytvořit?", + "brandName": "MyDevTools", + "stats": { + "tools": "Nástroje", + "favorites": "Oblíbené", + "pinned": "Připnuté", + "recent": "Nedávno použité" + }, + "sections": { + "recentlyUsed": "Nedávno použité", + "favorites": "Oblíbené", + "whatsNew": "Co je nového", + "pinned": "Připnuté" + }, + "groups": { + "apps": "Aplikace" + }, + "toolCard": { + "defaultDescription": "Prozkoumejte tento nástroj pro lepší funkce.", + "launchTool": "Spustit nástroj" + }, + "tools": { + "toDo": { + "title": "Úkoly", + "description": "Efektivně spravujte své seznamy úkolů." + }, + "notes": { + "title": "Poznámky", + "description": "Zaznamenávejte nápady a uspořádejte myšlenky." + }, + "passwordManager": { + "title": "Správce hesel", + "description": "Bezpečně ukládejte a spravujte hesla." + }, + "environmentManager": { + "title": "Správce prostředí", + "description": "Ukládejte proměnné ve stylu .env podle projektu a prostředí se značkami." + }, + "bookmarks": { + "title": "Záložky", + "description": "Spravujte a organizujte záložky." + }, + "apiClient": { + "title": "API klient", + "description": "Testujte a laděte HTTP požadavky." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Prozkoumejte databáze MongoDB." + }, + "emailValidator": { + "title": "Validátor e-mailu", + "description": "Ověřujte a validujte e-mailové adresy." + }, + "encryptionPlayground": { + "title": "Hraviště šifrování", + "description": "AES-GCM s holým klíčem nebo přístupovou frází; JSON balíčky, Web Crypto jen v prohlížeči." + }, + "jwtDecoder": { + "title": "Dekodér JWT", + "description": "Dekódujte hlavičku, payload a vypršení JWT." + }, + "certificatePemDecoder": { + "title": "Dekodér certifikátů / PEM", + "description": "Dekódujte certifikáty X.509 a CSR: subjekt, vydavatel, platnost a SAN." + }, + "sshKeyGenerator": { + "title": "Generátor klíčů SSH / RSA", + "description": "Generujte páry klíčů Ed25519 nebo RSA v prohlížeči. Stáhněte ve formátech OpenSSH a PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Formátovač GraphQL", + "description": "Formátujte GraphQL se zvýrazněním syntaxe a jednoduchým editorem dotazů." + }, + "yamlFormatter": { + "title": "Formátovač YAML", + "description": "Formátujte, ověřujte a převádějte YAML do JSON v prohlížeči." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Parser URL", + "description": "Rozdělte URL na protokol, host, cestu, dotazové parametry a hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Tajemství / API klíč", + "description": "Kryptograficky náhodné řetězce s nastavitelnou abecedou, délkou a hromadným exportem." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Kontrola kontrastu", + "description": "Kontrastní poměr WCAG 2.1 mezi textem a pozadím (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Generátor JSON schématu", + "description": "Odvození JSON schématu a typovaných modelů z ukázkového JSONu." + }, + "imageToBase64": { + "title": "Obrázek do Base64", + "description": "Převeďte obrázky na Data URI nebo nezpracované řetězce Base64." + }, + "imageCompressor": { + "title": "Komprese obrázků", + "description": "Komprimujte JPEG, PNG a WebP lokálně posuvníkem kvality." + }, + "cssGradientBuilder": { + "title": "CSS Gradient Builder", + "description": "Vizuální tvůrce přechodů CSS s ovládáním úhlu a barevnými zarážkami." + }, + "gitignoreGenerator": { + "title": "Generátor .gitignore", + "description": "Okamžitě vygenerujte sloučené soubory .gitignore z více než 500 technologických zásobníků." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Převádějte soubory CSV nebo Excel do JSON a exportujte zpět do CSV nebo XLSX." + }, + "snippetManager": { + "title": "Úryvky kódu", + "description": "Ukládejte úryvky se zvýrazněním syntaxe, automatickou detekcí jazyka a formátováním." + }, + "qrCodeGenerator": { + "title": "Generátor QR kódů", + "description": "Vytvářejte PNG QR kódy z textu nebo URL v prohlížeči." + }, + "ipSubnetCalculator": { + "title": "Kalkulačka IP / podsítě", + "description": "IPv4 a IPv6 CIDR: maska, rozsahy a počty adres." + }, + "dnsLookup": { + "title": "Vyhledávání DNS", + "description": "Dotazujte DNS záznamy pro libovolnou doménu — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Generátor hashů", + "description": "MD5, SHA-1 a SHA-2 pro text nebo soubory." + }, + "hmacGenerator": { + "title": "Generátor HMAC", + "description": "Podepisujte webhooky a testujte integrace API pomocí HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Generátor kódu TOTP / 2FA", + "description": "Vložte Base32 tajemství z autentikátoru; šestimístný kód se obnoví každých 30 s—vhodné pro testování přihlášení a MFA." + }, + "markdownPreview": { + "title": "Náhled Markdown", + "description": "Živý renderer Markdown s exportem HTML a převodem HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Převodník číselných soustav", + "description": "Převádějte celá čísla mezi soustavami o základu 2–36." + }, + "formatConverter": { + "title": "Převodník formátů", + "description": "Převod mezi JSON, YAML, TOML a XML libovolným směrem. Podporováno všech 12 kombinací." + }, + "mockDataGenerator": { + "title": "Generátor testovacích dat", + "description": "Definujte sloupce, zvolte typy a exportujte sady pro seedy, Postman nebo testy. Až 5000 řádků, vše v prohlížeči — nic se nenahrává." + }, + "httpStatusCodes": { + "title": "HTTP stavové kódy", + "description": "Hledejte HTTP stavové kódy s popisem (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Optimalizátor SVG", + "description": "Vložte SVG, odstraňte balast pomocí SVGO a uvidíte úsporu velikosti UTF-8." + }, + "unitConverter": { + "title": "Převodník jednotek", + "description": "Převádějte mezi 323 jednotkami ve 43 vědeckých a technických kategoriích." + }, + "dockerComposeGenerator": { + "title": "Generátor Docker Compose", + "description": "Vyberte databáze, cache, fronty, proxy a observabilitu; exportujte docker-compose.yml pro lokální stacky." + }, + "game2048": { + "title": "2048", + "description": "Posunutím a sloučením dlaždic dosáhneš roku 2048. Klasická číselná logická hra." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klasické číslo puzzle. 3 úrovně obtížnosti, 150 stupňů každý." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Procházejte klíče, kontrolujte hodnoty a spouštějte příkazy na jakékoli instanci Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt generátor a ověřovač", + "description": "Hashujte hesla pomocí bcrypt a ověřujte stávající hashe." + }, + "chmodCalculator": { + "title": "Kalkulačka chmod", + "description": "Převádějte unixová oprávnění mezi osmičkovým, symbolickým zápisem a zaškrtávacími políčky." + }, + "codeScreenshot": { + "title": "Snímek kódu", + "description": "Proměňte zdrojový kód v krásné sdílitelné obrázky." + }, + "cssGenerators": { + "title": "CSS generátory", + "description": "Generátory box-shadow, border-radius a plynulého clamp()." + }, + "exifViewer": { + "title": "EXIF prohlížeč a odstraňovač", + "description": "Prohlížejte a odstraňujte EXIF metadata fotek lokálně." + }, + "faviconGenerator": { + "title": "Generátor favicon", + "description": "Generujte favicony, ikony aplikací a úryvky manifestu." + }, + "jsonDiff": { + "title": "JSON diff", + "description": "Sémantické porovnání dvou JSON dokumentů zohledňující strukturu." + }, + "jsonpathPlayground": { + "title": "JSONPath hřiště", + "description": "Dotazujte se na JSON pomocí JSONPath nebo JMESPath a sledujte živé výsledky." + }, + "keycodeInspector": { + "title": "Inspektor kláves", + "description": "Stiskněte klávesu a zobrazte její JavaScriptové kódy událostí a modifikátory." + }, + "markdownTableGenerator": { + "title": "Generátor Markdown tabulek", + "description": "Vytvářejte Markdown a HTML tabulky v tabulkovém editoru připomínajícím sešit." + }, + "timezoneConverter": { + "title": "Převodník časových pásem", + "description": "Převádějte čas mezi mnoha pásmy s posunem dne a náznaky pracovní doby." + }, + "tokenCounter": { + "title": "Počítadlo LLM tokenů", + "description": "Počítejte LLM tokeny, vizualizujte bloky a odhadněte náklady na API." + }, + "webhookTester": { + "title": "Tester webhooků", + "description": "Zachytávejte a prohlížejte příchozí webhook požadavky v reálném čase." + }, + "websocketTester": { + "title": "Tester WebSocket", + "description": "Připojte se k WebSocket koncovým bodům a vyměňujte zprávy v reálném čase." + }, + "whoisLookup": { + "title": "Whois vyhledávání", + "description": "Vyhledejte registrační údaje domény a IP adresy přes RDAP." + } + }, + "tabs": { + "apps": "Aplikace", + "analytics": "Analytika", + "ui": "Rozhraní" + }, + "analytics": { + "title": "Vaše data na první pohled", + "subtitle": "Počty synchronizované s vaším účtem na serveru.", + "loadError": "Nelze načíst analytiku.", + "retry": "Zkusit znovu", + "loginHint": "Přihlaste se pro zobrazení analytiky a předvoleb rozhraní.", + "passwordEntries": "Záznamů hesel", + "bookmarks": "Záložky", + "bookmarkFolders": "Složky záložek", + "tasksTotal": "Úkoly", + "tasksCompleted": "Dokončené", + "tasksOngoing": "Probíhající", + "tasksNotStarted": "Nespuštěné", + "projects": "Projekty", + "nosqlConnections": "Připojení k DB", + "notes": "Poznámky", + "apiClientCollections": "Kolekce API", + "apiClientEnvironments": "Prostředí API", + "apiClientHistory": "Záznamy historie API", + "jsonFormatterDocuments": "Dokumenty JSON formátovače", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Úryvky kódu", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Předvolby rozhraní", + "subtitle": "Motiv, jazyk a viditelné nástroje v postranním panelu — z nastavení účtu.", + "loadError": "Nelze načíst předvolby.", + "loginHint": "Přihlaste se pro zobrazení uložených předvoleb rozhraní.", + "theme": "Motiv", + "accent": "Barva zvýraznění", + "locale": "Jazyk", + "enabledTools": "Viditelné nástroje", + "favoriteTools": "Oblíbené nástroje", + "toolsWithUsage": "Nástroje se sledováním použití" + }, + "signIn": "Přihlásit se", + "noResults": "Žádné nástroje neodpovídají vašemu hledání", + "noResultsHint": "Zkuste jiné klíčové slovo nebo vymažte filtr.", + "filterAll": "Vše", + "loginCta": { + "description": "Přihlaste se a sledujte naposledy použité nástroje." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Načítání...", + "myTasksTitle": "Moje úkoly", + "statsLine": "{total} úkolů • {percent} % hotovo", + "statsLineDesktop": "{total} celkem • {percent} % hotovo" + }, + "filters": { + "all": "Vše", + "total": "Celkem", + "allProjects": "Všechny projekty", + "export": "Export", + "kanban": "Kanban", + "list": "Seznam", + "searchPlaceholder": "Hledat úkoly...", + "searchAria": "Hledat úkoly", + "clearSearchAria": "Vymazat hledání", + "foundTasks": "Nalezeno {count} {count, plural, one {úkol} few {úkoly} many {úkolů} other {úkolů}}", + "filterByAllAria": "Filtrovat: vše", + "filterByStatusAria": "Filtrovat podle {status}" + }, + "status": { + "not-started": { + "label": "Nezahájeno", + "description": "Úkoly, které ještě nebyly zahájeny" + }, + "ongoing": { + "label": "Probíhá", + "description": "Úkoly právě rozpracované" + }, + "completed": { + "label": "Hotovo", + "description": "Dokončené úkoly" + } + }, + "priorities": { + "high": "Vysoká", + "medium": "Střední", + "low": "Nízká" + }, + "form": { + "addNewTaskLabel": "Přidat úkol", + "multilinePlaceholder": "Podrobnosti úkolu... (Shift+Enter nový řádek)", + "singlelinePlaceholder": "Co je potřeba udělat? Enter pro přidání", + "taskInputAria": "Zadání úkolu", + "projectPlaceholder": "Projekt", + "selectProject": "Vyberte projekt", + "errorSelectProject": "Vyberte projekt nebo vytvořte nový, abyste mohli přidat úkol.", + "hintMultiline": "Shift+Enter nový řádek, Enter přidá", + "hintSingleline": "Enter přidá", + "shortcutsAria": "Klávesové zkratky", + "shortcutsLabel": "Zkratky", + "shortcutAddTask": "Přidat úkol", + "shortcutNewLine": "Nový řádek", + "shortcutClear": "Vymazat", + "addButton": "Přidat" + }, + "list": { + "ariaLabel": "Seznam úkolů", + "loading": "Načítání úkolů...", + "emptyTitle": "Žádné úkoly", + "emptyPrimary": "Začněte přidáním prvního úkolu výše. Enter rychle přidá úkol.", + "emptySecondary": "Zkuste upravit filtry nebo hledaný text." + }, + "mobileDrawer": { + "addTaskSrOnly": "Přidat úkol", + "addNewTaskTitle": "Nový úkol", + "addNewTaskDescription": "Vytvořte úkol pro sledování pokroku.", + "cancel": "Zrušit" + }, + "kanban": { + "ariaLabel": "Kanban nástěnka", + "loading": "Načítání úkolů...", + "dropHere": "Přetáhněte úkol sem", + "noTasksYet": "Zatím žádné úkoly", + "emptyColumnAria": "Prázdný sloupec", + "columnAria": "Sloupec {title}, {count} úkolů", + "countInColumnAria": "{count} úkolů ve sloupci {title}" + }, + "taskItem": { + "confirmDelete": "Opravdu chcete tento úkol smazat?", + "statusPlaceholder": "Stav", + "changeStatusAria": "Změnit stav úkolu", + "taskOptionsAria": "Možnosti úkolu", + "editTask": "Upravit úkol", + "copyTask": "Kopírovat úkol", + "copied": "Zkopírováno!", + "deleteTask": "Smazat úkol", + "statusHeading": "Stav", + "editTaskAria": "Upravit úkol" + }, + "editDialog": { + "title": "Upravit úkol", + "description": "Upravte údaje, přidejte podúkoly, priority a další.", + "taskTitleLabel": "Název úkolu *", + "taskTitlePlaceholder": "Co je potřeba udělat?", + "descriptionLabel": "Popis", + "descriptionPlaceholder": "Další podrobnosti k úkolu...", + "statusLabel": "Stav", + "priorityLabel": "Priorita", + "dueDateLabel": "Termín", + "pickDate": "Vyberte datum", + "clearDate": "Vymazat datum", + "projectLabel": "Projekt", + "selectProjectPlaceholder": "Vyberte projekt", + "noProject": "Bez projektu", + "timeEstimateLabel": "Odhad času (minuty)", + "timeEstimatePlaceholder": "Jak dlouho to zabere?", + "tagsLabel": "Štítky", + "customTagNamePlaceholder": "Vlastní štítek", + "subtasksLabel": "Podúkoly", + "addSubtaskPlaceholder": "Přidat podúkol...", + "cancel": "Zrušit", + "saving": "Ukládání...", + "saveChanges": "Uložit změny" + }, + "export": { + "title": "Export úkolů", + "description": "Exportujte úkoly do JSON, CSV nebo Excel.", + "projectLabel": "Projekt", + "statusLabel": "Stav", + "tagLabel": "Štítek", + "allProjects": "Všechny projekty", + "allStatuses": "Všechny stavy", + "allTags": "Všechny štítky", + "exportAsJsonTitle": "Export jako JSON", + "exportAsJsonDescription": "Data úkolů včetně podúkolů, štítků a metadat.", + "exportJson": "Export JSON", + "exportAsCsvExcelTitle": "Export jako CSV / Excel", + "exportAsCsvExcelDescription": "Úkoly pro tabulkové aplikace.", + "exportCsv": "Export CSV", + "exportExcel": "Export Excel", + "close": "Zavřít", + "toastNoTasks": "S aktuálními filtry není co exportovat", + "toastExportedJson": "Exportováno {count} {count, plural, one {úkol} few {úkoly} many {úkolů} other {úkolů}} do JSON!", + "toastExportedCsv": "Exportováno {count} {count, plural, one {úkol} few {úkoly} many {úkolů} other {úkolů}} do CSV!", + "toastExportedExcel": "Exportováno {count} {count, plural, one {úkol} few {úkoly} many {úkolů} other {úkolů}} do Excel!", + "toastFailedJson": "Export do JSON se nezdařil", + "toastFailedCsv": "Export do CSV se nezdařil", + "toastFailedExcel": "Export do Excel se nezdařil", + "unknownProject": "Neznámý projekt" + }, + "ack": { + "taskAddedTitle": "Úkol byl přidán", + "taskAddedFailedTitle": "Úkol se nepodařilo přidat", + "taskUpdatedTitle": "Úkol byl aktualizován", + "taskUpdatedFailedTitle": "Úkol se nepodařilo aktualizovat", + "taskStatusUpdateFailedTitle": "Stav úkolu se nepodařilo změnit", + "taskMovedTitle": "Úkol přesunut do {status}", + "tryAgain": "Zkuste to prosím znovu.", + "taskDeletedTitle": "Úkol „{text}“ byl smazán", + "undo": "Zpět", + "taskRestoredTitle": "Úkol obnoven", + "taskRestoredSuccessTitle": "Úkol byl úspěšně obnoven", + "taskRestoreFailedTitle": "Obnovení úkolu se nezdařilo. Zkuste to znovu.", + "taskDeleteFailedTitle": "Smazání úkolu se nezdařilo. Zkuste to znovu.", + "tasksImportedTitle": "Úspěšně importováno {count} {count, plural, one {úkol} few {úkoly} many {úkolů} other {úkolů}}!", + "tasksImportFailedTitle": "Import úkolů se nezdařil. Zkuste to znovu.", + "unknownProject": "Neznámý projekt", + "projectCreatedTitle": "Projekt byl vytvořen", + "projectCreateFailedTitle": "Projekt se nepodařilo vytvořit", + "projectUpdatedTitle": "Projekt byl aktualizován", + "projectUpdateFailedTitle": "Projekt se nepodařilo aktualizovat", + "projectDeletedTitle": "Projekt byl smazán", + "projectDeleteFailedTitle": "Projekt se nepodařilo smazat" + } + }, + "Notes": { + "page": { + "loading": "Načítání..." + }, + "layout": { + "mobileNavTitle": "Navigace poznámek", + "openMenuAria": "Otevřít nabídku poznámek" + }, + "sidebar": { + "title": "Poznámky", + "createNoteAria": "Vytvořit poznámku", + "searchPlaceholder": "Hledat v poznámkách...", + "loading": "Načítání...", + "noNotesFound": "Žádné poznámky.", + "noNotesYet": "Zatím žádné poznámky. Klikněte na + pro vytvoření.", + "inParent": "v {parentTitle}", + "untitled": "Bez názvu", + "addSubPage": "Přidat podstránku", + "delete": "Smazat", + "deleteConfirmTitle": "Smazat poznámku?", + "deleteConfirmDescription": "Opravdu chcete smazat „{title}“? Tuto akci nelze vrátit.", + "cancel": "Zrušit", + "confirmDelete": "Smazat", + "noteOptionsAria": "Možnosti poznámky" + }, + "editor": { + "emptyState": "Vyberte poznámku nebo vytvořte novou.", + "titlePlaceholder": "Bez názvu", + "saving": "Ukládání…", + "ready": "Připraveno", + "saved": "Uloženo {time}", + "stats": "{words} slov · {read} min čtení", + "template": "Šablona", + "applyTemplate": "Použít šablonu", + "exportMdLabel": ".md", + "exportMd": "Exportovat jako Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Exportovat jako HTML", + "focusMode": "Režim soustředění", + "exitFocusMode": "Ukončit režim soustředění", + "save": "Uložit", + "addTag": "Přidat štítek…", + "removeTag": "Odebrat štítek {tag}", + "loading": "Načítání…", + "searchHint": "pro vyhledávání", + "focusHint": "režim soustředění", + "notePath": "Cesta poznámky" + }, + "context": { + "authRequiredError": "Uživatel není přihlášen", + "defaultTitle": "Bez názvu", + "cloudSyncImageError": "Zapněte cloudovou synchronizaci pro ukládání obrázků poznámek. Textové poznámky zůstávají místní." + } + }, + "PasswordManager": { + "page": { + "loading": "Načítání...", + "title": "Správce hesel", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Dešifrování hesel se nezdařilo", + "toastLoadFailed": "Načtení hesel se nezdařilo", + "toastUnlockedAuto": "Trezor byl odemčen automaticky", + "toastAutoUnlockFailed": "Automatické odemčení selhalo. Odemkněte ručně.", + "toastConnectFailed": "Připojení k serveru se nezdařilo", + "toastAutoUnlockWarning": "Trezor je odemčen, po obnovení stránky nemusí auto-odemčení fungovat", + "errorIncorrectPassword": "Nesprávné hlavní heslo", + "errorUnlockFailed": "Odemčení trezoru se nezdařilo", + "errorPasswordsMismatch": "Hesla se neshodují", + "errorPasswordTooShort": "Heslo musí mít alespoň 8 znaků", + "errorSetupFailed": "Nastavení trezoru se nezdařilo", + "errorConnectFailed": "Připojení k serveru se nezdařilo.", + "mobileTitleCreate": "Vytvořte trezor", + "mobileTitleWelcome": "Vítejte zpět", + "mobileSubtitleCreate": "Nastavte hlavní heslo pro ochranu přihlašovacích údajů", + "mobileSubtitleUnlock": "Zadejte hlavní heslo pro odemčení", + "placeholderMasterPassword": "Hlavní heslo", + "placeholderConfirmPassword": "Potvrďte heslo", + "labelMasterPassword": "Hlavní heslo", + "labelConfirmPassword": "Potvrzení hesla", + "placeholderEnterMaster": "Zadejte hlavní heslo", + "placeholderConfirmMaster": "Potvrďte hlavní heslo", + "buttonCreateVault": "Vytvořit trezor", + "buttonUnlock": "Odemknout", + "buttonUnlockVault": "Odemknout trezor", + "footerCreate": "Vaše heslo se neukládá a nelze ho obnovit", + "footerUnlock": "Chráněno end-to-end šifrováním", + "desktopTitleSetup": "Nastavení trezoru hesel", + "desktopTitleUnlock": "Odemknout trezor", + "desktopDescriptionSetup": "Vytvořte hlavní heslo pro zabezpečení údajů. Toto heslo se neukládá a nelze ho obnovit.", + "desktopDescriptionUnlock": "Zadejte hlavní heslo pro přístup k uloženým údajům." + }, + "list": { + "loading": "Načítání hesel...", + "emptyTitle": "Trezor je prázdný", + "emptyHintMobile": "Klepněte na + a přidejte první heslo", + "emptyHintDesktop": "Zatím nemáte uložená žádná hesla. Začněte tlačítkem „Přidat heslo“ výše.", + "addPassword": "Přidat heslo", + "searchPlaceholderMobile": "Hledat v trezoru...", + "searchPlaceholderDesktop": "Hledat v trezoru...", + "importExport": "Import / export", + "lockVault": "Zamknout trezor", + "allPasswordsTitle": "Všechna hesla", + "passwordCount": "{count, plural, one {# heslo} few {# hesla} many {# hesel} other {# hesel}}", + "vaultHealthDrawerTitle": "Stav trezoru", + "noResults": "Žádná hesla neodpovídají „{query}“", + "tableService": "Služba", + "tableUsername": "Uživatelské jméno", + "tablePassword": "Heslo", + "tableUrl": "URL", + "tableActions": "Akce", + "gridViewAria": "Mřížka", + "listViewAria": "Seznam", + "passwordStrengthTitle": "Síla hesla", + "deleteDialogTitle": "Smazat heslo", + "deleteDialogDescription": "Opravdu chcete toto heslo smazat? Tuto akci nelze vrátit.", + "cancel": "Zrušit", + "delete": "Smazat", + "edit": "Upravit" + }, + "toasts": { + "copiedPassword": "Heslo zkopírováno do schránky", + "copiedUsername": "Uživatelské jméno zkopírováno", + "passwordDeleted": "Heslo smazáno", + "deleteFailed": "Smazání hesla se nezdařilo", + "vaultLocked": "Trezor zamčen", + "importedCount": "Úspěšně importováno {count, plural, one {# heslo} few {# hesla} many {# hesel} other {# hesel}}" + }, + "form": { + "addTitle": "Nové heslo", + "editTitle": "Upravit heslo", + "serviceName": "Název služby", + "usernameEmail": "Uživatelské jméno / e-mail", + "password": "Heslo", + "urlOptional": "URL webu (volitelné)", + "notesOptional": "Poznámky (volitelné)", + "tags": "Štítky", + "placeholderService": "např. Netflix", + "placeholderUsername": "uzivatel@example.com", + "placeholderPassword": "Zadejte nebo vygenerujte heslo", + "placeholderUrl": "https://...", + "placeholderNotes": "Další podrobnosti...", + "placeholderTags": "Přidejte štítky (Enter nebo čárka)", + "strength": "Síla", + "strengthWeak": "Slabé", + "strengthMedium": "Střední", + "strengthStrong": "Silné", + "advancedGenerator": "Pokročilý generátor", + "cancel": "Zrušit", + "saving": "Ukládání...", + "savePassword": "Uložit heslo", + "updating": "Aktualizace...", + "updatePassword": "Aktualizovat heslo", + "addPasswordSrOnly": "Přidat heslo", + "triggerAdd": "Přidat heslo", + "toastSaved": "Heslo uloženo", + "toastSaveFailed": "Uložení hesla se nezdařilo", + "toastUpdated": "Heslo aktualizováno", + "toastUpdateFailed": "Aktualizace hesla se nezdařila" + }, + "security": { + "vaultHealth": "Stav trezoru", + "statusLabel": "Stav", + "totalPasswords": "Celkem hesel", + "activeEntries": "Aktivní záznamy v trezoru", + "protected": "Chráněno", + "weakPasswords": "Slabá hesla", + "needingImprovement": "Hesla k vylepšení", + "allGood": "V pořádku", + "reusedPasswords": "Opakovaná hesla", + "usedMultipleTimes": "Hesla použitá vícekrát", + "unique": "Jedinečná", + "total": "Celkem", + "weak": "Slabá", + "reused": "Opakovaná", + "totalCountLabel": "{count, plural, one {# heslo} few {# hesla} many {# hesel} other {# hesel}}", + "weakCountLabel": "{count} slabých", + "allSecure": "Vše v pořádku", + "scoreExcellent": "Výborně", + "scoreGood": "Dobře", + "scoreFair": "Ujde to", + "scoreCritical": "Kritické" + }, + "importExport": { + "title": "Import / export trezoru", + "description": "Spravujte data hesel. Export do JSON nebo import z JSON souboru.", + "tabExport": "Export", + "tabImport": "Import", + "warningTitle": "Upozornění", + "warningBody": "Exportované soubory obsahují hesla v čitelné podobě. Uchovávejte je v bezpečí a po použití smažte.", + "exportBlurb": "Stáhněte celý trezor jako JSON. Obsahuje služby, uživatelská jména, hesla a poznámky.", + "downloadJson": "Stáhnout JSON export", + "pasteLabel": "Vložit JSON data", + "pastePlaceholder": "[{\"service\": \"Příklad\", \"username\": \"uzivatel\", \"password\": \"123\", ...}]", + "pasteHint": "Vložte obsah dříve exportovaného JSON souboru.", + "importing": "Importování...", + "importPasswords": "Importovat hesla", + "trigger": "Import / export", + "toastExported": "Trezor úspěšně exportován", + "toastExportFailed": "Export trezoru se nezdařil", + "toastImportFailed": "Import se nezdařil: neplatný formát JSON" + }, + "card": { + "copyUsername": "Kopírovat uživatelské jméno", + "openUrl": "Otevřít URL", + "strength": "Síla" + }, + "swipeable": { + "copy": "Kopírovat", + "delete": "Smazat", + "copyPassword": "Kopírovat heslo", + "copyUsername": "Kopírovat uživatelské jméno", + "edit": "Upravit", + "deleteAction": "Smazat" + }, + "floatingMenu": { + "lockVault": "Zamknout trezor", + "importExport": "Import / export", + "addPassword": "Přidat heslo" + }, + "generator": { + "tabPassword": "Heslo", + "tabPassphrase": "Přístupová fráze", + "strength": "Síla", + "lengthLabel": "Délka: {length} znaků", + "lengthBadge": "{length} zn.", + "characterSets": "Sady znaků", + "uppercase": "Velká písmena", + "lowercase": "Malá písmena", + "numbers": "Číslice", + "symbols": "Symboly", + "wordCountLabel": "Počet slov: {count}", + "wordCountBadge": "{count, plural, one {# slovo} few {# slova} many {# slov} other {# slov}}", + "separator": "Oddělovač", + "capitalization": "Velká písmena ve slovech", + "includeNumber": "Zahrnout číslici" + } + }, + "EnvironmentManager": { + "subtitle": "Šifrované proměnné prostředí podle projektu a prostředí.", + "page": { + "loading": "Načítání…", + "title": "Správce prostředí", + "loadFailed": "Nepodařilo se načíst sady prostředí" + }, + "list": { + "title": "Prostředí", + "loading": "Načítání sad…", + "emptyTitle": "Zatím žádné sady prostředí", + "emptyHint": "Vytvořte sadu pro každý projekt a prostředí (např. produkční API klíče). Vše se před nahráním šifruje v prohlížeči.", + "searchPlaceholder": "Hledat projekt, prostředí, značky, poznámky…", + "tagFilterPlaceholder": "Filtrovat podle značky", + "variableCount": "{count, plural, one {# proměnná} few {# proměnné} other {# proměnných}}", + "updatedAgo": "Aktualizováno {time}", + "copyDotEnv": "Kopírovat .env", + "edit": "Upravit", + "delete": "Smazat", + "cancel": "Zrušit", + "confirmDelete": "Smazat", + "deleteDialogTitle": "Smazat sadu prostředí?", + "deleteDialogDescription": "Tím se odstraní šifrovaná data z účtu. Akci nelze vrátit zpět.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env zkopírováno do schránky", + "deleted": "Sada prostředí smazána", + "deleteFailed": "Smazání se nezdařilo" + }, + "form": { + "addTitle": "Nová sada prostředí", + "editTitle": "Upravit sadu prostředí", + "triggerAdd": "Přidat sadu prostředí", + "addSrOnly": "Přidat sadu prostředí", + "project": "Projekt", + "environment": "Prostředí", + "placeholderProject": "např. billing-api", + "placeholderEnvironment": "např. production", + "pasteEnvLabel": "Vložit .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Sloučit vložené řádky", + "variables": "Proměnné", + "addRow": "Přidat řádek", + "keyPlaceholder": "KLÍČ", + "valuePlaceholder": "hodnota", + "notesOptional": "Poznámky (volitelné)", + "placeholderNotes": "Rotace, vlastník, odkazy…", + "tags": "Značky", + "placeholderTags": "Přidat značky (Enter nebo čárka)", + "cancel": "Zrušit", + "save": "Uložit", + "saving": "Ukládání…", + "update": "Aktualizovat", + "updating": "Aktualizace…", + "toastSaved": "Sada prostředí uložena", + "toastSaveFailed": "Uložení se nezdařilo", + "toastUpdated": "Sada prostředí aktualizována", + "toastUpdateFailed": "Aktualizace se nezdařila", + "toastPasteEmpty": "Nenalezeny řádky KLÍČ=hodnota", + "toastPasteApplied": "Sloučeno {count} proměnných z vloženého textu", + "toastProjectEnvRequired": "Projekt a prostředí jsou povinné", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Načítání..." + }, + "manager": { + "foldersHeading": "Složky", + "tagsHeading": "Štítky", + "tagsMore": "+{count} dalších", + "tagsShowLess": "Show less", + "searchPlaceholder": "Hledat v záložkách...", + "import": "Import", + "export": "Export", + "exportAsHtml": "Export jako HTML", + "exportAsJson": "Export jako JSON", + "addBookmark": "Přidat záložku", + "matchingQuery": "odpovídá „{query}“", + "bookmarkCount": "{count, plural, one {# záložka} few {# záložky} many {# záložek} other {# záložek}}", + "allBookmarks": "Všechny záložky", + "uncategorized": "Bez kategorie", + "unknownFolder": "Neznámá složka" + }, + "card": { + "urlCopied": "URL zkopírována do schránky", + "bookmarkDeleted": "Záložka smazána", + "edit": "Upravit", + "delete": "Smazat", + "copyUrl": "Kopírovat URL", + "deleteConfirmTitle": "Smazat záložku?", + "deleteConfirmDescription": "Trvale smaže „{title}“.", + "cancel": "Zrušit" + }, + "grid": { + "emptyTitle": "Zatím žádné záložky", + "emptyDescription": "Přidejte první záložku nebo importujte z prohlížeče." + }, + "folderTree": { + "allBookmarks": "Všechny záložky", + "addSubfolder": "Přidat podsložku", + "rename": "Přejmenovat", + "deleteFolderTitle": "Smazat složku „{name}“?", + "deleteFolderDescription": "Smaže složku a všechny podsložky. Záložky budou přesunuty do Bez kategorie." + }, + "import": { + "title": "Import záložek", + "description": "Import záložek z exportovaného souboru prohlížeče.", + "parseError": "Soubor se nepodařilo načíst. Ověřte, že jde o platný export záložek.", + "successToast": "Importováno {bookmarks} záložek a {folders} složek", + "errorToast": "Import záložek se nezdařil", + "chromeInstruction": "Nabídka → Záložky → Správce záložek → ⋮ → Exportovat záložky", + "firefoxInstruction": "Nabídka → Záložky → Spravovat záložky → Import a záloha → Exportovat", + "safariInstruction": "Soubor → Exportovat záložky...", + "edgeInstruction": "Nabídka → Oblíbené → ⋯ → Exportovat oblíbené", + "dropZoneTitle": "Přetáhněte sem soubor se záložkami", + "dropZoneHint": "nebo klikněte pro výběr (HTML nebo JSON)", + "selectedFileHint": "Kliknutím vyberte jiný soubor", + "errorTitle": "Chyba", + "previewTitle": "Náhled importu", + "previewBookmarks": "{count, plural, one {# záložka} few {# záložky} many {# záložek} other {# záložek}}", + "previewFolders": "{count, plural, one {# složka} few {# složky} many {# složek} other {# složek}}", + "importModeLabel": "Režim importu", + "modeMerge": "Sloučit se stávajícími záložkami", + "modeReplace": "Nahradit všechny stávající záložky", + "cancel": "Zrušit", + "importButton": "Importovat", + "importButtonWithCount": "Importovat {count, plural, one {# záložku} few {# záložky} many {# záložek} other {# záložek}}" + }, + "addBookmark": { + "titleAdd": "Přidat záložku", + "titleEdit": "Upravit záložku", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Zadejte platnou URL", + "titleLabel": "Název", + "titlePlaceholder": "Název záložky", + "titleRequired": "Zadejte název", + "descriptionLabel": "Popis (volitelné)", + "descriptionPlaceholder": "Poznámka k této záložce", + "folderLabel": "Složka", + "folderPlaceholder": "Vyberte složku", + "noFolder": "Bez složky", + "tagsLabel": "Štítky", + "tagsPlaceholder": "Přidejte štítky (Enter)", + "toastUpdated": "Záložka aktualizována", + "toastAdded": "Záložka přidána", + "cancel": "Zrušit", + "submitAdd": "Přidat záložku", + "submitUpdate": "Aktualizovat záložku" + }, + "addFolder": { + "title": "Nová složka", + "nameLabel": "Název složky", + "namePlaceholder": "Moje složka", + "nameRequired": "Zadejte název složky", + "parentLabel": "Nadřazená složka", + "parentPlaceholder": "Vyberte nadřazenou složku", + "rootParent": "Kořen (bez nadřazené)", + "toastCreated": "Složka vytvořena", + "cancel": "Zrušit", + "submit": "Vytvořit složku" + }, + "editFolder": { + "title": "Přejmenovat složku", + "nameLabel": "Název složky", + "namePlaceholder": "Název složky", + "nameRequired": "Zadejte název složky", + "toastRenamed": "Složka přejmenována", + "cancel": "Zrušit", + "save": "Uložit" + } + }, + "EmailValidator": { + "header": { + "title": "Validátor e-mailů", + "description": "Ověřte e-mailové adresy: syntaxe, doména a doručitelnost." + }, + "tabs": { + "single": "Jednotlivě", + "bulk": "Hromadně" + }, + "single": { + "cardTitle": "Ověření jednoho e-mailu", + "cardDescription": "Zadejte adresu pro kontrolu doručitelnosti a reputace.", + "placeholder": "Zadejte e-mailovou adresu...", + "clearAria": "Vymazat e-mail", + "validate": "Ověřit", + "validating": "Ověřování...", + "formatError": "Zadejte platnou e-mailovou adresu" + }, + "toasts": { + "errorTitle": "Chyba", + "enterEmail": "Zadejte e-mailovou adresu", + "invalidFormatTitle": "Neplatný formát e-mailu", + "invalidFormatDescription": "Zadejte platnou e-mailovou adresu", + "validateFailedDescription": "Při ověřování došlo k chybě", + "noEmailsTitle": "Žádné e-maily", + "noEmailsDescription": "Ujistěte se, že Excel obsahuje sloupec „email“.", + "fileTooLargeTitle": "Soubor je příliš velký", + "fileTooLargeDescription": "Nalezeno {total} e-mailů. Ověří se pouze prvních 4000.", + "successTitle": "Úspěch", + "successValidated": "Úspěšně ověřeno {count} e-mailů.", + "batchFailedTitle": "Hromadné ověření selhalo", + "batchFailedDescription": "Při hromadném ověřování došlo k chybě.", + "noExportTitle": "Nic k exportu", + "noExportValidOnly": "Žádné platné e-maily k exportu.", + "noExportAll": "Žádné výsledky k exportu.", + "exportSuccessTitle": "Export dokončen", + "exportSuccessExcelAll": "Exportováno {count} e-mailů do Excel.", + "exportSuccessExcelValid": "Exportováno {count} platných e-mailů do Excel.", + "exportSuccessCsvAll": "Exportováno {count} e-mailů do CSV.", + "exportSuccessCsvValid": "Exportováno {count} platných e-mailů do CSV." + }, + "disposableAlert": { + "title": "Jednorázový e-mail", + "description": "Tato adresa patří poskytovateli jednorázových e-mailů. Může být dočasná nebo riziková." + }, + "score": { + "reliabilityTitle": "Skóre spolehlivosti", + "scoreLabel": "Skóre" + }, + "details": { + "sectionTitle": "Podrobnosti ověření" + }, + "validation": { + "syntaxLabel": "Platná syntaxe", + "syntaxTooltip": "Kontrola správného formátu adresy (např. uzivatel@domena.cz)", + "domainLabel": "Doména existuje", + "domainTooltip": "Ověření, že doména v adrese skutečně existuje", + "mxLabel": "Nalezeny MX záznamy", + "mxTooltip": "Kontrola, zda má doména nakonfigurované MX záznamy pro příjem pošty", + "mailboxLabel": "Schránka existuje", + "mailboxTooltip": "Ověření existence konkrétní schránky na mailovém serveru", + "disposableLabel": "Jednorázový e-mail", + "disposableTooltip": "Zda jde o dočasnou/jednorázovou službu (může být rizikové)", + "roleBasedLabel": "Roleová adresa", + "roleBasedTooltip": "Zda jde o sdílenou roli (např. podpora@, info@) místo osobní adresy" + }, + "copy": { + "emailCopied": "E-mail zkopírován!", + "copyTooltip": "Kopírovat e-mail" + }, + "bulk": { + "dropHere": "Přetáhněte soubor sem", + "dragHere": "Přetáhněte soubor sem", + "uploadHint": "Nahrajte soubor .xlsx, .xls nebo .csv se sloupcem „email“ pro ověření až 4000 adres.", + "filePrefix": "Soubor:", + "selectFile": "Vybrat soubor", + "downloadTemplate": "Stáhnout šablonu" + }, + "bulkLoading": { + "title": "Ověřování e-mailů...", + "batchProgress": "Zpracovává se dávka {current} z {total}..." + }, + "stats": { + "totalProcessed": "Celkem zpracováno", + "valid": "Platné", + "risky": "Rizikové", + "invalid": "Neplatné" + }, + "results": { + "detailedTitle": "Podrobné výsledky", + "searchPlaceholder": "Hledat e-maily...", + "newVerification": "Nové ověření", + "exportAll": "Exportovat vše", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Exportovat platné ({count})" + }, + "showingResults": "Zobrazeno {shown} z {total} výsledků", + "table": { + "email": "E-mail", + "status": "Stav", + "score": "Skóre", + "checks": "Kontroly" + }, + "noSearchResults": "Žádné výsledky pro „{query}“", + "checksTooltip": { + "syntax": "Syntaxe", + "domain": "Doména", + "mx": "MX", + "mailbox": "Schránka" + }, + "exportColumns": { + "email": "E-mail", + "status": "Stav", + "score": "Skóre", + "syntax": "Syntaxe", + "domain": "Doména", + "mx": "MX", + "mailbox": "Schránka", + "disposable": "Jednorázový", + "roleBased": "Roleová", + "valid": "Platný", + "invalid": "Neplatný", + "yes": "Ano", + "no": "Ne" + }, + "sheets": { + "validEmails": "Platné e-maily", + "results": "Výsledky", + "template": "Šablona" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "Průzkumník NoSQL", + "heroSubtitle": "Připojte se k databázím, spravujte kolekce, spouštějte dotazy a prohlížejte data.", + "featureMultiTitle": "Více připojení", + "featureMultiDesc": "Správa více databázových připojení najednou.", + "featureMongoTitle": "Podpora MongoDB", + "featureMongoDesc": "Nativní podpora MongoDB s pokročilým filtrováním.", + "featureQueryTitle": "Chytrý dotaz", + "featureQueryDesc": "Výkonná stavba dotazů se zvýrazněním syntaxe.", + "connectCta": "Připojit k MongoDB", + "footerNote": "Aktuálně podporuje {db}. Další databáze připravujeme.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Vyberte kolekci", + "selectCollectionDesc": "V postranním panelu vyberte kolekci a prohlížejte dokumenty.", + "dialogAddConnection": "Přidat připojení", + "toastConnectionAdded": "Připojení přidáno", + "toastFetchFailed": "Nepodařilo se načíst dokumenty pro {collection}", + "toastDocDeleted": "Dokument smazán", + "deleteDocTitle": "Opravdu to chcete?", + "deleteDocDescription": "Tuto akci nelze vrátit. Dokument bude trvale smazán z kolekce.", + "closeAllTitle": "Zavřít všechny karty?", + "closeAllDescription": "Zavřít všechny karty? Neuložené změny ve filtrech dotazů se ztratí.", + "closeAllConfirm": "Zavřít vše", + "cancel": "Zrušit", + "delete": "Smazat" + }, + "connection": { + "titleEdit": "Upravit připojení", + "titleConnect": "Připojit k MongoDB", + "subtitleEdit": "Upravte údaje o připojení níže.", + "subtitleConnect": "Zadejte connection string MongoDB a začněte prozkoumávat data.", + "labelName": "Název", + "placeholderName": "např. Produkční DB", + "labelConnectionString": "Connection string", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Formát: mongodb://uzivatel:heslo@host:port/databaze", + "testConnection": "Otestovat připojení", + "connecting": "Připojování...", + "updateConnect": "Aktualizovat a připojit", + "connect": "Připojit", + "cancelEdit": "Zrušit úpravy", + "savedConnections": "Uložená připojení", + "emptySaved": "Zatím žádná uložená připojení.", + "lastUsed": "Naposledy {time}", + "toastTestOk": "Připojení úspěšné!", + "toastTestFail": "Připojení selhalo: {message}", + "toastUpdated": "Připojení aktualizováno", + "confirmDelete": "Opravdu chcete smazat toto uložené připojení?", + "toastDeletedConn": "Připojení smazáno", + "toastDeleteFail": "Smazání připojení se nezdařilo", + "copyStringTitle": "Kopírovat connection string", + "toastStringCopied": "Connection string zkopírován!", + "editTitle": "Upravit", + "deleteTitle": "Smazat" + }, + "sidebar": { + "explorer": "Průzkumník", + "add": "Přidat", + "tooltipAdd": "Nové připojení", + "tooltipRefresh": "Obnovit připojení", + "searchPlaceholder": "Hledat...", + "loadingConnections": "Načítání připojení...", + "noConnectionsFound": "Žádná připojení", + "toastLoadFail": "Načtení připojení se nezdařilo", + "toastConnectFail": "Připojení k {name} se nezdařilo", + "toastCollectionsFail": "Načtení kolekcí pro {name} se nezdařilo", + "toastRenamed": "Připojení přejmenováno", + "toastRenameFail": "Přejmenování se nezdařilo", + "confirmDeleteConnection": "Opravdu smazat připojení „{name}“?", + "toastDeleted": "Připojení smazáno", + "toastDeleteConnFail": "Smazání připojení se nezdařilo", + "confirmDropDb": "Opravdu smazat databázi „{name}“? Tuto akci nelze vrátit.", + "toastDbDropped": "Databáze {name} smazána", + "confirmDropCollection": "Opravdu smazat kolekci „{name}“? Tuto akci nelze vrátit.", + "toastCollectionDropped": "Kolekce {name} smazána", + "toastCollectionRenamed": "Kolekce přejmenována na {name}", + "toastDatabaseRenamed": "Databáze přejmenována na {name}", + "connecting": "Připojování...", + "connectionFailed": "Připojení selhalo", + "noDatabases": "Žádné databáze", + "loading": "Načítání...", + "noCollections": "Žádné kolekce", + "renameCollectionTitle": "Přejmenovat kolekci", + "renameDatabaseTitle": "Přejmenovat databázi", + "placeholderNewCollection": "Nový název kolekce", + "placeholderNewDatabase": "Nový název databáze", + "rename": "Přejmenovat", + "refresh": "Obnovit", + "copyDbName": "Kopírovat název DB", + "toastDbNameCopied": "Název databáze zkopírován", + "dropDatabase": "Smazat databázi", + "dropCollection": "Smazat kolekci", + "menuRename": "Přejmenovat", + "menuCopyConnectionString": "Kopírovat connection string", + "menuDeleteConnection": "Smazat připojení", + "toastSidebarStringCopied": "Connection string zkopírován!", + "cancel": "Zrušit", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} dok.", + "table": "Tabulka", + "json": "JSON", + "tree": "Strom", + "tooltipTable": "Tabulkové zobrazení", + "tooltipJson": "JSON zobrazení", + "tooltipTree": "Stromové zobrazení", + "itemsPerPage": "Položek na stránku", + "perPage": "{n} / str.", + "prevPage": "Předchozí stránka", + "nextPage": "Další stránka", + "expandAll": "Rozbalit vše", + "collapseAll": "Sbalit vše", + "refreshData": "Obnovit data", + "insert": "Vložit", + "export": "Export", + "actions": "Akce", + "invalidJson": "Neplatný obsah JSON", + "copiedClipboard": "Zkopírováno do schránky", + "docUpdated": "Dokument úspěšně aktualizován", + "docInserted": "Dokument úspěšně vložen", + "invalidJsonShort": "Neplatný JSON", + "docCopied": "Dokument zkopírován", + "openFullEditor": "Otevřít v editoru", + "cellArray": "Pole({length})", + "cellObject": "Objekt", + "cellArrayBracket": "Pole[{length}]", + "emptyFilterTitle": "Žádné dokumenty neodpovídají dotazu", + "emptyFilterHint": "Upravte filtr nebo vymažte dotaz.", + "clearFilter": "Vymazat filtr", + "emptyCollectionTitle": "Kolekce je prázdná", + "emptyCollectionHint": "Vložte dokument a začněte.", + "insertDocument": "Vložit dokument", + "formatJson": "Formátovat JSON", + "compactJson": "Zhustit JSON", + "copyClipboard": "Kopírovat do schránky", + "copyJson": "Kopírovat JSON", + "duplicate": "Duplikovat", + "editDoc": "Upravit dokument", + "deleteDoc": "Smazat dokument", + "documentLabel": "Dokument {n}", + "editDialogTitle": "Upravit dokument", + "insertDialogTitle": "Vložit dokument", + "viewValueTitle": "Zobrazit hodnotu", + "saveChanges": "Uložit změny", + "close": "Zavřít", + "cancel": "Zrušit", + "insertButton": "Vložit", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Aktivní filtr", + "filterActiveTooltip": "Filtr je aktivní", + "closeAll": "Zavřít vše" + }, + "query": { + "migrateHistory": "Historie dotazů migrována do cloudu", + "saveHistoryFail": "Uložení historie dotazů se nezdařilo", + "deleteQueryFail": "Smazání dotazu se nezdařilo", + "invalidJsonQuery": "Neplatný JSON dotaz", + "placeholder": "např. { \"status\": \"active\" }", + "clearQuery": "Vymazat dotaz", + "openAdvanced": "Pokročilý editor", + "visualBuilder": "Vizuální tvůrce dotazu", + "filterRules": "Pravidla filtru", + "addRule": "Přidat pravidlo", + "noFilters": "Žádné filtry. Přidejte pravidlo.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "Pole", + "opPlaceholder": "Operátor", + "typePlaceholder": "Typ", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Not In", + "opExists": "Exists", + "typeAuto": "Auto", + "typeString": "Řetězec", + "typeNumber": "Číslo", + "typeBoolean": "Boolean", + "typeDate": "Datum", + "typeObjectId": "ObjectId", + "true": "Pravda", + "false": "Nepravda", + "valuePlaceholder": "Hodnota", + "valueListPlaceholder": "hodnota1, hodnota2", + "clearFilters": "Vymazat filtry", + "applyFilters": "Použít filtry", + "queryHistory": "Historie dotazů", + "noSavedQueries": "Žádné uložené dotazy", + "advancedEditorTitle": "Pokročilý editor dotazů", + "templatesPlaceholder": "Šablony", + "templateEmptyAgg": "Prázdná agregace", + "templateMatch": "$match (filtr)", + "templateLookup": "$lookup (join)", + "templateGroup": "$group (počet/součet)", + "format": "Formátovat", + "formattedJson": "Formátovaný JSON", + "runQuery": "Spustit dotaz" + }, + "export": { + "selectFieldsError": "Vyberte alespoň jedno pole k exportu", + "success": "Úspěšně exportováno do {filename}", + "fail": "Export dat se nezdařil", + "title": "Export dat", + "formatLabel": "Formát exportu", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Vybrat pole", + "selectAll": "Vybrat vše", + "fieldsSelected": "{count, plural, one {# pole vybráno} few {# pole vybrána} many {# polí vybráno} other {# polí vybráno}}", + "cancel": "Zrušit", + "export": "Export", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Typ: {type}", + "itemSummary": "{count, plural, one {# položka} few {# položky} many {# položek} other {# položek}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Nový požadavek", + "importedRequest": "Importovaný požadavek", + "untitledRequest": "Nepojmenovaný požadavek" + }, + "toasts": { + "curlImported": "cURL úspěšně importován", + "curlParseFailed": "Parsování příkazu cURL se nezdařilo", + "invalidJsonBody": "Neplatné tělo JSON", + "requestFailed": "Požadavek selhal: {message}", + "curlPasted": "cURL vložen a zpracován", + "responseCopied": "Odpověď zkopírována", + "codeCopied": "Kód zkopírován", + "copyFailed": "Nepodařilo se zkopírovat do schránky" + }, + "layout": { + "collections": "Kolekce", + "closeSidebar": "Zavřít postranní panel", + "openSidebar": "Otevřít postranní panel", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Metoda", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Odesílání...", + "send": "Odeslat", + "invalidJsonBodyHelp": "Nelze odeslat: tělo JSON není platné" + }, + "requestTabs": { + "params": "Parametry", + "headers": "Hlavičky", + "body": "Tělo", + "auth": "Autentizace", + "bodyType": "Typ těla", + "selectType": "Vyberte typ", + "bodyNone": "Žádné", + "bodyJson": "JSON", + "bodyText": "Text", + "noBodyHint": "Tento požadavek nemá tělo", + "switchToJson": "Přepnout na JSON tělo", + "authType": "Typ autentizace", + "noAuth": "Bez autentizace", + "bearerToken": "Bearer token", + "basicAuth": "Základní autentizace", + "apiKey": "API klíč", + "labelToken": "Token", + "placeholderBearerToken": "Bearer token", + "labelUsername": "Uživatelské jméno", + "placeholderUsername": "Uživatelské jméno", + "labelPassword": "Heslo", + "placeholderPassword": "Heslo", + "labelKey": "Klíč", + "placeholderApiKeyName": "Název klíče (např. X-Api-Key)", + "labelValue": "Hodnota", + "placeholderValue": "Hodnota", + "addTo": "Přidat do", + "header": "Hlavička", + "queryParams": "Dotazové parametry", + "noAuthHint": "Tento požadavek nepoužívá autentizaci." + }, + "responsePanel": { + "emptyTitle": "Připraveno k prozkoumání?", + "emptyHint": "Zadejte URL a klikněte Odeslat pro zobrazení odpovědi.", + "responsePreviewAlt": "Náhled odpovědi", + "previewUnavailable": "Náhled není pro tento typ obsahu k dispozici", + "status": "Stav", + "time": "Čas", + "size": "Velikost", + "copyBody": "Kopírovat tělo", + "downloadResponse": "Stáhnout odpověď", + "bodyTab": "Tělo", + "previewTab": "Náhled", + "headersTab": "Hlavičky", + "binaryRawView": "Binární data nelze zobrazit v surovém zobrazení.", + "errorStatusLabel": "Chyba", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Nová karta" + }, + "collectionsSidebar": { + "title": "API zdroje", + "newCollection": "Nová kolekce", + "tabCollections": "Kolekce", + "tabHistory": "Historie", + "noCollectionsTitle": "Žádné kolekce", + "noCollectionsHint": "Vytvořte kolekci pro organizaci požadavků.", + "createCollection": "Vytvořit kolekci", + "newFolder": "Nová složka", + "rename": "Přejmenovat", + "delete": "Smazat", + "emptyCollection": "Vyprázdnit kolekci", + "recentRequests": "Nedávné požadavky", + "clearAll": "Vymazat vše", + "noHistoryTitle": "Zatím žádná historie", + "noHistoryHint": "Odešlete požadavek a zobrazí se zde.", + "dialogNewFolderTitle": "Nová složka", + "dialogNewFolderDescription": "Nová složka pro organizaci požadavků.", + "labelFolderName": "Název složky", + "placeholderFolderName": "Moje složka", + "createFolder": "Vytvořit složku", + "dialogNewCollectionTitle": "Nová kolekce", + "dialogNewCollectionDescription": "Nová kolekce na nejvyšší úrovni.", + "labelCollectionName": "Název kolekce", + "placeholderCollectionName": "Moje kolekce", + "dialogRenameCollectionTitle": "Přejmenovat kolekci", + "dialogRenameCollectionDescription": "Zadejte nový název kolekce.", + "placeholderRenameCollection": "Název kolekce", + "renameAction": "Přejmenovat", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Nová složka", + "delete": "Smazat", + "emptyFolder": "Vyprázdnit složku" + }, + "environmentManager": { + "noEnvironment": "Žádné prostředí", + "manageDialogTitle": "Správa prostředí", + "placeholderNewEnv": "Nové prostředí", + "variablesHeading": "Proměnné: {name}", + "addVariable": "Přidat proměnnou", + "columnVariable": "Proměnná", + "columnValue": "Hodnota", + "placeholderKey": "Klíč", + "placeholderValue": "Hodnota", + "selectEnvToEdit": "Vyberte prostředí k úpravě" + }, + "codeGenerator": { + "triggerTitle": "Generovat kód", + "dialogTitle": "Generovat kód", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Kopírovat", + "errorGenerating": "Chyba při generování kódu" + }, + "keyValueEditor": { + "columnKey": "Klíč", + "columnValue": "Hodnota", + "placeholderKey": "Klíč", + "placeholderValue": "Hodnota", + "addItem": "Přidat položku" + }, + "importCurl": { + "trigger": "Importovat cURL", + "dialogTitle": "Importovat cURL", + "dialogDescription": "Vložte příkaz cURL pro import jako nový požadavek.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Importovat" + }, + "saveRequest": { + "trigger": "Uložit", + "dialogTitle": "Uložit požadavek", + "dialogDescription": "Uložit tento požadavek do složky kolekce.", + "labelName": "Název požadavku", + "placeholderName": "Můj požadavek", + "labelFolder": "Složka", + "placeholderFolder": "Vyberte složku", + "save": "Uložit" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Režim úprav", + "viewOnlyMode": "Pouze pro čtení", + "insertComponent": "Vložit komponentu", + "addUnorderedList": "Odrážkový seznam", + "addOrderedList": "Číslovaný seznam", + "addTable": "Přidat tabulku" + }, + "addBlock": "Přidat blok", + "media": { + "popoverTitle": "Přidat média", + "singleImage": "Jeden obrázek", + "singleImageDesc": "Nahrát jeden obrázek", + "multipleImages": "Více obrázků", + "multipleImagesDesc": "Mřížka obrázků", + "video": "Video", + "videoDesc": "Nahrát video", + "uploadingAlt": "Nahrávání...", + "uploadFailedAlt": "Nahrání selhalo" + }, + "export": { + "fabAria": "Export kódu", + "title": "Export kódu", + "description": "Zkopírujte HTML nebo JSON výstup obsahu editoru", + "preview": "Náhled", + "html": "HTML", + "json": "JSON", + "previewOptions": "Možnosti náhledu", + "enhanceSpaces": "Vylepšit mezery", + "livePreview": "Živý náhled vykresleného HTML", + "htmlTailwind": "HTML s třídami Tailwind CSS", + "editorJson": "Stav editoru jako JSON", + "copyHtml": "Kopírovat HTML", + "copyJson": "Kopírovat JSON", + "copied": "Zkopírováno!" + }, + "template": { + "fabTitle": "Změnit šablonu", + "title": "Vyberte šablonu", + "description": "Rychlý start s předpřipraveným rozvržením a obsahem", + "catAll": "Všechny šablony", + "catProductivity": "Produktivita", + "catCreative": "Kreativní", + "catBusiness": "Byznys", + "catPersonal": "Osobní", + "noTemplates": "V této kategorii nejsou šablony", + "confirmTitle": "Nahradit stávající obsah?", + "confirmDescription": "V editoru už je obsah. Použití šablony ho kompletně nahradí.", + "cannotUndo": "Tuto akci nelze vrátit.", + "cancel": "Zrušit", + "replaceContent": "Nahradit obsah" + }, + "command": { + "searchPlaceholder": "Hledat příkazy...", + "noResults": "Žádné příkazy.", + "turnInto": "Změnit na", + "h1": { + "label": "Nadpis 1", + "description": "Velký nadpis sekce", + "keywords": "h1,nadpis,titulek,velký" + }, + "h2": { + "label": "Nadpis 2", + "description": "Střední nadpis sekce", + "keywords": "h2,nadpis,podtitulek" + }, + "h3": { + "label": "Nadpis 3", + "description": "Malý nadpis sekce", + "keywords": "h3,nadpis,podnadpis" + }, + "h4": { + "label": "Nadpis 4", + "description": "Drobný nadpis", + "keywords": "h4,nadpis" + }, + "h5": { + "label": "Nadpis 5", + "description": "Menší nadpis", + "keywords": "h5,nadpis" + }, + "h6": { + "label": "Nadpis 6", + "description": "Nejmenší nadpis", + "keywords": "h6,nadpis" + }, + "p": { + "label": "Odstavec", + "description": "Běžný textový odstavec", + "keywords": "p,odstavec,text,normal" + }, + "code": { + "label": "Blok kódu", + "description": "Úryvek kódu", + "keywords": "code,kód,snippet,pre" + }, + "blockquote": { + "label": "Citace", + "description": "Bloková citace", + "keywords": "citace,blockquote" + }, + "li": { + "label": "Odrážkový seznam", + "description": "Položka s odrážkou", + "keywords": "seznam,odrážka,ul,li" + }, + "ol": { + "label": "Číslovaný seznam", + "description": "Číslovaná položka", + "keywords": "seznam,číslo,ol,li" + }, + "img": { + "label": "Obrázek", + "description": "Nahrát nebo vložit obrázek", + "keywords": "obrázek,foto,nahrát" + }, + "video": { + "label": "Video", + "description": "Nahrát nebo vložit video", + "keywords": "video,film,mp4,nahrát" + }, + "table": { + "label": "Tabulka", + "description": "Vytvořit tabulku", + "keywords": "tabulka,mřížka,řádky,sloupce" + } + }, + "toasts": { + "tableImported": "Tabulka importována", + "tableImportedDesc": "Markdown tabulka byla úspěšně importována", + "mediaPasted": "Média vložena", + "pasteMixed": "Úspěšně vloženo {imageCount} obr. a {videoCount} videí.", + "pasteImages": "Úspěšně vloženo {count} obrázků.", + "pasteVideos": "Úspěšně vloženo {count} videí.", + "pasteFailed": "Vložení selhalo", + "unexpectedError": "Došlo k neočekávané chybě", + "uploadFailed": "Nahrání selhalo" + }, + "blockBackground": { + "menuItem": "Změnit barvu pozadí", + "dialogTitle": "Barva pozadí bloku", + "dialogDescription": "Vyberte barvu pozadí tohoto bloku", + "tabPreset": "Předvolené barvy", + "tabCustom": "Vlastní barva", + "sectionHeading": "Barvy pozadí", + "apply": "Použít", + "colors": { + "none": "Žádná", + "red": "Červená", + "orange": "Oranžová", + "yellow": "Žlutá", + "green": "Zelená", + "blue": "Modrá", + "indigo": "Indigo", + "purple": "Fialová", + "pink": "Růžová", + "teal": "Tyrkysová", + "cyan": "Azurová", + "gray": "Šedá" + } + }, + "tableDialog": { + "title": "Vytvořit tabulku", + "description": "Nová tabulka nebo import z markdownu", + "markdownCheckbox": "Mám markdown tabulku", + "pasteMarkdownLabel": "Vložit markdown tabulku", + "markdownPlaceholder": "| Hlavička 1 | Hlavička 2 | Hlavička 3 |\n|------------|------------|------------|\n| Buňka 1 | Buňka 2 | Buňka 3 |\n| Buňka 4 | Buňka 5 | Buňka 6 |", + "markdownHint": "Vložte tabulku výše. Musí obsahovat řádek hlavičky a oddělovač.", + "rows": "Řádky", + "columns": "Sloupce", + "maxDimensions": "Maximum: 20 řádků × 10 sloupců", + "cancel": "Zrušit", + "importTable": "Importovat tabulku", + "createTable": "Vytvořit tabulku" + }, + "tableParse": { + "errors": { + "minRows": "Tabulka musí mít alespoň hlavičku a oddělovací řádek", + "pipeFormat": "Řádky musí začínat a končit znakem |", + "headerMinCol": "Hlavička musí mít alespoň jeden sloupec", + "separatorRow": "Druhý řádek musí být oddělovač (např. |---|---|)", + "rowColumnMismatch": "Řádek {row} má {got} sloupců, očekáváno {expected}", + "parseFailed": "Parsování markdown tabulky selhalo" + } + }, + "link": { + "editLink": "Upravit odkaz", + "addLink": "Přidat odkaz", + "selectedText": "Vybraný text: „{text}“", + "linkUrl": "URL odkazu", + "urlPlaceholder": "https://example.com", + "update": "Aktualizovat", + "addLinkButton": "Přidat odkaz", + "toastApplied": "Odkaz použit", + "toastLinkedTo": "Odkaz na: {url}", + "toastRemoved": "Odkaz odebrán", + "toastRemovedDesc": "Odkaz byl odebrán z výběru" + }, + "customClass": { + "devMode": "Vývojářský režim", + "toggleDevModeAria": "Přepnout vývojářský režim", + "searchPlaceholderDev": "Hledat třídy... (např. text, bg, flex)", + "searchPlaceholderUser": "Hledat styly... (např. červená, tučné, stín)", + "appliesTitle": "Použito: {className}", + "noMatches": "Žádné třídy pro „{query}“", + "toastClassReplaced": "Třída nahrazena", + "toastClassReplacedDesc": "„{replaced}“ nahrazeno „{className}“", + "toastCustomClassApplied": "Vlastní třída použita", + "toastAppliedClass": "Použita třída: {className}" + }, + "insertComponents": { + "title": "Vložit komponentu", + "description": "Vyberte komponentu pro vložení do dokumentu", + "noComponents": "Žádné komponenty k dispozici", + "categories": { + "media": "Média", + "layout": "Rozvržení", + "interactive": "Interaktivní", + "decoration": "Dekorace" + }, + "freeImage": { + "name": "Obrázek volného pohybu", + "description": "Obrázek s libovolnou pozicí a velikostí" + } + } + }, + "JsonFormatter": { + "title": "Formátovač JSON", + "description": "Formátujte, validujte a upravujte JSON v textovém i stromovém zobrazení.", + "documentNameText": "Textový dokument {n}", + "documentNameTree": "Stromový dokument {n}", + "newDocument": "Nový", + "copy": "Kopírovat", + "save": "Uložit", + "load": "Načíst", + "saving": "Ukládání...", + "leftDocNameLabel": "Název levého dokumentu", + "rightDocNameLabel": "Název pravého dokumentu", + "toastCopiedText": "Textový panel zkopírován", + "toastCopiedTree": "Stromový panel zkopírován", + "toastCopyFailed": "Nepodařilo se zkopírovat JSON", + "toastLoginRequired": "Pro uložení dokumentů se přihlaste", + "toastSaved": "Dokument uložen", + "toastSaveFailed": "Nepodařilo se uložit dokument", + "format": "Formátovat", + "compact": "Zkomprimovat", + "sortKeys": "Seřadit klíče", + "repair": "Opravit", + "query": "Dotaz", + "toastInvalidJson": "Neplatný JSON", + "toastAlreadyValid": "JSON je již platný", + "toastRepaired": "JSON opraven", + "toastRepairFailed": "JSON se nepodařilo opravit" + }, + "JwtDecoder": { + "title": "Dekodér JWT", + "subtitle": "Prohlédněte si hlavičku a payload. Exspirace používá claim exp. Nic se neposílá na server; podpis se neověřuje.", + "tokenLabel": "Token", + "clear": "Vymazat", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopírovat", + "expiryLabel": "Vypršení (exp)", + "signaturePresent": "Podpis přítomen (neověřen)", + "noExp": "V payload chybí claim exp.", + "status": { + "expired": "Vypršel", + "valid": "Platný" + }, + "fields": { + "unix": "Unix", + "local": "Místní", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Vydáno (iat)", + "notBefore": "Ne dříve než (nbf)" + }, + "panels": { + "header": "Hlavička", + "payload": "Payload" + }, + "errors": { + "empty": "Vložte JWT pro dekódování.", + "segments": "JWT potřebuje alespoň hlavičku a payload (dva segmenty).", + "base64": "Nepodařilo se dekódovat hlavičku nebo payload (base64url)." + }, + "relative": { + "future": "za {n} {unit}", + "past": "před {n} {unit}" + }, + "units": { + "second": "{count, plural, one {sekundu} few {sekundy} other {sekund}}", + "minute": "{count, plural, one {minutu} few {minuty} other {minut}}", + "hour": "{count, plural, one {hodinu} few {hodiny} other {hodin}}", + "day": "{count, plural, one {den} few {dny} other {dní}}" + }, + "tabs": { + "decode": "Dekódovat", + "sign": "Generátor / podepisovač" + }, + "signer": { + "algLabel": "Algoritmus (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "JSON payload", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON header", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Tajemství (HS*)", + "secretPlaceholder": "Vložte svůj HMAC secret…", + "privateKeyLabel": "Soukromý klíč PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Algoritmus \"none\" vytvoří nepodepsaný token. Je to nebezpečné a má sloužit jen pro testování.", + "sign": "Podepsat", + "verify": "Ověřit podpis", + "outputLabel": "Token (kompaktní JWS)", + "outputPlaceholder": "Klikněte na „Podepsat“…", + "copyToken": "Kopírovat token", + "copied": "Zkopírováno", + "verifyKeyLabel": "Ověřovací klíč (volitelné)", + "verifyKeyPlaceholder": "Pro RS*: vložte veřejný klíč (SPKI) PEM. Pro HS*: nechte prázdné a použije se secret.", + "verifyKeyHint": "Pokud je prázdné, ověření použije podepisovací klíč (užitečné pro HS*).", + "verifyStatus": { + "valid": "Podpis je platný", + "invalid": "Podpis je neplatný" + }, + "securityNote": "Vše běží lokálně v prohlížeči; klíče ani tokeny se nikam neodesílají." + }, + "signErrors": { + "invalidJson": "Header a payload musí být platný JSON.", + "unsupportedAlg": "Nepodporovaný algoritmus.", + "missingKey": "Pro tento algoritmus je vyžadován podepisovací klíč.", + "cryptoFailed": "Podepisování selhalo. Zkontrolujte formát klíče a zkuste to znovu." + }, + "verifyErrors": { + "empty": "Zadejte JWT k ověření.", + "segments": "JWT musí mít pro ověření podpisu přesně 3 segmenty.", + "unsupportedAlg": "Nepodporovaný nebo chybějící alg v headeru.", + "missingKey": "Pro tento algoritmus je vyžadován ověřovací klíč.", + "base64": "Nepodařilo se dekódovat header nebo podpis JWT.", + "cryptoFailed": "Ověření selhalo. Zkontrolujte formát klíče a zkuste to znovu." + } + }, + "CertificatePemDecoder": { + "title": "Dekodér certifikátů / PEM", + "subtitle": "Vložte certifikát X.509 (-----BEGIN CERTIFICATE-----) nebo CSR (-----BEGIN CERTIFICATE REQUEST-----). Subjekt, vydavatel, data a SAN se zpracují lokálně; nic se nenahrává.", + "pemLabel": "Vstup PEM", + "clear": "Vymazat", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Zpracovávání…", + "copyHex": "Kopírovat otisk (hex bez dvojteček)", + "errors": { + "empty": "Vložte PEM certifikát nebo CSR k dekódování.", + "noBlock": "Nebyl nalezen PEM blok certifikátu ani CSR.", + "parseFailed": "Tato PEM data se nepodařilo zpracovat." + }, + "block": { + "title": "PEM blok {index}", + "certificate": "Certifikát X.509", + "csr": "Žádost o podpis certifikátu" + }, + "fields": { + "subject": "Subjekt", + "issuer": "Vydavatel", + "notBefore": "Neplatné před", + "notAfter": "Neplatné po", + "serial": "Sériové číslo (hex)", + "sha256": "Otisk SHA-256", + "sans": "Alternativní jména subjektu", + "sansNone": "Rozšíření SAN není přítomno.", + "signatureAlgorithm": "Algoritmus podpisu", + "csrIssuerNote": "CSR neobsahuje vydavatele (ten je na vydaném certifikátu).", + "csrValidityNote": "CSR neobsahuje platnost (ta je na vydaném certifikátu)." + }, + "sanType": { + "dns": "DNS", + "dn": "Adresářové jméno", + "email": "E-mail", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Registrované ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Formátovač SQL", + "subtitle": "Formátujte SQL pro MySQL, PostgreSQL nebo SQLite. Běží v prohlížeči; nic se neodesílá.", + "dialectLabel": "Dialekt", + "keywordsLabel": "Klíčová slova", + "indentLabel": "Odsazení", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "VELKÁ", + "lower": "malá", + "preserve": "Zachovat" + }, + "spaces": "{n} mezer", + "format": "Formátovat", + "clear": "Vymazat", + "counter": "{current} / {max} znaků", + "inputPanel": "Vstup", + "formattedPanel": "Naformátováno", + "copy": "Kopírovat", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klikněte na „Formátovat“…", + "errors": { + "tooLong": "Vstup je omezen na {max} znaků.", + "couldNotFormat": "Tento SQL se nepodařilo naformátovat." + } + }, + "YamlFormatter": { + "title": "Formátovač YAML", + "subtitle": "Formátujte, ověřujte a převádějte YAML v prohlížeči. Nic se nenahrává.", + "outputLabel": "Výstup", + "outputModes": { + "yaml": "YAML (formátovaný)", + "json": "JSON" + }, + "indentLabel": "Odsazení", + "spaces": "{n} mezery", + "format": "Formátovat", + "clear": "Vymazat", + "copy": "Kopírovat", + "counter": "{current} / {max} znaků", + "inputPanel": "Vstup", + "outputPanel": "Výstup", + "errors": { + "tooLong": "Vstup je omezen na {max} znaků.", + "couldNotFormat": "Nepodařilo se analyzovat tento YAML." + } + }, + "GraphqlFormatter": { + "title": "Formátovač GraphQL", + "subtitle": "Formátujte dotazy, mutace a odběry GraphQL v prohlížeči. Nic se neodesílá.", + "tabs": { + "format": "Formátovat", + "build": "Editor dotazu" + }, + "outputModeLabel": "Výstup", + "outputModes": { + "pretty": "Čitelný výpis", + "minify": "Minifikovat" + }, + "format": "Formátovat", + "clear": "Vymazat", + "counter": "{current} / {max} znaků", + "inputPanel": "Vstup", + "outputPanel": "Výstup", + "copy": "Kopírovat", + "builderHint": "Sestavte kostru dokumentu a vložte ji do editoru. Definice proměnných v syntaxi GraphQL (např. $id: ID!) — závorky jsou volitelné.", + "builder": { + "operation": "Operace", + "operationName": "Název operace", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definice proměnných", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Výběrová sada", + "apply": "Použít na vstup", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Vstup je omezen na {max} znaků.", + "couldNotFormat": "Tento GraphQL se nepodařilo parsovat ani vypsat.", + "emptySelection": "Před použitím doplňte výběrovou sadu." + } + }, + "RegexTester": { + "title": "Tester regex", + "subtitle": "Regex ve stylu JavaScript živě nad vaším textem. Shody jsou zvýrazněny; nic neopustí prohlížeč.", + "patternLabel": "Vzor", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g globální", + "i": "i ignorovat velikost písmen", + "m": "m víceřádkové", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(prázdné)", + "noFlags": "—", + "matches": "{count, plural, one {shoda} few {shody} other {shod}}", + "testTextLabel": "Testovací text", + "counter": "{current} / {max}", + "clear": "Vymazat", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Text je omezen na {max} znaků.", + "invalid": "Neplatný regulární výraz.", + "runaway": "Zastaveno po {max} shodách (možný „runaway“ vzor)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Převodník časových razítek", + "subtitle": "Unix sekundy (≤10 číslic) nebo milisekundy (delší celá čísla), ISO-8601 nebo řetězce, které prohlížeč umí přečíst. Vše běží lokálně.", + "inputLabel": "Vstup", + "placeholder": "1744200000 nebo 2025-04-09T12:00:00Z", + "now": "Teď", + "copy": "Kopírovat", + "hint": "Vložte hodnotu nebo klikněte na Teď.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativní", + "human": "Čitelné" + }, + "fields": { + "seconds": "Sekundy", + "milliseconds": "Milisekundy", + "utc": "UTC", + "local": "Lokálně", + "distance": "Rozdíl" + }, + "errors": { + "empty": "Zadejte unixové razítko, ISO řetězec nebo text data.", + "invalidNumber": "Neplatné číselné razítko.", + "outOfRange": "Mimo rozsah pro JavaScript Date.", + "couldNotParse": "Nelze přečíst jako Unix, ISO-8601 nebo datum prohlížeče." + } + }, + "HttpStatusCodes": { + "title": "Stavové kódy HTTP", + "subtitle": "Vyhledávejte a procházejte stavové kódy HTTP odpovědí (1xx5xx) s krátkými popisy a odkazy na RFC. Běží lokálně v prohlížeči.", + "header": { + "title": "Stavové kódy HTTP", + "description": "Prohledávatelný seznam HTTP kódů odpovědí s popisy a odkazy na RFC." + }, + "searchPlaceholder": "Hledat: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtrovat třídu…", + "classFilterAll": "Všechny třídy", + "resultsCount": "{count} z {total}", + "table": { + "code": "Code", + "phrase": "Fráze", + "description": "Popis", + "copy": "Kopírovat" + }, + "noResults": "Žádné výsledky pro “{query}”.", + "specPrefix": "Spec.:", + "copied": "Kód zkopírován" + }, + "CronBuilder": { + "title": "Tvůrce cron výrazů", + "subtitle": "Standardní cron s 5 poli: minute hour day month weekday. Parser interně používá seconds=0. Volitelný 6-pólový formát (sec min hour dom mon dow) funguje v editoru.", + "tabs": { + "builder": "Vizuální tvůrce", + "expression": "Výraz" + }, + "presetsLabel": "Předvolby", + "fieldsLabel": "Pole", + "rawExpressionLabel": "Surový výraz", + "expressionHint": "Opuštěním pole dojde k parsování a synchronizaci tvůrce. {count} {count, plural, one {token} few {tokeny} other {tokenů}}.", + "currentExpressionLabel": "Aktuální výraz", + "copy": "Kopírovat", + "quickPick": "Rychlý výběr", + "emptyExpression": "—", + "plainLanguageLabel": "Popis", + "nextRunsLabel": "Další spuštění (místní čas)", + "fields": { + "minute": "Minuta", + "hour": "Hodina", + "dayOfMonth": "Den v měsíci", + "month": "Měsíc", + "dayOfWeek": "Den v týdnu" + }, + "presets": { + "everyMinute": "Každou minutu", + "every5Minutes": "Každých 5 minut", + "every15Minutes": "Každých 15 minut", + "everyHour": "Každou hodinu (v :00)", + "every6Hours": "Každých 6 hodin", + "dailyMidnight": "Denně o půlnoci", + "daily0900": "Denně v 09:00", + "weekdays0900": "Ve všední dny v 09:00", + "weeklySunday": "Týdně (neděle 00:00)", + "monthlyFirst": "Měsíčně (1. 00:00)", + "yearlyJan1": "Ročně (1. led 00:00)" + }, + "picks": { + "minute": { + "every": "každou minutu" + }, + "hour": { + "every": "každou hodinu" + }, + "dom": { + "any": "libovolný den", + "last": "poslední den" + }, + "month": { + "every": "každý měsíc" + }, + "dow": { + "any": "libovolný den", + "weekdays": "všední dny", + "weekend": "víkend" + } + }, + "months": { + "jan": "Led", + "feb": "Úno", + "mar": "Bře", + "apr": "Dub", + "may": "Kvě", + "jun": "Čvn", + "jul": "Čvc", + "aug": "Srp", + "sep": "Zář", + "oct": "Říj", + "nov": "Lis", + "dec": "Pro" + }, + "weekdays": { + "sun": "Ne", + "mon": "Po", + "tue": "Út", + "wed": "St", + "thu": "Čt", + "fri": "Pá", + "sat": "So" + }, + "errors": { + "fieldCount": "Očekáváno 5 polí (minute hour day month weekday) nebo 6 včetně sekund.", + "invalid": "Neplatný cron výraz." + } + }, + "Base64": { + "title": "Base64 kodér / dekodér", + "subtitle": "Kódujte text do Base64 nebo dekódujte Base64 řetězce", + "upload": "Nahrát", + "clear": "Vymazat", + "modes": { + "encode": "Kódovat", + "decode": "Dekódovat" + }, + "swapTitle": "Prohodit vstup a výstup", + "panels": { + "plainText": "Prostý text", + "base64Input": "Vstup Base64", + "base64Output": "Výstup Base64", + "decodedText": "Dekódovaný text" + }, + "placeholders": { + "encode": "Zadejte text pro kódování do Base64…", + "decode": "Vložte Base64 řetězec k dekódování…" + }, + "dropHere": "Pusťte soubor sem", + "charCount": "{count} znaků", + "download": { + "title": "Stáhnout výstup", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Kopírovat do schránky", + "outputPlaceholder": "Výstup se zobrazí zde…", + "errors": { + "invalidBase64": "Neplatný Base64 řetězec. Zkontrolujte vstup.", + "encodeFailed": "Kódování se nezdařilo." + } + }, + "UrlEncode": { + "title": "URL enkodér / dekodér", + "subtitle": "Procentové kódování/dekódování pomocí encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Nahrát", + "clear": "Vymazat", + "modes": { + "encode": "Enkódovat", + "decode": "Dekódovat" + }, + "swapTitle": "Prohodit vstup a výstup", + "panels": { + "plainText": "Prostý text", + "encodedInput": "Enkódovaný vstup", + "encodedOutput": "Enkódovaný výstup", + "decodedText": "Dekódovaný text" + }, + "placeholders": { + "encode": "Zadejte text pro procentové kódování…", + "decode": "Vložte percent-encoded text pro dekódování…" + }, + "dropHere": "Sem přetáhněte soubor", + "charCount": "{count} znaků", + "download": { + "title": "Stáhnout výstup", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Kopírovat do schránky", + "outputPlaceholder": "Výstup se zobrazí zde…", + "errors": { + "invalidEncoding": "Neplatné procentové kódování. Zkontrolujte neúplné dvojice % nebo chybné escape sekvence.", + "encodeFailed": "Nepodařilo se enkódovat (neplatné Unicode ve vstupu)." + } + }, + "UrlParser": { + "title": "Parser URL", + "subtitle": "Rozdělte URL na protokol, host, cestu, dotazové parametry a hash (fragment).", + "upload": "Nahrát", + "clear": "Vymazat", + "panels": { + "input": "Vstup URL", + "output": "Výsledek" + }, + "placeholders": { + "input": "Vložte URL (např. https://example.com/path?foo=bar#section) nebo host/cestu (např. example.com/path)…" + }, + "dropHere": "Přetáhněte soubor sem", + "charCount": "{count} znaků", + "outputPlaceholder": "Analyzované části se zobrazí zde…", + "fields": { + "protocol": "Protokol", + "host": "Host", + "path": "Cesta", + "hash": "Hash", + "queryParams": "Dotazové parametry" + }, + "paramCount": "{count} parametrů", + "noParams": "Žádné dotazové parametry.", + "paramKey": "Klíč", + "paramValue": "Hodnota", + "copyTitle": "Kopírovat do schránky", + "copyParamTitle": "Kopírovat jako key=value", + "download": { + "title": "Stáhnout JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Neplatné URL. Uveďte hostname (example.com) a zkontrolujte znaky." + } + }, + "UuidGenerator": { + "title": "Generátor UUID / ULID", + "subtitle": "Generujte UUID (RFC 9562) nebo ULID v prohlížeči. Hromadný export až {max} na spuštění.", + "format": "Formát", + "kindHints": { + "ulid": "Řaditelný, URL-bezpečný identifikátor o 26 znacích", + "uuid4": "Náhodné (RFC 9562)", + "uuid7": "Časově seřazené, Unix ms + náhoda", + "uuid1": "MAC/čas (RFC 9562)", + "uuid6": "Přeuspořádaný gregoriánský čas", + "uuid3": "MD5 hash jména + namespace", + "uuid5": "SHA‑1 hash jména + namespace", + "nil": "Nulové UUID (samé nuly, RFC 9562)" + }, + "countLabel": "Počet (1–{max})", + "nameLabel": "Jméno (vstupní řetězec)", + "namePlaceholder": "např. my.app/resource", + "bulkHint": "V dávce se přidá #0, #1, … aby byl každý ID jedinečný (v3/v5 jsou deterministické).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Vlastní UUID" + }, + "customNamespaceLabel": "UUID namespace", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Generovat", + "output": "Výstup", + "lines": "{count} řádků", + "download": { + "title": "Stáhnout .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Kopírovat vše", + "uppercaseToggle": "Výstup velkými písmeny", + "hyphensToggle": "Zobrazit pomlčky", + "copyJsonTitle": "Kopírovat jako pole JSON", + "outputPlaceholder": "Klikněte na „Generovat“…", + "errors": { + "invalidCustomNamespace": "Vlastní namespace musí být platná UUID.", + "missingName": "Pro UUID v3 a v5 je vyžadováno jméno.", + "unknown": "Nepodařilo se vygenerovat ID." + } + }, + "SecretApiKeyGenerator": { + "title": "Generátor tajného klíče / API klíče", + "subtitle": "Generujte kryptograficky náhodné řetězce s nastavitelnou délkou a abecedou. Hromadný export až {max} na běh. Doplňuje generátor UUID / ULID.", + "alphabetLabel": "Abeceda", + "alphabetHint": "Duplicitní znaky se odstraní. Každý symbol se volí rovnoměrně pomocí crypto.getRandomValues a metody odmítnutí (bez modulové zkreslení).", + "alphabetCount": "{count} jedinečných symbolů", + "presetsLabel": "Předvolby", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumerické", + "hex": "Hex (malá písmena)", + "asciiPrintable": "ASCII tisknutelné" + }, + "lengthLabel": "Délka na tajemství (1–{maxLen})", + "countLabel": "Počet (1–{maxBulk})", + "generate": "Generovat", + "output": "Výstup", + "lines": "{count} řádků", + "download": { + "title": "Stáhnout .txt", + "filename": "tajne-klice.txt" + }, + "copyTitle": "Kopírovat vše", + "outputPlaceholder": "Klikněte na „Generovat“…", + "entropy": "{bits} bitů entropie", + "strength": { + "weak": "Slabé", + "fair": "Průměrné", + "strong": "Silné" + }, + "excludeAmbiguous": "Vyloučit nejednoznačné znaky", + "excludeAmbiguousHint": "Odstraní {chars} — znaky, které lze snadno zaměnit nebo které škodí shellu.", + "prefixLabel": "Předpona klíče (volitelné)", + "errors": { + "emptyAlphabet": "Přidejte do abecedy alespoň jeden znak.", + "alphabetTooLong": "Po odstranění duplicit může mít abeceda nejvýše {maxAlpha} znaků.", + "lengthOutOfRange": "Délka musí být mezi 1 a {maxLen}.", + "countOutOfRange": "Počet musí být mezi 1 a {maxBulk}.", + "unknown": "Tajné klíče se nepodařilo vygenerovat." + } + }, + "LoremIpsum": { + "title": "Generátor Lorem Ipsum", + "subtitle": "Generujte zástupný latinský text pro vaše rozvržení a makety.", + "paragraphs": "Odstavce", + "sentences": "Věty", + "words": "Slova", + "count": "Počet", + "generate": "Generovat", + "copy": "Kopírovat", + "startWith": "Začít s 'Lorem ipsum...'", + "outputPlaceholder": "Výstup se zobrazí zde...", + "outputType": "Typ výstupu", + "bulletList": "Odrážkový seznam", + "htmlMarkup": "HTML značkování", + "htmlMarkupHint": "Zabalte výstup do odstavců nebo použijte seznam pro režim odrážek", + "output": "Výstup", + "download": "Stáhnout" + }, + "ColorPicker": { + "title": "Výběr a převodník barev", + "subtitle": "Převádějte HEX, RGB a HSL; vytvářejte harmonické palety. Veškeré zpracování probíhá v prohlížeči.", + "nativePicker": "Nativní výběr", + "chooseColor": "Vyberte barvu", + "eyeDropper": "Kapátko", + "invalidHex": "Neplatná barva HEX.", + "cssValues": "Hodnoty CSS", + "palettes": "Palety", + "copied": "Zkopírováno", + "clickToCopy": "Kliknutím zkopírujte", + "copyAllHex": "Kopírovat vše HEX", + "copy": "Kopírovat", + "copyColor": "Kopírovat {color}", + "paletteShades": "Odstíny", + "paletteComplementary": "Doplňkový", + "paletteTriadic": "Triadický", + "paletteAnalogous": "Analogický", + "paletteSplit": "Dělený dopl.", + "paletteSquare": "Čtvercový" + }, + "ContrastChecker": { + "title": "Kontrola kontrastu", + "subtitle": "Vypočítá kontrastní poměr WCAG 2.1 mezi popředím a pozadím. Zkontroluje AA a AAA pro běžný a velký text. Vše v prohlížeči.", + "foreground": "Text / popředí", + "background": "Pozadí", + "hexPlaceholder": "#RRGGBB", + "swap": "Prohodit barvy", + "chooseForeground": "Vybrat barvu popředí", + "chooseBackground": "Vybrat barvu pozadí", + "eyeDropper": "Kapátko", + "ratio": "Kontrastní poměr", + "copyRatio": "Kopírovat poměr", + "copied": "Zkopírováno", + "previewHeading": "Náhled", + "previewBody": "Příliš žluťoučký kůň úpěl ďábelské ódy. Nechtějí být ve víře řádů kódu šíleně zpívajícího ďábla.", + "previewCaption": "Ukázkový text ve výchozí velikosti.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — běžný text (4,5:1)", + "levelAALarge": "AA — velký text (3:1)", + "levelAAANormal": "AAA — běžný text (7:1)", + "levelAAALarge": "AAA — velký text (4,5:1)", + "pass": "Vyhovuje", + "fail": "Nevyhovuje", + "invalidHex": "Neplatná barva HEX.", + "largeTextHint": "Velký text: 18 pt+ řezný nebo 14 pt+ tučný (definice WCAG)." + }, + "DiffChecker": { + "title": "Porovnávač rozdílů", + "subtitle": "Porovnání řádků dvou textů vedle sebe. Vše zůstává ve vašem prohlížeči.", + "rowsCount": "{count} řádků", + "truncatedWarning": "Zobrazuje se prvních {max} řádků.", + "limitExceeded": "Každá strana je omezena na {max} znaků.", + "clearBoth": "Vymazat oboje", + "original": "Originál", + "modified": "Upraveno", + "originalPlaceholder": "Vložte původní text…", + "modifiedPlaceholder": "Vložte upravený text…", + "comparisonLabel": "Porovnání vedle sebe", + "emptyState": "Zadejte text do obou panelů pro porovnání." + }, + "JsonSchemaGenerator": { + "title": "Generátor JSON schématu", + "subtitle": "Vložte platný JSON a vygenerujte JSON schéma nebo typované modely. Vše běží v prohlížeči.", + "languageLabel": "Jazyk výstupu", + "inputLabel": "Vstup JSON", + "inputPlaceholder": "Vložte JSON sem…", + "outputLabel": "Vygenerovaný výstup", + "outputPlaceholder": "Schéma nebo vygenerované typy se zobrazí zde…", + "parseError": "Neplatný JSON", + "clear": "Vymazat", + "copy": "Kopírovat", + "copied": "Zkopírováno do schránky", + "copyFailed": "Kopírování se nezdařilo", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON schéma (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Načítání editoru…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Obrázek do Base64", + "subtitle": "Pusťte obrázek, získejte URI dat nebo nezpracovaný řetězec Base64. Vše zůstane ve vašem prohlížeči.", + "upload": "Nahrát obrázek", + "clear": "Jasný", + "modes": { + "dataUri": "Identifikátor URI dat", + "rawString": "Raw Base64" + }, + "panels": { + "imageInput": "Obrazový vstup", + "output": "Výstup Base64" + }, + "dropHere": "Přetáhněte obrázek sem", + "charCount": "{count} znaků", + "placeholders": { + "uploadImage": "Přetáhněte obrázek nebo začněte kliknutím na tlačítko nahrát." + }, + "fileTooLarge": "Soubor je příliš velký. Zpracování může zamrznout váš prohlížeč.", + "copyTitle": "Kopírovat do schránky", + "download": { + "title": "Stáhnout text", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Komprese obrázků", + "subtitle": "Zmenšete výstup JPEG, PNG nebo WebP lokálně s ovládáním kvality — nic se nenahrává.", + "upload": "Nahrát obrázek", + "clear": "Vymazat", + "dropHere": "Přetáhněte obrázek sem", + "placeholders": { + "uploadImage": "Přetáhněte obrázek nebo klikněte pro výběr souboru." + }, + "fileTooLarge": "Soubor je příliš velký (max. {maxMb} MB).", + "unsupportedFormat": "Tento typ souboru není podporován. Použijte JPEG, PNG, WebP, GIF, AVIF nebo BMP.", + "compressFailed": "Obrázek se nepodařilo zkomprimovat. Zkuste jiný soubor nebo formát.", + "panels": { + "original": "Originál", + "compressed": "Náhled po kompresi" + }, + "outputFormat": "Výstupní formát", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kvalita", + "qualityPercent": "{value} %", + "qualityPngHint": "PNG je v prohlížeči bezztrátové; posuvník kvality platí jen pro JPEG a WebP.", + "sizes": { + "original": "Originál: {size}", + "compressed": "Po kompresi: {size}", + "saved": "o {percent} % menší" + }, + "largerWarning": "Výstup je větší než vstup — zkuste jiný formát nebo kvalitu.", + "download": "Stáhnout", + "downloadFilename": "komprimovany-obrazek", + "processing": "Komprimuji…", + "emptyCompressed": "Náhled po kompresi se zobrazí zde." + }, + "CssGradientBuilder": { + "title": "CSS Gradient Builder", + "subtitle": "Vizuální tvůrce přechodů CSS s ovládáním úhlu, zarážkami barev a kopírováním CSS jedním kliknutím.", + "preview": "Náhled přechodu", + "settings": "Nastavení", + "type": "Typ", + "linear": "Lineární", + "radial": "Radiální", + "angle": "Úhel", + "stops": "Barva se zastaví", + "color": "Barva", + "position": "Pozice (%)", + "addStop": "Přidat Stop", + "deleteStop": "Smazat Stop", + "output": "Výstup CSS", + "copy": "Kopírovat CSS", + "copied": "Zkopírováno!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Generátor .gitignore", + "subtitle": "Okamžitě vygenerujte sloučené soubory .gitignore pro konkrétní sadu technologií nebo kombinaci.", + "searchPlaceholder": "Prohledat zásobníky (např. Node, Python, macOS)...", + "noResults": "Nebyly nalezeny žádné zásobníky.", + "selectedStacks": "Vybrané zásobníky", + "output": "Náhled výstupu", + "copy": "Kopírovat do schránky", + "copied": "Zkopírováno!", + "download": "Stáhnout .gitignore", + "downloading": "Stahování...", + "errorFetching": "Nepodařilo se načíst top-tal seznam.", + "errorGenerating": "Vygenerování souboru .gitignore z vybraných zásobníků se nezdařilo.", + "generating": "Generování...", + "emptyState": "Vyberte jeden nebo více technologických zásobníků pro generování vašeho .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Nahrajte CSV nebo Excel, upravte JSON a stáhněte CSV nebo XLSX. Pouze tabulková data; data z Excelu budou v JSON jako ISO časové značky.", + "dropTitle": "Přetáhněte soubor CSV nebo Excel", + "dropHint": "U .xlsx / .xls se použije první list. CSV musí mít řádek záhlaví.", + "chooseFile": "Vybrat soubor", + "jsonLabel": "JSON (pole objektů)", + "jsonPlaceholder": "Vložte JSON pole objektů nebo importujte CSV / Excel…", + "loadSample": "Načíst ukázku", + "actions": { + "format": "Formátovat JSON", + "clear": "Vymazat", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "downloadCsv": "Stáhnout CSV", + "downloadXlsx": "Stáhnout Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Data", + "toastLoadedCsv": "CSV načteno do editoru", + "toastLoadedExcel": "Excel načten do editoru", + "toastFormatted": "JSON naformátováno", + "toastExportedCsv": "CSV staženo", + "toastExportedXlsx": "Excel stažen", + "toastCopied": "Zkopírováno do schránky", + "footnote": "Vše probíhá v prohlížeči. JSON musí být neprázdné pole objektů. Vnořené hodnoty se v buňkách CSV serializují jako JSON.", + "errors": { + "unsupportedFile": "Použijte soubor .csv, .xlsx nebo .xls.", + "parseFailed": "Soubor nelze zpracovat.", + "invalidJson": "Neplatný JSON", + "exportFailed": "Export se nezdařil", + "copyFailed": "Kopírování se nezdařilo" + } + }, + "SnippetManager": { + "title": "Správce úryvků kódu", + "subtitle": "Ukládejte úryvky v prohlížeči nebo se přihlaste a synchronizujte je s účtem. Přepínejte zobrazení a úpravy, kopírujte jedním kliknutím, volte jazyk nebo automatickou detekci a formátujte JSON, SQL nebo kód podporovaný Monacem.", + "snippets": "Úryvky", + "newSnippet": "Nový úryvek", + "newSnippetTitle": "Nový úryvek", + "defaultSnippetTitle": "Vítejte", + "defaultSnippetCode": "// Vítejte u úryvků kódu\n// Zkuste zobrazení vs úpravy, kopírování, formátování a automatický jazyk\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Hledat úryvky…", + "searchAria": "Hledat úryvky", + "emptySearch": "Žádný úryvek neodpovídá hledání.", + "snippetTitle": "Název", + "snippetTitlePlaceholder": "Název úryvku", + "language": "Jazyk", + "detectedHint": "Zvýraznění: {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Zobrazit", + "modeEdit": "Upravit", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "format": "Formátovat", + "cancel": "Zrušit", + "deleteTitle": "Smazat úryvek?", + "deleteDescription": "„{title}“ bude odebrán z tohoto prohlížeče.", + "deleteConfirm": "Smazat", + "editorAria": "Editor kódu úryvku", + "editorLoading": "Načítání editoru…", + "toastCopied": "Zkopírováno do schránky", + "toastCopyFailed": "Kopírování se nezdařilo", + "toastNothingToCopy": "Nic ke kopírování", + "toastFormatted": "Naformátováno", + "toastFormatFailed": "Formátování se nezdařilo", + "toastFormatJsonInvalid": "Neplatné JSON — nejdřív opravte syntaxi", + "toastFormatUnsupported": "Pro tento jazyk není formátovač — zkuste JSON nebo SQL nebo režim úprav", + "toastDeleted": "Úryvek smazán", + "toastLoadFailed": "Úryvky se nepodařilo načíst ze serveru.", + "toastSyncFailed": "Synchronizace se serverem se nezdařila.", + "languages": { + "auto": "Automatická detekce", + "plaintext": "Prostý text", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL klient", + "dialogTitle": "SQL připojení", + "mobileSheetTitle": "SQL průzkumník", + "newTabLabel": "Nová karta dotazu", + "vaultLockedTitle": "Trezor uzamčen", + "vaultLockedDesc": "Odemkněte trezor pro přístup k uloženým připojením.", + "heroTitle": "SQL klient", + "heroDesc": "Připojte se k PostgreSQL, MySQL nebo MariaDB. Přihlašovací údaje jsou šifrovány v prohlížeči.", + "heroAddConnection": "Přidat připojení", + "selectTableTitle": "Vyberte tabulku nebo otevřete kartu dotazu", + "selectTableDesc": "Klikněte na tabulku v postranním panelu nebo použijte ⌘↩.", + "toastLoadFail": "Nepodařilo se načíst připojení." + }, + "connection": { + "titleNew": "Nové SQL připojení", + "titleEdit": "Upravit připojení", + "labelDbType": "Typ databáze", + "labelName": "Název připojení", + "placeholderName": "Moje produkční DB", + "labelHostPort": "Host a port", + "labelDatabase": "Databáze", + "placeholderDatabase": "mydb", + "labelUsername": "Uživatelské jméno", + "labelPassword": "Heslo", + "labelSsl": "Povolit SSL / TLS", + "sslDesc": "Šifrovat připojení k serveru", + "securityNote": "Přihlašovací údaje jsou před uložením šifrovány AES-256 v prohlížeči", + "btnTest": "Otestovat připojení", + "btnSave": "Uložit a připojit", + "btnUpdate": "Aktualizovat", + "btnCancelEdit": "Zrušit úpravy", + "savedConnections": "Uložená připojení", + "emptySaved": "Uložená připojení se zobrazí zde", + "confirmDelete": "Smazat toto připojení?", + "toastFillFields": "Před testem vyplňte host a databázi.", + "toastConnectOk": "Připojeno! {version}", + "toastConnectFail": "Připojení selhalo: {message}", + "toastUnlockVault": "Nejprve odemkněte trezor.", + "toastFieldsRequired": "Host a databáze jsou povinné.", + "toastUpdated": "Připojení aktualizováno.", + "toastSaved": "Připojení uloženo.", + "toastSaveFail": "Uložení selhalo: {message}", + "toastDeleted": "Připojení smazáno.", + "toastDeleteFail": "Nepodařilo se smazat připojení." + }, + "sidebar": { + "title": "SQL průzkumník", + "tooltipAddConnection": "Přidat připojení", + "tooltipRefreshSchema": "Obnovit schéma", + "noConnections": "Zatím žádná uložená připojení", + "toastSchemaFail": "Nepodařilo se načíst schéma: {message}" + }, + "editor": { + "btnRun": "Spustit", + "executing": "Provádí se dotaz…", + "emptyTitle": "Spusťte dotaz pro zobrazení výsledků", + "emptyHint": "Stiskněte ⌘↩ nebo klikněte Spustit", + "toastNoConnection": "Žádné aktivní připojení." + }, + "results": { + "filterPlaceholder": "Filtrovat výsledky…", + "rowCount": "{count} {count, plural, one {řádek} few {řádky} other {řádků}}", + "rowCountFiltered": "{filtered} / {total} řádků", + "exportCsv": "CSV", + "noRows": "Žádné řádky nebyly vráceny" + } + }, + "QrCodeGenerator": { + "title": "Generátor QR kódů", + "subtitle": "Převeďte libovolný text nebo URL na PNG QR kód. Vykreslení probíhá lokálně v prohlížeči.", + "contentLabel": "Obsah", + "contentPlaceholder": "https://example.com, prostý text, řetězec Wi‑Fi, vCard…", + "styleLabel": "Styl", + "dotTypeLabel": "Tvar teček", + "dotTypeSquare": "Čtverec", + "dotTypeDots": "Tečky", + "dotTypeRounded": "Zaoblené", + "dotTypeExtraRounded": "Extra zaoblené", + "dotTypeClassy": "Elegantní", + "dotTypeClassyRounded": "Elegantní zaoblené", + "cornerSquareLabel": "Rohové čtverce", + "cornerSquareDot": "Tečka", + "cornerSquareSquare": "Čtverec", + "cornerSquareExtraRounded": "Extra zaoblené", + "cornerDotLabel": "Rohové tečky", + "cornerDotDot": "Tečka", + "cornerDotSquare": "Čtverec", + "eccLabel": "Korekce chyb", + "eccL": "Nízká (~7 %)", + "eccM": "Střední (~15 %)", + "eccQ": "Kvartil (~25 %)", + "eccH": "Vysoká (~30 %)", + "marginLabel": "Okraj (moduly)", + "widthLabel": "Šířka obrázku", + "widthEffective": "Vykreslí se v {px} px", + "darkColor": "Tmavé moduly", + "lightColor": "Světlé moduly", + "resetColors": "Obnovit barvy", + "logoLabel": "Logo uprostřed", + "logoMarginLabel": "Odsazení", + "uploadLogo": "Nahrát obrázek", + "removeLogo": "Odebrat logo", + "downloadPng": "Stáhnout PNG", + "downloadSvg": "Stáhnout SVG", + "copyImage": "Kopírovat obrázek", + "copied": "Zkopírováno", + "clear": "Vymazat", + "previewAlt": "Náhled QR kódu", + "emptyHint": "Zadejte nebo vložte obsah pro náhled QR kódu.", + "filename": "qr-kod.png", + "errors": { + "generationFailed": "QR kód se nepodařilo vygenerovat. Zkuste kratší text nebo nižší korekci.", + "copyUnsupported": "Prohlížeč nepodporuje kopírování obrázků do schránky.", + "copyFailed": "Kopírování do schránky se nezdařilo." + } + }, + "IpSubnetCalculator": { + "title": "Kalkulačka IP / podsítě", + "subtitle": "IPv4 a IPv6 CIDR přímo v prohlížeči—bez síťových požadavků.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Síť", + "placeholderIpv4": "10.0.0.0/24 nebo 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 nebo 2001:db8::1/64", + "hintIpv4": "Použijte CIDR (např. /24) nebo IPv4 + tečkovou masku.", + "hintIpv6": "Uveďte délku prefixu (např. /64). Bez ní se předpokládá /128.", + "copyValue": "Kopírovat hodnotu", + "rows": { + "cidr": "CIDR", + "network": "Síťová adresa", + "netmask": "Maska podsítě", + "wildcard": "Wildcard maska", + "broadcast": "Broadcast", + "firstHost": "První hostitel", + "lastHost": "Poslední hostitel", + "totalAddresses": "Celkem adres", + "usableHosts": "Použitelní hostitelé", + "prefixLength": "Délka prefixu (bity)", + "networkIpv6": "Síťová adresa", + "lastIpv6": "Poslední adresa v rozsahu", + "totalAddressesIpv6": "Adres v podsíti" + }, + "usableNote": { + "pointToPoint": "/31 bodové spoje používají obě adresy jako hostitele (RFC 3021).", + "singleHost": "/32 je jedna hostitelská adresa." + }, + "ipv6RangeHint": { + "normal": "Zobrazuje se celý matematický rozsah; pravidla směrovače se mohou lišit.", + "single": "/128 je jedna adresa.", + "full": "/0 pokrývá celý IPv6." + }, + "errors": { + "invalidIpv4": "Neplatná IPv4 síť. Zkuste CIDR nebo adresu + masku.", + "invalidIpv6": "Neplatná IPv6 síť. Použijte adresa/prefix, např. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Generátor hashů", + "subtitle": "MD5, SHA-1 a SHA-2 pro text nebo soubory; bcrypt pro hesla (textový vstup). Vše proběhne lokálně v prohlížeči.", + "algorithmLabel": "Algoritmus", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Text", + "modeFile": "Soubor", + "inputLabelText": "Vstup", + "placeholderText": "Zadejte nebo vložte text…", + "chooseFile": "Vybrat soubor", + "clearFile": "Vymazat", + "fileHint": "Soubory se čtou jen v paměti; max. {maxMb} MB. Nic se nenahrává.", + "outputLabel": "Otisk (hex)", + "copyHash": "Kopírovat otisk", + "copied": "Zkopírováno", + "byteCount": "{count, plural, one {# bajt} few {# bajty} many {# bajtů} other {# bajtů}}", + "charCount": "{count, plural, one {# znak} few {# znaky} many {# znaků} other {# znaků}}", + "emptyHint": "Zadejte text nebo vyberte soubor.", + "securityNote": "MD5 a SHA-1 se pro nové bezpečnostní účely nedoporučují; použijte SHA-256 nebo SHA-512.", + "errors": { + "fileTooLarge": "Soubor je větší než {maxMb} MB.", + "fileReadFailed": "Soubor nelze přečíst." + }, + "bcryptRoundsLabel": "Náklad (kola)", + "bcryptRoundsHint": "Vyšší hodnoty jsou pomalejší a odolnější vůči hrubé síle. Běžně 10.", + "fileTabDisabledTitle": "bcrypt hashuje pouze hesla z textu, ne libovolné soubory.", + "placeholderBcrypt": "Zadejte nebo vložte heslo k hashování…", + "outputLabelBcrypt": "bcrypt hash", + "copyBcrypt": "Kopírovat hash", + "emptyHintBcrypt": "Zadejte heslo pro výpočet bcrypt hashe.", + "securityNoteBcrypt": "Každý výstup bcrypt obsahuje náhodnou sůl; stejné heslo dá pokaždé jiný řetězec. To je očekávané. V produkci používejte vyšší náklad.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Hraviště šifrování", + "subtitle": "AES-GCM v prohlížeči: šifrovat do JSON balíčku přístupovou frází nebo holým klíčem a dešifrovat lokálně. Výukové a občas praktické — nic se nenahrává.", + "tabEncrypt": "Zašifrovat", + "tabDecrypt": "Dešifrovat", + "keyModeLabel": "Klíčový materiál", + "modePassphrase": "Přístupová fráze (PBKDF2 + AES-GCM)", + "modeRawKey": "Holý klíč AES (hex nebo Base64)", + "aesBitsLabel": "Délka klíče AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Kódování holého klíče", + "encodingAuto": "Auto (hex pokud to vypadá jako hex, jinak Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Holý klíč", + "rawKeyPlaceholder": "Klíč 32 bajtů jako 64 hex znaků nebo Base64 pro AES-256 (délka závisí na zvolené velikosti AES).", + "rawKeyPlaceholderDecrypt": "Potřeba jen pokud balíček má keyDerivation \"none\".", + "passphraseLabel": "Přístupová fráze", + "passphrasePlaceholder": "Fráze použitá jen v prohlížeči k odvození klíče.", + "passphrasePlaceholderDecrypt": "Potřeba jen pokud balíček používá PBKDF2-SHA256.", + "pbkdf2IterLabel": "Iterace PBKDF2", + "pbkdf2Hint": "Mezi 10 000 a 2 000 000. Výchozí odpovídá běžným trezorům.", + "plaintextLabel": "Otevřený text", + "plaintextPlaceholder": "Zadejte nebo vložte zprávu k zašifrování…", + "bundleInputLabel": "Zašifrovaný balíček (JSON)", + "bundlePlaceholder": "Vložte JSON z Zašifrovat (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Zašifrovaný balíček", + "plaintextOutputLabel": "Otevřený text", + "encryptButton": "Zašifrovat", + "decryptButton": "Dešifrovat", + "copyBundle": "Kopírovat JSON", + "copyPlaintext": "Kopírovat otevřený text", + "copied": "Zkopírováno", + "emptyBundleHint": "Zašifrovaný JSON se zobrazí po zašifrování.", + "emptyDecryptHint": "Dešifrovaný text se zobrazí zde.", + "decryptKeyHint": "Použijte holý klíč při keyDerivation \"none\"; frázi u PBKDF2-SHA256. Balíček obsahuje sůl, iterace, IV a délku AES.", + "securityNote": "Klíče a otevřený text neopouštějí prohlížeč. Pro produkční tajemství použijte specializované nástroje.", + "errors": { + "emptyKey": "Zadejte holý klíč odpovídající velikosti AES.", + "emptyPassphrase": "Zadejte přístupovou frázi.", + "badKeyMaterial": "Klíč nelze přečíst jako hex ani Base64.", + "wrongKeyLength": "Délka klíče neodpovídá AES (16 / 24 / 32 bajtů).", + "invalidJson": "Neplatný JSON.", + "invalidBundle": "Tento JSON není podporovaný šifrovací balíček.", + "decryptFailed": "Dešifrování selhalo — špatné tajemství, poškozený balíček nebo parametry.", + "encryptFailed": "Šifrování selhalo. Zkontrolujte Web Crypto a vstupy." + } + }, + "HmacGenerator": { + "title": "Generátor HMAC", + "subtitle": "Pro podepisování webhooků a testování integrací API. HMAC-SHA1/256/384/512 v hex nebo Base64—počítáno lokálně v prohlížeči.", + "secretLabel": "Tajný klíč", + "messageLabel": "Zpráva / payload", + "placeholderSecret": "Vložte podpisové tajemství…", + "placeholderMessage": "Vložte surové tělo nebo řetězec k ověření (často přesné bajty webhooku).", + "algorithmLabel": "Algoritmus HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Formát výstupu", + "formatHex": "Hex (malá písmena)", + "formatBase64": "Base64", + "outputLabel": "Podpis", + "copyOutput": "Kopírovat podpis", + "copied": "Zkopírováno", + "byteCountHint": "{count, plural, one {# bajt} few {# bajty} other {# bajtů}} (UTF-8)", + "emptyHint": "Zadejte tajný klíč pro výpočet HMAC.", + "securityNote": "Nikdy nesdílejte podpisové tajemství. Tato stránka nic neodesílá—vše běží v prohlížeči.", + "errors": { + "cryptoFailed": "HMAC se nepodařilo vypočítat. Zkontrolujte podporu Web Crypto a zkuste znovu.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Generátor kódu TOTP / 2FA", + "subtitle": "Vložte Base32 tajemství a získejte aktuální šestimístný kód RFC 6238 (SHA-1, 30 s). Vše lokálně v prohlížeči—užitečné pro testování přihlášení nebo MFA.", + "secretLabel": "Tajemství (Base32)", + "placeholderSecret": "Vložte tajný klíč (písmena A–Z a číslice 2–7, jako při nastavení autentikátoru)…", + "codeLabel": "Aktuální kód", + "copyCode": "Kopírovat kód", + "copied": "Zkopírováno", + "nextIn": "Obnoví se za {seconds} s", + "standardHint": "Časový krok 30 s, 6 číslic, HMAC-SHA-1 (kompatibilní s Google Authenticator).", + "digitsLabel": "Počet číslic", + "periodLabel": "Perioda", + "algorithmLabel": "Algoritmus", + "periodSeconds": "{seconds} s", + "emptyHint": "Vložte tajemství pro vygenerování šestimístného kódu.", + "invalidHint": "Opravte tajemství pro zobrazení kódu.", + "securityNote": "Chraňte to jako heslo. Tajemství neopustí prohlížeč; nic se nenahrává.", + "importLabel": "Importovat z otpauth:// URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importovat", + "qrSectionTitle": "QR kód pro zřízení", + "qrHint": "Naskenujte pomocí aplikace ověřovatele (Google Authenticator, 1Password, Aegis…) a přidejte tento tajný klíč. QR kód kóduje tajný klíč — zacházejte s ním jako s heslem.", + "accountLabel": "Účet", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Vydavatel (volitelné)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Zadejte platný tajný klíč pro vygenerování QR kódu.", + "errors": { + "invalidSecret": "Neplatné Base32 tajemství.", + "cryptoFailed": "TOTP se nepodařilo spočítat. Zkontrolujte Web Crypto a zkuste znovu.", + "invalidUri": "Toto není platné otpauth:// TOTP URI.", + "unsupportedUri": "Toto URI používá hodnoty, které nástroj nepodporuje (počet číslic musí být 6 nebo 8; perioda 15, 30 nebo 60 s)." + } + }, + "MarkdownPreview": { + "title": "Náhled Markdown & HTML", + "subtitle": "Živý renderer Markdown s exportem HTML a převodem HTML → Markdown. Vše běží ve vašem prohlížeči.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Vstup Markdown", + "htmlInputLabel": "Vstup HTML", + "previewLabel": "Náhled", + "htmlOutputLabel": "Zdroj HTML", + "mdOutputLabel": "Výstup Markdown", + "inputPlaceholder": "# Ahoj\\n\\nZadejte svůj **markdown** zde…", + "htmlInputPlaceholder": "Vložte svůj HTML zde…", + "copyHtml": "Kopírovat HTML", + "copyMarkdown": "Kopírovat Markdown", + "exportHtml": "Exportovat HTML", + "copied": "Zkopírováno", + "charCount": "{count, plural, one {# znak} few {# znaky} other {# znaků}}", + "emptyHint": "Začněte psát Markdown pro zobrazení živého náhledu a HTML výstupu.", + "emptyHintHtmlToMd": "Vložte HTML pro převod na Markdown.", + "securityNote": "Vše běží lokálně ve vašem prohlížeči. Nic se nenahrává." + }, + "NumberBaseConverter": { + "title": "Převodník číselných soustav", + "subtitle": "Převod celých čísel mezi soustavami 2–36. Volitelné prefixy 0x / 0b / 0o. Vše lokálně v prohlížeči.", + "inputBaseLabel": "Ze základu", + "outputBaseLabel": "Do základu", + "inputLabel": "Vstupní číslo", + "placeholderInput": "Zadejte celé číslo ve zvolené vstupní soustavě…", + "outputLabel": "Převedená hodnota", + "copyOutput": "Kopírovat výsledek", + "copied": "Zkopírováno", + "swap": "Prohodit základy", + "byteCountHint": "{count, plural, one {# bajt} few {# bajty} other {# bajtů}} (délka UTF-8)", + "emptyHint": "Zadejte číslo k převodu.", + "note": "Číslice nad 9 jsou písmena A–Z (bez rozlišení velikosti). Mezery a podtržítka se ignorují.", + "errors": { + "invalidNumber": "Hodnota není platná pro zvolenou vstupní soustavu." + } + }, + "FormatConverter": { + "title": "Převodník formátů", + "subtitle": "Převádějte mezi JSON, YAML, TOML a XML v libovolném směru. Všech 12 kombinací. Vše běží ve vašem prohlížeči.", + "fromLabel": "Z", + "toLabel": "Do", + "swap": "Přepnout", + "swapTitle": "Přepněte formáty a použijte výstup jako vstup", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "charCount": "{count, plural, one {# znak} few {# znaky} other {# znaků}}", + "emptyHint": "Vložte nebo napište vstup vlevo a uvidíte převedený výstup.", + "localNote": "Vše běží lokálně ve vašem prohlížeči. Nic se nenahrává." + }, + "MimeTypeLookup": { + "title": "Vyhledání MIME typu", + "subtitle": "Vyhledejte MIME typ (media type) podle přípony nebo celého názvu souboru. Vše běží lokálně ve vašem prohlížeči.", + "inputLabel": "Přípona nebo název souboru", + "placeholder": "např. .png, package.json, photo.jpeg", + "hint": "Tip: vložte příponu (s tečkou nebo bez) nebo celý název souboru.", + "clear": "Vymazat", + "exampleFilename": "Použít ukázkový název souboru", + "exampleExtension": "Použít ukázkovou příponu", + "resultLabel": "Výsledek", + "normalizedLabel": "Normalizovaný vstup:", + "mimeTypeLabel": "MIME typ", + "contentTypeLabel": "Hlavička Content-Type", + "charsetLabel": "Znaková sada", + "copyMimeType": "Kopírovat MIME typ", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "emptyHint": "Zadejte příponu nebo název souboru pro vyhledání MIME typu.", + "notFound": "Pro tento vstup nebyl nalezen žádný MIME typ.", + "localNote": "Vše běží lokálně ve vašem prohlížeči. Nic se nenahrává." + }, + "UserAgentParser": { + "title": "Parser User-Agent", + "subtitle": "Vložte řetězec User-Agent (UA) a získejte rozpis prohlížeče, OS, enginu, zařízení a CPU. Vše běží lokálně ve vašem prohlížeči.", + "inputLabel": "Řetězec User-Agent", + "placeholder": "Vložte sem svůj User-Agent…", + "hint": "Tip: UA můžete zkopírovat z hlaviček požadavku, logů nebo z devtools prohlížeče.", + "clear": "Vymazat", + "exampleDesktop": "Použít příklad pro desktop", + "exampleMobile": "Použít příklad pro mobil", + "resultLabel": "Rozpis", + "browserLabel": "Prohlížeč", + "osLabel": "Operační systém", + "engineLabel": "Engine", + "deviceTypeLabel": "Typ zařízení", + "deviceVendorLabel": "Výrobce", + "deviceModelLabel": "Model", + "cpuArchLabel": "Architektura CPU", + "copyJson": "Kopírovat JSON", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "emptyHint": "Vložte řetězec User-Agent pro zobrazení rozpisu.", + "localNote": "Vše běží lokálně ve vašem prohlížeči. Nic se nenahrává." + }, + "MockDataGenerator": { + "title": "Generátor testovacích dat", + "subtitle": "Definujte sloupce, zvolte typy a exportujte sady pro seedy, Postman nebo testy. Až {max} řádků, vše v prohlížeči — nic se nenahrává.", + "mockarooLink": "Postup jako v Mockaroo", + "rowsLabel": "Řádky", + "rowsHint": "Nejvýše {max} řádků na generování, aby karta zůstala svižná.", + "formatLabel": "Formát", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Název SQL tabulky", + "presetsLabel": "Šablony", + "presetPlaceholder": "Načíst šablonu…", + "presetDefault": "Start (uživatel)", + "presetUsers": "Uživatelé a role", + "presetProducts": "SKU e-commerce", + "presetApiLog": "Log HTTP požadavků", + "generate": "Generovat", + "warningDuplicateNames": "Duplicitní názvy sloupců: pozdější přepíší dřívější v JSON objektech. Přejmenujte je.", + "schemaHeading": "Schéma", + "addField": "Přidat sloupec", + "removeField": "Odebrat sloupec", + "duplicateField": "Duplikovat sloupec", + "colName": "Název", + "colType": "Typ", + "colOptions": "Možnosti", + "colBlank": "Prázdné %", + "optMin": "min", + "optMax": "max", + "optDecimals": "des", + "optDateFrom": "od data", + "optDateTo": "do data", + "optValues": "a, b, c", + "optStep": "krok", + "optRowBase": "základ", + "output": "Výstup", + "outputPlaceholder": "Klikněte na Generovat pro vytvoření dat…", + "copy": "Kopírovat", + "download": "Stáhnout", + "copied": "Zkopírováno", + "groups": { + "personal": "Osobní", + "location": "Poloha", + "internetApi": "Internet a API", + "numbersIds": "Čísla a identifikátory", + "dateTime": "Datum a čas", + "textDesign": "Text a vzhled", + "custom": "Vlastní" + }, + "fieldTypes": { + "first_name": "Jméno", + "last_name": "Příjmení", + "full_name": "Celé jméno", + "email": "Email", + "username": "Uživatelské jméno", + "password": "Heslo", + "phone": "Telefon", + "company": "Společnost", + "job_title": "Pracovní pozice", + "avatar_url": "URL avatara", + "city": "Město", + "country": "Země", + "country_code": "Kód země (ISO)", + "street_address": "Adresa", + "zip_code": "PSČ", + "latitude": "Zeměpisná šířka", + "longitude": "Zeměpisná délka", + "url": "URL", + "domain": "Doména", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Celé číslo", + "float": "Desetinné číslo", + "boolean": "Logická hodnota", + "uuid": "UUID", + "credit_card": "Platební karta (test)", + "semver": "SemVer", + "row_index": "Index řádku", + "sequence": "Sekvence", + "date": "Datum", + "datetime": "Datum a čas", + "unix_timestamp": "Unixové časové razítko", + "timezone": "Časové pásmo", + "word": "Slovo", + "sentence": "Věta", + "paragraph": "Odstavec", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Kód měny", + "language_code": "Jazyková značka", + "custom_list": "Vlastní seznam" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Optimalizátor SVG", + "subtitle": "Vložte SVG, minifikujte pomocí SVGO v prohlížeči a porovnejte velikost UTF-8 před a po.", + "inputLabel": "Vstup", + "outputLabel": "Optimalizováno", + "placeholder": "Vložte úryvek nebo celý soubor…", + "outputPlaceholder": "Optimalizované SVG se zobrazí zde…", + "chars": "{count} znaků • {bytes} bajtů (UTF-8)", + "preview": "Náhled", + "localNote": "Zpracování probíhá lokálně v prohlížeči. Nic se nenahrává.", + "stats": { + "title": "Velikost (UTF-8)", + "before": "Před", + "after": "Po", + "savedBytes": "Ušetřeno {count} bajtů ({percent}%)", + "noChange": "Žádná redukce — už je to minimální nebo prázdný výstup." + }, + "actions": { + "clear": "Vymazat", + "sample": "Načíst ukázku", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "download": "Stáhnout" + }, + "errors": { + "optimizeFailed": "Tento SVG se nepodařilo optimalizovat." + } + }, + "UnitConverter": { + "title": "Převodník jednotek", + "subtitle": "Převádějte mezi 323 jednotkami ve 43 kategoriích", + "category": "Kategorie", + "from": "Z", + "to": "Na", + "value": "Hodnota", + "result": "Výsledek", + "swapUnits": "Přehodit jednotky", + "placeholder": "Zadejte hodnotu", + "copyResult": "Kopírovat výsledek", + "searchCategory": "Hledat kategorii...", + "selectCategory": "Vyberte kategorii", + "noResults": "Žádná kategorie nenalezena." + }, + "DockerComposeGenerator": { + "title": "Generátor Docker Compose", + "subtitle": "Vyberte populární databáze, cache, fronty zpráv, reverzní proxy a observabilitu — zkopírujte funkční docker-compose.yml pro lokální vývoj.", + "searchPlaceholder": "Hledat (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Služby", + "selectedCount": "{count, plural, one {# služba} few {# služby} many {# služeb} other {# služeb}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopírovat", + "copied": "Zkopírováno!", + "download": "Stáhnout", + "emptyState": "Vyberte jednu nebo více služeb pro vygenerování Compose YAML.", + "hintPorts": "Výchozí porty hostitele snižují konflikty; upravte je, pokud Docker nahlásí obsazený port. Před sdílenými prostředími nahraďte demo hesla.", + "categories": { + "databases": "Databáze a úložiště", + "cache": "Cache a in-memory", + "queues": "Fronty a messaging", + "search": "Vyhledávání a logy", + "storage": "Objektové úložiště", + "proxy": "Reverzní proxy a ingress", + "observability": "Metriky a trasování", + "mail": "E-mail (lokální vývoj)", + "admin": "Admin a BI rozhraní" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Vymazat" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (kompatibilní s Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (kompatibilní s Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (kompatibilní se S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (zachycení SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (dev server)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Posuň dlaždice a slouč je do roku 2048", + "score": "Skóre", + "best": "Nejlepší", + "newGame": "Nová hra", + "howToPlay": "Pomocí kláves se šipkami (nebo přejeďte na mobilu) přesuňte dlaždice. Dlaždice se stejným číslem se sloučí. Dosáhněte roku 2048 a vyhrajte!", + "youWin": "Dosáhli jste roku 2048!", + "keepGoing": "Neohlížej se", + "gameOver": "Konec Hry", + "tryAgain": "Zkusit to znovu", + "gameOverMessage": "Už žádné tahy.", + "undo": "Zpět" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "etapy dokončeny", + "time": "Hod", + "best": "Nejlepší", + "difficulty": { + "easy": "Lehká", + "medium": "Střední", + "hard": "Tvrdé" + }, + "stage": "Míra", + "solved": "Vyřešeno", + "score": "Skóre", + "nextStage": "Další etapa", + "replay": "Znovu přehrát", + "erase": "Smazat", + "howToPlay": "Klikněte na buňku a potom klepněte na číslo nebo stiskněte 1–9. Backspace nebo Delete vymaže buňku. Tučná čísla jsou vodítka. Vyplňte mřížku tak, aby každý řádek, sloupec a 3×3 pole obsahovaly 1–9 bez opakování.", + "pencil": "Tužka", + "undo": "Zpět", + "hint": "Nápověda", + "hintsUsed": "nápovědy použity", + "mistakes": "Chyby", + "failed": "Neúspěch!", + "mistakeMode": "Režim 3 chyb" + }, + "Snake": { + "title": "Had", + "subtitle": "Jez jídlo, roste, vyhýbej se zdím a sobě", + "score": "Skóre", + "best": "Nejlepší", + "startPrompt": "Stiskni jakoukoli šipku", + "startButton": "Spustit hru", + "gameOver": "Konec hry", + "playAgain": "Hrát znovu", + "howToPlay": "Pohybuj se šipkami nebo WASD. Jez a roste. Rychlost roste každých 50 bodů.", + "diffEasy": "Lehké", + "diffNormal": "Normální", + "diffHard": "Těžké", + "diffInfinite": "Nekonečno", + "orTap": "nebo klepni níže", + "howToPlayInfinite": "Zdi se opakují — žádná smrt od zdí. Jez a roste.", + "shieldActive": "Štít aktivní", + "speedBoost": "Zrychlení", + "powerSpeed": "Rychlost x2", + "powerShield": "Přežij jednu smrt", + "powerShrink": "Zkrátí ocas" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis připojení", + "heroTitle": "Redis Commander", + "heroDesc": "Připojte se k instancím Redis. Procházejte klíče, upravujte hodnoty a spouštějte příkazy. Přihlašovací údaje jsou šifrovány v prohlížeči.", + "heroAddConnection": "Přidat připojení", + "vaultLockedTitle": "Trezor zamčen", + "vaultLockedDesc": "Odemkněte trezor pro přístup k uloženým připojením." + } + }, + "Minesweeper": { + "title": "Hledání min", + "subtitle": "Najděte všechna bezpečná pole bez odpálení miny", + "mines": "Miny", + "newGame": "Nová hra", + "win": "Vyhráli jste!", + "loss": "Konec hry!", + "playAgain": "Hrát znovu", + "tryAgain": "Zkusit znovu", + "startPrompt": "Klikněte na libovolné pole pro začátek", + "difficulty": { + "beginner": "Začátečník", + "intermediate": "Středně pokročilý", + "expert": "Expert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Uspořádejte padající bloky a dokončete řady", + "next": "Další", + "score": "Skóre", + "best": "Nejlepší", + "lines": "Řádky", + "level": "Úroveň", + "pause": "Pauza", + "resume": "Pokračovat", + "start": "Start", + "restart": "Restart", + "paused": "Pozastaveno", + "gameOver": "Konec hry" + }, + "SshKeyGenerator": { + "title": "Generátor klíčů SSH / RSA", + "subtitle": "Generujte páry klíčů Ed25519 nebo RSA ve svém prohlížeči. Stáhněte soukromé a veřejné klíče ve formátech OpenSSH a PEM. Nic se nenahrává.", + "keyTypeLabel": "Typ klíče", + "recommended": "doporučeno", + "ed25519Desc": "Rychlý, malý, moderní. Podporován GitHubem, GitLabem a všemi hlavními SSH servery.", + "rsa2048Desc": "Široce kompatibilní. Bezpečný pro většinu použití.", + "rsa4096Desc": "Maximální kompatibilita a síla pro starší systémy.", + "commentLabel": "Komentář (volitelné)", + "commentHint": "Přidán k řádku veřejného klíče OpenSSH.", + "generateBtn": "Generovat pár klíčů", + "generating": "Generuji…", + "generatingLong": "Generuji (může chvíli trvat)…", + "copy": "Kopírovat", + "download": "Stáhnout", + "privateKeyLabel": "Soukromý klíč (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Veřejný klíč (formát OpenSSH)", + "publicKeySpkiLabel": "Veřejný klíč (SPKI PEM)", + "browserNote": "Všechny klíče jsou generovány ve vašem prohlížeči. Nic není odesíláno na žádný server.", + "errors": { + "generationFailed": "Generování klíče selhalo" + } + }, + "DnsLookup": { + "title": "Vyhledávání DNS", + "subtitle": "Dotazujte DNS záznamy pro libovolnou doménu — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Zadejte doménu (např. github.com)", + "lookupBtn": "Vyhledat", + "looking": "Vyhledávám…", + "refreshBtn": "Obnovit", + "recordTypesLabel": "Typy záznamů", + "allBtn": "Vše", + "tryLabel": "Zkusit:", + "errors": { + "lookupFailed": "Vyhledávání DNS selhalo" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt generátor a ověřovač", + "subtitle": "Hashujte hesla pomocí bcrypt a ověřujte hashe — vše zůstává ve vašem prohlížeči", + "tabs": { + "hash": "Hash", + "verify": "Ověřit" + }, + "showPassword": "Zobrazit heslo", + "hidePassword": "Skrýt heslo", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "hash": { + "passwordLabel": "Heslo", + "passwordPlaceholder": "Zadejte heslo k zahashování…", + "costLabel": "Faktor náročnosti", + "costHint": "Vyšší náročnost zdvojnásobuje práci na každý krok. 10–12 je běžná volba pro produkci.", + "slowWarning": "Faktory náročnosti 13 a vyšší mohou výpočet trvat několik sekund.", + "generate": "Vygenerovat hash", + "generating": "Hashuji…", + "resultLabel": "Bcrypt hash", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomie hashe", + "algorithm": "algoritmus", + "cost": "náročnost", + "salt": "sůl (22 znaků)", + "hash": "hash (31 znaků)" + } + }, + "verify": { + "passwordLabel": "Heslo", + "passwordPlaceholder": "Zadejte heslo k ověření…", + "hashLabel": "Bcrypt hash", + "hashPlaceholder": "$2b$10$…", + "idle": "Zadejte heslo a bcrypt hash k jejich porovnání.", + "invalidHash": "Toto nevypadá jako platný bcrypt hash.", + "checking": "Porovnávám…", + "match": "Shoda — heslo vytváří tento hash.", + "noMatch": "Neshoda — heslo nevytváří tento hash." + } + }, + "ChmodCalculator": { + "title": "Kalkulačka chmod", + "subtitle": "Převádějte unixová oprávnění souborů mezi zaškrtávacími políčky, osmičkovým a symbolickým zápisem", + "grid": { + "title": "Oprávnění", + "read": "Čtení", + "write": "Zápis", + "execute": "Spouštění", + "owner": "Vlastník", + "group": "Skupina", + "others": "Ostatní" + }, + "special": { + "title": "Speciální bity", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Osmičkově", + "placeholder": "755 nebo 4755", + "invalid": "Zadejte 3 nebo 4 osmičkové číslice (0–7)." + }, + "symbolic": { + "label": "Symbolicky", + "placeholder": "rwxr-xr-x", + "invalid": "Zadejte 9 znaků jako rwxr-xr-x (povoleno s/S, t/T)." + }, + "output": { + "title": "Příkaz", + "copy": "Kopírovat", + "copied": "Zkopírováno" + }, + "explain": { + "line": "{cls} může {perms}.", + "none": "{cls} nemá žádná oprávnění.", + "setuid": "Setuid: soubor běží s právy svého vlastníka.", + "setgid": "Setgid: soubor běží s právy své skupiny (nové soubory v adresáři dědí jeho skupinu).", + "sticky": "Sticky: soubor v tomto adresáři může smazat nebo přejmenovat pouze jeho vlastník." + }, + "presets": { + "title": "Běžné předvolby", + "octal": "Osmičkově", + "permissions": "Oprávnění", + "description": "Typické použití", + "p644": "Běžné soubory: vlastník zapisuje, všichni čtou.", + "p755": "Spustitelné soubory a adresáře: všichni mohou číst a vstoupit.", + "p600": "Soukromé soubory: čte a zapisuje pouze vlastník.", + "p700": "Soukromé spustitelné soubory a adresáře: pouze vlastník.", + "p664": "Soubory zapisovatelné skupinou.", + "p775": "Adresáře a spustitelné soubory zapisovatelné skupinou.", + "p400": "Pouze pro čtení vlastníkem (např. soukromé SSH klíče)." + } + }, + "CodeScreenshot": { + "title": "Snímek kódu", + "subtitle": "Proměňte kód v krásné sdílitelné obrázky", + "languageLabel": "Jazyk", + "autoDetect": "Automatická detekce", + "backgroundLabel": "Pozadí", + "paddingLabel": "Odsazení", + "filenameLabel": "Název souboru", + "filenamePlaceholder": "example.ts", + "windowLabel": "Tečky okna", + "themeLabel": "Motiv okna", + "themeDark": "Tmavý", + "themeLight": "Světlý", + "scaleLabel": "Měřítko exportu", + "copyImage": "Kopírovat obrázek", + "copied": "Zkopírováno", + "exportPng": "Exportovat PNG", + "inputLabel": "Kód", + "detected": "Detekováno: {language}", + "clear": "Vymazat", + "placeholder": "Vložte sem svůj kód…", + "previewLabel": "Náhled", + "emptyPreview": "Náhled vašeho kódu se zobrazí zde", + "exportFailed": "Export obrázku se nezdařil", + "copyUnsupported": "Kopírování obrázků není v tomto prohlížeči podporováno", + "copyFailed": "Kopírování obrázku se nezdařilo", + "backgrounds": { + "ocean": "Oceán", + "sunset": "Západ slunce", + "candy": "Bonbon", + "forest": "Les", + "midnight": "Půlnoc", + "gold": "Zlatá", + "aurora": "Polární záře", + "slate": "Břidlice", + "transparent": "Průhledné" + } + }, + "CssGenerators": { + "title": "CSS generátory", + "subtitle": "Box-shadow, border-radius a plynulý clamp() — zkopírujte CSS.", + "preview": "Náhled", + "output": "CSS", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Plynulý clamp()" + }, + "boxShadow": { + "presets": "Předvolby", + "layer": "Vrstva", + "addLayer": "Přidat vrstvu", + "removeLayer": "Odebrat vrstvu", + "x": "X", + "y": "Y", + "blur": "Rozmazání", + "spread": "Rozšíření", + "color": "Barva", + "alpha": "Alfa", + "inset": "Vnitřní", + "presetNames": { + "subtleCard": "Jemná karta", + "elevated": "Vyvýšená", + "hardOffset": "Ostrý posun", + "neonGlow": "Neonová záře", + "inner": "Vnitřní", + "layeredSmooth": "Vrstvená hladká" + } + }, + "borderRadius": { + "modeLabel": "Režim", + "modes": { + "simple": "Po rozích", + "blob": "Organická skvrna" + }, + "unit": "Jednotka", + "linkCorners": "Propojit rohy", + "randomize": "Náhodně", + "horizontal": "Vodorovné poloměry", + "vertical": "Svislé poloměry", + "corners": { + "tl": "Vlevo nahoře", + "tr": "Vpravo nahoře", + "br": "Vpravo dole", + "bl": "Vlevo dole" + } + }, + "clamp": { + "minFont": "Min. písmo (px)", + "maxFont": "Max. písmo (px)", + "minViewport": "Min. viewport (px)", + "maxViewport": "Max. viewport (px)", + "rootFont": "Kořenové písmo (px)", + "viewportWidth": "Simulovaný viewport", + "computedSize": "Vypočtená velikost", + "slope": "Sklon", + "intercept": "Průsečík", + "previewText": "Příliš žluťoučký kůň úpěl", + "invalid": "Zadejte platné, odlišné hranice viewportu." + } + }, + "ExifViewer": { + "title": "EXIF prohlížeč a odstraňovač", + "subtitle": "Prohlížejte metadata fotek a odstraňte je ve svém prohlížeči", + "dropTitle": "Sem přetáhněte obrázek", + "dropHint": "nebo klikněte pro procházení — váš obrázek nikdy neopustí vaše zařízení", + "browse": "Vybrat obrázek", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Čtu metadata…", + "parseFailed": "Čtení tohoto obrázku se nezdařilo", + "clear": "Odebrat obrázek", + "gpsWarning": "Obsahuje GPS polohu", + "openMap": "Zobrazit na OpenStreetMap", + "noMetadata": "V tomto obrázku nebyla nalezena žádná vložená metadata.", + "facts": { + "name": "Název", + "type": "Typ", + "dimensions": "Rozměry", + "size": "Velikost" + }, + "groups": { + "camera": "Fotoaparát", + "exposure": "Expozice", + "timestamps": "Časové značky", + "gps": "GPS poloha", + "software": "Software a ostatní" + }, + "gpsCard": { + "latitude": "Zeměpisná šířka", + "longitude": "Zeměpisná délka", + "altitude": "Nadmořská výška" + }, + "raw": { + "title": "Všechna metadata", + "copyJson": "Kopírovat JSON", + "copied": "Zkopírováno" + }, + "remove": { + "title": "Odstranit metadata", + "jpegDescription": "Bezeztrátově odstraňte EXIF, XMP, IPTC a komentáře bez opětovné komprese obrázku.", + "reencodeDescription": "Znovu zakódujte obrázek a odstraňte vložená metadata. Tímto se pixely znovu komprimují.", + "keepIcc": "Zachovat barevný profil (ICC)", + "stripButton": "Odstranit metadata", + "reencodeButton": "Znovu zakódovat a odstranit", + "unsupported": "Odstranění metadat není pro tento typ souboru podporováno.", + "failed": "Odstranění metadat se nezdařilo", + "before": "Před", + "after": "Po", + "download": "Stáhnout vyčištěný obrázek" + } + }, + "FaviconGenerator": { + "title": "Generátor favicon", + "subtitle": "Vytvořte favicony a ikony aplikací z jednoho obrázku", + "dropTitle": "Sem přetáhněte obrázek", + "dropHint": "PNG, JPEG nebo SVG — klikněte pro procházení", + "browse": "Vybrat obrázek", + "clear": "Odebrat obrázek", + "backgroundLabel": "Pozadí", + "transparent": "Průhledné", + "solidColor": "Plná barva", + "cornerLabel": "Tvar rohů", + "cornerNone": "Hranatý", + "cornerRounded": "Zaoblený", + "cornerCircle": "Kruh", + "maskPaddingLabel": "Odsazení pro maskování", + "maskable": "maskable", + "previewLabel": "Náhled", + "copy": "Kopírovat", + "headSnippet": "Úryvek HTML head", + "downloadIco": "Stáhnout favicon.ico", + "downloadZip": "Stáhnout vše (.zip)" + }, + "JsonDiff": { + "title": "JSON diff", + "subtitle": "Sémantické porovnání dvou JSON dokumentů zohledňující strukturu — pořadí klíčů a formátování se ignoruje", + "options": { + "ignoreArrayOrder": "Ignorovat pořadí polí" + }, + "loadBothSamples": "Načíst ukázky", + "sample": "Ukázka", + "format": "Formátovat", + "clear": "Vymazat", + "formatError": "Nelze formátovat: vstup není platný JSON", + "placeholder": "Vložte nebo napište JSON sem…", + "panels": { + "left": "Originál (vlevo)", + "right": "Upravený (vpravo)", + "results": "Rozdíly" + }, + "summary": { + "added": "{count} přidáno", + "removed": "{count} odebráno", + "changed": "{count} změněno" + }, + "types": { + "added": "Přidáno", + "removed": "Odebráno", + "changed": "Změněno" + }, + "copyChanges": "Kopírovat jako JSON", + "copied": "Zkopírováno", + "empty": "Vložte JSON do obou panelů, abyste viděli jejich rozdíly.", + "identical": "Oba dokumenty jsou sémanticky totožné." + }, + "JsonpathPlayground": { + "title": "JSONPath hřiště", + "subtitle": "Dotazujte se na JSON dokumenty pomocí JSONPath nebo JMESPath a sledujte živé výsledky", + "engineLabel": "Engine", + "queryLabel": "Dotaz", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Tahák", + "cheats": { + "jpAllTitles": "Všechny názvy knih", + "jpAllAuthors": "Každý autor, kdekoli v dokumentu", + "jpFirstBook": "První kniha", + "jpLastBook": "Poslední kniha", + "jpSlice": "První dvě knihy (výřez)", + "jpFilterPrice": "Knihy levnější než 40", + "jpFilterInStock": "Názvy skladem dostupných knih", + "jpRecursive": "Všechny hodnoty, rekurzivně", + "jmAllTitles": "Všechny názvy knih", + "jmFirstAuthor": "Autor první knihy", + "jmLastBook": "Poslední kniha", + "jmSlice": "První dvě knihy (výřez)", + "jmFilterPrice": "Názvy knih levnějších než 40", + "jmCount": "Počet skladem dostupných knih", + "jmProjection": "Přeformujte každou knihu do nového objektu", + "jmSort": "Názvy seřazené podle ceny" + }, + "panels": { + "input": "JSON vstup", + "results": "Výsledky" + }, + "sample": "Ukázka", + "format": "Formátovat", + "clear": "Vymazat", + "formatError": "Nelze formátovat: vstup není platný JSON", + "placeholder": "Vložte nebo napište JSON sem…", + "outputPlaceholder": "Shody se zobrazí zde…", + "matchCount": "{count, plural, one {# shoda} few {# shody} many {# shody} other {# shod}}", + "copy": "Kopírovat", + "copied": "Zkopírováno" + }, + "KeycodeInspector": { + "title": "Inspektor kláves", + "subtitle": "Stiskněte libovolnou klávesu a prozkoumejte její JavaScriptovou klávesovou událost", + "capture": { + "prompt": "Stiskněte libovolnou klávesu…", + "ready": "Naslouchám — stiskněte libovolnou klávesu na klávesnici", + "clickToFocus": "Klikněte sem pro zaměření, poté stiskněte libovolnou klávesu" + }, + "hero": { + "spaceKey": "(Mezerník)", + "keyLabel": "event.key", + "repeat": "opakování" + }, + "copyJson": "Kopírovat JSON", + "copiedJson": "JSON události zkopírován do schránky", + "deprecated": "zastaralé", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standardní", + "left": "Levá", + "right": "Pravá", + "numpad": "Numerická klávesnice", + "unknown": "Neznámé" + }, + "history": { + "title": "Historie", + "clear": "Vymazat historii", + "empty": "Stisknuté klávesy se zobrazí zde." + } + }, + "MarkdownTableGenerator": { + "title": "Generátor Markdown tabulek", + "subtitle": "Upravujte mřížku, vkládejte ze sešitu a kopírujte pěkně zarovnanou Markdown nebo HTML tabulku", + "addRow": "Přidat řádek", + "addColumn": "Přidat sloupec", + "importLabel": "Import", + "importPlaceholder": "Vložte sem TSV (z Excelu / Tabulek Google) nebo CSV…", + "importButton": "Importovat do mřížky", + "pasteClipboard": "Vložit ze schránky", + "importEmpty": "Není co importovat — nejprve vložte nějaké řádky", + "clipboardError": "Nepodařilo se číst ze schránky", + "clear": "Vymazat", + "panels": { + "editor": "Editor tabulky", + "output": "Výstup" + }, + "align": { + "left": "Zarovnat vlevo", + "center": "Zarovnat na střed", + "right": "Zarovnat vpravo" + }, + "headerPlaceholder": "Záhlaví", + "cellPlaceholder": "Buňka", + "removeRow": "Odebrat řádek", + "removeColumn": "Odebrat sloupec", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopírovat", + "copied": "Zkopírováno" + }, + "TimezoneConverter": { + "title": "Převodník časových pásem", + "subtitle": "Převeďte jeden okamžik po celém světě pro plánování schůzek.", + "dateTimeLabel": "Datum a čas", + "now": "Nyní", + "sourceZoneLabel": "Zdrojové časové pásmo", + "selectZone": "Vyberte pásmo", + "searchZone": "Hledat časová pásma...", + "noZone": "Žádné časové pásmo nenalezeno.", + "scrubLabel": "Posunout čas", + "reset": "Obnovit", + "targetsLabel": "Cílová pásma", + "addZone": "Přidat pásmo", + "remove": "Odebrat", + "localBadge": "Místní", + "invalidDate": "Zadejte platné datum a čas.", + "bands": { + "work": "Pracovní doba (09:00–18:00)", + "edge": "Okrajové hodiny (ráno / večer)", + "night": "Noc" + } + }, + "TokenCounter": { + "title": "Počítadlo LLM tokenů", + "subtitle": "Počítejte tokeny, vizualizujte bloky a odhadněte náklady na API.", + "inputLabel": "Text", + "sample": "Ukázka", + "clear": "Vymazat", + "placeholder": "Vložte text, kód nebo JSON pro počítání tokenů...", + "encoding": "Kódování", + "loading": "Načítám tokenizér...", + "encodings": { + "o200k": "o200k_base (GPT-4o / řada o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokeny", + "characters": "Znaky", + "words": "Slova", + "ratio": "Tokeny/slovo", + "ratioSub": "přibližně" + }, + "claudeRow": "Claude (přibližně)", + "estimate": "odhad", + "viz": { + "title": "Vizualizace tokenů", + "toggle": "Zobrazit", + "hint": "Přepněte pro obarvení každého tokenu jako bloku.", + "empty": "Zadejte text pro vizualizaci tokenů.", + "truncated": "Zobrazuji prvních {limit} z {total} tokenů." + }, + "cost": { + "title": "Kalkulačka nákladů", + "disclaimer": "upravitelné výchozí hodnoty — ověřte si aktuální ceny", + "model": "Model", + "expectedOutput": "Očekávané výstupní tokeny", + "customInput": "Vstup $/MTok", + "customOutput": "Výstup $/MTok", + "inputCost": "Náklady na vstup", + "outputCost": "Náklady na výstup", + "total": "Celkem", + "per1000": "Na 1000 volání" + } + }, + "WebhookTester": { + "title": "Tester webhooků", + "subtitle": "Vytvořte dočasnou URL a živě prohlížejte příchozí webhook požadavky.", + "intro": { + "title": "Vytvořte webhook URL pro zahájení zachytávání požadavků", + "hint": "Získáte neuhodnutelnou URL, která zaznamenává každý příchozí požadavek. Vyprší po 24 hodinách." + }, + "createBtn": "Vytvořit webhook URL", + "creating": "Vytvářím…", + "urlLabel": "Vaše webhook URL", + "expiresIn": "Vyprší za {countdown}", + "copyUrlBtn": "Kopírovat URL", + "copied": "Zkopírováno", + "clearBtn": "Vymazat", + "newBinBtn": "Nová URL", + "requestsTitle": "Požadavky", + "selectPrompt": "Vyberte požadavek pro zobrazení jeho detailů.", + "expiredNotice": "Tato webhook URL vypršela. Vytvořte novou pro pokračování v testování.", + "base64Badge": "base64", + "truncatedBadge": "zkráceno", + "empty": { + "title": "Čekám na požadavky…", + "hint": "Odešlete požadavek na svou URL a okamžitě se zde objeví. Zkuste toto:" + }, + "detail": { + "receivedAt": "Přijato", + "source": "Zdrojová IP", + "contentType": "Content-Type", + "query": "Parametry dotazu", + "headers": "Hlavičky", + "body": "Tělo", + "copyBody": "Kopírovat tělo", + "emptyBody": "Bez těla" + }, + "errors": { + "createFailed": "Vytvoření webhook URL se nezdařilo. Zkuste to prosím znovu.", + "clearFailed": "Vymazání požadavků se nezdařilo. Zkuste to prosím znovu." + } + }, + "WebsocketTester": { + "title": "Tester WebSocket", + "subtitle": "Připojte se k WebSocket koncovému bodu a živě odesílejte, přijímejte a prohlížejte zprávy", + "urlLabel": "URL koncového bodu", + "protocolsLabel": "Subprotokoly", + "protocolsPlaceholder": "volitelné, oddělené čárkou", + "connect": "Připojit", + "disconnect": "Odpojit", + "status": { + "disconnected": "Odpojeno", + "connecting": "Připojuji", + "connected": "Připojeno", + "error": "Chyba" + }, + "messages": { + "connecting": "Připojuji k {url}…", + "connected": "Spojení navázáno", + "connectedWithProtocol": "Spojení navázáno (protokol: {protocol})", + "invalidUrl": "Neplatná URL — musí začínat ws:// nebo wss://", + "connectFailed": "Otevření spojení se nezdařilo", + "socketError": "Došlo k chybě spojení", + "notConnected": "Nepřipojeno — nejprve se připojte", + "binaryMessage": "Přijata binární zpráva" + }, + "log": { + "title": "Protokol zpráv", + "empty": "Zatím žádné zprávy. Připojte se a začněte odesílat.", + "copyAll": "Kopírovat vše", + "download": "Stáhnout jako .txt", + "clear": "Vymazat protokol", + "prettyPrint": "Zformátovat JSON", + "showRaw": "Zobrazit surový text" + }, + "composer": { + "placeholder": "Napište zprávu k odeslání…", + "send": "Odeslat", + "formatJson": "Formátovat jako JSON", + "invalidJson": "Zpráva není platný JSON", + "hint": "Cmd/Ctrl+Enter pro odeslání" + }, + "ping": { + "label": "Automatický ping", + "seconds": "s", + "intervalTitle": "Interval pingu v sekundách", + "payloadPlaceholder": "obsah pingu" + } + }, + "WhoisLookup": { + "title": "Whois vyhledávání", + "subtitle": "Vyhledejte registrační údaje libovolné domény nebo IP adresy přes RDAP", + "placeholder": "example.com nebo 8.8.8.8", + "lookupBtn": "Vyhledat", + "looking": "Vyhledávám…", + "refreshBtn": "Obnovit", + "tryLabel": "Zkuste:", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "notAvailable": "Není k dispozici", + "summary": { + "registrar": "Registrátor", + "network": "Síť" + }, + "dates": { + "created": "Vytvořeno", + "updated": "Aktualizováno", + "expires": "Vyprší" + }, + "sections": { + "status": "Stav", + "nameservers": "Jmenné servery", + "contacts": "Kontakty", + "rawRdap": "Surové RDAP" + }, + "contacts": { + "roles": "Role", + "name": "Jméno", + "organization": "Organizace" + }, + "errors": { + "invalidTarget": "Zadejte platný název domény nebo IP adresu", + "lookupFailed": "Vyhledávání se nezdařilo (stav {status})" + } + } +} diff --git a/apps/desktop-ui/messages/da.json b/apps/desktop-ui/messages/da.json new file mode 100644 index 00000000..7ddfa0fd --- /dev/null +++ b/apps/desktop-ui/messages/da.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Oversigt", + "settings": "Indstillinger", + "apiClient": "API-klient", + "httpStatusCodes": "HTTP-statuskoder", + "passwordManager": "Adgangskodeadministration", + "environmentManager": "Miljøadministration", + "bookmarks": "Bogmærker", + "jsonFormatter": "JSON-formatering", + "emailValidator": "E-mailvalidering", + "nosqlExplorer": "Databaseudforsker", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Krypteringslegeplads", + "jwtDecoder": "JWT-dekoder", + "certificatePemDecoder": "Certifikat / PEM-afkoder", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL-formater", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL-parser", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Hemmelighed / API-nøgle", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Kontrastkontrol", + "jsonSchemaGenerator": "JSON-skemagenerator", + "imageToBase64": "Billede til Base64", + "imageCompressor": "Billedkomprimering", + "cssGradientBuilder": "CSS Gradient Builder", + "gitignoreGenerator": ".gitignore Generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Kodestykker", + "qrCodeGenerator": "QR-kodegenerator", + "dnsLookup": "DNS-opslag", + "ipSubnetCalculator": "IP-/subnetberegner", + "hashGenerator": "Hash-generator", + "hmacGenerator": "HMAC-generator", + "totpGenerator": "TOTP / 2FA-kode", + "markdownPreview": "Markdown-forhåndsvisning", + "numberBaseConverter": "Talsystem-konverter", + "formatConverter": "Formatkonverter", + "mockDataGenerator": "Mock-datagenerator", + "svgOptimizer": "SVG-optimering", + "unitConverter": "Enhedskonverter", + "dockerComposeGenerator": "Docker Compose-generator", + "breakRoom": "Pauserum", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAML-formatering", + "sshKeyGenerator": "SSH / RSA nøglegenerator", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod-beregner", + "codeScreenshot": "Kodeskærmbillede", + "cssGenerators": "CSS-generatorer", + "exifViewer": "EXIF-fremviser", + "faviconGenerator": "Favicon-generator", + "jsonDiff": "JSON-diff", + "jsonpathPlayground": "JSONPath-legeplads", + "keycodeInspector": "Tastekode-inspektør", + "markdownTableGenerator": "Markdown-tabel", + "timezoneConverter": "Tidszonekonverter", + "tokenCounter": "Token-tæller", + "webhookTester": "Webhook-tester", + "websocketTester": "WebSocket-tester", + "whoisLookup": "Whois-opslag" + }, + "Help": { + "title": "Hjælp og dokumentation", + "subtitle": "Sådan hænger MyDevTools sammen, hvad hver app gør, og hvordan sikkerhed og kryptering beskytter dine data.", + "tabs": { + "overview": "Overblik", + "apps": "Apps", + "security": "Sikkerhed", + "tips": "Tips" + }, + "overview": { + "whatIs": { + "title": "Hvad er MyDevTools?", + "description": "Et udvikler‑workspace med login: en oversigt og et sæt værktøjer til hverdagsopgaver.", + "p1": "Når du har logget ind, opretter appen en session med MyDevTools API, så værktøjer kan indlæse og gemme dine data. Sidebaren viser alle apps; du kan skjule værktøjer, du ikke bruger, i", + "settingsLink": "Indstillinger", + "p1After": ".", + "p2": "Nogle funktioner bruger en masteradgangskode (eller global vault‑nøgle), så hemmeligheder som adgangskoder og forbindelsesstrenge krypteres på din enhed, før de når serveren." + }, + "quickLinks": { + "title": "Hurtige links", + "dashboard": "Oversigt", + "settings": "Indstillinger" + } + }, + "apps": { + "intro": "Åbn et værktøj fra sidebaren eller oversigten. Nedenfor er en kort guide pr. app.", + "open": "Åbn", + "howItWorks": "Sådan fungerer det", + "generic": "Brug dette værktøj fra sidebaren til dets dedikerede arbejdsgang." + }, + "security": { + "session": { + "title": "Login og API‑session", + "description": "Identitet og hvordan browseren taler med din backend.", + "items": { + "authentication": "Godkendelse bruger Firebase til login. Efter login udveksler appen din Firebase ID‑token til API‑cookies.", + "accessTokens": "Access tokens er JWT’er signeret på serveren (HS256), som viser hvilken bruger der kalder API’et.", + "refreshTokens": "Refresh tokens gemmes i HttpOnly cookies; serveren gemmer en SHA‑256 hash af tokenet, ikke selve tokenet." + } + }, + "encryption": { + "title": "Kryptering du bør kende", + "description": "Klientside‑krypto for vault og følsomme forbindelsesdata.", + "p1": "Til adgangskodeadministration, miljøadministration og gemte forbindelser i NoSQL Explorer bruger webappen browserens Web Crypto API:", + "items": { + "kdf": "PBKDF2 med SHA‑256, 100.000 iterationer og en tilfældig salt afleder en 256‑bit nøgle fra din masteradgangskode.", + "aes": "AES‑256‑GCM krypterer data; en tilfældig 12‑byte IV bruges for hver kryptering.", + "scope": "Den afledte nøgle bliver i browseren (ikke‑udtrækkelig). Serveren modtager kun ciphertext og IV’er, ikke din masteradgangskode eller hemmeligheder i klartekst." + }, + "warning": "Hvis du glemmer masteradgangskoden, kan krypterede data ikke gendannes. Opbevar den sikkert." + }, + "logout": { + "title": "Log ud og lokale data", + "body": "Log ud rydder hemmeligheder i hukommelsen, vault‑tilstand og (hvis relevant) master‑key‑materiale fra IndexedDB og afslutter API‑sessionen. Data, der kun ligger i lokal lagring på enheden, kan blive liggende, indtil du rydder dem i browseren." + } + }, + "tips": { + "title": "Brug MyDevTools sikkert", + "items": { + "masterPassword": "Brug en stærk og unik masteradgangskode til vault‑funktioner.", + "apiClient": "Behandl API‑klienten som enhver HTTP‑klient: undgå at indsætte produktions‑secrets i delte miljøer, hvis du ikke ved hvem der kan se dem.", + "nosql": "Databaseudforskeren forbinder til databaser du konfigurerer; brug mindst‑privilegerede brugere og netværksregler hvor muligt.", + "json": "JSON‑formateringen kører lokalt i browseren—undgå stadig at indsætte meget følsomme data på delte maskiner.", + "settings": "Tilpas synlige værktøjer i Indstillinger for at holde sidebaren fokuseret." + } + } + }, + "Settings": { + "title": "Indstillinger", + "language": "Sprog", + "spanish": "Spansk", + "english": "Engelsk", + "french": "Fransk", + "arabic": "Arabisk", + "catalan": "Catalansk", + "chineseSimplified": "kinesisk (forenklet)", + "czech": "Čeština", + "danish": "Dansk", + "greek": "Græsk", + "german": "Tysk", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukrainsk" + }, + "SettingsPage": { + "title": "Indstillinger", + "subtitle": "Administrer dine kontoindstillinger og præferencer.", + "userProfile": { + "title": "Brugerprofil", + "description": "Dine personlige oplysninger og kontooplysninger.", + "avatarAlt": "Bruger", + "anonymousUser": "Anonym bruger", + "noEmailProvided": "Ingen e-mail angivet", + "notLoggedIn": "Ikke logget ind." + }, + "tools": { + "title": "Værktøjer og funktioner", + "description": "Vælg, hvilke værktøjer der skal vises i din navigationssidebjælke." + }, + "appearance": { + "title": "Udseende", + "description": "Tilpas, hvordan applikationen ser ud på din enhed.", + "themePreference": "Temapræference", + "themes": { + "light": "Lys", + "dark": "Mørk", + "system": "System" + } + }, + "language": { + "title": "Sprog", + "description": "Indstil det sprog, der bruges på tværs af platformen.", + "selectLanguage": "Vælg sprog", + "selectPlaceholder": "Vælg et sprog", + "helpText": "Ændring af dit sprog vil opdatere menuer og etiketter på tværs af platformen.", + "languages": { + "en": "Engelsk (English)", + "fr": "Français (Fransk)", + "es": "Spansk (Español)", + "ar": "عربى (Arabisk)", + "ca": "Català (Catalan)", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Græsk)", + "de": "Deutsch (Tysk)", + "da": "Dansk", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italiensk (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "God morgen", + "afternoon": "God eftermiddag", + "evening": "God aften" + }, + "commaName": ", {name}", + "welcomeBack": "Velkommen tilbage", + "welcomeBackNamed": "Velkommen tilbage, {name}", + "tagline": "Hvad vil du bygge i dag?", + "brandName": "MyDevTools", + "stats": { + "tools": "Værktøjer", + "favorites": "Favoritter", + "pinned": "Fastgjort", + "recent": "Senest brugt" + }, + "sections": { + "recentlyUsed": "Senest brugt", + "favorites": "Favoritter", + "whatsNew": "Nyheder", + "pinned": "Fastgjort" + }, + "groups": { + "apps": "Apps" + }, + "toolCard": { + "defaultDescription": "Udforsk dette værktøj for bedre funktionalitet.", + "launchTool": "Åbn værktøj" + }, + "tools": { + "toDo": { + "title": "Opgaver", + "description": "Administrer dine to-do-lister effektivt." + }, + "notes": { + "title": "Noter", + "description": "Fang idéer og organiser dine tanker." + }, + "passwordManager": { + "title": "Adgangskodeadministrator", + "description": "Gem og administrer adgangskoder sikkert." + }, + "environmentManager": { + "title": "Miljøadministration", + "description": "Gem .env-lignende variabler pr. projekt og miljø med tags." + }, + "bookmarks": { + "title": "Bogmærker", + "description": "Administrer og organiser bogmærker." + }, + "apiClient": { + "title": "API-klient", + "description": "Test og fejlfind HTTP-anmodninger." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Udforsk MongoDB-databaser." + }, + "emailValidator": { + "title": "E-mail-validator", + "description": "Bekræft og valider e-mailadresser." + }, + "encryptionPlayground": { + "title": "Krypteringslegeplads", + "description": "AES-GCM med rå nøgle eller adgangssætning; JSON-pakker, Web Crypto kun i browseren." + }, + "jwtDecoder": { + "title": "JWT-dekoder", + "description": "Afkod JWT-header, payload og udløb." + }, + "certificatePemDecoder": { + "title": "Certifikat / PEM-afkoder", + "description": "Afkod X.509-certifikater og CSR’er: emne, udsteder, gyldighed og SAN." + }, + "sshKeyGenerator": { + "title": "SSH / RSA-nøglegenerator", + "description": "Generér Ed25519- eller RSA-nøglepar i browseren. Download i OpenSSH- og PEM-format." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL-formater", + "description": "Formatér GraphQL med syntaksfremhævning og en enkel forespørgselsbygger." + }, + "yamlFormatter": { + "title": "YAML-formatering", + "description": "Formater, valider og konverter YAML til JSON i browseren." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL-parser", + "description": "Opdel en URL i protokol, vært, sti, forespørgselsparametre og hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Hemmelighed / API-nøgle", + "description": "Kryptografisk tilfældige strenge med konfigurerbart alfabet, længde og masseeksport." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Kontrastkontrol", + "description": "WCAG 2.1-kontrastforhold mellem tekst og baggrund (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "JSON-skemagenerator", + "description": "Udled et JSON-skema og typede modeller fra eksempel-JSON." + }, + "imageToBase64": { + "title": "Billede til Base64", + "description": "Konverter billeder til data-URI eller rå Base64-strenge." + }, + "imageCompressor": { + "title": "Billedkomprimering", + "description": "Komprimér JPEG, PNG og WebP lokalt med en kvalitetsskyder." + }, + "cssGradientBuilder": { + "title": "CSS Gradient Builder", + "description": "Visuel CSS-gradientbygger med vinkelkontrol og farvestop." + }, + "gitignoreGenerator": { + "title": ".gitignore Generator", + "description": "Generer flettede .gitignore-filer med det samme fra 500+ teknologistakke." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Konverter CSV- eller Excel-filer til JSON og eksporter tilbage til CSV eller XLSX." + }, + "snippetManager": { + "title": "Kodestykker", + "description": "Gem kodestykker med syntaksfremhævelse, automatisk sprogdetektion og formatering." + }, + "qrCodeGenerator": { + "title": "QR-kodegenerator", + "description": "Opret PNG QR-koder fra tekst eller URL i browseren." + }, + "ipSubnetCalculator": { + "title": "IP-/subnetberegner", + "description": "IPv4 og IPv6 CIDR: netmaske, intervaller og adresser." + }, + "dnsLookup": { + "title": "DNS-opslag", + "description": "Forespørg DNS-poster for ethvert domæne — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Hash-generator", + "description": "MD5, SHA-1 og SHA-2 til tekst eller filer." + }, + "hmacGenerator": { + "title": "HMAC-generator", + "description": "Underskriv webhooks og test API-integrationer med HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "TOTP / 2FA-kodegenerator", + "description": "Indsæt en Base32-hemmelighed fra autentificeringsappen; den sekscifrede kode opdateres hvert 30. sekund—nyttigt til login- og MFA-tests." + }, + "markdownPreview": { + "title": "Markdown-forhåndsvisning", + "description": "Live Markdown-renderer med HTML-eksport og HTML → Markdown-konvertering." + }, + "numberBaseConverter": { + "title": "Talsystem-konverter", + "description": "Konverter heltal mellem baser 2–36." + }, + "formatConverter": { + "title": "Formatkonverter", + "description": "Konvertér mellem JSON, YAML, TOML og XML i alle retninger. Alle 12 kombinationer understøttes." + }, + "mockDataGenerator": { + "title": "Mock-datagenerator", + "description": "Definér kolonner, vælg datatyper og eksporter datasæt til seeds, Postman eller tests. Op til 5000 rækker, alt i browseren — intet uploades." + }, + "httpStatusCodes": { + "title": "HTTP-statuskoder", + "description": "Søg HTTP-statuskoder med beskrivelser (1xx–5xx)." + }, + "svgOptimizer": { + "title": "SVG-optimering", + "description": "Indsæt SVG, fjern støj med SVGO og se UTF-8-størrelsesbesparelse." + }, + "unitConverter": { + "title": "Enhedskonverter", + "description": "Konverter mellem 323 enheder på tværs af 43 videnskabelige og tekniske kategorier." + }, + "dockerComposeGenerator": { + "title": "Docker Compose-generator", + "description": "Vælg databaser, caches, køer, proxyer og observabilitets-images; eksporter docker-compose.yml til lokale stacks." + }, + "game2048": { + "title": "2048", + "description": "Skub og flet fliser for at nå 2048. Klassisk nummerpuslespil." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klassisk nummerpuslespil. 3 sværhedsgrader, 150 faser hver." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Gennemse nøgler, undersøg værdier og kør kommandoer på enhver Redis-instans." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt-generator og -kontrol", + "description": "Hash adgangskoder med bcrypt, og verificér eksisterende hashes." + }, + "chmodCalculator": { + "title": "Chmod-beregner", + "description": "Konvertér Unix-tilladelser mellem oktal, symbolsk notation og afkrydsningsfelter." + }, + "codeScreenshot": { + "title": "Kodeskærmbillede", + "description": "Forvandl kildekode til smukke, delbare billeder." + }, + "cssGenerators": { + "title": "CSS-generatorer", + "description": "Generatorer til box-shadow, border-radius og flydende clamp()." + }, + "exifViewer": { + "title": "EXIF-fremviser og -fjerner", + "description": "Inspicér og fjern EXIF-metadata fra fotos lokalt." + }, + "faviconGenerator": { + "title": "Favicon-generator", + "description": "Generér favicons, app-ikoner og manifest-uddrag." + }, + "jsonDiff": { + "title": "JSON-diff", + "description": "Semantisk, strukturbevidst sammenligning af to JSON-dokumenter." + }, + "jsonpathPlayground": { + "title": "JSONPath-legeplads", + "description": "Forespørg JSON med JSONPath eller JMESPath, og se resultater live." + }, + "keycodeInspector": { + "title": "Tastekode-inspektør", + "description": "Tryk på en tast for at se dens JavaScript-hændelseskoder og modifikatorer." + }, + "markdownTableGenerator": { + "title": "Markdown-tabelgenerator", + "description": "Byg Markdown- og HTML-tabeller i en regnearkslignende gittereditor." + }, + "timezoneConverter": { + "title": "Tidszonekonverter", + "description": "Konvertér et tidspunkt på tværs af mange zoner med dagsforskydning og arbejdstidsmarkeringer." + }, + "tokenCounter": { + "title": "LLM-token-tæller", + "description": "Tæl LLM-tokens, visualisér chunks, og estimér API-omkostninger." + }, + "webhookTester": { + "title": "Webhook-tester", + "description": "Opfang og inspicér indgående webhook-anmodninger i realtid." + }, + "websocketTester": { + "title": "WebSocket-tester", + "description": "Forbind til WebSocket-endpoints, og udveksl beskeder i realtid." + }, + "whoisLookup": { + "title": "Whois-opslag", + "description": "Slå registreringsoplysninger for domæner og IP-adresser op via RDAP." + } + }, + "tabs": { + "apps": "Apps", + "analytics": "Analyse", + "ui": "Brugerflade" + }, + "analytics": { + "title": "Dine data på et øjeblik", + "subtitle": "Antal synkroniseret med din konto på serveren.", + "loadError": "Kunne ikke indlæse analyse.", + "retry": "Prøv igen", + "loginHint": "Log ind for at se brugsanalyse og grænsefladeindstillinger.", + "passwordEntries": "Adgangskodeposter", + "bookmarks": "Bogmærker", + "bookmarkFolders": "Bogmærkemapper", + "tasksTotal": "Opgaver", + "tasksCompleted": "Fuldført", + "tasksOngoing": "I gang", + "tasksNotStarted": "Ikke startet", + "projects": "Projekter", + "nosqlConnections": "DB-forbindelser", + "notes": "Noter", + "apiClientCollections": "API-samlinger", + "apiClientEnvironments": "API-miljøer", + "apiClientHistory": "API-historikposter", + "jsonFormatterDocuments": "JSON-formatdokumenter", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Kode-snippets", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Brugerfladeindstillinger", + "subtitle": "Tema, sprog og synlige værktøjer i sidebjælken — fra dine kontoindstillinger.", + "loadError": "Kunne ikke indlæse indstillinger.", + "loginHint": "Log ind for at se dine gemte grænsefladeindstillinger.", + "theme": "Tema", + "accent": "Accentfarve", + "locale": "Sprog", + "enabledTools": "Synlige værktøjer", + "favoriteTools": "Foretrukne værktøjer", + "toolsWithUsage": "Værktøjer med brugsstatistik" + }, + "signIn": "Log ind", + "noResults": "Ingen værktøjer matcher din søgning", + "noResultsHint": "Prøv et andet nøgleord, eller ryd filteret.", + "filterAll": "Alle", + "loginCta": { + "description": "Log ind for at spore dine senest brugte værktøjer." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Indlæser...", + "myTasksTitle": "Mine opgaver", + "statsLine": "{total} opgaver • {percent}% fuldført", + "statsLineDesktop": "{total} i alt • {percent}% fuldført" + }, + "filters": { + "all": "Alle", + "total": "Total", + "allProjects": "Alle projekter", + "export": "Eksportere", + "kanban": "Kanban", + "list": "Liste", + "searchPlaceholder": "Søg opgaver...", + "searchAria": "Søg opgaver", + "clearSearchAria": "Ryd søgning", + "foundTasks": "Fundet {count} {count, plural, one {task} other {tasks}}", + "filterByAllAria": "Filtrer efter alle", + "filterByStatusAria": "Filtrer efter {status}" + }, + "status": { + "not-started": { + "label": "Ikke startet", + "description": "Opgaver, der ikke er startet endnu" + }, + "ongoing": { + "label": "Løbende", + "description": "Igangværende opgaver" + }, + "completed": { + "label": "Afsluttet", + "description": "Opgaver der er færdige" + } + }, + "priorities": { + "high": "Høj", + "medium": "Medium", + "low": "Lav" + }, + "form": { + "addNewTaskLabel": "Tilføj ny opgave", + "multilinePlaceholder": "Tilføj opgavedetaljer... (Shift+Enter for ny linje)", + "singlelinePlaceholder": "Hvad skal der gøres? Tryk på Enter for at tilføje", + "taskInputAria": "Opgaveinput", + "projectPlaceholder": "Projekt", + "selectProject": "Vælg Projekt", + "errorSelectProject": "Vælg venligst et projekt, eller opret et nyt for at tilføje en opgave.", + "hintMultiline": "Shift+Enter for ny linje, Enter for at tilføje", + "hintSingleline": "Indtast for at tilføje", + "shortcutsAria": "Tastaturgenveje", + "shortcutsLabel": "Genveje", + "shortcutAddTask": "Tilføj opgave", + "shortcutNewLine": "Ny linje", + "shortcutClear": "Klar", + "addButton": "Tilføje" + }, + "list": { + "ariaLabel": "Opgaveliste", + "loading": "Indlæser dine opgaver...", + "emptyTitle": "Ingen opgaver fundet", + "emptyPrimary": "Kom i gang ved at tilføje din første opgave ovenfor! Tryk på Enter for hurtigt at tilføje opgaver.", + "emptySecondary": "Prøv at justere dine filtre eller søgeforespørgsel." + }, + "mobileDrawer": { + "addTaskSrOnly": "Tilføj opgave", + "addNewTaskTitle": "Tilføj ny opgave", + "addNewTaskDescription": "Opret en ny opgave for at spore dine fremskridt.", + "cancel": "Ophæve" + }, + "kanban": { + "ariaLabel": "Kanban bord", + "loading": "Indlæser dine opgaver...", + "dropHere": "Drop opgave her", + "noTasksYet": "Ingen opgaver endnu", + "emptyColumnAria": "Tom kolonne", + "columnAria": "{title} kolonne med {count} opgaver", + "countInColumnAria": "{count} opgaver i {title}" + }, + "taskItem": { + "confirmDelete": "Er du sikker på, at du vil slette denne opgave?", + "statusPlaceholder": "Status", + "changeStatusAria": "Skift opgavestatus", + "taskOptionsAria": "Opgavemuligheder", + "editTask": "Rediger opgave", + "copyTask": "Kopiér opgave", + "copied": "Kopieret!", + "deleteTask": "Slet opgave", + "statusHeading": "Status", + "editTaskAria": "Rediger opgave" + }, + "editDialog": { + "title": "Rediger opgave", + "description": "Opdater opgavedetaljer, tilføj underopgaver, sæt prioriteter og mere.", + "taskTitleLabel": "Opgavetitel *", + "taskTitlePlaceholder": "Hvad skal der gøres?", + "descriptionLabel": "Beskrivelse", + "descriptionPlaceholder": "Tilføj flere detaljer om denne opgave...", + "statusLabel": "Status", + "priorityLabel": "Prioritet", + "dueDateLabel": "Forfaldsdato", + "pickDate": "Vælg en dato", + "clearDate": "Ryd dato", + "projectLabel": "Projekt", + "selectProjectPlaceholder": "Vælg et projekt", + "noProject": "Intet projekt", + "timeEstimateLabel": "Tidsestimat (minutter)", + "timeEstimatePlaceholder": "Hvor lang tid vil det tage?", + "tagsLabel": "Tags", + "customTagNamePlaceholder": "Brugerdefineret tagnavn", + "subtasksLabel": "Underopgaver", + "addSubtaskPlaceholder": "Tilføj en underopgave...", + "cancel": "Ophæve", + "saving": "Gemmer...", + "saveChanges": "Gem ændringer" + }, + "export": { + "title": "Eksporter opgaver", + "description": "Eksporter dine opgaver til JSON-, CSV- eller Excel-formater.", + "projectLabel": "Projekt", + "statusLabel": "Status", + "tagLabel": "Tag", + "allProjects": "Alle projekter", + "allStatuses": "Alle statusser", + "allTags": "Alle tags", + "exportAsJsonTitle": "Eksporter som JSON", + "exportAsJsonDescription": "Eksporter opgavedata inklusive underopgaver, tags og metadata.", + "exportJson": "Eksporter JSON", + "exportAsCsvExcelTitle": "Eksporter som CSV / Excel", + "exportAsCsvExcelDescription": "Eksporter opgaver til regnearksapplikationer.", + "exportCsv": "Eksporter CSV", + "exportExcel": "Eksporter Excel", + "close": "Tæt", + "toastNoTasks": "Ingen opgaver at eksportere med aktuelle filtre", + "toastExportedJson": "Eksporterede {count} opgaver til JSON!", + "toastExportedCsv": "Eksporterede {count} opgaver til CSV!", + "toastExportedExcel": "Eksporterede {count} opgaver til Excel!", + "toastFailedJson": "Kunne ikke eksportere til JSON", + "toastFailedCsv": "Kunne ikke eksportere til CSV", + "toastFailedExcel": "Kunne ikke eksportere til Excel", + "unknownProject": "Ukendt projekt" + }, + "ack": { + "taskAddedTitle": "Opgaven blev tilføjet", + "taskAddedFailedTitle": "Opgaven kunne ikke tilføjes", + "taskUpdatedTitle": "Opgaven blev opdateret", + "taskUpdatedFailedTitle": "Opgaven kunne ikke opdateres", + "taskStatusUpdateFailedTitle": "Opgavestatus kunne ikke opdateres", + "taskMovedTitle": "Opgave flyttet til {status}", + "tryAgain": "Prøv venligst igen.", + "taskDeletedTitle": "Opgave \"{text}\" slettet", + "undo": "Fortryd", + "taskRestoredTitle": "Opgaven gendannet", + "taskRestoredSuccessTitle": "Opgaven blev gendannet", + "taskRestoreFailedTitle": "Opgaven kunne ikke gendannes. Prøv venligst igen.", + "taskDeleteFailedTitle": "Opgaven kunne ikke slettes. Prøv venligst igen.", + "tasksImportedTitle": "Importeret {count} {count, plural, one {task} other {tasks}}!", + "tasksImportFailedTitle": "Kunne ikke importere opgaver. Prøv venligst igen.", + "unknownProject": "Ukendt projekt", + "projectCreatedTitle": "Projektet blev oprettet", + "projectCreateFailedTitle": "Projektet kunne ikke oprettes", + "projectUpdatedTitle": "Projektet blev opdateret", + "projectUpdateFailedTitle": "Projektet kunne ikke opdateres", + "projectDeletedTitle": "Projektet blev slettet", + "projectDeleteFailedTitle": "Projektet kunne ikke slettes" + } + }, + "Notes": { + "page": { + "loading": "Indlæser..." + }, + "layout": { + "mobileNavTitle": "Noter Navigation", + "openMenuAria": "Åbn notemenuen" + }, + "sidebar": { + "title": "Noter", + "createNoteAria": "Opret note", + "searchPlaceholder": "Søg i noter...", + "loading": "Indlæser...", + "noNotesFound": "Ingen noter fundet.", + "noNotesYet": "Ingen noter endnu. Klik på + for at oprette en.", + "inParent": "i {parentTitle}", + "untitled": "Uden titel", + "addSubPage": "Tilføj underside", + "delete": "Slet", + "deleteConfirmTitle": "Slet note?", + "deleteConfirmDescription": "Er du sikker på, at du vil slette \"{title}\"? Denne handling kan ikke fortrydes.", + "cancel": "Ophæve", + "confirmDelete": "Slet", + "noteOptionsAria": "Bemærk muligheder" + }, + "editor": { + "emptyState": "Vælg en note, eller opret en ny for at komme i gang.", + "titlePlaceholder": "Uden titel", + "saving": "Gemmer…", + "ready": "Klar", + "saved": "Gemt {time}", + "stats": "{words} ord · {read} min læsning", + "template": "Skabelon", + "applyTemplate": "Anvend skabelon", + "exportMdLabel": ".md", + "exportMd": "Eksportér som Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Eksportér som HTML", + "focusMode": "Fokustilstand", + "exitFocusMode": "Afslut fokustilstand", + "save": "Gem", + "addTag": "Tilføj tag…", + "removeTag": "Fjern tag {tag}", + "loading": "Indlæser…", + "searchHint": "for at søge", + "focusHint": "fokustilstand", + "notePath": "Notesti" + }, + "context": { + "authRequiredError": "Brugeren er ikke godkendt", + "defaultTitle": "Uden titel", + "cloudSyncImageError": "Slå cloudsynkronisering til for at gemme notebilleder. Tekstnoter forbliver lokale." + } + }, + "PasswordManager": { + "page": { + "loading": "Indlæser...", + "title": "Password Manager", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Kunne ikke dekryptere adgangskoder", + "toastLoadFailed": "Kunne ikke indlæse adgangskoder", + "toastUnlockedAuto": "Vault låses op automatisk", + "toastAutoUnlockFailed": "Automatisk oplåsning mislykkedes. Lås venligst op manuelt.", + "toastConnectFailed": "Kunne ikke oprette forbindelse til serveren", + "toastAutoUnlockWarning": "Vault oplåst, men automatisk oplåsning fungerer muligvis ikke ved genindlæsning", + "errorIncorrectPassword": "Forkert hovedadgangskode", + "errorUnlockFailed": "Kunne ikke låse op", + "errorPasswordsMismatch": "Adgangskoder stemmer ikke overens", + "errorPasswordTooShort": "Adgangskoden skal være på mindst 8 tegn", + "errorSetupFailed": "Kunne ikke konfigurere boksen", + "errorConnectFailed": "Kunne ikke oprette forbindelse til serveren.", + "mobileTitleCreate": "Opret din boks", + "mobileTitleWelcome": "Velkommen tilbage", + "mobileSubtitleCreate": "Indstil en hovedadgangskode for at beskytte dine legitimationsoplysninger", + "mobileSubtitleUnlock": "Indtast din hovedadgangskode for at låse op", + "placeholderMasterPassword": "Hovedadgangskode", + "placeholderConfirmPassword": "Bekræft adgangskode", + "labelMasterPassword": "Hovedadgangskode", + "labelConfirmPassword": "Bekræft adgangskode", + "placeholderEnterMaster": "Indtast hovedadgangskoden", + "placeholderConfirmMaster": "Bekræft hovedadgangskoden", + "buttonCreateVault": "Opret hvælving", + "buttonUnlock": "Lås op", + "buttonUnlockVault": "Lås hvælving op", + "footerCreate": "Din adgangskode gemmes aldrig og kan ikke gendannes", + "footerUnlock": "Sikret med ende-til-ende-kryptering", + "desktopTitleSetup": "Konfigurer adgangskodeboks", + "desktopTitleUnlock": "Lås hvælving op", + "desktopDescriptionSetup": "Opret en hovedadgangskode for at sikre dine legitimationsoplysninger. Denne adgangskode gemmes aldrig og kan ikke gendannes.", + "desktopDescriptionUnlock": "Indtast din hovedadgangskode for at få adgang til dine sikrede legitimationsoplysninger." + }, + "list": { + "loading": "Indlæser dine adgangskoder...", + "emptyTitle": "Vault er tom", + "emptyHintMobile": "Tryk på knappen + for at tilføje din første adgangskode", + "emptyHintDesktop": "Du har ikke gemt nogen adgangskoder endnu. Klik på knappen \"Tilføj adgangskode\" ovenfor for at komme i gang.", + "addPassword": "Tilføj adgangskode", + "searchPlaceholderMobile": "Søg i din boks...", + "searchPlaceholderDesktop": "Søg i boksen...", + "importExport": "Import/eksport", + "lockVault": "Lås hvælving", + "allPasswordsTitle": "Alle adgangskoder", + "passwordCount": "{count, plural, one {# password} other {# passwords}}", + "vaultHealthDrawerTitle": "Vault sundhed", + "noResults": "Ingen adgangskoder fundet matchende \"{query}\"", + "tableService": "Service", + "tableUsername": "Brugernavn", + "tablePassword": "Adgangskode", + "tableUrl": "URL", + "tableActions": "Handlinger", + "gridViewAria": "Gittervisning", + "listViewAria": "Listevisning", + "passwordStrengthTitle": "Adgangskodestyrke", + "deleteDialogTitle": "Slet adgangskode", + "deleteDialogDescription": "Er du sikker på, at du vil slette denne adgangskode? Denne handling kan ikke fortrydes.", + "cancel": "Ophæve", + "delete": "Slet", + "edit": "Redigere" + }, + "toasts": { + "copiedPassword": "Adgangskode kopieret til udklipsholder", + "copiedUsername": "Brugernavn kopieret til udklipsholder", + "passwordDeleted": "Adgangskode slettet", + "deleteFailed": "Adgangskoden kunne ikke slettes", + "vaultLocked": "Hvælving låst", + "importedCount": "Importeret {count, plural, one {# password} other {# passwords}}" + }, + "form": { + "addTitle": "Tilføj ny adgangskode", + "editTitle": "Rediger adgangskode", + "serviceName": "Tjenestenavn", + "usernameEmail": "Brugernavn / email", + "password": "Adgangskode", + "urlOptional": "Webstedets URL (valgfrit)", + "notesOptional": "Noter (valgfrit)", + "tags": "Tags", + "placeholderService": "f.eks. Netflix", + "placeholderUsername": "bruger@eksempel.com", + "placeholderPassword": "Indtast eller generér adgangskode", + "placeholderUrl": "https://...", + "placeholderNotes": "Yderligere detaljer...", + "placeholderTags": "Tilføj tags (tryk på Enter eller komma)", + "strength": "Styrke", + "strengthWeak": "Svag", + "strengthMedium": "Medium", + "strengthStrong": "Stærk", + "advancedGenerator": "Avanceret generator", + "cancel": "Ophæve", + "saving": "Gemmer...", + "savePassword": "Gem adgangskode", + "updating": "Opdaterer...", + "updatePassword": "Opdater adgangskode", + "addPasswordSrOnly": "Tilføj adgangskode", + "triggerAdd": "Tilføj adgangskode", + "toastSaved": "Adgangskoden blev gemt", + "toastSaveFailed": "Adgangskoden kunne ikke gemmes", + "toastUpdated": "Adgangskoden blev opdateret", + "toastUpdateFailed": "Kunne ikke opdatere adgangskoden" + }, + "security": { + "vaultHealth": "Vault sundhed", + "statusLabel": "Status", + "totalPasswords": "Samlet adgangskode", + "activeEntries": "Aktive poster i din boks", + "protected": "Beskyttet", + "weakPasswords": "Svage adgangskoder", + "needingImprovement": "Adgangskoder trænger til forbedring", + "allGood": "Alt godt", + "reusedPasswords": "Genbrugte adgangskoder", + "usedMultipleTimes": "Adgangskoder brugt flere gange", + "unique": "Enestående", + "total": "Total", + "weak": "Svag", + "reused": "Genbrugt", + "totalCountLabel": "{count, plural, one {# password} other {# passwords}}", + "weakCountLabel": "{count} svag", + "allSecure": "Alt sammen sikkert", + "scoreExcellent": "Fremragende", + "scoreGood": "God", + "scoreFair": "Retfærdig", + "scoreCritical": "Kritisk" + }, + "importExport": { + "title": "Import/eksport boks", + "description": "Administrer dine adgangskodedata. Du kan eksportere din boks til JSON eller importere fra en JSON-fil.", + "tabExport": "Eksportere", + "tabImport": "Importere", + "warningTitle": "Advarsel", + "warningBody": "Eksporterede filer indeholder dine adgangskoder i almindelig tekst. Opbevar denne fil sikkert og slet den efter brug.", + "exportBlurb": "Download hele din boks som en JSON-fil. Dette inkluderer alle tjenester, brugernavne, adgangskoder og noter.", + "downloadJson": "Download JSON-eksport", + "pasteLabel": "Indsæt JSON-data", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Indsæt indholdet af en tidligere eksporteret JSON-fil her.", + "importing": "Importerer...", + "importPasswords": "Importer adgangskoder", + "trigger": "Import/eksport", + "toastExported": "Vault blev eksporteret", + "toastExportFailed": "Kunne ikke eksportere boksen", + "toastImportFailed": "Kunne ikke importeres: ugyldigt JSON-format" + }, + "card": { + "copyUsername": "Kopiér brugernavn", + "openUrl": "Åbn URL", + "strength": "Styrke" + }, + "swipeable": { + "copy": "Kopi", + "delete": "Slet", + "copyPassword": "Kopiér adgangskoden", + "copyUsername": "Kopiér brugernavn", + "edit": "Redigere", + "deleteAction": "Slet" + }, + "floatingMenu": { + "lockVault": "Lås hvælving", + "importExport": "Import/eksport", + "addPassword": "Tilføj adgangskode" + }, + "generator": { + "tabPassword": "Adgangskode", + "tabPassphrase": "Adgangssætning", + "strength": "Styrke", + "lengthLabel": "Længde: {length} tegn", + "lengthBadge": "{length} tegn", + "characterSets": "Karaktersæt", + "uppercase": "Store bogstaver", + "lowercase": "Små bogstaver", + "numbers": "Tal", + "symbols": "Symboler", + "wordCountLabel": "Ordantal: {count}", + "wordCountBadge": "{count, plural, one {# word} other {# words}}", + "separator": "Separator", + "capitalization": "Brug af store bogstaver", + "includeNumber": "Inkluder nummer" + } + }, + "EnvironmentManager": { + "subtitle": "Krypterede miljøvariabler pr. projekt og miljø.", + "page": { + "loading": "Indlæser…", + "title": "Miljøadministration", + "loadFailed": "Kunne ikke indlæse miljøsæt" + }, + "list": { + "title": "Miljøer", + "loading": "Indlæser dine sæt…", + "emptyTitle": "Ingen miljøsæt endnu", + "emptyHint": "Opret et sæt for hvert projekt og miljø (f.eks. produktions-API-nøgler). Alt krypteres i din browser før upload.", + "searchPlaceholder": "Søg projekt, miljø, tags, noter…", + "tagFilterPlaceholder": "Filtrér efter tag", + "variableCount": "{count, plural, one {# variabel} other {# variabler}}", + "updatedAgo": "Opdateret {time}", + "copyDotEnv": "Kopiér .env", + "edit": "Redigér", + "delete": "Slet", + "cancel": "Annuller", + "confirmDelete": "Slet", + "deleteDialogTitle": "Slet miljøsæt?", + "deleteDialogDescription": "Dette fjerner den krypterede blob fra din konto. Kan ikke fortrydes.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env kopieret til udklipsholder", + "deleted": "Miljøsæt slettet", + "deleteFailed": "Kunne ikke slette" + }, + "form": { + "addTitle": "Nyt miljøsæt", + "editTitle": "Redigér miljøsæt", + "triggerAdd": "Tilføj miljøsæt", + "addSrOnly": "Tilføj miljøsæt", + "project": "Projekt", + "environment": "Miljø", + "placeholderProject": "f.eks. billing-api", + "placeholderEnvironment": "f.eks. production", + "pasteEnvLabel": "Indsæt .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Flet indsatte linjer", + "variables": "Variabler", + "addRow": "Tilføj række", + "keyPlaceholder": "NØGLE", + "valuePlaceholder": "værdi", + "notesOptional": "Noter (valgfrit)", + "placeholderNotes": "Rotationspolitik, ejer, links…", + "tags": "Tags", + "placeholderTags": "Tilføj tags (Enter eller komma)", + "cancel": "Annuller", + "save": "Gem", + "saving": "Gemmer…", + "update": "Opdatér", + "updating": "Opdaterer…", + "toastSaved": "Miljøsæt gemt", + "toastSaveFailed": "Kunne ikke gemme", + "toastUpdated": "Miljøsæt opdateret", + "toastUpdateFailed": "Kunne ikke opdatere", + "toastPasteEmpty": "Ingen NØGLE=værdi-linjer fundet", + "toastPasteApplied": "{count} variabler flettet fra indsætning", + "toastProjectEnvRequired": "Projekt og miljø er påkrævet", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Indlæser..." + }, + "manager": { + "foldersHeading": "Mapper", + "tagsHeading": "Tags", + "tagsMore": "+{count} mere", + "tagsShowLess": "Show less", + "searchPlaceholder": "Søg i dine bogmærker...", + "import": "Importere", + "export": "Eksportere", + "exportAsHtml": "Eksporter som HTML", + "exportAsJson": "Eksporter som JSON", + "addBookmark": "Tilføj bogmærke", + "matchingQuery": "matchende \"{query}\"", + "bookmarkCount": "{count, plural, one {# bookmark} other {# bookmarks}}", + "allBookmarks": "Alle bogmærker", + "uncategorized": "Ikke kategoriseret", + "unknownFolder": "Ukendt" + }, + "card": { + "urlCopied": "URL kopieret til udklipsholder", + "bookmarkDeleted": "Bogmærke slettet", + "edit": "Redigere", + "delete": "Slet", + "copyUrl": "Kopiér URL", + "deleteConfirmTitle": "Vil du slette bogmærke?", + "deleteConfirmDescription": "Dette vil permanent slette \"{title}\".", + "cancel": "Ophæve" + }, + "grid": { + "emptyTitle": "Ingen bogmærker endnu", + "emptyDescription": "Tilføj dit første bogmærke eller importer fra din browser for at komme i gang." + }, + "folderTree": { + "allBookmarks": "Alle bogmærker", + "addSubfolder": "Tilføj undermappe", + "rename": "Omdøb", + "deleteFolderTitle": "Vil du slette mappen \"{name}\"?", + "deleteFolderDescription": "Dette vil slette mappen og alle dens undermapper. Bogmærker i disse mapper vil blive flyttet til Ukategoriseret." + }, + "import": { + "title": "Importer bogmærker", + "description": "Importer bogmærker fra din browsers eksporterede fil.", + "parseError": "Bogmærkefil kunne ikke parses. Sørg for, at det er en gyldig bogmærkeeksport.", + "successToast": "Importerede {bookmarks} bogmærker og {folders} mapper", + "errorToast": "Kunne ikke importere bogmærker", + "chromeInstruction": "Menu → Bogmærker → Bogmærkehåndtering → ⋮ → Eksporter bogmærker", + "firefoxInstruction": "Menu → Bogmærker → Administrer bogmærker → Importer og sikkerhedskopier → Eksporter", + "safariInstruction": "Fil → Eksporter bogmærker...", + "edgeInstruction": "Menu → Favoritter → ⋯ → Eksporter favoritter", + "dropZoneTitle": "Slip din bogmærkefil her", + "dropZoneHint": "eller klik for at gennemse (HTML eller JSON)", + "selectedFileHint": "Klik for at vælge en anden fil", + "errorTitle": "Fejl", + "previewTitle": "Importer forhåndsvisning", + "previewBookmarks": "{count, plural, one {# bookmark} other {# bookmarks}}", + "previewFolders": "{count, plural, one {# folder} other {# folders}}", + "importModeLabel": "Importtilstand", + "modeMerge": "Flet sammen med eksisterende bogmærker", + "modeReplace": "Erstat alle eksisterende bogmærker", + "cancel": "Ophæve", + "importButton": "Importere", + "importButtonWithCount": "Importer {count, plural, one {# bookmark} other {# bookmarks}}" + }, + "addBookmark": { + "titleAdd": "Tilføj bogmærke", + "titleEdit": "Rediger bogmærke", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Indtast venligst en gyldig URL", + "titleLabel": "Titel", + "titlePlaceholder": "Bogmærke titel", + "titleRequired": "Indtast venligst en titel", + "descriptionLabel": "Beskrivelse (valgfrit)", + "descriptionPlaceholder": "Tilføj en note om dette bogmærke", + "folderLabel": "Folder", + "folderPlaceholder": "Vælg mappe", + "noFolder": "Ingen mappe", + "tagsLabel": "Tags", + "tagsPlaceholder": "Tilføj tags (tryk på Enter)", + "toastUpdated": "Bogmærke opdateret", + "toastAdded": "Bogmærke tilføjet", + "cancel": "Ophæve", + "submitAdd": "Tilføj bogmærke", + "submitUpdate": "Opdater bogmærke" + }, + "addFolder": { + "title": "Opret mappe", + "nameLabel": "Mappenavn", + "namePlaceholder": "Min mappe", + "nameRequired": "Indtast venligst et mappenavn", + "parentLabel": "Forældremappe", + "parentPlaceholder": "Vælg overordnet mappe", + "rootParent": "Rod (ingen forælder)", + "toastCreated": "Mappe oprettet", + "cancel": "Ophæve", + "submit": "Opret mappe" + }, + "editFolder": { + "title": "Omdøb mappe", + "nameLabel": "Mappenavn", + "namePlaceholder": "Mappenavn", + "nameRequired": "Indtast venligst et mappenavn", + "toastRenamed": "Mappe omdøbt", + "cancel": "Ophæve", + "save": "Spare" + } + }, + "EmailValidator": { + "header": { + "title": "E-mail validator", + "description": "Bekræft og valider e-mailadresser for syntaks, domæne og leveringsevne." + }, + "tabs": { + "single": "Enkelt verifikation", + "bulk": "Bulk upload" + }, + "single": { + "cardTitle": "Enkel e-mailbekræftelse", + "cardDescription": "Indtast en e-mailadresse for at kontrollere dens leveringsdygtighed og omdømme.", + "placeholder": "Indtast e-mailadresse...", + "clearAria": "Ryd e-mail", + "validate": "Valider", + "validating": "Validerer...", + "formatError": "Indtast venligst en gyldig e-mailadresse" + }, + "toasts": { + "errorTitle": "Fejl", + "enterEmail": "Indtast venligst en e-mailadresse", + "invalidFormatTitle": "Ugyldigt e-mail-format", + "invalidFormatDescription": "Indtast venligst en gyldig e-mailadresse", + "validateFailedDescription": "Der opstod en fejl under validering af e-mailen", + "noEmailsTitle": "Ingen e-mails fundet", + "noEmailsDescription": "Sørg for, at din Excel har en 'e-mail'-kolonne.", + "fileTooLargeTitle": "Filen er for stor", + "fileTooLargeDescription": "Fandt {total} e-mails. Kun de første 4000 vil blive valideret.", + "successTitle": "Succes", + "successValidated": "Valideret {count} e-mails med succes.", + "batchFailedTitle": "Batchvalidering mislykkedes", + "batchFailedDescription": "Der opstod en fejl under behandling af massevalidering.", + "noExportTitle": "Ingen data at eksportere", + "noExportValidOnly": "Der blev ikke fundet nogen gyldige e-mails til eksport.", + "noExportAll": "Ingen resultater at eksportere.", + "exportSuccessTitle": "Eksporten lykkedes", + "exportSuccessExcelAll": "Eksporterede {count} e-mails til Excel.", + "exportSuccessExcelValid": "Eksporterede {count} gyldige e-mails til Excel.", + "exportSuccessCsvAll": "Eksporterede {count} e-mails til CSV.", + "exportSuccessCsvValid": "Eksporterede {count} gyldige e-mails til CSV." + }, + "disposableAlert": { + "title": "Engangs-e-mail fundet", + "description": "Denne e-mail-adresse tilhører en engangs-e-mail-udbyder. Det kan være midlertidigt eller usikkert." + }, + "score": { + "reliabilityTitle": "Pålidelighedsscore", + "scoreLabel": "Score" + }, + "details": { + "sectionTitle": "Valideringsdetaljer" + }, + "validation": { + "syntaxLabel": "Syntaks gyldig", + "syntaxTooltip": "Kontrollerer, om e-mailadressen følger det korrekte format (f.eks. bruger@domæne.com)", + "domainLabel": "Domæne findes", + "domainTooltip": "Bekræfter, at domænenavnet i e-mailadressen faktisk eksisterer", + "mxLabel": "MX-poster fundet", + "mxTooltip": "Kontrollerer, om domænet har MX-poster (mail exchange) konfigureret til at modtage e-mails", + "mailboxLabel": "Postkasse findes", + "mailboxTooltip": "Bekræfter, at den specifikke postkasse/konto findes på mailserveren", + "disposableLabel": "Engangs-e-mail", + "disposableTooltip": "Angiver, om denne e-mail tilhører en midlertidig/engangs-e-mail-tjeneste (kan være risikabelt)", + "roleBasedLabel": "Rollebaseret e-mail", + "roleBasedTooltip": "Identificerer, om dette er en rollebaseret e-mail (f.eks. support@, info@) i stedet for en personlig adresse" + }, + "copy": { + "emailCopied": "E-mail kopieret!", + "copyTooltip": "Kopiér e-mail" + }, + "bulk": { + "dropHere": "Slip din fil her", + "dragHere": "Træk og slip din fil her", + "uploadHint": "Upload en .xlsx-, .xls- eller .csv-fil, der indeholder en \"e-mail\"-kolonne for at validere op til 4000 e-mails.", + "filePrefix": "Fil:", + "selectFile": "Vælg Fil", + "downloadTemplate": "Download skabelon" + }, + "bulkLoading": { + "title": "Validerer e-mails...", + "batchProgress": "Behandler batch {current} af {total}..." + }, + "stats": { + "totalProcessed": "Samlet behandlet", + "valid": "Gyldig", + "risky": "Risikabel", + "invalid": "Ugyldig" + }, + "results": { + "detailedTitle": "Detaljerede resultater", + "searchPlaceholder": "Søg i e-mails...", + "newVerification": "Ny bekræftelse", + "exportAll": "Eksporter alle", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Eksport gyldig ({count})" + }, + "showingResults": "Viser {shown} af {total} resultater", + "table": { + "email": "E-mail", + "status": "Status", + "score": "Score", + "checks": "Checks" + }, + "noSearchResults": "Ingen resultater fundet matchende \"{query}\"", + "checksTooltip": { + "syntax": "Syntaks", + "domain": "Domæne", + "mx": "MX", + "mailbox": "Postkasse" + }, + "exportColumns": { + "email": "E-mail", + "status": "Status", + "score": "Score", + "syntax": "Syntaks", + "domain": "Domæne", + "mx": "MX", + "mailbox": "Postkasse", + "disposable": "Engangs", + "roleBased": "Rollebaseret", + "valid": "Gyldig", + "invalid": "Ugyldig", + "yes": "Ja", + "no": "Ingen" + }, + "sheets": { + "validEmails": "Gyldige e-mails", + "results": "Resultater", + "template": "Skabelon" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Explorer", + "heroSubtitle": "Opret forbindelse til dine databaser for at administrere samlinger, udføre forespørgsler og visualisere dine data.", + "featureMultiTitle": "Multiforbindelse", + "featureMultiDesc": "Administrer flere databaseforbindelser samtidigt.", + "featureMongoTitle": "MongoDB Support", + "featureMongoDesc": "Native support til MongoDB med avanceret filtrering.", + "featureQueryTitle": "Smart forespørgsel", + "featureQueryDesc": "Kraftig forespørgselsbygger med syntaksfremhævning.", + "connectCta": "Opret forbindelse til MongoDB", + "footerNote": "Understøtter i øjeblikket {db}. Flere databaser kommer snart.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Vælg en samling", + "selectCollectionDesc": "Vælg en samling fra sidebjælken for at se dokumenter og begynde at udforske dine data.", + "dialogAddConnection": "Tilføj forbindelse", + "toastConnectionAdded": "Forbindelse tilføjet", + "toastFetchFailed": "Kunne ikke hente dokumenter for {collection}", + "toastDocDeleted": "Dokument slettet", + "deleteDocTitle": "Er du helt sikker?", + "deleteDocDescription": "Denne handling kan ikke fortrydes. Dette vil permanent slette dokumentet fra din samling.", + "closeAllTitle": "Vil du lukke alle faner?", + "closeAllDescription": "Er du sikker på, at du vil lukke alle faner? Alle ikke-gemte ændringer i forespørgselsfiltre vil gå tabt.", + "closeAllConfirm": "Luk alle", + "cancel": "Ophæve", + "delete": "Slet" + }, + "connection": { + "titleEdit": "Rediger forbindelse", + "titleConnect": "Opret forbindelse til MongoDB", + "subtitleEdit": "Opdater dine forbindelsesoplysninger nedenfor.", + "subtitleConnect": "Indtast din MongoDB-forbindelsesstreng for at begynde at udforske dine data.", + "labelName": "Navn", + "placeholderName": "f.eks. Produktion DB", + "labelConnectionString": "Forbindelsesstreng", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://brugernavn:password@host:port/database", + "testConnection": "Test forbindelse", + "connecting": "Opretter forbindelse...", + "updateConnect": "Opdater og tilslut", + "connect": "Forbinde", + "cancelEdit": "Annuller redigering", + "savedConnections": "Gemte forbindelser", + "emptySaved": "Ingen gemte forbindelser endnu.", + "lastUsed": "Sidst brugt {time}", + "toastTestOk": "Forbindelsen lykkedes!", + "toastTestFail": "Forbindelsen mislykkedes: {message}", + "toastUpdated": "Forbindelsen er opdateret", + "confirmDelete": "Er du sikker på, at du vil slette denne gemte forbindelse?", + "toastDeletedConn": "Forbindelsen slettet", + "toastDeleteFail": "Forbindelsen kunne ikke slettes", + "copyStringTitle": "Kopiér forbindelsesstrengen", + "toastStringCopied": "Forbindelsesstrengen er kopieret!", + "editTitle": "Redigere", + "deleteTitle": "Slet" + }, + "sidebar": { + "explorer": "Explorer", + "add": "Tilføje", + "tooltipAdd": "Tilføj ny forbindelse", + "tooltipRefresh": "Opdater forbindelser", + "searchPlaceholder": "Søge...", + "loadingConnections": "Indlæser forbindelser...", + "noConnectionsFound": "Ingen forbindelser fundet", + "toastLoadFail": "Kunne ikke indlæse forbindelser", + "toastConnectFail": "Kunne ikke oprette forbindelse til {name}", + "toastCollectionsFail": "Kunne ikke hente samlinger for {name}", + "toastRenamed": "Forbindelsen omdøbt", + "toastRenameFail": "Forbindelsen kunne ikke omdøbes", + "confirmDeleteConnection": "Er du sikker på, at du vil slette forbindelsen \"{name}\"?", + "toastDeleted": "Forbindelsen slettet", + "toastDeleteConnFail": "Forbindelsen kunne ikke slettes", + "confirmDropDb": "Er du sikker på, at du vil slette databasen \"{name}\"? Denne handling kan ikke fortrydes.", + "toastDbDropped": "Database {name} droppet", + "confirmDropCollection": "Er du sikker på, at du vil droppe samlingen \"{name}\"? Denne handling kan ikke fortrydes.", + "toastCollectionDropped": "Indsamling {name} droppet", + "toastCollectionRenamed": "Samling omdøbt til {name}", + "toastDatabaseRenamed": "Database omdøbt til {name}", + "connecting": "Opretter forbindelse...", + "connectionFailed": "Forbindelsen mislykkedes", + "noDatabases": "Ingen databaser", + "loading": "Indlæser...", + "noCollections": "Ingen samlinger", + "renameCollectionTitle": "Omdøb samling", + "renameDatabaseTitle": "Omdøb database", + "placeholderNewCollection": "Nyt samlingsnavn", + "placeholderNewDatabase": "Nyt databasenavn", + "rename": "Omdøb", + "refresh": "Opfriske", + "copyDbName": "Kopier DB-navn", + "toastDbNameCopied": "Databasenavn kopieret til udklipsholder", + "dropDatabase": "Drop database", + "dropCollection": "Drop Collection", + "menuRename": "Omdøb", + "menuCopyConnectionString": "Kopier forbindelsesstreng", + "menuDeleteConnection": "Slet forbindelse", + "toastSidebarStringCopied": "Forbindelsesstrengen er kopieret!", + "cancel": "Ophæve", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} dokumenter", + "table": "Tabel", + "json": "JSON", + "tree": "Træ", + "tooltipTable": "Tabelvisning", + "tooltipJson": "JSON-visning", + "tooltipTree": "Træudsigt", + "itemsPerPage": "Varer pr. side", + "perPage": "{n} / side", + "prevPage": "Forrige side", + "nextPage": "Næste side", + "expandAll": "Udvid alle", + "collapseAll": "Skjul alle", + "refreshData": "Opdater data", + "insert": "Indsæt", + "export": "Eksportere", + "actions": "Handlinger", + "invalidJson": "Ugyldigt JSON-indhold", + "copiedClipboard": "Kopieret til udklipsholder", + "docUpdated": "Dokumentet blev opdateret", + "docInserted": "Dokumentet blev indsat", + "invalidJsonShort": "Ugyldig JSON", + "docCopied": "Dokument kopieret til udklipsholder", + "openFullEditor": "Åbn i fuld editor", + "cellArray": "Array ({length})", + "cellObject": "Objekt", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Ingen dokumenter matcher din forespørgsel", + "emptyFilterHint": "Prøv at justere dit filter eller rydde forespørgslen.", + "clearFilter": "Ryd filter", + "emptyCollectionTitle": "Denne samling er tom", + "emptyCollectionHint": "Indsæt et dokument for at komme i gang.", + "insertDocument": "Indsæt dokument", + "formatJson": "Formater JSON", + "compactJson": "Kompakt JSON", + "copyClipboard": "Kopiér til udklipsholder", + "copyJson": "Kopier JSON", + "duplicate": "Duplikere", + "editDoc": "Rediger dokument", + "deleteDoc": "Slet dokument", + "documentLabel": "Dokument {n}", + "editDialogTitle": "Rediger dokument", + "insertDialogTitle": "Indsæt dokument", + "viewValueTitle": "Se værdi", + "saveChanges": "Gem ændringer", + "close": "Tæt", + "cancel": "Ophæve", + "insertButton": "Indsæt", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Aktivt filter anvendt", + "filterActiveTooltip": "Filter aktivt", + "closeAll": "Luk alle" + }, + "query": { + "migrateHistory": "Migrerede forespørgselshistorik til skyen", + "saveHistoryFail": "Forespørgselshistorikken kunne ikke gemmes", + "deleteQueryFail": "Forespørgslen kunne ikke slettes", + "invalidJsonQuery": "Ugyldig JSON-forespørgsel", + "placeholder": "f.eks. { \"status\": \"active\" }", + "clearQuery": "Ryd forespørgsel", + "openAdvanced": "Åbn Advanced Editor", + "visualBuilder": "Visual Query Builder", + "filterRules": "Filterregler", + "addRule": "Tilføj regel", + "noFilters": "Ingen filtre anvendt. Tilføj en regel for at filtrere dokumenter.", + "where": "HVOR", + "and": "OG", + "fieldPlaceholder": "Felt", + "opPlaceholder": "Op", + "typePlaceholder": "Type", + "opRegex": "Regex", + "opIn": "I", + "opNin": "Ikke i", + "opExists": "Eksisterer", + "typeAuto": "Auto", + "typeString": "Snor", + "typeNumber": "Antal", + "typeBoolean": "Boolean", + "typeDate": "Dato", + "typeObjectId": "ObjectId", + "true": "ægte", + "false": "falsk", + "valuePlaceholder": "Værdi", + "valueListPlaceholder": "værdi1, værdi2", + "clearFilters": "Ryd filtre", + "applyFilters": "Anvend filtre", + "queryHistory": "Forespørgselshistorik", + "noSavedQueries": "Ingen gemte forespørgsler endnu", + "advancedEditorTitle": "Avanceret forespørgselseditor", + "templatesPlaceholder": "Skabeloner", + "templateEmptyAgg": "Tom samling", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Deltag)", + "templateGroup": "$gruppe (antal/sum)", + "format": "Format", + "formattedJson": "Formateret JSON", + "runQuery": "Kør forespørgsel" + }, + "export": { + "selectFieldsError": "Vælg venligst mindst ét ​​felt til eksport", + "success": "Eksporteret til {filename}", + "fail": "Kunne ikke eksportere data", + "title": "Eksporter data", + "formatLabel": "Eksporter format", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Vælg felter", + "selectAll": "Vælg alle", + "fieldsSelected": "{count, plural, one {# field selected} other {# fields selected}}", + "cancel": "Ophæve", + "export": "Eksportere", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Type: {type}", + "itemSummary": "{count, plural, one {# item} other {# items}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Ny anmodning", + "importedRequest": "Importeret anmodning", + "untitledRequest": "Unavngivet anmodning" + }, + "toasts": { + "curlImported": "cURL blev importeret", + "curlParseFailed": "Kunne ikke parse cURL-kommandoen", + "invalidJsonBody": "Ugyldig JSON-tekst", + "requestFailed": "Forespørgsel mislykkedes: {message}", + "curlPasted": "cURL blev indsat og parset", + "responseCopied": "Svar kopieret til udklipsholder", + "codeCopied": "Kode kopieret til udklipsholder", + "copyFailed": "Kopiering til udklipsholder mislykkedes" + }, + "layout": { + "collections": "Samlinger", + "closeSidebar": "Luk sidebjælken", + "openSidebar": "Åbn sidepanelet", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Metode", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Sender...", + "send": "Sende", + "invalidJsonBodyHelp": "Kan ikke sende: JSON-brødteksten er ugyldig" + }, + "requestTabs": { + "params": "Params", + "headers": "Overskrifter", + "body": "Legeme", + "auth": "Auth", + "bodyType": "Kropstype", + "selectType": "Vælg type", + "bodyNone": "Ingen", + "bodyJson": "JSON", + "bodyText": "Tekst", + "noBodyHint": "Intet kropsindhold for denne anmodning", + "switchToJson": "Skift til JSON body", + "authType": "Auth type", + "noAuth": "Ingen auth", + "bearerToken": "Bærertegn", + "basicAuth": "Grundlæggende auth", + "apiKey": "API nøgle", + "labelToken": "Token", + "placeholderBearerToken": "Bærertegn", + "labelUsername": "Brugernavn", + "placeholderUsername": "Brugernavn", + "labelPassword": "Adgangskode", + "placeholderPassword": "Adgangskode", + "labelKey": "Nøgle", + "placeholderApiKeyName": "Nøglenavn (f.eks. X-Api-Key)", + "labelValue": "Værdi", + "placeholderValue": "Værdi", + "addTo": "Tilføj til", + "header": "Overskrift", + "queryParams": "Forespørgselsparametre", + "noAuthHint": "Denne anmodning bruger ikke godkendelse." + }, + "responsePanel": { + "emptyTitle": "Klar til at udforske?", + "emptyHint": "Indtast en URL, og klik på Send for at se svardataene her.", + "responsePreviewAlt": "Forhåndsvisning af svar", + "previewUnavailable": "Forhåndsvisning er ikke tilgængelig for denne indholdstype", + "status": "Status", + "time": "Tid", + "size": "Størrelse", + "copyBody": "Kopiér brødtekst", + "downloadResponse": "Download svar", + "bodyTab": "Legeme", + "previewTab": "Forhåndsvisning", + "headersTab": "Overskrifter", + "binaryRawView": "Binære data kan ikke vises i rå visning.", + "errorStatusLabel": "Fejl", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Ny fane" + }, + "collectionsSidebar": { + "title": "API-ressourcer", + "newCollection": "Ny kollektion", + "tabCollections": "Samlinger", + "tabHistory": "Historie", + "noCollectionsTitle": "Ingen samlinger", + "noCollectionsHint": "Opret en samling for at organisere dine API-anmodninger.", + "createCollection": "Opret samling", + "newFolder": "Ny mappe", + "rename": "Omdøb", + "delete": "Slet", + "emptyCollection": "Tom samling", + "recentRequests": "Seneste anmodninger", + "clearAll": "Ryd alle", + "noHistoryTitle": "Ingen historie endnu", + "noHistoryHint": "Send en anmodning om at se den her.", + "dialogNewFolderTitle": "Ny mappe", + "dialogNewFolderDescription": "Opret en ny mappe for at organisere dine anmodninger.", + "labelFolderName": "Mappenavn", + "placeholderFolderName": "Min mappe", + "createFolder": "Opret mappe", + "dialogNewCollectionTitle": "Ny kollektion", + "dialogNewCollectionDescription": "Opret en ny samling på øverste niveau.", + "labelCollectionName": "Samlingens navn", + "placeholderCollectionName": "Min samling", + "dialogRenameCollectionTitle": "Omdøb samling", + "dialogRenameCollectionDescription": "Indtast et nyt navn til samlingen.", + "placeholderRenameCollection": "Samlingens navn", + "renameAction": "Omdøb", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Ny mappe", + "delete": "Slet", + "emptyFolder": "Tom mappe" + }, + "environmentManager": { + "noEnvironment": "Intet miljø", + "manageDialogTitle": "Administrer miljøer", + "placeholderNewEnv": "Nyt miljø", + "variablesHeading": "{name} variabler", + "addVariable": "Tilføj variabel", + "columnVariable": "Variabel", + "columnValue": "Værdi", + "placeholderKey": "Nøgle", + "placeholderValue": "Værdi", + "selectEnvToEdit": "Vælg et miljø, der skal redigeres" + }, + "codeGenerator": { + "triggerTitle": "Generer kode", + "dialogTitle": "Generer kode", + "languageCurl": "krølle", + "languageJavaScript": "JavaScript (hent)", + "languagePython": "Python (anmodninger)", + "languageGo": "Gå", + "copy": "Kopi", + "errorGenerating": "Fejl ved generering af kode" + }, + "keyValueEditor": { + "columnKey": "Nøgle", + "columnValue": "Værdi", + "placeholderKey": "Nøgle", + "placeholderValue": "Værdi", + "addItem": "Tilføj element" + }, + "importCurl": { + "trigger": "Importer cURL", + "dialogTitle": "Importer cURL", + "dialogDescription": "Indsæt din cURL-kommando nedenfor for at importere den som en ny anmodning.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Importere" + }, + "saveRequest": { + "trigger": "Spare", + "dialogTitle": "Gem anmodning", + "dialogDescription": "Gem denne anmodning i en samlingsmappe.", + "labelName": "Anmod om navn", + "placeholderName": "Min anmodning", + "labelFolder": "Folder", + "placeholderFolder": "Vælg en mappe", + "save": "Spare" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Redigeringstilstand", + "viewOnlyMode": "Kun visningstilstand", + "insertComponent": "Indsæt komponent", + "addUnorderedList": "Tilføj uordnet liste", + "addOrderedList": "Tilføj bestilt liste", + "addTable": "Tilføj tabel" + }, + "addBlock": "Tilføj blok", + "media": { + "popoverTitle": "Tilføj medier", + "singleImage": "Enkelt billede", + "singleImageDesc": "Upload et billede", + "multipleImages": "Flere billeder", + "multipleImagesDesc": "Upload billedgitter", + "video": "Video", + "videoDesc": "Upload videofil", + "uploadingAlt": "Uploader...", + "uploadFailedAlt": "Upload mislykkedes" + }, + "export": { + "fabAria": "Eksport kode", + "title": "Eksportkode", + "description": "Kopier HTML- eller JSON-output af dit editorindhold", + "preview": "Forhåndsvisning", + "html": "HTML", + "json": "JSON", + "previewOptions": "Indstillinger for forhåndsvisning", + "enhanceSpaces": "Forbedre mellemrum", + "livePreview": "Live preview af gengivet HTML", + "htmlTailwind": "HTML med Tailwind CSS klasser", + "editorJson": "Redaktørstatus som JSON", + "copyHtml": "Kopier HTML", + "copyJson": "Kopier JSON", + "copied": "Kopieret!" + }, + "template": { + "fabTitle": "Skift skabelon", + "title": "Vælg en skabelon", + "description": "Vælg en skabelon for at komme hurtigt i gang med forudbyggede layouts og indhold", + "catAll": "Alle skabeloner", + "catProductivity": "Produktivitet", + "catCreative": "Kreativ", + "catBusiness": "Forretning", + "catPersonal": "Personlig", + "noTemplates": "Ingen skabeloner fundet i denne kategori", + "confirmTitle": "Vil du udskifte eksisterende indhold?", + "confirmDescription": "Du har eksisterende indhold i editoren. Anvendelse af denne skabelon vil erstatte alt nuværende indhold.", + "cannotUndo": "Denne handling kan ikke fortrydes.", + "cancel": "Ophæve", + "replaceContent": "Erstat indhold" + }, + "command": { + "searchPlaceholder": "Søgekommandoer...", + "noResults": "Ingen kommandoer fundet.", + "turnInto": "Slå til", + "h1": { + "label": "Overskrift 1", + "description": "Stor sektionsoverskrift", + "keywords": "h1,overskrift,titel,stor" + }, + "h2": { + "label": "Overskrift 2", + "description": "Mellem sektionsoverskrift", + "keywords": "h2,overskrift,undertekst" + }, + "h3": { + "label": "Overskrift 3", + "description": "Lille afsnitsoverskrift", + "keywords": "h3,overskrift,underoverskrift" + }, + "h4": { + "label": "Overskrift 4", + "description": "Lille sektionsoverskrift", + "keywords": "h4, overskrift" + }, + "h5": { + "label": "Overskrift 5", + "description": "Mindre overskrift", + "keywords": "h5, overskrift" + }, + "h6": { + "label": "Overskrift 6", + "description": "Den mindste overskrift", + "keywords": "h6, overskrift" + }, + "p": { + "label": "Afsnit", + "description": "Almindelig tekstafsnit", + "keywords": "p,afsnit,tekst,normal" + }, + "code": { + "label": "Kodeblok", + "description": "Kodestykke", + "keywords": "kode, kodeblok, snippet, præ" + }, + "blockquote": { + "label": "Citere", + "description": "Blok citat", + "keywords": "citat, blokcitat, citat" + }, + "li": { + "label": "Punktliste", + "description": "Enkelt listeelement med punkttegn", + "keywords": "liste,kugle,uordnet,ul,li" + }, + "ol": { + "label": "Nummereret liste", + "description": "Nummereret listeelement", + "keywords": "liste,nummereret,ordnet,ol,li" + }, + "img": { + "label": "Billede", + "description": "Upload eller integrer et billede", + "keywords": "billede, img, billede, foto, upload" + }, + "video": { + "label": "Video", + "description": "Upload eller integrer en video", + "keywords": "video, vid, film, mp4, upload" + }, + "table": { + "label": "Tabel", + "description": "Lav en tabel", + "keywords": "tabel, gitter, rækker, kolonner, celler" + } + }, + "toasts": { + "tableImported": "Tabel importeret", + "tableImportedDesc": "Markdown-tabel er blevet importeret", + "mediaPasted": "Medier indsat", + "pasteMixed": "{imageCount} billede(r) og {videoCount} video(er) blev indsat.", + "pasteImages": "{count} billede(r) blev indsat.", + "pasteVideos": "{count} video(er) blev indsat.", + "pasteFailed": "Indsæt mislykkedes", + "unexpectedError": "Der opstod en uventet fejl", + "uploadFailed": "Upload mislykkedes" + }, + "blockBackground": { + "menuItem": "Skift baggrundsfarve", + "dialogTitle": "Blok baggrundsfarve", + "dialogDescription": "Vælg en baggrundsfarve til denne blok", + "tabPreset": "Forudindstillede farver", + "tabCustom": "Brugerdefineret farve", + "sectionHeading": "Baggrundsfarver", + "apply": "Anvende", + "colors": { + "none": "Ingen", + "red": "Rød", + "orange": "Orange", + "yellow": "Gul", + "green": "Grøn", + "blue": "Blå", + "indigo": "Indigo", + "purple": "Lilla", + "pink": "Lyserød", + "teal": "Blågrøn", + "cyan": "Cyan", + "gray": "Grå" + } + }, + "tableDialog": { + "title": "Opret tabel", + "description": "Opret en ny tabel eller importer fra markdown", + "markdownCheckbox": "Jeg har en nedskrivningstabel", + "pasteMarkdownLabel": "Indsæt Markdown-tabel", + "markdownPlaceholder": "| Overskrift 1 | Overskrift 2 | Overskrift 3 |\n|--------|--------|--------|\n| Celle 1 | Celle 2 | Celle 3 |\n| Celle 4 | Celle 5 | Celle 6 |", + "markdownHint": "Indsæt din markdown-tabel ovenfor. Skal indeholde overskrifts- og separatorrækker.", + "rows": "Rækker", + "columns": "Kolonner", + "maxDimensions": "Maksimum: 20 rækker × 10 kolonner", + "cancel": "Ophæve", + "importTable": "Importtabel", + "createTable": "Opret tabel" + }, + "tableParse": { + "errors": { + "minRows": "Tabellen skal mindst have en overskriftsrække og en separatorrække", + "pipeFormat": "Tabelrækker skal starte og slutte med |", + "headerMinCol": "Overskriftsrækken skal have mindst én kolonne", + "separatorRow": "Anden række skal være en separator (f.eks. |---|---|)", + "rowColumnMismatch": "Række {row} har {got} kolonner, forventet {expected}", + "parseFailed": "Kunne ikke parse markdown-tabel" + } + }, + "link": { + "editLink": "Rediger link", + "addLink": "Tilføj link", + "selectedText": "Valgt tekst: \"{text}\"", + "linkUrl": "Link URL", + "urlPlaceholder": "https://example.com", + "update": "Opdatering", + "addLinkButton": "Tilføj link", + "toastApplied": "Link anvendt", + "toastLinkedTo": "Linket til: {url}", + "toastRemoved": "Link fjernet", + "toastRemovedDesc": "Linket er blevet fjernet fra valget" + }, + "customClass": { + "devMode": "Dev-tilstand", + "toggleDevModeAria": "Skift udviklertilstand", + "searchPlaceholderDev": "Søg efter klasser... (f.eks. 'tekst', 'bg', 'flex')", + "searchPlaceholderUser": "Søgestile... (f.eks. 'rød', 'fed', 'skygge')", + "appliesTitle": "Gælder: {className}", + "noMatches": "Ingen klasser fundet matchende \"{query}\"", + "toastClassReplaced": "Klasse udskiftet", + "toastClassReplacedDesc": "Erstattet \"{replaced}\" med \"{className}\"", + "toastCustomClassApplied": "Brugerdefineret klasse anvendt", + "toastAppliedClass": "Anvendt klasse: {className}" + }, + "insertComponents": { + "title": "Indsæt komponent", + "description": "Vælg en komponent, der skal indsættes i dit dokument", + "noComponents": "Ingen tilgængelige komponenter", + "categories": { + "media": "Medier", + "layout": "Layout", + "interactive": "Interaktiv", + "decoration": "Dekoration" + }, + "freeImage": { + "name": "Gratis bevægelsesbillede", + "description": "Tilføj et billede, der kan placeres hvor som helst og frit kan ændre størrelsen" + } + } + }, + "JsonFormatter": { + "title": "JSON-formatering", + "description": "Formater, valider og rediger JSON i tekst- og trævisning.", + "documentNameText": "Tekstdokument {n}", + "documentNameTree": "Trædokument {n}", + "newDocument": "Ny", + "copy": "Kopiér", + "save": "Gem", + "load": "Indlæs", + "saving": "Gemmer...", + "leftDocNameLabel": "Venstre dokumentnavn", + "rightDocNameLabel": "Højre dokumentnavn", + "toastCopiedText": "Tekstpanel kopieret", + "toastCopiedTree": "Træpanel kopieret", + "toastCopyFailed": "Kunne ikke kopiere JSON", + "toastLoginRequired": "Log ind for at gemme dokumenter", + "toastSaved": "Dokument gemt", + "toastSaveFailed": "Kunne ikke gemme dokument", + "format": "Formatér", + "compact": "Komprimer", + "sortKeys": "Sortér nøgler", + "repair": "Reparér", + "query": "Forespørgsel", + "toastInvalidJson": "Ugyldig JSON", + "toastAlreadyValid": "JSON er allerede gyldig", + "toastRepaired": "JSON repareret", + "toastRepairFailed": "Kunne ikke reparere JSON" + }, + "JwtDecoder": { + "title": "JWT-dekoder", + "subtitle": "Inspicér header og payload. Udløb bruger exp-claim. Intet sendes til en server; signaturen verificeres ikke.", + "tokenLabel": "Token", + "clear": "Ryd", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopiér", + "expiryLabel": "Udløb (exp)", + "signaturePresent": "Signatur til stede (ikke verificeret)", + "noExp": "Ingen exp-claim i payload.", + "status": { + "expired": "Udløbet", + "valid": "Gyldig" + }, + "fields": { + "unix": "Unix", + "local": "Lokal", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Udstedt (iat)", + "notBefore": "Ikke før (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Indsæt en JWT for at dekode.", + "segments": "En JWT kræver mindst header og payload (to segmenter).", + "base64": "Kunne ikke base64url-dekode header eller payload." + }, + "relative": { + "future": "om {n} {unit}", + "past": "for {n} {unit} siden" + }, + "units": { + "second": "{count, plural, one {sekund} other {sekunder}}", + "minute": "{count, plural, one {minut} other {minutter}}", + "hour": "{count, plural, one {time} other {timer}}", + "day": "{count, plural, one {dag} other {dage}}" + }, + "tabs": { + "decode": "Dekod", + "sign": "Generator / signering" + }, + "signer": { + "algLabel": "Algoritme (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Hemmelighed (HS*)", + "secretPlaceholder": "Indsæt din HMAC-hemmelighed…", + "privateKeyLabel": "Privat nøgle PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "\"none\"-algoritmen laver et usigneret token. Det er usikkert og bør kun bruges til test.", + "sign": "Signér", + "verify": "Verificér signatur", + "outputLabel": "Token (kompakt JWS)", + "outputPlaceholder": "Klik \"Signér\"…", + "copyToken": "Kopiér token", + "copied": "Kopieret", + "verifyKeyLabel": "Verifikationsnøgle (valgfri)", + "verifyKeyPlaceholder": "For RS*: indsæt den offentlige nøgle (SPKI) PEM. For HS*: lad stå tom for at genbruge hemmeligheden.", + "verifyKeyHint": "Hvis tom, bruger verificering signeringsnøglen (nyttigt for HS*).", + "verifyStatus": { + "valid": "Signatur gyldig", + "invalid": "Signatur ugyldig" + }, + "securityNote": "Alt kører lokalt i din browser; nøgler og tokens uploades ikke." + }, + "signErrors": { + "invalidJson": "Header og payload skal være gyldig JSON.", + "unsupportedAlg": "Algoritme understøttes ikke.", + "missingKey": "En signeringsnøgle er påkrævet for denne algoritme.", + "cryptoFailed": "Signering mislykkedes. Tjek nøgleformatet og prøv igen." + }, + "verifyErrors": { + "empty": "Indtast en JWT for at verificere.", + "segments": "En JWT skal have præcis 3 segmenter for at verificere en signatur.", + "unsupportedAlg": "Algoritme understøttes ikke eller mangler alg i headeren.", + "missingKey": "En verifikationsnøgle er påkrævet for denne algoritme.", + "base64": "Kunne ikke dekode JWT-header eller signatur.", + "cryptoFailed": "Verificering mislykkedes. Tjek nøgleformatet og prøv igen." + } + }, + "CertificatePemDecoder": { + "title": "Certifikat / PEM-afkoder", + "subtitle": "Indsæt et X.509-certifikat (-----BEGIN CERTIFICATE-----) eller en CSR (-----BEGIN CERTIFICATE REQUEST-----). Emne, udsteder, datoer og SAN analyseres lokalt; intet uploades.", + "pemLabel": "PEM-input", + "clear": "Ryd", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Parser…", + "copyHex": "Kopiér fingeraftryk (hex uden kolon)", + "errors": { + "empty": "Indsæt et PEM-certifikat eller en CSR for at afkode.", + "noBlock": "Ingen PEM-blok for certifikat eller CSR fundet.", + "parseFailed": "Kunne ikke parse disse PEM-data." + }, + "block": { + "title": "PEM-blok {index}", + "certificate": "X.509-certifikat", + "csr": "Certifikatunderskrivningsanmodning" + }, + "fields": { + "subject": "Emne", + "issuer": "Udsteder", + "notBefore": "Ikke gyldig før", + "notAfter": "Ikke gyldig efter", + "serial": "Serienummer (hex)", + "sha256": "SHA-256-fingeraftryk", + "sans": "Emnealtenative navne", + "sansNone": "Ingen SAN-udvidelse.", + "signatureAlgorithm": "Signaturalgoritme", + "csrIssuerNote": "CSR’er har ingen udsteder (den fremgår af det udstedte certifikat).", + "csrValidityNote": "CSR’er har ingen gyldighedsdatoer (de fremgår af det udstedte certifikat)." + }, + "sanType": { + "dns": "DNS", + "dn": "Biblioteksnavn", + "email": "E-mail", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Registreret ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL-formater", + "subtitle": "Formatér SQL til MySQL, PostgreSQL eller SQLite. Kører i din browser; intet uploades.", + "dialectLabel": "Dialekt", + "keywordsLabel": "Nøgleord", + "indentLabel": "Indrykning", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "STORE", + "lower": "små", + "preserve": "Bevar" + }, + "spaces": "{n} mellemrum", + "format": "Formatér", + "clear": "Ryd", + "counter": "{current} / {max} tegn", + "inputPanel": "Input", + "formattedPanel": "Formateret", + "copy": "Kopiér", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klik \"Formatér\"…", + "errors": { + "tooLong": "Input er begrænset til {max} tegn.", + "couldNotFormat": "Kunne ikke formatere denne SQL." + } + }, + "YamlFormatter": { + "title": "YAML-formatering", + "subtitle": "Formater, valider og konverter YAML i din browser. Intet uploades.", + "outputLabel": "Output", + "outputModes": { + "yaml": "YAML (formateret)", + "json": "JSON" + }, + "indentLabel": "Indrykning", + "spaces": "{n} mellemrum", + "format": "Formater", + "clear": "Ryd", + "copy": "Kopiér", + "counter": "{current} / {max} tegn", + "inputPanel": "Input", + "outputPanel": "Output", + "errors": { + "tooLong": "Input er begrænset til {max} tegn.", + "couldNotFormat": "Kunne ikke fortolke dette YAML." + } + }, + "GraphqlFormatter": { + "title": "GraphQL-formater", + "subtitle": "Formatér GraphQL-forespørgsler, mutationer og abonnementer i browseren. Intet uploades.", + "tabs": { + "format": "Formatér", + "build": "Forespørgselsbygger" + }, + "outputModeLabel": "Output", + "outputModes": { + "pretty": "Læsevenlig udskrift", + "minify": "Minificér" + }, + "format": "Formatér", + "clear": "Ryd", + "counter": "{current} / {max} tegn", + "inputPanel": "Input", + "outputPanel": "Output", + "copy": "Kopiér", + "builderHint": "Byg et skelet og brug det i editoren. Variabeldefinitioner i GraphQL-syntaks (f.eks. $id: ID!) — parenteser er valgfri.", + "builder": { + "operation": "Operation", + "operationName": "Operationsnavn", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Variabeldefinitioner", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Udvalg", + "apply": "Anvend på input", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Input er begrænset til {max} tegn.", + "couldNotFormat": "Kunne ikke parse eller udskrive denne GraphQL.", + "emptySelection": "Tilføj et udvalg før du anvender." + } + }, + "RegexTester": { + "title": "Regex-tester", + "subtitle": "JavaScript-lignende regex live mod din tekst. Matches fremhæves; intet forlader din browser.", + "patternLabel": "Mønster", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i ignorer store/små bogstaver", + "m": "m multiline", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(tom)", + "noFlags": "—", + "matches": "{count, plural, one {match} other {matches}}", + "testTextLabel": "Testtekst", + "counter": "{current} / {max}", + "clear": "Ryd", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Teksten er begrænset til {max} tegn.", + "invalid": "Ugyldigt regulært udtryk.", + "runaway": "Stoppede efter {max} matches (muligt løbsk mønster)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Timestamp-konverter", + "subtitle": "Unix-sekunder (≤10 cifre) eller millisekunder (længere heltal), ISO-8601 eller strenge som din browser kan parse. Alt kører lokalt.", + "inputLabel": "Input", + "placeholder": "1744200000 eller 2025-04-09T12:00:00Z", + "now": "Nu", + "copy": "Kopiér", + "hint": "Indsæt en værdi eller klik Nu.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativ", + "human": "Læsbar" + }, + "fields": { + "seconds": "Sekunder", + "milliseconds": "Millisekunder", + "utc": "UTC", + "local": "Lokal", + "distance": "Afstand" + }, + "errors": { + "empty": "Indtast et Unix-timestamp, en ISO-streng eller datotekst.", + "invalidNumber": "Ugyldigt numerisk timestamp.", + "outOfRange": "Uden for rækkevidde for JavaScript Date.", + "couldNotParse": "Kunne ikke parse som Unix, ISO-8601 eller browser-datostreng." + } + }, + "HttpStatusCodes": { + "title": "HTTP-statuskoder", + "subtitle": "Søg og gennemse HTTP-statuskoder for svar (1xx5xx) med korte beskrivelser og RFC-referencer. Kører lokalt.", + "header": { + "title": "HTTP-statuskoder", + "description": "Søgbar reference over HTTP-svarskoder med beskrivelser og RFC-referencer." + }, + "searchPlaceholder": "Søg: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtrér klasse…", + "classFilterAll": "Alle klasser", + "resultsCount": "{count} af {total}", + "table": { + "code": "Code", + "phrase": "Frase", + "description": "Beskrivelse", + "copy": "Kopiér" + }, + "noResults": "Ingen resultater for “{query}”.", + "specPrefix": "Spec:", + "copied": "Kode kopieret" + }, + "CronBuilder": { + "title": "Cron-udtryksbygger", + "subtitle": "Standard 5-felts cron: minute hour day month weekday. Parseren bruger seconds=0 internt. Valgfrit 6-felts format (sec min hour dom mon dow) virker i editoren.", + "tabs": { + "builder": "Visuel bygger", + "expression": "Udtryk" + }, + "presetsLabel": "Forudindstillinger", + "fieldsLabel": "Felter", + "rawExpressionLabel": "Rå udtryk", + "expressionHint": "Forlad feltet for at parse og synkronisere byggeren. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Aktuelt udtryk", + "copy": "Kopiér", + "quickPick": "Hurtigt valg", + "emptyExpression": "—", + "plainLanguageLabel": "Almindeligt sprog", + "nextRunsLabel": "Næste kørsler (lokal tid)", + "fields": { + "minute": "Minut", + "hour": "Time", + "dayOfMonth": "Dag i måneden", + "month": "Måned", + "dayOfWeek": "Ugedag" + }, + "presets": { + "everyMinute": "Hvert minut", + "every5Minutes": "Hver 5. minut", + "every15Minutes": "Hver 15. minut", + "everyHour": "Hver time (ved :00)", + "every6Hours": "Hver 6. time", + "dailyMidnight": "Dagligt ved midnat", + "daily0900": "Dagligt kl. 09:00", + "weekdays0900": "Hverdage kl. 09:00", + "weeklySunday": "Ugentligt (søndag 00:00)", + "monthlyFirst": "Månedligt (1. 00:00)", + "yearlyJan1": "Årligt (1. jan 00:00)" + }, + "picks": { + "minute": { + "every": "hvert minut" + }, + "hour": { + "every": "hver time" + }, + "dom": { + "any": "vilkårlig dag", + "last": "sidste dag" + }, + "month": { + "every": "hver måned" + }, + "dow": { + "any": "vilkårlig ugedag", + "weekdays": "hverdage", + "weekend": "weekend" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mar", + "apr": "Apr", + "may": "Maj", + "jun": "Jun", + "jul": "Jul", + "aug": "Aug", + "sep": "Sep", + "oct": "Okt", + "nov": "Nov", + "dec": "Dec" + }, + "weekdays": { + "sun": "Søn", + "mon": "Man", + "tue": "Tir", + "wed": "Ons", + "thu": "Tor", + "fri": "Fre", + "sat": "Lør" + }, + "errors": { + "fieldCount": "Forventer 5 felter (minute hour day month weekday) eller 6 inkl. sekunder.", + "invalid": "Ugyldigt cron-udtryk." + } + }, + "Base64": { + "title": "Base64-koder / dekoder", + "subtitle": "Kod tekst til Base64 eller dekod Base64-strenge", + "upload": "Upload", + "clear": "Ryd", + "modes": { + "encode": "Kod", + "decode": "Dekod" + }, + "swapTitle": "Byt input og output", + "panels": { + "plainText": "Ren tekst", + "base64Input": "Base64-input", + "base64Output": "Base64-output", + "decodedText": "Dekodet tekst" + }, + "placeholders": { + "encode": "Indtast tekst for at kode til Base64…", + "decode": "Indsæt Base64-streng for at dekode…" + }, + "dropHere": "Slip fil her", + "charCount": "{count} tegn", + "download": { + "title": "Download output", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Kopiér til udklipsholder", + "outputPlaceholder": "Output vises her…", + "errors": { + "invalidBase64": "Ugyldig Base64-streng. Tjek input.", + "encodeFailed": "Kunne ikke kode input." + } + }, + "UrlEncode": { + "title": "URL-koder / dekoder", + "subtitle": "Procentkod/afkod med encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Upload", + "clear": "Ryd", + "modes": { + "encode": "Kod", + "decode": "Afkod" + }, + "swapTitle": "Byt input og output", + "panels": { + "plainText": "Ren tekst", + "encodedInput": "Kodet input", + "encodedOutput": "Kodet output", + "decodedText": "Afkodet tekst" + }, + "placeholders": { + "encode": "Indtast tekst til procentkodning…", + "decode": "Indsæt procentkodet tekst til afkodning…" + }, + "dropHere": "Slip fil her", + "charCount": "{count} tegn", + "download": { + "title": "Download output", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Kopiér til udklipsholder", + "outputPlaceholder": "Output vises her…", + "errors": { + "invalidEncoding": "Ugyldig procentkodning. Tjek ufuldstændige %‑par eller escapes.", + "encodeFailed": "Kunne ikke kode (ugyldig Unicode i input)." + } + }, + "UrlParser": { + "title": "URL-parser", + "subtitle": "Opdel en URL i protokol, vært, sti, forespørgselsparametre og hash (fragment).", + "upload": "Upload", + "clear": "Ryd", + "panels": { + "input": "URL-input", + "output": "Resultat" + }, + "placeholders": { + "input": "Indsæt en URL (fx https://example.com/path?foo=bar#section) eller vært/sti (fx example.com/path)…" + }, + "dropHere": "Slip fil her", + "charCount": "{count} tegn", + "outputPlaceholder": "De parse‑de komponenter vises her…", + "fields": { + "protocol": "Protokol", + "host": "Vært", + "path": "Sti", + "hash": "Hash", + "queryParams": "Forespørgselsparametre" + }, + "paramCount": "{count} parametre", + "noParams": "Ingen forespørgselsparametre.", + "paramKey": "Nøgle", + "paramValue": "Værdi", + "copyTitle": "Kopiér til udklipsholder", + "copyParamTitle": "Kopiér som key=value", + "download": { + "title": "Download JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Ugyldig URL. Inkludér et værtsnavn (example.com) og tjek tegn." + } + }, + "UuidGenerator": { + "title": "UUID / ULID-generator", + "subtitle": "Generér RFC 9562-UUID’er eller ULID’er i browseren. Masseeksport op til {max} pr. kørsel.", + "format": "Format", + "kindHints": { + "ulid": "Sortérbar, URL-sikker identifikator på 26 tegn", + "uuid4": "Tilfældig (RFC 9562)", + "uuid7": "Tidsordnet, Unix ms + tilfældig", + "uuid1": "MAC-/tidsbaseret (RFC 9562)", + "uuid6": "Omarrangeret gregoriansk tidsbaseret", + "uuid3": "MD5-hash af navn + namespace", + "uuid5": "SHA‑1-hash af navn + namespace", + "nil": "Nul-UUID (alt nuller, RFC 9562)" + }, + "countLabel": "Antal (1–{max})", + "nameLabel": "Navn (inputstreng)", + "namePlaceholder": "fx my.app/resource", + "bulkHint": "Ved batch tilføjes #0, #1, … så hvert ID er unikt (v3/v5 er deterministiske).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Brugerdefineret UUID" + }, + "customNamespaceLabel": "Namespace-UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Generér", + "output": "Output", + "lines": "{count} linjer", + "download": { + "title": "Download .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Kopiér alt", + "uppercaseToggle": "Output med store bogstaver", + "hyphensToggle": "Vis bindestreger", + "copyJsonTitle": "Kopiér som JSON-array", + "outputPlaceholder": "Klik på \"Generér\"…", + "errors": { + "invalidCustomNamespace": "Brugerdefineret namespace skal være en gyldig UUID.", + "missingName": "Navn er påkrævet for UUID v3 og v5.", + "unknown": "Kunne ikke generere ID’er." + } + }, + "SecretApiKeyGenerator": { + "title": "Generator til hemmelighed / API-nøgle", + "subtitle": "Generér kryptografisk tilfældige strenge med konfigurerbar længde og alfabet. Masseeksport op til {max} pr. kørsel. Supplerer UUID-/ULID-generator.", + "alphabetLabel": "Alfabet", + "alphabetHint": "Dubletter fjernes. Hvert symbol vælges ensartet med crypto.getRandomValues og rejection sampling (ingen modulo-bias).", + "alphabetCount": "{count} unikke symboler", + "presetsLabel": "Forudindstillinger", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumerisk", + "hex": "Hex (små bogstaver)", + "asciiPrintable": "ASCII udskrivbar" + }, + "lengthLabel": "Længde pr. hemmelighed (1–{maxLen})", + "countLabel": "Antal (1–{maxBulk})", + "generate": "Generér", + "output": "Output", + "lines": "{count} linjer", + "download": { + "title": "Download .txt", + "filename": "hemmeligheder.txt" + }, + "copyTitle": "Kopiér alt", + "outputPlaceholder": "Klik på \"Generér\"…", + "entropy": "{bits} bits entropi", + "strength": { + "weak": "Svag", + "fair": "Middel", + "strong": "Stærk" + }, + "excludeAmbiguous": "Udelad tvetydige tegn", + "excludeAmbiguousHint": "Fjerner {chars} — tegn der let læses forkert eller er fjendtlige over for shellen.", + "prefixLabel": "Nøgleprefiks (valgfrit)", + "errors": { + "emptyAlphabet": "Tilføj mindst ét tegn til alfabetet.", + "alphabetTooLong": "Efter fjernelse af dubletter kan alfabetet højst have {maxAlpha} tegn.", + "lengthOutOfRange": "Længden skal være mellem 1 og {maxLen}.", + "countOutOfRange": "Antal skal være mellem 1 og {maxBulk}.", + "unknown": "Kunne ikke generere hemmeligheder." + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum Generator", + "subtitle": "Generer latinsk plassholdertekst til dine layouts og mockups.", + "paragraphs": "Afsnit", + "sentences": "Sætninger", + "words": "Ord", + "count": "Antal", + "generate": "Generer", + "copy": "Kopiér", + "startWith": "Start med 'Lorem ipsum...'", + "outputPlaceholder": "Output vises her...", + "outputType": "Outputtype", + "bulletList": "Punktliste", + "htmlMarkup": "HTML-markup", + "htmlMarkupHint": "Indpak output i afsnit, eller brug en liste til punkttilstand", + "output": "Output", + "download": "Download" + }, + "ColorPicker": { + "title": "Farvevælger og konverter", + "subtitle": "Konverter HEX, RGB og HSL; byg harmoniske paletter. Al behandling sker i din browser.", + "nativePicker": "Indbygget vælger", + "chooseColor": "Vælg farve", + "eyeDropper": "Øjedråber", + "invalidHex": "Ugyldig HEX-farve.", + "cssValues": "CSS-værdier", + "palettes": "Paletter", + "copied": "Kopieret", + "clickToCopy": "Klik for at kopiere", + "copyAllHex": "Kopiér alle HEX", + "copy": "Kopiér", + "copyColor": "Kopiér {color}", + "paletteShades": "Nuancer", + "paletteComplementary": "Komplementær", + "paletteTriadic": "Triadisk", + "paletteAnalogous": "Analog", + "paletteSplit": "Delt kompl.", + "paletteSquare": "Firkantet" + }, + "ContrastChecker": { + "title": "Kontrastkontrol", + "subtitle": "Beregner WCAG 2.1-kontrastforholdet mellem forgrund og baggrund. Viser AA og AAA for normal og stor tekst. Kører i din browser.", + "foreground": "Tekst / forgrund", + "background": "Baggrund", + "hexPlaceholder": "#RRGGBB", + "swap": "Byt farver", + "chooseForeground": "Vælg forgrundsfarve", + "chooseBackground": "Vælg baggrundsfarve", + "eyeDropper": "Pipette", + "ratio": "Kontrastforhold", + "copyRatio": "Kopiér forhold", + "copied": "Kopieret", + "previewHeading": "Forhåndsvisning", + "previewBody": "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon.", + "previewCaption": "Eksempeltekst i standardstørrelse.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — normal tekst (4,5:1)", + "levelAALarge": "AA — stor tekst (3:1)", + "levelAAANormal": "AAA — normal tekst (7:1)", + "levelAAALarge": "AAA — stor tekst (4,5:1)", + "pass": "Bestået", + "fail": "Ikke bestået", + "invalidHex": "Ugyldig HEX-farve.", + "largeTextHint": "Stor tekst: 18 pt+ almindelig eller 14 pt+ fed (WCAG-definitioner)." + }, + "DiffChecker": { + "title": "Diff-kontrol", + "subtitle": "Side-om-side linjediff af to tekster. Alt forbliver i din browser.", + "rowsCount": "{count} rækker", + "truncatedWarning": "Viser de første {max} rækker.", + "limitExceeded": "Hver side er begrænset til {max} tegn.", + "clearBoth": "Ryd begge", + "original": "Original", + "modified": "Ændret", + "originalPlaceholder": "Indsæt original tekst…", + "modifiedPlaceholder": "Indsæt ændret tekst…", + "comparisonLabel": "Side-om-side sammenligning", + "emptyState": "Indtast tekst i begge paneler for at sammenligne." + }, + "JsonSchemaGenerator": { + "title": "JSON-skemagenerator", + "subtitle": "Indsæt gyldig JSON for at generere JSON-skema eller typede modeller. Alt kører i din browser.", + "languageLabel": "Outputsprog", + "inputLabel": "JSON-input", + "inputPlaceholder": "Indsæt JSON her…", + "outputLabel": "Genereret output", + "outputPlaceholder": "Skemaet eller de genererede typer vises her…", + "parseError": "Ugyldig JSON", + "clear": "Ryd", + "copy": "Kopiér", + "copied": "Kopieret til udklipsholder", + "copyFailed": "Kunne ikke kopiere", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-skema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Indlæser editor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Billede til Base64", + "subtitle": "Slip et billede, få en data-URI eller rå Base64-streng. Alt forbliver i din browser.", + "upload": "Upload billede", + "clear": "Klar", + "modes": { + "dataUri": "Data-URI", + "rawString": "Raw Base64" + }, + "panels": { + "imageInput": "Billedinput", + "output": "Base64 output" + }, + "dropHere": "Drop billedet her", + "charCount": "{count} tegn", + "placeholders": { + "uploadImage": "Træk og slip et billede, eller klik på upload for at starte." + }, + "fileTooLarge": "Filen er for stor. Behandlingen kan fryse din browser.", + "copyTitle": "Kopiér til udklipsholder", + "download": { + "title": "Download tekst", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Billedkomprimering", + "subtitle": "Formindsk JPEG-, PNG- eller WebP-output lokalt med kvalitetskontrol — intet uploades.", + "upload": "Upload billede", + "clear": "Ryd", + "dropHere": "Slip billede her", + "placeholders": { + "uploadImage": "Træk og slip et billede, eller klik for at vælge en fil." + }, + "fileTooLarge": "Filen er for stor (maks. {maxMb} MB).", + "unsupportedFormat": "Filtypen understøttes ikke. Brug JPEG, PNG, WebP, GIF, AVIF eller BMP.", + "compressFailed": "Kunne ikke komprimere billedet. Prøv en anden fil eller et andet format.", + "panels": { + "original": "Original", + "compressed": "Komprimeret forhåndsvisning" + }, + "outputFormat": "Outputformat", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kvalitet", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG er tabsfri i browseren; kvalitetsskyderen gælder kun JPEG og WebP.", + "sizes": { + "original": "Original: {size}", + "compressed": "Komprimeret: {size}", + "saved": "{percent}% mindre" + }, + "largerWarning": "Output er større end input — prøv et andet format eller en anden kvalitet.", + "download": "Download", + "downloadFilename": "komprimeret-billede", + "processing": "Komprimerer…", + "emptyCompressed": "Komprimeret forhåndsvisning vises her." + }, + "CssGradientBuilder": { + "title": "CSS Gradient Builder", + "subtitle": "Visuel CSS-gradientbygger med vinkelkontrol, farvestop og CSS-kopi med et enkelt klik.", + "preview": "Forhåndsvisning af gradient", + "settings": "Indstillinger", + "type": "Type", + "linear": "Lineær", + "radial": "Radial", + "angle": "Vinkel", + "stops": "Farve stopper", + "color": "Farve", + "position": "Stilling (%)", + "addStop": "Tilføj stop", + "deleteStop": "Slet Stop", + "output": "CSS output", + "copy": "Kopier CSS", + "copied": "Kopieret!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Generator", + "subtitle": "Generer flettede .gitignore-filer øjeblikkeligt til en specifik teknisk stak eller kombination.", + "searchPlaceholder": "Søg stakke (f.eks. Node, Python, macOS)...", + "noResults": "Ingen stakke fundet.", + "selectedStacks": "Udvalgte stakke", + "output": "Forhåndsvisning af output", + "copy": "Kopiér til udklipsholder", + "copied": "Kopieret!", + "download": "Download .gitignore", + "downloading": "Downloader...", + "errorFetching": "Det lykkedes ikke at hente toplisten.", + "errorGenerating": "Kunne ikke generere .gitignore fra udvalgte stakke.", + "generating": "Genererer...", + "emptyState": "Vælg en eller flere tekniske stakke for at generere din .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Upload CSV eller Excel, rediger JSON, og download CSV eller XLSX. Kun tabeldata; Excel-datoer bliver ISO-tidsstempler i JSON.", + "dropTitle": "Slip en CSV- eller Excel-fil", + "dropHint": "Første ark bruges til .xlsx / .xls. CSV skal have en header-række.", + "chooseFile": "Vælg fil", + "jsonLabel": "JSON (array af objekter)", + "jsonPlaceholder": "Indsæt et JSON-array af objekter, eller importér CSV / Excel…", + "loadSample": "Indlæs eksempel", + "actions": { + "format": "Formatér JSON", + "clear": "Ryd", + "copy": "Kopiér", + "copied": "Kopieret", + "downloadCsv": "Download CSV", + "downloadXlsx": "Download Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Data", + "toastLoadedCsv": "CSV indlæst i editoren", + "toastLoadedExcel": "Excel indlæst i editoren", + "toastFormatted": "JSON formateret", + "toastExportedCsv": "CSV downloadet", + "toastExportedXlsx": "Excel downloadet", + "toastCopied": "Kopieret til udklipsholder", + "footnote": "Alt kører i din browser. JSON skal være et ikke-tomt array af objekter. Indlejrede værdier serialiseres som JSON i CSV-celler.", + "errors": { + "unsupportedFile": "Brug en .csv-, .xlsx- eller .xls-fil.", + "parseFailed": "Kunne ikke læse filen.", + "invalidJson": "Ugyldig JSON", + "exportFailed": "Eksport mislykkedes", + "copyFailed": "Kunne ikke kopiere" + } + }, + "SnippetManager": { + "title": "Kodestykke-manager", + "subtitle": "Gem snippets i browseren eller log ind for at synkronisere med din konto. Skift mellem visning og redigering, kopiér med ét klik, vælg sprog eller autodetektér, og formatér JSON, SQL eller Monaco-understøttet kode.", + "snippets": "Snippets", + "newSnippet": "Nyt snippet", + "newSnippetTitle": "Nyt snippet", + "defaultSnippetTitle": "Velkommen", + "defaultSnippetCode": "// Velkommen til kodestykker\n// Prøv visning vs redigering, kopiér, formatér og automatisk sprog\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Søg i snippets…", + "searchAria": "Søg i snippets", + "emptySearch": "Ingen snippets matcher din søgning.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Snippet-navn", + "language": "Sprog", + "detectedHint": "Fremhævning som {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Vis", + "modeEdit": "Rediger", + "copy": "Kopiér", + "copied": "Kopieret", + "format": "Formatér", + "cancel": "Annuller", + "deleteTitle": "Slet snippet?", + "deleteDescription": "«{title}» fjernes fra denne browser.", + "deleteConfirm": "Slet", + "editorAria": "Snippet-kodeeditor", + "editorLoading": "Indlæser editor…", + "toastCopied": "Kopieret til udklipsholder", + "toastCopyFailed": "Kunne ikke kopiere", + "toastNothingToCopy": "Intet at kopiere", + "toastFormatted": "Formateret", + "toastFormatFailed": "Kunne ikke formatere", + "toastFormatJsonInvalid": "Ugyldig JSON — ret syntaksen først", + "toastFormatUnsupported": "Ingen formatter til dette sprog — prøv JSON eller SQL eller redigeringstilstand", + "toastDeleted": "Snippet slettet", + "toastLoadFailed": "Kunne ikke indlæse snippets fra serveren.", + "toastSyncFailed": "Kunne ikke synkronisere med serveren.", + "languages": { + "auto": "Automatisk registrering", + "plaintext": "Almindelig tekst", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL-klient", + "dialogTitle": "SQL-forbindelser", + "mobileSheetTitle": "SQL-stifinder", + "newTabLabel": "Ny forespørgselsfane", + "vaultLockedTitle": "Boks låst", + "vaultLockedDesc": "Lås boksen op for at få adgang til gemte forbindelser.", + "heroTitle": "SQL-klient", + "heroDesc": "Opret forbindelse til PostgreSQL, MySQL eller MariaDB. Legitimationsoplysninger krypteres i browseren.", + "heroAddConnection": "Tilføj forbindelse", + "selectTableTitle": "Vælg en tabel eller åbn en forespørgselsfane", + "selectTableDesc": "Klik på en tabel i sidebjælken eller brug ⌘↩.", + "toastLoadFail": "Indlæsning af forbindelser mislykkedes." + }, + "connection": { + "titleNew": "Ny SQL-forbindelse", + "titleEdit": "Rediger forbindelse", + "labelDbType": "Databasetype", + "labelName": "Forbindelsesnavn", + "placeholderName": "Min produktionsdatabase", + "labelHostPort": "Vært og port", + "labelDatabase": "Database", + "placeholderDatabase": "mydb", + "labelUsername": "Brugernavn", + "labelPassword": "Adgangskode", + "labelSsl": "Aktivér SSL / TLS", + "sslDesc": "Kryptér forbindelsen til serveren", + "securityNote": "Legitimationsoplysninger krypteres AES-256 i browseren inden lagring", + "btnTest": "Test forbindelse", + "btnSave": "Gem og opret forbindelse", + "btnUpdate": "Opdater", + "btnCancelEdit": "Annuller redigering", + "savedConnections": "Gemte forbindelser", + "emptySaved": "Gemte forbindelser vises her", + "confirmDelete": "Slet denne forbindelse?", + "toastFillFields": "Udfyld vært og database inden test.", + "toastConnectOk": "Forbundet! {version}", + "toastConnectFail": "Forbindelse mislykkedes: {message}", + "toastUnlockVault": "Lås boksen op først.", + "toastFieldsRequired": "Vært og database er påkrævet.", + "toastUpdated": "Forbindelse opdateret.", + "toastSaved": "Forbindelse gemt.", + "toastSaveFail": "Lagring mislykkedes: {message}", + "toastDeleted": "Forbindelse slettet.", + "toastDeleteFail": "Sletning af forbindelse mislykkedes." + }, + "sidebar": { + "title": "SQL-stifinder", + "tooltipAddConnection": "Tilføj forbindelse", + "tooltipRefreshSchema": "Opdater skema", + "noConnections": "Ingen gemte forbindelser endnu", + "toastSchemaFail": "Indlæsning af skema mislykkedes: {message}" + }, + "editor": { + "btnRun": "Kør", + "executing": "Udfører forespørgsel…", + "emptyTitle": "Kør en forespørgsel for at se resultater", + "emptyHint": "Tryk ⌘↩ eller klik Kør", + "toastNoConnection": "Ingen aktiv forbindelse." + }, + "results": { + "filterPlaceholder": "Filtrer resultater…", + "rowCount": "{count} {count, plural, one {række} other {rækker}}", + "rowCountFiltered": "{filtered} / {total} rækker", + "exportCsv": "CSV", + "noRows": "Ingen rækker returneret" + } + }, + "QrCodeGenerator": { + "title": "QR-kodegenerator", + "subtitle": "Gør enhver tekst eller URL til en PNG QR-kode. Alt gengives lokalt i browseren.", + "contentLabel": "Indhold", + "contentPlaceholder": "https://example.com, almindelig tekst, Wi‑Fi-streng, vCard…", + "styleLabel": "Stil", + "dotTypeLabel": "Punktform", + "dotTypeSquare": "Kvadrat", + "dotTypeDots": "Prikker", + "dotTypeRounded": "Afrundet", + "dotTypeExtraRounded": "Ekstra afrundet", + "dotTypeClassy": "Elegant", + "dotTypeClassyRounded": "Elegant afrundet", + "cornerSquareLabel": "Hjørnekvadrat", + "cornerSquareDot": "Prik", + "cornerSquareSquare": "Kvadrat", + "cornerSquareExtraRounded": "Ekstra afrundet", + "cornerDotLabel": "Hjørneprikker", + "cornerDotDot": "Prik", + "cornerDotSquare": "Kvadrat", + "eccLabel": "Fejlretning", + "eccL": "Lav (~7%)", + "eccM": "Mellem (~15%)", + "eccQ": "Kvartil (~25%)", + "eccH": "Høj (~30%)", + "marginLabel": "Margin (moduler)", + "widthLabel": "Billedbredde", + "widthEffective": "Gengives i {px} px", + "darkColor": "Mørke moduler", + "lightColor": "Lyse moduler", + "resetColors": "Nulstil farver", + "logoLabel": "Midterlogo", + "logoMarginLabel": "Margen", + "uploadLogo": "Upload billede", + "removeLogo": "Fjern logo", + "downloadPng": "Download PNG", + "downloadSvg": "Download SVG", + "copyImage": "Kopiér billede", + "copied": "Kopieret", + "clear": "Ryd", + "previewAlt": "Forhåndsvisning af QR-kode", + "emptyHint": "Skriv eller indsæt indhold for at se QR-koden.", + "filename": "qr-kode.png", + "errors": { + "generationFailed": "Kunne ikke generere QR-koden. Prøv kortere tekst eller lavere fejlretning.", + "copyUnsupported": "Browseren understøtter ikke kopiering af billeder til udklipsholder.", + "copyFailed": "Kunne ikke kopiere til udklipsholder." + } + }, + "IpSubnetCalculator": { + "title": "IP-/subnetberegner", + "subtitle": "IPv4- og IPv6-CIDR i browseren—ingen eksterne kald.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Netværk", + "placeholderIpv4": "10.0.0.0/24 eller 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 eller 2001:db8::1/64", + "hintIpv4": "Brug CIDR (fx /24) eller IPv4-adresse + punktum-netmaske.", + "hintIpv6": "Angiv præfikslængde (fx /64). Uden angivelse antages /128.", + "copyValue": "Kopiér værdi", + "rows": { + "cidr": "CIDR", + "network": "Netværksadresse", + "netmask": "Subnetmaske", + "wildcard": "Wildcard-maske", + "broadcast": "Broadcast", + "firstHost": "Første vært", + "lastHost": "Sidste vært", + "totalAddresses": "Adresser i alt", + "usableHosts": "Brugbare værter", + "prefixLength": "Præfiks (bit)", + "networkIpv6": "Netværksadresse", + "lastIpv6": "Sidste adresse i interval", + "totalAddressesIpv6": "Adresser i subnet" + }, + "usableNote": { + "pointToPoint": "/31 punkt-til-punkt bruger begge adresser som værter (RFC 3021).", + "singleHost": "/32 er én værtsadresse." + }, + "ipv6RangeHint": { + "normal": "Viser det fulde matematiske interval; routerregler kan variere.", + "single": "/128 er én adresse.", + "full": "/0 dækker hele IPv6." + }, + "errors": { + "invalidIpv4": "Ugyldigt IPv4-netværk. Prøv CIDR eller adresse + maske.", + "invalidIpv6": "Ugyldigt IPv6-netværk. Brug adresse/præfiks, fx 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Hash-generator", + "subtitle": "MD5-, SHA-1- og SHA-2-summer for tekst eller filer; bcrypt til adgangskoder (tekstinput). Alt kører lokalt i din browser.", + "algorithmLabel": "Algoritme", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Tekst", + "modeFile": "Fil", + "inputLabelText": "Input", + "placeholderText": "Skriv eller indsæt tekst…", + "chooseFile": "Vælg fil", + "clearFile": "Ryd", + "fileHint": "Filer læses kun i hukommelsen; maks. {maxMb} MB. Intet uploades.", + "outputLabel": "Digest (hex)", + "copyHash": "Kopiér digest", + "copied": "Kopieret", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# tegn} other {# tegn}}", + "emptyHint": "Indtast tekst eller vælg en fil.", + "securityNote": "MD5 og SHA-1 anbefales ikke til nye sikkerhedsformål; brug SHA-256 eller SHA-512.", + "errors": { + "fileTooLarge": "Filen er større end {maxMb} MB.", + "fileReadFailed": "Kunne ikke læse filen." + }, + "bcryptRoundsLabel": "Omkostning (runder)", + "bcryptRoundsHint": "Højere værdier er langsommere og mere modstandsdygtige. Almindelig standard er 10.", + "fileTabDisabledTitle": "bcrypt hasher kun adgangskoder fra tekst, ikke vilkårlige filer.", + "placeholderBcrypt": "Skriv eller indsæt en adgangskode…", + "outputLabelBcrypt": "bcrypt-hash", + "copyBcrypt": "Kopiér hash", + "emptyHintBcrypt": "Indtast en adgangskode for at beregne bcrypt-hash.", + "securityNoteBcrypt": "Hver bcrypt-output indeholder et tilfældigt salt; samme adgangskode giver en ny streng hver gang. Det er forventet. Brug høj omkostning i produktion.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Krypteringslegeplads", + "subtitle": "AES-GCM i browseren: kryptér til en JSON-pakke med adgangssætning eller rå nøgle og dekryptér lokalt. Pædagogisk og nogle gange praktisk — intet uploades.", + "tabEncrypt": "Kryptér", + "tabDecrypt": "Dekryptér", + "keyModeLabel": "Nøglemateriale", + "modePassphrase": "Adgangssætning (PBKDF2 + AES-GCM)", + "modeRawKey": "Rå AES-nøgle (hex eller Base64)", + "aesBitsLabel": "AES-nøglelængde", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Kodning af rå nøgle", + "encodingAuto": "Auto (hex hvis det ligner hex, ellers Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Rå nøgle", + "rawKeyPlaceholder": "32-byte nøgle som 64 hex-tegn eller Base64 til AES-256 (længden afhænger af AES-størrelsen ovenfor).", + "rawKeyPlaceholderDecrypt": "Kun nødvendigt hvis pakken har keyDerivation \"none\".", + "passphraseLabel": "Adgangssætning", + "passphrasePlaceholder": "Adgangssætning bruges kun i browseren til at udlede nøglen.", + "passphrasePlaceholderDecrypt": "Kun nødvendigt hvis pakken bruger PBKDF2-SHA256.", + "pbkdf2IterLabel": "PBKDF2-iterationer", + "pbkdf2Hint": "Mellem 10.000 og 2.000.000. Standard matcher typiske vault-indstillinger.", + "plaintextLabel": "Klartekst", + "plaintextPlaceholder": "Skriv eller indsæt besked, der skal krypteres…", + "bundleInputLabel": "Krypteret pakke (JSON)", + "bundlePlaceholder": "Indsæt JSON fra Kryptér (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Krypteret pakke", + "plaintextOutputLabel": "Klartekst", + "encryptButton": "Kryptér", + "decryptButton": "Dekryptér", + "copyBundle": "Kopiér JSON", + "copyPlaintext": "Kopiér klartekst", + "copied": "Kopieret", + "emptyBundleHint": "Krypteret JSON vises her efter kryptering.", + "emptyDecryptHint": "Dekrypteret tekst vises her.", + "decryptKeyHint": "Brug rå nøgle hvis keyDerivation er \"none\"; adgangssætning ved PBKDF2-SHA256. Pakken gemmer salt, iterationer, IV og AES-længde.", + "securityNote": "Nøgler og klartekst forlader ikke din browser. Brug dedikerede værktøjer til produktionshemmeligheder.", + "errors": { + "emptyKey": "Indtast en rå nøgle, der matcher AES-størrelsen.", + "emptyPassphrase": "Indtast en adgangssætning.", + "badKeyMaterial": "Nøglen kunne ikke læses som hex eller Base64.", + "wrongKeyLength": "Nøglelængden matcher ikke AES (16 / 24 / 32 bytes).", + "invalidJson": "Ugyldig JSON.", + "invalidBundle": "Denne JSON er ikke en understøttet krypteringspakke.", + "decryptFailed": "Dekryptering mislykkedes — forkert hemmelighed, beskadiget pakke eller forkerte parametre.", + "encryptFailed": "Kryptering mislykkedes. Tjek Web Crypto og input." + } + }, + "HmacGenerator": { + "title": "HMAC-generator", + "subtitle": "Til underskrift af webhooks og test af API-integrationer. HMAC-SHA1/256/384/512 i hex eller Base64—beregnet lokalt i din browser.", + "secretLabel": "Hemmelig nøgle", + "messageLabel": "Besked / payload", + "placeholderSecret": "Indsæt din underskriftshemmelighed…", + "placeholderMessage": "Indsæt den rå krop eller streng, der skal godkendes (ofte de præcise webhook-bytes).", + "algorithmLabel": "HMAC-algoritme", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Outputformat", + "formatHex": "Hex (små bogstaver)", + "formatBase64": "Base64", + "outputLabel": "Signatur", + "copyOutput": "Kopiér signatur", + "copied": "Kopieret", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8)", + "emptyHint": "Indtast en hemmelig nøgle for at beregne HMAC.", + "securityNote": "Del aldrig din underskriftshemmelighed. Denne side uploader ikke data—alt kører i din browser.", + "errors": { + "cryptoFailed": "Kunne ikke beregne HMAC. Kontroller Web Crypto-understøttelse og prøv igen.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA-kodegenerator", + "subtitle": "Indsæt Base32-hemmeligheden og få den aktuelle sekscifrede RFC 6238-kode (SHA-1, 30 s). Kører lokalt i browseren—nyttig til test af login eller MFA.", + "secretLabel": "Hemmelighed (Base32)", + "placeholderSecret": "Indsæt den hemmelige nøgle (bogstaver A–Z og cifre 2–7, som ved opsætning af autentifikatoren)…", + "codeLabel": "Aktuel kode", + "copyCode": "Kopiér kode", + "copied": "Kopieret", + "nextIn": "Opdateres om {seconds} s", + "standardHint": "30 s tidsinterval, 6 cifre, HMAC-SHA-1 (kompatibel med Google Authenticator).", + "digitsLabel": "Cifre", + "periodLabel": "Periode", + "algorithmLabel": "Algoritme", + "periodSeconds": "{seconds} s", + "emptyHint": "Indsæt en hemmelighed for at generere en sekscifret kode.", + "invalidHint": "Ret hemmeligheden for at se koden.", + "securityNote": "Behandl det som en adgangskode. Hemmeligheden forlader ikke browseren; intet uploades.", + "importLabel": "Importer fra otpauth://-URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importer", + "qrSectionTitle": "QR-kode til provisionering", + "qrHint": "Scan med en autentificerings-app (Google Authenticator, 1Password, Aegis…) for at tilføje denne hemmelighed. QR-koden indeholder hemmeligheden — behandl den som en adgangskode.", + "accountLabel": "Konto", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Udsteder (valgfri)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Indtast en gyldig hemmelighed for at generere en QR-kode.", + "errors": { + "invalidSecret": "Ugyldig Base32-hemmelighed.", + "cryptoFailed": "Kunne ikke beregne TOTP. Tjek Web Crypto og prøv igen.", + "invalidUri": "Dette er ikke en gyldig otpauth:// TOTP-URI.", + "unsupportedUri": "Denne URI bruger værdier, som værktøjet ikke understøtter (cifre skal være 6 eller 8; periode 15, 30 eller 60 s)." + } + }, + "MarkdownPreview": { + "title": "Markdown-forhåndsvisning & HTML", + "subtitle": "Live Markdown-renderer med HTML-eksport og HTML → Markdown-konvertering. Alt kører i din browser.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown-input", + "htmlInputLabel": "HTML-input", + "previewLabel": "Forhåndsvisning", + "htmlOutputLabel": "HTML-kilde", + "mdOutputLabel": "Markdown-output", + "inputPlaceholder": "# Hej\\n\\nSkriv din **markdown** her…", + "htmlInputPlaceholder": "Indsæt din HTML her…", + "copyHtml": "Kopiér HTML", + "copyMarkdown": "Kopiér Markdown", + "exportHtml": "Eksportér HTML", + "copied": "Kopieret", + "charCount": "{count, plural, one {# tegn} other {# tegn}}", + "emptyHint": "Begynd at skrive Markdown for at se live-forhåndsvisning og HTML-output.", + "emptyHintHtmlToMd": "Indsæt HTML for at konvertere det til Markdown.", + "securityNote": "Alt kører lokalt i din browser. Intet uploades." + }, + "NumberBaseConverter": { + "title": "Talsystem-konverter", + "subtitle": "Konverter heltal mellem baser 2–36. Valgfri 0x / 0b / 0o-præfiks. Alt kører lokalt i din browser.", + "inputBaseLabel": "Fra base", + "outputBaseLabel": "Til base", + "inputLabel": "Inputtal", + "placeholderInput": "Indtast et heltal i den valgte input-base…", + "outputLabel": "Konverteret værdi", + "copyOutput": "Kopiér resultat", + "copied": "Kopieret", + "swap": "Byt baser", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8-længde)", + "emptyHint": "Indtast et tal for at konvertere.", + "note": "Cifre over 9 bruger bogstaver A–Z (ikke versalfølsomt). Mellemrum og understregning ignoreres.", + "errors": { + "invalidNumber": "Værdien er ikke gyldig for den valgte input-base." + } + }, + "FormatConverter": { + "title": "Formatkonverter", + "subtitle": "Konverter mellem JSON, YAML, TOML og XML i enhver retning. Alle 12 kombinationer. Alt kører i din browser.", + "fromLabel": "Fra", + "toLabel": "Til", + "swap": "Skift", + "swapTitle": "Skift formater og brug output som input", + "copy": "Kopiér", + "copied": "Kopieret", + "charCount": "{count, plural, one {# tegn} other {# tegn}}", + "emptyHint": "Indsæt eller skriv dit input til venstre for at se det konverterede output.", + "localNote": "Alt kører lokalt i din browser. Intet uploades." + }, + "MimeTypeLookup": { + "title": "MIME-type opslag", + "subtitle": "Slå MIME-type (medietype) op for en filendelse eller et fuldt filnavn. Alt kører lokalt i din browser.", + "inputLabel": "Filendelse eller filnavn", + "placeholder": "fx .png, package.json, photo.jpeg", + "hint": "Tip: indsæt enten en endelse (med eller uden punktum) eller et fuldt filnavn.", + "clear": "Ryd", + "exampleFilename": "Brug eksempel-filnavn", + "exampleExtension": "Brug eksempel-endelse", + "resultLabel": "Resultat", + "normalizedLabel": "Normaliseret input:", + "mimeTypeLabel": "MIME-type", + "contentTypeLabel": "Content-Type-header", + "charsetLabel": "Tegnsæt", + "copyMimeType": "Kopiér MIME-type", + "copy": "Kopiér", + "copied": "Kopieret", + "emptyHint": "Indtast en endelse eller et filnavn for at slå MIME-typen op.", + "notFound": "Ingen MIME-type fundet for dette input.", + "localNote": "Alt kører lokalt i din browser. Intet uploades." + }, + "UserAgentParser": { + "title": "User-Agent-parser", + "subtitle": "Indsæt en User-Agent (UA)-streng og få en oversigt over browser, OS, engine, enhed og CPU. Alt kører lokalt i din browser.", + "inputLabel": "User-Agent-streng", + "placeholder": "Indsæt din User-Agent her…", + "hint": "Tip: du kan kopiere en UA fra request-headers, logs eller browserens devtools.", + "clear": "Ryd", + "exampleDesktop": "Brug desktop-eksempel", + "exampleMobile": "Brug mobil-eksempel", + "resultLabel": "Oversigt", + "browserLabel": "Browser", + "osLabel": "Operativsystem", + "engineLabel": "Engine", + "deviceTypeLabel": "Enhedstype", + "deviceVendorLabel": "Producent", + "deviceModelLabel": "Model", + "cpuArchLabel": "CPU-arkitektur", + "copyJson": "Kopiér JSON", + "copy": "Kopiér", + "copied": "Kopieret", + "emptyHint": "Indsæt en User-Agent-streng for at se oversigten.", + "localNote": "Alt kører lokalt i din browser. Intet uploades." + }, + "MockDataGenerator": { + "title": "Mock-datagenerator", + "subtitle": "Definér kolonner, vælg datatyper og eksporter datasæt til seeds, Postman eller tests. Op til {max} rækker, alt i browseren — intet uploades.", + "mockarooLink": "Mockaroo-lignende arbejdsgang", + "rowsLabel": "Rækker", + "rowsHint": "Højst {max} rækker pr. kørsel for at holde fanen responsiv.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "SQL INSERT", + "xml": "XML" + }, + "tableNameLabel": "SQL-tabelnavn", + "presetsLabel": "Skabeloner", + "presetPlaceholder": "Indlæs skabelon…", + "presetDefault": "Start (bruger)", + "presetUsers": "Brugere og roller", + "presetProducts": "E-handels-SKU’er", + "presetApiLog": "HTTP-anmodningslog", + "generate": "Generér", + "warningDuplicateNames": "Dublette kolonnenavne: senere kolonner overskriver tidligere i JSON-objekter. Omdøb dem.", + "schemaHeading": "Skema", + "addField": "Tilføj kolonne", + "removeField": "Fjern kolonne", + "duplicateField": "Duplikér kolonne", + "colName": "Navn", + "colType": "Type", + "colOptions": "Indstillinger", + "colBlank": "Tom %", + "optMin": "min", + "optMax": "maks", + "optDecimals": "dec", + "optDateFrom": "fra dato", + "optDateTo": "til dato", + "optValues": "a, b, c", + "optStep": "trin", + "optRowBase": "basis", + "output": "Output", + "outputPlaceholder": "Klik på Generér for at oprette data…", + "copy": "Kopiér", + "download": "Download", + "copied": "Kopieret", + "groups": { + "personal": "Personligt", + "location": "Placering", + "internetApi": "Internet og API", + "numbersIds": "Tal og ID’er", + "dateTime": "Dato og tid", + "textDesign": "Tekst og design", + "custom": "Tilpasset" + }, + "fieldTypes": { + "first_name": "Fornavn", + "last_name": "Efternavn", + "full_name": "Fuldt navn", + "email": "Email", + "username": "Brugernavn", + "password": "Adgangskode", + "phone": "Telefon", + "company": "Virksomhed", + "job_title": "Stilling", + "avatar_url": "Avatar-URL", + "city": "By", + "country": "Land", + "country_code": "Landekode (ISO)", + "street_address": "Adresse", + "zip_code": "Postnummer", + "latitude": "Breddegrad", + "longitude": "Længdegrad", + "url": "URL", + "domain": "Domæne", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Heltal", + "float": "Decimal", + "boolean": "Boolsk", + "uuid": "UUID", + "credit_card": "Testkort", + "semver": "SemVer", + "row_index": "Rækkeindeks", + "sequence": "Sekvens", + "date": "Dato", + "datetime": "Dato og klokkeslæt", + "unix_timestamp": "Unix-tidsstempel", + "timezone": "Tidszone", + "word": "Ord", + "sentence": "Sætning", + "paragraph": "Afsnit", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Valutakode", + "language_code": "Sprogkode", + "custom_list": "Brugerdefineret liste" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG-optimering", + "subtitle": "Indsæt SVG, minificér med SVGO i browseren og sammenlign UTF-8-størrelse før og efter.", + "inputLabel": "Input", + "outputLabel": "Optimeret", + "placeholder": "Indsæt et -uddrag eller hele filen…", + "outputPlaceholder": "Optimeret SVG vises her…", + "chars": "{count} tegn • {bytes} bytes (UTF-8)", + "preview": "Forhåndsvisning", + "localNote": "Al behandling sker lokalt i din browser. Intet uploades.", + "stats": { + "title": "Størrelse (UTF-8)", + "before": "Før", + "after": "Efter", + "savedBytes": "{count} bytes sparet ({percent}%)", + "noChange": "Ingen reduktion — allerede minimal eller tom output." + }, + "actions": { + "clear": "Ryd", + "sample": "Indlæs eksempel", + "copy": "Kopiér", + "copied": "Kopieret", + "download": "Download" + }, + "errors": { + "optimizeFailed": "Kunne ikke optimere denne SVG." + } + }, + "UnitConverter": { + "title": "Enhedskonverter", + "subtitle": "Konverter mellem 323 enheder på tværs af 43 kategorier", + "category": "Kategori", + "from": "Fra", + "to": "Til", + "value": "Værdi", + "result": "Resultat", + "swapUnits": "Byt enheder", + "placeholder": "Angiv værdi", + "copyResult": "Kopiér resultat", + "searchCategory": "Søg kategori...", + "selectCategory": "Vælg en kategori", + "noResults": "Ingen kategori fundet." + }, + "DockerComposeGenerator": { + "title": "Docker Compose-generator", + "subtitle": "Vælg populære databaser, caches, beskedkøer, reverse proxyer og observabilitet — kopiér en docker-compose.yml klar til lokal udvikling.", + "searchPlaceholder": "Søg (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Tjenester", + "selectedCount": "{count, plural, one {# tjeneste} other {# tjenester}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopiér", + "copied": "Kopieret!", + "download": "Download", + "emptyState": "Vælg én eller flere tjenester for at generere Compose-YAML.", + "hintPorts": "Standardværtsporte minimerer konflikter; justér hvis Docker melder port i brug. Udskift demo-adgangskoder før delte miljøer.", + "categories": { + "databases": "Databaser og lagring", + "cache": "Cache og in-memory", + "queues": "Køer og messaging", + "search": "Søgning og logs", + "storage": "Objektlager", + "proxy": "Reverse proxy og ingress", + "observability": "Metrics og tracing", + "mail": "E-mail (lokal udvikling)", + "admin": "Admin- og BI-grænseflader" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Ryd" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (Redis-kompatibel)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (Kafka-kompatibel)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (S3-kompatibel)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (SMTP-optagelse)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (dev-server)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Skub fliserne, og flet dem sammen for at nå 2048", + "score": "Score", + "best": "Bedst", + "newGame": "Nyt spil", + "howToPlay": "Brug piletasterne (eller stryg på mobilen) til at flytte fliser. Fliser med samme nummer flettes. Nå 2048 for at vinde!", + "youWin": "Du har nået 2048!", + "keepGoing": "\"Fortsæt.\"", + "gameOver": "Spil forbi", + "tryAgain": "Prøv igen", + "gameOverMessage": "Ikke flere bevægelser tilbage.", + "undo": "Fortryd" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "faser gennemført", + "time": "Tidspunkt", + "best": "Bedst", + "difficulty": { + "easy": "Nemt", + "medium": "Mellemstor", + "hard": "Svær" + }, + "stage": "Näyttämö", + "solved": "Besvaret", + "score": "Score", + "nextStage": "De næste skridt", + "replay": "Besvar", + "erase": "Slet", + "howToPlay": "Klik på en celle, og tryk derefter på et tal, eller tryk på 1–9. Backspace eller Delete rydder cellen. Dristige tal er ledetråde. Udfyld gitteret, så hver række, kolonne og 3×3 boks indeholder 1–9 uden gentagelser.", + "pencil": "Blyant", + "undo": "Fortryd", + "hint": "Tip", + "hintsUsed": "tips brugt", + "mistakes": "Fejl", + "failed": "Mislykkedes!", + "mistakeMode": "3-fejl tilstand" + }, + "Snake": { + "title": "Snake", + "subtitle": "Spis mad, voks, undgå vægge og dig selv", + "score": "Point", + "best": "Bedste", + "startPrompt": "Tryk en piletast", + "startButton": "Start spil", + "gameOver": "Spil slut", + "playAgain": "Spil igen", + "howToPlay": "Brug piletaster eller WASD. Spis mad for at vokse. Hastighed stiger hvert 50. point.", + "diffEasy": "Let", + "diffNormal": "Normal", + "diffHard": "Svær", + "diffInfinite": "Uendelig", + "orTap": "eller tryk nedenfor", + "howToPlayInfinite": "Vægge wrap rundt — ingen vægdød. Spis mad for at vokse.", + "shieldActive": "Skjold aktivt", + "speedBoost": "Hastighedsforøgelse", + "powerSpeed": "Hastighed x2", + "powerShield": "Overlev én død", + "powerShrink": "Forkorter halen" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis-forbindelser", + "heroTitle": "Redis Commander", + "heroDesc": "Opret forbindelse til Redis-instanser. Gennemse nøgler, rediger værdier og kør kommandoer. Legitimationsoplysninger krypteres i din browser.", + "heroAddConnection": "Tilføj forbindelse", + "vaultLockedTitle": "Boks låst", + "vaultLockedDesc": "Lås boksen op for at få adgang til gemte forbindelser." + } + }, + "Minesweeper": { + "title": "Minestryger", + "subtitle": "Find alle sikre felter uden at ramme en mine", + "mines": "Miner", + "newGame": "Nyt spil", + "win": "Du vandt!", + "loss": "Spillet slut!", + "playAgain": "Spil igen", + "tryAgain": "Prøv igen", + "startPrompt": "Klik på et felt for at starte", + "difficulty": { + "beginner": "Begynder", + "intermediate": "Øvet", + "expert": "Ekspert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Arranger de faldende blokke for at fuldføre rækker", + "next": "Næste", + "score": "Score", + "best": "Bedst", + "lines": "Linjer", + "level": "Niveau", + "pause": "Pause", + "resume": "Genoptag", + "start": "Start", + "restart": "Genstart", + "paused": "Pauset", + "gameOver": "Spillet er slut" + }, + "SshKeyGenerator": { + "title": "SSH / RSA nøglegenerator", + "subtitle": "Generer Ed25519 eller RSA nøglepar i din browser. Download private og offentlige nøgler i OpenSSH og PEM-formater. Intet uploades.", + "keyTypeLabel": "Nøgletype", + "recommended": "anbefalet", + "ed25519Desc": "Hurtig, lille og moderne. Understøttet af GitHub, GitLab og alle større SSH-servere.", + "rsa2048Desc": "Bredt kompatibel. Sikker til de fleste anvendelser.", + "rsa4096Desc": "Maksimal kompatibilitet og styrke til ældre systemer.", + "commentLabel": "Kommentar (valgfri)", + "commentHint": "Tilføjes til OpenSSH-nøglelinjen.", + "generateBtn": "Generer nøglepar", + "generating": "Genererer…", + "generatingLong": "Genererer (kan tage et øjeblik)…", + "copy": "Kopiér", + "download": "Download", + "privateKeyLabel": "Privat nøgle (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Offentlig nøgle (OpenSSH-format)", + "publicKeySpkiLabel": "Offentlig nøgle (SPKI PEM)", + "browserNote": "Alle nøgler genereres i din browser. Intet sendes til nogen server.", + "errors": { + "generationFailed": "Nøglegenerering mislykkedes" + } + }, + "DnsLookup": { + "title": "DNS-opslag", + "subtitle": "Forespørg DNS-poster for ethvert domæne — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Indtast domæne (f.eks. github.com)", + "lookupBtn": "Slå op", + "looking": "Slår op…", + "refreshBtn": "Opdater", + "recordTypesLabel": "Posttyper", + "allBtn": "Alle", + "tryLabel": "Prøv:", + "errors": { + "lookupFailed": "DNS-opslag mislykkedes" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt-generator og -kontrol", + "subtitle": "Hash adgangskoder med bcrypt, og verificér hashes — alt bliver i din browser", + "tabs": { + "hash": "Hash", + "verify": "Verificér" + }, + "showPassword": "Vis adgangskode", + "hidePassword": "Skjul adgangskode", + "copy": "Kopiér", + "copied": "Kopieret", + "hash": { + "passwordLabel": "Adgangskode", + "passwordPlaceholder": "Indtast en adgangskode, der skal hashes…", + "costLabel": "Omkostningsfaktor", + "costHint": "Højere omkostning fordobler arbejdet pr. trin. 10-12 er et almindeligt valg til produktion.", + "slowWarning": "Omkostningsfaktorer på 13 og derover kan tage flere sekunder at beregne.", + "generate": "Generér hash", + "generating": "Hasher…", + "resultLabel": "Bcrypt-hash", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Hash-anatomi", + "algorithm": "algoritme", + "cost": "omkostning", + "salt": "salt (22 tegn)", + "hash": "hash (31 tegn)" + } + }, + "verify": { + "passwordLabel": "Adgangskode", + "passwordPlaceholder": "Indtast adgangskoden, der skal kontrolleres…", + "hashLabel": "Bcrypt-hash", + "hashPlaceholder": "$2b$10$…", + "idle": "Indtast en adgangskode og en bcrypt-hash for at sammenligne dem.", + "invalidHash": "Dette ligner ikke en gyldig bcrypt-hash.", + "checking": "Sammenligner…", + "match": "Match — adgangskoden producerer denne hash.", + "noMatch": "Intet match — adgangskoden producerer ikke denne hash." + } + }, + "ChmodCalculator": { + "title": "Chmod-beregner", + "subtitle": "Konvertér Unix-filtilladelser mellem afkrydsningsfelter, oktal og symbolsk notation", + "grid": { + "title": "Tilladelser", + "read": "Læs", + "write": "Skriv", + "execute": "Kør", + "owner": "Ejer", + "group": "Gruppe", + "others": "Andre" + }, + "special": { + "title": "Specielle bits", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Oktal", + "placeholder": "755 eller 4755", + "invalid": "Indtast 3 eller 4 oktale cifre (0-7)." + }, + "symbolic": { + "label": "Symbolsk", + "placeholder": "rwxr-xr-x", + "invalid": "Indtast 9 tegn som rwxr-xr-x (s/S, t/T tilladt)." + }, + "output": { + "title": "Kommando", + "copy": "Kopiér", + "copied": "Kopieret" + }, + "explain": { + "line": "{cls} kan {perms}.", + "none": "{cls} har ingen tilladelser.", + "setuid": "Setuid: filen kører med ejerens rettigheder.", + "setgid": "Setgid: filen kører med gruppens rettigheder (nye filer i en mappe arver dens gruppe).", + "sticky": "Sticky: kun en fils ejer kan slette eller omdøbe den i denne mappe." + }, + "presets": { + "title": "Almindelige forudindstillinger", + "octal": "Oktal", + "permissions": "Tilladelser", + "description": "Typisk brug", + "p644": "Almindelige filer: ejeren skriver, alle læser.", + "p755": "Eksekverbare filer og mapper: alle kan læse og gå ind.", + "p600": "Private filer: kun ejeren læser og skriver.", + "p700": "Private eksekverbare filer og mapper: kun ejeren.", + "p664": "Gruppe-skrivbare filer.", + "p775": "Gruppe-skrivbare mapper og eksekverbare filer.", + "p400": "Skrivebeskyttet for ejeren (f.eks. private SSH-nøgler)." + } + }, + "CodeScreenshot": { + "title": "Kodeskærmbillede", + "subtitle": "Forvandl kode til smukke, delbare billeder", + "languageLabel": "Sprog", + "autoDetect": "Automatisk registrering", + "backgroundLabel": "Baggrund", + "paddingLabel": "Udfyldning", + "filenameLabel": "Filnavn", + "filenamePlaceholder": "example.ts", + "windowLabel": "Vinduesprikker", + "themeLabel": "Vinduestema", + "themeDark": "Mørkt", + "themeLight": "Lyst", + "scaleLabel": "Eksportskala", + "copyImage": "Kopiér billede", + "copied": "Kopieret", + "exportPng": "Eksportér PNG", + "inputLabel": "Kode", + "detected": "Registreret: {language}", + "clear": "Ryd", + "placeholder": "Indsæt din kode her…", + "previewLabel": "Forhåndsvisning", + "emptyPreview": "Forhåndsvisningen af din kode vises her", + "exportFailed": "Kunne ikke eksportere billede", + "copyUnsupported": "Kopiering af billeder understøttes ikke i denne browser", + "copyFailed": "Kunne ikke kopiere billede", + "backgrounds": { + "ocean": "Ocean", + "sunset": "Solnedgang", + "candy": "Slik", + "forest": "Skov", + "midnight": "Midnat", + "gold": "Guld", + "aurora": "Nordlys", + "slate": "Skifer", + "transparent": "Gennemsigtig" + } + }, + "CssGenerators": { + "title": "CSS-generatorer", + "subtitle": "Box-shadow, border-radius og flydende clamp() — kopiér CSS'en.", + "preview": "Forhåndsvisning", + "output": "CSS", + "copy": "Kopiér", + "copied": "Kopieret", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Flydende clamp()" + }, + "boxShadow": { + "presets": "Forudindstillinger", + "layer": "Lag", + "addLayer": "Tilføj lag", + "removeLayer": "Fjern lag", + "x": "X", + "y": "Y", + "blur": "Sløring", + "spread": "Spredning", + "color": "Farve", + "alpha": "Alfa", + "inset": "Indvendig", + "presetNames": { + "subtleCard": "Diskret kort", + "elevated": "Hævet", + "hardOffset": "Hård forskydning", + "neonGlow": "Neonglød", + "inner": "Indvendig", + "layeredSmooth": "Lagdelt blød" + } + }, + "borderRadius": { + "modeLabel": "Tilstand", + "modes": { + "simple": "Pr. hjørne", + "blob": "Organisk klat" + }, + "unit": "Enhed", + "linkCorners": "Sammenkæd hjørner", + "randomize": "Tilfældiggør", + "horizontal": "Vandrette radier", + "vertical": "Lodrette radier", + "corners": { + "tl": "Øverst til venstre", + "tr": "Øverst til højre", + "br": "Nederst til højre", + "bl": "Nederst til venstre" + } + }, + "clamp": { + "minFont": "Min. skrift (px)", + "maxFont": "Maks. skrift (px)", + "minViewport": "Min. viewport (px)", + "maxViewport": "Maks. viewport (px)", + "rootFont": "Rodskrift (px)", + "viewportWidth": "Simuleret viewport", + "computedSize": "Beregnet størrelse", + "slope": "Hældning", + "intercept": "Skæring", + "previewText": "Quizdeltagerne spiste jordbær", + "invalid": "Indtast gyldige, forskellige viewport-grænser." + } + }, + "ExifViewer": { + "title": "EXIF-fremviser og -fjerner", + "subtitle": "Inspicér fotos' metadata, og fjern dem i din browser", + "dropTitle": "Slip et billede her", + "dropHint": "eller klik for at gennemse — dit billede forlader aldrig din enhed", + "browse": "Vælg billede", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Læser metadata…", + "parseFailed": "Kunne ikke læse dette billede", + "clear": "Fjern billede", + "gpsWarning": "Indeholder GPS-placering", + "openMap": "Vis på OpenStreetMap", + "noMetadata": "Der blev ikke fundet nogen indlejrede metadata i dette billede.", + "facts": { + "name": "Navn", + "type": "Type", + "dimensions": "Dimensioner", + "size": "Størrelse" + }, + "groups": { + "camera": "Kamera", + "exposure": "Eksponering", + "timestamps": "Tidsstempler", + "gps": "GPS-placering", + "software": "Software og andet" + }, + "gpsCard": { + "latitude": "Breddegrad", + "longitude": "Længdegrad", + "altitude": "Højde" + }, + "raw": { + "title": "Alle metadata", + "copyJson": "Kopiér JSON", + "copied": "Kopieret" + }, + "remove": { + "title": "Fjern metadata", + "jpegDescription": "Fjern EXIF, XMP, IPTC og kommentarer tabsfrit uden at genkomprimere billedet.", + "reencodeDescription": "Genkod billedet for at fjerne indlejrede metadata. Dette genkomprimerer pixels.", + "keepIcc": "Behold farveprofil (ICC)", + "stripButton": "Fjern metadata", + "reencodeButton": "Genkod og fjern", + "unsupported": "Fjernelse af metadata understøttes ikke for denne filtype.", + "failed": "Kunne ikke fjerne metadata", + "before": "Før", + "after": "Efter", + "download": "Download renset billede" + } + }, + "FaviconGenerator": { + "title": "Favicon-generator", + "subtitle": "Opret favicons og app-ikoner ud fra ét billede", + "dropTitle": "Slip et billede her", + "dropHint": "PNG, JPEG eller SVG — klik for at gennemse", + "browse": "Vælg billede", + "clear": "Fjern billede", + "backgroundLabel": "Baggrund", + "transparent": "Gennemsigtig", + "solidColor": "Ensfarvet", + "cornerLabel": "Hjørneform", + "cornerNone": "Firkantet", + "cornerRounded": "Afrundet", + "cornerCircle": "Cirkel", + "maskPaddingLabel": "Maskerbar udfyldning", + "maskable": "maskable", + "previewLabel": "Forhåndsvisning", + "copy": "Kopiér", + "headSnippet": "HTML head-uddrag", + "downloadIco": "Download favicon.ico", + "downloadZip": "Download alle (.zip)" + }, + "JsonDiff": { + "title": "JSON-diff", + "subtitle": "Semantisk, strukturbevidst sammenligning af to JSON-dokumenter — nøglerækkefølge og formatering ignoreres", + "options": { + "ignoreArrayOrder": "Ignorér array-rækkefølge" + }, + "loadBothSamples": "Indlæs eksempler", + "sample": "Eksempel", + "format": "Format", + "clear": "Ryd", + "formatError": "Kan ikke formatere: input er ikke gyldig JSON", + "placeholder": "Indsæt eller skriv JSON her…", + "panels": { + "left": "Original (venstre)", + "right": "Ændret (højre)", + "results": "Forskelle" + }, + "summary": { + "added": "{count} tilføjet", + "removed": "{count} fjernet", + "changed": "{count} ændret" + }, + "types": { + "added": "Tilføjet", + "removed": "Fjernet", + "changed": "Ændret" + }, + "copyChanges": "Kopiér som JSON", + "copied": "Kopieret", + "empty": "Indsæt JSON i begge paneler for at se deres forskelle.", + "identical": "De to dokumenter er semantisk identiske." + }, + "JsonpathPlayground": { + "title": "JSONPath-legeplads", + "subtitle": "Forespørg JSON-dokumenter med JSONPath eller JMESPath, og se resultater live", + "engineLabel": "Motor", + "queryLabel": "Forespørgsel", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Snydeark", + "cheats": { + "jpAllTitles": "Alle bogtitler", + "jpAllAuthors": "Hver forfatter, hvor som helst i dokumentet", + "jpFirstBook": "Første bog", + "jpLastBook": "Sidste bog", + "jpSlice": "De første to bøger (udsnit)", + "jpFilterPrice": "Bøger billigere end 40", + "jpFilterInStock": "Titler på bøger på lager", + "jpRecursive": "Alle værdier, rekursivt", + "jmAllTitles": "Alle bogtitler", + "jmFirstAuthor": "Forfatter af den første bog", + "jmLastBook": "Sidste bog", + "jmSlice": "De første to bøger (udsnit)", + "jmFilterPrice": "Titler på bøger billigere end 40", + "jmCount": "Antal bøger på lager", + "jmProjection": "Omform hver bog til et nyt objekt", + "jmSort": "Titler sorteret efter pris" + }, + "panels": { + "input": "JSON-input", + "results": "Resultater" + }, + "sample": "Eksempel", + "format": "Format", + "clear": "Ryd", + "formatError": "Kan ikke formatere: input er ikke gyldig JSON", + "placeholder": "Indsæt eller skriv JSON her…", + "outputPlaceholder": "Match vises her…", + "matchCount": "{count, plural, one {# match} other {# match}}", + "copy": "Kopiér", + "copied": "Kopieret" + }, + "KeycodeInspector": { + "title": "Tastekode-inspektør", + "subtitle": "Tryk på en vilkårlig tast for at inspicere dens JavaScript-tastaturhændelse", + "capture": { + "prompt": "Tryk på en vilkårlig tast…", + "ready": "Lytter — tryk på en vilkårlig tast på dit tastatur", + "clickToFocus": "Klik her for at fokusere, og tryk derefter på en vilkårlig tast" + }, + "hero": { + "spaceKey": "(Mellemrum)", + "keyLabel": "event.key", + "repeat": "gentagelse" + }, + "copyJson": "Kopiér JSON", + "copiedJson": "Hændelses-JSON kopieret til udklipsholder", + "deprecated": "forældet", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standard", + "left": "Venstre", + "right": "Højre", + "numpad": "Numerisk tastatur", + "unknown": "Ukendt" + }, + "history": { + "title": "Historik", + "clear": "Ryd historik", + "empty": "Tastetryk vises her." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown-tabelgenerator", + "subtitle": "Rediger et gitter, indsæt fra et regneark, og kopiér en pænt justeret Markdown- eller HTML-tabel", + "addRow": "Tilføj række", + "addColumn": "Tilføj kolonne", + "importLabel": "Importér", + "importPlaceholder": "Indsæt TSV (fra Excel / Google Sheets) eller CSV her…", + "importButton": "Importér til gitter", + "pasteClipboard": "Indsæt fra udklipsholder", + "importEmpty": "Intet at importere — indsæt nogle rækker først", + "clipboardError": "Kunne ikke læse fra udklipsholderen", + "clear": "Ryd", + "panels": { + "editor": "Tabeleditor", + "output": "Output" + }, + "align": { + "left": "Justér venstre", + "center": "Centrér", + "right": "Justér højre" + }, + "headerPlaceholder": "Overskrift", + "cellPlaceholder": "Celle", + "removeRow": "Fjern række", + "removeColumn": "Fjern kolonne", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopiér", + "copied": "Kopieret" + }, + "TimezoneConverter": { + "title": "Tidszonekonverter", + "subtitle": "Konvertér ét øjeblik på tværs af verden til mødeplanlægning.", + "dateTimeLabel": "Dato og tid", + "now": "Nu", + "sourceZoneLabel": "Kilde-tidszone", + "selectZone": "Vælg zone", + "searchZone": "Søg tidszoner...", + "noZone": "Ingen tidszone fundet.", + "scrubLabel": "Forskyd tid", + "reset": "Nulstil", + "targetsLabel": "Målzoner", + "addZone": "Tilføj zone", + "remove": "Fjern", + "localBadge": "Lokal", + "invalidDate": "Indtast en gyldig dato og tid.", + "bands": { + "work": "Arbejdstid (09:00–18:00)", + "edge": "Randtimer (tidligt / aften)", + "night": "Nat" + } + }, + "TokenCounter": { + "title": "LLM-token-tæller", + "subtitle": "Tæl tokens, visualisér chunks, og estimér API-omkostninger.", + "inputLabel": "Tekst", + "sample": "Eksempel", + "clear": "Ryd", + "placeholder": "Indsæt tekst, kode eller JSON for at tælle tokens...", + "encoding": "Kodning", + "loading": "Indlæser tokenizer...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-serien)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Tegn", + "words": "Ord", + "ratio": "Tokens/ord", + "ratioSub": "cirka" + }, + "claudeRow": "Claude (omtrentlig)", + "estimate": "estimat", + "viz": { + "title": "Token-visualisering", + "toggle": "Vis", + "hint": "Slå til for at farve hvert token som en chunk.", + "empty": "Indtast tekst for at visualisere tokens.", + "truncated": "Viser de første {limit} af {total} tokens." + }, + "cost": { + "title": "Omkostningsberegner", + "disclaimer": "redigerbare standardværdier — verificér aktuelle priser", + "model": "Model", + "expectedOutput": "Forventede output-tokens", + "customInput": "Input $/MTok", + "customOutput": "Output $/MTok", + "inputCost": "Input-omkostning", + "outputCost": "Output-omkostning", + "total": "I alt", + "per1000": "Pr. 1000 kald" + } + }, + "WebhookTester": { + "title": "Webhook-tester", + "subtitle": "Opret en midlertidig URL, og inspicér indgående webhook-anmodninger live.", + "intro": { + "title": "Opret en webhook-URL for at begynde at opfange anmodninger", + "hint": "Du får en ugættelig URL, der registrerer hver indgående anmodning. Den udløber efter 24 timer." + }, + "createBtn": "Opret webhook-URL", + "creating": "Opretter…", + "urlLabel": "Din webhook-URL", + "expiresIn": "Udløber om {countdown}", + "copyUrlBtn": "Kopiér URL", + "copied": "Kopieret", + "clearBtn": "Ryd", + "newBinBtn": "Ny URL", + "requestsTitle": "Anmodninger", + "selectPrompt": "Vælg en anmodning for at inspicere dens detaljer.", + "expiredNotice": "Denne webhook-URL er udløbet. Opret en ny for at fortsætte med at teste.", + "base64Badge": "base64", + "truncatedBadge": "afkortet", + "empty": { + "title": "Venter på anmodninger…", + "hint": "Send en anmodning til din URL, så vises den her med det samme. Prøv dette:" + }, + "detail": { + "receivedAt": "Modtaget", + "source": "Kilde-IP", + "contentType": "Content-Type", + "query": "Forespørgselsparametre", + "headers": "Headers", + "body": "Body", + "copyBody": "Kopiér body", + "emptyBody": "Ingen body" + }, + "errors": { + "createFailed": "Kunne ikke oprette webhook-URL. Prøv igen.", + "clearFailed": "Kunne ikke rydde anmodninger. Prøv igen." + } + }, + "WebsocketTester": { + "title": "WebSocket-tester", + "subtitle": "Forbind til et WebSocket-endpoint, og send, modtag og inspicér beskeder live", + "urlLabel": "Endpoint-URL", + "protocolsLabel": "Subprotokoller", + "protocolsPlaceholder": "valgfrit, kommasepareret", + "connect": "Forbind", + "disconnect": "Afbryd", + "status": { + "disconnected": "Afbrudt", + "connecting": "Forbinder", + "connected": "Forbundet", + "error": "Fejl" + }, + "messages": { + "connecting": "Forbinder til {url}…", + "connected": "Forbindelse etableret", + "connectedWithProtocol": "Forbindelse etableret (protokol: {protocol})", + "invalidUrl": "Ugyldig URL — skal begynde med ws:// eller wss://", + "connectFailed": "Kunne ikke åbne forbindelsen", + "socketError": "Der opstod en forbindelsesfejl", + "notConnected": "Ikke forbundet — forbind først", + "binaryMessage": "Modtog en binær besked" + }, + "log": { + "title": "Beskedlog", + "empty": "Ingen beskeder endnu. Forbind, og begynd at sende.", + "copyAll": "Kopiér alle", + "download": "Download som .txt", + "clear": "Ryd log", + "prettyPrint": "Formatér JSON pænt", + "showRaw": "Vis rå tekst" + }, + "composer": { + "placeholder": "Skriv en besked, der skal sendes…", + "send": "Send", + "formatJson": "Formatér som JSON", + "invalidJson": "Beskeden er ikke gyldig JSON", + "hint": "Cmd/Ctrl+Enter for at sende" + }, + "ping": { + "label": "Auto-ping", + "seconds": "sek", + "intervalTitle": "Ping-interval i sekunder", + "payloadPlaceholder": "ping-payload" + } + }, + "WhoisLookup": { + "title": "Whois-opslag", + "subtitle": "Slå registreringsoplysninger op for ethvert domæne eller IP-adresse via RDAP", + "placeholder": "example.com eller 8.8.8.8", + "lookupBtn": "Slå op", + "looking": "Slår op…", + "refreshBtn": "Opdatér", + "tryLabel": "Prøv:", + "copy": "Kopiér", + "copied": "Kopieret", + "notAvailable": "Ikke tilgængelig", + "summary": { + "registrar": "Registrator", + "network": "Netværk" + }, + "dates": { + "created": "Oprettet", + "updated": "Opdateret", + "expires": "Udløber" + }, + "sections": { + "status": "Status", + "nameservers": "Navneservere", + "contacts": "Kontakter", + "rawRdap": "Rå RDAP" + }, + "contacts": { + "roles": "Roller", + "name": "Navn", + "organization": "Organisation" + }, + "errors": { + "invalidTarget": "Indtast et gyldigt domænenavn eller IP-adresse", + "lookupFailed": "Opslag mislykkedes (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/de.json b/apps/desktop-ui/messages/de.json new file mode 100644 index 00000000..72ef4168 --- /dev/null +++ b/apps/desktop-ui/messages/de.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Dashboard", + "settings": "Einstellungen", + "toDo": "Aufgaben", + "notes": "Notizen", + "passwordManager": "Passwort-Manager", + "environmentManager": "Umgebungs-Manager", + "bookmarks": "Lesezeichen", + "emailValidator": "E-Mail-Validator", + "encryptionPlayground": "Verschlüsselungs-Spielplatz", + "jwtDecoder": "JWT-Decoder", + "certificatePemDecoder": "Zertifikat- / PEM-Decoder", + "jsonFormatter": "JSON-Formatierer", + "sqlFormatter": "SQL-Formatierer", + "graphqlFormatter": "GraphQL-Formatierer", + "diffChecker": "Textvergleich", + "regexTester": "Regex-Tester", + "timestampConverter": "Zeitstempel-Konverter", + "cronBuilder": "Cron-Editor", + "base64Encoder": "Base64-Kodierung", + "urlEncoder": "URL-Kodierung", + "urlParser": "URL-Parser", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Geheimnis / API-Schlüssel", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Farbauswahl", + "contrastChecker": "Kontrastprüfung", + "apiClient": "API-Client", + "httpStatusCodes": "HTTP-Statuscodes", + "nosqlExplorer": "Datenbank-Explorer", + "jsonSchemaGenerator": "JSON-Schema-Generator", + "imageToBase64": "Bild zu Base64", + "imageCompressor": "Bildkomprimierung", + "cssGradientBuilder": "CSS-Verlaufsgenerator", + "gitignoreGenerator": ".gitignore-Generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Code-Snippets", + "qrCodeGenerator": "QR-Code-Generator", + "dnsLookup": "DNS-Abfrage", + "ipSubnetCalculator": "IP-/Subnetz-Rechner", + "hashGenerator": "Hash-Generator", + "hmacGenerator": "HMAC-Generator", + "totpGenerator": "TOTP / 2FA-Code", + "markdownPreview": "Markdown-Vorschau", + "numberBaseConverter": "Basiskonverter", + "formatConverter": "Format-Konverter", + "mockDataGenerator": "Mock-Datengenerator", + "svgOptimizer": "SVG-Optimierer", + "unitConverter": "Einheitenrechner", + "dockerComposeGenerator": "Docker-Compose-Generator", + "breakRoom": "Pausenraum", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAML-Formatierer", + "sshKeyGenerator": "SSH / RSA-Schlüsselgenerator", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod-Rechner", + "codeScreenshot": "Code-Screenshot", + "cssGenerators": "CSS-Generatoren", + "exifViewer": "EXIF-Viewer", + "faviconGenerator": "Favicon-Generator", + "jsonDiff": "JSON-Diff", + "jsonpathPlayground": "JSONPath-Playground", + "keycodeInspector": "Keycode-Inspektor", + "markdownTableGenerator": "Markdown-Tabelle", + "timezoneConverter": "Zeitzonen-Konverter", + "tokenCounter": "Token-Zähler", + "webhookTester": "Webhook-Tester", + "websocketTester": "WebSocket-Tester", + "whoisLookup": "Whois-Abfrage" + }, + "Help": { + "title": "Hilfe & Dokumentation", + "subtitle": "Wie MyDevTools zusammenhängt, was jede App macht und wie Sicherheit und Verschlüsselung deine Daten schützen.", + "tabs": { + "overview": "Übersicht", + "apps": "Apps", + "security": "Sicherheit", + "tips": "Tipps" + }, + "overview": { + "whatIs": { + "title": "Was ist MyDevTools?", + "description": "Ein Entwickler‑Workspace mit Login: Dashboard und Tools für den Alltag.", + "p1": "Nach dem Anmelden stellt die App eine Sitzung mit der MyDevTools‑API her, damit Tools Daten laden und speichern können. Die Seitenleiste listet alle Apps; ungenutzte Tools kannst du in", + "settingsLink": "Einstellungen", + "p1After": "ausblenden.", + "p2": "Einige Funktionen nutzen ein Master‑Passwort (oder einen globalen Vault‑Key), sodass Geheimnisse wie Passwörter und Verbindungsstrings auf deinem Gerät verschlüsselt werden, bevor sie den Server erreichen." + }, + "quickLinks": { + "title": "Schnelllinks", + "dashboard": "Dashboard", + "settings": "Einstellungen" + } + }, + "apps": { + "intro": "Öffne ein Tool über die Seitenleiste oder das Dashboard. Unten findest du eine kurze Anleitung pro App.", + "open": "Öffnen", + "howItWorks": "So funktioniert’s", + "generic": "Nutze dieses Tool über die Seitenleiste für den vorgesehenen Workflow." + }, + "security": { + "session": { + "title": "Login und API‑Sitzung", + "description": "Identität und wie der Browser mit deinem Backend spricht.", + "items": { + "authentication": "Authentifizierung erfolgt über Firebase. Nach dem Login tauscht die App dein Firebase‑ID‑Token gegen API‑Cookies.", + "accessTokens": "Access‑Tokens sind serverseitig signierte JWTs (HS256). Sie zeigen, welcher Nutzer die API aufruft.", + "refreshTokens": "Refresh‑Tokens liegen in HttpOnly‑Cookies; der Server speichert einen SHA‑256‑Hash des Tokens, nicht das Token im Klartext." + } + }, + "encryption": { + "title": "Wichtige Verschlüsselung", + "description": "Client‑seitige Krypto für Vault und sensible Verbindungsdaten.", + "p1": "Für den Passwort‑Manager, den Umgebungs‑Manager und gespeicherte Verbindungen im NoSQL‑Explorer nutzt die Web‑App die Web Crypto API des Browsers:", + "items": { + "kdf": "PBKDF2 mit SHA‑256, 100.000 Iterationen und zufälligem Salt leitet aus dem Master‑Passwort einen 256‑Bit‑Key ab.", + "aes": "AES‑256‑GCM verschlüsselt Nutzdaten; pro Verschlüsselung wird ein zufälliger 12‑Byte‑IV verwendet.", + "scope": "Der abgeleitete Schlüssel bleibt im Browser (nicht exportierbar). Der Server erhält Ciphertext und IVs, nicht dein Master‑Passwort oder Klartext‑Geheimnisse." + }, + "warning": "Wenn du das Master‑Passwort vergisst, können verschlüsselte Daten nicht wiederhergestellt werden. Bewahre es sicher auf." + }, + "logout": { + "title": "Abmelden und lokale Daten", + "body": "Beim Abmelden werden Secrets im Speicher, Vault‑Status und – falls zutreffend – Master‑Key‑Material aus IndexedDB gelöscht und die API‑Sitzung beendet. Daten, die nur im lokalen Speicher liegen, bleiben ggf. bis zur Browser‑Bereinigung erhalten." + } + }, + "tips": { + "title": "MyDevTools sicher nutzen", + "items": { + "masterPassword": "Nutze ein starkes, einzigartiges Master‑Passwort für Vault‑Funktionen.", + "apiClient": "Behandle den API‑Client wie jeden HTTP‑Client: füge keine Produktions‑Secrets in geteilte Umgebungen ein, wenn du nicht weißt, wer sie sieht.", + "nosql": "Der Datenbank‑Explorer verbindet sich mit von dir konfigurierten Datenbanken; nutze Least‑Privilege‑Accounts und Netzregeln, wenn möglich.", + "json": "Der JSON‑Formatierer läuft lokal im Browser—trotzdem keine hochsensiblen Daten auf geteilten Geräten einfügen.", + "settings": "Blende Tools in den Einstellungen aus, um die Seitenleiste fokussiert zu halten." + } + } + }, + "Settings": { + "title": "Einstellungen", + "language": "Sprache", + "spanish": "Spanisch", + "english": "Englisch", + "french": "Französisch", + "arabic": "Arabisch", + "catalan": "Katalanisch", + "chineseSimplified": "Chinesisch (Vereinfacht)", + "czech": "Tschechisch", + "greek": "Griechisch", + "german": "Deutsch", + "danish": "Dänisch", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukrainisch" + }, + "SettingsPage": { + "title": "Einstellungen", + "subtitle": "Verwalten Sie Ihre Kontoeinstellungen und Präferenzen.", + "userProfile": { + "title": "Benutzerprofil", + "description": "Ihre persönlichen Daten und Kontoinformationen.", + "avatarAlt": "Benutzer", + "anonymousUser": "Anonymer Benutzer", + "noEmailProvided": "Keine E-Mail angegeben", + "notLoggedIn": "Nicht angemeldet." + }, + "tools": { + "title": "Tools & Funktionen", + "description": "Wählen Sie, welche Tools in Ihrer Seitenleiste erscheinen." + }, + "appearance": { + "title": "Erscheinungsbild", + "description": "Passen Sie das Aussehen der Anwendung auf Ihrem Gerät an.", + "themePreference": "Theme", + "themes": { + "light": "Hell", + "dark": "Dunkel", + "system": "System" + } + }, + "language": { + "title": "Sprache", + "description": "Legen Sie die Sprache für die gesamte Plattform fest.", + "selectLanguage": "Sprache auswählen", + "selectPlaceholder": "Sprache wählen", + "helpText": "Wenn Sie die Sprache ändern, werden Menüs und Beschriftungen auf der gesamten Plattform aktualisiert.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spanisch)", + "ar": "عربى (Arabisch)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Griechisch)", + "de": "Deutsch", + "da": "Dansk", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italienisch (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Guten Morgen", + "afternoon": "Guten Tag", + "evening": "Guten Abend" + }, + "commaName": ", {name}", + "welcomeBack": "Willkommen zurück", + "welcomeBackNamed": "Willkommen zurück, {name}", + "tagline": "Was möchten Sie heute erstellen?", + "brandName": "MyDevTools", + "stats": { + "tools": "Tools", + "favorites": "Favoriten", + "pinned": "Angeheftet", + "recent": "Zuletzt verwendet" + }, + "sections": { + "recentlyUsed": "Zuletzt verwendet", + "favorites": "Favoriten", + "whatsNew": "Neuigkeiten", + "pinned": "Angeheftet" + }, + "groups": { + "apps": "Apps" + }, + "toolCard": { + "defaultDescription": "Entdecken Sie dieses Tool für mehr Möglichkeiten.", + "launchTool": "Tool öffnen" + }, + "tools": { + "toDo": { + "title": "Aufgaben", + "description": "Verwalten Sie Ihre To-do-Listen effizient." + }, + "notes": { + "title": "Notizen", + "description": "Erfassen Sie Ideen und strukturieren Sie Ihre Gedanken." + }, + "passwordManager": { + "title": "Passwort-Manager", + "description": "Speichern und verwalten Sie Ihre Passwörter sicher." + }, + "environmentManager": { + "title": "Umgebungs-Manager", + "description": ".env-ähnliche Variablen nach Projekt und Umgebung mit Tags speichern." + }, + "bookmarks": { + "title": "Lesezeichen", + "description": "Verwalten und organisieren Sie Ihre Lesezeichen." + }, + "apiClient": { + "title": "API-Client", + "description": "HTTP-Anfragen testen und debuggen." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "MongoDB-Datenbanken erkunden." + }, + "emailValidator": { + "title": "E-Mail-Validator", + "description": "E-Mail-Adressen prüfen und validieren." + }, + "encryptionPlayground": { + "title": "Verschlüsselungs-Spielplatz", + "description": "AES-GCM mit Rohschlüssel oder Passphrase; JSON-Bündel, Web Crypto nur im Browser." + }, + "jwtDecoder": { + "title": "JWT-Decoder", + "description": "JWT-Header, Nutzdaten und Ablaufzeit dekodieren." + }, + "certificatePemDecoder": { + "title": "Zertifikat- / PEM-Decoder", + "description": "X.509-Zertifikate und CSRs dekodieren: Antragsteller, Aussteller, Gültigkeit, SANs." + }, + "sshKeyGenerator": { + "title": "SSH / RSA-Schlüsselgenerator", + "description": "Ed25519- oder RSA-Schlüsselpaare generieren. Private und öffentliche Schlüssel im OpenSSH- und PEM-Format herunterladen." + }, + "jsonFormatter": { + "title": "JSON-Formatierer", + "description": "JSON formatieren, validieren und bearbeiten." + }, + "sqlFormatter": { + "title": "SQL-Formatierer", + "description": "SQL für MySQL, PostgreSQL und SQLite formatieren." + }, + "graphqlFormatter": { + "title": "GraphQL-Formatierer", + "description": "GraphQL mit Syntaxhervorhebung formatieren und mit einem einfachen Abfrage-Editor zusammenstellen." + }, + "yamlFormatter": { + "title": "YAML-Formatierer", + "description": "YAML im Browser formatieren, validieren und in JSON konvertieren." + }, + "diffChecker": { + "title": "Textvergleich", + "description": "Zwei Texte zeilenweise nebeneinander vergleichen." + }, + "regexTester": { + "title": "Regex-Tester", + "description": "Reguläre Ausdrücke mit Live-Hervorhebung testen." + }, + "timestampConverter": { + "title": "Zeitstempel-Konverter", + "description": "Unix-, ISO- und relative Zeitangaben umrechnen." + }, + "cronBuilder": { + "title": "Cron-Editor", + "description": "Visueller Cron-Editor, Vorlagen und Parser." + }, + "base64Encoder": { + "title": "Base64-Kodierung", + "description": "Zeichenketten Base64 kodieren und dekodieren." + }, + "urlEncoder": { + "title": "URL-Kodierung", + "description": "URL-Bestandteile prozentkodieren und dekodieren." + }, + "urlParser": { + "title": "URL-Parser", + "description": "Eine URL in Protokoll, Host, Pfad, Query-Parameter und Hash aufteilen." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "UUIDs und ULIDs erzeugen und massenhaft exportieren." + }, + "secretApiKeyGenerator": { + "title": "Geheimnis / API-Schlüssel", + "description": "Kryptografisch zufällige Zeichenketten mit konfigurierbarem Alphabet, Länge und Massenexport." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Platzhalter-Lateintext für Layouts und Mockups erzeugen." + }, + "colorPicker": { + "title": "Farbauswahl", + "description": "HEX, RGB, HSL umrechnen und Farbpaletten erkunden." + }, + "contrastChecker": { + "title": "Kontrastprüfung", + "description": "WCAG-2.1-Kontrastverhältnis für Text und Hintergrund (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "JSON-Schema-Generator", + "description": "Leiten Sie ein JSON-Schema und typisierte Modelle aus Beispiel-JSON ab." + }, + "imageToBase64": { + "title": "Bild zu Base64", + "description": "Konvertieren Sie Bilder in Daten-URI oder rohe Base64-Strings." + }, + "imageCompressor": { + "title": "Bildkomprimierung", + "description": "JPEG, PNG und WebP lokal mit einem Qualitätsregler komprimieren." + }, + "cssGradientBuilder": { + "title": "CSS-Verlaufsgenerator", + "description": "Visueller CSS-Verlaufsgenerator mit Winkelsteuerung und Farbstopps." + }, + "gitignoreGenerator": { + "title": ".gitignore-Generator", + "description": "Generieren Sie sofort zusammengeführte .gitignore-Dateien aus über 500 Tech-Stacks." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "CSV- oder Excel-Dateien in JSON umwandeln und nach CSV oder XLSX exportieren." + }, + "snippetManager": { + "title": "Code-Snippets", + "description": "Snippets mit Syntaxhervorhebung, automatischer Spracherkennung und Formatierung speichern." + }, + "qrCodeGenerator": { + "title": "QR-Code-Generator", + "description": "Erzeuge PNG-QR-Codes aus Text oder URLs im Browser." + }, + "ipSubnetCalculator": { + "title": "IP-/Subnetz-Rechner", + "description": "IPv4 und IPv6 CIDR: Netzmaske, Bereiche und Adressanzahl." + }, + "dnsLookup": { + "title": "DNS-Abfrage", + "description": "DNS-Einträge für jede Domain abfragen — A, AAAA, MX, TXT, NS, CNAME, SOA, CAA." + }, + "hashGenerator": { + "title": "Hash-Generator", + "description": "MD5, SHA-1 und SHA-2 für Text oder Dateien." + }, + "hmacGenerator": { + "title": "HMAC-Generator", + "description": "Webhooks signieren und API-Integrationen mit HMAC-SHA1/256/384/512 testen." + }, + "totpGenerator": { + "title": "TOTP- / 2FA-Codegenerator", + "description": "Base32-Geheimnis einfügen; der sechsstellige Code erneuert sich alle 30 Sekunden—praktisch zum Testen von Login und MFA." + }, + "markdownPreview": { + "title": "Markdown-Vorschau", + "description": "Live-Markdown-Renderer mit HTML-Export und HTML → Markdown-Konvertierung." + }, + "numberBaseConverter": { + "title": "Zahlenbasen-Konverter", + "description": "Ganzzahlen zwischen Basen 2–36 umrechnen." + }, + "formatConverter": { + "title": "Format-Konverter", + "description": "Konvertieren Sie zwischen JSON, YAML, TOML und XML in jede Richtung. Alle 12 Kombinationen werden unterstützt." + }, + "mockDataGenerator": { + "title": "Mock-Datengenerator", + "description": "Spalten definieren, Datentypen wählen und Fixtures für Seeds, Postman oder Tests exportieren. Bis zu 5000 Zeilen, alles im Browser — nichts wird hochgeladen." + }, + "httpStatusCodes": { + "title": "HTTP-Statuscodes", + "description": "HTTP-Statuscodes mit Beschreibungen durchsuchen (1xx–5xx)." + }, + "svgOptimizer": { + "title": "SVG-Optimierer", + "description": "SVG einfügen, mit SVGO Ballast entfernen und die UTF-8-Größenreduktion sehen." + }, + "unitConverter": { + "title": "Einheitenrechner", + "description": "Konvertieren zwischen 323 Einheiten in 43 wissenschaftlichen und technischen Kategorien." + }, + "dockerComposeGenerator": { + "title": "Docker-Compose-Generator", + "description": "Datenbanken, Caches, Warteschlangen, Proxys und Observability wählen; docker-compose.yml für lokale Stacks exportieren." + }, + "game2048": { + "title": "2.048", + "description": "Schiebe und füge Steine zusammen, um 2048 zu erreichen. Klassisches Zahlenpuzzlespiel." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klassisches Zahlenrätsel. 3 Schwierigkeitsgrade mit je 150 Stufen." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Schlüssel durchsuchen, Werte prüfen und Befehle auf einer Redis-Instanz ausführen." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt-Generator & -Prüfer", + "description": "Passwörter mit bcrypt hashen und bestehende Hashes verifizieren." + }, + "chmodCalculator": { + "title": "Chmod-Rechner", + "description": "Unix-Berechtigungen zwischen Oktal, symbolisch und Kontrollkästchen umrechnen." + }, + "codeScreenshot": { + "title": "Code-Screenshot", + "description": "Quellcode in schöne, teilbare Bilder verwandeln." + }, + "cssGenerators": { + "title": "CSS-Generatoren", + "description": "Generatoren für box-shadow, border-radius und fluides clamp()." + }, + "exifViewer": { + "title": "EXIF-Viewer & -Entferner", + "description": "EXIF-Metadaten von Fotos lokal prüfen und entfernen." + }, + "faviconGenerator": { + "title": "Favicon-Generator", + "description": "Favicons, App-Icons und Manifest-Snippets erzeugen." + }, + "jsonDiff": { + "title": "JSON-Diff", + "description": "Semantischer, strukturbewusster Vergleich zweier JSON-Dokumente." + }, + "jsonpathPlayground": { + "title": "JSONPath-Playground", + "description": "JSON mit JSONPath oder JMESPath abfragen und Ergebnisse live sehen." + }, + "keycodeInspector": { + "title": "Keycode-Inspektor", + "description": "Eine Taste drücken, um ihre JavaScript-Event-Codes und Modifikatoren zu sehen." + }, + "markdownTableGenerator": { + "title": "Markdown-Tabellen-Generator", + "description": "Markdown- und HTML-Tabellen in einem tabellenkalkulationsähnlichen Raster-Editor erstellen." + }, + "timezoneConverter": { + "title": "Zeitzonen-Konverter", + "description": "Eine Zeit über viele Zonen umrechnen, mit Hinweisen zu Tageswechsel und Arbeitszeiten." + }, + "tokenCounter": { + "title": "LLM-Token-Zähler", + "description": "LLM-Tokens zählen, Chunks visualisieren und API-Kosten schätzen." + }, + "webhookTester": { + "title": "Webhook-Tester", + "description": "Eingehende Webhook-Anfragen in Echtzeit erfassen und untersuchen." + }, + "websocketTester": { + "title": "WebSocket-Tester", + "description": "Mit WebSocket-Endpunkten verbinden und Nachrichten in Echtzeit austauschen." + }, + "whoisLookup": { + "title": "Whois-Abfrage", + "description": "Registrierungsdaten von Domains und IPs per RDAP abrufen." + } + }, + "tabs": { + "apps": "Apps", + "analytics": "Analysen", + "ui": "Oberfläche" + }, + "analytics": { + "title": "Ihre Daten auf einen Blick", + "subtitle": "Zählerstand mit Ihrem Konto auf dem Server synchronisiert.", + "loadError": "Analysen konnten nicht geladen werden.", + "retry": "Erneut versuchen", + "loginHint": "Melden Sie sich an, um Nutzungsanalysen und Oberflächeneinstellungen zu sehen.", + "passwordEntries": "Passwort-Einträge", + "bookmarks": "Lesezeichen", + "bookmarkFolders": "Lesezeichen-Ordner", + "tasksTotal": "Aufgaben", + "tasksCompleted": "Erledigt", + "tasksOngoing": "In Bearbeitung", + "tasksNotStarted": "Nicht gestartet", + "projects": "Projekte", + "nosqlConnections": "DB-Verbindungen", + "notes": "Notizen", + "apiClientCollections": "API-Sammlungen", + "apiClientEnvironments": "API-Umgebungen", + "apiClientHistory": "API-Verlaufseinträge", + "jsonFormatterDocuments": "JSON-Formatierer-Dokumente", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Code-Snippets", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Oberflächeneinstellungen", + "subtitle": "Design, Sprache und sichtbare Tools in der Seitenleiste — aus Ihren Kontoeinstellungen.", + "loadError": "Einstellungen konnten nicht geladen werden.", + "loginHint": "Melden Sie sich an, um Ihre gespeicherten Oberflächeneinstellungen zu sehen.", + "theme": "Design", + "accent": "Akzentfarbe", + "locale": "Sprache", + "enabledTools": "Sichtbare Tools", + "favoriteTools": "Favoriten-Tools", + "toolsWithUsage": "Tools mit Nutzungsstatistik" + }, + "signIn": "Anmelden", + "noResults": "Keine Werkzeuge entsprechen deiner Suche", + "noResultsHint": "Versuche ein anderes Stichwort oder lösche den Filter.", + "filterAll": "Alle", + "loginCta": { + "description": "Melde dich an, um deine zuletzt verwendeten Werkzeuge zu verfolgen." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Laden...", + "myTasksTitle": "Meine Aufgaben", + "statsLine": "{total} Aufgaben • {percent} % erledigt", + "statsLineDesktop": "{total} gesamt • {percent} % erledigt" + }, + "filters": { + "all": "Alle", + "total": "Gesamt", + "allProjects": "Alle Projekte", + "export": "Exportieren", + "kanban": "Kanban", + "list": "Liste", + "searchPlaceholder": "Aufgaben suchen...", + "searchAria": "Aufgaben suchen", + "clearSearchAria": "Suche löschen", + "foundTasks": "{count} {count, plural, one {Aufgabe} other {Aufgaben}} gefunden", + "filterByAllAria": "Nach Alle filtern", + "filterByStatusAria": "Nach {status} filtern" + }, + "status": { + "not-started": { + "label": "Nicht gestartet", + "description": "Aufgaben, die noch nicht begonnen wurden" + }, + "ongoing": { + "label": "In Bearbeitung", + "description": "Aufgaben, die gerade bearbeitet werden" + }, + "completed": { + "label": "Erledigt", + "description": "Abgeschlossene Aufgaben" + } + }, + "priorities": { + "high": "Hoch", + "medium": "Mittel", + "low": "Niedrig" + }, + "form": { + "addNewTaskLabel": "Neue Aufgabe", + "multilinePlaceholder": "Aufgabendetails... (Umschalt+Enter für neue Zeile)", + "singlelinePlaceholder": "Was ist zu tun? Enter zum Hinzufügen", + "taskInputAria": "Aufgabeneingabe", + "projectPlaceholder": "Projekt", + "selectProject": "Projekt wählen", + "errorSelectProject": "Bitte wählen Sie ein Projekt oder legen Sie ein neues an.", + "hintMultiline": "Umschalt+Enter für neue Zeile, Enter zum Hinzufügen", + "hintSingleline": "Enter zum Hinzufügen", + "shortcutsAria": "Tastenkürzel", + "shortcutsLabel": "Kürzel", + "shortcutAddTask": "Aufgabe hinzufügen", + "shortcutNewLine": "Neue Zeile", + "shortcutClear": "Leeren", + "addButton": "Hinzufügen" + }, + "list": { + "ariaLabel": "Aufgabenliste", + "loading": "Aufgaben werden geladen...", + "emptyTitle": "Keine Aufgaben", + "emptyPrimary": "Fügen Sie oben Ihre erste Aufgabe hinzu! Mit Enter können Sie schnell Aufgaben anlegen.", + "emptySecondary": "Passen Sie Filter oder Suchbegriff an." + }, + "mobileDrawer": { + "addTaskSrOnly": "Aufgabe hinzufügen", + "addNewTaskTitle": "Neue Aufgabe", + "addNewTaskDescription": "Legen Sie eine neue Aufgabe an, um den Fortschritt zu verfolgen.", + "cancel": "Abbrechen" + }, + "kanban": { + "ariaLabel": "Kanban-Board", + "loading": "Aufgaben werden geladen...", + "dropHere": "Aufgabe hier ablegen", + "noTasksYet": "Noch keine Aufgaben", + "emptyColumnAria": "Leere Spalte", + "columnAria": "Spalte {title} mit {count} Aufgaben", + "countInColumnAria": "{count} Aufgaben in {title}" + }, + "taskItem": { + "confirmDelete": "Diese Aufgabe wirklich löschen?", + "statusPlaceholder": "Status", + "changeStatusAria": "Aufgabenstatus ändern", + "taskOptionsAria": "Aufgabenoptionen", + "editTask": "Aufgabe bearbeiten", + "copyTask": "Aufgabe kopieren", + "copied": "Kopiert!", + "deleteTask": "Aufgabe löschen", + "statusHeading": "Status", + "editTaskAria": "Aufgabe bearbeiten" + }, + "editDialog": { + "title": "Aufgabe bearbeiten", + "description": "Details anpassen, Unteraufgaben hinzufügen, Prioritäten setzen und mehr.", + "taskTitleLabel": "Aufgabentitel *", + "taskTitlePlaceholder": "Was ist zu tun?", + "descriptionLabel": "Beschreibung", + "descriptionPlaceholder": "Weitere Details zu dieser Aufgabe...", + "statusLabel": "Status", + "priorityLabel": "Priorität", + "dueDateLabel": "Fälligkeitsdatum", + "pickDate": "Datum wählen", + "clearDate": "Datum löschen", + "projectLabel": "Projekt", + "selectProjectPlaceholder": "Projekt wählen", + "noProject": "Kein Projekt", + "timeEstimateLabel": "Zeitschätzung (Minuten)", + "timeEstimatePlaceholder": "Wie lange wird es dauern?", + "tagsLabel": "Tags", + "customTagNamePlaceholder": "Eigener Tag-Name", + "subtasksLabel": "Unteraufgaben", + "addSubtaskPlaceholder": "Unteraufgabe hinzufügen...", + "cancel": "Abbrechen", + "saving": "Speichern...", + "saveChanges": "Änderungen speichern" + }, + "export": { + "title": "Aufgaben exportieren", + "description": "Aufgaben als JSON, CSV oder Excel exportieren.", + "projectLabel": "Projekt", + "statusLabel": "Status", + "tagLabel": "Tag", + "allProjects": "Alle Projekte", + "allStatuses": "Alle Status", + "allTags": "Alle Tags", + "exportAsJsonTitle": "Als JSON exportieren", + "exportAsJsonDescription": "Aufgabendaten inkl. Unteraufgaben, Tags und Metadaten.", + "exportJson": "JSON exportieren", + "exportAsCsvExcelTitle": "Als CSV / Excel exportieren", + "exportAsCsvExcelDescription": "Aufgaben für Tabellenkalkulationen exportieren.", + "exportCsv": "CSV exportieren", + "exportExcel": "Excel exportieren", + "close": "Schließen", + "toastNoTasks": "Mit den aktuellen Filtern gibt es nichts zu exportieren", + "toastExportedJson": "{count} Aufgaben als JSON exportiert!", + "toastExportedCsv": "{count} Aufgaben als CSV exportiert!", + "toastExportedExcel": "{count} Aufgaben als Excel exportiert!", + "toastFailedJson": "JSON-Export fehlgeschlagen", + "toastFailedCsv": "CSV-Export fehlgeschlagen", + "toastFailedExcel": "Excel-Export fehlgeschlagen", + "unknownProject": "Unbekanntes Projekt" + }, + "ack": { + "taskAddedTitle": "Aufgabe hinzugefügt", + "taskAddedFailedTitle": "Aufgabe konnte nicht hinzugefügt werden", + "taskUpdatedTitle": "Aufgabe aktualisiert", + "taskUpdatedFailedTitle": "Aufgabe konnte nicht aktualisiert werden", + "taskStatusUpdateFailedTitle": "Status konnte nicht aktualisiert werden", + "taskMovedTitle": "Aufgabe nach {status} verschoben", + "tryAgain": "Bitte erneut versuchen.", + "taskDeletedTitle": "Aufgabe „{text}“ gelöscht", + "undo": "Rückgängig", + "taskRestoredTitle": "Aufgabe wiederhergestellt", + "taskRestoredSuccessTitle": "Aufgabe erfolgreich wiederhergestellt", + "taskRestoreFailedTitle": "Wiederherstellung fehlgeschlagen. Bitte erneut versuchen.", + "taskDeleteFailedTitle": "Löschen fehlgeschlagen. Bitte erneut versuchen.", + "tasksImportedTitle": "{count} {count, plural, one {Aufgabe} other {Aufgaben}} importiert!", + "tasksImportFailedTitle": "Import fehlgeschlagen. Bitte erneut versuchen.", + "unknownProject": "Unbekanntes Projekt", + "projectCreatedTitle": "Projekt angelegt", + "projectCreateFailedTitle": "Projekt konnte nicht angelegt werden", + "projectUpdatedTitle": "Projekt aktualisiert", + "projectUpdateFailedTitle": "Projekt konnte nicht aktualisiert werden", + "projectDeletedTitle": "Projekt gelöscht", + "projectDeleteFailedTitle": "Projekt konnte nicht gelöscht werden" + } + }, + "Notes": { + "page": { + "loading": "Laden..." + }, + "layout": { + "mobileNavTitle": "Notizen-Navigation", + "openMenuAria": "Notizmenü öffnen" + }, + "sidebar": { + "title": "Notizen", + "createNoteAria": "Notiz erstellen", + "searchPlaceholder": "Notizen suchen...", + "loading": "Laden...", + "noNotesFound": "Keine Notizen gefunden.", + "noNotesYet": "Noch keine Notizen. Klicken Sie auf +, um eine zu erstellen.", + "inParent": "in {parentTitle}", + "untitled": "Ohne Titel", + "addSubPage": "Unterseite hinzufügen", + "delete": "Löschen", + "deleteConfirmTitle": "Notiz löschen?", + "deleteConfirmDescription": "„{title}“ wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", + "cancel": "Abbrechen", + "confirmDelete": "Löschen", + "noteOptionsAria": "Notizoptionen" + }, + "editor": { + "emptyState": "Wählen Sie eine Notiz oder legen Sie eine neue an.", + "titlePlaceholder": "Ohne Titel", + "saving": "Wird gespeichert…", + "ready": "Bereit", + "saved": "Gespeichert {time}", + "stats": "{words} Wörter · {read} Min. Lesezeit", + "template": "Vorlage", + "applyTemplate": "Vorlage anwenden", + "exportMdLabel": ".md", + "exportMd": "Als Markdown exportieren", + "exportHtmlLabel": ".html", + "exportHtml": "Als HTML exportieren", + "focusMode": "Fokusmodus", + "exitFocusMode": "Fokusmodus beenden", + "save": "Speichern", + "addTag": "Tag hinzufügen…", + "removeTag": "Tag {tag} entfernen", + "loading": "Wird geladen…", + "searchHint": "zum Suchen", + "focusHint": "Fokusmodus", + "notePath": "Notizpfad" + }, + "context": { + "authRequiredError": "Benutzer nicht angemeldet", + "defaultTitle": "Ohne Titel", + "cloudSyncImageError": "Aktiviere die Cloud-Synchronisierung, um Notizbilder zu speichern. Textnotizen bleiben lokal." + } + }, + "PasswordManager": { + "page": { + "loading": "Laden...", + "title": "Passwort-Manager", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Passwörter konnten nicht entschlüsselt werden", + "toastLoadFailed": "Passwörter konnten nicht geladen werden", + "toastUnlockedAuto": "Tresor automatisch entsperrt", + "toastAutoUnlockFailed": "Automatisches Entsperren fehlgeschlagen. Bitte manuell entsperren.", + "toastConnectFailed": "Verbindung zum Server fehlgeschlagen", + "toastAutoUnlockWarning": "Tresor entsperrt, automatisches Entsperren nach Neuladen ggf. nicht verfügbar", + "errorIncorrectPassword": "Falsches Master-Passwort", + "errorUnlockFailed": "Tresor konnte nicht entsperrt werden", + "errorPasswordsMismatch": "Passwörter stimmen nicht überein", + "errorPasswordTooShort": "Passwort muss mindestens 8 Zeichen haben", + "errorSetupFailed": "Tresor konnte nicht eingerichtet werden", + "errorConnectFailed": "Verbindung zum Server fehlgeschlagen.", + "mobileTitleCreate": "Tresor anlegen", + "mobileTitleWelcome": "Willkommen zurück", + "mobileSubtitleCreate": "Legen Sie ein Master-Passwort zum Schutz Ihrer Zugangsdaten fest", + "mobileSubtitleUnlock": "Geben Sie Ihr Master-Passwort ein", + "placeholderMasterPassword": "Master-Passwort", + "placeholderConfirmPassword": "Passwort bestätigen", + "labelMasterPassword": "Master-Passwort", + "labelConfirmPassword": "Passwort bestätigen", + "placeholderEnterMaster": "Master-Passwort eingeben", + "placeholderConfirmMaster": "Master-Passwort bestätigen", + "buttonCreateVault": "Tresor erstellen", + "buttonUnlock": "Entsperren", + "buttonUnlockVault": "Tresor entsperren", + "footerCreate": "Ihr Passwort wird nicht gespeichert und kann nicht wiederhergestellt werden", + "footerUnlock": "Geschützt mit Ende-zu-Ende-Verschlüsselung", + "desktopTitleSetup": "Passwort-Tresor einrichten", + "desktopTitleUnlock": "Tresor entsperren", + "desktopDescriptionSetup": "Legen Sie ein Master-Passwort fest. Es wird nicht gespeichert und kann nicht wiederhergestellt werden.", + "desktopDescriptionUnlock": "Geben Sie Ihr Master-Passwort ein, um auf Ihre Zugangsdaten zuzugreifen." + }, + "list": { + "loading": "Passwörter werden geladen...", + "emptyTitle": "Tresor ist leer", + "emptyHintMobile": "Tippen Sie auf +, um Ihr erstes Passwort hinzuzufügen", + "emptyHintDesktop": "Noch keine Passwörter. Klicken Sie oben auf „Passwort hinzufügen“.", + "addPassword": "Passwort hinzufügen", + "searchPlaceholderMobile": "Tresor durchsuchen...", + "searchPlaceholderDesktop": "Tresor durchsuchen...", + "importExport": "Import / Export", + "lockVault": "Tresor sperren", + "allPasswordsTitle": "Alle Passwörter", + "passwordCount": "{count, plural, one {# Passwort} other {# Passwörter}}", + "vaultHealthDrawerTitle": "Tresor-Gesundheit", + "noResults": "Keine Passwörter für „{query}“", + "tableService": "Dienst", + "tableUsername": "Benutzername", + "tablePassword": "Passwort", + "tableUrl": "URL", + "tableActions": "Aktionen", + "gridViewAria": "Rasteransicht", + "listViewAria": "Listenansicht", + "passwordStrengthTitle": "Passwortstärke", + "deleteDialogTitle": "Passwort löschen", + "deleteDialogDescription": "Dieses Passwort wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", + "cancel": "Abbrechen", + "delete": "Löschen", + "edit": "Bearbeiten" + }, + "toasts": { + "copiedPassword": "Passwort in die Zwischenablage kopiert", + "copiedUsername": "Benutzername in die Zwischenablage kopiert", + "passwordDeleted": "Passwort gelöscht", + "deleteFailed": "Passwort konnte nicht gelöscht werden", + "vaultLocked": "Tresor gesperrt", + "importedCount": "{count, plural, one {# Passwort} other {# Passwörter}} importiert" + }, + "form": { + "addTitle": "Neues Passwort", + "editTitle": "Passwort bearbeiten", + "serviceName": "Dienstname", + "usernameEmail": "Benutzername / E-Mail", + "password": "Passwort", + "urlOptional": "Website-URL (optional)", + "notesOptional": "Notizen (optional)", + "tags": "Tags", + "placeholderService": "z. B. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Passwort eingeben oder generieren", + "placeholderUrl": "https://...", + "placeholderNotes": "Zusätzliche Angaben...", + "placeholderTags": "Tags hinzufügen (Enter oder Komma)", + "strength": "Stärke", + "strengthWeak": "Schwach", + "strengthMedium": "Mittel", + "strengthStrong": "Stark", + "advancedGenerator": "Erweiterter Generator", + "cancel": "Abbrechen", + "saving": "Speichern...", + "savePassword": "Passwort speichern", + "updating": "Aktualisieren...", + "updatePassword": "Passwort aktualisieren", + "addPasswordSrOnly": "Passwort hinzufügen", + "triggerAdd": "Passwort hinzufügen", + "toastSaved": "Passwort gespeichert", + "toastSaveFailed": "Speichern fehlgeschlagen", + "toastUpdated": "Passwort aktualisiert", + "toastUpdateFailed": "Aktualisierung fehlgeschlagen" + }, + "security": { + "vaultHealth": "Tresor-Gesundheit", + "statusLabel": "Status", + "totalPasswords": "Passwörter gesamt", + "activeEntries": "Aktive Einträge im Tresor", + "protected": "Geschützt", + "weakPasswords": "Schwache Passwörter", + "needingImprovement": "Passwörter mit Verbesserungsbedarf", + "allGood": "Alles in Ordnung", + "reusedPasswords": "Wiederverwendete Passwörter", + "usedMultipleTimes": "Mehrfach verwendete Passwörter", + "unique": "Eindeutig", + "total": "Gesamt", + "weak": "Schwach", + "reused": "Wiederverwendet", + "totalCountLabel": "{count, plural, one {# Passwort} other {# Passwörter}}", + "weakCountLabel": "{count} schwach", + "allSecure": "Alles sicher", + "scoreExcellent": "Ausgezeichnet", + "scoreGood": "Gut", + "scoreFair": "Ausreichend", + "scoreCritical": "Kritisch" + }, + "importExport": { + "title": "Tresor importieren / exportieren", + "description": "Passwortdaten verwalten. Export als JSON oder Import aus einer JSON-Datei.", + "tabExport": "Export", + "tabImport": "Import", + "warningTitle": "Warnung", + "warningBody": "Exportdateien enthalten Passwörter im Klartext. Datei sicher aufbewahren und nach Gebrauch löschen.", + "exportBlurb": "Gesamten Tresor als JSON herunterladen (Dienste, Benutzernamen, Passwörter, Notizen).", + "downloadJson": "JSON-Export herunterladen", + "pasteLabel": "JSON-Daten einfügen", + "pastePlaceholder": "[{\"service\": \"Beispiel\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Inhalt einer zuvor exportierten JSON-Datei hier einfügen.", + "importing": "Importiere...", + "importPasswords": "Passwörter importieren", + "trigger": "Import / Export", + "toastExported": "Tresor exportiert", + "toastExportFailed": "Export fehlgeschlagen", + "toastImportFailed": "Import fehlgeschlagen: ungültiges JSON" + }, + "card": { + "copyUsername": "Benutzername kopieren", + "openUrl": "URL öffnen", + "strength": "Stärke" + }, + "swipeable": { + "copy": "Kopieren", + "delete": "Löschen", + "copyPassword": "Passwort kopieren", + "copyUsername": "Benutzername kopieren", + "edit": "Bearbeiten", + "deleteAction": "Löschen" + }, + "floatingMenu": { + "lockVault": "Tresor sperren", + "importExport": "Import / Export", + "addPassword": "Passwort hinzufügen" + }, + "generator": { + "tabPassword": "Passwort", + "tabPassphrase": "Passphrase", + "strength": "Stärke", + "lengthLabel": "Länge: {length} Zeichen", + "lengthBadge": "{length} Z.", + "characterSets": "Zeichensätze", + "uppercase": "Großbuchstaben", + "lowercase": "Kleinbuchstaben", + "numbers": "Ziffern", + "symbols": "Symbole", + "wordCountLabel": "Wortanzahl: {count}", + "wordCountBadge": "{count, plural, one {# Wort} other {# Wörter}}", + "separator": "Trennzeichen", + "capitalization": "Großschreibung", + "includeNumber": "Ziffer einbeziehen" + } + }, + "EnvironmentManager": { + "subtitle": "Verschlüsselte Umgebungsvariablen nach Projekt und Umgebung.", + "page": { + "loading": "Wird geladen…", + "title": "Umgebungs-Manager", + "loadFailed": "Umgebungssets konnten nicht geladen werden" + }, + "list": { + "title": "Umgebungen", + "loading": "Sets werden geladen…", + "emptyTitle": "Noch keine Umgebungssets", + "emptyHint": "Legen Sie ein Set pro Projekt und Umgebung an (z. B. Produktions-API-Schlüssel). Alles wird vor dem Upload im Browser verschlüsselt.", + "searchPlaceholder": "Projekt, Umgebung, Tags, Notizen durchsuchen…", + "tagFilterPlaceholder": "Nach Tag filtern", + "variableCount": "{count, plural, one {# Variable} other {# Variablen}}", + "updatedAgo": "Aktualisiert {time}", + "copyDotEnv": ".env kopieren", + "edit": "Bearbeiten", + "delete": "Löschen", + "cancel": "Abbrechen", + "confirmDelete": "Löschen", + "deleteDialogTitle": "Umgebungsset löschen?", + "deleteDialogDescription": "Der verschlüsselte Datensatz wird aus Ihrem Konto entfernt. Dies lässt sich nicht rückgängig machen.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env in die Zwischenablage kopiert", + "deleted": "Umgebungsset gelöscht", + "deleteFailed": "Löschen fehlgeschlagen" + }, + "form": { + "addTitle": "Neues Umgebungsset", + "editTitle": "Umgebungsset bearbeiten", + "triggerAdd": "Umgebungsset hinzufügen", + "addSrOnly": "Umgebungsset hinzufügen", + "project": "Projekt", + "environment": "Umgebung", + "placeholderProject": "z. B. billing-api", + "placeholderEnvironment": "z. B. production", + "pasteEnvLabel": ".env einfügen", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Eingefügte Zeilen zusammenführen", + "variables": "Variablen", + "addRow": "Zeile hinzufügen", + "keyPlaceholder": "SCHLÜSSEL", + "valuePlaceholder": "Wert", + "notesOptional": "Notizen (optional)", + "placeholderNotes": "Rotation, Verantwortliche, Links…", + "tags": "Tags", + "placeholderTags": "Tags hinzufügen (Enter oder Komma)", + "cancel": "Abbrechen", + "save": "Speichern", + "saving": "Speichern…", + "update": "Aktualisieren", + "updating": "Aktualisieren…", + "toastSaved": "Umgebungsset gespeichert", + "toastSaveFailed": "Speichern fehlgeschlagen", + "toastUpdated": "Umgebungsset aktualisiert", + "toastUpdateFailed": "Aktualisierung fehlgeschlagen", + "toastPasteEmpty": "Keine KEY=Zeilen gefunden", + "toastPasteApplied": "{count} Variablen aus der Einfügung übernommen", + "toastProjectEnvRequired": "Projekt und Umgebung sind erforderlich", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Laden..." + }, + "manager": { + "foldersHeading": "Ordner", + "tagsHeading": "Tags", + "tagsMore": "+{count} weitere", + "tagsShowLess": "Show less", + "searchPlaceholder": "Lesezeichen suchen...", + "import": "Importieren", + "export": "Exportieren", + "exportAsHtml": "Als HTML exportieren", + "exportAsJson": "Als JSON exportieren", + "addBookmark": "Lesezeichen hinzufügen", + "matchingQuery": "Treffer für „{query}“", + "bookmarkCount": "{count, plural, one {# Lesezeichen} other {# Lesezeichen}}", + "allBookmarks": "Alle Lesezeichen", + "uncategorized": "Ohne Kategorie", + "unknownFolder": "Unbekannt" + }, + "card": { + "urlCopied": "URL in die Zwischenablage kopiert", + "bookmarkDeleted": "Lesezeichen gelöscht", + "edit": "Bearbeiten", + "delete": "Löschen", + "copyUrl": "URL kopieren", + "deleteConfirmTitle": "Lesezeichen löschen?", + "deleteConfirmDescription": "„{title}“ wird dauerhaft gelöscht.", + "cancel": "Abbrechen" + }, + "grid": { + "emptyTitle": "Noch keine Lesezeichen", + "emptyDescription": "Fügen Sie ein Lesezeichen hinzu oder importieren Sie aus dem Browser." + }, + "folderTree": { + "allBookmarks": "Alle Lesezeichen", + "addSubfolder": "Unterordner hinzufügen", + "rename": "Umbenennen", + "deleteFolderTitle": "Ordner „{name}“ löschen?", + "deleteFolderDescription": "Der Ordner und alle Unterordner werden gelöscht. Lesezeichen werden nach „Ohne Kategorie“ verschoben." + }, + "import": { + "title": "Lesezeichen importieren", + "description": "Lesezeichen aus einer vom Browser exportierten Datei importieren.", + "parseError": "Datei konnte nicht gelesen werden. Bitte gültigen Lesezeichen-Export verwenden.", + "successToast": "{bookmarks} Lesezeichen und {folders} Ordner importiert", + "errorToast": "Import fehlgeschlagen", + "chromeInstruction": "Menü → Lesezeichen → Lesezeichen-Manager → ⋮ → Lesezeichen exportieren", + "firefoxInstruction": "Menü → Lesezeichen → Lesezeichen verwalten → Importieren und sichern → Exportieren", + "safariInstruction": "Ablage → Lesezeichen exportieren …", + "edgeInstruction": "Menü → Favoriten → ⋯ → Favoriten exportieren", + "dropZoneTitle": "Lesezeichen-Datei hier ablegen", + "dropZoneHint": "oder klicken zum Durchsuchen (HTML oder JSON)", + "selectedFileHint": "Klicken, um eine andere Datei zu wählen", + "errorTitle": "Fehler", + "previewTitle": "Importvorschau", + "previewBookmarks": "{count, plural, one {# Lesezeichen} other {# Lesezeichen}}", + "previewFolders": "{count, plural, one {# Ordner} other {# Ordner}}", + "importModeLabel": "Importmodus", + "modeMerge": "Mit vorhandenen Lesezeichen zusammenführen", + "modeReplace": "Alle vorhandenen Lesezeichen ersetzen", + "cancel": "Abbrechen", + "importButton": "Importieren", + "importButtonWithCount": "{count, plural, one {# Lesezeichen} other {# Lesezeichen}} importieren" + }, + "addBookmark": { + "titleAdd": "Lesezeichen hinzufügen", + "titleEdit": "Lesezeichen bearbeiten", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Bitte eine gültige URL eingeben", + "titleLabel": "Titel", + "titlePlaceholder": "Lesezeichentitel", + "titleRequired": "Bitte einen Titel eingeben", + "descriptionLabel": "Beschreibung (optional)", + "descriptionPlaceholder": "Notiz zu diesem Lesezeichen", + "folderLabel": "Ordner", + "folderPlaceholder": "Ordner wählen", + "noFolder": "Kein Ordner", + "tagsLabel": "Tags", + "tagsPlaceholder": "Tags hinzufügen (Enter)", + "toastUpdated": "Lesezeichen aktualisiert", + "toastAdded": "Lesezeichen hinzugefügt", + "cancel": "Abbrechen", + "submitAdd": "Lesezeichen hinzufügen", + "submitUpdate": "Lesezeichen aktualisieren" + }, + "addFolder": { + "title": "Ordner erstellen", + "nameLabel": "Ordnername", + "namePlaceholder": "Mein Ordner", + "nameRequired": "Bitte einen Ordnernamen eingeben", + "parentLabel": "Übergeordneter Ordner", + "parentPlaceholder": "Übergeordneten Ordner wählen", + "rootParent": "Stamm (kein übergeordneter Ordner)", + "toastCreated": "Ordner erstellt", + "cancel": "Abbrechen", + "submit": "Ordner erstellen" + }, + "editFolder": { + "title": "Ordner umbenennen", + "nameLabel": "Ordnername", + "namePlaceholder": "Ordnername", + "nameRequired": "Bitte einen Ordnernamen eingeben", + "toastRenamed": "Ordner umbenannt", + "cancel": "Abbrechen", + "save": "Speichern" + } + }, + "EmailValidator": { + "header": { + "title": "E-Mail-Validator", + "description": "E-Mail-Adressen auf Syntax, Domain und Zustellbarkeit prüfen." + }, + "tabs": { + "single": "Einzelprüfung", + "bulk": "Massenupload" + }, + "single": { + "cardTitle": "Einzelne E-Mail prüfen", + "cardDescription": "Eine Adresse eingeben, um Zustellbarkeit und Reputation zu prüfen.", + "placeholder": "E-Mail-Adresse eingeben...", + "clearAria": "E-Mail löschen", + "validate": "Prüfen", + "validating": "Wird geprüft...", + "formatError": "Bitte eine gültige E-Mail-Adresse eingeben" + }, + "toasts": { + "errorTitle": "Fehler", + "enterEmail": "Bitte eine E-Mail-Adresse eingeben", + "invalidFormatTitle": "Ungültiges E-Mail-Format", + "invalidFormatDescription": "Bitte eine gültige E-Mail-Adresse eingeben", + "validateFailedDescription": "Beim Prüfen der E-Mail ist ein Fehler aufgetreten", + "noEmailsTitle": "Keine E-Mails gefunden", + "noEmailsDescription": "Die Excel-Datei sollte eine Spalte „email“ enthalten.", + "fileTooLargeTitle": "Datei zu groß", + "fileTooLargeDescription": "{total} E-Mails gefunden. Es werden nur die ersten 4000 geprüft.", + "successTitle": "Erfolg", + "successValidated": "{count} E-Mails erfolgreich geprüft.", + "batchFailedTitle": "Massenprüfung fehlgeschlagen", + "batchFailedDescription": "Beim Verarbeiten der Massenprüfung ist ein Fehler aufgetreten.", + "noExportTitle": "Keine Daten zum Exportieren", + "noExportValidOnly": "Keine gültigen E-Mails zum Exportieren.", + "noExportAll": "Keine Ergebnisse zum Exportieren.", + "exportSuccessTitle": "Export erfolgreich", + "exportSuccessExcelAll": "{count} E-Mails nach Excel exportiert.", + "exportSuccessExcelValid": "{count} gültige E-Mails nach Excel exportiert.", + "exportSuccessCsvAll": "{count} E-Mails nach CSV exportiert.", + "exportSuccessCsvValid": "{count} gültige E-Mails nach CSV exportiert." + }, + "disposableAlert": { + "title": "Wegwerf-E-Mail erkannt", + "description": "Diese Adresse stammt von einem Wegwerf-Anbieter. Sie kann temporär oder unsicher sein." + }, + "score": { + "reliabilityTitle": "Zuverlässigkeitswert", + "scoreLabel": "Wert" + }, + "details": { + "sectionTitle": "Prüfdetails" + }, + "validation": { + "syntaxLabel": "Syntax gültig", + "syntaxTooltip": "Prüft, ob die Adresse dem richtigen Format entspricht (z. B. user@domain.com)", + "domainLabel": "Domain vorhanden", + "domainTooltip": "Prüft, ob die Domain in der Adresse existiert", + "mxLabel": "MX-Einträge gefunden", + "mxTooltip": "Prüft, ob für die Domain MX-Einträge zum Empfang von E-Mails existieren", + "mailboxLabel": "Postfach vorhanden", + "mailboxTooltip": "Prüft, ob das Postfach auf dem Mailserver existiert", + "disposableLabel": "Wegwerf-E-Mail", + "disposableTooltip": "Zeigt an, ob die Adresse zu einem temporären/Wegwerf-Dienst gehört (evtl. riskant)", + "roleBasedLabel": "Rollen-Adresse", + "roleBasedTooltip": "Kennzeichnet Adressen wie support@ oder info@ statt persönlicher Postfächer" + }, + "copy": { + "emailCopied": "E-Mail kopiert!", + "copyTooltip": "E-Mail kopieren" + }, + "bulk": { + "dropHere": "Datei hier ablegen", + "dragHere": "Datei per Drag & Drop hier ablegen", + "uploadHint": "Laden Sie eine .xlsx-, .xls- oder .csv-Datei mit einer Spalte „email“ hoch (bis 4000 Adressen).", + "filePrefix": "Datei:", + "selectFile": "Datei wählen", + "downloadTemplate": "Vorlage herunterladen" + }, + "bulkLoading": { + "title": "E-Mails werden geprüft...", + "batchProgress": "Batch {current} von {total}..." + }, + "stats": { + "totalProcessed": "Gesamt verarbeitet", + "valid": "Gültig", + "risky": "Riskant", + "invalid": "Ungültig" + }, + "results": { + "detailedTitle": "Detaillierte Ergebnisse", + "searchPlaceholder": "E-Mails suchen...", + "newVerification": "Neue Prüfung", + "exportAll": "Alles exportieren", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Gültige exportieren ({count})" + }, + "showingResults": "{shown} von {total} Ergebnissen", + "table": { + "email": "E-Mail", + "status": "Status", + "score": "Wert", + "checks": "Prüfungen" + }, + "noSearchResults": "Keine Treffer für „{query}“", + "checksTooltip": { + "syntax": "Syntax", + "domain": "Domain", + "mx": "MX", + "mailbox": "Postfach" + }, + "exportColumns": { + "email": "E-Mail", + "status": "Status", + "score": "Wert", + "syntax": "Syntax", + "domain": "Domain", + "mx": "MX", + "mailbox": "Postfach", + "disposable": "Wegwerf", + "roleBased": "Rollenadresse", + "valid": "Gültig", + "invalid": "Ungültig", + "yes": "Ja", + "no": "Nein" + }, + "sheets": { + "validEmails": "Gültige E-Mails", + "results": "Ergebnisse", + "template": "Vorlage" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Explorer", + "heroSubtitle": "Mit Ihren Datenbanken verbinden, Sammlungen verwalten, Abfragen ausführen und Daten visualisieren.", + "featureMultiTitle": "Mehrere Verbindungen", + "featureMultiDesc": "Mehrere Datenbankverbindungen gleichzeitig verwalten.", + "featureMongoTitle": "MongoDB-Unterstützung", + "featureMongoDesc": "Native MongoDB-Unterstützung mit erweiterten Filtern.", + "featureQueryTitle": "Intelligente Abfrage", + "featureQueryDesc": "Leistungsstarker Abfrage-Editor mit Syntaxhervorhebung.", + "connectCta": "Mit MongoDB verbinden", + "footerNote": "Derzeit {db}. Weitere Datenbanken folgen.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Sammlung wählen", + "selectCollectionDesc": "Wählen Sie in der Seitenleiste eine Sammlung, um Dokumente anzuzeigen.", + "dialogAddConnection": "Verbindung hinzufügen", + "toastConnectionAdded": "Verbindung hinzugefügt", + "toastFetchFailed": "Dokumente für {collection} konnten nicht geladen werden", + "toastDocDeleted": "Dokument gelöscht", + "deleteDocTitle": "Wirklich fortfahren?", + "deleteDocDescription": "Dies kann nicht rückgängig gemacht werden. Das Dokument wird dauerhaft aus der Sammlung entfernt.", + "closeAllTitle": "Alle Registerkarten schließen?", + "closeAllDescription": "Alle Registerkarten schließen? Ungespeicherte Änderungen in Abfragefiltern gehen verloren.", + "closeAllConfirm": "Alle schließen", + "cancel": "Abbrechen", + "delete": "Löschen" + }, + "connection": { + "titleEdit": "Verbindung bearbeiten", + "titleConnect": "Mit MongoDB verbinden", + "subtitleEdit": "Verbindungsdaten unten anpassen.", + "subtitleConnect": "MongoDB-Verbindungszeichenfolge eingeben, um zu starten.", + "labelName": "Name", + "placeholderName": "z. B. Produktions-DB", + "labelConnectionString": "Verbindungszeichenfolge", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://benutzer:passwort@host:port/datenbank", + "testConnection": "Verbindung testen", + "connecting": "Verbinde...", + "updateConnect": "Aktualisieren & verbinden", + "connect": "Verbinden", + "cancelEdit": "Bearbeitung abbrechen", + "savedConnections": "Gespeicherte Verbindungen", + "emptySaved": "Noch keine gespeicherten Verbindungen.", + "lastUsed": "Zuletzt genutzt {time}", + "toastTestOk": "Verbindung erfolgreich!", + "toastTestFail": "Verbindung fehlgeschlagen: {message}", + "toastUpdated": "Verbindung aktualisiert", + "confirmDelete": "Diese gespeicherte Verbindung wirklich löschen?", + "toastDeletedConn": "Verbindung gelöscht", + "toastDeleteFail": "Verbindung konnte nicht gelöscht werden", + "copyStringTitle": "Verbindungszeichenfolge kopieren", + "toastStringCopied": "Verbindungszeichenfolge kopiert!", + "editTitle": "Bearbeiten", + "deleteTitle": "Löschen" + }, + "sidebar": { + "explorer": "Explorer", + "add": "Hinzufügen", + "tooltipAdd": "Neue Verbindung", + "tooltipRefresh": "Verbindungen aktualisieren", + "searchPlaceholder": "Suchen...", + "loadingConnections": "Verbindungen werden geladen...", + "noConnectionsFound": "Keine Verbindungen gefunden", + "toastLoadFail": "Verbindungen konnten nicht geladen werden", + "toastConnectFail": "Verbindung zu {name} fehlgeschlagen", + "toastCollectionsFail": "Sammlungen für {name} konnten nicht geladen werden", + "toastRenamed": "Verbindung umbenannt", + "toastRenameFail": "Umbenennen fehlgeschlagen", + "confirmDeleteConnection": "Verbindung „{name}“ wirklich löschen?", + "toastDeleted": "Verbindung gelöscht", + "toastDeleteConnFail": "Verbindung konnte nicht gelöscht werden", + "confirmDropDb": "Datenbank „{name}“ wirklich löschen? Dies kann nicht rückgängig gemacht werden.", + "toastDbDropped": "Datenbank {name} gelöscht", + "confirmDropCollection": "Sammlung „{name}“ wirklich löschen? Dies kann nicht rückgängig gemacht werden.", + "toastCollectionDropped": "Sammlung {name} gelöscht", + "toastCollectionRenamed": "Sammlung umbenannt in {name}", + "toastDatabaseRenamed": "Datenbank umbenannt in {name}", + "connecting": "Verbinde...", + "connectionFailed": "Verbindung fehlgeschlagen", + "noDatabases": "Keine Datenbanken", + "loading": "Laden...", + "noCollections": "Keine Sammlungen", + "renameCollectionTitle": "Sammlung umbenennen", + "renameDatabaseTitle": "Datenbank umbenennen", + "placeholderNewCollection": "Neuer Sammlungsname", + "placeholderNewDatabase": "Neuer Datenbankname", + "rename": "Umbenennen", + "refresh": "Aktualisieren", + "copyDbName": "DB-Name kopieren", + "toastDbNameCopied": "Datenbankname in die Zwischenablage kopiert", + "dropDatabase": "Datenbank löschen", + "dropCollection": "Sammlung löschen", + "menuRename": "Umbenennen", + "menuCopyConnectionString": "Verbindungszeichenfolge kopieren", + "menuDeleteConnection": "Verbindung löschen", + "toastSidebarStringCopied": "Verbindungszeichenfolge kopiert!", + "cancel": "Abbrechen", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} Dok.", + "table": "Tabelle", + "json": "JSON", + "tree": "Baum", + "tooltipTable": "Tabellenansicht", + "tooltipJson": "JSON-Ansicht", + "tooltipTree": "Baumansicht", + "itemsPerPage": "Einträge pro Seite", + "perPage": "{n} / Seite", + "prevPage": "Vorherige Seite", + "nextPage": "Nächste Seite", + "expandAll": "Alle aufklappen", + "collapseAll": "Alle zuklappen", + "refreshData": "Daten aktualisieren", + "insert": "Einfügen", + "export": "Exportieren", + "actions": "Aktionen", + "invalidJson": "Ungültiger JSON-Inhalt", + "copiedClipboard": "In die Zwischenablage kopiert", + "docUpdated": "Dokument aktualisiert", + "docInserted": "Dokument eingefügt", + "invalidJsonShort": "Ungültiges JSON", + "docCopied": "Dokument in die Zwischenablage kopiert", + "openFullEditor": "Im vollen Editor öffnen", + "cellArray": "Array({length})", + "cellObject": "Objekt", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Keine Dokumente für diese Abfrage", + "emptyFilterHint": "Filter anpassen oder Abfrage leeren.", + "clearFilter": "Filter leeren", + "emptyCollectionTitle": "Diese Sammlung ist leer", + "emptyCollectionHint": "Fügen Sie ein Dokument ein, um zu starten.", + "insertDocument": "Dokument einfügen", + "formatJson": "JSON formatieren", + "compactJson": "JSON komprimieren", + "copyClipboard": "In Zwischenablage kopieren", + "copyJson": "JSON kopieren", + "duplicate": "Duplizieren", + "editDoc": "Dokument bearbeiten", + "deleteDoc": "Dokument löschen", + "documentLabel": "Dokument {n}", + "editDialogTitle": "Dokument bearbeiten", + "insertDialogTitle": "Dokument einfügen", + "viewValueTitle": "Wert anzeigen", + "saveChanges": "Änderungen speichern", + "close": "Schließen", + "cancel": "Abbrechen", + "insertButton": "Einfügen", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Filter aktiv", + "filterActiveTooltip": "Filter aktiv", + "closeAll": "Alle schließen" + }, + "query": { + "migrateHistory": "Abfrageverlauf in die Cloud migriert", + "saveHistoryFail": "Abfrageverlauf konnte nicht gespeichert werden", + "deleteQueryFail": "Abfrage konnte nicht gelöscht werden", + "invalidJsonQuery": "Ungültige JSON-Abfrage", + "placeholder": "z. B. { \"status\": \"active\" }", + "clearQuery": "Abfrage leeren", + "openAdvanced": "Erweiterten Editor öffnen", + "visualBuilder": "Visueller Abfrage-Editor", + "filterRules": "Filterregeln", + "addRule": "Regel hinzufügen", + "noFilters": "Keine Filter. Fügen Sie eine Regel hinzu.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "Feld", + "opPlaceholder": "Op", + "typePlaceholder": "Typ", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Not In", + "opExists": "Exists", + "typeAuto": "Auto", + "typeString": "Zeichenkette", + "typeNumber": "Zahl", + "typeBoolean": "Boolesch", + "typeDate": "Datum", + "typeObjectId": "ObjectId", + "true": "Wahr", + "false": "Falsch", + "valuePlaceholder": "Wert", + "valueListPlaceholder": "wert1, wert2", + "clearFilters": "Filter leeren", + "applyFilters": "Filter anwenden", + "queryHistory": "Abfrageverlauf", + "noSavedQueries": "Noch keine gespeicherten Abfragen", + "advancedEditorTitle": "Erweiterter Abfrage-Editor", + "templatesPlaceholder": "Vorlagen", + "templateEmptyAgg": "Leere Aggregation", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Join)", + "templateGroup": "$group (Anzahl/Summe)", + "format": "Formatieren", + "formattedJson": "Formatiertes JSON", + "runQuery": "Abfrage ausführen" + }, + "export": { + "selectFieldsError": "Bitte mindestens ein Feld zum Exportieren wählen", + "success": "Erfolgreich nach {filename} exportiert", + "fail": "Export fehlgeschlagen", + "title": "Daten exportieren", + "formatLabel": "Exportformat", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Felder wählen", + "selectAll": "Alle wählen", + "fieldsSelected": "{count, plural, one {# Feld gewählt} other {# Felder gewählt}}", + "cancel": "Abbrechen", + "export": "Exportieren", + "sheetName": "Daten" + }, + "jsonTree": { + "typeLabel": "Typ: {type}", + "itemSummary": "{count, plural, one {# Element} other {# Elemente}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Neue Anfrage", + "importedRequest": "Importierte Anfrage", + "untitledRequest": "Unbenannte Anfrage" + }, + "toasts": { + "curlImported": "cURL erfolgreich importiert", + "curlParseFailed": "cURL-Befehl konnte nicht gelesen werden", + "invalidJsonBody": "Ungültiger JSON-Body", + "requestFailed": "Anfrage fehlgeschlagen: {message}", + "curlPasted": "cURL eingefügt und erfolgreich gelesen", + "responseCopied": "Antwort in die Zwischenablage kopiert", + "codeCopied": "Code in die Zwischenablage kopiert", + "copyFailed": "In die Zwischenablage kopieren fehlgeschlagen" + }, + "layout": { + "collections": "Sammlungen", + "closeSidebar": "Seitenleiste schließen", + "openSidebar": "Seitenleiste öffnen", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Methode", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Senden...", + "send": "Senden", + "invalidJsonBodyHelp": "Senden nicht möglich: Der JSON-Body ist ungültig" + }, + "requestTabs": { + "params": "Parameter", + "headers": "Header", + "body": "Body", + "auth": "Auth", + "bodyType": "Body-Typ", + "selectType": "Typ wählen", + "bodyNone": "Keiner", + "bodyJson": "JSON", + "bodyText": "Text", + "noBodyHint": "Kein Body für diese Anfrage", + "switchToJson": "Auf JSON-Body wechseln", + "authType": "Auth-Typ", + "noAuth": "Keine Auth", + "bearerToken": "Bearer-Token", + "basicAuth": "Basic-Auth", + "apiKey": "API-Schlüssel", + "labelToken": "Token", + "placeholderBearerToken": "Bearer-Token", + "labelUsername": "Benutzername", + "placeholderUsername": "Benutzername", + "labelPassword": "Passwort", + "placeholderPassword": "Passwort", + "labelKey": "Schlüssel", + "placeholderApiKeyName": "Schlüsselname (z. B. X-Api-Key)", + "labelValue": "Wert", + "placeholderValue": "Wert", + "addTo": "Hinzufügen zu", + "header": "Header", + "queryParams": "Abfrageparameter", + "noAuthHint": "Diese Anfrage nutzt keine Authentifizierung." + }, + "responsePanel": { + "emptyTitle": "Bereit zum Ausprobieren?", + "emptyHint": "URL eingeben und Senden klicken, um die Antwort hier zu sehen.", + "responsePreviewAlt": "Antwortvorschau", + "previewUnavailable": "Vorschau für diesen Inhaltstyp nicht verfügbar", + "status": "Status", + "time": "Zeit", + "size": "Größe", + "copyBody": "Body kopieren", + "downloadResponse": "Antwort herunterladen", + "bodyTab": "Body", + "previewTab": "Vorschau", + "headersTab": "Header", + "binaryRawView": "Binärdaten können in der Rohansicht nicht angezeigt werden.", + "errorStatusLabel": "Fehler", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Neue Registerkarte" + }, + "collectionsSidebar": { + "title": "API-Ressourcen", + "newCollection": "Neue Sammlung", + "tabCollections": "Sammlungen", + "tabHistory": "Verlauf", + "noCollectionsTitle": "Keine Sammlungen", + "noCollectionsHint": "Legen Sie eine Sammlung an, um Anfragen zu ordnen.", + "createCollection": "Sammlung erstellen", + "newFolder": "Neuer Ordner", + "rename": "Umbenennen", + "delete": "Löschen", + "emptyCollection": "Leere Sammlung", + "recentRequests": "Letzte Anfragen", + "clearAll": "Alles leeren", + "noHistoryTitle": "Noch kein Verlauf", + "noHistoryHint": "Senden Sie eine Anfrage, um sie hier zu sehen.", + "dialogNewFolderTitle": "Neuer Ordner", + "dialogNewFolderDescription": "Neuen Ordner für Ihre Anfragen anlegen.", + "labelFolderName": "Ordnername", + "placeholderFolderName": "Mein Ordner", + "createFolder": "Ordner erstellen", + "dialogNewCollectionTitle": "Neue Sammlung", + "dialogNewCollectionDescription": "Neue Sammlung auf oberster Ebene anlegen.", + "labelCollectionName": "Sammlungsname", + "placeholderCollectionName": "Meine Sammlung", + "dialogRenameCollectionTitle": "Sammlung umbenennen", + "dialogRenameCollectionDescription": "Neuen Namen für die Sammlung eingeben.", + "placeholderRenameCollection": "Sammlungsname", + "renameAction": "Umbenennen", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Neuer Ordner", + "delete": "Löschen", + "emptyFolder": "Leerer Ordner" + }, + "environmentManager": { + "noEnvironment": "Keine Umgebung", + "manageDialogTitle": "Umgebungen verwalten", + "placeholderNewEnv": "Neue Umgebung", + "variablesHeading": "Variablen: {name}", + "addVariable": "Variable hinzufügen", + "columnVariable": "Variable", + "columnValue": "Wert", + "placeholderKey": "Schlüssel", + "placeholderValue": "Wert", + "selectEnvToEdit": "Umgebung zum Bearbeiten wählen" + }, + "codeGenerator": { + "triggerTitle": "Code generieren", + "dialogTitle": "Code generieren", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Kopieren", + "errorGenerating": "Fehler beim Generieren des Codes" + }, + "keyValueEditor": { + "columnKey": "Schlüssel", + "columnValue": "Wert", + "placeholderKey": "Schlüssel", + "placeholderValue": "Wert", + "addItem": "Eintrag hinzufügen" + }, + "importCurl": { + "trigger": "cURL importieren", + "dialogTitle": "cURL importieren", + "dialogDescription": "cURL-Befehl unten einfügen, um ihn als neue Anfrage zu importieren.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Importieren" + }, + "saveRequest": { + "trigger": "Speichern", + "dialogTitle": "Anfrage speichern", + "dialogDescription": "Diese Anfrage in einem Sammlungsordner speichern.", + "labelName": "Anfragename", + "placeholderName": "Meine Anfrage", + "labelFolder": "Ordner", + "placeholderFolder": "Ordner wählen", + "save": "Speichern" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Bearbeitungsmodus", + "viewOnlyMode": "Nur-Ansicht", + "insertComponent": "Komponente einfügen", + "addUnorderedList": "Aufzählungsliste", + "addOrderedList": "Nummerierte Liste", + "addTable": "Tabelle einfügen" + }, + "addBlock": "Block hinzufügen", + "media": { + "popoverTitle": "Medien hinzufügen", + "singleImage": "Einzelbild", + "singleImageDesc": "Ein Bild hochladen", + "multipleImages": "Mehrere Bilder", + "multipleImagesDesc": "Bildraster hochladen", + "video": "Video", + "videoDesc": "Videodatei hochladen", + "uploadingAlt": "Wird hochgeladen...", + "uploadFailedAlt": "Upload fehlgeschlagen" + }, + "export": { + "fabAria": "Code exportieren", + "title": "Code exportieren", + "description": "HTML- oder JSON-Ausgabe des Editors in die Zwischenablage kopieren", + "preview": "Vorschau", + "html": "HTML", + "json": "JSON", + "previewOptions": "Vorschauoptionen", + "enhanceSpaces": "Abstände verbessern", + "livePreview": "Live-Vorschau des gerenderten HTML", + "htmlTailwind": "HTML mit Tailwind-CSS-Klassen", + "editorJson": "Editorzustand als JSON", + "copyHtml": "HTML kopieren", + "copyJson": "JSON kopieren", + "copied": "Kopiert!" + }, + "template": { + "fabTitle": "Vorlage wechseln", + "title": "Vorlage wählen", + "description": "Schnellstart mit vorgefertigten Layouts und Inhalten", + "catAll": "Alle Vorlagen", + "catProductivity": "Produktivität", + "catCreative": "Kreativ", + "catBusiness": "Business", + "catPersonal": "Persönlich", + "noTemplates": "Keine Vorlagen in dieser Kategorie", + "confirmTitle": "Vorhandenen Inhalt ersetzen?", + "confirmDescription": "Der Editor enthält bereits Inhalt. Diese Vorlage ersetzt alles.", + "cannotUndo": "Dies kann nicht rückgängig gemacht werden.", + "cancel": "Abbrechen", + "replaceContent": "Inhalt ersetzen" + }, + "command": { + "searchPlaceholder": "Befehle suchen...", + "noResults": "Keine Befehle gefunden.", + "turnInto": "Umwandeln in", + "h1": { + "label": "Überschrift 1", + "description": "Große Abschnittsüberschrift", + "keywords": "h1,überschrift,titel,groß" + }, + "h2": { + "label": "Überschrift 2", + "description": "Mittlere Abschnittsüberschrift", + "keywords": "h2,überschrift,untertitel" + }, + "h3": { + "label": "Überschrift 3", + "description": "Kleine Abschnittsüberschrift", + "keywords": "h3,überschrift" + }, + "h4": { + "label": "Überschrift 4", + "description": "Sehr kleine Überschrift", + "keywords": "h4,überschrift" + }, + "h5": { + "label": "Überschrift 5", + "description": "Kleinere Überschrift", + "keywords": "h5,überschrift" + }, + "h6": { + "label": "Überschrift 6", + "description": "Kleinste Überschrift", + "keywords": "h6,überschrift" + }, + "p": { + "label": "Absatz", + "description": "Normaler Textabsatz", + "keywords": "p,absatz,text" + }, + "code": { + "label": "Codeblock", + "description": "Codeausschnitt", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "Zitat", + "description": "Blockzitat", + "keywords": "zitat,blockquote" + }, + "li": { + "label": "Aufzählungsliste", + "description": "Listeneintrag mit Aufzählungszeichen", + "keywords": "liste,aufzählung,ul,li" + }, + "ol": { + "label": "Nummerierte Liste", + "description": "Nummerierter Listeneintrag", + "keywords": "liste,nummeriert,ol,li" + }, + "img": { + "label": "Bild", + "description": "Bild hochladen oder einbetten", + "keywords": "bild,img,foto,upload" + }, + "video": { + "label": "Video", + "description": "Video hochladen oder einbetten", + "keywords": "video,mp4,upload" + }, + "table": { + "label": "Tabelle", + "description": "Tabelle erstellen", + "keywords": "tabelle,gitter,zeilen,spalten" + } + }, + "toasts": { + "tableImported": "Tabelle importiert", + "tableImportedDesc": "Markdown-Tabelle wurde importiert", + "mediaPasted": "Medien eingefügt", + "pasteMixed": "{imageCount} Bild(er) und {videoCount} Video(s) eingefügt.", + "pasteImages": "{count} Bild(er) eingefügt.", + "pasteVideos": "{count} Video(s) eingefügt.", + "pasteFailed": "Einfügen fehlgeschlagen", + "unexpectedError": "Ein unerwarteter Fehler ist aufgetreten", + "uploadFailed": "Upload fehlgeschlagen" + }, + "blockBackground": { + "menuItem": "Hintergrundfarbe ändern", + "dialogTitle": "Block-Hintergrundfarbe", + "dialogDescription": "Hintergrundfarbe für diesen Block wählen", + "tabPreset": "Vordefinierte Farben", + "tabCustom": "Eigene Farbe", + "sectionHeading": "Hintergrundfarben", + "apply": "Anwenden", + "colors": { + "none": "Keine", + "red": "Rot", + "orange": "Orange", + "yellow": "Gelb", + "green": "Grün", + "blue": "Blau", + "indigo": "Indigo", + "purple": "Violett", + "pink": "Rosa", + "teal": "Petrol", + "cyan": "Cyan", + "gray": "Grau" + } + }, + "tableDialog": { + "title": "Tabelle erstellen", + "description": "Neue Tabelle anlegen oder aus Markdown importieren", + "markdownCheckbox": "Ich habe eine Markdown-Tabelle", + "pasteMarkdownLabel": "Markdown-Tabelle einfügen", + "markdownPlaceholder": "| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "Markdown-Tabelle oben einfügen. Kopf- und Trennzeile erforderlich.", + "rows": "Zeilen", + "columns": "Spalten", + "maxDimensions": "Maximum: 20 Zeilen × 10 Spalten", + "cancel": "Abbrechen", + "importTable": "Tabelle importieren", + "createTable": "Tabelle erstellen" + }, + "tableParse": { + "errors": { + "minRows": "Tabelle braucht mindestens Kopf- und Trennzeile", + "pipeFormat": "Zeilen müssen mit | beginnen und enden", + "headerMinCol": "Kopfzeile braucht mindestens eine Spalte", + "separatorRow": "Zweite Zeile muss Trennzeile sein (z. B. |---|---|)", + "rowColumnMismatch": "Zeile {row}: {got} Spalten, erwartet {expected}", + "parseFailed": "Markdown-Tabelle konnte nicht gelesen werden" + } + }, + "link": { + "editLink": "Link bearbeiten", + "addLink": "Link hinzufügen", + "selectedText": "Markierter Text: „{text}“", + "linkUrl": "Link-URL", + "urlPlaceholder": "https://example.com", + "update": "Aktualisieren", + "addLinkButton": "Link hinzufügen", + "toastApplied": "Link angewendet", + "toastLinkedTo": "Verknüpft mit: {url}", + "toastRemoved": "Link entfernt", + "toastRemovedDesc": "Link aus der Auswahl entfernt" + }, + "customClass": { + "devMode": "Entwicklermodus", + "toggleDevModeAria": "Entwicklermodus umschalten", + "searchPlaceholderDev": "Klassen suchen... (z. B. text, bg, flex)", + "searchPlaceholderUser": "Stile suchen... (z. B. rot, fett, schatten)", + "appliesTitle": "Wendet an: {className}", + "noMatches": "Keine Klassen für „{query}“", + "toastClassReplaced": "Klasse ersetzt", + "toastClassReplacedDesc": "„{replaced}“ durch „{className}“ ersetzt", + "toastCustomClassApplied": "Benutzerdefinierte Klasse angewendet", + "toastAppliedClass": "Angewendete Klasse: {className}" + }, + "insertComponents": { + "title": "Komponente einfügen", + "description": "Komponente zum Einfügen wählen", + "noComponents": "Keine Komponenten verfügbar", + "categories": { + "media": "Medien", + "layout": "Layout", + "interactive": "Interaktiv", + "decoration": "Dekoration" + }, + "freeImage": { + "name": "Frei positionierbares Bild", + "description": "Bild, das frei positioniert und in der Größe geändert werden kann" + } + } + }, + "JsonFormatter": { + "title": "JSON-Formatierer", + "description": "JSON im Text- und Baum-Modus formatieren, validieren und bearbeiten.", + "documentNameText": "Textdokument {n}", + "documentNameTree": "Baumdokument {n}", + "newDocument": "Neu", + "copy": "Kopieren", + "save": "Speichern", + "load": "Laden", + "saving": "Wird gespeichert...", + "leftDocNameLabel": "Name des linken Dokuments", + "rightDocNameLabel": "Name des rechten Dokuments", + "toastCopiedText": "Textbereich kopiert", + "toastCopiedTree": "Baumansicht kopiert", + "toastCopyFailed": "JSON konnte nicht kopiert werden", + "toastLoginRequired": "Bitte melden Sie sich an, um Dokumente zu speichern", + "toastSaved": "Dokument gespeichert", + "toastSaveFailed": "Dokument konnte nicht gespeichert werden", + "format": "Formatieren", + "compact": "Komprimieren", + "sortKeys": "Schlüssel sortieren", + "repair": "Reparieren", + "query": "Abfrage", + "toastInvalidJson": "Ungültiges JSON", + "toastAlreadyValid": "JSON ist bereits gültig", + "toastRepaired": "JSON repariert", + "toastRepairFailed": "JSON konnte nicht repariert werden" + }, + "JwtDecoder": { + "title": "JWT-Decoder", + "subtitle": "Header und Payload ansehen. Die Ablaufzeit nutzt den exp-Claim. Es wird nichts an einen Server gesendet; die Signatur wird nicht verifiziert.", + "tokenLabel": "Token", + "clear": "Leeren", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopieren", + "expiryLabel": "Ablauf (exp)", + "signaturePresent": "Signatur vorhanden (nicht verifiziert)", + "noExp": "Kein exp-Claim in der Payload.", + "status": { + "expired": "Abgelaufen", + "valid": "Gültig" + }, + "fields": { + "unix": "Unix", + "local": "Lokal", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Ausgestellt am (iat)", + "notBefore": "Nicht vor (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "JWT zum Dekodieren einfügen.", + "segments": "Ein JWT braucht mindestens Header und Payload (zwei Segmente).", + "base64": "Header oder Payload konnte nicht base64url-dekodiert werden." + }, + "relative": { + "future": "in {n} {unit}", + "past": "vor {n} {unit}" + }, + "units": { + "second": "{count, plural, one {Sekunde} other {Sekunden}}", + "minute": "{count, plural, one {Minute} other {Minuten}}", + "hour": "{count, plural, one {Stunde} other {Stunden}}", + "day": "{count, plural, one {Tag} other {Tagen}}" + }, + "tabs": { + "decode": "Dekodieren", + "sign": "Generator / Signierer" + }, + "signer": { + "algLabel": "Algorithmus (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload-JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header-JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Secret (HS*)", + "secretPlaceholder": "Füge dein HMAC-Secret ein…", + "privateKeyLabel": "Privater Schlüssel PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Der Algorithmus \"none\" erstellt ein unsigniertes Token. Das ist unsicher und sollte nur zum Testen verwendet werden.", + "sign": "Signieren", + "verify": "Signatur prüfen", + "outputLabel": "Token (kompaktes JWS)", + "outputPlaceholder": "Klicke auf „Signieren“…", + "copyToken": "Token kopieren", + "copied": "Kopiert", + "verifyKeyLabel": "Prüfschlüssel (optional)", + "verifyKeyPlaceholder": "Für RS*: den öffentlichen Schlüssel (SPKI) PEM einfügen. Für HS*: leer lassen, um das Secret wiederzuverwenden.", + "verifyKeyHint": "Wenn leer, verwendet die Prüfung den Signierschlüssel (nützlich für HS*).", + "verifyStatus": { + "valid": "Signatur gültig", + "invalid": "Signatur ungültig" + }, + "securityNote": "Alles läuft lokal im Browser; Schlüssel und Tokens werden nicht hochgeladen." + }, + "signErrors": { + "invalidJson": "Header und Payload müssen gültiges JSON sein.", + "unsupportedAlg": "Nicht unterstützter Algorithmus.", + "missingKey": "Für diesen Algorithmus ist ein Signierschlüssel erforderlich.", + "cryptoFailed": "Signieren fehlgeschlagen. Prüfe das Schlüsselformat und versuche es erneut." + }, + "verifyErrors": { + "empty": "Gib ein JWT zur Prüfung ein.", + "segments": "Ein JWT muss genau 3 Segmente haben, um eine Signatur zu prüfen.", + "unsupportedAlg": "Nicht unterstützter oder fehlender alg im Header.", + "missingKey": "Für diesen Algorithmus ist ein Prüfschlüssel erforderlich.", + "base64": "JWT-Header oder Signatur konnten nicht dekodiert werden.", + "cryptoFailed": "Prüfung fehlgeschlagen. Prüfe das Schlüsselformat und versuche es erneut." + } + }, + "CertificatePemDecoder": { + "title": "Zertifikat- / PEM-Decoder", + "subtitle": "Fügen Sie ein X.509-Zertifikat (-----BEGIN CERTIFICATE-----) oder eine CSR (-----BEGIN CERTIFICATE REQUEST-----) ein. Antragsteller, Aussteller, Daten und SANs werden lokal ausgewertet; es wird nichts hochgeladen.", + "pemLabel": "PEM-Eingabe", + "clear": "Leeren", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Wird analysiert…", + "copyHex": "Fingerabdruck kopieren (Hex ohne Doppelpunkte)", + "errors": { + "empty": "Fügen Sie ein PEM-Zertifikat oder eine CSR zum Dekodieren ein.", + "noBlock": "Kein PEM-Block für Zertifikat oder CSR gefunden.", + "parseFailed": "Diese PEM-Daten konnten nicht verarbeitet werden." + }, + "block": { + "title": "PEM-Block {index}", + "certificate": "X.509-Zertifikat", + "csr": "Zertifikatsignaturanforderung" + }, + "fields": { + "subject": "Antragsteller", + "issuer": "Aussteller", + "notBefore": "Nicht gültig vor", + "notAfter": "Nicht gültig nach", + "serial": "Seriennummer (Hex)", + "sha256": "SHA-256-Fingerabdruck", + "sans": "Alternative Antragstellernamen", + "sansNone": "Keine SAN-Erweiterung vorhanden.", + "signatureAlgorithm": "Signaturalgorithmus", + "csrIssuerNote": "CSRs enthalten keinen Aussteller (der steht auf dem ausgestellten Zertifikat).", + "csrValidityNote": "CSRs enthalten keine Gültigkeitsdaten (die stehen auf dem ausgestellten Zertifikat)." + }, + "sanType": { + "dns": "DNS", + "dn": "Verzeichnisname", + "email": "E-Mail", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Registrierte ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL-Formatierer", + "subtitle": "SQL für MySQL, PostgreSQL oder SQLite formatieren. Läuft im Browser; es wird nichts hochgeladen.", + "dialectLabel": "Dialekt", + "keywordsLabel": "Schlüsselwörter", + "indentLabel": "Einzug", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "GROSS", + "lower": "klein", + "preserve": "Beibehalten" + }, + "spaces": "{n} Leerzeichen", + "format": "Formatieren", + "clear": "Leeren", + "counter": "{current} / {max} Zeichen", + "inputPanel": "Eingabe", + "formattedPanel": "Formatiert", + "copy": "Kopieren", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klicke auf „Formatieren“…", + "errors": { + "tooLong": "Eingabe ist auf {max} Zeichen begrenzt.", + "couldNotFormat": "SQL konnte nicht formatiert werden." + } + }, + "YamlFormatter": { + "title": "YAML-Formatierer", + "subtitle": "YAML im Browser formatieren, validieren und konvertieren. Es wird nichts hochgeladen.", + "outputLabel": "Ausgabe", + "outputModes": { + "yaml": "YAML (formatiert)", + "json": "JSON" + }, + "indentLabel": "Einzug", + "spaces": "{n} Leerzeichen", + "format": "Formatieren", + "clear": "Löschen", + "copy": "Kopieren", + "counter": "{current} / {max} Zeichen", + "inputPanel": "Eingabe", + "outputPanel": "Ausgabe", + "errors": { + "tooLong": "Die Eingabe ist auf {max} Zeichen begrenzt.", + "couldNotFormat": "Dieses YAML konnte nicht verarbeitet werden." + } + }, + "GraphqlFormatter": { + "title": "GraphQL-Formatierer", + "subtitle": "Formatiert GraphQL-Abfragen, Mutationen und Subscriptions im Browser. Es wird nichts hochgeladen.", + "tabs": { + "format": "Formatieren", + "build": "Abfrage-Editor" + }, + "outputModeLabel": "Ausgabe", + "outputModes": { + "pretty": "Lesbare Ausgabe", + "minify": "Minifizieren" + }, + "format": "Formatieren", + "clear": "Leeren", + "counter": "{current} / {max} Zeichen", + "inputPanel": "Eingabe", + "outputPanel": "Ausgabe", + "copy": "Kopieren", + "builderHint": "Baue ein Gerüst und übernimm es in den Editor. Variablendefinitionen in GraphQL-Syntax (z. B. $id: ID!), Klammern sind optional.", + "builder": { + "operation": "Operation", + "operationName": "Operationsname", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Variablendefinitionen", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Selektionssatz", + "apply": "In die Eingabe übernehmen", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Die Eingabe ist auf {max} Zeichen begrenzt.", + "couldNotFormat": "Dieses GraphQL konnte nicht geparst oder ausgegeben werden.", + "emptySelection": "Bitte zuerst einen Selektionssatz einfügen." + } + }, + "RegexTester": { + "title": "Regex-Tester", + "subtitle": "JavaScript-ähnliche Regex live gegen deinen Text. Treffer werden markiert; nichts verlässt deinen Browser.", + "patternLabel": "Muster", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i ohne Beachtung der Groß-/Kleinschreibung", + "m": "m mehrzeilig", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(leer)", + "noFlags": "—", + "matches": "{count, plural, one {Treffer} other {Treffer}}", + "testTextLabel": "Testtext", + "counter": "{current} / {max}", + "clear": "Leeren", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Text ist auf {max} Zeichen begrenzt.", + "invalid": "Ungültiger regulärer Ausdruck.", + "runaway": "Nach {max} Treffern gestoppt (möglicherweise ausuferndes Muster)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Zeitstempel-Konverter", + "subtitle": "Unix-Sekunden (≤10 Ziffern) oder Millisekunden (längere Ganzzahlen), ISO-8601 oder Strings, die dein Browser parsen kann. Läuft lokal.", + "inputLabel": "Eingabe", + "placeholder": "1744200000 oder 2025-04-09T12:00:00Z", + "now": "Jetzt", + "copy": "Kopieren", + "hint": "Wert einfügen oder Jetzt klicken.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativ", + "human": "Lesbar" + }, + "fields": { + "seconds": "Sekunden", + "milliseconds": "Millisekunden", + "utc": "UTC", + "local": "Lokal", + "distance": "Abstand" + }, + "errors": { + "empty": "Gib einen Unix-Zeitstempel, ISO-String oder Datums-Text ein.", + "invalidNumber": "Ungültiger numerischer Zeitstempel.", + "outOfRange": "Außerhalb des Bereichs für JavaScript Date.", + "couldNotParse": "Konnte nicht als Unix, ISO-8601 oder Browser-Datum geparst werden." + } + }, + "HttpStatusCodes": { + "title": "HTTP-Statuscodes", + "subtitle": "Suche und durchsuche HTTP-Statuscodes für Antworten (1xx5xx) mit Kurzbeschreibungen und RFC-Referenzen. Läuft lokal im Browser.", + "header": { + "title": "HTTP-Statuscodes", + "description": "Durchsuchbare Referenz für HTTP-Antwortcodes mit Beschreibungen und RFC-Referenzen." + }, + "searchPlaceholder": "Suche: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Klasse filtern…", + "classFilterAll": "Alle Klassen", + "resultsCount": "{count} von {total}", + "table": { + "code": "Code", + "phrase": "Bezeichnung", + "description": "Description", + "copy": "Kopieren" + }, + "noResults": "Keine Treffer für „{query}“.", + "specPrefix": "Spez.:", + "copied": "Code kopiert" + }, + "CronBuilder": { + "title": "Cron-Ausdruck-Builder", + "subtitle": "Standard-Cron mit 5 Feldern: minute hour day month weekday. Der Parser nutzt seconds=0 intern. Optionaler 6-Feld-Ausdruck (sec min hour dom mon dow) funktioniert im Editor.", + "tabs": { + "builder": "Visueller Builder", + "expression": "Ausdruck" + }, + "presetsLabel": "Vorlagen", + "fieldsLabel": "Felder", + "rawExpressionLabel": "Roh-Ausdruck", + "expressionHint": "Feld verlassen, um zu parsen und den Builder zu synchronisieren. {count} {count, plural, one {Token} other {Tokens}}.", + "currentExpressionLabel": "Aktueller Ausdruck", + "copy": "Kopieren", + "quickPick": "Schnellauswahl", + "emptyExpression": "—", + "plainLanguageLabel": "Klartext", + "nextRunsLabel": "Nächste Ausführungen (lokale Zeit)", + "fields": { + "minute": "Minute", + "hour": "Stunde", + "dayOfMonth": "Tag des Monats", + "month": "Monat", + "dayOfWeek": "Wochentag" + }, + "presets": { + "everyMinute": "Jede Minute", + "every5Minutes": "Alle 5 Minuten", + "every15Minutes": "Alle 15 Minuten", + "everyHour": "Jede Stunde (um :00)", + "every6Hours": "Alle 6 Stunden", + "dailyMidnight": "Täglich um Mitternacht", + "daily0900": "Täglich um 09:00", + "weekdays0900": "Werktags um 09:00", + "weeklySunday": "Wöchentlich (Sonntag 00:00)", + "monthlyFirst": "Monatlich (1. 00:00)", + "yearlyJan1": "Jährlich (1. Jan 00:00)" + }, + "picks": { + "minute": { + "every": "jede Minute" + }, + "hour": { + "every": "jede Stunde" + }, + "dom": { + "any": "beliebiger Tag", + "last": "letzter Tag" + }, + "month": { + "every": "jeder Monat" + }, + "dow": { + "any": "beliebiger Wochentag", + "weekdays": "Werktage", + "weekend": "Wochenende" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mär", + "apr": "Apr", + "may": "Mai", + "jun": "Jun", + "jul": "Jul", + "aug": "Aug", + "sep": "Sep", + "oct": "Okt", + "nov": "Nov", + "dec": "Dez" + }, + "weekdays": { + "sun": "So", + "mon": "Mo", + "tue": "Di", + "wed": "Mi", + "thu": "Do", + "fri": "Fr", + "sat": "Sa" + }, + "errors": { + "fieldCount": "Erwartet: 5 Felder (minute hour day month weekday) oder 6 inkl. Sekunden.", + "invalid": "Ungültiger Cron-Ausdruck." + } + }, + "Base64": { + "title": "Base64 Encoder / Decoder", + "subtitle": "Text nach Base64 kodieren oder Base64-Strings dekodieren", + "upload": "Hochladen", + "clear": "Leeren", + "modes": { + "encode": "Kodieren", + "decode": "Dekodieren" + }, + "swapTitle": "Eingabe und Ausgabe tauschen", + "panels": { + "plainText": "Klartext", + "base64Input": "Base64-Eingabe", + "base64Output": "Base64-Ausgabe", + "decodedText": "Dekodierter Text" + }, + "placeholders": { + "encode": "Text eingeben, um nach Base64 zu kodieren…", + "decode": "Base64-String einfügen, um zu dekodieren…" + }, + "dropHere": "Datei hier ablegen", + "charCount": "{count} Zeichen", + "download": { + "title": "Ausgabe herunterladen", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "In die Zwischenablage kopieren", + "outputPlaceholder": "Ausgabe erscheint hier…", + "errors": { + "invalidBase64": "Ungültiger Base64-String. Bitte Eingabe prüfen.", + "encodeFailed": "Eingabe konnte nicht kodiert werden." + } + }, + "UrlEncode": { + "title": "URL Encoder / Decoder", + "subtitle": "Prozentkodieren/-dekodieren mit encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Hochladen", + "clear": "Leeren", + "modes": { + "encode": "Kodieren", + "decode": "Dekodieren" + }, + "swapTitle": "Eingabe und Ausgabe tauschen", + "panels": { + "plainText": "Klartext", + "encodedInput": "Kodierte Eingabe", + "encodedOutput": "Kodierte Ausgabe", + "decodedText": "Dekodierter Text" + }, + "placeholders": { + "encode": "Text zum Prozentkodieren eingeben (z. B. Query-Parameter)…", + "decode": "Prozentkodierten Text zum Dekodieren einfügen…" + }, + "dropHere": "Datei hier ablegen", + "charCount": "{count} Zeichen", + "download": { + "title": "Ausgabe herunterladen", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "In die Zwischenablage kopieren", + "outputPlaceholder": "Ausgabe erscheint hier…", + "errors": { + "invalidEncoding": "Ungültige Prozentkodierung. Prüfe unvollständige %‑Paare oder Escape-Sequenzen.", + "encodeFailed": "Konnte nicht kodieren (ungültiges Unicode in der Eingabe)." + } + }, + "UrlParser": { + "title": "URL-Parser", + "subtitle": "Eine URL in Protokoll, Host, Pfad, Query-Parameter und Hash (Fragment) aufteilen.", + "upload": "Hochladen", + "clear": "Leeren", + "panels": { + "input": "URL-Eingabe", + "output": "Ergebnis" + }, + "placeholders": { + "input": "URL einfügen (z. B. https://example.com/path?foo=bar#section) oder Host/Pfad (z. B. example.com/path)…" + }, + "dropHere": "Datei hier ablegen", + "charCount": "{count} Zeichen", + "outputPlaceholder": "Die geparsten Komponenten erscheinen hier…", + "fields": { + "protocol": "Protokoll", + "host": "Host", + "path": "Pfad", + "hash": "Hash", + "queryParams": "Query-Parameter" + }, + "paramCount": "{count} Parameter", + "noParams": "Keine Query-Parameter.", + "paramKey": "Schlüssel", + "paramValue": "Wert", + "copyTitle": "In die Zwischenablage kopieren", + "copyParamTitle": "Als key=value kopieren", + "download": { + "title": "JSON herunterladen", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Ungültige URL. Bitte Hostname (example.com) angeben und Zeichen prüfen." + } + }, + "UuidGenerator": { + "title": "UUID-/ULID-Generator", + "subtitle": "Erzeuge RFC‑9562‑UUIDs oder ULIDs im Browser. Bulk-Export bis zu {max} pro Durchlauf.", + "format": "Format", + "kindHints": { + "ulid": "Sortierbarer, URL-sicherer 26‑Zeichen‑Bezeichner", + "uuid4": "Zufällig (RFC 9562)", + "uuid7": "Zeitgeordnet, Unix‑ms + Zufall", + "uuid1": "MAC-/zeitbasiert (RFC 9562)", + "uuid6": "Neu geordnete gregorianische Zeit", + "uuid3": "MD5-Hash aus Name + Namespace", + "uuid5": "SHA‑1-Hash aus Name + Namespace", + "nil": "Nil-UUID (nur Nullen, RFC 9562)" + }, + "countLabel": "Anzahl (1–{max})", + "nameLabel": "Name (Eingabe-String)", + "namePlaceholder": "z. B. my.app/resource", + "bulkHint": "Bei Bulk-Läufen wird #0, #1, … angehängt, damit jede ID eindeutig ist (v3/v5 sind deterministisch).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Benutzerdefinierte UUID" + }, + "customNamespaceLabel": "Namespace-UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Generieren", + "output": "Ausgabe", + "lines": "{count} Zeilen", + "download": { + "title": ".txt herunterladen", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Alles kopieren", + "uppercaseToggle": "Ausgabe in Großbuchstaben", + "hyphensToggle": "Bindestriche anzeigen", + "copyJsonTitle": "Als JSON-Array kopieren", + "outputPlaceholder": "Klicke auf „Generieren“…", + "errors": { + "invalidCustomNamespace": "Der benutzerdefinierte Namespace muss eine gültige UUID sein.", + "missingName": "Für UUID v3 und v5 ist ein Name erforderlich.", + "unknown": "IDs konnten nicht erzeugt werden." + } + }, + "SecretApiKeyGenerator": { + "title": "Generator für Geheimnis / API-Schlüssel", + "subtitle": "Erzeuge kryptografisch zufällige Zeichenketten mit konfigurierbarer Länge und Zeichensatz. Massenexport bis zu {max} pro Lauf. Ergänzt den UUID-/ULID-Generator.", + "alphabetLabel": "Alphabet", + "alphabetHint": "Doppelte Zeichen werden entfernt. Jedes Zeichen wird gleichverteilt mit crypto.getRandomValues und Rejection Sampling gezogen (ohne Modulo-Bias).", + "alphabetCount": "{count} eindeutige Zeichen", + "presetsLabel": "Voreinstellungen", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alphanumerisch", + "hex": "Hex (Kleinbuchstaben)", + "asciiPrintable": "ASCII druckbar" + }, + "lengthLabel": "Länge pro Geheimnis (1–{maxLen})", + "countLabel": "Anzahl (1–{maxBulk})", + "generate": "Generieren", + "output": "Ausgabe", + "lines": "{count} Zeilen", + "download": { + "title": ".txt herunterladen", + "filename": "geheimnisse.txt" + }, + "copyTitle": "Alles kopieren", + "outputPlaceholder": "Klicke auf „Generieren“…", + "entropy": "{bits} Bit Entropie", + "strength": { + "weak": "Schwach", + "fair": "Mittel", + "strong": "Stark" + }, + "excludeAmbiguous": "Mehrdeutige Zeichen ausschließen", + "excludeAmbiguousHint": "Entfernt {chars} — leicht zu verwechselnde oder shell-feindliche Zeichen.", + "prefixLabel": "Schlüsselpräfix (optional)", + "errors": { + "emptyAlphabet": "Füge mindestens ein Zeichen zum Alphabet hinzu.", + "alphabetTooLong": "Nach Entfernen von Duplikaten darf das Alphabet höchstens {maxAlpha} Zeichen haben.", + "lengthOutOfRange": "Die Länge muss zwischen 1 und {maxLen} liegen.", + "countOutOfRange": "Die Anzahl muss zwischen 1 und {maxBulk} liegen.", + "unknown": "Geheimnisse konnten nicht erzeugt werden." + } + }, + "LoremIpsum": { + "title": "Lorem-Ipsum-Generator", + "subtitle": "Generiere lateinischen Platzhaltertext für deine Layouts und Mockups.", + "paragraphs": "Absätze", + "sentences": "Sätze", + "words": "Wörter", + "count": "Anzahl", + "generate": "Generieren", + "copy": "Kopieren", + "startWith": "Mit 'Lorem ipsum...' beginnen", + "outputPlaceholder": "Ausgabe erscheint hier...", + "outputType": "Ausgabetyp", + "bulletList": "Aufzählungsliste", + "htmlMarkup": "HTML-Markup", + "htmlMarkupHint": "Ausgabe in Absätze einschließen oder eine Liste für den Listenmodus verwenden", + "output": "Ausgabe", + "download": "Herunterladen" + }, + "ColorPicker": { + "title": "Farbwähler und Konverter", + "subtitle": "Konvertiere HEX, RGB und HSL; erstelle harmonische Paletten. Die Verarbeitung erfolgt komplett im Browser.", + "nativePicker": "Nativer Farbwähler", + "chooseColor": "Farbe wählen", + "eyeDropper": "Pipette", + "invalidHex": "Ungültige HEX-Farbe.", + "cssValues": "CSS-Werte", + "palettes": "Paletten", + "copied": "Kopiert", + "clickToCopy": "Zum Kopieren klicken", + "copyAllHex": "Alle HEX kopieren", + "copy": "Kopieren", + "copyColor": "{color} kopieren", + "paletteShades": "Abstufungen", + "paletteComplementary": "Komplementär", + "paletteTriadic": "Triadisch", + "paletteAnalogous": "Analog", + "paletteSplit": "Geteiltes Kompl.", + "paletteSquare": "Quadratisch" + }, + "ContrastChecker": { + "title": "Kontrastprüfung", + "subtitle": "Berechnet das WCAG-2.1-Kontrastverhältnis zwischen Vorder- und Hintergrundfarbe. Prüft AA und AAA für normalen und großen Text. Alles läuft in Ihrem Browser.", + "foreground": "Text / Vordergrund", + "background": "Hintergrund", + "hexPlaceholder": "#RRGGBB", + "swap": "Farben tauschen", + "chooseForeground": "Vordergrundfarbe wählen", + "chooseBackground": "Hintergrundfarbe wählen", + "eyeDropper": "Pipette", + "ratio": "Kontrastverhältnis", + "copyRatio": "Verhältnis kopieren", + "copied": "Kopiert", + "previewHeading": "Vorschau", + "previewBody": "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich. Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.", + "previewCaption": "Beispieltext in normaler Größe.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — normaler Text (4,5:1)", + "levelAALarge": "AA — großer Text (3:1)", + "levelAAANormal": "AAA — normaler Text (7:1)", + "levelAAALarge": "AAA — großer Text (4,5:1)", + "pass": "Bestanden", + "fail": "Nicht bestanden", + "invalidHex": "Ungültige HEX-Farbe.", + "largeTextHint": "Großer Text: 18 pt+ regulär oder 14 pt+ fett (Definitionen nach WCAG)." + }, + "DiffChecker": { + "title": "Diff-Prüfer", + "subtitle": "Zeile-für-Zeile-Vergleich zweier Texte nebeneinander. Alles bleibt in Ihrem Browser.", + "rowsCount": "{count} Zeilen", + "truncatedWarning": "Zeige die ersten {max} Zeilen.", + "limitExceeded": "Jede Seite ist auf {max} Zeichen begrenzt.", + "clearBoth": "Beide leeren", + "original": "Original", + "modified": "Geändert", + "originalPlaceholder": "Originaltext einfügen…", + "modifiedPlaceholder": "Geänderten Text einfügen…", + "comparisonLabel": "Nebeneinander-Vergleich", + "emptyState": "Text in beiden Feldern eingeben, um zu vergleichen." + }, + "JsonSchemaGenerator": { + "title": "JSON-Schema-Generator", + "subtitle": "Fügen Sie gültiges JSON ein, um ein JSON-Schema oder typisierte Modelle zu erzeugen. Alles läuft im Browser.", + "languageLabel": "Ausgabesprache", + "inputLabel": "JSON-Eingabe", + "inputPlaceholder": "JSON hier einfügen…", + "outputLabel": "Generierte Ausgabe", + "outputPlaceholder": "Schema oder generierte Typen erscheinen hier…", + "parseError": "Ungültiges JSON", + "clear": "Leeren", + "copy": "Kopieren", + "copied": "In die Zwischenablage kopiert", + "copyFailed": "Kopieren fehlgeschlagen", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-Schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Editor wird geladen…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Bild zu Base64", + "subtitle": "Legen Sie ein Bild ab und erhalten Sie einen Daten-URI oder eine unformatierte Base64-Zeichenfolge. Alles bleibt in Ihrem Browser.", + "upload": "Bild hochladen", + "clear": "Klar", + "modes": { + "dataUri": "Daten-URI", + "rawString": "Rohes Base64" + }, + "panels": { + "imageInput": "Bildeingabe", + "output": "Base64-Ausgabe" + }, + "dropHere": "Bild hier einfügen", + "charCount": "{count} Zeichen", + "placeholders": { + "uploadImage": "Ziehen Sie ein Bild per Drag-and-Drop oder klicken Sie zum Starten auf „Hochladen“." + }, + "fileTooLarge": "Die Datei ist zu groß. Bei der Verarbeitung kann es sein, dass Ihr Browser einfriert.", + "copyTitle": "In die Zwischenablage kopieren", + "download": { + "title": "Text herunterladen", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Bildkomprimierung", + "subtitle": "JPEG-, PNG- oder WebP-Ausgabe lokal mit Qualitätssteuerung verkleinern — nichts wird hochgeladen.", + "upload": "Bild hochladen", + "clear": "Zurücksetzen", + "dropHere": "Bild hier ablegen", + "placeholders": { + "uploadImage": "Bild per Drag-and-drop ablegen oder klicken, um eine Datei zu wählen." + }, + "fileTooLarge": "Datei ist zu groß (max. {maxMb} MB).", + "unsupportedFormat": "Dieser Dateityp wird nicht unterstützt. Verwenden Sie JPEG, PNG, WebP, GIF, AVIF oder BMP.", + "compressFailed": "Bild konnte nicht komprimiert werden. Andere Datei oder ein anderes Format versuchen.", + "panels": { + "original": "Original", + "compressed": "Vorschau (komprimiert)" + }, + "outputFormat": "Ausgabeformat", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Qualität", + "qualityPercent": "{value} %", + "qualityPngHint": "PNG ist im Browser verlustfrei; der Qualitätsregler gilt nur für JPEG und WebP.", + "sizes": { + "original": "Original: {size}", + "compressed": "Komprimiert: {size}", + "saved": "{percent} % kleiner" + }, + "largerWarning": "Ausgabe ist größer als die Eingabe — anderes Format oder Qualität versuchen.", + "download": "Herunterladen", + "downloadFilename": "komprimiertes-bild", + "processing": "Komprimiere…", + "emptyCompressed": "Die komprimierte Vorschau erscheint hier." + }, + "CssGradientBuilder": { + "title": "CSS-Verlaufsgenerator", + "subtitle": "Visueller CSS-Verlaufsersteller mit Winkelsteuerung, Farbstopps und CSS-Kopie mit einem Klick.", + "preview": "Verlaufsvorschau", + "settings": "Einstellungen", + "type": "Typ", + "linear": "Linear", + "radial": "Radial", + "angle": "Winkel", + "stops": "Farbstopps", + "color": "Farbe", + "position": "Position (%)", + "addStop": "Stopp hinzufügen", + "deleteStop": "Stopp löschen", + "output": "CSS-Ausgabe", + "copy": "CSS kopieren", + "copied": "Kopiert!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore-Generator", + "subtitle": "Generieren Sie sofort zusammengeführte .gitignore-Dateien für einen bestimmten Tech-Stack oder eine bestimmte Kombination.", + "searchPlaceholder": "Suchstacks (z. B. Node, Python, macOS) ...", + "noResults": "Keine Stapel gefunden.", + "selectedStacks": "Ausgewählte Stapel", + "output": "Vorschau der Ausgabe", + "copy": "In die Zwischenablage kopieren", + "copied": "Kopiert!", + "download": "Laden Sie .gitignore herunter", + "downloading": "Herunterladen...", + "errorFetching": "Die Top-Liste konnte nicht abgerufen werden.", + "errorGenerating": "Fehler beim Generieren von .gitignore aus ausgewählten Stapeln.", + "generating": "Generieren...", + "emptyState": "Wählen Sie einen oder mehrere Tech-Stacks aus, um Ihr .gitignore zu generieren." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV- oder Excel-Datei hochladen, JSON bearbeiten, dann als CSV oder XLSX herunterladen. Nur Tabellendaten; Excel-Datumswerte werden im JSON als ISO-Zeitstempel ausgegeben.", + "dropTitle": "CSV- oder Excel-Datei hier ablegen", + "dropHint": "Für .xlsx / .xls wird das erste Arbeitsblatt verwendet. CSV benötigt eine Kopfzeile.", + "chooseFile": "Datei wählen", + "jsonLabel": "JSON (Array von Objekten)", + "jsonPlaceholder": "JSON-Array einfügen oder CSV / Excel importieren…", + "loadSample": "Beispiel laden", + "actions": { + "format": "JSON formatieren", + "clear": "Leeren", + "copy": "Kopieren", + "copied": "Kopiert", + "downloadCsv": "CSV herunterladen", + "downloadXlsx": "Excel herunterladen" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Daten", + "toastLoadedCsv": "CSV in den Editor geladen", + "toastLoadedExcel": "Excel-Datei in den Editor geladen", + "toastFormatted": "JSON formatiert", + "toastExportedCsv": "CSV heruntergeladen", + "toastExportedXlsx": "Excel-Datei heruntergeladen", + "toastCopied": "In die Zwischenablage kopiert", + "footnote": "Alles läuft im Browser. JSON muss ein nicht leeres Array von Objekten sein. Verschachtelte Werte werden in CSV-Zellen als JSON-String gespeichert.", + "errors": { + "unsupportedFile": "Bitte .csv-, .xlsx- oder .xls-Datei verwenden.", + "parseFailed": "Diese Datei konnte nicht gelesen werden.", + "invalidJson": "Ungültiges JSON", + "exportFailed": "Export fehlgeschlagen", + "copyFailed": "Kopieren fehlgeschlagen" + } + }, + "SnippetManager": { + "title": "Code-Snippet-Manager", + "subtitle": "Speichern Sie Snippets im Browser oder melden Sie sich an, um sie mit Ihrem Konto zu synchronisieren. Wechseln Sie zwischen Ansicht und Bearbeitung, kopieren Sie mit einem Klick, wählen Sie eine Sprache oder Auto-Erkennung und formatieren Sie JSON, SQL oder von Monaco unterstützten Code.", + "snippets": "Snippets", + "newSnippet": "Neues Snippet", + "newSnippetTitle": "Neues Snippet", + "defaultSnippetTitle": "Willkommen", + "defaultSnippetCode": "// Willkommen bei Code-Snippets\n// Testen Sie Ansicht vs. Bearbeiten, Kopieren, Formatieren und Auto-Sprache\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Snippets durchsuchen…", + "searchAria": "Snippets durchsuchen", + "emptySearch": "Keine Snippets entsprechen Ihrer Suche.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Snippet-Name", + "language": "Sprache", + "detectedHint": "Hervorhebung als {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Ansicht", + "modeEdit": "Bearbeiten", + "copy": "Kopieren", + "copied": "Kopiert", + "format": "Formatieren", + "cancel": "Abbrechen", + "deleteTitle": "Snippet löschen?", + "deleteDescription": "„{title}“ wird aus diesem Browser entfernt.", + "deleteConfirm": "Löschen", + "editorAria": "Snippet-Code-Editor", + "editorLoading": "Editor wird geladen…", + "toastCopied": "In die Zwischenablage kopiert", + "toastCopyFailed": "Kopieren fehlgeschlagen", + "toastNothingToCopy": "Nichts zu kopieren", + "toastFormatted": "Formatiert", + "toastFormatFailed": "Formatierung fehlgeschlagen", + "toastFormatJsonInvalid": "Ungültiges JSON — zuerst Syntax beheben", + "toastFormatUnsupported": "Kein Formatierer für diese Sprache — JSON oder SQL versuchen oder Bearbeiten", + "toastDeleted": "Snippet gelöscht", + "toastLoadFailed": "Snippets konnten nicht vom Server geladen werden.", + "toastSyncFailed": "Synchronisation mit dem Server fehlgeschlagen.", + "languages": { + "auto": "Automatisch erkennen", + "plaintext": "Nur Text", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL-Client", + "dialogTitle": "SQL-Verbindungen", + "mobileSheetTitle": "SQL-Explorer", + "newTabLabel": "Neuer Tab", + "vaultLockedTitle": "Tresor gesperrt", + "vaultLockedDesc": "Entsperren Sie Ihren Tresor, um auf gespeicherte Verbindungen zuzugreifen.", + "heroTitle": "SQL-Client", + "heroDesc": "Verbinden Sie sich mit PostgreSQL, MySQL oder MariaDB. Anmeldedaten werden im Browser verschlüsselt.", + "heroAddConnection": "Verbindung hinzufügen", + "selectTableTitle": "Tabelle auswählen oder Tab öffnen", + "selectTableDesc": "Klicken Sie auf eine Tabelle in der Seitenleiste oder drücken Sie ⌘↩.", + "toastLoadFail": "Verbindungen konnten nicht geladen werden." + }, + "connection": { + "titleNew": "Neue SQL-Verbindung", + "titleEdit": "Verbindung bearbeiten", + "labelDbType": "Datenbanktyp", + "labelName": "Verbindungsname", + "placeholderName": "Meine Produktionsdatenbank", + "labelHostPort": "Host & Port", + "labelDatabase": "Datenbank", + "placeholderDatabase": "meindb", + "labelUsername": "Benutzername", + "labelPassword": "Passwort", + "labelSsl": "SSL / TLS aktivieren", + "sslDesc": "Verbindung zum Server verschlüsseln", + "securityNote": "Anmeldedaten werden AES-256 im Browser verschlüsselt, bevor sie gespeichert werden", + "btnTest": "Verbindung testen", + "btnSave": "Speichern & Verbinden", + "btnUpdate": "Aktualisieren", + "btnCancelEdit": "Bearbeitung abbrechen", + "savedConnections": "Gespeicherte Verbindungen", + "emptySaved": "Gespeicherte Verbindungen erscheinen hier", + "confirmDelete": "Diese Verbindung löschen?", + "toastFillFields": "Bitte Host und Datenbank vor dem Test ausfüllen.", + "toastConnectOk": "Verbunden! {version}", + "toastConnectFail": "Verbindung fehlgeschlagen: {message}", + "toastUnlockVault": "Bitte entsperren Sie zuerst Ihren Tresor.", + "toastFieldsRequired": "Host und Datenbank sind erforderlich.", + "toastUpdated": "Verbindung aktualisiert.", + "toastSaved": "Verbindung gespeichert.", + "toastSaveFail": "Speichern fehlgeschlagen: {message}", + "toastDeleted": "Verbindung gelöscht.", + "toastDeleteFail": "Verbindung konnte nicht gelöscht werden." + }, + "sidebar": { + "title": "SQL-Explorer", + "tooltipAddConnection": "Verbindung hinzufügen", + "tooltipRefreshSchema": "Schema aktualisieren", + "noConnections": "Noch keine Verbindungen gespeichert", + "toastSchemaFail": "Schema konnte nicht geladen werden: {message}" + }, + "editor": { + "btnRun": "Ausführen", + "executing": "Abfrage wird ausgeführt…", + "emptyTitle": "Abfrage ausführen, um Ergebnisse zu sehen", + "emptyHint": "⌘↩ drücken oder Ausführen klicken", + "toastNoConnection": "Keine aktive Verbindung." + }, + "results": { + "filterPlaceholder": "Ergebnisse filtern…", + "rowCount": "{count} {count, plural, one {Zeile} other {Zeilen}}", + "rowCountFiltered": "{filtered} / {total} Zeilen", + "exportCsv": "CSV", + "noRows": "Keine Zeilen zurückgegeben" + } + }, + "QrCodeGenerator": { + "title": "QR-Code-Generator", + "subtitle": "Wandle beliebigen Text oder URLs in einen PNG-QR-Code um. Die Darstellung erfolgt lokal im Browser.", + "contentLabel": "Inhalt", + "contentPlaceholder": "https://beispiel.de, Klartext, WLAN-String, vCard …", + "styleLabel": "Stil", + "dotTypeLabel": "Punktform", + "dotTypeSquare": "Quadrat", + "dotTypeDots": "Punkte", + "dotTypeRounded": "Abgerundet", + "dotTypeExtraRounded": "Extra abgerundet", + "dotTypeClassy": "Elegant", + "dotTypeClassyRounded": "Elegant abgerundet", + "cornerSquareLabel": "Eckquadrate", + "cornerSquareDot": "Punkt", + "cornerSquareSquare": "Quadrat", + "cornerSquareExtraRounded": "Extra abgerundet", + "cornerDotLabel": "Eckpunkte", + "cornerDotDot": "Punkt", + "cornerDotSquare": "Quadrat", + "eccLabel": "Fehlerkorrektur", + "eccL": "Niedrig (~7 %)", + "eccM": "Mittel (~15 %)", + "eccQ": "Quartil (~25 %)", + "eccH": "Hoch (~30 %)", + "marginLabel": "Ruhezone (Module)", + "widthLabel": "Bildbreite", + "widthEffective": "Wird mit {px} px gerendert", + "darkColor": "Dunkle Module", + "lightColor": "Helle Module", + "resetColors": "Farben zurücksetzen", + "logoLabel": "Mittellogo", + "logoMarginLabel": "Abstand", + "uploadLogo": "Bild hochladen", + "removeLogo": "Logo entfernen", + "downloadPng": "PNG herunterladen", + "downloadSvg": "SVG herunterladen", + "copyImage": "Bild kopieren", + "copied": "Kopiert", + "clear": "Leeren", + "previewAlt": "QR-Code-Vorschau", + "emptyHint": "Inhalt eingeben oder einfügen, um die Vorschau zu sehen.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "QR-Code konnte nicht erzeugt werden. Kürzeren Text oder niedrigere Fehlerkorrektur versuchen.", + "copyUnsupported": "Ihr Browser unterstützt das Kopieren von Bildern in die Zwischenablage nicht.", + "copyFailed": "Kopieren in die Zwischenablage fehlgeschlagen." + } + }, + "IpSubnetCalculator": { + "title": "IP-/Subnetz-Rechner", + "subtitle": "IPv4- und IPv6-CIDR-Details im Browser—ohne Serveranfragen.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Netzwerk", + "placeholderIpv4": "10.0.0.0/24 oder 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 oder 2001:db8::1/64", + "hintIpv4": "CIDR (z. B. /24) oder IPv4-Adresse plus dezimale Netzmaske.", + "hintIpv6": "Präfixlänge angeben (z. B. /64). Ohne Eintrag wird /128 angenommen.", + "copyValue": "Wert kopieren", + "rows": { + "cidr": "CIDR", + "network": "Netzadresse", + "netmask": "Subnetzmaske", + "wildcard": "Wildcard-Maske", + "broadcast": "Broadcast", + "firstHost": "Erster Host", + "lastHost": "Letzter Host", + "totalAddresses": "Adressen gesamt", + "usableHosts": "Nutzbare Hosts", + "prefixLength": "Präfixlänge (Bits)", + "networkIpv6": "Netzadresse", + "lastIpv6": "Letzte Adresse im Bereich", + "totalAddressesIpv6": "Adressen im Subnetz" + }, + "usableNote": { + "pointToPoint": "/31-Punkt-zu-Punkt-Verbindungen nutzen beide Adressen als Hosts (RFC 3021).", + "singleHost": "/32 ist eine Einzelhost-Adresse." + }, + "ipv6RangeHint": { + "normal": "Es wird der vollständige mathematische Bereich gezeigt; Routerregeln können abweichen.", + "single": "/128 ist eine einzelne Adresse.", + "full": "/0 umfasst den gesamten IPv6-Raum." + }, + "errors": { + "invalidIpv4": "Ungültiges IPv4-Netz. CIDR (10.0.0.0/24) oder Adresse + Maske verwenden.", + "invalidIpv6": "Ungültiges IPv6-Netz. Adresse/Präfix angeben, z. B. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Hash-Generator", + "subtitle": "MD5-, SHA-1- und SHA-2-Prüfsummen für Text oder Dateien; bcrypt für Passwörter (Texteingabe). Alles läuft lokal im Browser.", + "algorithmLabel": "Algorithmus", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Text", + "modeFile": "Datei", + "inputLabelText": "Eingabe", + "placeholderText": "Text zum Hashen eingeben oder einfügen…", + "chooseFile": "Datei wählen", + "clearFile": "Leeren", + "fileHint": "Dateien werden nur im Speicher gelesen; max. {maxMb} MB. Kein Upload.", + "outputLabel": "Digest (hex)", + "copyHash": "Digest kopieren", + "copied": "Kopiert", + "byteCount": "{count, plural, one {# Byte} other {# Bytes}}", + "charCount": "{count, plural, one {# Zeichen} other {# Zeichen}}", + "emptyHint": "Text eingeben oder eine Datei wählen.", + "securityNote": "MD5 und SHA-1 sind für neue sicherheitskritische Zwecke nicht empfohlen; SHA-256 oder SHA-512 bevorzugen.", + "errors": { + "fileTooLarge": "Datei ist größer als {maxMb} MB.", + "fileReadFailed": "Datei konnte nicht gelesen werden." + }, + "bcryptRoundsLabel": "Kostenfaktor (Runden)", + "bcryptRoundsHint": "Höhere Werte sind langsamer und robuster gegen Brute-Force. Üblich ist 10.", + "fileTabDisabledTitle": "bcrypt hasht nur Passwörter aus Text, keine beliebigen Dateien.", + "placeholderBcrypt": "Passwort zum Hashen eingeben oder einfügen…", + "outputLabelBcrypt": "bcrypt-Hash", + "copyBcrypt": "Hash kopieren", + "emptyHintBcrypt": "Geben Sie ein Passwort ein, um einen bcrypt-Hash zu berechnen.", + "securityNoteBcrypt": "Jede bcrypt-Ausgabe enthält ein zufälliges Salz; dasselbe Passwort ergibt jedes Mal eine andere Zeichenkette. Das ist erwartet. In Produktion hohe Kosten verwenden.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Verschlüsselungs-Spielplatz", + "subtitle": "AES-GCM im Browser: mit Passphrase oder Rohschlüssel verschlüsseln, als JSON-Bündel speichern und lokal wieder entschlüsseln. Lehrreich und gelegentlich praktisch — nichts wird hochgeladen.", + "tabEncrypt": "Verschlüsseln", + "tabDecrypt": "Entschlüsseln", + "keyModeLabel": "Schlüsselmaterial", + "modePassphrase": "Passphrase (PBKDF2 + AES-GCM)", + "modeRawKey": "Roh-AES-Schlüssel (Hex oder Base64)", + "aesBitsLabel": "AES-Schlüssellänge", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Kodierung des Rohschlüssels", + "encodingAuto": "Automatisch (Hex, wenn es wie Hex aussieht, sonst Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Rohschlüssel", + "rawKeyPlaceholder": "32-Byte-Schlüssel als 64 Hex-Zeichen oder Base64 für AES-256 (Länge hängt von der gewählten AES-Größe ab).", + "rawKeyPlaceholderDecrypt": "Nur nötig, wenn im Bündel keyDerivation \"none\" steht.", + "passphraseLabel": "Passphrase", + "passphrasePlaceholder": "Passphrase — nur im Browser zur Schlüsselableitung.", + "passphrasePlaceholderDecrypt": "Nur nötig, wenn das Bündel PBKDF2-SHA256 verwendet.", + "pbkdf2IterLabel": "PBKDF2-Iterationen", + "pbkdf2Hint": "Begrenzt auf 10.000 bis 2.000.000. Standard entspricht gängigen Tresor-Einstellungen.", + "plaintextLabel": "Klartext", + "plaintextPlaceholder": "Nachricht zum Verschlüsseln eingeben oder einfügen …", + "bundleInputLabel": "Verschlüsseltes Bündel (JSON)", + "bundlePlaceholder": "JSON aus „Verschlüsseln“ einfügen (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Verschlüsseltes Bündel", + "plaintextOutputLabel": "Klartext", + "encryptButton": "Verschlüsseln", + "decryptButton": "Entschlüsseln", + "copyBundle": "JSON kopieren", + "copyPlaintext": "Klartext kopieren", + "copied": "Kopiert", + "emptyBundleHint": "Nach dem Verschlüsseln erscheint hier das JSON.", + "emptyDecryptHint": "Entschlüsselter Text erscheint hier.", + "decryptKeyHint": "Rohschlüssel nutzen, wenn keyDerivation \"none\" lautet; Passphrase, wenn PBKDF2-SHA256. Das Bündel enthält Salt, Iterationen, IV und AES-Länge.", + "securityNote": "Schlüssel und Klartext verlassen Ihren Browser nicht. Für produktive Geheimnisse lieber spezialisierte Werkzeuge.", + "errors": { + "emptyKey": "Geben Sie einen Rohschlüssel passend zur AES-Größe ein.", + "emptyPassphrase": "Geben Sie eine Passphrase ein.", + "badKeyMaterial": "Schlüssel konnte weder als Hex noch als Base64 gelesen werden.", + "wrongKeyLength": "Schlüssellänge passt nicht zur gewählten AES-Größe (16 / 24 / 32 Byte).", + "invalidJson": "Kein gültiges JSON.", + "invalidBundle": "Dieses JSON ist kein unterstütztes Verschlüsselungsbündel.", + "decryptFailed": "Entschlüsselung fehlgeschlagen — falsches Geheimnis, beschädigtes Bündel oder falsche Parameter.", + "encryptFailed": "Verschlüsselung fehlgeschlagen. Prüfen Sie Web Crypto und die Eingaben." + } + }, + "HmacGenerator": { + "title": "HMAC-Generator", + "subtitle": "Zum Signieren von Webhooks und Testen von API-Integrationen. HMAC-SHA1/256/384/512 als Hex oder Base64—lokal im Browser berechnet.", + "secretLabel": "Geheimer Schlüssel", + "messageLabel": "Nachricht / Nutzdaten", + "placeholderSecret": "Signing-Secret einfügen…", + "placeholderMessage": "Rohdaten oder Zeichenkette zum Verifizieren einfügen (oft exakte Webhook-Body-Bytes).", + "algorithmLabel": "HMAC-Algorithmus", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Ausgabeformat", + "formatHex": "Hex (Kleinbuchstaben)", + "formatBase64": "Base64", + "outputLabel": "Signatur", + "copyOutput": "Signatur kopieren", + "copied": "Kopiert", + "byteCountHint": "{count, plural, one {# Byte} other {# Bytes}} (UTF-8)", + "emptyHint": "Geben Sie einen geheimen Schlüssel ein, um HMAC zu berechnen.", + "securityNote": "Teilen Sie Ihr Signing-Secret niemals. Diese Seite lädt keine Daten hoch—alles läuft im Browser.", + "errors": { + "cryptoFailed": "HMAC konnte nicht berechnet werden. Prüfen Sie Web Crypto und versuchen Sie es erneut.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP- / 2FA-Codegenerator", + "subtitle": "Authenticator-Geheimnis (Base32) einfügen und den aktuellen sechsstelligen RFC-6238-Code (SHA-1, 30 s) erhalten. Läuft lokal im Browser—nützlich zum Testen von Anmeldung oder MFA.", + "secretLabel": "Geheimnis (Base32)", + "placeholderSecret": "Geheimen Schlüssel einfügen (Buchstaben A–Z und Ziffern 2–7, wie bei der Einrichtung der Authenticator-App)…", + "codeLabel": "Aktueller Code", + "copyCode": "Code kopieren", + "copied": "Kopiert", + "nextIn": "Erneuert in {seconds} s", + "standardHint": "30-Sekunden-Zeitschritt, 6 Ziffern, HMAC-SHA-1 (kompatibel mit Google Authenticator).", + "digitsLabel": "Ziffern", + "periodLabel": "Zeitraum", + "algorithmLabel": "Algorithmus", + "periodSeconds": "{seconds} s", + "emptyHint": "Geheimnis einfügen, um einen sechsstelligen Code zu erzeugen.", + "invalidHint": "Korrigieren Sie das Geheimnis, um den Code zu sehen.", + "securityNote": "Behandeln Sie das wie ein Passwort. Das Geheimnis verlässt nie Ihren Browser; es wird nichts hochgeladen.", + "importLabel": "Aus otpauth://-URI importieren", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importieren", + "qrSectionTitle": "Bereitstellungs-QR-Code", + "qrHint": "Scannen Sie mit einer Authenticator-App (Google Authenticator, 1Password, Aegis…), um dieses Geheimnis hinzuzufügen. Der QR-Code kodiert das Geheimnis — behandeln Sie ihn wie ein Passwort.", + "accountLabel": "Konto", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Aussteller (optional)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Geben Sie ein gültiges Geheimnis ein, um einen QR-Code zu generieren.", + "errors": { + "invalidSecret": "Das ist kein gültiges Base32-Geheimnis.", + "cryptoFailed": "TOTP konnte nicht berechnet werden. Prüfen Sie Web Crypto und versuchen Sie es erneut.", + "invalidUri": "Das ist keine gültige otpauth:// TOTP-URI.", + "unsupportedUri": "Diese URI verwendet Werte, die das Tool nicht unterstützt (Ziffern müssen 6 oder 8 sein; Zeitraum 15, 30 oder 60 s)." + } + }, + "MarkdownPreview": { + "title": "Markdown-Vorschau & HTML", + "subtitle": "Live-Markdown-Renderer mit HTML-Export und HTML → Markdown-Konvertierung. Alles läuft in Ihrem Browser.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown-Eingabe", + "htmlInputLabel": "HTML-Eingabe", + "previewLabel": "Vorschau", + "htmlOutputLabel": "HTML-Quelle", + "mdOutputLabel": "Markdown-Ausgabe", + "inputPlaceholder": "# Hallo\\n\\nGeben Sie hier Ihr **Markdown** ein…", + "htmlInputPlaceholder": "Fügen Sie hier Ihr HTML ein…", + "copyHtml": "HTML kopieren", + "copyMarkdown": "Markdown kopieren", + "exportHtml": "HTML exportieren", + "copied": "Kopiert", + "charCount": "{count, plural, one {# Zeichen} other {# Zeichen}}", + "emptyHint": "Beginnen Sie mit der Eingabe von Markdown, um die Live-Vorschau und HTML-Ausgabe zu sehen.", + "emptyHintHtmlToMd": "Fügen Sie HTML ein, um es in Markdown zu konvertieren.", + "securityNote": "Alles läuft lokal in Ihrem Browser. Es werden keine Daten hochgeladen." + }, + "NumberBaseConverter": { + "title": "Zahlenbasen-Konverter", + "subtitle": "Ganzzahlen zwischen Basen 2–36 umrechnen. Optionale Präfixe 0x / 0b / 0o. Alles lokal im Browser.", + "inputBaseLabel": "Von Basis", + "outputBaseLabel": "Zu Basis", + "inputLabel": "Eingabezahl", + "placeholderInput": "Geben Sie eine Ganzzahl in der gewählten Eingabebasis ein…", + "outputLabel": "Ergebnis", + "copyOutput": "Ergebnis kopieren", + "copied": "Kopiert", + "swap": "Basen tauschen", + "byteCountHint": "{count, plural, one {# Byte} other {# Bytes}} (UTF-8-Eingabelänge)", + "emptyHint": "Geben Sie eine Zahl zum Umrechnen ein.", + "note": "Ziffern über 9 sind Buchstaben A–Z (Groß-/Kleinschreibung egal). Leerzeichen und Unterstriche werden ignoriert.", + "errors": { + "invalidNumber": "Ungültiger Wert für die gewählte Eingabebasis." + } + }, + "FormatConverter": { + "title": "Format-Konverter", + "subtitle": "Konvertieren zwischen JSON, YAML, TOML und XML in jeder Richtung. Alle 12 Kombinationen. Alles läuft in Ihrem Browser.", + "fromLabel": "Von", + "toLabel": "Nach", + "swap": "Tauschen", + "swapTitle": "Formate tauschen und Ausgabe als Eingabe verwenden", + "copy": "Kopieren", + "copied": "Kopiert", + "charCount": "{count, plural, one {# Zeichen} other {# Zeichen}}", + "emptyHint": "Fügen Sie Ihre Eingabe links ein oder tippen Sie, um die konvertierte Ausgabe zu sehen.", + "localNote": "Alles läuft lokal in Ihrem Browser. Es wird nichts hochgeladen." + }, + "MimeTypeLookup": { + "title": "MIME-Typ nachschlagen", + "subtitle": "MIME-Typ (Medientyp) für eine Dateiendung oder einen vollständigen Dateinamen nachschlagen. Alles läuft lokal in Ihrem Browser.", + "inputLabel": "Dateiendung oder Dateiname", + "placeholder": "z. B. .png, package.json, foto.jpeg", + "hint": "Tipp: Fügen Sie entweder eine Endung (mit oder ohne Punkt) oder einen vollständigen Dateinamen ein.", + "clear": "Leeren", + "exampleFilename": "Beispiel-Dateiname", + "exampleExtension": "Beispiel-Endung", + "resultLabel": "Ergebnis", + "normalizedLabel": "Normalisierte Eingabe:", + "mimeTypeLabel": "MIME-Typ", + "contentTypeLabel": "Content-Type-Header", + "charsetLabel": "Zeichensatz", + "copyMimeType": "MIME-Typ kopieren", + "copy": "Kopieren", + "copied": "Kopiert", + "emptyHint": "Geben Sie eine Endung oder einen Dateinamen ein, um den MIME-Typ nachzuschlagen.", + "notFound": "Kein MIME-Typ für diese Eingabe gefunden.", + "localNote": "Alles läuft lokal in Ihrem Browser. Es wird nichts hochgeladen." + }, + "UserAgentParser": { + "title": "User-Agent-Parser", + "subtitle": "Fügen Sie eine User-Agent (UA)-Zeichenkette ein und erhalten Sie eine Aufschlüsselung nach Browser, OS, Engine, Gerät und CPU. Alles läuft lokal in Ihrem Browser.", + "inputLabel": "User-Agent-Zeichenkette", + "placeholder": "Fügen Sie Ihren User-Agent hier ein…", + "hint": "Tipp: Sie können einen UA aus Request-Headern, Logs oder den Browser-Devtools kopieren.", + "clear": "Leeren", + "exampleDesktop": "Desktop-Beispiel verwenden", + "exampleMobile": "Mobil-Beispiel verwenden", + "resultLabel": "Aufschlüsselung", + "browserLabel": "Browser", + "osLabel": "Betriebssystem", + "engineLabel": "Engine", + "deviceTypeLabel": "Gerätetyp", + "deviceVendorLabel": "Hersteller", + "deviceModelLabel": "Modell", + "cpuArchLabel": "CPU-Architektur", + "copyJson": "JSON kopieren", + "copy": "Kopieren", + "copied": "Kopiert", + "emptyHint": "Fügen Sie eine User-Agent-Zeichenkette ein, um die Aufschlüsselung zu sehen.", + "localNote": "Alles läuft lokal in Ihrem Browser. Es wird nichts hochgeladen." + }, + "MockDataGenerator": { + "title": "Mock-Datengenerator", + "subtitle": "Spalten definieren, Datentypen wählen und Fixtures für Seeds, Postman oder Tests exportieren. Bis zu {max} Zeilen, alles im Browser — nichts wird hochgeladen.", + "mockarooLink": "Workflow wie bei Mockaroo", + "rowsLabel": "Zeilen", + "rowsHint": "Maximal {max} Zeilen pro Lauf, damit der Tab reaktionsfähig bleibt.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "SQL-INSERT", + "xml": "XML" + }, + "tableNameLabel": "SQL-Tabellenname", + "presetsLabel": "Vorlagen", + "presetPlaceholder": "Vorlage laden…", + "presetDefault": "Einstieg (Benutzer)", + "presetUsers": "Benutzer & Rollen", + "presetProducts": "E-Commerce-SKUs", + "presetApiLog": "HTTP-Anfrageprotokoll", + "generate": "Erzeugen", + "warningDuplicateNames": "Doppelte Spaltennamen: spätere Spalten überschreiben frühere in JSON-Objekten. Bitte umbenennen.", + "schemaHeading": "Schema", + "addField": "Spalte hinzufügen", + "removeField": "Spalte entfernen", + "duplicateField": "Spalte duplizieren", + "colName": "Name", + "colType": "Typ", + "colOptions": "Optionen", + "colBlank": "Leer %", + "optMin": "min", + "optMax": "max", + "optDecimals": "Dez", + "optDateFrom": "von Datum", + "optDateTo": "bis Datum", + "optValues": "a, b, c", + "optStep": "Schritt", + "optRowBase": "Basis", + "output": "Ausgabe", + "outputPlaceholder": "Auf „Erzeugen“ klicken, um Daten zu erzeugen…", + "copy": "Kopieren", + "download": "Herunterladen", + "copied": "Kopiert", + "groups": { + "personal": "Persönliches", + "location": "Standort", + "internetApi": "Internet & API", + "numbersIds": "Zahlen & IDs", + "dateTime": "Datum & Uhrzeit", + "textDesign": "Text & Gestaltung", + "custom": "Benutzerdefiniert" + }, + "fieldTypes": { + "first_name": "Vorname", + "last_name": "Nachname", + "full_name": "Vollständiger Name", + "email": "Email", + "username": "Benutzername", + "password": "Passwort", + "phone": "Telefon", + "company": "Unternehmen", + "job_title": "Berufsbezeichnung", + "avatar_url": "Avatar-URL", + "city": "Stadt", + "country": "Land", + "country_code": "Ländercode (ISO)", + "street_address": "Straßenadresse", + "zip_code": "Postleitzahl", + "latitude": "Breitengrad", + "longitude": "Längengrad", + "url": "URL", + "domain": "Domain", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Ganzzahl", + "float": "Dezimalzahl", + "boolean": "Boolesch", + "uuid": "UUID", + "credit_card": "Kreditkarte (Test)", + "semver": "SemVer", + "row_index": "Zeilenindex", + "sequence": "Sequenz", + "date": "Datum", + "datetime": "Datum und Uhrzeit", + "unix_timestamp": "Unix-Zeitstempel", + "timezone": "Zeitzone", + "word": "Wort", + "sentence": "Satz", + "paragraph": "Absatz", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Währungscode", + "language_code": "Sprach-Tag", + "custom_list": "Eigene Liste" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG-Optimierer", + "subtitle": "SVG einfügen, im Browser mit SVGO minifizieren und UTF-8-Größe vorher/nachher vergleichen.", + "inputLabel": "Eingabe", + "outputLabel": "Optimiert", + "placeholder": "-Ausschnitt oder ganze Datei einfügen…", + "outputPlaceholder": "Optimiertes SVG erscheint hier…", + "chars": "{count} Zeichen • {bytes} Bytes (UTF-8)", + "preview": "Vorschau", + "localNote": "Die Verarbeitung erfolgt lokal im Browser. Nichts wird hochgeladen.", + "stats": { + "title": "Größe (UTF-8)", + "before": "Vorher", + "after": "Nachher", + "savedBytes": "{count} Bytes gespart ({percent}%)", + "noChange": "Keine Reduzierung — schon minimal oder leere Ausgabe." + }, + "actions": { + "clear": "Leeren", + "sample": "Beispiel laden", + "copy": "Kopieren", + "copied": "Kopiert", + "download": "Herunterladen" + }, + "errors": { + "optimizeFailed": "Dieses SVG konnte nicht optimiert werden." + } + }, + "UnitConverter": { + "title": "Einheitenrechner", + "subtitle": "Konvertieren zwischen 323 Einheiten in 43 Kategorien", + "category": "Kategorie", + "from": "Von", + "to": "Nach", + "value": "Wert", + "result": "Ergebnis", + "swapUnits": "Einheiten tauschen", + "placeholder": "Wert eingeben", + "copyResult": "Ergebnis kopieren", + "searchCategory": "Kategorie suchen...", + "selectCategory": "Kategorie auswählen", + "noResults": "Keine Kategorie gefunden." + }, + "DockerComposeGenerator": { + "title": "Docker-Compose-Generator", + "subtitle": "Beliebte Datenbanken, Caches, Messaging, Reverse Proxys und Observability auswählen — fertiges docker-compose.yml für die lokale Entwicklung kopieren.", + "searchPlaceholder": "Suchen (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Dienste", + "selectedCount": "{count, plural, one {# Dienst} other {# Dienste}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopieren", + "copied": "Kopiert!", + "download": "Herunterladen", + "emptyState": "Wählen Sie einen oder mehrere Dienste, um Compose-YAML zu erzeugen.", + "hintPorts": "Standard-Hostports sind verteilt, um Kollisionen zu vermeiden; bei Bind-Fehlern anpassen. Demo-Passwörter vor geteilten Umgebungen ersetzen.", + "categories": { + "databases": "Datenbanken & Speicher", + "cache": "Cache & In-Memory", + "queues": "Warteschlangen & Messaging", + "search": "Suche & Logs", + "storage": "Objektspeicher", + "proxy": "Reverse Proxys & Ingress", + "observability": "Metriken & Tracing", + "mail": "E-Mail (lokal)", + "admin": "Admin- & BI-Oberflächen" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Leeren" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (Redis-kompatibel)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (Kafka-kompatibel)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (S3-kompatibel)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (SMTP-Mitschnitt)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (Dev-Server)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Schieben Sie die Kacheln und führen Sie sie zusammen, um 2048 zu erreichen", + "score": "Ergebnis", + "best": "Optimal", + "newGame": "Neues Spiel", + "howToPlay": "Verwenden Sie die Pfeiltasten (oder wischen Sie auf dem Handy), um Kacheln zu verschieben. Steine mit der gleichen Nummer werden zusammengeführt. Erreiche 2048, um zu gewinnen!", + "youWin": "Du hast 2048 erreicht!", + "keepGoing": "Bleibt unaufhaltsam", + "gameOver": "Game Over", + "tryAgain": "Erneut versuchen", + "gameOverMessage": "Keine Spielzüge mehr.", + "undo": "Rückgängig" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "abgeschlossene Etappen", + "time": "Uhrzeit", + "best": "Optimal", + "difficulty": { + "easy": "Niveau: Einfach", + "medium": "Medium", + "hard": "Hard" + }, + "stage": "Runde", + "solved": "Gelöst!", + "score": "Ergebnis", + "nextStage": "Nächster Schritt", + "replay": "Erneut wiedergeben", + "erase": "Löschen", + "howToPlay": "Klicken Sie auf eine Zelle und tippen Sie dann auf eine Zahl oder drücken Sie 1-9. Rücktaste oder Löschen löscht die Zelle. Fettgedruckte Zahlen sind Hinweise. Füllen Sie das Raster so aus, dass jede Zeile, Spalte und 3× 3-Box 1–9 ohne Wiederholungen enthält.", + "pencil": "Bleistift", + "undo": "Rückgängig", + "hint": "Hinweis", + "hintsUsed": "Hinweise verwendet", + "mistakes": "Fehler", + "failed": "Gescheitert!", + "mistakeMode": "3-Fehler-Modus" + }, + "Snake": { + "title": "Snake", + "subtitle": "Iss Essen, wachse, vermeide Wände und dich selbst", + "score": "Punkte", + "best": "Beste", + "startPrompt": "Pfeiltaste drücken", + "startButton": "Spiel starten", + "gameOver": "Spiel vorbei", + "playAgain": "Nochmal spielen", + "howToPlay": "Pfeiltasten oder WASD zum Bewegen. Iss Essen zum Wachsen. Geschwindigkeit steigt alle 50 Punkte.", + "diffEasy": "Leicht", + "diffNormal": "Normal", + "diffHard": "Schwer", + "diffInfinite": "Unendlich", + "orTap": "oder unten tippen", + "howToPlayInfinite": "Wände umwickeln — kein Wandtod. Iss Essen zum Wachsen.", + "shieldActive": "Schild aktiv", + "speedBoost": "Geschwindigkeitsboost", + "powerSpeed": "Tempo x2", + "powerShield": "Überlebt einen Tod", + "powerShrink": "Kürzt den Schwanz" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis-Verbindungen", + "heroTitle": "Redis Commander", + "heroDesc": "Verbinden Sie sich mit Redis-Instanzen. Schlüssel durchsuchen, Werte bearbeiten und Befehle ausführen. Anmeldedaten werden im Browser verschlüsselt.", + "heroAddConnection": "Verbindung hinzufügen", + "vaultLockedTitle": "Tresor gesperrt", + "vaultLockedDesc": "Entsperren Sie den Tresor, um auf gespeicherte Verbindungen zuzugreifen." + } + }, + "Minesweeper": { + "title": "Minesweeper", + "subtitle": "Finde alle sicheren Felder, ohne eine Mine auszulösen", + "mines": "Minen", + "newGame": "Neues Spiel", + "win": "Gewonnen!", + "loss": "Verloren!", + "playAgain": "Nochmal spielen", + "tryAgain": "Erneut versuchen", + "startPrompt": "Klicke auf ein Feld, um zu beginnen", + "difficulty": { + "beginner": "Anfänger", + "intermediate": "Fortgeschritten", + "expert": "Experte" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Ordne die fallenden Blöcke, um Reihen zu vervollständigen", + "next": "Nächstes", + "score": "Punkte", + "best": "Rekord", + "lines": "Reihen", + "level": "Level", + "pause": "Pause", + "resume": "Fortsetzen", + "start": "Start", + "restart": "Neustart", + "paused": "Pausiert", + "gameOver": "Spielende" + }, + "SshKeyGenerator": { + "title": "SSH / RSA-Schlüsselgenerator", + "subtitle": "Generiere Ed25519- oder RSA-Schlüsselpaare im Browser. Lade private und öffentliche Schlüssel in OpenSSH- und PEM-Formaten herunter. Es wird nichts hochgeladen.", + "keyTypeLabel": "Schlüsseltyp", + "recommended": "empfohlen", + "ed25519Desc": "Schnell, klein, modern. Unterstützt von GitHub, GitLab und allen wichtigen SSH-Servern.", + "rsa2048Desc": "Weitgehend kompatibel. Sicher für die meisten Verwendungen.", + "rsa4096Desc": "Maximale Kompatibilität und Stärke für ältere Systeme.", + "commentLabel": "Kommentar (optional)", + "commentHint": "Wird an den OpenSSH-Public-Key-Eintrag angehängt.", + "generateBtn": "Schlüsselpaar generieren", + "generating": "Generiere…", + "generatingLong": "Generiere (kann einen Moment dauern)…", + "copy": "Kopieren", + "download": "Herunterladen", + "privateKeyLabel": "Privater Schlüssel (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Öffentlicher Schlüssel (OpenSSH-Format)", + "publicKeySpkiLabel": "Öffentlicher Schlüssel (SPKI PEM)", + "browserNote": "Alle Schlüssel werden in deinem Browser generiert. Es wird nichts an einen Server gesendet.", + "errors": { + "generationFailed": "Schlüsselgenerierung fehlgeschlagen" + } + }, + "DnsLookup": { + "title": "DNS-Abfrage", + "subtitle": "DNS-Einträge für jede Domain abfragen — A, AAAA, MX, TXT, NS, CNAME, SOA, CAA.", + "placeholder": "Domain eingeben (z. B. github.com)", + "lookupBtn": "Abfragen", + "looking": "Abfrage läuft…", + "refreshBtn": "Aktualisieren", + "recordTypesLabel": "Eintragstypen", + "allBtn": "Alle", + "tryLabel": "Beispiele:", + "errors": { + "lookupFailed": "DNS-Abfrage fehlgeschlagen" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt-Generator & -Prüfer", + "subtitle": "Passwörter mit bcrypt hashen und Hashes verifizieren – alles bleibt in deinem Browser", + "tabs": { + "hash": "Hashen", + "verify": "Verifizieren" + }, + "showPassword": "Passwort anzeigen", + "hidePassword": "Passwort verbergen", + "copy": "Kopieren", + "copied": "Kopiert", + "hash": { + "passwordLabel": "Passwort", + "passwordPlaceholder": "Ein Passwort zum Hashen eingeben…", + "costLabel": "Kostenfaktor", + "costHint": "Ein höherer Kostenfaktor verdoppelt den Aufwand pro Schritt. 10-12 ist eine gängige Wahl für die Produktion.", + "slowWarning": "Kostenfaktoren ab 13 können mehrere Sekunden zur Berechnung benötigen.", + "generate": "Hash erzeugen", + "generating": "Hashen…", + "resultLabel": "Bcrypt-Hash", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Aufbau des Hashes", + "algorithm": "Algorithmus", + "cost": "Kosten", + "salt": "Salt (22 Zeichen)", + "hash": "Hash (31 Zeichen)" + } + }, + "verify": { + "passwordLabel": "Passwort", + "passwordPlaceholder": "Zu prüfendes Passwort eingeben…", + "hashLabel": "Bcrypt-Hash", + "hashPlaceholder": "$2b$10$…", + "idle": "Gib ein Passwort und einen Bcrypt-Hash ein, um sie zu vergleichen.", + "invalidHash": "Das sieht nicht nach einem gültigen Bcrypt-Hash aus.", + "checking": "Vergleiche…", + "match": "Übereinstimmung – das Passwort erzeugt diesen Hash.", + "noMatch": "Keine Übereinstimmung – das Passwort erzeugt diesen Hash nicht." + } + }, + "ChmodCalculator": { + "title": "Chmod-Rechner", + "subtitle": "Unix-Dateiberechtigungen zwischen Kontrollkästchen, Oktal und symbolischer Notation umrechnen", + "grid": { + "title": "Berechtigungen", + "read": "Lesen", + "write": "Schreiben", + "execute": "Ausführen", + "owner": "Eigentümer", + "group": "Gruppe", + "others": "Andere" + }, + "special": { + "title": "Spezielle Bits", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Oktal", + "placeholder": "755 oder 4755", + "invalid": "Gib 3 oder 4 Oktalziffern (0-7) ein." + }, + "symbolic": { + "label": "Symbolisch", + "placeholder": "rwxr-xr-x", + "invalid": "Gib 9 Zeichen wie rwxr-xr-x ein (s/S, t/T erlaubt)." + }, + "output": { + "title": "Befehl", + "copy": "Kopieren", + "copied": "Kopiert" + }, + "explain": { + "line": "{cls} kann {perms}.", + "none": "{cls} hat keine Berechtigungen.", + "setuid": "Setuid: Die Datei läuft mit den Rechten ihres Eigentümers.", + "setgid": "Setgid: Die Datei läuft mit den Rechten ihrer Gruppe (neue Dateien in einem Verzeichnis erben dessen Gruppe).", + "sticky": "Sticky: Nur der Eigentümer einer Datei kann sie in diesem Verzeichnis löschen oder umbenennen." + }, + "presets": { + "title": "Gängige Voreinstellungen", + "octal": "Oktal", + "permissions": "Berechtigungen", + "description": "Typische Verwendung", + "p644": "Normale Dateien: Eigentümer schreibt, alle lesen.", + "p755": "Ausführbare Dateien und Verzeichnisse: alle können lesen und betreten.", + "p600": "Private Dateien: nur der Eigentümer liest und schreibt.", + "p700": "Private ausführbare Dateien und Verzeichnisse: nur der Eigentümer.", + "p664": "Für die Gruppe schreibbare Dateien.", + "p775": "Für die Gruppe schreibbare Verzeichnisse und ausführbare Dateien.", + "p400": "Nur Lesezugriff für den Eigentümer (z. B. private SSH-Schlüssel)." + } + }, + "CodeScreenshot": { + "title": "Code-Screenshot", + "subtitle": "Code in schöne, teilbare Bilder verwandeln", + "languageLabel": "Sprache", + "autoDetect": "Automatisch erkennen", + "backgroundLabel": "Hintergrund", + "paddingLabel": "Innenabstand", + "filenameLabel": "Dateiname", + "filenamePlaceholder": "example.ts", + "windowLabel": "Fensterpunkte", + "themeLabel": "Fenster-Theme", + "themeDark": "Dunkel", + "themeLight": "Hell", + "scaleLabel": "Export-Skalierung", + "copyImage": "Bild kopieren", + "copied": "Kopiert", + "exportPng": "PNG exportieren", + "inputLabel": "Code", + "detected": "Erkannt: {language}", + "clear": "Leeren", + "placeholder": "Füge deinen Code hier ein…", + "previewLabel": "Vorschau", + "emptyPreview": "Die Vorschau deines Codes erscheint hier", + "exportFailed": "Bild konnte nicht exportiert werden", + "copyUnsupported": "Das Kopieren von Bildern wird in diesem Browser nicht unterstützt", + "copyFailed": "Bild konnte nicht kopiert werden", + "backgrounds": { + "ocean": "Ozean", + "sunset": "Sonnenuntergang", + "candy": "Bonbon", + "forest": "Wald", + "midnight": "Mitternacht", + "gold": "Gold", + "aurora": "Polarlicht", + "slate": "Schiefer", + "transparent": "Transparent" + } + }, + "CssGenerators": { + "title": "CSS-Generatoren", + "subtitle": "Box-shadow, border-radius und fluides clamp() – kopiere das CSS.", + "preview": "Vorschau", + "output": "CSS", + "copy": "Kopieren", + "copied": "Kopiert", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Fluides clamp()" + }, + "boxShadow": { + "presets": "Voreinstellungen", + "layer": "Ebene", + "addLayer": "Ebene hinzufügen", + "removeLayer": "Ebene entfernen", + "x": "X", + "y": "Y", + "blur": "Weichzeichnen", + "spread": "Ausbreitung", + "color": "Farbe", + "alpha": "Alpha", + "inset": "Innen", + "presetNames": { + "subtleCard": "Dezente Karte", + "elevated": "Erhöht", + "hardOffset": "Harter Versatz", + "neonGlow": "Neon-Leuchten", + "inner": "Innen", + "layeredSmooth": "Weich geschichtet" + } + }, + "borderRadius": { + "modeLabel": "Modus", + "modes": { + "simple": "Pro Ecke", + "blob": "Organischer Blob" + }, + "unit": "Einheit", + "linkCorners": "Ecken verknüpfen", + "randomize": "Zufällig", + "horizontal": "Horizontale Radien", + "vertical": "Vertikale Radien", + "corners": { + "tl": "Oben links", + "tr": "Oben rechts", + "br": "Unten rechts", + "bl": "Unten links" + } + }, + "clamp": { + "minFont": "Min. Schrift (px)", + "maxFont": "Max. Schrift (px)", + "minViewport": "Min. Viewport (px)", + "maxViewport": "Max. Viewport (px)", + "rootFont": "Wurzelschrift (px)", + "viewportWidth": "Simuliertes Viewport", + "computedSize": "Berechnete Größe", + "slope": "Steigung", + "intercept": "Achsenabschnitt", + "previewText": "Franz jagt im Taxi quer durch Bayern", + "invalid": "Gib gültige, unterschiedliche Viewport-Grenzen ein." + } + }, + "ExifViewer": { + "title": "EXIF-Viewer & -Entferner", + "subtitle": "Foto-Metadaten prüfen und in deinem Browser entfernen", + "dropTitle": "Ein Bild hier ablegen", + "dropHint": "oder zum Durchsuchen klicken – dein Bild verlässt nie dein Gerät", + "browse": "Bild auswählen", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Metadaten werden gelesen…", + "parseFailed": "Dieses Bild konnte nicht gelesen werden", + "clear": "Bild entfernen", + "gpsWarning": "Enthält GPS-Standort", + "openMap": "Auf OpenStreetMap ansehen", + "noMetadata": "In diesem Bild wurden keine eingebetteten Metadaten gefunden.", + "facts": { + "name": "Name", + "type": "Typ", + "dimensions": "Abmessungen", + "size": "Größe" + }, + "groups": { + "camera": "Kamera", + "exposure": "Belichtung", + "timestamps": "Zeitstempel", + "gps": "GPS-Standort", + "software": "Software & Sonstiges" + }, + "gpsCard": { + "latitude": "Breitengrad", + "longitude": "Längengrad", + "altitude": "Höhe" + }, + "raw": { + "title": "Alle Metadaten", + "copyJson": "JSON kopieren", + "copied": "Kopiert" + }, + "remove": { + "title": "Metadaten entfernen", + "jpegDescription": "EXIF, XMP, IPTC und Kommentare verlustfrei entfernen, ohne das Bild neu zu komprimieren.", + "reencodeDescription": "Das Bild neu codieren, um eingebettete Metadaten zu entfernen. Dabei werden die Pixel neu komprimiert.", + "keepIcc": "Farbprofil (ICC) beibehalten", + "stripButton": "Metadaten entfernen", + "reencodeButton": "Neu codieren & entfernen", + "unsupported": "Das Entfernen von Metadaten wird für diesen Dateityp nicht unterstützt.", + "failed": "Metadaten konnten nicht entfernt werden", + "before": "Vorher", + "after": "Nachher", + "download": "Bereinigtes Bild herunterladen" + } + }, + "FaviconGenerator": { + "title": "Favicon-Generator", + "subtitle": "Favicons und App-Icons aus einem einzigen Bild erstellen", + "dropTitle": "Ein Bild hier ablegen", + "dropHint": "PNG, JPEG oder SVG – zum Durchsuchen klicken", + "browse": "Bild auswählen", + "clear": "Bild entfernen", + "backgroundLabel": "Hintergrund", + "transparent": "Transparent", + "solidColor": "Einfarbig", + "cornerLabel": "Eckenform", + "cornerNone": "Quadratisch", + "cornerRounded": "Abgerundet", + "cornerCircle": "Kreis", + "maskPaddingLabel": "Maskable-Abstand", + "maskable": "maskable", + "previewLabel": "Vorschau", + "copy": "Kopieren", + "headSnippet": "HTML-head-Snippet", + "downloadIco": "favicon.ico herunterladen", + "downloadZip": "Alles herunterladen (.zip)" + }, + "JsonDiff": { + "title": "JSON-Diff", + "subtitle": "Semantischer, strukturbewusster Vergleich zweier JSON-Dokumente – Schlüsselreihenfolge und Formatierung werden ignoriert", + "options": { + "ignoreArrayOrder": "Array-Reihenfolge ignorieren" + }, + "loadBothSamples": "Beispiele laden", + "sample": "Beispiel laden", + "format": "Formatieren", + "clear": "Leeren", + "formatError": "Formatieren nicht möglich: Die Eingabe ist kein gültiges JSON", + "placeholder": "JSON hier einfügen oder eingeben…", + "panels": { + "left": "Original (links)", + "right": "Geändert (rechts)", + "results": "Unterschiede" + }, + "summary": { + "added": "{count} hinzugefügt", + "removed": "{count} entfernt", + "changed": "{count} geändert" + }, + "types": { + "added": "Hinzugefügt", + "removed": "Entfernt", + "changed": "Geändert" + }, + "copyChanges": "Als JSON kopieren", + "copied": "Kopiert", + "empty": "Füge JSON in beide Panels ein, um ihre Unterschiede zu sehen.", + "identical": "Die beiden Dokumente sind semantisch identisch." + }, + "JsonpathPlayground": { + "title": "JSONPath-Playground", + "subtitle": "JSON-Dokumente mit JSONPath oder JMESPath abfragen und Ergebnisse live sehen", + "engineLabel": "Engine", + "queryLabel": "Abfrage", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Spickzettel", + "cheats": { + "jpAllTitles": "Alle Buchtitel", + "jpAllAuthors": "Jeder Autor, überall im Dokument", + "jpFirstBook": "Erstes Buch", + "jpLastBook": "Letztes Buch", + "jpSlice": "Die ersten zwei Bücher (Slice)", + "jpFilterPrice": "Bücher günstiger als 40", + "jpFilterInStock": "Titel der vorrätigen Bücher", + "jpRecursive": "Alle Werte, rekursiv", + "jmAllTitles": "Alle Buchtitel", + "jmFirstAuthor": "Autor des ersten Buches", + "jmLastBook": "Letztes Buch", + "jmSlice": "Die ersten zwei Bücher (Slice)", + "jmFilterPrice": "Titel der Bücher günstiger als 40", + "jmCount": "Vorrätige Bücher zählen", + "jmProjection": "Jedes Buch in ein neues Objekt umformen", + "jmSort": "Nach Preis sortierte Titel" + }, + "panels": { + "input": "JSON-Eingabe", + "results": "Ergebnisse" + }, + "sample": "Beispiel laden", + "format": "Formatieren", + "clear": "Leeren", + "formatError": "Formatieren nicht möglich: Die Eingabe ist kein gültiges JSON", + "placeholder": "JSON hier einfügen oder eingeben…", + "outputPlaceholder": "Treffer erscheinen hier…", + "matchCount": "{count, plural, one {# Treffer} other {# Treffer}}", + "copy": "Kopieren", + "copied": "Kopiert" + }, + "KeycodeInspector": { + "title": "Keycode-Inspektor", + "subtitle": "Drücke eine beliebige Taste, um ihr JavaScript-Tastaturevent zu untersuchen", + "capture": { + "prompt": "Drücke eine beliebige Taste…", + "ready": "Höre zu – drücke eine beliebige Taste auf deiner Tastatur", + "clickToFocus": "Klicke hier, um zu fokussieren, und drücke dann eine beliebige Taste" + }, + "hero": { + "spaceKey": "(Leertaste)", + "keyLabel": "event.key", + "repeat": "Wiederholung" + }, + "copyJson": "JSON kopieren", + "copiedJson": "Event-JSON in die Zwischenablage kopiert", + "deprecated": "veraltet", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standard", + "left": "Links", + "right": "Rechts", + "numpad": "Ziffernblock", + "unknown": "Unbekannt" + }, + "history": { + "title": "Verlauf", + "clear": "Verlauf löschen", + "empty": "Tastendrücke erscheinen hier." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown-Tabellen-Generator", + "subtitle": "Ein Raster bearbeiten, aus einer Tabellenkalkulation einfügen und eine hübsch ausgerichtete Markdown- oder HTML-Tabelle kopieren", + "addRow": "Zeile hinzufügen", + "addColumn": "Spalte hinzufügen", + "importLabel": "Importieren", + "importPlaceholder": "TSV (aus Excel / Google Sheets) oder CSV hier einfügen…", + "importButton": "Ins Raster importieren", + "pasteClipboard": "Aus Zwischenablage einfügen", + "importEmpty": "Nichts zu importieren – füge zuerst ein paar Zeilen ein", + "clipboardError": "Zwischenablage konnte nicht gelesen werden", + "clear": "Leeren", + "panels": { + "editor": "Tabelleneditor", + "output": "Ausgabe" + }, + "align": { + "left": "Linksbündig", + "center": "Zentriert", + "right": "Rechtsbündig" + }, + "headerPlaceholder": "Kopfzeile", + "cellPlaceholder": "Zelle", + "removeRow": "Zeile entfernen", + "removeColumn": "Spalte entfernen", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopieren", + "copied": "Kopiert" + }, + "TimezoneConverter": { + "title": "Zeitzonen-Konverter", + "subtitle": "Einen Moment weltweit umrechnen, um Meetings zu planen.", + "dateTimeLabel": "Datum & Uhrzeit", + "now": "Jetzt", + "sourceZoneLabel": "Ausgangszeitzone", + "selectZone": "Zone auswählen", + "searchZone": "Zeitzonen suchen...", + "noZone": "Keine Zeitzone gefunden.", + "scrubLabel": "Zeit verschieben", + "reset": "Zurücksetzen", + "targetsLabel": "Zielzonen", + "addZone": "Zone hinzufügen", + "remove": "Entfernen", + "localBadge": "Lokal", + "invalidDate": "Gib ein gültiges Datum und eine gültige Uhrzeit ein.", + "bands": { + "work": "Arbeitszeiten (09:00–18:00)", + "edge": "Randzeiten (früh / abends)", + "night": "Nacht" + } + }, + "TokenCounter": { + "title": "LLM-Token-Zähler", + "subtitle": "Tokens zählen, Chunks visualisieren und API-Kosten schätzen.", + "inputLabel": "Text", + "sample": "Beispiel laden", + "clear": "Leeren", + "placeholder": "Text, Code oder JSON einfügen, um Tokens zu zählen...", + "encoding": "Codierung", + "loading": "Tokenizer wird geladen...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-Serie)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Zeichen", + "words": "Wörter", + "ratio": "Tokens/Wort", + "ratioSub": "ca." + }, + "claudeRow": "Claude (näherungsweise)", + "estimate": "Schätzung", + "viz": { + "title": "Token-Visualisierung", + "toggle": "Anzeigen", + "hint": "Umschalten, um jedes Token als Chunk einzufärben.", + "empty": "Gib Text ein, um Tokens zu visualisieren.", + "truncated": "Zeigt die ersten {limit} von {total} Tokens." + }, + "cost": { + "title": "Kostenrechner", + "disclaimer": "editierbare Standardwerte – aktuelle Preise prüfen", + "model": "Modell", + "expectedOutput": "Erwartete Ausgabe-Tokens", + "customInput": "Eingabe $/MTok", + "customOutput": "Ausgabe $/MTok", + "inputCost": "Eingabekosten", + "outputCost": "Ausgabekosten", + "total": "Gesamt", + "per1000": "Pro 1000 Aufrufe" + } + }, + "WebhookTester": { + "title": "Webhook-Tester", + "subtitle": "Eine temporäre URL erstellen und eingehende Webhook-Anfragen live untersuchen.", + "intro": { + "title": "Erstelle eine Webhook-URL, um Anfragen zu erfassen", + "hint": "Du erhältst eine nicht erratbare URL, die jede eingehende Anfrage aufzeichnet. Sie läuft nach 24 Stunden ab." + }, + "createBtn": "Webhook-URL erstellen", + "creating": "Wird erstellt…", + "urlLabel": "Deine Webhook-URL", + "expiresIn": "Läuft ab in {countdown}", + "copyUrlBtn": "URL kopieren", + "copied": "Kopiert", + "clearBtn": "Leeren", + "newBinBtn": "Neue URL", + "requestsTitle": "Anfragen", + "selectPrompt": "Wähle eine Anfrage aus, um ihre Details zu untersuchen.", + "expiredNotice": "Diese Webhook-URL ist abgelaufen. Erstelle eine neue, um weiter zu testen.", + "base64Badge": "base64", + "truncatedBadge": "gekürzt", + "empty": { + "title": "Warte auf Anfragen…", + "hint": "Sende eine Anfrage an deine URL und sie erscheint hier sofort. Probiere das:" + }, + "detail": { + "receivedAt": "Empfangen", + "source": "Quell-IP", + "contentType": "Content-Type", + "query": "Query-Parameter", + "headers": "Header", + "body": "Body", + "copyBody": "Body kopieren", + "emptyBody": "Kein Body" + }, + "errors": { + "createFailed": "Webhook-URL konnte nicht erstellt werden. Bitte versuche es erneut.", + "clearFailed": "Anfragen konnten nicht gelöscht werden. Bitte versuche es erneut." + } + }, + "WebsocketTester": { + "title": "WebSocket-Tester", + "subtitle": "Mit einem WebSocket-Endpunkt verbinden und Nachrichten live senden, empfangen und untersuchen", + "urlLabel": "Endpunkt-URL", + "protocolsLabel": "Subprotokolle", + "protocolsPlaceholder": "optional, durch Kommas getrennt", + "connect": "Verbinden", + "disconnect": "Trennen", + "status": { + "disconnected": "Getrennt", + "connecting": "Verbinde", + "connected": "Verbunden", + "error": "Fehler" + }, + "messages": { + "connecting": "Verbinde mit {url}…", + "connected": "Verbindung hergestellt", + "connectedWithProtocol": "Verbindung hergestellt (Protokoll: {protocol})", + "invalidUrl": "Ungültige URL – muss mit ws:// oder wss:// beginnen", + "connectFailed": "Verbindung konnte nicht geöffnet werden", + "socketError": "Es ist ein Verbindungsfehler aufgetreten", + "notConnected": "Nicht verbunden – zuerst verbinden", + "binaryMessage": "Eine Binärnachricht empfangen" + }, + "log": { + "title": "Nachrichtenprotokoll", + "empty": "Noch keine Nachrichten. Verbinde dich und beginne zu senden.", + "copyAll": "Alle kopieren", + "download": "Als .txt herunterladen", + "clear": "Protokoll leeren", + "prettyPrint": "JSON formatieren", + "showRaw": "Rohtext anzeigen" + }, + "composer": { + "placeholder": "Eine Nachricht zum Senden eingeben…", + "send": "Senden", + "formatJson": "Als JSON formatieren", + "invalidJson": "Die Nachricht ist kein gültiges JSON", + "hint": "Cmd/Ctrl+Enter zum Senden" + }, + "ping": { + "label": "Auto-Ping", + "seconds": "Sek.", + "intervalTitle": "Ping-Intervall in Sekunden", + "payloadPlaceholder": "Ping-Payload" + } + }, + "WhoisLookup": { + "title": "Whois-Abfrage", + "subtitle": "Registrierungsdaten einer beliebigen Domain oder IP-Adresse per RDAP abrufen", + "placeholder": "example.com oder 8.8.8.8", + "lookupBtn": "Abfragen", + "looking": "Abfrage läuft…", + "refreshBtn": "Aktualisieren", + "tryLabel": "Probiere:", + "copy": "Kopieren", + "copied": "Kopiert", + "notAvailable": "Nicht verfügbar", + "summary": { + "registrar": "Registrar", + "network": "Netzwerk" + }, + "dates": { + "created": "Erstellt", + "updated": "Aktualisiert", + "expires": "Läuft ab" + }, + "sections": { + "status": "Status", + "nameservers": "Nameserver", + "contacts": "Kontakte", + "rawRdap": "Rohes RDAP" + }, + "contacts": { + "roles": "Rollen", + "name": "Name", + "organization": "Organisation" + }, + "errors": { + "invalidTarget": "Gib einen gültigen Domainnamen oder eine gültige IP-Adresse ein", + "lookupFailed": "Abfrage fehlgeschlagen (Status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/el.json b/apps/desktop-ui/messages/el.json new file mode 100644 index 00000000..912b71b2 --- /dev/null +++ b/apps/desktop-ui/messages/el.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Ταμπλό", + "settings": "Ρυθμίσεις", + "apiClient": "Πελάτης API", + "httpStatusCodes": "Κωδικοί κατάστασης HTTP", + "passwordManager": "Διαχειριστής κωδικών πρόσβασης", + "environmentManager": "Διαχειριστής περιβάλλοντος", + "bookmarks": "Σελιδοδείκτες", + "jsonFormatter": "Μορφοποιητής JSON", + "emailValidator": "Email Validator", + "nosqlExplorer": "Εξερευνητής βάσεων δεδομένων", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Χώρος δοκιμών κρυπτογράφησης", + "jwtDecoder": "Αποκωδικοποιητής JWT", + "certificatePemDecoder": "Αποκωδικοποιητής πιστοποιητικού / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Μορφοποιητής GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Αναλυτής URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Μυστικό / κλειδί API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Έλεγχος αντίθεσης", + "jsonSchemaGenerator": "Γεννήτρια JSON Schema", + "imageToBase64": "Εικόνα στο Base64", + "imageCompressor": "Συμπίεση εικόνας", + "cssGradientBuilder": "CSS Gradient Builder", + "gitignoreGenerator": ".gitignore Γεννήτρια", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Αποσπάσματα κώδικα", + "qrCodeGenerator": "Δημιουργός κωδικών QR", + "dnsLookup": "Αναζήτηση DNS", + "ipSubnetCalculator": "Υπολογιστής IP / υποδικτύου", + "hashGenerator": "Γεννήτρια hash", + "hmacGenerator": "Γεννήτρια HMAC", + "totpGenerator": "Κωδικός TOTP / 2FA", + "markdownPreview": "Προεπισκόπηση Markdown", + "numberBaseConverter": "Μετατροπέας βάσης", + "formatConverter": "Μετατροπέας Μορφών", + "mockDataGenerator": "Γεννήτρια δοκιμαστικών δεδομένων", + "svgOptimizer": "Βελτιστοποιητής SVG", + "unitConverter": "Μετατροπέας Μονάδων", + "dockerComposeGenerator": "Δημιουργός Docker Compose", + "breakRoom": "Αίθουσα διαλειμμάτων", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Μορφοποιητής YAML", + "sshKeyGenerator": "Γεννήτρια κλειδιών SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Υπολογιστής chmod", + "codeScreenshot": "Στιγμιότυπο κώδικα", + "cssGenerators": "Γεννήτριες CSS", + "exifViewer": "Προβολή EXIF", + "faviconGenerator": "Γεννήτρια favicon", + "jsonDiff": "Σύγκριση JSON", + "jsonpathPlayground": "Παιχνιδότοπος JSONPath", + "keycodeInspector": "Επιθεωρητής πλήκτρων", + "markdownTableGenerator": "Πίνακας Markdown", + "timezoneConverter": "Μετατροπέας ζωνών ώρας", + "tokenCounter": "Μετρητής token", + "webhookTester": "Δοκιμαστής webhook", + "websocketTester": "Δοκιμαστής WebSocket", + "whoisLookup": "Αναζήτηση Whois" + }, + "Help": { + "title": "Βοήθεια και τεκμηρίωση", + "subtitle": "Πώς συνδέεται το MyDevTools, τι κάνει κάθε εφαρμογή και πώς η ασφάλεια και η κρυπτογράφηση προστατεύουν τα δεδομένα σας.", + "tabs": { + "overview": "Επισκόπηση", + "apps": "Εφαρμογές", + "security": "Ασφάλεια", + "tips": "Συμβουλές" + }, + "overview": { + "whatIs": { + "title": "Τι είναι το MyDevTools;", + "description": "Ένας χώρος εργασίας για προγραμματιστές με σύνδεση: ταμπλό και εργαλεία για καθημερινές εργασίες.", + "p1": "Αφού συνδεθείτε, η εφαρμογή δημιουργεί μια συνεδρία με το MyDevTools API ώστε τα εργαλεία να μπορούν να φορτώνουν και να αποθηκεύουν τα δεδομένα σας. Η πλευρική γραμμή εμφανίζει όλες τις εφαρμογές· μπορείτε να κρύψετε εργαλεία που δεν χρησιμοποιείτε από τις", + "settingsLink": "Ρυθμίσεις", + "p1After": ".", + "p2": "Ορισμένες λειτουργίες χρησιμοποιούν κύριο κωδικό (ή καθολικό κλειδί θησαυροφυλακίου) ώστε μυστικά όπως κωδικοί και strings σύνδεσης να κρυπτογραφούνται στη συσκευή σας πριν φτάσουν στον διακομιστή." + }, + "quickLinks": { + "title": "Γρήγοροι σύνδεσμοι", + "dashboard": "Ταμπλό", + "settings": "Ρυθμίσεις" + } + }, + "apps": { + "intro": "Ανοίξτε οποιοδήποτε εργαλείο από την πλευρική γραμμή ή το ταμπλό. Παρακάτω υπάρχει ένας σύντομος οδηγός ανά εφαρμογή.", + "open": "Άνοιγμα", + "howItWorks": "Πώς λειτουργεί", + "generic": "Χρησιμοποιήστε αυτό το εργαλείο από την πλευρική γραμμή για τη ροή εργασίας του." + }, + "security": { + "session": { + "title": "Σύνδεση και συνεδρία API", + "description": "Ταυτότητα και πώς ο browser επικοινωνεί με το backend.", + "items": { + "authentication": "Αυθεντικοποίηση με Firebase. Μετά το login, η εφαρμογή ανταλλάσσει το Firebase ID token με cookies API.", + "accessTokens": "Access tokens είναι JWT υπογεγραμμένα στον server (HS256) και αποδεικνύουν ποιος χρήστης καλεί το API.", + "refreshTokens": "Refresh tokens αποθηκεύονται σε HttpOnly cookies· ο server κρατά SHA-256 hash του token, όχι το ίδιο το token." + } + }, + "encryption": { + "title": "Κρυπτογράφηση που πρέπει να γνωρίζετε", + "description": "Κρυπτογραφία στην πλευρά του πελάτη για το θησαυροφυλάκιο και ευαίσθητα δεδομένα σύνδεσης.", + "p1": "Για τον διαχειριστή κωδικών πρόσβασης, τον διαχειριστή περιβάλλοντος και τις αποθηκευμένες συνδέσεις στον NoSQL Explorer, η εφαρμογή web χρησιμοποιεί το Web Crypto API του προγράμματος περιήγησης:", + "items": { + "kdf": "PBKDF2 με SHA-256, 100.000 επαναλήψεις και τυχαίο salt παράγει κλειδί 256-bit από τον κύριο κωδικό.", + "aes": "AES-256-GCM κρυπτογραφεί δεδομένα· χρησιμοποιείται τυχαίο IV 12 bytes σε κάθε κρυπτογράφηση.", + "scope": "Το παράγωγο κλειδί μένει στον browser (μη εξαγώγιμο). Ο server λαμβάνει μόνο ciphertext και IV, όχι τον κύριο κωδικό ή μυστικά σε απλό κείμενο." + }, + "warning": "Αν ξεχάσετε τον κύριο κωδικό, τα κρυπτογραφημένα δεδομένα δεν μπορούν να ανακτηθούν. Φυλάξτε τον με ασφάλεια." + }, + "logout": { + "title": "Αποσύνδεση και τοπικά δεδομένα", + "body": "Η αποσύνδεση καθαρίζει μυστικά από τη μνήμη, κατάσταση του θησαυροφυλακίου και (όπου ισχύει) υλικό κύριου κλειδιού από το IndexedDB και τερματίζει τη συνεδρία API. Δεδομένα που υπάρχουν μόνο στο local storage μπορεί να παραμείνουν μέχρι να τα διαγράψετε στον browser." + } + }, + "tips": { + "title": "Ασφαλής χρήση του MyDevTools", + "items": { + "masterPassword": "Χρησιμοποιήστε ισχυρό, μοναδικό κύριο κωδικό για λειτουργίες θησαυροφυλακίου.", + "apiClient": "Χειριστείτε τον API Client όπως κάθε HTTP client: αποφύγετε να επικολλάτε μυστικά παραγωγής σε κοινόχρηστα περιβάλλοντα αν δεν ξέρετε ποιος μπορεί να τα δει.", + "nosql": "Ο Εξερευνητής βάσεων δεδομένων συνδέεται σε βάσεις που ρυθμίζετε· χρησιμοποιήστε χρήστες ελάχιστων δικαιωμάτων και κανόνες δικτύου όπου είναι δυνατόν.", + "json": "Ο JSON μορφοποιητής τρέχει τοπικά στον browser—παρόλα αυτά αποφύγετε να επικολλάτε πολύ ευαίσθητα δεδομένα σε κοινόχρηστες συσκευές.", + "settings": "Προσαρμόστε τα ορατά εργαλεία στις Ρυθμίσεις για να παραμείνει εστιασμένη η πλευρική γραμμή." + } + } + }, + "Settings": { + "title": "Ρυθμίσεις", + "language": "Γλώσσα", + "spanish": "ισπανικά", + "english": "αγγλικός", + "french": "Γαλλικά", + "arabic": "Αραβικά", + "catalan": "καταλανικά", + "chineseSimplified": "Κινεζικά (Απλοποιημένα)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Γερμανός", + "danish": "Δανικά", + "afrikaans": "Αφρικάανς", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ουκρανικά" + }, + "SettingsPage": { + "title": "Ρυθμίσεις", + "subtitle": "Διαχειριστείτε τις ρυθμίσεις και τις προτιμήσεις του λογαριασμού σας.", + "userProfile": { + "title": "Προφίλ χρήστη", + "description": "Τα προσωπικά σας στοιχεία και τα στοιχεία του λογαριασμού σας.", + "avatarAlt": "Χρήστης", + "anonymousUser": "Ανώνυμος χρήστης", + "noEmailProvided": "Δεν παρέχεται email", + "notLoggedIn": "Δεν έχετε συνδεθεί." + }, + "tools": { + "title": "Εργαλεία & Χαρακτηριστικά", + "description": "Επιλέξτε ποια εργαλεία θα εμφανίζονται στην πλαϊνή γραμμή πλοήγησής σας." + }, + "appearance": { + "title": "Εμφάνιση", + "description": "Προσαρμόστε τον τρόπο εμφάνισης της εφαρμογής στη συσκευή σας.", + "themePreference": "Προτίμηση θέματος", + "themes": { + "light": "Φως", + "dark": "Σκοτάδι", + "system": "Σύστημα" + } + }, + "language": { + "title": "Γλώσσα", + "description": "Ορίστε τη γλώσσα που χρησιμοποιείται στην πλατφόρμα.", + "selectLanguage": "Επιλέξτε Γλώσσα", + "selectPlaceholder": "Επιλέξτε μια γλώσσα", + "helpText": "Η αλλαγή της γλώσσας σας θα ενημερώσει τα μενού και τις ετικέτες σε όλη την πλατφόρμα.", + "languages": { + "en": "αγγλικός", + "fr": "Français (Γαλλικά)", + "es": "Español (Ισπανικά)", + "ar": "عربى (Αραβικά)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά", + "de": "Deutsch (Γερμανικά)", + "da": "Dansk (Δανικά)", + "af": "Afrikaans (Αφρικάανς)", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Ιταλικά (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Καλημέρα", + "afternoon": "Καλό απόγευμα", + "evening": "Καλησπέρα" + }, + "commaName": ", {name}", + "welcomeBack": "Καλώς ήρθατε πίσω", + "welcomeBackNamed": "Καλώς ήρθατε πίσω, {name}", + "tagline": "Τι θα θέλατε να δημιουργήσετε σήμερα;", + "brandName": "MyDevTools", + "stats": { + "tools": "Εργαλεία", + "favorites": "Αγαπημένα", + "pinned": "Καρφιτσωμένα", + "recent": "Πρόσφατα" + }, + "sections": { + "recentlyUsed": "Πρόσφατα", + "favorites": "Αγαπημένα", + "whatsNew": "Τι νέο υπάρχει", + "pinned": "Καρφιτσωμένα" + }, + "groups": { + "apps": "Εφαρμογές" + }, + "toolCard": { + "defaultDescription": "Εξερευνήστε αυτό το εργαλείο για καλύτερη λειτουργικότητα.", + "launchTool": "Άνοιγμα εργαλείου" + }, + "tools": { + "toDo": { + "title": "Εργασίες", + "description": "Διαχειριστείτε τις λίστες εργασιών σας αποτελεσματικά." + }, + "notes": { + "title": "Σημειώσεις", + "description": "Καταγράψτε ιδέες και οργανώστε τις σκέψεις σας." + }, + "passwordManager": { + "title": "Διαχειριστής κωδικών", + "description": "Αποθηκεύστε και διαχειριστείτε τους κωδικούς με ασφάλεια." + }, + "environmentManager": { + "title": "Διαχειριστής περιβάλλοντος", + "description": "Αποθηκεύστε μεταβλητές τύπου .env ανά έργο και περιβάλλον με ετικέτες." + }, + "bookmarks": { + "title": "Σελιδοδείκτες", + "description": "Διαχειριστείτε και οργανώστε τους σελιδοδείκτες σας." + }, + "apiClient": { + "title": "Πελάτης API", + "description": "Δοκιμάστε και αποσφαλματώστε αιτήματα HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Εξερευνήστε βάσεις δεδομένων MongoDB." + }, + "emailValidator": { + "title": "Επικύρωση email", + "description": "Επαληθεύστε και επικυρώστε διευθύνσεις email." + }, + "encryptionPlayground": { + "title": "Χώρος δοκιμών κρυπτογράφησης", + "description": "AES-GCM με ακατέργαστο κλειδί ή συνθηματική φράση· δέσμες JSON, Web Crypto μόνο στον browser." + }, + "jwtDecoder": { + "title": "Αποκωδικοποιητής JWT", + "description": "Αποκωδικοποιήστε κεφαλίδα, payload και λήξη JWT." + }, + "certificatePemDecoder": { + "title": "Αποκωδικοποιητής πιστοποιητικού / PEM", + "description": "Αποκωδικοποίηση πιστοποιητικών X.509 και CSR: θέμα, εκδότης, ισχύς, SAN." + }, + "sshKeyGenerator": { + "title": "Γεννήτρια κλειδιών SSH / RSA", + "description": "Δημιουργία ζευγών κλειδιών Ed25519 ή RSA στο πρόγραμμα περιήγησης. Λήψη σε μορφές OpenSSH και PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Μορφοποιητής GraphQL", + "description": "Μορφοποιήστε GraphQL με επισήμανση σύνταξης και απλό επεξεργαστή ερωτημάτων." + }, + "yamlFormatter": { + "title": "Μορφοποιητής YAML", + "description": "Μορφοποίηση, επαλήθευση και μετατροπή YAML σε JSON στον περιηγητή." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Αναλυτής URL", + "description": "Χωρίστε ένα URL σε πρωτόκολλο, κεντρικό υπολογιστή, διαδρομή, παραμέτρους ερωτήματος και hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Μυστικό / κλειδί API", + "description": "Κρυπτογραφικά τυχαίες συμβολοσειρές με ρυθμιζόμενο αλφάβητο, μήκος και μαζική εξαγωγή." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Έλεγχος αντίθεσης", + "description": "Αναλογία αντίθεσης WCAG 2.1 κειμένου και φόντου (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Γεννήτρια JSON Schema", + "description": "Συμπεράνετε ένα JSON Schema και τυποποιημένα μοντέλα από δείγμα JSON." + }, + "imageToBase64": { + "title": "Εικόνα στο Base64", + "description": "Μετατροπή εικόνων σε Data URI ή σε ακατέργαστες συμβολοσειρές Base64." + }, + "imageCompressor": { + "title": "Συμπίεση εικόνας", + "description": "Συμπίεση JPEG, PNG και WebP τοπικά με ρυθμιστή ποιότητας." + }, + "cssGradientBuilder": { + "title": "CSS Gradient Builder", + "description": "Visual CSS gradient builder με έλεγχο γωνίας και χρωματικές στάσεις." + }, + "gitignoreGenerator": { + "title": ".gitignore Γεννήτρια", + "description": "Δημιουργήστε συγχωνευμένα αρχεία .gitignore άμεσα από 500+ στοίβες τεχνολογίας." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Μετατρέψτε αρχεία CSV ή Excel σε JSON και εξάγετε ξανά σε CSV ή XLSX." + }, + "snippetManager": { + "title": "Αποσπάσματα κώδικα", + "description": "Αποθηκεύστε αποσπάσματα με τονισμό σύνταξης, αυτόματη ανίχνευση γλώσσας και μορφοποίηση." + }, + "qrCodeGenerator": { + "title": "Δημιουργός κωδικών QR", + "description": "Δημιουργήστε κωδικές QR PNG από κείμενο ή URL στο πρόγραμμα περιήγησης." + }, + "ipSubnetCalculator": { + "title": "Υπολογιστής IP / υποδικτύου", + "description": "IPv4 και IPv6 CIDR: μάσκα, εύρη και πλήθος διευθύνσεων." + }, + "dnsLookup": { + "title": "Αναζήτηση DNS", + "description": "Αναζητήστε εγγραφές DNS για οποιοδήποτε τομέα — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Γεννήτρια hash", + "description": "MD5, SHA-1 και SHA-2 για κείμενο ή αρχεία." + }, + "hmacGenerator": { + "title": "Γεννήτρια HMAC", + "description": "Υπογράψτε webhooks και δοκιμάστε ενσωματώσεις API με HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Γεννήτρια κωδικού TOTP / 2FA", + "description": "Επικολλήστε μυστικό Base32 από την εφαρμογή ελέγχου ταυτότητας· ο εξαψήφιος κωδικός ανανεώνεται κάθε 30 δ—χρήσιμο για δοκιμές σύνδεσης και MFA." + }, + "markdownPreview": { + "title": "Προεπισκόπηση Markdown", + "description": "Ζωντανός renderer Markdown με εξαγωγή HTML και μετατροπή HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Μετατροπέας αριθμητικής βάσης", + "description": "Μετατροπή ακεραίων μεταξύ βάσεων 2–36." + }, + "formatConverter": { + "title": "Μετατροπέας Μορφών", + "description": "Μετατροπή μεταξύ JSON, YAML, TOML και XML προς οποιαδήποτε κατεύθυνση. Υποστηρίζονται και οι 12 συνδυασμοί." + }, + "mockDataGenerator": { + "title": "Γεννήτρια δοκιμαστικών δεδομένων", + "description": "Ορίστε στήλες, επιλέξτε τύπους και εξάγετε σύνολα για seeds, Postman ή δοκιμές. Έως 5000 γραμμές, όλα στον browser — χωρίς μεταφόρτωση." + }, + "httpStatusCodes": { + "title": "Κωδικοί κατάστασης HTTP", + "description": "Αναζήτηση κωδικών κατάστασης HTTP με περιγραφές (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Βελτιστοποιητής SVG", + "description": "Επικολλήστε SVG, αφαιρέστε περιττά με το SVGO και δείτε εξοικονόμηση UTF-8." + }, + "unitConverter": { + "title": "Μετατροπέας Μονάδων", + "description": "Μετατροπή μεταξύ 323 μονάδων σε 43 επιστημονικές και μηχανολογικές κατηγορίες." + }, + "dockerComposeGenerator": { + "title": "Δημιουργός Docker Compose", + "description": "Επιλέξτε βάσεις δεδομένων, cache, ουρές, proxy και εικόνες παρατηρησιμότητας· εξάγετε docker-compose.yml για τοπικές στοίβες." + }, + "game2048": { + "title": "2048", + "description": "Σύρετε και συγχωνεύστε τα πλακίδια για να φτάσετε στο 2048. Κλασικό παιχνίδι παζλ αριθμών." + }, + "sudoku": { + "title": "Sudoku", + "description": "Κλασικό αριθμητικό παζλ. 3 επίπεδα δυσκολίας, 150 στάδια το καθένα." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Περιηγηθείτε σε κλειδιά, επιθεωρήστε τιμές και εκτελέστε εντολές σε οποιοδήποτε στιγμιότυπο Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Γεννήτρια και ελεγκτής Bcrypt", + "description": "Κατακερματίστε κωδικούς με bcrypt και επαληθεύστε υπάρχοντες κατακερματισμούς." + }, + "chmodCalculator": { + "title": "Υπολογιστής chmod", + "description": "Μετατρέψτε δικαιώματα Unix μεταξύ οκταδικής, συμβολικής μορφής και πλαισίων ελέγχου." + }, + "codeScreenshot": { + "title": "Στιγμιότυπο κώδικα", + "description": "Μετατρέψτε τον πηγαίο κώδικα σε όμορφες εικόνες για κοινή χρήση." + }, + "cssGenerators": { + "title": "Γεννήτριες CSS", + "description": "Γεννήτριες box-shadow, border-radius και ρευστού clamp()." + }, + "exifViewer": { + "title": "Προβολή και αφαίρεση EXIF", + "description": "Επιθεωρήστε και αφαιρέστε τα μεταδεδομένα EXIF φωτογραφιών τοπικά." + }, + "faviconGenerator": { + "title": "Γεννήτρια favicon", + "description": "Δημιουργήστε favicons, εικονίδια εφαρμογών και αποσπάσματα manifest." + }, + "jsonDiff": { + "title": "Σύγκριση JSON", + "description": "Σημασιολογική σύγκριση δύο εγγράφων JSON με επίγνωση της δομής." + }, + "jsonpathPlayground": { + "title": "Παιχνιδότοπος JSONPath", + "description": "Κάντε ερωτήματα σε JSON με JSONPath ή JMESPath και δείτε ζωντανά αποτελέσματα." + }, + "keycodeInspector": { + "title": "Επιθεωρητής πλήκτρων", + "description": "Πατήστε ένα πλήκτρο για να δείτε τους κωδικούς συμβάντων JavaScript και τους τροποποιητές του." + }, + "markdownTableGenerator": { + "title": "Γεννήτρια πινάκων Markdown", + "description": "Δημιουργήστε πίνακες Markdown και HTML σε έναν επεξεργαστή πλέγματος τύπου υπολογιστικού φύλλου." + }, + "timezoneConverter": { + "title": "Μετατροπέας ζωνών ώρας", + "description": "Μετατρέψτε μια ώρα σε πολλές ζώνες με ενδείξεις μετατόπισης ημέρας και ωραρίου εργασίας." + }, + "tokenCounter": { + "title": "Μετρητής token LLM", + "description": "Μετρήστε token LLM, οπτικοποιήστε τμήματα και εκτιμήστε το κόστος API." + }, + "webhookTester": { + "title": "Δοκιμαστής webhook", + "description": "Καταγράψτε και επιθεωρήστε εισερχόμενα αιτήματα webhook σε πραγματικό χρόνο." + }, + "websocketTester": { + "title": "Δοκιμαστής WebSocket", + "description": "Συνδεθείτε σε άκρα WebSocket και ανταλλάξτε μηνύματα σε πραγματικό χρόνο." + }, + "whoisLookup": { + "title": "Αναζήτηση Whois", + "description": "Αναζητήστε στοιχεία καταχώρισης τομέα και IP μέσω RDAP." + } + }, + "tabs": { + "apps": "Εφαρμογές", + "analytics": "Αναλυτικά", + "ui": "Διεπαφή" + }, + "analytics": { + "title": "Τα δεδομένα σας με μια ματιά", + "subtitle": "Μετρήσεις συγχρονισμένες με τον λογαριασμό σας στον διακομιστή.", + "loadError": "Δεν ήταν δυνατή η φόρτωση αναλυτικών.", + "retry": "Επανάληψη", + "loginHint": "Συνδεθείτε για να δείτε αναλυτικά χρήσης και προτιμήσεις διεπαφής.", + "passwordEntries": "Καταχωρήσεις κωδικών", + "bookmarks": "Σελιδοδείκτες", + "bookmarkFolders": "Φάκελοι σελιδοδεικτών", + "tasksTotal": "Εργασίες", + "tasksCompleted": "Ολοκληρωμένες", + "tasksOngoing": "Σε εξέλιξη", + "tasksNotStarted": "Δεν ξεκίνησαν", + "projects": "Έργα", + "nosqlConnections": "Συνδέσεις ΒΔ", + "notes": "Σημειώσεις", + "apiClientCollections": "Συλλογές API", + "apiClientEnvironments": "Περιβάλλοντα API", + "apiClientHistory": "Καταχωρήσεις ιστορικού API", + "jsonFormatterDocuments": "Έγγραφα μορφοποιητή JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Αποσπάσματα κώδικα", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Προτιμήσεις διεπαφής", + "subtitle": "Θέμα, γλώσσα και ορατά εργαλεία στην πλευρική γραμμή — από τις ρυθμίσεις λογαριασμού.", + "loadError": "Δεν ήταν δυνατή η φόρτωση προτιμήσεων.", + "loginHint": "Συνδεθείτε για να δείτε τις αποθηκευμένες προτιμήσεις διεπαφής.", + "theme": "Θέμα", + "accent": "Χρώμα έμφασης", + "locale": "Γλώσσα", + "enabledTools": "Ορατά εργαλεία", + "favoriteTools": "Αγαπημένα εργαλεία", + "toolsWithUsage": "Εργαλεία με παρακολούθηση χρήσης" + }, + "signIn": "Σύνδεση", + "noResults": "Κανένα εργαλείο δεν ταιριάζει με την αναζήτησή σας", + "noResultsHint": "Δοκιμάστε διαφορετική λέξη-κλειδί ή καθαρίστε το φίλτρο.", + "filterAll": "Όλα", + "loginCta": { + "description": "Συνδεθείτε για να παρακολουθείτε τα πρόσφατα χρησιμοποιημένα εργαλεία σας." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Φόρτωση...", + "myTasksTitle": "Οι εργασίες μου", + "statsLine": "{total} εργασίες • Ολοκληρώθηκε το {percent}%.", + "statsLineDesktop": "{total} σύνολο • {percent}% ολοκληρώθηκε" + }, + "filters": { + "all": "Ολοι", + "total": "Σύνολο", + "allProjects": "Όλα τα Έργα", + "export": "Εξαγωγή", + "kanban": "Kanban", + "list": "Λίστα", + "searchPlaceholder": "Εργασίες αναζήτησης...", + "searchAria": "Εργασίες αναζήτησης", + "clearSearchAria": "Εκκαθάριση αναζήτησης", + "foundTasks": "Βρέθηκε {count} {count, plural, one {task} other {tasks}}", + "filterByAllAria": "Φιλτράρισμα κατά όλα", + "filterByStatusAria": "Φιλτράρισμα κατά {status}" + }, + "status": { + "not-started": { + "label": "Δεν έχει ξεκινήσει", + "description": "Εργασίες που δεν έχουν ξεκινήσει ακόμα" + }, + "ongoing": { + "label": "Σε εξέλιξη", + "description": "Εργασίες σε εξέλιξη" + }, + "completed": { + "label": "Ολοκληρώθηκε το", + "description": "Εργασίες που έχουν τελειώσει" + } + }, + "priorities": { + "high": "Ψηλά", + "medium": "Μέσον", + "low": "Χαμηλός" + }, + "form": { + "addNewTaskLabel": "Προσθήκη νέας εργασίας", + "multilinePlaceholder": "Προσθήκη λεπτομερειών εργασίας... (Shift+Enter για νέα γραμμή)", + "singlelinePlaceholder": "Τι πρέπει να γίνει; Πατήστε Enter για προσθήκη", + "taskInputAria": "Εισαγωγή εργασιών", + "projectPlaceholder": "Σχέδιο", + "selectProject": "Επιλέξτε Έργο", + "errorSelectProject": "Επιλέξτε ένα έργο ή δημιουργήστε ένα νέο για να προσθέσετε μια εργασία.", + "hintMultiline": "Shift+Enter για νέα γραμμή, Enter για προσθήκη", + "hintSingleline": "Εισαγάγετε για προσθήκη", + "shortcutsAria": "Συντομεύσεις πληκτρολογίου", + "shortcutsLabel": "Συντομεύσεις", + "shortcutAddTask": "Προσθήκη εργασίας", + "shortcutNewLine": "Νέα γραμμή", + "shortcutClear": "Σαφής", + "addButton": "Προσθέτω" + }, + "list": { + "ariaLabel": "Λίστα εργασιών", + "loading": "Φόρτωση των εργασιών σας...", + "emptyTitle": "Δεν βρέθηκαν εργασίες", + "emptyPrimary": "Ξεκινήστε προσθέτοντας την πρώτη σας εργασία παραπάνω! Πατήστε Enter για γρήγορη προσθήκη εργασιών.", + "emptySecondary": "Δοκιμάστε να προσαρμόσετε τα φίλτρα ή το ερώτημα αναζήτησης." + }, + "mobileDrawer": { + "addTaskSrOnly": "Προσθήκη Εργασίας", + "addNewTaskTitle": "Προσθήκη νέας εργασίας", + "addNewTaskDescription": "Δημιουργήστε μια νέα εργασία για να παρακολουθείτε την πρόοδό σας.", + "cancel": "Ματαίωση" + }, + "kanban": { + "ariaLabel": "Πίνακας Kanban", + "loading": "Φόρτωση των εργασιών σας...", + "dropHere": "Απόθεση εργασίας εδώ", + "noTasksYet": "Δεν υπάρχουν ακόμη εργασίες", + "emptyColumnAria": "Κενή στήλη", + "columnAria": "στήλη {title} με {count} εργασίες", + "countInColumnAria": "{count} εργασίες στο {title}" + }, + "taskItem": { + "confirmDelete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την εργασία;", + "statusPlaceholder": "Κατάσταση", + "changeStatusAria": "Αλλαγή κατάστασης εργασίας", + "taskOptionsAria": "Επιλογές εργασιών", + "editTask": "Επεξεργασία εργασίας", + "copyTask": "Αντιγραφή εργασίας", + "copied": "Αντιγράφηκε!", + "deleteTask": "Διαγραφή Εργασίας", + "statusHeading": "Κατάσταση", + "editTaskAria": "Επεξεργασία εργασίας" + }, + "editDialog": { + "title": "Επεξεργασία εργασίας", + "description": "Ενημερώστε τις λεπτομέρειες εργασιών, προσθέστε δευτερεύουσες εργασίες, ορίστε προτεραιότητες και πολλά άλλα.", + "taskTitleLabel": "Τίτλος εργασίας *", + "taskTitlePlaceholder": "Τι πρέπει να γίνει;", + "descriptionLabel": "Περιγραφή", + "descriptionPlaceholder": "Προσθέστε περισσότερες λεπτομέρειες σχετικά με αυτήν την εργασία...", + "statusLabel": "Κατάσταση", + "priorityLabel": "Προτεραιότητα", + "dueDateLabel": "Ημερομηνία λήξης", + "pickDate": "Επιλέξτε μια ημερομηνία", + "clearDate": "Καθαρή ημερομηνία", + "projectLabel": "Σχέδιο", + "selectProjectPlaceholder": "Επιλέξτε ένα έργο", + "noProject": "Κανένα Έργο", + "timeEstimateLabel": "Εκτίμηση χρόνου (λεπτά)", + "timeEstimatePlaceholder": "Πόσο καιρό θα πάρει αυτό;", + "tagsLabel": "Ετικέτες", + "customTagNamePlaceholder": "Όνομα προσαρμοσμένης ετικέτας", + "subtasksLabel": "Υποεργασίες", + "addSubtaskPlaceholder": "Προσθήκη δευτερεύουσας εργασίας...", + "cancel": "Ματαίωση", + "saving": "Οικονομία...", + "saveChanges": "Αποθήκευση αλλαγών" + }, + "export": { + "title": "Εξαγωγή εργασιών", + "description": "Εξάγετε τις εργασίες σας σε μορφές JSON, CSV ή Excel.", + "projectLabel": "Σχέδιο", + "statusLabel": "Κατάσταση", + "tagLabel": "Ετικέτα", + "allProjects": "Όλα τα Έργα", + "allStatuses": "Όλες οι καταστάσεις", + "allTags": "Όλες οι ετικέτες", + "exportAsJsonTitle": "Εξαγωγή ως JSON", + "exportAsJsonDescription": "Εξαγωγή δεδομένων εργασιών, συμπεριλαμβανομένων δευτερευουσών εργασιών, ετικετών και μεταδεδομένων.", + "exportJson": "Εξαγωγή JSON", + "exportAsCsvExcelTitle": "Εξαγωγή ως CSV / Excel", + "exportAsCsvExcelDescription": "Εξαγωγή εργασιών για εφαρμογές υπολογιστικών φύλλων.", + "exportCsv": "Εξαγωγή CSV", + "exportExcel": "Εξαγωγή Excel", + "close": "Κοντά", + "toastNoTasks": "Δεν υπάρχουν εργασίες για εξαγωγή με τα τρέχοντα φίλτρα", + "toastExportedJson": "Έγινε εξαγωγή {count} εργασιών σε JSON!", + "toastExportedCsv": "Έγινε εξαγωγή {count} εργασιών σε CSV!", + "toastExportedExcel": "Έγινε εξαγωγή {count} εργασιών στο Excel!", + "toastFailedJson": "Απέτυχε η εξαγωγή σε JSON", + "toastFailedCsv": "Απέτυχε η εξαγωγή σε CSV", + "toastFailedExcel": "Απέτυχε η εξαγωγή στο Excel", + "unknownProject": "Άγνωστο έργο" + }, + "ack": { + "taskAddedTitle": "Η εργασία προστέθηκε με επιτυχία", + "taskAddedFailedTitle": "Αποτυχία προσθήκης εργασίας", + "taskUpdatedTitle": "Η εργασία ενημερώθηκε με επιτυχία", + "taskUpdatedFailedTitle": "Η ενημέρωση της εργασίας απέτυχε", + "taskStatusUpdateFailedTitle": "Η ενημέρωση της κατάστασης της εργασίας απέτυχε", + "taskMovedTitle": "Η εργασία μετακινήθηκε στο {status}", + "tryAgain": "Δοκιμάστε ξανά.", + "taskDeletedTitle": "Η εργασία \"{text}\" διαγράφηκε", + "undo": "Ξεκάνω", + "taskRestoredTitle": "Έγινε επαναφορά της εργασίας", + "taskRestoredSuccessTitle": "Η εργασία αποκαταστάθηκε με επιτυχία", + "taskRestoreFailedTitle": "Η επαναφορά της εργασίας απέτυχε. Δοκιμάστε ξανά.", + "taskDeleteFailedTitle": "Αποτυχία διαγραφής εργασίας. Δοκιμάστε ξανά.", + "tasksImportedTitle": "Έγινε επιτυχής εισαγωγή {count} {count, plural, one {task} other {tasks}}!", + "tasksImportFailedTitle": "Αποτυχία εισαγωγής εργασιών. Δοκιμάστε ξανά.", + "unknownProject": "Άγνωστο έργο", + "projectCreatedTitle": "Το έργο δημιουργήθηκε με επιτυχία", + "projectCreateFailedTitle": "Αποτυχία δημιουργίας έργου", + "projectUpdatedTitle": "Το έργο ενημερώθηκε με επιτυχία", + "projectUpdateFailedTitle": "Η ενημέρωση του έργου απέτυχε", + "projectDeletedTitle": "Το έργο διαγράφηκε με επιτυχία", + "projectDeleteFailedTitle": "Αποτυχία διαγραφής έργου" + } + }, + "Notes": { + "page": { + "loading": "Φόρτωση..." + }, + "layout": { + "mobileNavTitle": "Σημειώσεις Πλοήγηση", + "openMenuAria": "Ανοίξτε το μενού σημειώσεων" + }, + "sidebar": { + "title": "Σημειώσεις", + "createNoteAria": "Δημιουργία σημείωσης", + "searchPlaceholder": "Αναζήτηση σημειώσεων...", + "loading": "Φόρτωση...", + "noNotesFound": "Δεν βρέθηκαν σημειώσεις.", + "noNotesYet": "Δεν υπάρχουν σημειώσεις ακόμα. Κάντε κλικ στο + για να δημιουργήσετε ένα.", + "inParent": "στο {parentTitle}", + "untitled": "Χωρίς τίτλο", + "addSubPage": "Προσθήκη υποσελίδας", + "delete": "Διαγράφω", + "deleteConfirmTitle": "Διαγραφή σημείωσης;", + "deleteConfirmDescription": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το \"{title}\"; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "cancel": "Ματαίωση", + "confirmDelete": "Διαγράφω", + "noteOptionsAria": "Επιλογές σημειώσεων" + }, + "editor": { + "emptyState": "Επιλέξτε μια σημείωση ή δημιουργήστε μια νέα για να ξεκινήσετε.", + "titlePlaceholder": "Χωρίς τίτλο", + "saving": "Αποθήκευση…", + "ready": "Έτοιμο", + "saved": "Αποθηκεύτηκε {time}", + "stats": "{words} λέξεις · {read} λεπτά ανάγνωσης", + "template": "Πρότυπο", + "applyTemplate": "Εφαρμογή προτύπου", + "exportMdLabel": ".md", + "exportMd": "Εξαγωγή ως Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Εξαγωγή ως HTML", + "focusMode": "Λειτουργία εστίασης", + "exitFocusMode": "Έξοδος από λειτουργία εστίασης", + "save": "Αποθήκευση", + "addTag": "Προσθήκη ετικέτας…", + "removeTag": "Αφαίρεση ετικέτας {tag}", + "loading": "Φόρτωση…", + "searchHint": "για αναζήτηση", + "focusHint": "λειτουργία εστίασης", + "notePath": "Διαδρομή σημείωσης" + }, + "context": { + "authRequiredError": "Ο χρήστης δεν έχει επικυρωθεί", + "defaultTitle": "Χωρίς τίτλο", + "cloudSyncImageError": "Ενεργοποιήστε τον συγχρονισμό cloud για αποθήκευση εικόνων σημειώσεων. Οι σημειώσεις κειμένου παραμένουν τοπικές." + } + }, + "PasswordManager": { + "page": { + "loading": "Φόρτωση...", + "title": "Διαχειριστής κωδικών πρόσβασης", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Απέτυχε η αποκρυπτογράφηση των κωδικών πρόσβασης", + "toastLoadFailed": "Αποτυχία φόρτωσης κωδικών πρόσβασης", + "toastUnlockedAuto": "Το Vault ξεκλειδώθηκε αυτόματα", + "toastAutoUnlockFailed": "Το αυτόματο ξεκλείδωμα απέτυχε. Ξεκλειδώστε χειροκίνητα.", + "toastConnectFailed": "Απέτυχε η σύνδεση με τον διακομιστή", + "toastAutoUnlockWarning": "Το Vault ξεκλειδώθηκε, αλλά το αυτόματο ξεκλείδωμα ενδέχεται να μην λειτουργεί κατά την επαναφόρτωση", + "errorIncorrectPassword": "Λανθασμένος κύριος κωδικός πρόσβασης", + "errorUnlockFailed": "Αποτυχία ξεκλειδώματος θησαυροφυλάκιου", + "errorPasswordsMismatch": "Οι κωδικοί πρόσβασης δεν ταιριάζουν", + "errorPasswordTooShort": "Ο κωδικός πρόσβασης πρέπει να αποτελείται από τουλάχιστον 8 χαρακτήρες", + "errorSetupFailed": "Αποτυχία ρύθμισης θησαυροφυλάκιου", + "errorConnectFailed": "Απέτυχε η σύνδεση με τον διακομιστή.", + "mobileTitleCreate": "Δημιουργήστε το θησαυροφυλάκιο σας", + "mobileTitleWelcome": "Καλώς ήρθες πίσω", + "mobileSubtitleCreate": "Ορίστε έναν κύριο κωδικό πρόσβασης για να προστατεύσετε τα διαπιστευτήριά σας", + "mobileSubtitleUnlock": "Εισαγάγετε τον κύριο κωδικό πρόσβασης για ξεκλείδωμα", + "placeholderMasterPassword": "Κύριος κωδικός πρόσβασης", + "placeholderConfirmPassword": "Επιβεβαίωση κωδικού πρόσβασης", + "labelMasterPassword": "Κύριος κωδικός πρόσβασης", + "labelConfirmPassword": "Επιβεβαίωση κωδικού πρόσβασης", + "placeholderEnterMaster": "Εισαγάγετε τον κύριο κωδικό πρόσβασης", + "placeholderConfirmMaster": "Επιβεβαιώστε τον κύριο κωδικό πρόσβασης", + "buttonCreateVault": "Δημιουργία θησαυροφυλάκιου", + "buttonUnlock": "Ξεκλειδώνω", + "buttonUnlockVault": "Ξεκλειδώστε το θησαυροφυλάκιο", + "footerCreate": "Ο κωδικός πρόσβασής σας δεν αποθηκεύεται ποτέ και δεν μπορεί να ανακτηθεί", + "footerUnlock": "Ασφαλής με κρυπτογράφηση από άκρο σε άκρο", + "desktopTitleSetup": "Ρύθμιση αποθήκευσης κωδικού πρόσβασης", + "desktopTitleUnlock": "Ξεκλειδώστε το θησαυροφυλάκιο", + "desktopDescriptionSetup": "Δημιουργήστε έναν κύριο κωδικό πρόσβασης για να προστατεύσετε τα διαπιστευτήριά σας. Αυτός ο κωδικός πρόσβασης δεν αποθηκεύεται ποτέ και δεν μπορεί να ανακτηθεί.", + "desktopDescriptionUnlock": "Εισαγάγετε τον κύριο κωδικό πρόσβασης για πρόσβαση στα ασφαλή διαπιστευτήριά σας." + }, + "list": { + "loading": "Φόρτωση των κωδικών πρόσβασής σας...", + "emptyTitle": "Το θησαυροφυλάκιο είναι άδειο", + "emptyHintMobile": "Πατήστε το κουμπί + για να προσθέσετε τον πρώτο σας κωδικό πρόσβασης", + "emptyHintDesktop": "Δεν έχετε αποθηκεύσει ακόμα κανέναν κωδικό πρόσβασης. Κάντε κλικ στο κουμπί \"Προσθήκη κωδικού πρόσβασης\" παραπάνω για να ξεκινήσετε.", + "addPassword": "Προσθήκη κωδικού πρόσβασης", + "searchPlaceholderMobile": "Αναζητήστε το θησαυροφυλάκιό σας...", + "searchPlaceholderDesktop": "Θησαυροφυλάκιο αναζήτησης...", + "importExport": "Εισαγωγή / εξαγωγή", + "lockVault": "Θόλος κλειδαριάς", + "allPasswordsTitle": "Όλοι οι κωδικοί πρόσβασης", + "passwordCount": "{count, plural, one {# password} other {# passwords}}", + "vaultHealthDrawerTitle": "Υγεία θησαυροφυλάκιο", + "noResults": "Δεν βρέθηκαν κωδικοί πρόσβασης που να ταιριάζουν με το \"{query}\"", + "tableService": "Υπηρεσία", + "tableUsername": "Όνομα χρήστη", + "tablePassword": "Σύνθημα", + "tableUrl": "URL", + "tableActions": "Δράσεις", + "gridViewAria": "Προβολή πλέγματος", + "listViewAria": "Προβολή λίστας", + "passwordStrengthTitle": "Ισχύς κωδικού πρόσβασης", + "deleteDialogTitle": "Διαγραφή κωδικού πρόσβασης", + "deleteDialogDescription": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον κωδικό πρόσβασης; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "cancel": "Ματαίωση", + "delete": "Διαγράφω", + "edit": "Εκδίδω" + }, + "toasts": { + "copiedPassword": "Ο κωδικός πρόσβασης αντιγράφηκε στο πρόχειρο", + "copiedUsername": "Το όνομα χρήστη αντιγράφηκε στο πρόχειρο", + "passwordDeleted": "Διαγράφηκε ο κωδικός πρόσβασης", + "deleteFailed": "Αποτυχία διαγραφής κωδικού πρόσβασης", + "vaultLocked": "Θόλος κλειδωμένος", + "importedCount": "Έγινε επιτυχής εισαγωγή {count, plural, one {# password} other {# passwords}}" + }, + "form": { + "addTitle": "Προσθήκη νέου κωδικού πρόσβασης", + "editTitle": "Επεξεργασία κωδικού πρόσβασης", + "serviceName": "Όνομα υπηρεσίας", + "usernameEmail": "Όνομα χρήστη / email", + "password": "Σύνθημα", + "urlOptional": "URL ιστότοπου (προαιρετικό)", + "notesOptional": "Σημειώσεις (προαιρετικό)", + "tags": "Ετικέτες", + "placeholderService": "π.χ. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Εισαγάγετε ή δημιουργήστε κωδικό πρόσβασης", + "placeholderUrl": "https://...", + "placeholderNotes": "Πρόσθετες λεπτομέρειες...", + "placeholderTags": "Προσθήκη ετικετών (πατήστε Enter ή κόμμα)", + "strength": "Δύναμη", + "strengthWeak": "Αδύναμος", + "strengthMedium": "Μέσον", + "strengthStrong": "Ισχυρός", + "advancedGenerator": "Προηγμένη γεννήτρια", + "cancel": "Ματαίωση", + "saving": "Οικονομία...", + "savePassword": "Αποθήκευση κωδικού πρόσβασης", + "updating": "Ενημέρωση...", + "updatePassword": "Ενημέρωση κωδικού πρόσβασης", + "addPasswordSrOnly": "Προσθήκη κωδικού πρόσβασης", + "triggerAdd": "Προσθήκη κωδικού πρόσβασης", + "toastSaved": "Ο κωδικός πρόσβασης αποθηκεύτηκε με επιτυχία", + "toastSaveFailed": "Αποτυχία αποθήκευσης κωδικού πρόσβασης", + "toastUpdated": "Ο κωδικός πρόσβασης ενημερώθηκε με επιτυχία", + "toastUpdateFailed": "Αποτυχία ενημέρωσης κωδικού πρόσβασης" + }, + "security": { + "vaultHealth": "Υγεία θησαυροφυλάκιο", + "statusLabel": "Κατάσταση", + "totalPasswords": "Σύνολο κωδικών πρόσβασης", + "activeEntries": "Ενεργές καταχωρήσεις στο θησαυροφυλάκιό σας", + "protected": "Προστατεύεται", + "weakPasswords": "Αδύναμοι κωδικοί πρόσβασης", + "needingImprovement": "Οι κωδικοί πρόσβασης που χρειάζονται βελτίωση", + "allGood": "Όλα καλά", + "reusedPasswords": "Επαναχρησιμοποιημένοι κωδικοί πρόσβασης", + "usedMultipleTimes": "Κωδικοί πρόσβασης που χρησιμοποιούνται πολλές φορές", + "unique": "Μοναδικός", + "total": "Σύνολο", + "weak": "Αδύναμος", + "reused": "Επαναχρησιμοποίηση", + "totalCountLabel": "{count, plural, one {# password} other {# passwords}}", + "weakCountLabel": "{count} αδύναμο", + "allSecure": "Όλα ασφαλή", + "scoreExcellent": "Εξοχος", + "scoreGood": "Καλός", + "scoreFair": "Εκθεση", + "scoreCritical": "Κρίσιμος" + }, + "importExport": { + "title": "Θησαυροφυλάκιο εισαγωγής/εξαγωγής", + "description": "Διαχειριστείτε τα δεδομένα του κωδικού πρόσβασής σας. Μπορείτε να εξαγάγετε το θησαυροφυλάκιό σας σε JSON ή να κάνετε εισαγωγή από ένα αρχείο JSON.", + "tabExport": "Εξαγωγή", + "tabImport": "Εισαγωγή", + "warningTitle": "Προειδοποίηση", + "warningBody": "Τα εξαγόμενα αρχεία περιέχουν τους κωδικούς πρόσβασής σας σε απλό κείμενο. Διατηρήστε αυτό το αρχείο ασφαλές και διαγράψτε το μετά τη χρήση.", + "exportBlurb": "Κατεβάστε ολόκληρο το θησαυροφυλάκιό σας ως αρχείο JSON. Αυτό περιλαμβάνει όλες τις υπηρεσίες, τα ονόματα χρήστη, τους κωδικούς πρόσβασης και τις σημειώσεις.", + "downloadJson": "Λήψη εξαγωγής JSON", + "pasteLabel": "Επικόλληση δεδομένων JSON", + "pastePlaceholder": "[{\"υπηρεσία\": \"Παράδειγμα\", \"όνομα χρήστη\": \"χρήστης\", \"κωδικός πρόσβασης\": \"123\", ...}]", + "pasteHint": "Επικολλήστε εδώ το περιεχόμενο ενός αρχείου JSON που έχει εξαχθεί προηγουμένως.", + "importing": "Εισαγωγή...", + "importPasswords": "Εισαγωγή κωδικών πρόσβασης", + "trigger": "Εισαγωγή / εξαγωγή", + "toastExported": "Το Vault εξήχθη με επιτυχία", + "toastExportFailed": "Αποτυχία εξαγωγής θησαυροφυλάκιου", + "toastImportFailed": "Αποτυχία εισαγωγής: μη έγκυρη μορφή JSON" + }, + "card": { + "copyUsername": "Αντιγραφή ονόματος χρήστη", + "openUrl": "Ανοίξτε τη διεύθυνση URL", + "strength": "Δύναμη" + }, + "swipeable": { + "copy": "Αντίγραφο", + "delete": "Διαγράφω", + "copyPassword": "Αντιγραφή κωδικού πρόσβασης", + "copyUsername": "Αντιγραφή ονόματος χρήστη", + "edit": "Εκδίδω", + "deleteAction": "Διαγράφω" + }, + "floatingMenu": { + "lockVault": "Θόλος κλειδαριάς", + "importExport": "Εισαγωγή / εξαγωγή", + "addPassword": "Προσθήκη κωδικού πρόσβασης" + }, + "generator": { + "tabPassword": "Σύνθημα", + "tabPassphrase": "Φράση πρόσβασης", + "strength": "Δύναμη", + "lengthLabel": "Μήκος: {length} χαρακτήρες", + "lengthBadge": "{length} χαρακτήρες", + "characterSets": "Σύνολα χαρακτήρων", + "uppercase": "Κεφαλαία", + "lowercase": "Πεζά", + "numbers": "Αριθμοί", + "symbols": "Σύμβολα", + "wordCountLabel": "Αριθμός λέξεων: {count}", + "wordCountBadge": "{count, plural, one {# word} other {# words}}", + "separator": "Διαχωριστής", + "capitalization": "Κεφαλοποίηση", + "includeNumber": "Συμπεριλάβετε τον αριθμό" + } + }, + "EnvironmentManager": { + "subtitle": "Κρυπτογραφημένες μεταβλητές περιβάλλοντος ανά έργο και περιβάλλον.", + "page": { + "loading": "Φόρτωση…", + "title": "Διαχειριστής περιβάλλοντος", + "loadFailed": "Αποτυχία φόρτωσης συνόλων περιβάλλοντος" + }, + "list": { + "title": "Περιβάλλοντα", + "loading": "Φόρτωση των συνόλων σας…", + "emptyTitle": "Δεν υπάρχουν ακόμα σύνολα περιβάλλοντος", + "emptyHint": "Δημιουργήστε ένα σύνολο για κάθε έργο και περιβάλλον (π.χ. κλειδιά API παραγωγής). Όλα κρυπτογραφούνται στο πρόγραμμα περιήγησης πριν την αποστολή.", + "searchPlaceholder": "Αναζήτηση έργου, περιβάλλοντος, ετικετών, σημειώσεων…", + "tagFilterPlaceholder": "Φιλτράρισμα κατά ετικέτα", + "variableCount": "{count, plural, one {# μεταβλητή} other {# μεταβλητές}}", + "updatedAgo": "Ενημερώθηκε {time}", + "copyDotEnv": "Αντιγραφή .env", + "edit": "Επεξεργασία", + "delete": "Διαγραφή", + "cancel": "Ακύρωση", + "confirmDelete": "Διαγραφή", + "deleteDialogTitle": "Διαγραφή συνόλου περιβάλλοντος;", + "deleteDialogDescription": "Αφαιρεί το κρυπτογραφημένο αρχείο από τον λογαριασμό σας. Δεν μπορεί να αναιρεθεί.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": "Το .env αντιγράφηκε στο πρόχειρο", + "deleted": "Το σύνολο περιβάλλοντος διαγράφηκε", + "deleteFailed": "Η διαγραφή απέτυχε" + }, + "form": { + "addTitle": "Νέο σύνολο περιβάλλοντος", + "editTitle": "Επεξεργασία συνόλου περιβάλλοντος", + "triggerAdd": "Προσθήκη συνόλου περιβάλλοντος", + "addSrOnly": "Προσθήκη συνόλου περιβάλλοντος", + "project": "Έργο", + "environment": "Περιβάλλον", + "placeholderProject": "π.χ. billing-api", + "placeholderEnvironment": "π.χ. production", + "pasteEnvLabel": "Επικόλληση .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Συγχώνευση επικολλημένων γραμμών", + "variables": "Μεταβλητές", + "addRow": "Προσθήκη γραμμής", + "keyPlaceholder": "ΚΛΕΙΔΙ", + "valuePlaceholder": "τιμή", + "notesOptional": "Σημειώσεις (προαιρετικό)", + "placeholderNotes": "Πολιτική περιστροφής, ιδιοκτήτης, σύνδεσμοι…", + "tags": "Ετικέτες", + "placeholderTags": "Προσθέστε ετικέτες (Enter ή κόμμα)", + "cancel": "Ακύρωση", + "save": "Αποθήκευση", + "saving": "Αποθήκευση…", + "update": "Ενημέρωση", + "updating": "Ενημέρωση…", + "toastSaved": "Το σύνολο περιβάλλοντος αποθηκεύτηκε", + "toastSaveFailed": "Η αποθήκευση απέτυχε", + "toastUpdated": "Το σύνολο περιβάλλοντος ενημερώθηκε", + "toastUpdateFailed": "Η ενημέρωση απέτυχε", + "toastPasteEmpty": "Δεν βρέθηκαν γραμμές ΚΛΕΙΔΙ=τιμή", + "toastPasteApplied": "Συγχωνεύτηκαν {count} μεταβλητές από την επικόλληση", + "toastProjectEnvRequired": "Το έργο και το περιβάλλον είναι υποχρεωτικά", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Φόρτωση..." + }, + "manager": { + "foldersHeading": "Φάκελοι", + "tagsHeading": "Ετικέτες", + "tagsMore": "+{count} ακόμη", + "tagsShowLess": "Show less", + "searchPlaceholder": "Αναζήτηση στους σελιδοδείκτες σας...", + "import": "Εισαγωγή", + "export": "Εξαγωγή", + "exportAsHtml": "Εξαγωγή ως HTML", + "exportAsJson": "Εξαγωγή ως JSON", + "addBookmark": "Προσθήκη σελιδοδείκτη", + "matchingQuery": "αντιστοίχιση \"{query}\"", + "bookmarkCount": "{count, plural, one {# bookmark} other {# bookmarks}}", + "allBookmarks": "Όλοι οι σελιδοδείκτες", + "uncategorized": "Χωρίς κατηγοριοποίηση", + "unknownFolder": "Αγνωστος" + }, + "card": { + "urlCopied": "Η διεύθυνση URL αντιγράφηκε στο πρόχειρο", + "bookmarkDeleted": "Ο σελιδοδείκτης διαγράφηκε", + "edit": "Εκδίδω", + "delete": "Διαγράφω", + "copyUrl": "Αντιγραφή διεύθυνσης URL", + "deleteConfirmTitle": "Διαγραφή σελιδοδείκτη;", + "deleteConfirmDescription": "Αυτό θα διαγράψει οριστικά το \"{title}\".", + "cancel": "Ματαίωση" + }, + "grid": { + "emptyTitle": "Δεν υπάρχουν ακόμη σελιδοδείκτες", + "emptyDescription": "Προσθέστε τον πρώτο σας σελιδοδείκτη ή εισαγάγετε από το πρόγραμμα περιήγησής σας για να ξεκινήσετε." + }, + "folderTree": { + "allBookmarks": "Όλοι οι σελιδοδείκτες", + "addSubfolder": "Προσθήκη υποφακέλου", + "rename": "Μετονομάζω", + "deleteFolderTitle": "Διαγραφή του φακέλου \"{name}\";", + "deleteFolderDescription": "Αυτό θα διαγράψει τον φάκελο και όλους τους υποφακέλους του. Οι σελιδοδείκτες σε αυτούς τους φακέλους θα μετακινηθούν στο Uncategorized." + }, + "import": { + "title": "Εισαγωγή σελιδοδεικτών", + "description": "Εισαγάγετε σελιδοδείκτες από το εξαγόμενο αρχείο του προγράμματος περιήγησής σας.", + "parseError": "Απέτυχε η ανάλυση του αρχείου σελιδοδεικτών. Βεβαιωθείτε ότι πρόκειται για έγκυρη εξαγωγή σελιδοδεικτών.", + "successToast": "Εισήχθησαν {σελιδοδείκτες} σελιδοδείκτες και φάκελοι {folders}", + "errorToast": "Αποτυχία εισαγωγής σελιδοδεικτών", + "chromeInstruction": "Μενού → Σελιδοδείκτες → Διαχείριση σελιδοδεικτών → ⋮ → Εξαγωγή σελιδοδεικτών", + "firefoxInstruction": "Μενού → Σελιδοδείκτες → Διαχείριση σελιδοδεικτών → Εισαγωγή και δημιουργία αντιγράφων ασφαλείας → Εξαγωγή", + "safariInstruction": "Αρχείο → Εξαγωγή σελιδοδεικτών...", + "edgeInstruction": "Μενού → Αγαπημένα → ⋯ → Εξαγωγή αγαπημένων", + "dropZoneTitle": "Αποθέστε το αρχείο σελιδοδεικτών σας εδώ", + "dropZoneHint": "ή κάντε κλικ για περιήγηση (HTML ή JSON)", + "selectedFileHint": "Κάντε κλικ για να επιλέξετε ένα διαφορετικό αρχείο", + "errorTitle": "Σφάλμα", + "previewTitle": "Εισαγωγή προεπισκόπησης", + "previewBookmarks": "{count, plural, one {# bookmark} other {# bookmarks}}", + "previewFolders": "{count, plural, one {# folder} other {# folders}}", + "importModeLabel": "Λειτουργία εισαγωγής", + "modeMerge": "Συγχώνευση με υπάρχοντες σελιδοδείκτες", + "modeReplace": "Αντικαταστήστε όλους τους υπάρχοντες σελιδοδείκτες", + "cancel": "Ματαίωση", + "importButton": "Εισαγωγή", + "importButtonWithCount": "Εισαγωγή {count, plural, one {# bookmark} other {# bookmarks}}" + }, + "addBookmark": { + "titleAdd": "Προσθήκη σελιδοδείκτη", + "titleEdit": "Επεξεργασία σελιδοδείκτη", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Εισαγάγετε μια έγκυρη διεύθυνση URL", + "titleLabel": "Τίτλος", + "titlePlaceholder": "Τίτλος σελιδοδείκτη", + "titleRequired": "Εισαγάγετε έναν τίτλο", + "descriptionLabel": "Περιγραφή (προαιρετικό)", + "descriptionPlaceholder": "Προσθέστε μια σημείωση σχετικά με αυτόν τον σελιδοδείκτη", + "folderLabel": "Ντοσιέ", + "folderPlaceholder": "Επιλέξτε φάκελο", + "noFolder": "Χωρίς φάκελο", + "tagsLabel": "Ετικέτες", + "tagsPlaceholder": "Προσθήκη ετικετών (πατήστε Enter)", + "toastUpdated": "Ο σελιδοδείκτης ενημερώθηκε", + "toastAdded": "Προστέθηκε σελιδοδείκτης", + "cancel": "Ματαίωση", + "submitAdd": "Προσθήκη σελιδοδείκτη", + "submitUpdate": "Ενημέρωση σελιδοδείκτη" + }, + "addFolder": { + "title": "Δημιουργία φακέλου", + "nameLabel": "Όνομα φακέλου", + "namePlaceholder": "Ο φάκελος μου", + "nameRequired": "Εισαγάγετε ένα όνομα φακέλου", + "parentLabel": "Γονικός φάκελος", + "parentPlaceholder": "Επιλέξτε γονικό φάκελο", + "rootParent": "Root (Χωρίς γονέα)", + "toastCreated": "Ο φάκελος δημιουργήθηκε", + "cancel": "Ματαίωση", + "submit": "Δημιουργία φακέλου" + }, + "editFolder": { + "title": "Μετονομασία φακέλου", + "nameLabel": "Όνομα φακέλου", + "namePlaceholder": "Όνομα φακέλου", + "nameRequired": "Εισαγάγετε ένα όνομα φακέλου", + "toastRenamed": "Ο φάκελος μετονομάστηκε", + "cancel": "Ματαίωση", + "save": "Εκτός" + } + }, + "EmailValidator": { + "header": { + "title": "Email Validator", + "description": "Επαληθεύστε και επικυρώστε διευθύνσεις email για σύνταξη, τομέα και δυνατότητα παράδοσης." + }, + "tabs": { + "single": "Ενιαία επαλήθευση", + "bulk": "Μαζική μεταφόρτωση" + }, + "single": { + "cardTitle": "Ενιαία επαλήθευση ηλεκτρονικού ταχυδρομείου", + "cardDescription": "Εισαγάγετε μια διεύθυνση email για να ελέγξετε την παράδοση και τη φήμη της.", + "placeholder": "Εισαγάγετε τη διεύθυνση email...", + "clearAria": "Εκκαθάριση email", + "validate": "Επικυρώνω", + "validating": "Επικύρωση...", + "formatError": "Εισαγάγετε μια έγκυρη διεύθυνση email" + }, + "toasts": { + "errorTitle": "Σφάλμα", + "enterEmail": "Εισαγάγετε μια διεύθυνση email", + "invalidFormatTitle": "Μη έγκυρη μορφή email", + "invalidFormatDescription": "Εισαγάγετε μια έγκυρη διεύθυνση email", + "validateFailedDescription": "Παρουσιάστηκε σφάλμα κατά την επικύρωση του email", + "noEmailsTitle": "Δεν βρέθηκαν μηνύματα ηλεκτρονικού ταχυδρομείου", + "noEmailsDescription": "Βεβαιωθείτε ότι το Excel σας έχει μια στήλη \"email\".", + "fileTooLargeTitle": "Το αρχείο είναι πολύ μεγάλο", + "fileTooLargeDescription": "Βρέθηκαν {total} μηνύματα ηλεκτρονικού ταχυδρομείου. Μόνο τα πρώτα 4000 θα επικυρωθούν.", + "successTitle": "Επιτυχία", + "successValidated": "Επικυρώθηκαν με επιτυχία {count} email.", + "batchFailedTitle": "Η επικύρωση παρτίδας απέτυχε", + "batchFailedDescription": "Παρουσιάστηκε σφάλμα κατά την επεξεργασία της μαζικής επικύρωσης.", + "noExportTitle": "Δεν υπάρχουν δεδομένα για εξαγωγή", + "noExportValidOnly": "Δεν βρέθηκαν έγκυρα μηνύματα ηλεκτρονικού ταχυδρομείου για εξαγωγή.", + "noExportAll": "Δεν υπάρχουν αποτελέσματα για εξαγωγή.", + "exportSuccessTitle": "Η εξαγωγή ολοκληρώθηκε με επιτυχία", + "exportSuccessExcelAll": "Έγινε εξαγωγή {count} μηνυμάτων ηλεκτρονικού ταχυδρομείου στο Excel.", + "exportSuccessExcelValid": "Εξήχθησαν {count} έγκυρα μηνύματα ηλεκτρονικού ταχυδρομείου στο Excel.", + "exportSuccessCsvAll": "Έγινε εξαγωγή {count} μηνυμάτων ηλεκτρονικού ταχυδρομείου σε CSV.", + "exportSuccessCsvValid": "Εξήχθησαν {count} έγκυρα μηνύματα ηλεκτρονικού ταχυδρομείου σε CSV." + }, + "disposableAlert": { + "title": "Εντοπίστηκε email μιας χρήσης", + "description": "Αυτή η διεύθυνση email ανήκει σε πάροχο email μιας χρήσης. Μπορεί να είναι προσωρινό ή μη ασφαλές." + }, + "score": { + "reliabilityTitle": "Βαθμολογία αξιοπιστίας", + "scoreLabel": "Σκορ" + }, + "details": { + "sectionTitle": "Λεπτομέρειες επικύρωσης" + }, + "validation": { + "syntaxLabel": "Έγκυρη σύνταξη", + "syntaxTooltip": "Ελέγχει εάν η διεύθυνση ηλεκτρονικού ταχυδρομείου ακολουθεί τη σωστή μορφή (π.χ. user@domain.com)", + "domainLabel": "Ο τομέας υπάρχει", + "domainTooltip": "Επαληθεύει ότι το όνομα τομέα στη διεύθυνση email υπάρχει πράγματι", + "mxLabel": "Βρέθηκαν εγγραφές MX", + "mxTooltip": "Ελέγχει εάν ο τομέας έχει εγγραφές ανταλλαγής αλληλογραφίας (MX) διαμορφωμένες για λήψη email", + "mailboxLabel": "Το γραμματοκιβώτιο υπάρχει", + "mailboxTooltip": "Επαληθεύει ότι το συγκεκριμένο γραμματοκιβώτιο/λογαριασμός υπάρχει στον διακομιστή αλληλογραφίας", + "disposableLabel": "Email μιας χρήσης", + "disposableTooltip": "Υποδεικνύει εάν αυτό το μήνυμα ηλεκτρονικού ταχυδρομείου ανήκει σε μια προσωρινή/μιας χρήσης υπηρεσία email (μπορεί να είναι επικίνδυνο)", + "roleBasedLabel": "Email με βάση ρόλους", + "roleBasedTooltip": "Προσδιορίζει εάν πρόκειται για email βασισμένο σε ρόλους (π.χ. support@, info@) αντί για προσωπική διεύθυνση" + }, + "copy": { + "emailCopied": "Το email αντιγράφηκε!", + "copyTooltip": "Αντιγραφή email" + }, + "bulk": { + "dropHere": "Ρίξτε το αρχείο σας εδώ", + "dragHere": "Σύρετε και αποθέστε το αρχείο σας εδώ", + "uploadHint": "Ανεβάστε ένα αρχείο .xlsx, .xls ή .csv που περιέχει μια στήλη \"email\" για επικύρωση έως και 4000 μηνυμάτων ηλεκτρονικού ταχυδρομείου.", + "filePrefix": "Αρχείο:", + "selectFile": "Επιλέξτε Αρχείο", + "downloadTemplate": "Λήψη προτύπου" + }, + "bulkLoading": { + "title": "Επικύρωση μηνυμάτων ηλεκτρονικού ταχυδρομείου...", + "batchProgress": "Επεξεργασία παρτίδας {current} από {total}..." + }, + "stats": { + "totalProcessed": "Σύνολο επεξεργασίας", + "valid": "Εγκυρος", + "risky": "Επικίνδυνος", + "invalid": "Ακυρος" + }, + "results": { + "detailedTitle": "Αναλυτικά Αποτελέσματα", + "searchPlaceholder": "Αναζήτηση email...", + "newVerification": "Νέα επαλήθευση", + "exportAll": "Εξαγωγή όλων", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Έγκυρη εξαγωγή ({count})" + }, + "showingResults": "Εμφάνιση {shown} από {total} αποτελέσματα", + "table": { + "email": "E-mail", + "status": "Κατάσταση", + "score": "Σκορ", + "checks": "Έλεγχοι" + }, + "noSearchResults": "Δεν βρέθηκαν αποτελέσματα που να ταιριάζουν με το \"{query}\"", + "checksTooltip": { + "syntax": "Σύνταξη", + "domain": "Πεδίο ορισμού", + "mx": "MX", + "mailbox": "Γραμματοκιβώτιο" + }, + "exportColumns": { + "email": "E-mail", + "status": "Κατάσταση", + "score": "Σκορ", + "syntax": "Σύνταξη", + "domain": "Πεδίο ορισμού", + "mx": "MX", + "mailbox": "Γραμματοκιβώτιο", + "disposable": "Διαθέσιμος", + "roleBased": "RoleBased", + "valid": "Εγκυρος", + "invalid": "Ακυρος", + "yes": "Ναί", + "no": "Οχι" + }, + "sheets": { + "validEmails": "Έγκυρα email", + "results": "Αποτελέσματα", + "template": "Περίγραμμα" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Explorer", + "heroSubtitle": "Συνδεθείτε στις βάσεις δεδομένων σας για να διαχειριστείτε συλλογές, να εκτελέσετε ερωτήματα και να οπτικοποιήσετε τα δεδομένα σας.", + "featureMultiTitle": "Πολυσύνδεση", + "featureMultiDesc": "Διαχειριστείτε πολλαπλές συνδέσεις βάσεων δεδομένων ταυτόχρονα.", + "featureMongoTitle": "Υποστήριξη MongoDB", + "featureMongoDesc": "Εγγενής υποστήριξη για MongoDB με προηγμένο φιλτράρισμα.", + "featureQueryTitle": "Έξυπνο ερώτημα", + "featureQueryDesc": "Ισχυρό πρόγραμμα δημιουργίας ερωτημάτων με επισήμανση σύνταξης.", + "connectCta": "Συνδεθείτε στο MongoDB", + "footerNote": "Αυτήν τη στιγμή υποστηρίζεται το {db}. Περισσότερες βάσεις δεδομένων έρχονται σύντομα.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Επιλέξτε μια Συλλογή", + "selectCollectionDesc": "Επιλέξτε μια συλλογή από την πλαϊνή γραμμή για να προβάλετε έγγραφα και να ξεκινήσετε την εξερεύνηση των δεδομένων σας.", + "dialogAddConnection": "Προσθήκη σύνδεσης", + "toastConnectionAdded": "Η σύνδεση προστέθηκε", + "toastFetchFailed": "Αποτυχία λήψης εγγράφων για {collection}", + "toastDocDeleted": "Το έγγραφο διαγράφηκε", + "deleteDocTitle": "Είσαι απολύτως σίγουρος;", + "deleteDocDescription": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. Αυτό θα διαγράψει οριστικά το έγγραφο από τη συλλογή σας.", + "closeAllTitle": "Κλείσιμο όλων των καρτελών;", + "closeAllDescription": "Είστε βέβαιοι ότι θέλετε να κλείσετε όλες τις καρτέλες; Τυχόν μη αποθηκευμένες αλλαγές στα φίλτρα ερωτημάτων θα χαθούν.", + "closeAllConfirm": "Κλείσιμο όλων", + "cancel": "Ματαίωση", + "delete": "Διαγράφω" + }, + "connection": { + "titleEdit": "Επεξεργασία σύνδεσης", + "titleConnect": "Συνδεθείτε στο MongoDB", + "subtitleEdit": "Ενημερώστε τα στοιχεία σύνδεσής σας παρακάτω.", + "subtitleConnect": "Εισαγάγετε τη συμβολοσειρά σύνδεσης MongoDB για να ξεκινήσετε την εξερεύνηση των δεδομένων σας.", + "labelName": "Ονομα", + "placeholderName": "π.χ. DB Παραγωγής", + "labelConnectionString": "Συμβολοσειρά σύνδεσης", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Μορφή: mongodb://username:password@host:port/database", + "testConnection": "Δοκιμή σύνδεσης", + "connecting": "Συνδετικός...", + "updateConnect": "Ενημέρωση & Σύνδεση", + "connect": "Συνδέω", + "cancelEdit": "Ακύρωση επεξεργασίας", + "savedConnections": "Αποθηκευμένες συνδέσεις", + "emptySaved": "Δεν υπάρχουν ακόμα αποθηκευμένες συνδέσεις.", + "lastUsed": "Τελευταία χρήση {time}", + "toastTestOk": "Επιτυχής σύνδεση!", + "toastTestFail": "Η σύνδεση απέτυχε: {message}", + "toastUpdated": "Η σύνδεση ενημερώθηκε", + "confirmDelete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την αποθηκευμένη σύνδεση;", + "toastDeletedConn": "Η σύνδεση διαγράφηκε", + "toastDeleteFail": "Η διαγραφή της σύνδεσης απέτυχε", + "copyStringTitle": "Αντιγραφή συμβολοσειράς σύνδεσης", + "toastStringCopied": "Η συμβολοσειρά σύνδεσης αντιγράφηκε!", + "editTitle": "Εκδίδω", + "deleteTitle": "Διαγράφω" + }, + "sidebar": { + "explorer": "Εξερευνητής", + "add": "Προσθέτω", + "tooltipAdd": "Προσθήκη νέας σύνδεσης", + "tooltipRefresh": "Ανανέωση συνδέσεων", + "searchPlaceholder": "Ερευνα...", + "loadingConnections": "Φόρτωση συνδέσεων...", + "noConnectionsFound": "Δεν βρέθηκαν συνδέσεις", + "toastLoadFail": "Η φόρτωση των συνδέσεων απέτυχε", + "toastConnectFail": "Απέτυχε η σύνδεση στο {name}", + "toastCollectionsFail": "Απέτυχε η ανάκτηση συλλογών για {name}", + "toastRenamed": "Η σύνδεση μετονομάστηκε", + "toastRenameFail": "Η μετονομασία της σύνδεσης απέτυχε", + "confirmDeleteConnection": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη σύνδεση \"{name}\";", + "toastDeleted": "Η σύνδεση διαγράφηκε", + "toastDeleteConnFail": "Η διαγραφή της σύνδεσης απέτυχε", + "confirmDropDb": "Είστε βέβαιοι ότι θέλετε να απορρίψετε τη βάση δεδομένων \"{name}\"; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "toastDbDropped": "Η βάση δεδομένων {name} απορρίφθηκε", + "confirmDropCollection": "Είστε βέβαιοι ότι θέλετε να απορρίψετε τη συλλογή \"{name}\"; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "toastCollectionDropped": "Η συλλογή {name} απορρίφθηκε", + "toastCollectionRenamed": "Η συλλογή μετονομάστηκε σε {name}", + "toastDatabaseRenamed": "Η βάση δεδομένων μετονομάστηκε σε {name}", + "connecting": "Συνδετικός...", + "connectionFailed": "Η σύνδεση απέτυχε", + "noDatabases": "Χωρίς βάσεις δεδομένων", + "loading": "Φόρτωση...", + "noCollections": "Χωρίς συλλογές", + "renameCollectionTitle": "Μετονομασία συλλογής", + "renameDatabaseTitle": "Μετονομασία βάσης δεδομένων", + "placeholderNewCollection": "Όνομα νέας συλλογής", + "placeholderNewDatabase": "Νέο όνομα βάσης δεδομένων", + "rename": "Μετονομάζω", + "refresh": "Φρεσκάρω", + "copyDbName": "Αντιγραφή ονόματος DB", + "toastDbNameCopied": "Το όνομα βάσης δεδομένων αντιγράφηκε στο πρόχειρο", + "dropDatabase": "Απόθεση βάσης δεδομένων", + "dropCollection": "Drop Collection", + "menuRename": "Μετονομάζω", + "menuCopyConnectionString": "Αντιγραφή συμβολοσειράς σύνδεσης", + "menuDeleteConnection": "Διαγραφή σύνδεσης", + "toastSidebarStringCopied": "Η συμβολοσειρά σύνδεσης αντιγράφηκε!", + "cancel": "Ματαίωση", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} έγγραφα", + "table": "Τραπέζι", + "json": "JSON", + "tree": "Δέντρο", + "tooltipTable": "Προβολή πίνακα", + "tooltipJson": "Προβολή JSON", + "tooltipTree": "Θέα δέντρου", + "itemsPerPage": "Στοιχεία ανά σελίδα", + "perPage": "{n} / σελίδα", + "prevPage": "Προηγούμενη Σελίδα", + "nextPage": "Επόμενη Σελίδα", + "expandAll": "Ανάπτυξη όλων", + "collapseAll": "Σύμπτυξη όλων", + "refreshData": "Ανανέωση δεδομένων", + "insert": "Εισάγω", + "export": "Εξαγωγή", + "actions": "Δράσεις", + "invalidJson": "Μη έγκυρο περιεχόμενο JSON", + "copiedClipboard": "Αντιγράφηκε στο πρόχειρο", + "docUpdated": "Το έγγραφο ενημερώθηκε με επιτυχία", + "docInserted": "Το έγγραφο εισήχθη με επιτυχία", + "invalidJsonShort": "Μη έγκυρο JSON", + "docCopied": "Το έγγραφο αντιγράφηκε στο πρόχειρο", + "openFullEditor": "Άνοιγμα σε πλήρη επεξεργασία", + "cellArray": "Πίνακας ({length})", + "cellObject": "Αντικείμενο", + "cellArrayBracket": "Πίνακας[{length}]", + "emptyFilterTitle": "Δεν υπάρχουν έγγραφα που να αντιστοιχούν στο ερώτημά σας", + "emptyFilterHint": "Δοκιμάστε να προσαρμόσετε το φίλτρο σας ή να διαγράψετε το ερώτημα.", + "clearFilter": "Διαγραφή φίλτρου", + "emptyCollectionTitle": "Αυτή η συλλογή είναι άδεια", + "emptyCollectionHint": "Εισαγάγετε ένα έγγραφο για να ξεκινήσετε.", + "insertDocument": "Εισαγωγή εγγράφου", + "formatJson": "Μορφοποίηση JSON", + "compactJson": "Συμπαγές JSON", + "copyClipboard": "Αντιγραφή στο Πρόχειρο", + "copyJson": "Αντιγράψτε το JSON", + "duplicate": "Αντίγραφο", + "editDoc": "Επεξεργασία εγγράφου", + "deleteDoc": "Διαγραφή εγγράφου", + "documentLabel": "Έγγραφο {n}", + "editDialogTitle": "Επεξεργασία εγγράφου", + "insertDialogTitle": "Εισαγωγή εγγράφου", + "viewValueTitle": "Προβολή αξίας", + "saveChanges": "Αποθήκευση αλλαγών", + "close": "Κοντά", + "cancel": "Ματαίωση", + "insertButton": "Εισάγω", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Εφαρμόστηκε ενεργό φίλτρο", + "filterActiveTooltip": "Ενεργό φίλτρο", + "closeAll": "Κλείσιμο όλων" + }, + "query": { + "migrateHistory": "Μετεγκατάσταση ιστορικού ερωτημάτων στο cloud", + "saveHistoryFail": "Αποτυχία αποθήκευσης ιστορικού ερωτημάτων", + "deleteQueryFail": "Αποτυχία διαγραφής ερωτήματος", + "invalidJsonQuery": "Μη έγκυρο ερώτημα JSON", + "placeholder": "π.χ. { \"status\": \"active\" }", + "clearQuery": "Εκκαθάριση ερωτήματος", + "openAdvanced": "Ανοίξτε το Advanced Editor", + "visualBuilder": "Visual Query Builder", + "filterRules": "Κανόνες φίλτρου", + "addRule": "Προσθήκη κανόνα", + "noFilters": "Δεν εφαρμόστηκαν φίλτρα. Προσθέστε έναν κανόνα για να φιλτράρετε έγγραφα.", + "where": "ΟΠΟΥ", + "and": "ΚΑΙ", + "fieldPlaceholder": "Πεδίο", + "opPlaceholder": "Op", + "typePlaceholder": "Τύπος", + "opRegex": "Regex", + "opIn": "Σε", + "opNin": "Όχι μέσα", + "opExists": "Υπάρχει", + "typeAuto": "Αυτο", + "typeString": "Σειρά", + "typeNumber": "Αριθμός", + "typeBoolean": "Boolean", + "typeDate": "Ημερομηνία", + "typeObjectId": "ObjectId", + "true": "Αληθής", + "false": "Ψευδής", + "valuePlaceholder": "Αξία", + "valueListPlaceholder": "τιμή 1, τιμή 2", + "clearFilters": "Διαγραφή φίλτρων", + "applyFilters": "Εφαρμογή φίλτρων", + "queryHistory": "Ιστορικό ερωτημάτων", + "noSavedQueries": "Δεν υπάρχουν ακόμα αποθηκευμένα ερωτήματα", + "advancedEditorTitle": "Σύνθετη επεξεργασία ερωτημάτων", + "templatesPlaceholder": "Πρότυπα", + "templateEmptyAgg": "Κενή Συγκέντρωση", + "templateMatch": "$match (Φίλτρο)", + "templateLookup": "$lookup (Εγγραφή)", + "templateGroup": "$group (Count/Sum)", + "format": "Σχήμα και διάταξις βιβλίου", + "formattedJson": "Μορφοποιημένο JSON", + "runQuery": "Εκτελέστε το ερώτημα" + }, + "export": { + "selectFieldsError": "Επιλέξτε τουλάχιστον ένα πεδίο για εξαγωγή", + "success": "Έγινε επιτυχής εξαγωγή στο {filename}", + "fail": "Αποτυχία εξαγωγής δεδομένων", + "title": "Εξαγωγή δεδομένων", + "formatLabel": "Μορφή εξαγωγής", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Επιλέξτε Πεδία", + "selectAll": "Επιλέξτε Όλα", + "fieldsSelected": "{count, plural, one {# επιλεγμένο πεδίο} other {# επιλεγμένα πεδία}}", + "cancel": "Ματαίωση", + "export": "Εξαγωγή", + "sheetName": "Δεδομένα" + }, + "jsonTree": { + "typeLabel": "Τύπος: {type}", + "itemSummary": "{count, plural, one {# item} other {# items}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Νέο αίτημα", + "importedRequest": "Εισαγόμενο αίτημα", + "untitledRequest": "Αίτημα χωρίς τίτλο" + }, + "toasts": { + "curlImported": "Το cURL εισήχθη με επιτυχία", + "curlParseFailed": "Απέτυχε η ανάλυση της εντολής cURL", + "invalidJsonBody": "Μη έγκυρο σώμα JSON", + "requestFailed": "Το αίτημα απέτυχε: {message}", + "curlPasted": "Το cURL επικολλήθηκε και αναλύθηκε με επιτυχία", + "responseCopied": "Η απάντηση αντιγράφηκε στο πρόχειρο", + "codeCopied": "Ο κώδικας αντιγράφηκε στο πρόχειρο", + "copyFailed": "Αποτυχία αντιγραφής στο πρόχειρο" + }, + "layout": { + "collections": "Συλλογές", + "closeSidebar": "Κλείσιμο πλαϊνής γραμμής", + "openSidebar": "Ανοίξτε την πλαϊνή γραμμή", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Μέθοδος", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Αποστολή...", + "send": "Στέλνω", + "invalidJsonBodyHelp": "Αδύνατη η αποστολή: το σώμα JSON δεν είναι έγκυρο" + }, + "requestTabs": { + "params": "Params", + "headers": "Κεφαλίδες", + "body": "Σώμα", + "auth": "Auth", + "bodyType": "Σωματότυπος", + "selectType": "Επιλέξτε τύπο", + "bodyNone": "Κανένας", + "bodyJson": "JSON", + "bodyText": "Κείμενο", + "noBodyHint": "Δεν υπάρχει περιεχόμενο σώματος για αυτό το αίτημα", + "switchToJson": "Μετάβαση στο σώμα JSON", + "authType": "Τύπος Auth", + "noAuth": "Όχι auth", + "bearerToken": "Κουπόνι φορέα", + "basicAuth": "Βασικό auth", + "apiKey": "Κλειδί API", + "labelToken": "Ενδειξη", + "placeholderBearerToken": "Κουπόνι φορέα", + "labelUsername": "Όνομα χρήστη", + "placeholderUsername": "Όνομα χρήστη", + "labelPassword": "Σύνθημα", + "placeholderPassword": "Σύνθημα", + "labelKey": "Κλειδί", + "placeholderApiKeyName": "Όνομα κλειδιού (π.χ. X-Api-Key)", + "labelValue": "Αξία", + "placeholderValue": "Αξία", + "addTo": "Προσθήκη σε", + "header": "Επί κεφαλής", + "queryParams": "Παράμετροι ερωτήματος", + "noAuthHint": "Αυτό το αίτημα δεν χρησιμοποιεί έλεγχο ταυτότητας." + }, + "responsePanel": { + "emptyTitle": "Έτοιμοι να εξερευνήσετε;", + "emptyHint": "Εισαγάγετε μια διεύθυνση URL και κάντε κλικ στην Αποστολή για να δείτε τα δεδομένα απόκρισης εδώ.", + "responsePreviewAlt": "Προεπισκόπηση απόκρισης", + "previewUnavailable": "Η προεπισκόπηση δεν είναι διαθέσιμη για αυτόν τον τύπο περιεχομένου", + "status": "Κατάσταση", + "time": "Φορά", + "size": "Μέγεθος", + "copyBody": "Αντιγραφή σώματος", + "downloadResponse": "Λήψη απάντησης", + "bodyTab": "Σώμα", + "previewTab": "Πρεμιέρα", + "headersTab": "Κεφαλίδες", + "binaryRawView": "Τα δυαδικά δεδομένα δεν μπορούν να εμφανιστούν σε μη επεξεργασμένη προβολή.", + "errorStatusLabel": "Σφάλμα", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Νέα καρτέλα" + }, + "collectionsSidebar": { + "title": "Πόροι API", + "newCollection": "Νέα συλλογή", + "tabCollections": "Συλλογές", + "tabHistory": "Ιστορία", + "noCollectionsTitle": "Χωρίς συλλογές", + "noCollectionsHint": "Δημιουργήστε μια συλλογή για να οργανώσετε τα αιτήματά σας API.", + "createCollection": "Δημιουργία συλλογής", + "newFolder": "Νέος φάκελος", + "rename": "Μετονομάζω", + "delete": "Διαγράφω", + "emptyCollection": "Κενή συλλογή", + "recentRequests": "Πρόσφατα αιτήματα", + "clearAll": "Καθαρίστε όλα", + "noHistoryTitle": "Δεν υπάρχει ακόμη ιστορικό", + "noHistoryHint": "Στείλτε αίτημα για να το δείτε εδώ.", + "dialogNewFolderTitle": "Νέος φάκελος", + "dialogNewFolderDescription": "Δημιουργήστε έναν νέο φάκελο για να οργανώσετε τα αιτήματά σας.", + "labelFolderName": "Όνομα φακέλου", + "placeholderFolderName": "Ο φάκελος μου", + "createFolder": "Δημιουργία φακέλου", + "dialogNewCollectionTitle": "Νέα συλλογή", + "dialogNewCollectionDescription": "Δημιουργήστε μια νέα συλλογή ανώτατου επιπέδου.", + "labelCollectionName": "Όνομα συλλογής", + "placeholderCollectionName": "Η συλλογή μου", + "dialogRenameCollectionTitle": "Μετονομασία συλλογής", + "dialogRenameCollectionDescription": "Εισαγάγετε ένα νέο όνομα για τη συλλογή.", + "placeholderRenameCollection": "Όνομα συλλογής", + "renameAction": "Μετονομάζω", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Νέος φάκελος", + "delete": "Διαγράφω", + "emptyFolder": "Κενός φάκελος" + }, + "environmentManager": { + "noEnvironment": "Χωρίς περιβάλλον", + "manageDialogTitle": "Διαχειριστείτε περιβάλλοντα", + "placeholderNewEnv": "Νέο περιβάλλον", + "variablesHeading": "{name} μεταβλητές", + "addVariable": "Προσθήκη μεταβλητής", + "columnVariable": "Μεταβλητός", + "columnValue": "Αξία", + "placeholderKey": "Κλειδί", + "placeholderValue": "Αξία", + "selectEnvToEdit": "Επιλέξτε ένα περιβάλλον για επεξεργασία" + }, + "codeGenerator": { + "triggerTitle": "Δημιουργία κώδικα", + "dialogTitle": "Δημιουργία κώδικα", + "languageCurl": "μπούκλα", + "languageJavaScript": "JavaScript (ανάκτηση)", + "languagePython": "Python (αιτήματα)", + "languageGo": "Πάω", + "copy": "Αντίγραφο", + "errorGenerating": "Σφάλμα κατά τη δημιουργία κωδικού" + }, + "keyValueEditor": { + "columnKey": "Κλειδί", + "columnValue": "Αξία", + "placeholderKey": "Κλειδί", + "placeholderValue": "Αξία", + "addItem": "Προσθήκη στοιχείου" + }, + "importCurl": { + "trigger": "Εισαγωγή cURL", + "dialogTitle": "Εισαγωγή cURL", + "dialogDescription": "Επικολλήστε την εντολή cURL παρακάτω για να την εισαγάγετε ως νέο αίτημα.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Εισαγωγή" + }, + "saveRequest": { + "trigger": "Εκτός", + "dialogTitle": "Αποθήκευση αιτήματος", + "dialogDescription": "Αποθηκεύστε αυτό το αίτημα σε έναν φάκελο συλλογής.", + "labelName": "Αίτημα ονόματος", + "placeholderName": "Το αίτημά μου", + "labelFolder": "Ντοσιέ", + "placeholderFolder": "Επιλέξτε ένα φάκελο", + "save": "Εκτός" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Λειτουργία επεξεργασίας", + "viewOnlyMode": "Λειτουργία προβολής μόνο", + "insertComponent": "Εισαγάγετε το στοιχείο", + "addUnorderedList": "Προσθήκη μη ταξινομημένης λίστας", + "addOrderedList": "Προσθήκη ταξινομημένης λίστας", + "addTable": "Προσθήκη πίνακα" + }, + "addBlock": "Προσθήκη μπλοκ", + "media": { + "popoverTitle": "Προσθήκη πολυμέσων", + "singleImage": "Ενιαία Εικόνα", + "singleImageDesc": "Ανεβάστε μία εικόνα", + "multipleImages": "Πολλαπλές εικόνες", + "multipleImagesDesc": "Μεταφόρτωση πλέγματος εικόνων", + "video": "Βίντεο", + "videoDesc": "Μεταφόρτωση αρχείου βίντεο", + "uploadingAlt": "Μεταφόρτωση...", + "uploadFailedAlt": "Η μεταφόρτωση απέτυχε" + }, + "export": { + "fabAria": "Κωδικός εξαγωγής", + "title": "Κωδικός εξαγωγής", + "description": "Αντιγράψτε την έξοδο HTML ή JSON του περιεχομένου του επεξεργαστή σας", + "preview": "Πρεμιέρα", + "html": "HTML", + "json": "JSON", + "previewOptions": "Επιλογές προεπισκόπησης", + "enhanceSpaces": "Βελτιώστε τους χώρους", + "livePreview": "Ζωντανή προεπισκόπηση του αποδοθέντος HTML", + "htmlTailwind": "HTML με κλάσεις Tailwind CSS", + "editorJson": "Κατάσταση επεξεργασίας ως JSON", + "copyHtml": "Αντιγραφή HTML", + "copyJson": "Αντιγράψτε το JSON", + "copied": "Αντιγράφηκε!" + }, + "template": { + "fabTitle": "Πρότυπο διακόπτη", + "title": "Επιλέξτε ένα πρότυπο", + "description": "Επιλέξτε ένα πρότυπο για να ξεκινήσετε γρήγορα με προκατασκευασμένες διατάξεις και περιεχόμενο", + "catAll": "Όλα τα Πρότυπα", + "catProductivity": "Παραγωγικότητα", + "catCreative": "Δημιουργικός", + "catBusiness": "Επιχείρηση", + "catPersonal": "Προσωπικός", + "noTemplates": "Δεν βρέθηκαν πρότυπα σε αυτήν την κατηγορία", + "confirmTitle": "Αντικατάσταση υπάρχοντος περιεχομένου;", + "confirmDescription": "Έχετε υπάρχον περιεχόμενο στο πρόγραμμα επεξεργασίας. Η εφαρμογή αυτού του προτύπου θα αντικαταστήσει όλο το τρέχον περιεχόμενο.", + "cannotUndo": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "cancel": "Ματαίωση", + "replaceContent": "Αντικατάσταση περιεχομένου" + }, + "command": { + "searchPlaceholder": "Εντολές αναζήτησης...", + "noResults": "Δεν βρέθηκαν εντολές.", + "turnInto": "Μετατρέψτε σε", + "h1": { + "label": "Επικεφαλίδα 1", + "description": "Επικεφαλίδα μεγάλης ενότητας", + "keywords": "h1, επικεφαλίδα, τίτλος, μεγάλο" + }, + "h2": { + "label": "Επικεφαλίδα 2", + "description": "Επικεφαλίδα μεσαίου τμήματος", + "keywords": "h2, επικεφαλίδα, υπότιτλος" + }, + "h3": { + "label": "Κεφάλαιο 3", + "description": "Επικεφαλίδα μικρής ενότητας", + "keywords": "h3, επικεφαλίδα, υπότιτλος" + }, + "h4": { + "label": "Κεφάλαιο 4", + "description": "Επικεφαλίδα μικροσκοπικού τμήματος", + "keywords": "h4, επικεφαλίδα" + }, + "h5": { + "label": "Κεφάλαιο 5", + "description": "Μικρότερη επικεφαλίδα", + "keywords": "h5, επικεφαλίδα" + }, + "h6": { + "label": "Κεφάλαιο 6", + "description": "Η μικρότερη κατεύθυνση", + "keywords": "h6, επικεφαλίδα" + }, + "p": { + "label": "Παράγραφος", + "description": "Παράγραφος κανονικού κειμένου", + "keywords": "p,παράγραφος,κείμενο,κανονικό" + }, + "code": { + "label": "Μπλοκ κωδικών", + "description": "Απόσπασμα κώδικα", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "Παραθέτω", + "description": "Μπλοκ απόσπασμα", + "keywords": "απόσπασμα, μπλοκ, παραπομπή" + }, + "li": { + "label": "Λίστα με κουκκίδες", + "description": "Απλό στοιχείο λίστας με κουκκίδα", + "keywords": "λίστα,κουκκίδα,χωρίς σειρά,ul,li" + }, + "ol": { + "label": "Αριθμημένη λίστα", + "description": "Αριθμημένο στοιχείο λίστας", + "keywords": "κατάλογος,αριθμημένος,τακτοποιημένος,ol,li" + }, + "img": { + "label": "Εικών", + "description": "Μεταφορτώστε ή ενσωματώστε μια εικόνα", + "keywords": "εικόνα, img, εικόνα, φωτογραφία, μεταφόρτωση" + }, + "video": { + "label": "Βίντεο", + "description": "Μεταφορτώστε ή ενσωματώστε ένα βίντεο", + "keywords": "βίντεο, βίντεο, ταινία, mp4, μεταφόρτωση" + }, + "table": { + "label": "Τραπέζι", + "description": "Δημιουργήστε έναν πίνακα", + "keywords": "πίνακας, πλέγμα, γραμμές, στήλες, κελιά" + } + }, + "toasts": { + "tableImported": "Εισαγωγή πίνακα", + "tableImportedDesc": "Ο πίνακας Markdown έχει εισαχθεί με επιτυχία", + "mediaPasted": "Μέσα επικολλήθηκαν", + "pasteMixed": "{imageCount} εικόνα(εις) και {videoCount} βίντεο επικολλήθηκαν με επιτυχία.", + "pasteImages": "{count} εικόνα(εις) επικολλήθηκαν με επιτυχία.", + "pasteVideos": "{count} βίντεο επικολλήθηκαν με επιτυχία.", + "pasteFailed": "Η επικόλληση απέτυχε", + "unexpectedError": "Παρουσιάστηκε απροσδόκητο σφάλμα", + "uploadFailed": "Η μεταφόρτωση απέτυχε" + }, + "blockBackground": { + "menuItem": "Αλλαγή χρώματος φόντου", + "dialogTitle": "Αποκλεισμός χρώματος φόντου", + "dialogDescription": "Επιλέξτε ένα χρώμα φόντου για αυτό το μπλοκ", + "tabPreset": "Προκαθορισμένα χρώματα", + "tabCustom": "Προσαρμοσμένο χρώμα", + "sectionHeading": "Χρώματα φόντου", + "apply": "Εφαρμόζω", + "colors": { + "none": "Κανένας", + "red": "Κόκκινος", + "orange": "Πορτοκάλι", + "yellow": "Κίτρινος", + "green": "Πράσινος", + "blue": "Μπλε", + "indigo": "Ινδικό", + "purple": "Μωβ", + "pink": "Ροζ", + "teal": "Βάσκας", + "cyan": "κυανό", + "gray": "Γκρί" + } + }, + "tableDialog": { + "title": "Δημιουργία πίνακα", + "description": "Δημιουργήστε έναν νέο πίνακα ή εισαγάγετε από τη σήμανση", + "markdownCheckbox": "Έχω έναν πίνακα επισήμανσης", + "pasteMarkdownLabel": "Επικολλήστε τον πίνακα Markdown", + "markdownPlaceholder": "| Κεφαλίδα 1 | Κεφαλίδα 2 | Κεφαλίδα 3 |\n|----------|----------|-----------|\n| Κελί 1 | Κελί 2 | Κελί 3 |\n| Κελί 4 | Κελί 5 | Κελί 6 |", + "markdownHint": "Επικολλήστε τον πίνακα σήμανσης παραπάνω. Πρέπει να περιλαμβάνει σειρές κεφαλίδας και διαχωριστικού.", + "rows": "Σειρές", + "columns": "Στήλες", + "maxDimensions": "Μέγιστο: 20 σειρές × 10 στήλες", + "cancel": "Ματαίωση", + "importTable": "Πίνακας εισαγωγής", + "createTable": "Δημιουργία πίνακα" + }, + "tableParse": { + "errors": { + "minRows": "Ο πίνακας πρέπει να έχει τουλάχιστον μια σειρά κεφαλίδας και μια διαχωριστική γραμμή", + "pipeFormat": "Οι σειρές του πίνακα πρέπει να ξεκινούν και να τελειώνουν με |", + "headerMinCol": "Η γραμμή κεφαλίδας πρέπει να έχει τουλάχιστον μία στήλη", + "separatorRow": "Η δεύτερη σειρά πρέπει να είναι διαχωριστικό (π.χ. |---|---|)", + "rowColumnMismatch": "Η σειρά {row} έχει {got} στήλες, αναμένεται {αναμενόταν}", + "parseFailed": "Απέτυχε η ανάλυση του πίνακα σήμανσης" + } + }, + "link": { + "editLink": "Επεξεργασία συνδέσμου", + "addLink": "Προσθήκη συνδέσμου", + "selectedText": "Επιλεγμένο κείμενο: \"{text}\"", + "linkUrl": "URL σύνδεσης", + "urlPlaceholder": "https://example.com", + "update": "Εκσυγχρονίζω", + "addLinkButton": "Προσθήκη συνδέσμου", + "toastApplied": "Εφαρμόστηκε σύνδεσμος", + "toastLinkedTo": "Συνδέεται με: {url}", + "toastRemoved": "Ο σύνδεσμος καταργήθηκε", + "toastRemovedDesc": "Ο σύνδεσμος έχει αφαιρεθεί από την επιλογή" + }, + "customClass": { + "devMode": "Λειτουργία προγραμματιστή", + "toggleDevModeAria": "Εναλλαγή λειτουργίας προγραμματισμού", + "searchPlaceholderDev": "Αναζήτηση τάξεων... (π.χ. \"κείμενο\", \"bg\", \"flex\")", + "searchPlaceholderUser": "Στυλ αναζήτησης... (π.χ. \"κόκκινο\", \"έντονη\", \"σκιά\")", + "appliesTitle": "Ισχύει: {className}", + "noMatches": "Δεν βρέθηκαν τάξεις που να ταιριάζουν με το \"{query}\"", + "toastClassReplaced": "Η τάξη αντικαταστάθηκε", + "toastClassReplacedDesc": "Αντικαταστάθηκε το \"{replaced}\" με το \"{className}\"", + "toastCustomClassApplied": "Εφαρμόστηκε προσαρμοσμένη τάξη", + "toastAppliedClass": "Εφαρμοσμένη τάξη: {className}" + }, + "insertComponents": { + "title": "Εισαγωγή στοιχείου", + "description": "Επιλέξτε ένα στοιχείο για εισαγωγή στο έγγραφό σας", + "noComponents": "Δεν υπάρχουν διαθέσιμα εξαρτήματα", + "categories": { + "media": "Μέσα ενημέρωσης", + "layout": "Σχέδιο", + "interactive": "Αλληλεπιδραστικός", + "decoration": "Διακόσμηση" + }, + "freeImage": { + "name": "Εικόνα ελεύθερης κίνησης", + "description": "Προσθέστε μια εικόνα που μπορεί να τοποθετηθεί οπουδήποτε και να αλλάξει το μέγεθος ελεύθερα" + } + } + }, + "JsonFormatter": { + "title": "Μορφοποιητής JSON", + "description": "Μορφοποιήστε, επικυρώστε και επεξεργαστείτε JSON σε προβολή κειμένου και δέντρου.", + "documentNameText": "Έγγραφο κειμένου {n}", + "documentNameTree": "Δενδρικό έγγραφο {n}", + "newDocument": "Νέο", + "copy": "Αντιγραφή", + "save": "Αποθήκευση", + "load": "Φόρτωση", + "saving": "Αποθήκευση...", + "leftDocNameLabel": "Όνομα αριστερού εγγράφου", + "rightDocNameLabel": "Όνομα δεξιού εγγράφου", + "toastCopiedText": "Το πάνελ κειμένου αντιγράφηκε", + "toastCopiedTree": "Το πάνελ δέντρου αντιγράφηκε", + "toastCopyFailed": "Αποτυχία αντιγραφής JSON", + "toastLoginRequired": "Συνδεθείτε για να αποθηκεύσετε έγγραφα", + "toastSaved": "Το έγγραφο αποθηκεύτηκε", + "toastSaveFailed": "Αποτυχία αποθήκευσης εγγράφου", + "format": "Μορφοποίηση", + "compact": "Συμπίεση", + "sortKeys": "Ταξινόμηση κλειδιών", + "repair": "Επιδιόρθωση", + "query": "Ερώτημα", + "toastInvalidJson": "Μη έγκυρο JSON", + "toastAlreadyValid": "Το JSON είναι ήδη έγκυρο", + "toastRepaired": "Το JSON επιδιορθώθηκε", + "toastRepairFailed": "Δεν ήταν δυνατή η επιδιόρθωση του JSON" + }, + "JwtDecoder": { + "title": "Αποκωδικοποιητής JWT", + "subtitle": "Επιθεωρήστε κεφαλίδα και payload. Η λήξη χρησιμοποιεί το claim exp. Δεν αποστέλλεται τίποτα σε διακομιστή· η υπογραφή δεν επαληθεύεται.", + "tokenLabel": "Token", + "clear": "Καθαρισμός", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Αντιγραφή", + "expiryLabel": "Λήξη (exp)", + "signaturePresent": "Υπογραφή υπάρχει (δεν επαληθεύεται)", + "noExp": "Δεν υπάρχει claim exp στο payload.", + "status": { + "expired": "Έληξε", + "valid": "Έγκυρο" + }, + "fields": { + "unix": "Unix", + "local": "Τοπική", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Έκδοση (iat)", + "notBefore": "Όχι πριν (nbf)" + }, + "panels": { + "header": "Κεφαλίδα", + "payload": "Payload" + }, + "errors": { + "empty": "Επικολλήστε ένα JWT για αποκωδικοποίηση.", + "segments": "Ένα JWT χρειάζεται τουλάχιστον κεφαλίδα και payload (δύο τμήματα).", + "base64": "Αποτυχία αποκωδικοποίησης base64url κεφαλίδας ή payload." + }, + "relative": { + "future": "σε {n} {unit}", + "past": "πριν από {n} {unit}" + }, + "units": { + "second": "{count, plural, one {δευτερόλεπτο} other {δευτερόλεπτα}}", + "minute": "{count, plural, one {λεπτό} other {λεπτά}}", + "hour": "{count, plural, one {ώρα} other {ώρες}}", + "day": "{count, plural, one {ημέρα} other {ημέρες}}" + }, + "tabs": { + "decode": "Αποκωδικοποίηση", + "sign": "Γεννήτρια / υπογραφή" + }, + "signer": { + "algLabel": "Αλγόριθμος (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Μυστικό (HS*)", + "secretPlaceholder": "Επικολλήστε το HMAC μυστικό…", + "privateKeyLabel": "Ιδιωτικό κλειδί PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Ο αλγόριθμος \"none\" δημιουργεί μη υπογεγραμμένο token. Είναι ανασφαλές και πρέπει να χρησιμοποιείται μόνο για δοκιμές.", + "sign": "Υπογραφή", + "verify": "Επαλήθευση υπογραφής", + "outputLabel": "Token (συμπαγές JWS)", + "outputPlaceholder": "Κάντε κλικ \"Υπογραφή\"…", + "copyToken": "Αντιγραφή token", + "copied": "Αντιγράφηκε", + "verifyKeyLabel": "Κλειδί επαλήθευσης (προαιρετικό)", + "verifyKeyPlaceholder": "Για RS*: επικολλήστε το δημόσιο κλειδί (SPKI) PEM. Για HS*: αφήστε κενό για χρήση του μυστικού.", + "verifyKeyHint": "Αν είναι κενό, η επαλήθευση χρησιμοποιεί το κλειδί υπογραφής (χρήσιμο για HS*).", + "verifyStatus": { + "valid": "Έγκυρη υπογραφή", + "invalid": "Μη έγκυρη υπογραφή" + }, + "securityNote": "Όλα εκτελούνται τοπικά στον browser· κλειδιά και tokens δεν ανεβαίνουν." + }, + "signErrors": { + "invalidJson": "Το header και το payload πρέπει να είναι έγκυρο JSON.", + "unsupportedAlg": "Μη υποστηριζόμενος αλγόριθμος.", + "missingKey": "Απαιτείται κλειδί υπογραφής για αυτόν τον αλγόριθμο.", + "cryptoFailed": "Η υπογραφή απέτυχε. Ελέγξτε τη μορφή κλειδιού και δοκιμάστε ξανά." + }, + "verifyErrors": { + "empty": "Εισάγετε ένα JWT για επαλήθευση.", + "segments": "Το JWT πρέπει να έχει ακριβώς 3 τμήματα για επαλήθευση υπογραφής.", + "unsupportedAlg": "Μη υποστηριζόμενο ή λείπει το alg στο header.", + "missingKey": "Απαιτείται κλειδί επαλήθευσης για αυτόν τον αλγόριθμο.", + "base64": "Δεν ήταν δυνατή η αποκωδικοποίηση του header ή της υπογραφής.", + "cryptoFailed": "Η επαλήθευση απέτυχε. Ελέγξτε τη μορφή κλειδιού και δοκιμάστε ξανά." + } + }, + "CertificatePemDecoder": { + "title": "Αποκωδικοποιητής πιστοποιητικού / PEM", + "subtitle": "Επικολλήστε πιστοποιητικό X.509 (-----BEGIN CERTIFICATE-----) ή CSR (-----BEGIN CERTIFICATE REQUEST-----). Θέμα, εκδότης, ημερομηνίες και SAN αναλύονται τοπικά· δεν μεταφορτώνεται τίποτα.", + "pemLabel": "Είσοδος PEM", + "clear": "Εκκαθάριση", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Γίνεται ανάλυση…", + "copyHex": "Αντιγραφή αποτυπώματος (hex χωρίς άνω τελείες)", + "errors": { + "empty": "Επικολλήστε PEM πιστοποιητικό ή CSR για αποκωδικοποίηση.", + "noBlock": "Δεν βρέθηκε μπλοκ PEM πιστοποιητικού ή CSR.", + "parseFailed": "Δεν ήταν δυνατή η ανάλυση αυτών των δεδομένων PEM." + }, + "block": { + "title": "Μπλοκ PEM {index}", + "certificate": "Πιστοποιητικό X.509", + "csr": "Αίτηση υπογραφής πιστοποιητικού" + }, + "fields": { + "subject": "Θέμα", + "issuer": "Εκδότης", + "notBefore": "Μη έγκυρο πριν από", + "notAfter": "Μη έγκυρο μετά από", + "serial": "Σειριακός αριθμός (hex)", + "sha256": "Αποτύπωμα SHA-256", + "sans": "Εναλλακτικά ονόματα θέματος", + "sansNone": "Δεν υπάρχει επέκταση SAN.", + "signatureAlgorithm": "Αλγόριθμος υπογραφής", + "csrIssuerNote": "Τα CSR δεν περιλαμβάνουν εκδότη (εμφανίζεται στο εκδοθέν πιστοποιητικό).", + "csrValidityNote": "Τα CSR δεν περιλαμβάνουν ημερομηνίες ισχύος (εμφανίζονται στο εκδοθέν πιστοποιητικό)." + }, + "sanType": { + "dns": "DNS", + "dn": "Όνομα καταλόγου", + "email": "Email", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Καταχωρισμένο ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Μορφοποιητής SQL", + "subtitle": "Μορφοποιήστε SQL για MySQL, PostgreSQL ή SQLite. Τρέχει στον browser· δεν ανεβαίνει τίποτα.", + "dialectLabel": "Διάλεκτος", + "keywordsLabel": "Λέξεις-κλειδιά", + "indentLabel": "Εσοχή", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "ΚΕΦΑΛΑΙΑ", + "lower": "πεζά", + "preserve": "Διατήρηση" + }, + "spaces": "{n} κενά", + "format": "Μορφοποίηση", + "clear": "Καθαρισμός", + "counter": "{current} / {max} χαρακτήρες", + "inputPanel": "Είσοδος", + "formattedPanel": "Μορφοποιημένο", + "copy": "Αντιγραφή", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Πατήστε «Μορφοποίηση»…", + "errors": { + "tooLong": "Η είσοδος περιορίζεται σε {max} χαρακτήρες.", + "couldNotFormat": "Δεν ήταν δυνατή η μορφοποίηση αυτού του SQL." + } + }, + "YamlFormatter": { + "title": "Μορφοποιητής YAML", + "subtitle": "Μορφοποιήστε, επαληθεύστε και μετατρέψτε YAML στον περιηγητή σας. Τίποτα δεν ανεβαίνει.", + "outputLabel": "Έξοδος", + "outputModes": { + "yaml": "YAML (μορφοποιημένο)", + "json": "JSON" + }, + "indentLabel": "Εσοχή", + "spaces": "{n} κενά", + "format": "Μορφοποίηση", + "clear": "Καθαρισμός", + "copy": "Αντιγραφή", + "counter": "{current} / {max} χαρακτήρες", + "inputPanel": "Είσοδος", + "outputPanel": "Έξοδος", + "errors": { + "tooLong": "Η είσοδος περιορίζεται σε {max} χαρακτήρες.", + "couldNotFormat": "Δεν ήταν δυνατή η ανάλυση αυτού του YAML." + } + }, + "GraphqlFormatter": { + "title": "Μορφοποιητής GraphQL", + "subtitle": "Μορφοποιήστε ερωτήματα, μεταλλάξεις και συνδρομές GraphQL στο πρόγραμμα περιήγησης. Δεν ανεβαίνει τίποτα.", + "tabs": { + "format": "Μορφοποίηση", + "build": "Δημιουργός ερωτήματος" + }, + "outputModeLabel": "Έξοδος", + "outputModes": { + "pretty": "Αναγνώσιμη εκτύπωση", + "minify": "Σμίκρυνση" + }, + "format": "Μορφοποίηση", + "clear": "Εκκαθάριση", + "counter": "{current} / {max} χαρακτήρες", + "inputPanel": "Είσοδος", + "outputPanel": "Έξοδος", + "copy": "Αντιγραφή", + "builderHint": "Συνθέστε ένα σκελετό και τον εφαρμόζετε στον επεξεργαστή. Ορισμοί μεταβλητών με σύνταξη GraphQL (π.χ. $id: ID!) — οι παρενθέσεις είναι προαιρετικές.", + "builder": { + "operation": "Λειτουργία", + "operationName": "Όνομα λειτουργίας", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Ορισμοί μεταβλητών", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Σύνολο επιλογής", + "apply": "Εφαρμογή στην είσοδο", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Η είσοδος περιορίζεται σε {max} χαρακτήρες.", + "couldNotFormat": "Δεν ήταν δυνατή η ανάλυση ή εκτύπωση αυτού του GraphQL.", + "emptySelection": "Προσθέστε σύνολο επιλογής πριν από την εφαρμογή." + } + }, + "RegexTester": { + "title": "Δοκιμή regex", + "subtitle": "Regex τύπου JavaScript σε πραγματικό χρόνο στο κείμενό σας. Οι αντιστοιχίσεις επισημαίνονται· τίποτα δεν φεύγει από τον browser.", + "patternLabel": "Μοτίβο", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g καθολικό", + "i": "i χωρίς διάκριση πεζών/κεφαλαίων", + "m": "m πολυγραμμικό", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(κενό)", + "noFlags": "—", + "matches": "{count, plural, one {αντιστοίχιση} other {αντιστοιχίσεις}}", + "testTextLabel": "Κείμενο δοκιμής", + "counter": "{current} / {max}", + "clear": "Καθαρισμός", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Το κείμενο περιορίζεται σε {max} χαρακτήρες.", + "invalid": "Μη έγκυρη κανονική έκφραση.", + "runaway": "Σταμάτησε μετά από {max} αντιστοιχίσεις (πιθανό runaway μοτίβο)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Μετατροπέας timestamp", + "subtitle": "Unix δευτερόλεπτα (≤10 ψηφία) ή χιλιοστά (μεγαλύτεροι ακέραιοι), ISO-8601 ή συμβολοσειρές που μπορεί να διαβάσει ο browser. Όλα τρέχουν τοπικά.", + "inputLabel": "Είσοδος", + "placeholder": "1744200000 ή 2025-04-09T12:00:00Z", + "now": "Τώρα", + "copy": "Αντιγραφή", + "hint": "Επικολλήστε μια τιμή ή πατήστε Τώρα.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Σχετικό", + "human": "Αναγνώσιμο" + }, + "fields": { + "seconds": "Δευτερόλεπτα", + "milliseconds": "Χιλιοστά", + "utc": "UTC", + "local": "Τοπικά", + "distance": "Απόσταση" + }, + "errors": { + "empty": "Εισάγετε Unix timestamp, ISO string ή κείμενο ημερομηνίας.", + "invalidNumber": "Μη έγκυρο αριθμητικό timestamp.", + "outOfRange": "Εκτός ορίων για JavaScript Date.", + "couldNotParse": "Δεν ήταν δυνατή η ανάλυση ως Unix, ISO-8601 ή ημερομηνία browser." + } + }, + "HttpStatusCodes": { + "title": "Κωδικοί κατάστασης HTTP", + "subtitle": "Αναζητήστε και περιηγηθείτε στους κωδικούς κατάστασης απαντήσεων HTTP (1xx5xx) με σύντομες περιγραφές και αναφορές RFC. Εκτελείται τοπικά.", + "header": { + "title": "Κωδικοί κατάστασης HTTP", + "description": "Αναζητήσιμη λίστα κωδικών απάντησης HTTP με περιγραφές και αναφορές RFC." + }, + "searchPlaceholder": "Αναζήτηση: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Φιλτράρισμα κλάσης…", + "classFilterAll": "Όλες οι κλάσεις", + "resultsCount": "{count} από {total}", + "table": { + "code": "Code", + "phrase": "Φράση", + "description": "Περιγραφή", + "copy": "Αντιγραφή" + }, + "noResults": "Δεν βρέθηκαν αποτελέσματα για “{query}”.", + "specPrefix": "Προδιαγ.:", + "copied": "Ο κωδικός αντιγράφηκε" + }, + "CronBuilder": { + "title": "Δημιουργός εκφράσεων Cron", + "subtitle": "Τυπικό cron 5 πεδίων: minute hour day month weekday. Ο parser χρησιμοποιεί seconds=0 εσωτερικά. Προαιρετικό 6-πεδίων (sec min hour dom mon dow) λειτουργεί στον editor.", + "tabs": { + "builder": "Οπτικός δημιουργός", + "expression": "Έκφραση" + }, + "presetsLabel": "Προεπιλογές", + "fieldsLabel": "Πεδία", + "rawExpressionLabel": "Ωμή έκφραση", + "expressionHint": "Βγείτε από το πεδίο για ανάλυση και συγχρονισμό. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Τρέχουσα έκφραση", + "copy": "Αντιγραφή", + "quickPick": "Γρήγορη επιλογή", + "emptyExpression": "—", + "plainLanguageLabel": "Φυσική γλώσσα", + "nextRunsLabel": "Επόμενες εκτελέσεις (τοπική ώρα)", + "fields": { + "minute": "Λεπτό", + "hour": "Ώρα", + "dayOfMonth": "Ημέρα μήνα", + "month": "Μήνας", + "dayOfWeek": "Ημέρα εβδομάδας" + }, + "presets": { + "everyMinute": "Κάθε λεπτό", + "every5Minutes": "Κάθε 5 λεπτά", + "every15Minutes": "Κάθε 15 λεπτά", + "everyHour": "Κάθε ώρα (στο :00)", + "every6Hours": "Κάθε 6 ώρες", + "dailyMidnight": "Καθημερινά τα μεσάνυχτα", + "daily0900": "Καθημερινά στις 09:00", + "weekdays0900": "Καθημερινές στις 09:00", + "weeklySunday": "Εβδομαδιαία (Κυριακή 00:00)", + "monthlyFirst": "Μηνιαία (1η 00:00)", + "yearlyJan1": "Ετήσια (1 Ιαν 00:00)" + }, + "picks": { + "minute": { + "every": "κάθε λεπτό" + }, + "hour": { + "every": "κάθε ώρα" + }, + "dom": { + "any": "οποιαδήποτε ημέρα", + "last": "τελευταία ημέρα" + }, + "month": { + "every": "κάθε μήνα" + }, + "dow": { + "any": "οποιαδήποτε ημέρα", + "weekdays": "καθημερινές", + "weekend": "ΣΚ" + } + }, + "months": { + "jan": "Ιαν", + "feb": "Φεβ", + "mar": "Μαρ", + "apr": "Απρ", + "may": "Μάι", + "jun": "Ιουν", + "jul": "Ιουλ", + "aug": "Αυγ", + "sep": "Σεπ", + "oct": "Οκτ", + "nov": "Νοε", + "dec": "Δεκ" + }, + "weekdays": { + "sun": "Κυρ", + "mon": "Δευ", + "tue": "Τρι", + "wed": "Τετ", + "thu": "Πεμ", + "fri": "Παρ", + "sat": "Σαβ" + }, + "errors": { + "fieldCount": "Αναμένονται 5 πεδία (minute hour day month weekday) ή 6 με δευτερόλεπτα.", + "invalid": "Μη έγκυρη cron έκφραση." + } + }, + "Base64": { + "title": "Κωδικοποιητής / αποκωδικοποιητής Base64", + "subtitle": "Κωδικοποιήστε κείμενο σε Base64 ή αποκωδικοποιήστε συμβολοσειρές Base64", + "upload": "Μεταφόρτωση", + "clear": "Καθαρισμός", + "modes": { + "encode": "Κωδικοποίηση", + "decode": "Αποκωδικοποίηση" + }, + "swapTitle": "Εναλλαγή εισόδου/εξόδου", + "panels": { + "plainText": "Απλό κείμενο", + "base64Input": "Είσοδος Base64", + "base64Output": "Έξοδος Base64", + "decodedText": "Αποκωδικοποιημένο κείμενο" + }, + "placeholders": { + "encode": "Πληκτρολογήστε κείμενο για κωδικοποίηση σε Base64…", + "decode": "Επικολλήστε Base64 για αποκωδικοποίηση…" + }, + "dropHere": "Αφήστε το αρχείο εδώ", + "charCount": "{count} χαρακτήρες", + "download": { + "title": "Λήψη εξόδου", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Αντιγραφή στο πρόχειρο", + "outputPlaceholder": "Η έξοδος θα εμφανιστεί εδώ…", + "errors": { + "invalidBase64": "Μη έγκυρη συμβολοσειρά Base64. Ελέγξτε την είσοδο.", + "encodeFailed": "Αποτυχία κωδικοποίησης." + } + }, + "UrlEncode": { + "title": "Κωδικοποιητής / αποκωδικοποιητής URL", + "subtitle": "Κωδικοποίηση/αποκωδικοποίηση ποσοστού με encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Μεταφόρτωση", + "clear": "Καθαρισμός", + "modes": { + "encode": "Κωδικοποίηση", + "decode": "Αποκωδικοποίηση" + }, + "swapTitle": "Εναλλαγή εισόδου και εξόδου", + "panels": { + "plainText": "Απλό κείμενο", + "encodedInput": "Κωδικοποιημένη είσοδος", + "encodedOutput": "Κωδικοποιημένη έξοδος", + "decodedText": "Αποκωδικοποιημένο κείμενο" + }, + "placeholders": { + "encode": "Εισαγάγετε κείμενο για percent-encoding…", + "decode": "Επικολλήστε percent-encoded κείμενο για αποκωδικοποίηση…" + }, + "dropHere": "Αφήστε το αρχείο εδώ", + "charCount": "{count} χαρακτ.", + "download": { + "title": "Λήψη εξόδου", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Αντιγραφή στο πρόχειρο", + "outputPlaceholder": "Η έξοδος θα εμφανιστεί εδώ…", + "errors": { + "invalidEncoding": "Μη έγκυρο percent-encoding. Ελέγξτε για ελλιπή ζεύγη % ή λάθος ακολουθίες διαφυγής.", + "encodeFailed": "Δεν ήταν δυνατή η κωδικοποίηση (μη έγκυρο Unicode στην είσοδο)." + } + }, + "UrlParser": { + "title": "Αναλυτής URL", + "subtitle": "Χωρίστε ένα URL σε πρωτόκολλο, κεντρικό υπολογιστή, διαδρομή, παραμέτρους ερωτήματος και hash (τμήμα).", + "upload": "Μεταφόρτωση", + "clear": "Εκκαθάριση", + "panels": { + "input": "Είσοδος URL", + "output": "Αποτέλεσμα" + }, + "placeholders": { + "input": "Επικολλήστε ένα URL (π.χ. https://example.com/path?foo=bar#section) ή κεντρικό/διαδρομή (π.χ. example.com/path)…" + }, + "dropHere": "Αποθέστε το αρχείο εδώ", + "charCount": "{count} χαρακτήρες", + "outputPlaceholder": "Τα αναλυμένα στοιχεία θα εμφανιστούν εδώ…", + "fields": { + "protocol": "Πρωτόκολλο", + "host": "Κεντρικός υπολογιστής", + "path": "Διαδρομή", + "hash": "Hash", + "queryParams": "Παράμετροι ερωτήματος" + }, + "paramCount": "{count} παράμετροι", + "noParams": "Δεν υπάρχουν παράμετροι ερωτήματος.", + "paramKey": "Κλειδί", + "paramValue": "Τιμή", + "copyTitle": "Αντιγραφή στο πρόχειρο", + "copyParamTitle": "Αντιγραφή ως key=value", + "download": { + "title": "Λήψη JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Μη έγκυρο URL. Συμπεριλάβετε όνομα κεντρικού υπολογιστή (example.com) και ελέγξτε τους χαρακτήρες." + } + }, + "UuidGenerator": { + "title": "Γεννήτρια UUID / ULID", + "subtitle": "Δημιουργήστε UUID (RFC 9562) ή ULID στον browser. Μαζική εξαγωγή έως {max} ανά εκτέλεση.", + "format": "Μορφή", + "kindHints": { + "ulid": "Ταξινομήσιμο, URL-safe αναγνωριστικό 26 χαρακτήρων", + "uuid4": "Τυχαίο (RFC 9562)", + "uuid7": "Χρονικά ταξινομημένο, Unix ms + τυχαίο", + "uuid1": "MAC/χρόνος (RFC 9562)", + "uuid6": "Αναδιατεταγμένος γρηγοριανός χρόνος", + "uuid3": "MD5 hash ονόματος + namespace", + "uuid5": "SHA‑1 hash ονόματος + namespace", + "nil": "Μηδενικό UUID (όλα μηδενικά, RFC 9562)" + }, + "countLabel": "Πλήθος (1–{max})", + "nameLabel": "Όνομα (συμβολοσειρά εισόδου)", + "namePlaceholder": "π.χ. my.app/resource", + "bulkHint": "Στη μαζική δημιουργία προστίθενται #0, #1, … ώστε κάθε ID να είναι διαφορετικό (v3/v5 είναι ντετερμινιστικά).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Προσαρμοσμένο UUID" + }, + "customNamespaceLabel": "UUID namespace", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Δημιουργία", + "output": "Έξοδος", + "lines": "{count} γραμμές", + "download": { + "title": "Λήψη .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Αντιγραφή όλων", + "uppercaseToggle": "Έξοδος με κεφαλαία", + "hyphensToggle": "Εμφάνιση παύλων", + "copyJsonTitle": "Αντιγραφή ως πίνακα JSON", + "outputPlaceholder": "Πατήστε «Δημιουργία»…", + "errors": { + "invalidCustomNamespace": "Το προσαρμοσμένο namespace πρέπει να είναι έγκυρο UUID.", + "missingName": "Απαιτείται όνομα για UUID v3 και v5.", + "unknown": "Δεν ήταν δυνατή η δημιουργία των ID." + } + }, + "SecretApiKeyGenerator": { + "title": "Γεννήτρια μυστικού / κλειδιού API", + "subtitle": "Δημιουργήστε κρυπτογραφικά τυχαίες συμβολοσειρές με ρυθμιζόμενο μήκος και αλφάβητο. Μαζική εξαγωγή έως {max} ανά εκτέλεση. Συμπληρώνει τον γεννήτρια UUID / ULID.", + "alphabetLabel": "Αλφάβητο", + "alphabetHint": "Τα διπλότυπα αφαιρούνται. Κάθε σύμβολο επιλέγεται ομοιόμορφα με crypto.getRandomValues και δειγματοληψία απόρριψης (χωρίς προκατάληψη modulo).", + "alphabetCount": "{count} μοναδικά σύμβολα", + "presetsLabel": "Προρυθμίσεις", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Αλφαριθμητικό", + "hex": "Δεκαεξαδικό (πεζά)", + "asciiPrintable": "ASCII εκτυπώσιμο" + }, + "lengthLabel": "Μήκος ανά μυστικό (1–{maxLen})", + "countLabel": "Πλήθος (1–{maxBulk})", + "generate": "Δημιουργία", + "output": "Έξοδος", + "lines": "{count} γραμμές", + "download": { + "title": "Λήψη .txt", + "filename": "μυστικά.txt" + }, + "copyTitle": "Αντιγραφή όλων", + "outputPlaceholder": "Κάντε κλικ στο \"Δημιουργία\"…", + "entropy": "{bits} bit εντροπίας", + "strength": { + "weak": "Αδύναμο", + "fair": "Μέτριο", + "strong": "Ισχυρό" + }, + "excludeAmbiguous": "Εξαίρεση διφορούμενων χαρακτήρων", + "excludeAmbiguousHint": "Αφαιρεί τα {chars} — χαρακτήρες που διαβάζονται εύκολα λάθος ή είναι εχθρικοί για το shell.", + "prefixLabel": "Πρόθεμα κλειδιού (προαιρετικό)", + "errors": { + "emptyAlphabet": "Προσθέστε τουλάχιστον έναν χαρακτήρα στο αλφάβητο.", + "alphabetTooLong": "Μετά την αφαίρεση διπλοτύπων, το αλφάβητο μπορεί να έχει το πολύ {maxAlpha} χαρακτήρες.", + "lengthOutOfRange": "Το μήκος πρέπει να είναι μεταξύ 1 και {maxLen}.", + "countOutOfRange": "Το πλήθος πρέπει να είναι μεταξύ 1 και {maxBulk}.", + "unknown": "Δεν ήταν δυνατή η δημιουργία μυστικών." + } + }, + "LoremIpsum": { + "title": "Γεννήτρια Lorem Ipsum", + "subtitle": "Δημιουργήστε λατινικό κείμενο κράτησης θέσης για τις διατάξεις και τα μακέτα σας.", + "paragraphs": "Παράγραφοι", + "sentences": "Προτάσεις", + "words": "Λέξεις", + "count": "Αριθμός", + "generate": "Δημιουργία", + "copy": "Αντιγραφή", + "startWith": "Έναρξη με 'Lorem ipsum...'", + "outputPlaceholder": "Η έξοδος θα εμφανιστεί εδώ...", + "outputType": "Τύπος εξόδου", + "bulletList": "Λίστα κουκκίδων", + "htmlMarkup": "Σήμανση HTML", + "htmlMarkupHint": "Τυλίξτε την έξοδο σε παραγράφους ή χρησιμοποιήστε λίστα για λειτουργία κουκκίδων", + "output": "Έξοδος", + "download": "Λήψη" + }, + "ColorPicker": { + "title": "Επιλογέας και μετατροπέας χρωμάτων", + "subtitle": "Μετατρέψτε HEX, RGB και HSL· δημιουργήστε αρμονικές παλέτες. Όλη η επεξεργασία γίνεται στον περιηγητή σας.", + "nativePicker": "Εγγενής επιλογέας", + "chooseColor": "Επιλέξτε χρώμα", + "eyeDropper": "Σταγονόμετρο", + "invalidHex": "Μη έγκυρο χρώμα HEX.", + "cssValues": "Τιμές CSS", + "palettes": "Παλέτες", + "copied": "Αντιγράφηκε", + "clickToCopy": "Κλικ για αντιγραφή", + "copyAllHex": "Αντιγραφή όλων HEX", + "copy": "Αντιγραφή", + "copyColor": "Αντιγραφή {color}", + "paletteShades": "Αποχρώσεις", + "paletteComplementary": "Συμπληρωματικό", + "paletteTriadic": "Τριαδικό", + "paletteAnalogous": "Ανάλογο", + "paletteSplit": "Διαιρ. συμπλ.", + "paletteSquare": "Τετράγωνο" + }, + "ContrastChecker": { + "title": "Έλεγχος αντίθεσης", + "subtitle": "Υπολογίζει την αναλογία αντίθεσης WCAG 2.1 μεταξύ προσκηνίου και φόντου. Ελέγχει AA και AAA για κανονικό και μεγάλο κείμενο. Όλα στον περιηγητή σας.", + "foreground": "Κείμενο / προσκήνιο", + "background": "Φόντο", + "hexPlaceholder": "#RRGGBB", + "swap": "Εναλλαγή χρωμάτων", + "chooseForeground": "Επιλογή χρώματος προσκηνίου", + "chooseBackground": "Επιλογή χρώματος φόντου", + "eyeDropper": "Σταγονόμετρο", + "ratio": "Αναλογία αντίθεσης", + "copyRatio": "Αντιγραφή αναλογίας", + "copied": "Αντιγράφηκε", + "previewHeading": "Προεπισκόπηση", + "previewBody": "Ταχίστη αλώπηξ βαφής ψημένη γης δρασκελίζει υπέρ νωθρού κυνός. Ξεσκεπάζω την ψυχοφθόρα βδελυγμία.", + "previewCaption": "Δείγμα κειμένου στο προεπιλεγμένο μέγεθος.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — κανονικό κείμενο (4,5:1)", + "levelAALarge": "AA — μεγάλο κείμενο (3:1)", + "levelAAANormal": "AAA — κανονικό κείμενο (7:1)", + "levelAAALarge": "AAA — μεγάλο κείμενο (4,5:1)", + "pass": "Περνάει", + "fail": "Αποτυγχάνει", + "invalidHex": "Μη έγκυρο χρώμα HEX.", + "largeTextHint": "Μεγάλο κείμενο: 18 pt+ κανονικό ή 14 pt+ έντονο (ορισμοί WCAG)." + }, + "DiffChecker": { + "title": "Έλεγχος διαφορών", + "subtitle": "Σύγκριση γραμμών δύο κειμένων παράλληλα. Όλα παραμένουν στο πρόγραμμα περιήγησής σας.", + "rowsCount": "{count} γραμμές", + "truncatedWarning": "Εμφάνιση πρώτων {max} γραμμών.", + "limitExceeded": "Κάθε πλευρά περιορίζεται σε {max} χαρακτήρες.", + "clearBoth": "Εκκαθάριση και των δύο", + "original": "Πρωτότυπο", + "modified": "Τροποποιημένο", + "originalPlaceholder": "Επικολλήστε το αρχικό κείμενο…", + "modifiedPlaceholder": "Επικολλήστε το τροποποιημένο κείμενο…", + "comparisonLabel": "Παράλληλη σύγκριση", + "emptyState": "Εισαγάγετε κείμενο και στα δύο παράθυρα για σύγκριση." + }, + "JsonSchemaGenerator": { + "title": "Γεννήτρια JSON Schema", + "subtitle": "Επικολλήστε έγκυρο JSON για να δημιουργήσετε JSON Schema ή τυποποιημένα μοντέλα. Όλα εκτελούνται στον browser σας.", + "languageLabel": "Γλώσσα εξόδου", + "inputLabel": "Είσοδος JSON", + "inputPlaceholder": "Επικολλήστε JSON εδώ…", + "outputLabel": "Δημιουργημένη έξοδος", + "outputPlaceholder": "Το σχήμα ή οι τύποι θα εμφανιστούν εδώ…", + "parseError": "Μη έγκυρο JSON", + "clear": "Εκκαθάριση", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε στο πρόχειρο", + "copyFailed": "Αποτυχία αντιγραφής", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON Schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Φόρτωση επεξεργαστή…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Εικόνα στο Base64", + "subtitle": "Ρίξτε μια εικόνα, λάβετε ένα URI δεδομένων ή μια ακατέργαστη συμβολοσειρά Base64. Όλα παραμένουν στο πρόγραμμα περιήγησής σας.", + "upload": "Μεταφόρτωση εικόνας", + "clear": "Σαφής", + "modes": { + "dataUri": "URI δεδομένων", + "rawString": "Ακατέργαστη βάση 64" + }, + "panels": { + "imageInput": "Είσοδος εικόνας", + "output": "Έξοδος Base64" + }, + "dropHere": "Ρίξτε εικόνα εδώ", + "charCount": "{count} χαρακτήρες", + "placeholders": { + "uploadImage": "Σύρετε και αποθέστε μια εικόνα ή κάντε κλικ στη μεταφόρτωση για να ξεκινήσετε." + }, + "fileTooLarge": "Το αρχείο είναι πολύ μεγάλο. Η επεξεργασία ενδέχεται να παγώσει το πρόγραμμα περιήγησής σας.", + "copyTitle": "Αντιγραφή στο πρόχειρο", + "download": { + "title": "Λήψη κειμένου", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Συμπίεση εικόνας", + "subtitle": "Μικρύνετε την έξοδο JPEG, PNG ή WebP τοπικά με έλεγχο ποιότητας — τίποτα δεν ανεβαίνει.", + "upload": "Μεταφόρτωση εικόνας", + "clear": "Εκκαθάριση", + "dropHere": "Αφήστε την εικόνα εδώ", + "placeholders": { + "uploadImage": "Σύρετε και αφήστε μια εικόνα ή κάντε κλικ για επιλογή αρχείου." + }, + "fileTooLarge": "Το αρχείο είναι πολύ μεγάλο (μέγ. {maxMb} MB).", + "unsupportedFormat": "Μη υποστηριζόμενος τύπος αρχείου. Χρησιμοποιήστε JPEG, PNG, WebP, GIF, AVIF ή BMP.", + "compressFailed": "Δεν ήταν δυνατή η συμπίεση της εικόνας. Δοκιμάστε άλλο αρχείο ή μορφή.", + "panels": { + "original": "Πρωτότυπο", + "compressed": "Προεπισκόπηση συμπιεσμένου" + }, + "outputFormat": "Μορφή εξόδου", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Ποιότητα", + "qualityPercent": "{value}%", + "qualityPngHint": "Το PNG είναι χωρίς απώλειες στον browser· ο έλεγχος ποιότητας ισχύει μόνο για JPEG και WebP.", + "sizes": { + "original": "Πρωτότυπο: {size}", + "compressed": "Συμπιεσμένο: {size}", + "saved": "{percent}% μικρότερο" + }, + "largerWarning": "Η έξοδος είναι μεγαλύτερη από την είσοδο — δοκιμάστε άλλη μορφή ή ποιότητα.", + "download": "Λήψη", + "downloadFilename": "συμπιεσμένη-εικόνα", + "processing": "Συμπίεση…", + "emptyCompressed": "Η προεπισκόπηση εμφανίζεται εδώ." + }, + "CssGradientBuilder": { + "title": "CSS Gradient Builder", + "subtitle": "Visual CSS gradient builder με έλεγχο γωνίας, χρωματικές στάσεις και αντιγραφή CSS με ένα κλικ.", + "preview": "Προεπισκόπηση κλίσης", + "settings": "Ρυθμίσεις", + "type": "Τύπος", + "linear": "Γραμμικός", + "radial": "Ακτινικός", + "angle": "Γωνία", + "stops": "Σταματά το χρώμα", + "color": "Χρώμα", + "position": "Θέση (%)", + "addStop": "Προσθήκη Stop", + "deleteStop": "Διαγραφή Stop", + "output": "Έξοδος CSS", + "copy": "Αντιγραφή CSS", + "copied": "Αντιγράφηκε!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Γεννήτρια", + "subtitle": "Δημιουργήστε συγχωνευμένα αρχεία .gitignore άμεσα για μια συγκεκριμένη στοίβα τεχνολογίας ή συνδυασμό.", + "searchPlaceholder": "Αναζήτηση στοίβων (π.χ. Node, Python, macOS)...", + "noResults": "Δεν βρέθηκαν στοίβες.", + "selectedStacks": "Επιλεγμένες στοίβες", + "output": "Έξοδος προεπισκόπησης", + "copy": "Αντιγραφή στο Πρόχειρο", + "copied": "Αντιγράφηκε!", + "download": "Λήψη .gitignore", + "downloading": "Λήψη...", + "errorFetching": "Αποτυχία λήψης της λίστας κορυφαίων.", + "errorGenerating": "Απέτυχε η δημιουργία .gitignore από επιλεγμένες στοίβες.", + "generating": "Δημιουργία...", + "emptyState": "Επιλέξτε μία ή περισσότερες στοίβες τεχνολογίας για να δημιουργήσετε το .gitignore σας." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Ανεβάστε CSV ή Excel, επεξεργαστείτε JSON και κατεβάστε CSV ή XLSX. Μόνο πίνακας· οι ημερομηνίες Excel γίνονται ISO στο JSON.", + "dropTitle": "Αφήστε αρχείο CSV ή Excel", + "dropHint": "Για .xlsx / .xls χρησιμοποιείται το πρώτο φύλλο. Το CSV χρειάζεται γραμμή κεφαλίδας.", + "chooseFile": "Επιλογή αρχείου", + "jsonLabel": "JSON (πίνακας αντικειμένων)", + "jsonPlaceholder": "Επικολλήστε πίνακα JSON αντικειμένων ή εισάγετε CSV / Excel…", + "loadSample": "Φόρτωση δείγματος", + "actions": { + "format": "Μορφοποίηση JSON", + "clear": "Εκκαθάριση", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "downloadCsv": "Λήψη CSV", + "downloadXlsx": "Λήψη Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Δεδομένα", + "toastLoadedCsv": "Το CSV φορτώθηκε στον επεξεργαστή", + "toastLoadedExcel": "Το Excel φορτώθηκε στον επεξεργαστή", + "toastFormatted": "Το JSON μορφοποιήθηκε", + "toastExportedCsv": "Το CSV λήφθηκε", + "toastExportedXlsx": "Το Excel λήφθηκε", + "toastCopied": "Αντιγράφηκε στο πρόχειρο", + "footnote": "Όλα τρέχουν στον browser. Το JSON πρέπει να είναι μη κενός πίνακας αντικειμένων. Ένθετες τιμές σειριοποιούνται ως JSON στα κελιά CSV.", + "errors": { + "unsupportedFile": "Χρησιμοποιήστε αρχείο .csv, .xlsx ή .xls.", + "parseFailed": "Δεν ήταν δυνατή η ανάλυση του αρχείου.", + "invalidJson": "Μη έγκυρο JSON", + "exportFailed": "Η εξαγωγή απέτυχε", + "copyFailed": "Δεν ήταν δυνατή η αντιγραφή" + } + }, + "SnippetManager": { + "title": "Διαχειριστής αποσπασμάτων κώδικα", + "subtitle": "Αποθηκεύστε αποσπάσματα στον browser ή συνδεθείτε για συγχρονισμό με τον λογαριασμό σας. Εναλλαγή προβολής και επεξεργασίας, αντιγραφή με ένα κλικ, επιλογή γλώσσας ή αυτόματη ανίχνευση και μορφοποίηση JSON, SQL ή κώδικα που υποστηρίζει το Monaco.", + "snippets": "Αποσπάσματα", + "newSnippet": "Νέο απόσπασμα", + "newSnippetTitle": "Νέο απόσπασμα", + "defaultSnippetTitle": "Καλώς ήρθατε", + "defaultSnippetCode": "// Καλώς ήρθατε στα αποσπάσματα κώδικα\n// Δοκιμάστε προβολή vs επεξεργασία, αντιγραφή, μορφοποίηση και αυτόματη γλώσσα\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Αναζήτηση αποσπασμάτων…", + "searchAria": "Αναζήτηση αποσπασμάτων", + "emptySearch": "Κανένα απόσπασμα δεν ταιριάζει στην αναζήτηση.", + "snippetTitle": "Τίτλος", + "snippetTitlePlaceholder": "Όνομα αποσπάσματος", + "language": "Γλώσσα", + "detectedHint": "Τονισμός ως {lang}", + "badgeAuto": "Αυτόματο · {lang}", + "modeView": "Προβολή", + "modeEdit": "Επεξεργασία", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "format": "Μορφοποίηση", + "cancel": "Άκυρο", + "deleteTitle": "Διαγραφή αποσπάσματος;", + "deleteDescription": "Το «{title}» θα αφαιρεθεί από αυτόν τον browser.", + "deleteConfirm": "Διαγραφή", + "editorAria": "Επεξεργαστής κώδικα αποσπάσματος", + "editorLoading": "Φόρτωση επεξεργαστή…", + "toastCopied": "Αντιγράφηκε στο πρόχειρο", + "toastCopyFailed": "Αδυναμία αντιγραφής", + "toastNothingToCopy": "Τίποτα για αντιγραφή", + "toastFormatted": "Μορφοποιήθηκε", + "toastFormatFailed": "Αδυναμία μορφοποίησης", + "toastFormatJsonInvalid": "Μη έγκυρο JSON — διορθώστε πρώτα τη σύνταξη", + "toastFormatUnsupported": "Δεν υπάρχει μορφοποιητής για αυτή τη γλώσσα — δοκιμάστε JSON ή SQL ή λειτουργία επεξεργασίας", + "toastDeleted": "Το απόσπασμα διαγράφηκε", + "toastLoadFailed": "Δεν ήταν δυνατή η φόρτωση αποσπασμάτων από τον διακομιστή.", + "toastSyncFailed": "Δεν ήταν δυνατός ο συγχρονισμός με τον διακομιστή.", + "languages": { + "auto": "Αυτόματη ανίχνευση", + "plaintext": "Απλό κείμενο", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Πελάτης SQL", + "dialogTitle": "Συνδέσεις SQL", + "mobileSheetTitle": "Εξερευνητής SQL", + "newTabLabel": "Νέα καρτέλα ερωτήματος", + "vaultLockedTitle": "Το θησαυροφυλάκιο είναι κλειδωμένο", + "vaultLockedDesc": "Ξεκλειδώστε το θησαυροφυλάκιο για πρόσβαση στις αποθηκευμένες συνδέσεις.", + "heroTitle": "Πελάτης SQL", + "heroDesc": "Συνδεθείτε σε PostgreSQL, MySQL ή MariaDB. Τα διαπιστευτήρια κρυπτογραφούνται στο πρόγραμμα περιήγησης.", + "heroAddConnection": "Προσθήκη σύνδεσης", + "selectTableTitle": "Επιλέξτε πίνακα ή ανοίξτε καρτέλα ερωτήματος", + "selectTableDesc": "Κάντε κλικ σε πίνακα στην πλαϊνή μπάρα ή χρησιμοποιήστε ⌘↩.", + "toastLoadFail": "Αποτυχία φόρτωσης συνδέσεων." + }, + "connection": { + "titleNew": "Νέα σύνδεση SQL", + "titleEdit": "Επεξεργασία σύνδεσης", + "labelDbType": "Τύπος βάσης δεδομένων", + "labelName": "Όνομα σύνδεσης", + "placeholderName": "Η βάση παραγωγής μου", + "labelHostPort": "Κεντρικός υπολογιστής & Θύρα", + "labelDatabase": "Βάση δεδομένων", + "placeholderDatabase": "mydb", + "labelUsername": "Όνομα χρήστη", + "labelPassword": "Κωδικός πρόσβασης", + "labelSsl": "Ενεργοποίηση SSL / TLS", + "sslDesc": "Κρυπτογράφηση σύνδεσης με τον διακομιστή", + "securityNote": "Τα διαπιστευτήρια κρυπτογραφούνται AES-256 στο πρόγραμμα περιήγησης πριν την αποθήκευση", + "btnTest": "Δοκιμή σύνδεσης", + "btnSave": "Αποθήκευση & Σύνδεση", + "btnUpdate": "Ενημέρωση", + "btnCancelEdit": "Ακύρωση επεξεργασίας", + "savedConnections": "Αποθηκευμένες συνδέσεις", + "emptySaved": "Οι αποθηκευμένες συνδέσεις θα εμφανιστούν εδώ", + "confirmDelete": "Διαγραφή αυτής της σύνδεσης;", + "toastFillFields": "Συμπληρώστε τον κεντρικό υπολογιστή και τη βάση δεδομένων πριν τη δοκιμή.", + "toastConnectOk": "Συνδέθηκε! {version}", + "toastConnectFail": "Αποτυχία σύνδεσης: {message}", + "toastUnlockVault": "Ξεκλειδώστε πρώτα το θησαυροφυλάκιο.", + "toastFieldsRequired": "Ο κεντρικός υπολογιστής και η βάση δεδομένων είναι απαραίτητα.", + "toastUpdated": "Η σύνδεση ενημερώθηκε.", + "toastSaved": "Η σύνδεση αποθηκεύτηκε.", + "toastSaveFail": "Αποτυχία αποθήκευσης: {message}", + "toastDeleted": "Η σύνδεση διαγράφηκε.", + "toastDeleteFail": "Αποτυχία διαγραφής σύνδεσης." + }, + "sidebar": { + "title": "Εξερευνητής SQL", + "tooltipAddConnection": "Προσθήκη σύνδεσης", + "tooltipRefreshSchema": "Ανανέωση σχήματος", + "noConnections": "Δεν υπάρχουν αποθηκευμένες συνδέσεις", + "toastSchemaFail": "Αποτυχία φόρτωσης σχήματος: {message}" + }, + "editor": { + "btnRun": "Εκτέλεση", + "executing": "Εκτέλεση ερωτήματος…", + "emptyTitle": "Εκτελέστε ένα ερώτημα για να δείτε αποτελέσματα", + "emptyHint": "Πατήστε ⌘↩ ή κάντε κλικ στο Εκτέλεση", + "toastNoConnection": "Δεν υπάρχει ενεργή σύνδεση." + }, + "results": { + "filterPlaceholder": "Φιλτράρισμα αποτελεσμάτων…", + "rowCount": "{count} γραμμή(ές)", + "rowCountFiltered": "{filtered} / {total} γραμμές", + "exportCsv": "CSV", + "noRows": "Δεν επιστράφηκαν γραμμές" + } + }, + "QrCodeGenerator": { + "title": "Δημιουργός κωδικών QR", + "subtitle": "Μετατρέψτε οποιοδήποτε κείμενο ή URL σε PNG QR. Η απόδοση γίνεται τοπικά στον browser.", + "contentLabel": "Περιεχόμενο", + "contentPlaceholder": "https://example.com, απλό κείμενο, συμβολοσειρά Wi‑Fi, vCard…", + "styleLabel": "Στυλ", + "dotTypeLabel": "Σχήμα τελείας", + "dotTypeSquare": "Τετράγωνο", + "dotTypeDots": "Τελείες", + "dotTypeRounded": "Στρογγυλεμένο", + "dotTypeExtraRounded": "Έξτρα στρογγυλεμένο", + "dotTypeClassy": "Κομψό", + "dotTypeClassyRounded": "Κομψό στρογγυλεμένο", + "cornerSquareLabel": "Γωνιακά τετράγωνα", + "cornerSquareDot": "Τελεία", + "cornerSquareSquare": "Τετράγωνο", + "cornerSquareExtraRounded": "Έξτρα στρογγυλεμένο", + "cornerDotLabel": "Γωνιακές τελείες", + "cornerDotDot": "Τελεία", + "cornerDotSquare": "Τετράγωνο", + "eccLabel": "Διόρθωση σφαλμάτων", + "eccL": "Χαμηλή (~7%)", + "eccM": "Μέτρια (~15%)", + "eccQ": "Τεταρτημόριο (~25%)", + "eccH": "Υψηλή (~30%)", + "marginLabel": "Περιθώριο (ενότητες)", + "widthLabel": "Πλάτος εικόνας", + "widthEffective": "Απόδοση σε {px} px", + "darkColor": "Σκούρες ενότητες", + "lightColor": "Ανοιχτές ενότητες", + "resetColors": "Επαναφορά χρωμάτων", + "logoLabel": "Κεντρικό λογότυπο", + "logoMarginLabel": "Περιθώριο", + "uploadLogo": "Μεταφόρτωση εικόνας", + "removeLogo": "Αφαίρεση λογοτύπου", + "downloadPng": "Λήψη PNG", + "downloadSvg": "Λήψη SVG", + "copyImage": "Αντιγραφή εικόνας", + "copied": "Αντιγράφηκε", + "clear": "Εκκαθάριση", + "previewAlt": "Προεπισκόπηση QR", + "emptyHint": "Πληκτρολογήστε ή επικολλήστε περιεχόμενο για προεπισκόπηση.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "Δεν ήταν δυνατή η δημιουργία QR. Δοκιμάστε μικρότερο κείμενο ή χαμηλότερη διόρθωση.", + "copyUnsupported": "Ο browser δεν υποστηρίζει αντιγραφή εικόνας στο πρόχειρο.", + "copyFailed": "Αποτυχία αντιγραφής στο πρόχειρο." + } + }, + "IpSubnetCalculator": { + "title": "Υπολογιστής IP / υποδικτύου", + "subtitle": "Λεπτομέρειες CIDR IPv4 και IPv6 στον browser—χωρίς εξωτερικά αιτήματα.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Δίκτυο", + "placeholderIpv4": "10.0.0.0/24 ή 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 ή 2001:db8::1/64", + "hintIpv4": "Χρησιμοποιήστε CIDR (π.χ. /24) ή IPv4 + μάσκα με τελείες.", + "hintIpv6": "Συμπεριλάβετε μήκος προθέματος (π.χ. /64). Χωρίς αυτό /128.", + "copyValue": "Αντιγραφή τιμής", + "rows": { + "cidr": "CIDR", + "network": "Διεύθυνση δικτύου", + "netmask": "Μάσκα υποδικτύου", + "wildcard": "Μάσκα wildcard", + "broadcast": "Broadcast", + "firstHost": "Πρώτος κόμβος", + "lastHost": "Τελευταίος κόμβος", + "totalAddresses": "Συνολικές διευθύνσεις", + "usableHosts": "Χρήσιμοι κόμβοι", + "prefixLength": "Μήκος προθέματος (bits)", + "networkIpv6": "Διεύθυνση δικτύου", + "lastIpv6": "Τελευταία διεύθυνση στο εύρος", + "totalAddressesIpv6": "Διευθύνσεις στο υποδίκτυο" + }, + "usableNote": { + "pointToPoint": "Συνδέσεις /31 point-to-point χρησιμοποιούν και τις δύο διευθύνσεις ως hosts (RFC 3021).", + "singleHost": "/32 είναι μία διεύθυνση host." + }, + "ipv6RangeHint": { + "normal": "Εμφανίζεται πλήρες μαθηματικό εύρος· οι κανόνες δρομολογητή μπορεί να διαφέρουν.", + "single": "/128 είναι μία διεύθυνση.", + "full": "/0 καλύπτει όλο το IPv6." + }, + "errors": { + "invalidIpv4": "Μη έγκυρο IPv4 δίκτυο. Δοκιμάστε CIDR ή διεύθυνση + μάσκα.", + "invalidIpv6": "Μη έγκυρο IPv6 δίκτυο. Χρησιμοποιήστε διεύθυνση/πρόθεμα, π.χ. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Γεννήτρια hash", + "subtitle": "Αποσπάσματα MD5, SHA-1 και SHA-2 για κείμενο ή αρχεία· bcrypt για κωδικούς (εισαγωγή κειμένου). Όλα τοπικά στον περιηγητή.", + "algorithmLabel": "Αλγόριθμος", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Κείμενο", + "modeFile": "Αρχείο", + "inputLabelText": "Είσοδος", + "placeholderText": "Πληκτρολογήστε ή επικολλήστε κείμενο…", + "chooseFile": "Επιλογή αρχείου", + "clearFile": "Εκκαθάριση", + "fileHint": "Τα αρχεία διαβάζονται μόνο στη μνήμη· μέγ. {maxMb} MB. Χωρίς μεταφόρτωση.", + "outputLabel": "Digest (hex)", + "copyHash": "Αντιγραφή digest", + "copied": "Αντιγράφηκε", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# χαρακτήρας} other {# χαρακτήρες}}", + "emptyHint": "Εισάγετε κείμενο ή επιλέξτε αρχείο.", + "securityNote": "Τα MD5 και SHA-1 δεν συνιστώνται για νέες εφαρμογές ασφαλείας· προτιμήστε SHA-256 ή SHA-512.", + "errors": { + "fileTooLarge": "Το αρχείο είναι μεγαλύτερο από {maxMb} MB.", + "fileReadFailed": "Αποτυχία ανάγνωσης αρχείου." + }, + "bcryptRoundsLabel": "Κόστος (γύροι)", + "bcryptRoundsHint": "Υψηλότερες τιμές είναι πιο αργές και πιο ανθεκτικές. Συνήθης προεπιλογή 10.", + "fileTabDisabledTitle": "Το bcrypt κατακερματίζει μόνο κωδικούς από κείμενο, όχι αυθαίρετα αρχεία.", + "placeholderBcrypt": "Πληκτρολογήστε ή επικολλήστε κωδικό για κατακερματισμό…", + "outputLabelBcrypt": "Κατακερματισμός bcrypt", + "copyBcrypt": "Αντιγραφή κατακερματισμού", + "emptyHintBcrypt": "Εισάγετε κωδικό για υπολογισμό bcrypt.", + "securityNoteBcrypt": "Κάθε έξοδος bcrypt περιλαμβάνει τυχαίο αλάτι· ο ίδιος κωδικός δίνει διαφορετική συμβολοσειρά κάθε φορά. Αυτό αναμένεται. Χρησιμοποιήστε υψηλό κόστος σε παραγωγή.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Χώρος δοκιμών κρυπτογράφησης", + "subtitle": "AES-GCM στον browser: κρυπτογράφηση σε δέσμη JSON με συνθηματική ή ακατέργαστο κλειδί και αποκρυπτογράφηση τοπικά. Εκπαιδευτικό και περιστασιακά πρακτικό — δεν ανεβαίνει τίποτα.", + "tabEncrypt": "Κρυπτογράφηση", + "tabDecrypt": "Αποκρυπτογράφηση", + "keyModeLabel": "Υλικό κλειδιού", + "modePassphrase": "Συνθηματική φράση (PBKDF2 + AES-GCM)", + "modeRawKey": "Ακατέργαστο κλειδί AES (hex ή Base64)", + "aesBitsLabel": "Μήκος κλειδιού AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Κωδικοποίηση ακατέργαστου κλειδιού", + "encodingAuto": "Αυτόματο (hex αν μοιάζει με hex, αλλιώς Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Ακατέργαστο κλειδί", + "rawKeyPlaceholder": "Κλειδί 32 byte ως 64 hex χαρακτήρες ή Base64 για AES-256 (το μήκος εξαρτάται από το AES παραπάνω).", + "rawKeyPlaceholderDecrypt": "Χρειάζεται μόνο όταν η δέσμη έχει keyDerivation \"none\".", + "passphraseLabel": "Συνθηματική φράση", + "passphrasePlaceholder": "Η φράση χρησιμοποιείται μόνο στον browser για παραγωγή κλειδιού.", + "passphrasePlaceholderDecrypt": "Χρειάζεται μόνο όταν η δέσμη χρησιμοποιεί PBKDF2-SHA256.", + "pbkdf2IterLabel": "Επαναλήψεις PBKDF2", + "pbkdf2Hint": "Μεταξύ 10.000 και 2.000.000. Η προεπιλογή ταιριάζει με κοινές ρυθμίσεις vault.", + "plaintextLabel": "Απλό κείμενο", + "plaintextPlaceholder": "Πληκτρολογήστε ή επικολλήστε το μήνυμα προς κρυπτογράφηση…", + "bundleInputLabel": "Κρυπτογραφημένη δέσμη (JSON)", + "bundlePlaceholder": "Επικολλήστε το JSON από Κρυπτογράφηση (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Κρυπτογραφημένη δέσμη", + "plaintextOutputLabel": "Απλό κείμενο", + "encryptButton": "Κρυπτογράφηση", + "decryptButton": "Αποκρυπτογράφηση", + "copyBundle": "Αντιγραφή JSON", + "copyPlaintext": "Αντιγραφή απλού κειμένου", + "copied": "Αντιγράφηκε", + "emptyBundleHint": "Το κρυπτογραφημένο JSON εμφανίζεται εδώ μετά την κρυπτογράφηση.", + "emptyDecryptHint": "Το αποκρυπτογραφημένο κείμενο εμφανίζεται εδώ.", + "decryptKeyHint": "Χρησιμοποιήστε ακατέργαστο κλειδί αν keyDerivation είναι \"none\"· τη συνθηματική για PBKDF2-SHA256. Η δέσμη αποθηκεύει αλάτι, επαναλήψεις, IV και μήκος AES.", + "securityNote": "Τα κλειδιά και το απλό κείμενο δεν εγκαταλείπουν τον browser. Για μυστικά παραγωγής προτιμήστε εξειδικευμένα εργαλεία.", + "errors": { + "emptyKey": "Εισάγετε ακατέργαστο κλειδί που ταιριάζει στο μέγεθος AES.", + "emptyPassphrase": "Εισάγετε συνθηματική φράση.", + "badKeyMaterial": "Δεν ήταν δυνατή η ανάγνωση του κλειδιού ως hex ή Base64.", + "wrongKeyLength": "Το μήκος κλειδιού δεν ταιριάζει στο AES (16 / 24 / 32 byte).", + "invalidJson": "Μη έγκυρο JSON.", + "invalidBundle": "Αυτό το JSON δεν είναι υποστηριζόμενη δέσμη κρυπτογράφησης.", + "decryptFailed": "Η αποκρυπτογράφηση απέτυχε — λάθος μυστικό, κατεστραμμένη δέσμη ή παράμετροι.", + "encryptFailed": "Η κρυπτογράφηση απέτυχε. Ελέγξτε το Web Crypto και τις εισόδους." + } + }, + "HmacGenerator": { + "title": "Γεννήτρια HMAC", + "subtitle": "Για υπογραφή webhooks και δοκιμή ενσωματώσεων API. HMAC-SHA1/256/384/512 σε hex ή Base64—υπολογισμός τοπικά στον περιηγητή.", + "secretLabel": "Μυστικό κλειδί", + "messageLabel": "Μήνυμα / φορτίο", + "placeholderSecret": "Επικολλήστε το μυστικό υπογραφής…", + "placeholderMessage": "Επικολλήστε το ακατέργαστο σώμα ή τη συμβολοσειρά επαλήθευσης (συχνά τα ακριβή bytes του webhook).", + "algorithmLabel": "Αλγόριθμος HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Μορφή εξόδου", + "formatHex": "Hex (πεζά)", + "formatBase64": "Base64", + "outputLabel": "Υπογραφή", + "copyOutput": "Αντιγραφή υπογραφής", + "copied": "Αντιγράφηκε", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8)", + "emptyHint": "Εισάγετε μυστικό κλειδί για υπολογισμό HMAC.", + "securityNote": "Μην μοιράζεστε ποτέ το μυστικό υπογραφής. Αυτή η σελίδα δεν ανεβάζει δεδομένα—όλα εκτελούνται στον περιηγητή.", + "errors": { + "cryptoFailed": "Αδυναμία υπολογισμού HMAC. Ελέγξτε την υποστήριξη Web Crypto και δοκιμάστε ξανά.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Γεννήτρια κωδικού TOTP / 2FA", + "subtitle": "Επικολλήστε το μυστικό Base32 και λάβετε τον τρέχοντα εξαψήφιο κωδικό RFC 6238 (SHA-1, 30 δ). Τοπικά στον περιηγητή—χρήσιμο για δοκιμές σύνδεσης ή MFA.", + "secretLabel": "Μυστικό (Base32)", + "placeholderSecret": "Επικολλήστε το μυστικό κλειδί (γράμματα A–Z και ψηφία 2–7, όπως κατά τη ρύθμιση του authenticator)…", + "codeLabel": "Τρέχων κωδικός", + "copyCode": "Αντιγραφή κωδικού", + "copied": "Αντιγράφηκε", + "nextIn": "Ανανέωση σε {seconds} δ", + "standardHint": "Βήμα χρόνου 30 δ, 6 ψηφία, HMAC-SHA-1 (συμβατό με Google Authenticator).", + "digitsLabel": "Ψηφία", + "periodLabel": "Περίοδος", + "algorithmLabel": "Αλγόριθμος", + "periodSeconds": "{seconds} δ", + "emptyHint": "Επικολλήστε μυστικό για εξαψήφιο κωδικό.", + "invalidHint": "Διορθώστε το μυστικό για να εμφανιστεί ο κωδικός.", + "securityNote": "Αντιμετωπίστε το ως κωδικό πρόσβασης. Το μυστικό δεν εγκαταλείπει τον περιηγητή· δεν γίνεται μεταφόρτωση.", + "importLabel": "Εισαγωγή από otpauth:// URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Εισαγωγή", + "qrSectionTitle": "Κωδικός QR παροχής", + "qrHint": "Σαρώστε με μια εφαρμογή ελέγχου ταυτότητας (Google Authenticator, 1Password, Aegis…) για να προσθέσετε αυτό το μυστικό. Ο κωδικός QR κωδικοποιεί το μυστικό — μεταχειριστείτε το σαν κωδικό πρόσβασης.", + "accountLabel": "Λογαριασμός", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Εκδότης (προαιρετικό)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Εισαγάγετε ένα έγκυρο μυστικό για να δημιουργήσετε κωδικό QR.", + "errors": { + "invalidSecret": "Μη έγκυρο μυστικό Base32.", + "cryptoFailed": "Δεν ήταν δυνατός ο υπολογισμός TOTP. Ελέγξτε το Web Crypto και δοκιμάστε ξανά.", + "invalidUri": "Αυτό δεν είναι έγκυρο otpauth:// TOTP URI.", + "unsupportedUri": "Αυτό το URI χρησιμοποιεί τιμές που δεν υποστηρίζει το εργαλείο (τα ψηφία πρέπει να είναι 6 ή 8· η περίοδος 15, 30 ή 60 δευτ.)." + } + }, + "MarkdownPreview": { + "title": "Προεπισκόπηση Markdown & HTML", + "subtitle": "Ζωντανός renderer Markdown με εξαγωγή HTML και μετατροπή HTML → Markdown. Όλα εκτελούνται στο πρόγραμμα περιήγησής σας.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Είσοδος Markdown", + "htmlInputLabel": "Είσοδος HTML", + "previewLabel": "Προεπισκόπηση", + "htmlOutputLabel": "Πηγαίος HTML", + "mdOutputLabel": "Έξοδος Markdown", + "inputPlaceholder": "# Γεια\\n\\nΠληκτρολογήστε το **markdown** εδώ…", + "htmlInputPlaceholder": "Επικολλήστε το HTML εδώ…", + "copyHtml": "Αντιγραφή HTML", + "copyMarkdown": "Αντιγραφή Markdown", + "exportHtml": "Εξαγωγή HTML", + "copied": "Αντιγράφηκε", + "charCount": "{count, plural, one {# χαρακτήρας} other {# χαρακτήρες}}", + "emptyHint": "Ξεκινήστε να πληκτρολογείτε Markdown για να δείτε την προεπισκόπηση και την έξοδο HTML.", + "emptyHintHtmlToMd": "Επικολλήστε HTML για μετατροπή σε Markdown.", + "securityNote": "Όλα εκτελούνται τοπικά στο πρόγραμμα περιήγησής σας. Δεν γίνεται καμία μεταφόρτωση." + }, + "NumberBaseConverter": { + "title": "Μετατροπέας αριθμητικής βάσης", + "subtitle": "Μετατροπή ακεραίων μεταξύ βάσεων 2–36. Προαιρετικά προθέματα 0x / 0b / 0o. Όλα τοπικά στον περιηγητή.", + "inputBaseLabel": "Από βάση", + "outputBaseLabel": "Σε βάση", + "inputLabel": "Αριθμός εισόδου", + "placeholderInput": "Εισάγετε ακέραιο στη επιλεγμένη βάση εισόδου…", + "outputLabel": "Μετατραπείσα τιμή", + "copyOutput": "Αντιγραφή αποτελέσματος", + "copied": "Αντιγράφηκε", + "swap": "Εναλλαγή βάσεων", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (μήκος UTF-8)", + "emptyHint": "Εισάγετε αριθμό για μετατροπή.", + "note": "Ψηφία πάνω από 9 χρησιμοποιούν A–Z (ανεξάρτητα πεζών/κεφαλαίων). Κενά και υπογραμμίσεις αγνοούνται.", + "errors": { + "invalidNumber": "Μη έγκυρη τιμή για την επιλεγμένη βάση εισόδου." + } + }, + "FormatConverter": { + "title": "Μετατροπέας Μορφών", + "subtitle": "Μετατρέψτε μεταξύ JSON, YAML, TOML και XML σε οποιαδήποτε κατεύθυνση. Και οι 12 συνδυασμοί. Όλα εκτελούνται στο πρόγραμμα περιήγησής σας.", + "fromLabel": "Από", + "toLabel": "Σε", + "swap": "Εναλλαγή", + "swapTitle": "Εναλλαγή μορφών και χρήση εξόδου ως είσοδος", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "charCount": "{count, plural, one {# χαρακτήρας} other {# χαρακτήρες}}", + "emptyHint": "Επικολλήστε ή πληκτρολογήστε την είσοδό σας αριστερά για να δείτε την μετατραπείσα έξοδο.", + "localNote": "Όλα εκτελούνται τοπικά στο πρόγραμμα περιήγησής σας. Τίποτα δεν ανεβαίνει." + }, + "MimeTypeLookup": { + "title": "Αναζήτηση τύπου MIME", + "subtitle": "Αναζητήστε τον τύπο MIME (media type) για επέκταση ή πλήρες όνομα αρχείου. Όλα εκτελούνται τοπικά στο πρόγραμμα περιήγησής σας.", + "inputLabel": "Επέκταση ή όνομα αρχείου", + "placeholder": "π.χ. .png, package.json, photo.jpeg", + "hint": "Συμβουλή: επικολλήστε είτε επέκταση (με ή χωρίς τελεία) είτε πλήρες όνομα αρχείου.", + "clear": "Εκκαθάριση", + "exampleFilename": "Χρήση παραδείγματος ονόματος αρχείου", + "exampleExtension": "Χρήση παραδείγματος επέκτασης", + "resultLabel": "Αποτέλεσμα", + "normalizedLabel": "Κανονικοποιημένη είσοδος:", + "mimeTypeLabel": "Τύπος MIME", + "contentTypeLabel": "Κεφαλίδα Content-Type", + "charsetLabel": "Σύνολο χαρακτήρων", + "copyMimeType": "Αντιγραφή τύπου MIME", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "emptyHint": "Εισάγετε επέκταση ή όνομα αρχείου για να αναζητήσετε τον τύπο MIME.", + "notFound": "Δεν βρέθηκε τύπος MIME για αυτή την είσοδο.", + "localNote": "Όλα εκτελούνται τοπικά στο πρόγραμμα περιήγησής σας. Τίποτα δεν ανεβαίνει." + }, + "UserAgentParser": { + "title": "Αναλυτής User-Agent", + "subtitle": "Επικολλήστε μια συμβολοσειρά User-Agent (UA) και δείτε ανάλυση για πρόγραμμα περιήγησης, OS, engine, συσκευή και CPU. Όλα εκτελούνται τοπικά στο πρόγραμμα περιήγησής σας.", + "inputLabel": "Συμβολοσειρά User-Agent", + "placeholder": "Επικολλήστε το User-Agent εδώ…", + "hint": "Συμβουλή: μπορείτε να αντιγράψετε UA από headers αιτήματος, logs ή τα devtools του προγράμματος περιήγησης.", + "clear": "Εκκαθάριση", + "exampleDesktop": "Χρήση παραδείγματος desktop", + "exampleMobile": "Χρήση παραδείγματος mobile", + "resultLabel": "Ανάλυση", + "browserLabel": "Πρόγραμμα περιήγησης", + "osLabel": "Λειτουργικό σύστημα", + "engineLabel": "Engine", + "deviceTypeLabel": "Τύπος συσκευής", + "deviceVendorLabel": "Κατασκευαστής", + "deviceModelLabel": "Μοντέλο", + "cpuArchLabel": "Αρχιτεκτονική CPU", + "copyJson": "Αντιγραφή JSON", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "emptyHint": "Επικολλήστε μια συμβολοσειρά User-Agent για να δείτε την ανάλυση.", + "localNote": "Όλα εκτελούνται τοπικά στο πρόγραμμα περιήγησής σας. Τίποτα δεν ανεβαίνει." + }, + "MockDataGenerator": { + "title": "Γεννήτρια δοκιμαστικών δεδομένων", + "subtitle": "Ορίστε στήλες, επιλέξτε τύπους και εξάγετε σύνολα για seeds, Postman ή δοκιμές. Έως {max} γραμμές, όλα στον browser — χωρίς μεταφόρτωση.", + "mockarooLink": "Ροή εργασίας σαν το Mockaroo", + "rowsLabel": "Γραμμές", + "rowsHint": "Έως {max} γραμμές ανά δημιουργία για να παραμείνει η καρτέλα γρήγορη.", + "formatLabel": "Μορφή", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Όνομα πίνακα SQL", + "presetsLabel": "Πρότυπα", + "presetPlaceholder": "Φόρτωση προτύπου…", + "presetDefault": "Έναρξη (χρήστης)", + "presetUsers": "Χρήστες και ρόλοι", + "presetProducts": "SKU ηλεκτρονικού εμπορίου", + "presetApiLog": "Καταγραφή αιτημάτων HTTP", + "generate": "Δημιουργία", + "warningDuplicateNames": "Διπλά ονόματα στηλών: οι νεότερες αντικαθιστούν τις παλαιότερες σε αντικείμενα JSON. Μετονομάστε.", + "schemaHeading": "Σχήμα", + "addField": "Προσθήκη στήλης", + "removeField": "Αφαίρεση στήλης", + "duplicateField": "Αντιγραφή στήλης", + "colName": "Όνομα", + "colType": "Τύπος", + "colOptions": "Επιλογές", + "colBlank": "Κενό %", + "optMin": "ελάχ", + "optMax": "μέγ", + "optDecimals": "δεκ", + "optDateFrom": "από ημ/νία", + "optDateTo": "έως ημ/νία", + "optValues": "a, b, c", + "optStep": "βήμα", + "optRowBase": "βάση", + "output": "Έξοδος", + "outputPlaceholder": "Κάντε κλικ στη Δημιουργία για δεδομένα…", + "copy": "Αντιγραφή", + "download": "Λήψη", + "copied": "Αντιγράφηκε", + "groups": { + "personal": "Προσωπικά", + "location": "Τοποθεσία", + "internetApi": "Διαδίκτυο & API", + "numbersIds": "Αριθμοί & αναγνωριστικά", + "dateTime": "Ημερομηνία & ώρα", + "textDesign": "Κείμενο & σχεδίαση", + "custom": "Προσαρμοσμένο" + }, + "fieldTypes": { + "first_name": "Όνομα", + "last_name": "Επώνυμο", + "full_name": "Πλήρες όνομα", + "email": "Email", + "username": "Όνομα χρήστη", + "password": "Κωδικός", + "phone": "Τηλέφωνο", + "company": "Εταιρεία", + "job_title": "Θέση", + "avatar_url": "URL αβατάρ", + "city": "Πόλη", + "country": "Χώρα", + "country_code": "Κωδικός χώρας (ISO)", + "street_address": "Διεύθυνση", + "zip_code": "Ταχ. κώδικας", + "latitude": "Γεωγρ. πλάτος", + "longitude": "Γεωγρ. μήκος", + "url": "URL", + "domain": "Τομέας", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Ακέραιος", + "float": "Δεκαδικός", + "boolean": "Λογικό", + "uuid": "UUID", + "credit_card": "Κάρτα (δοκιμή)", + "semver": "SemVer", + "row_index": "Δείκτης γραμμής", + "sequence": "Ακολουθία", + "date": "Ημερομηνία", + "datetime": "Ημερομηνία και ώρα", + "unix_timestamp": "Χρονική σήμανση Unix", + "timezone": "Ζώνη ώρας", + "word": "Λέξη", + "sentence": "Πρόταση", + "paragraph": "Παράγραφος", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Κωδικός νομίσματος", + "language_code": "Ετικέτα γλώσσας", + "custom_list": "Προσαρμοσμένη λίστα" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Βελτιστοποιητής SVG", + "subtitle": "Επικολλήστε SVG, συμπιέστε με SVGO στον browser και συγκρίνετε το μέγεθος UTF-8 πριν/μετά.", + "inputLabel": "Είσοδος", + "outputLabel": "Βελτιστοποιημένο", + "placeholder": "Επικολλήστε απόσπασμα ή ολόκληρο αρχείο…", + "outputPlaceholder": "Το βελτιστοποιημένο SVG εμφανίζεται εδώ…", + "chars": "{count} χαρακτήρες • {bytes} bytes (UTF-8)", + "preview": "Προεπισκόπηση", + "localNote": "Όλη η επεξεργασία τοπικά στον browser. Δεν ανεβαίνει τίποτα.", + "stats": { + "title": "Μέγεθος (UTF-8)", + "before": "Πριν", + "after": "Μετά", + "savedBytes": "Εξοικονόμηση {count} bytes ({percent}%)", + "noChange": "Χωρίς μείωση — ήδη ελάχιστο ή κενή έξοδος." + }, + "actions": { + "clear": "Εκκαθάριση", + "sample": "Φόρτωση δείγματος", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "download": "Λήψη" + }, + "errors": { + "optimizeFailed": "Δεν ήταν δυνατή η βελτιστοποίηση αυτού του SVG." + } + }, + "UnitConverter": { + "title": "Μετατροπέας Μονάδων", + "subtitle": "Μετατροπή μεταξύ 323 μονάδων σε 43 κατηγορίες", + "category": "Κατηγορία", + "from": "Από", + "to": "Σε", + "value": "Τιμή", + "result": "Αποτέλεσμα", + "swapUnits": "Εναλλαγή μονάδων", + "placeholder": "Εισάγετε τιμή", + "copyResult": "Αντιγραφή αποτελέσματος", + "searchCategory": "Αναζήτηση κατηγορίας...", + "selectCategory": "Επιλέξτε κατηγορία", + "noResults": "Δεν βρέθηκε κατηγορία." + }, + "DockerComposeGenerator": { + "title": "Δημιουργός Docker Compose", + "subtitle": "Επιλέξτε δημοφιλείς βάσεις δεδομένων, cache, ουρές μηνυμάτων, reverse proxy και παρατηρησιμότητα — αντιγράψτε ένα docker-compose.yml έτοιμο για τοπική ανάπτυξη.", + "searchPlaceholder": "Αναζήτηση (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Υπηρεσίες", + "selectedCount": "{count, plural, one {# υπηρεσία} other {# υπηρεσίες}}", + "outputTitle": "docker-compose.yml", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε!", + "download": "Λήψη", + "emptyState": "Επιλέξτε μία ή περισσότερες υπηρεσίες για δημιουργία YAML Compose.", + "hintPorts": "Οι προεπιλεγμένες θύρες μειώνουν συγκρούσεις· αλλάξτε τις αν το Docker αναφέρει δέσμευση. Αντικαταστήστε κωδικούς επίδειξης πριν από κοινά περιβάλλοντα.", + "categories": { + "databases": "Βάσεις δεδομένων και αποθήκευση", + "cache": "Cache και in-memory", + "queues": "Ουρές και ανταλλαγή μηνυμάτων", + "search": "Αναζήτηση και αρχεία καταγραφής", + "storage": "Αποθήκευση αντικειμένων", + "proxy": "Reverse proxy και ingress", + "observability": "Μετρήσεις και ιχνηλάτηση", + "mail": "Ηλεκτρονικό ταχυδρομείο (τοπική ανάπτυξη)", + "admin": "Διαχειριστικές και BI διεπαφές" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Εκκαθάριση" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (συμβατό με Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (συμβατό με Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (συμβατό με S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (καταγραφή SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (διακομιστής ανάπτυξης)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Σύρετε τα πλακίδια και συγχωνεύστε τα για να φτάσετε στο 2048", + "score": "Βαθμολογία", + "best": "Άριστες", + "newGame": "#-#-#-#-# five-or-more.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο παιχνίδι#-#-#-#-# gnome-mahjongg.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο παιχνίδι#-#-#-#-# gnome-nibbles.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο Παιχνίδι#-#-#-#-# tali.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο παιχνίδι#-#-#-#-# iagno.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο Παιχνίδι#-#-#-#-# gnome-mines.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο Παιχνίδι#-#-#-#-# aisleriot.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο παιχνίδι#-#-#-#-# gnome-klotski.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο Παιχνίδι#-#-#-#-# gnome-robots.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο Παιχνίδι#-#-#-#-# quadrapassel.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο παιχνίδι#-#-#-#-# gnome-tetravex.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο παιχνίδι#-#-#-#-# gnome-sudoku.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο Παιχνίδι#-#-#-#-# lightsoff.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο Παιχνίδι#-#-#-#-# swell-foop.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο παιχνίδι#-#-#-#-# four-in-a-row.master.el.po (gnome-games.HEAD.el) #-#-#-#-#Νέο Παιχνίδι", + "howToPlay": "Χρησιμοποιήστε τα πλήκτρα βέλους (ή σύρετε στο κινητό) για να μετακινήσετε τα πλακίδια. Τα πλακίδια με τον ίδιο αριθμό συγχωνεύονται. Φτάστε στο 2048 για να κερδίσετε!", + "youWin": "Φτάσατε στο 2048!", + "keepGoing": "Συνεχίζει.", + "gameOver": "Τέλος παιχνιδιού", + "tryAgain": "Δοκιμάστε ξανά", + "gameOverMessage": "Δεν έχεις άλλες κινήσεις", + "undo": "Αναίρεση" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "ολοκληρώθηκαν τα στάδια", + "time": "Χρόνος", + "best": "Άριστες", + "difficulty": { + "easy": "Εύκολο", + "medium": "Μεσαίες", + "hard": "Δύσκολο" + }, + "stage": "Φάση", + "solved": "Επιλύθηκε", + "score": "Βαθμολογία", + "nextStage": "Επόμενο στάδιο της διαδικασίας", + "replay": "Επανάληψη", + "erase": "Σβήσιμο", + "howToPlay": "Κάντε κλικ σε ένα κελί και, στη συνέχεια, πατήστε σε έναν αριθμό ή πατήστε 1–9. Το Backspace ή το Delete διαγράφει το κελί. Οι έντονοι αριθμοί είναι ενδείξεις. Γεμίστε το πλέγμα έτσι ώστε κάθε σειρά, στήλη και πλαίσιο 3×3 να περιέχει 1–9 χωρίς επαναλήψεις.", + "pencil": "Μολύβι", + "undo": "Αναίρεση", + "hint": "Υπόδειξη", + "hintsUsed": "υποδείξεις χρησιμοποιήθηκαν", + "mistakes": "Λάθη", + "failed": "Απέτυχες!", + "mistakeMode": "Λειτουργία 3 λαθών" + }, + "Snake": { + "title": "Φίδι", + "subtitle": "Φάε, μεγάλωσε, απόφυγε τοίχους και τον εαυτό σου", + "score": "Σκορ", + "best": "Καλύτερο", + "startPrompt": "Πάτησε οποιοδήποτε πλήκτρο βέλους", + "startButton": "Έναρξη παιχνιδιού", + "gameOver": "Τέλος παιχνιδιού", + "playAgain": "Παίξε ξανά", + "howToPlay": "Χρησιμοποίησε τα βέλη ή WASD. Φάε για να μεγαλώσεις. Η ταχύτητα αυξάνεται κάθε 50 πόντους.", + "diffEasy": "Εύκολο", + "diffNormal": "Κανονικό", + "diffHard": "Δύσκολο", + "diffInfinite": "Άπειρο", + "orTap": "ή πατήστε παρακάτω", + "howToPlayInfinite": "Τοίχοι περιτυλίγονται — χωρίς θάνατο από τοίχους. Φάε για να μεγαλώσεις.", + "shieldActive": "Ασπίδα ενεργή", + "speedBoost": "Ενίσχυση ταχύτητας", + "powerSpeed": "Ταχύτητα x2", + "powerShield": "Επιβίωση θανάτου", + "powerShrink": "Συντομεύει ουρά" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Συνδέσεις Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Συνδεθείτε σε στιγμιότυπα Redis. Περιηγηθείτε σε κλειδιά, επεξεργαστείτε τιμές και εκτελέστε εντολές. Τα διαπιστευτήρια κρυπτογραφούνται στο πρόγραμμα περιήγησής σας.", + "heroAddConnection": "Προσθήκη σύνδεσης", + "vaultLockedTitle": "Θησαυροφυλάκιο κλειδωμένο", + "vaultLockedDesc": "Ξεκλειδώστε το θησαυροφυλάκιο για πρόσβαση στις αποθηκευμένες συνδέσεις." + } + }, + "Minesweeper": { + "title": "Ναρκαλιευτής", + "subtitle": "Βρείτε όλα τα ασφαλή κελιά χωρίς να πατήσετε νάρκη", + "mines": "Νάρκες", + "newGame": "Νέο παιχνίδι", + "win": "Κερδίσατε!", + "loss": "Τέλος παιχνιδιού!", + "playAgain": "Παίξτε ξανά", + "tryAgain": "Δοκιμάστε ξανά", + "startPrompt": "Κάντε κλικ σε οποιοδήποτε κελί για να ξεκινήσετε", + "difficulty": { + "beginner": "Αρχάριος", + "intermediate": "Μεσαίος", + "expert": "Ειδικός" + } + }, + "Tetris": { + "title": "Τέτρις", + "subtitle": "Τακτοποιήστε τα μπλοκ που πέφτουν για να ολοκληρώσετε σειρές", + "next": "Επόμενο", + "score": "Σκορ", + "best": "Καλύτερο", + "lines": "Γραμμές", + "level": "Επίπεδο", + "pause": "Παύση", + "resume": "Συνέχεια", + "start": "Εκκίνηση", + "restart": "Επανεκκίνηση", + "paused": "Σε παύση", + "gameOver": "Τέλος παιχνιδιού" + }, + "SshKeyGenerator": { + "title": "Γεννήτρια κλειδιών SSH / RSA", + "subtitle": "Δημιουργήστε ζεύγη κλειδιών Ed25519 ή RSA στο πρόγραμμα περιήγησής σας. Κατεβάστε ιδιωτικά και δημόσια κλειδιά σε μορφές OpenSSH και PEM. Τίποτα δεν μεταφορτώνεται.", + "keyTypeLabel": "Τύπος κλειδιού", + "recommended": "προτεινόμενο", + "ed25519Desc": "Γρήγορο, μικρό, σύγχρονο. Υποστηρίζεται από GitHub, GitLab και όλους τους μεγάλους SSH διακομιστές.", + "rsa2048Desc": "Ευρεία συμβατότητα. Ασφαλές για τις περισσότερες χρήσεις.", + "rsa4096Desc": "Μέγιστη συμβατότητα και ισχύς για παλαιά συστήματα.", + "commentLabel": "Σχόλιο (προαιρετικό)", + "commentHint": "Προστίθεται στη γραμμή δημόσιου κλειδιού OpenSSH.", + "generateBtn": "Δημιουργία ζεύγους κλειδιών", + "generating": "Δημιουργία…", + "generatingLong": "Δημιουργία (μπορεί να χρειαστεί λίγο)…", + "copy": "Αντιγραφή", + "download": "Λήψη", + "privateKeyLabel": "Ιδιωτικό κλειδί (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Δημόσιο κλειδί (μορφή OpenSSH)", + "publicKeySpkiLabel": "Δημόσιο κλειδί (SPKI PEM)", + "browserNote": "Όλα τα κλειδιά δημιουργούνται στο πρόγραμμα περιήγησής σας. Τίποτα δεν αποστέλλεται σε κανένα διακομιστή.", + "errors": { + "generationFailed": "Η δημιουργία κλειδιού απέτυχε" + } + }, + "DnsLookup": { + "title": "Αναζήτηση DNS", + "subtitle": "Αναζητήστε εγγραφές DNS για οποιοδήποτε τομέα — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Εισαγάγετε τομέα (π.χ. github.com)", + "lookupBtn": "Αναζήτηση", + "looking": "Γίνεται αναζήτηση…", + "refreshBtn": "Ανανέωση", + "recordTypesLabel": "Τύποι εγγραφών", + "allBtn": "Όλα", + "tryLabel": "Δοκιμάστε:", + "errors": { + "lookupFailed": "Η αναζήτηση DNS απέτυχε" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Γεννήτρια και ελεγκτής Bcrypt", + "subtitle": "Κατακερματίστε κωδικούς με bcrypt και επαληθεύστε κατακερματισμούς — όλα παραμένουν στο πρόγραμμα περιήγησής σας", + "tabs": { + "hash": "Κατακερματισμός", + "verify": "Επαλήθευση" + }, + "showPassword": "Εμφάνιση κωδικού", + "hidePassword": "Απόκρυψη κωδικού", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "hash": { + "passwordLabel": "Κωδικός", + "passwordPlaceholder": "Εισαγάγετε έναν κωδικό για κατακερματισμό…", + "costLabel": "Συντελεστής κόστους", + "costHint": "Υψηλότερο κόστος διπλασιάζει την εργασία ανά βήμα. Το 10–12 είναι συνήθης επιλογή για παραγωγή.", + "slowWarning": "Συντελεστές κόστους 13 και άνω μπορεί να χρειαστούν αρκετά δευτερόλεπτα για υπολογισμό.", + "generate": "Δημιουργία κατακερματισμού", + "generating": "Κατακερματισμός…", + "resultLabel": "Κατακερματισμός Bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Ανατομία κατακερματισμού", + "algorithm": "αλγόριθμος", + "cost": "κόστος", + "salt": "salt (22 χαρακτήρες)", + "hash": "hash (31 χαρακτήρες)" + } + }, + "verify": { + "passwordLabel": "Κωδικός", + "passwordPlaceholder": "Εισαγάγετε τον κωδικό προς έλεγχο…", + "hashLabel": "Κατακερματισμός Bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Εισαγάγετε έναν κωδικό και έναν κατακερματισμό bcrypt για να τους συγκρίνετε.", + "invalidHash": "Αυτό δεν φαίνεται να είναι έγκυρος κατακερματισμός bcrypt.", + "checking": "Σύγκριση…", + "match": "Ταιριάζει — ο κωδικός παράγει αυτόν τον κατακερματισμό.", + "noMatch": "Δεν ταιριάζει — ο κωδικός δεν παράγει αυτόν τον κατακερματισμό." + } + }, + "ChmodCalculator": { + "title": "Υπολογιστής chmod", + "subtitle": "Μετατρέψτε δικαιώματα αρχείων Unix μεταξύ πλαισίων ελέγχου, οκταδικής και συμβολικής σημειογραφίας", + "grid": { + "title": "Δικαιώματα", + "read": "Ανάγνωση", + "write": "Εγγραφή", + "execute": "Εκτέλεση", + "owner": "Ιδιοκτήτης", + "group": "Ομάδα", + "others": "Άλλοι" + }, + "special": { + "title": "Ειδικά bit", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Οκταδικό", + "placeholder": "755 ή 4755", + "invalid": "Εισαγάγετε 3 ή 4 οκταδικά ψηφία (0–7)." + }, + "symbolic": { + "label": "Συμβολικό", + "placeholder": "rwxr-xr-x", + "invalid": "Εισαγάγετε 9 χαρακτήρες όπως rwxr-xr-x (επιτρέπονται s/S, t/T)." + }, + "output": { + "title": "Εντολή", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε" + }, + "explain": { + "line": "{cls} μπορεί {perms}.", + "none": "{cls} δεν έχει δικαιώματα.", + "setuid": "Setuid: το αρχείο εκτελείται με τα προνόμια του ιδιοκτήτη του.", + "setgid": "Setgid: το αρχείο εκτελείται με τα προνόμια της ομάδας του (νέα αρχεία σε έναν κατάλογο κληρονομούν την ομάδα του).", + "sticky": "Sticky: μόνο ο ιδιοκτήτης ενός αρχείου μπορεί να το διαγράψει ή να το μετονομάσει μέσα σε αυτόν τον κατάλογο." + }, + "presets": { + "title": "Κοινές προεπιλογές", + "octal": "Οκταδικό", + "permissions": "Δικαιώματα", + "description": "Τυπική χρήση", + "p644": "Κανονικά αρχεία: ο ιδιοκτήτης γράφει, όλοι διαβάζουν.", + "p755": "Εκτελέσιμα και κατάλογοι: όλοι μπορούν να διαβάσουν και να εισέλθουν.", + "p600": "Ιδιωτικά αρχεία: μόνο ο ιδιοκτήτης διαβάζει και γράφει.", + "p700": "Ιδιωτικά εκτελέσιμα και κατάλογοι: μόνο ο ιδιοκτήτης.", + "p664": "Αρχεία εγγράψιμα από την ομάδα.", + "p775": "Κατάλογοι και εκτελέσιμα εγγράψιμα από την ομάδα.", + "p400": "Μόνο για ανάγνωση από τον ιδιοκτήτη (π.χ. ιδιωτικά κλειδιά SSH)." + } + }, + "CodeScreenshot": { + "title": "Στιγμιότυπο κώδικα", + "subtitle": "Μετατρέψτε τον κώδικα σε όμορφες εικόνες για κοινή χρήση", + "languageLabel": "Γλώσσα", + "autoDetect": "Αυτόματη ανίχνευση", + "backgroundLabel": "Φόντο", + "paddingLabel": "Εσωτερικό περιθώριο", + "filenameLabel": "Όνομα αρχείου", + "filenamePlaceholder": "example.ts", + "windowLabel": "Κουκκίδες παραθύρου", + "themeLabel": "Θέμα παραθύρου", + "themeDark": "Σκούρο", + "themeLight": "Ανοιχτό", + "scaleLabel": "Κλίμακα εξαγωγής", + "copyImage": "Αντιγραφή εικόνας", + "copied": "Αντιγράφηκε", + "exportPng": "Εξαγωγή PNG", + "inputLabel": "Κώδικας", + "detected": "Ανιχνεύθηκε: {language}", + "clear": "Καθαρισμός", + "placeholder": "Επικολλήστε τον κώδικά σας εδώ…", + "previewLabel": "Προεπισκόπηση", + "emptyPreview": "Η προεπισκόπηση του κώδικά σας θα εμφανιστεί εδώ", + "exportFailed": "Η εξαγωγή της εικόνας απέτυχε", + "copyUnsupported": "Η αντιγραφή εικόνων δεν υποστηρίζεται σε αυτό το πρόγραμμα περιήγησης", + "copyFailed": "Η αντιγραφή της εικόνας απέτυχε", + "backgrounds": { + "ocean": "Ωκεανός", + "sunset": "Ηλιοβασίλεμα", + "candy": "Καραμέλα", + "forest": "Δάσος", + "midnight": "Μεσάνυχτα", + "gold": "Χρυσό", + "aurora": "Σέλας", + "slate": "Σχιστόλιθος", + "transparent": "Διαφανές" + } + }, + "CssGenerators": { + "title": "Γεννήτριες CSS", + "subtitle": "Box-shadow, border-radius και ρευστό clamp() — αντιγράψτε το CSS.", + "preview": "Προεπισκόπηση", + "output": "CSS", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Ρευστό clamp()" + }, + "boxShadow": { + "presets": "Προεπιλογές", + "layer": "Επίπεδο", + "addLayer": "Προσθήκη επιπέδου", + "removeLayer": "Αφαίρεση επιπέδου", + "x": "X", + "y": "Y", + "blur": "Θόλωση", + "spread": "Διάχυση", + "color": "Χρώμα", + "alpha": "Άλφα", + "inset": "Εσωτερικό", + "presetNames": { + "subtleCard": "Διακριτική κάρτα", + "elevated": "Ανυψωμένο", + "hardOffset": "Έντονη μετατόπιση", + "neonGlow": "Λάμψη νέον", + "inner": "Εσωτερικό", + "layeredSmooth": "Πολυεπίπεδο απαλό" + } + }, + "borderRadius": { + "modeLabel": "Λειτουργία", + "modes": { + "simple": "Ανά γωνία", + "blob": "Οργανικός λεκές" + }, + "unit": "Μονάδα", + "linkCorners": "Σύνδεση γωνιών", + "randomize": "Τυχαιοποίηση", + "horizontal": "Οριζόντιες ακτίνες", + "vertical": "Κατακόρυφες ακτίνες", + "corners": { + "tl": "Πάνω αριστερά", + "tr": "Πάνω δεξιά", + "br": "Κάτω δεξιά", + "bl": "Κάτω αριστερά" + } + }, + "clamp": { + "minFont": "Ελάχ. γραμματοσειρά (px)", + "maxFont": "Μέγ. γραμματοσειρά (px)", + "minViewport": "Ελάχ. viewport (px)", + "maxViewport": "Μέγ. viewport (px)", + "rootFont": "Ριζική γραμματοσειρά (px)", + "viewportWidth": "Προσομοιωμένο viewport", + "computedSize": "Υπολογισμένο μέγεθος", + "slope": "Κλίση", + "intercept": "Τεταγμένη επί την αρχή", + "previewText": "Ξεσκεπάζω την ψυχοφθόρα βδελυγμία", + "invalid": "Εισαγάγετε έγκυρα, διαφορετικά όρια viewport." + } + }, + "ExifViewer": { + "title": "Προβολή και αφαίρεση EXIF", + "subtitle": "Επιθεωρήστε τα μεταδεδομένα φωτογραφιών και αφαιρέστε τα στο πρόγραμμα περιήγησής σας", + "dropTitle": "Αποθέστε μια εικόνα εδώ", + "dropHint": "ή κάντε κλικ για αναζήτηση — η εικόνα σας δεν φεύγει ποτέ από τη συσκευή σας", + "browse": "Επιλογή εικόνας", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Ανάγνωση μεταδεδομένων…", + "parseFailed": "Η ανάγνωση αυτής της εικόνας απέτυχε", + "clear": "Αφαίρεση εικόνας", + "gpsWarning": "Περιέχει τοποθεσία GPS", + "openMap": "Προβολή στο OpenStreetMap", + "noMetadata": "Δεν βρέθηκαν ενσωματωμένα μεταδεδομένα σε αυτήν την εικόνα.", + "facts": { + "name": "Όνομα", + "type": "Τύπος", + "dimensions": "Διαστάσεις", + "size": "Μέγεθος" + }, + "groups": { + "camera": "Κάμερα", + "exposure": "Έκθεση", + "timestamps": "Χρονικές σημάνσεις", + "gps": "Τοποθεσία GPS", + "software": "Λογισμικό και άλλα" + }, + "gpsCard": { + "latitude": "Γεωγραφικό πλάτος", + "longitude": "Γεωγραφικό μήκος", + "altitude": "Υψόμετρο" + }, + "raw": { + "title": "Όλα τα μεταδεδομένα", + "copyJson": "Αντιγραφή JSON", + "copied": "Αντιγράφηκε" + }, + "remove": { + "title": "Αφαίρεση μεταδεδομένων", + "jpegDescription": "Αφαιρέστε χωρίς απώλειες EXIF, XMP, IPTC και σχόλια χωρίς επανασυμπίεση της εικόνας.", + "reencodeDescription": "Επανακωδικοποιήστε την εικόνα για να αφαιρεθούν τα ενσωματωμένα μεταδεδομένα. Αυτό επανασυμπιέζει τα pixel.", + "keepIcc": "Διατήρηση προφίλ χρώματος (ICC)", + "stripButton": "Αφαίρεση μεταδεδομένων", + "reencodeButton": "Επανακωδικοποίηση και αφαίρεση", + "unsupported": "Η αφαίρεση μεταδεδομένων δεν υποστηρίζεται για αυτόν τον τύπο αρχείου.", + "failed": "Η αφαίρεση μεταδεδομένων απέτυχε", + "before": "Πριν", + "after": "Μετά", + "download": "Λήψη καθαρισμένης εικόνας" + } + }, + "FaviconGenerator": { + "title": "Γεννήτρια favicon", + "subtitle": "Δημιουργήστε favicons και εικονίδια εφαρμογών από μία εικόνα", + "dropTitle": "Αποθέστε μια εικόνα εδώ", + "dropHint": "PNG, JPEG ή SVG — κάντε κλικ για αναζήτηση", + "browse": "Επιλογή εικόνας", + "clear": "Αφαίρεση εικόνας", + "backgroundLabel": "Φόντο", + "transparent": "Διαφανές", + "solidColor": "Συμπαγές", + "cornerLabel": "Σχήμα γωνίας", + "cornerNone": "Τετράγωνο", + "cornerRounded": "Στρογγυλεμένο", + "cornerCircle": "Κύκλος", + "maskPaddingLabel": "Περιθώριο maskable", + "maskable": "maskable", + "previewLabel": "Προεπισκόπηση", + "copy": "Αντιγραφή", + "headSnippet": "Απόσπασμα HTML head", + "downloadIco": "Λήψη favicon.ico", + "downloadZip": "Λήψη όλων (.zip)" + }, + "JsonDiff": { + "title": "Σύγκριση JSON", + "subtitle": "Σημασιολογική σύγκριση δύο εγγράφων JSON με επίγνωση της δομής — η σειρά κλειδιών και η μορφοποίηση αγνοούνται", + "options": { + "ignoreArrayOrder": "Αγνόηση σειράς πινάκων" + }, + "loadBothSamples": "Φόρτωση δειγμάτων", + "sample": "Δείγμα", + "format": "Μορφοποίηση", + "clear": "Καθαρισμός", + "formatError": "Αδυναμία μορφοποίησης: η είσοδος δεν είναι έγκυρο JSON", + "placeholder": "Επικολλήστε ή πληκτρολογήστε JSON εδώ…", + "panels": { + "left": "Πρωτότυπο (αριστερά)", + "right": "Τροποποιημένο (δεξιά)", + "results": "Διαφορές" + }, + "summary": { + "added": "{count} προστέθηκαν", + "removed": "{count} αφαιρέθηκαν", + "changed": "{count} άλλαξαν" + }, + "types": { + "added": "Προστέθηκε", + "removed": "Αφαιρέθηκε", + "changed": "Άλλαξε" + }, + "copyChanges": "Αντιγραφή ως JSON", + "copied": "Αντιγράφηκε", + "empty": "Επικολλήστε JSON και στα δύο πλαίσια για να δείτε τις διαφορές τους.", + "identical": "Τα δύο έγγραφα είναι σημασιολογικά πανομοιότυπα." + }, + "JsonpathPlayground": { + "title": "Παιχνιδότοπος JSONPath", + "subtitle": "Κάντε ερωτήματα σε έγγραφα JSON με JSONPath ή JMESPath και δείτε ζωντανά αποτελέσματα", + "engineLabel": "Μηχανή", + "queryLabel": "Ερώτημα", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Σύντομος οδηγός", + "cheats": { + "jpAllTitles": "Όλοι οι τίτλοι βιβλίων", + "jpAllAuthors": "Κάθε συγγραφέας, οπουδήποτε στο έγγραφο", + "jpFirstBook": "Πρώτο βιβλίο", + "jpLastBook": "Τελευταίο βιβλίο", + "jpSlice": "Τα δύο πρώτα βιβλία (τμήμα)", + "jpFilterPrice": "Βιβλία φθηνότερα από 40", + "jpFilterInStock": "Τίτλοι διαθέσιμων βιβλίων", + "jpRecursive": "Όλες οι τιμές, αναδρομικά", + "jmAllTitles": "Όλοι οι τίτλοι βιβλίων", + "jmFirstAuthor": "Συγγραφέας του πρώτου βιβλίου", + "jmLastBook": "Τελευταίο βιβλίο", + "jmSlice": "Τα δύο πρώτα βιβλία (τμήμα)", + "jmFilterPrice": "Τίτλοι βιβλίων φθηνότερων από 40", + "jmCount": "Πλήθος διαθέσιμων βιβλίων", + "jmProjection": "Αναδιαμορφώστε κάθε βιβλίο σε νέο αντικείμενο", + "jmSort": "Τίτλοι ταξινομημένοι κατά τιμή" + }, + "panels": { + "input": "Είσοδος JSON", + "results": "Αποτελέσματα" + }, + "sample": "Δείγμα", + "format": "Μορφοποίηση", + "clear": "Καθαρισμός", + "formatError": "Αδυναμία μορφοποίησης: η είσοδος δεν είναι έγκυρο JSON", + "placeholder": "Επικολλήστε ή πληκτρολογήστε JSON εδώ…", + "outputPlaceholder": "Οι αντιστοιχίσεις θα εμφανιστούν εδώ…", + "matchCount": "{count, plural, one {# αντιστοίχιση} other {# αντιστοιχίσεις}}", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε" + }, + "KeycodeInspector": { + "title": "Επιθεωρητής πλήκτρων", + "subtitle": "Πατήστε οποιοδήποτε πλήκτρο για να επιθεωρήσετε το συμβάν πληκτρολογίου JavaScript του", + "capture": { + "prompt": "Πατήστε οποιοδήποτε πλήκτρο…", + "ready": "Ακρόαση — πατήστε οποιοδήποτε πλήκτρο στο πληκτρολόγιό σας", + "clickToFocus": "Κάντε κλικ εδώ για εστίαση, μετά πατήστε οποιοδήποτε πλήκτρο" + }, + "hero": { + "spaceKey": "(Διάστημα)", + "keyLabel": "event.key", + "repeat": "επανάληψη" + }, + "copyJson": "Αντιγραφή JSON", + "copiedJson": "Το JSON του συμβάντος αντιγράφηκε στο πρόχειρο", + "deprecated": "καταργημένο", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Τυπική", + "left": "Αριστερά", + "right": "Δεξιά", + "numpad": "Αριθμητικό πληκτρολόγιο", + "unknown": "Άγνωστη" + }, + "history": { + "title": "Ιστορικό", + "clear": "Καθαρισμός ιστορικού", + "empty": "Τα πατήματα πλήκτρων θα εμφανιστούν εδώ." + } + }, + "MarkdownTableGenerator": { + "title": "Γεννήτρια πινάκων Markdown", + "subtitle": "Επεξεργαστείτε ένα πλέγμα, επικολλήστε από υπολογιστικό φύλλο και αντιγράψτε έναν όμορφα στοιχισμένο πίνακα Markdown ή HTML", + "addRow": "Προσθήκη γραμμής", + "addColumn": "Προσθήκη στήλης", + "importLabel": "Εισαγωγή", + "importPlaceholder": "Επικολλήστε TSV (από Excel / Φύλλα Google) ή CSV εδώ…", + "importButton": "Εισαγωγή στο πλέγμα", + "pasteClipboard": "Επικόλληση από το πρόχειρο", + "importEmpty": "Δεν υπάρχει τίποτα για εισαγωγή — επικολλήστε πρώτα κάποιες γραμμές", + "clipboardError": "Δεν ήταν δυνατή η ανάγνωση από το πρόχειρο", + "clear": "Καθαρισμός", + "panels": { + "editor": "Επεξεργαστής πίνακα", + "output": "Έξοδος" + }, + "align": { + "left": "Στοίχιση αριστερά", + "center": "Στοίχιση στο κέντρο", + "right": "Στοίχιση δεξιά" + }, + "headerPlaceholder": "Κεφαλίδα", + "cellPlaceholder": "Κελί", + "removeRow": "Αφαίρεση γραμμής", + "removeColumn": "Αφαίρεση στήλης", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε" + }, + "TimezoneConverter": { + "title": "Μετατροπέας ζωνών ώρας", + "subtitle": "Μετατρέψτε μια στιγμή σε όλο τον κόσμο για τον προγραμματισμό συναντήσεων.", + "dateTimeLabel": "Ημερομηνία και ώρα", + "now": "Τώρα", + "sourceZoneLabel": "Ζώνη ώρας προέλευσης", + "selectZone": "Επιλογή ζώνης", + "searchZone": "Αναζήτηση ζωνών ώρας...", + "noZone": "Δεν βρέθηκε ζώνη ώρας.", + "scrubLabel": "Μετατόπιση ώρας", + "reset": "Επαναφορά", + "targetsLabel": "Ζώνες προορισμού", + "addZone": "Προσθήκη ζώνης", + "remove": "Αφαίρεση", + "localBadge": "Τοπική", + "invalidDate": "Εισαγάγετε έγκυρη ημερομηνία και ώρα.", + "bands": { + "work": "Ώρες εργασίας (09:00–18:00)", + "edge": "Οριακές ώρες (νωρίς / βράδυ)", + "night": "Νύχτα" + } + }, + "TokenCounter": { + "title": "Μετρητής token LLM", + "subtitle": "Μετρήστε token, οπτικοποιήστε τμήματα και εκτιμήστε το κόστος API.", + "inputLabel": "Κείμενο", + "sample": "Δείγμα", + "clear": "Καθαρισμός", + "placeholder": "Επικολλήστε κείμενο, κώδικα ή JSON για μέτρηση token...", + "encoding": "Κωδικοποίηση", + "loading": "Φόρτωση tokenizer...", + "encodings": { + "o200k": "o200k_base (GPT-4o / σειρά o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Token", + "characters": "Χαρακτήρες", + "words": "Λέξεις", + "ratio": "Token/λέξη", + "ratioSub": "περίπου" + }, + "claudeRow": "Claude (κατά προσέγγιση)", + "estimate": "εκτίμηση", + "viz": { + "title": "Οπτικοποίηση token", + "toggle": "Εμφάνιση", + "hint": "Εναλλάξτε για να χρωματίσετε κάθε token ως τμήμα.", + "empty": "Εισαγάγετε κείμενο για την οπτικοποίηση token.", + "truncated": "Εμφάνιση των πρώτων {limit} από {total} token." + }, + "cost": { + "title": "Υπολογιστής κόστους", + "disclaimer": "επεξεργάσιμες προεπιλογές — επαληθεύστε τις τρέχουσες τιμές", + "model": "Μοντέλο", + "expectedOutput": "Αναμενόμενα token εξόδου", + "customInput": "Είσοδος $/MTok", + "customOutput": "Έξοδος $/MTok", + "inputCost": "Κόστος εισόδου", + "outputCost": "Κόστος εξόδου", + "total": "Σύνολο", + "per1000": "Ανά 1000 κλήσεις" + } + }, + "WebhookTester": { + "title": "Δοκιμαστής webhook", + "subtitle": "Δημιουργήστε ένα προσωρινό URL και επιθεωρήστε ζωντανά τα εισερχόμενα αιτήματα webhook.", + "intro": { + "title": "Δημιουργήστε ένα URL webhook για να ξεκινήσετε την καταγραφή αιτημάτων", + "hint": "Θα λάβετε ένα μη προβλέψιμο URL που καταγράφει κάθε εισερχόμενο αίτημα. Λήγει μετά από 24 ώρες." + }, + "createBtn": "Δημιουργία URL webhook", + "creating": "Δημιουργία…", + "urlLabel": "Το URL webhook σας", + "expiresIn": "Λήγει σε {countdown}", + "copyUrlBtn": "Αντιγραφή URL", + "copied": "Αντιγράφηκε", + "clearBtn": "Καθαρισμός", + "newBinBtn": "Νέο URL", + "requestsTitle": "Αιτήματα", + "selectPrompt": "Επιλέξτε ένα αίτημα για να επιθεωρήσετε τις λεπτομέρειές του.", + "expiredNotice": "Αυτό το URL webhook έχει λήξει. Δημιουργήστε ένα νέο για να συνεχίσετε τις δοκιμές.", + "base64Badge": "base64", + "truncatedBadge": "περικομμένο", + "empty": { + "title": "Αναμονή αιτημάτων…", + "hint": "Στείλτε ένα αίτημα στο URL σας και θα εμφανιστεί εδώ αμέσως. Δοκιμάστε αυτό:" + }, + "detail": { + "receivedAt": "Ελήφθη", + "source": "IP προέλευσης", + "contentType": "Content-Type", + "query": "Παράμετροι ερωτήματος", + "headers": "Κεφαλίδες", + "body": "Σώμα", + "copyBody": "Αντιγραφή σώματος", + "emptyBody": "Χωρίς σώμα" + }, + "errors": { + "createFailed": "Η δημιουργία του URL webhook απέτυχε. Δοκιμάστε ξανά.", + "clearFailed": "Ο καθαρισμός των αιτημάτων απέτυχε. Δοκιμάστε ξανά." + } + }, + "WebsocketTester": { + "title": "Δοκιμαστής WebSocket", + "subtitle": "Συνδεθείτε σε ένα άκρο WebSocket και στείλτε, λάβετε και επιθεωρήστε μηνύματα ζωντανά", + "urlLabel": "URL άκρου", + "protocolsLabel": "Υποπρωτόκολλα", + "protocolsPlaceholder": "προαιρετικό, διαχωρισμένα με κόμμα", + "connect": "Σύνδεση", + "disconnect": "Αποσύνδεση", + "status": { + "disconnected": "Αποσυνδεδεμένο", + "connecting": "Σύνδεση", + "connected": "Συνδεδεμένο", + "error": "Σφάλμα" + }, + "messages": { + "connecting": "Σύνδεση σε {url}…", + "connected": "Η σύνδεση δημιουργήθηκε", + "connectedWithProtocol": "Η σύνδεση δημιουργήθηκε (πρωτόκολλο: {protocol})", + "invalidUrl": "Μη έγκυρο URL — πρέπει να ξεκινά με ws:// ή wss://", + "connectFailed": "Το άνοιγμα της σύνδεσης απέτυχε", + "socketError": "Παρουσιάστηκε σφάλμα σύνδεσης", + "notConnected": "Μη συνδεδεμένο — συνδεθείτε πρώτα", + "binaryMessage": "Ελήφθη δυαδικό μήνυμα" + }, + "log": { + "title": "Αρχείο μηνυμάτων", + "empty": "Δεν υπάρχουν ακόμη μηνύματα. Συνδεθείτε και ξεκινήστε την αποστολή.", + "copyAll": "Αντιγραφή όλων", + "download": "Λήψη ως .txt", + "clear": "Καθαρισμός αρχείου", + "prettyPrint": "Μορφοποίηση JSON", + "showRaw": "Εμφάνιση ακατέργαστου κειμένου" + }, + "composer": { + "placeholder": "Πληκτρολογήστε ένα μήνυμα για αποστολή…", + "send": "Αποστολή", + "formatJson": "Μορφοποίηση ως JSON", + "invalidJson": "Το μήνυμα δεν είναι έγκυρο JSON", + "hint": "Cmd/Ctrl+Enter για αποστολή" + }, + "ping": { + "label": "Αυτόματο ping", + "seconds": "δευτ", + "intervalTitle": "Διάστημα ping σε δευτερόλεπτα", + "payloadPlaceholder": "φορτίο ping" + } + }, + "WhoisLookup": { + "title": "Αναζήτηση Whois", + "subtitle": "Αναζητήστε στοιχεία καταχώρισης για οποιονδήποτε τομέα ή διεύθυνση IP μέσω RDAP", + "placeholder": "example.com ή 8.8.8.8", + "lookupBtn": "Αναζήτηση", + "looking": "Αναζήτηση…", + "refreshBtn": "Ανανέωση", + "tryLabel": "Δοκιμάστε:", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "notAvailable": "Μη διαθέσιμο", + "summary": { + "registrar": "Καταχωρητής", + "network": "Δίκτυο" + }, + "dates": { + "created": "Δημιουργήθηκε", + "updated": "Ενημερώθηκε", + "expires": "Λήγει" + }, + "sections": { + "status": "Κατάσταση", + "nameservers": "Διακομιστές ονομάτων", + "contacts": "Επαφές", + "rawRdap": "Ακατέργαστο RDAP" + }, + "contacts": { + "roles": "Ρόλοι", + "name": "Όνομα", + "organization": "Οργανισμός" + }, + "errors": { + "invalidTarget": "Εισαγάγετε ένα έγκυρο όνομα τομέα ή διεύθυνση IP", + "lookupFailed": "Η αναζήτηση απέτυχε (κατάσταση {status})" + } + } +} diff --git a/apps/desktop-ui/messages/en.json b/apps/desktop-ui/messages/en.json new file mode 100644 index 00000000..e5f5100f --- /dev/null +++ b/apps/desktop-ui/messages/en.json @@ -0,0 +1,4606 @@ +{ + "Activate": { + "title": "Activate MyDevTools", + "subtitle": "Sign in once with your browser to activate the app. After that, everything works fully offline.", + "signInWithBrowser": "Sign in with your browser", + "waitingForBrowser": "Waiting for the browser…", + "finishing": "Finishing activation…", + "authSuccess": "Authentication successful", + "subscriptionCreated": "Subscription created", + "plan": "Plan", + "earlyAdopter": "Pro · Early adopter", + "lifetimeNote": "Lifetime plan — never expires.", + "grantedOn": "Granted on", + "offlineNote": "Yours forever — the app works fully offline from here on.", + "start": "Start using MyDevTools", + "privacyNote": "Your data stays on this device. The account is only used to activate the app and manage your plan." + }, + "PlanCard": { + "title": "Plan", + "earlyAdopter": "Pro · Early adopter", + "lifetimeDescription": "Lifetime plan — never expires.", + "freeDescription": "Free plan.", + "grantedOn": "Granted on", + "manage": "Manage plan on mydevtools.tech" + }, + "Navigation": { + "dashboard": "Dashboard", + "settings": "Settings", + "toDo": "Tasks", + "notes": "Notes", + "passwordManager": "Password Manager", + "environmentManager": "Environment Manager", + "bookmarks": "Bookmarks", + "emailValidator": "Email Validator", + "jwtDecoder": "JWT Decoder", + "encryptionPlayground": "Encryption Playground", + "certificatePemDecoder": "Certificate / PEM Decoder", + "jsonFormatter": "JSON Formatter", + "jsonSchemaGenerator": "JSON Schema Generator", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL Formatter", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "imageToBase64": "Image to Base64", + "imageCompressor": "Image Compressor", + "urlEncoder": "URL Encoder", + "urlParser": "URL Parser", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Secret / API key", + "qrCodeGenerator": "QR Code Generator", + "dnsLookup": "DNS Lookup", + "ipSubnetCalculator": "IP / Subnet Calculator", + "hashGenerator": "Hash Generator", + "hmacGenerator": "HMAC Generator", + "totpGenerator": "TOTP / 2FA Code", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Contrast Checker", + "apiClient": "API Client", + "httpStatusCodes": "HTTP status codes", + "nosqlExplorer": "Database Explorer", + "cssGradientBuilder": "CSS Gradient Builder", + "gitignoreGenerator": ".gitignore Generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Code Snippets", + "markdownPreview": "Markdown Preview", + "numberBaseConverter": "Number Base Converter", + "formatConverter": "Format Converter", + "mockDataGenerator": "Mock Data Generator", + "svgOptimizer": "SVG Optimizer", + "unitConverter": "Unit Converter", + "dockerComposeGenerator": "Docker Compose Generator", + "breakRoom": "Break Room", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAML Formatter", + "sshKeyGenerator": "SSH / RSA Key Generator", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod Calculator", + "codeScreenshot": "Code Screenshot", + "cssGenerators": "CSS Generators", + "exifViewer": "EXIF Viewer", + "faviconGenerator": "Favicon Generator", + "jsonDiff": "JSON Diff", + "jsonpathPlayground": "JSONPath Playground", + "keycodeInspector": "Keycode Inspector", + "markdownTableGenerator": "Markdown Table", + "timezoneConverter": "Timezone Converter", + "tokenCounter": "Token Counter", + "webhookTester": "Webhook Tester", + "websocketTester": "WebSocket Tester", + "whoisLookup": "Whois Lookup" + }, + "Help": { + "title": "Help & documentation", + "subtitle": "How MyDevTools fits together, what each app does, and how security and encryption protect your data.", + "tabs": { + "overview": "Overview", + "apps": "Apps", + "security": "Security", + "tips": "Tips" + }, + "overview": { + "whatIs": { + "title": "What is MyDevTools?", + "description": "A signed-in developer workspace: a dashboard and a set of tools for everyday tasks.", + "p1": "After you sign in with your account, the app establishes a session with the MyDevTools API so tools can load and save your data. The sidebar lists every app; you can hide tools you do not use from", + "settingsLink": "Settings", + "p1After": ".", + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted on your device before they reach the server." + }, + "quickLinks": { + "title": "Quick links", + "dashboard": "Dashboard", + "settings": "Settings" + } + }, + "apps": { + "intro": "Open any tool from the sidebar or dashboard. Below is a short guide per app.", + "open": "Open", + "howItWorks": "How it works", + "generic": "Use this tool from the sidebar for its dedicated workflow." + }, + "security": { + "session": { + "title": "Sign-in and API session", + "description": "Identity and how the browser talks to your backend.", + "items": { + "authentication": "Authentication uses Firebase for sign-in. After login, the app exchanges your Firebase ID token for API cookies.", + "accessTokens": "Access tokens are JWTs signed on the server (HS256). They prove which user is calling the API.", + "refreshTokens": "Refresh tokens are stored in HttpOnly cookies; the server stores a SHA-256 hash of the refresh token, not the raw token." + } + }, + "encryption": { + "title": "Encryption you should know about", + "description": "Client-side crypto for vault and sensitive connection data.", + "p1": "For the password manager, environment manager, and NoSQL Explorer saved connections, the web app uses the browser Web Crypto API:", + "items": { + "kdf": "PBKDF2 with SHA-256, 100,000 iterations, and a random salt derives a 256-bit key from your master password.", + "aes": "AES-256-GCM encrypts payloads; a random 12-byte IV is used per encryption.", + "scope": "The derived key stays in the browser (non-extractable). The server receives ciphertext and IVs, not your master password or plaintext secrets." + }, + "warning": "If you forget the master password, encrypted data cannot be recovered. Store it safely." + }, + "logout": { + "title": "Logout and local data", + "body": "Logging out clears in-memory secrets, vault-related state, and master key material from IndexedDB where applicable, and ends the API session. Anything kept only in local storage on a device may remain until you clear it in the browser." + } + }, + "tips": { + "title": "Using MyDevTools safely", + "items": { + "masterPassword": "Use a strong, unique master password for vault features.", + "apiClient": "Treat the API Client like any HTTP client: avoid pasting production secrets into shared environments unless you understand who can see them.", + "nosql": "Database Explorer connects to databases you configure; use least-privilege users and network rules where possible.", + "json": "JSON Formatter is local to the browser—still avoid pasting highly sensitive data on shared machines.", + "settings": "Customize visible tools in Settings to keep the sidebar focused." + } + } + }, + "Settings": { + "title": "Settings", + "language": "Language", + "spanish": "Spanish", + "english": "English", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalan", + "chineseSimplified": "Chinese (Simplified)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "German", + "danish": "Danish", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "norwegian": "Norsk", + "dutch": "Nederlands", + "polish": "Polish", + "vietnamese": "Tiếng Việt", + "ukrainian": "Українська" + }, + "SettingsPage": { + "title": "Settings", + "subtitle": "Manage your account settings and preferences.", + "userProfile": { + "title": "User Profile", + "description": "Your personal information and account details.", + "avatarAlt": "User", + "anonymousUser": "Anonymous User", + "noEmailProvided": "No email provided", + "notLoggedIn": "Not logged in." + }, + "tools": { + "title": "Tools & Features", + "description": "Choose which tools appear in your navigation sidebar." + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + }, + "appearance": { + "title": "Appearance", + "description": "Customize how the application looks on your device.", + "themePreference": "Theme Preference", + "themes": { + "light": "Light", + "dark": "Dark", + "system": "System" + } + }, + "language": { + "title": "Language", + "description": "Set the language used across the platform.", + "selectLanguage": "Select Language", + "selectPlaceholder": "Select a language", + "helpText": "Changing your language will update menus and labels across the platform.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spanish)", + "ar": "عربى (Arabic)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Greek)", + "de": "Deutsch (German)", + "da": "Dansk (Danish)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italiano", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski (Polish)", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + } + }, + "Dashboard": { + "noResults": "No tools match your search", + "noResultsHint": "Try a different keyword or clear the filter.", + "popularTools": "Popular tools", + "filterAll": "All", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "loginCta": { + "description": "Sign in to track your recently used tools." + }, + "greeting": { + "morning": "Good morning", + "afternoon": "Good afternoon", + "evening": "Good evening" + }, + "commaName": ", {name}", + "welcomeBack": "Welcome back", + "welcomeBackNamed": "Welcome back, {name}", + "tagline": "What would you like to build today?", + "brandName": "MyDevTools", + "viewActivity": "Recent activity", + "signIn": "Sign in", + "stats": { + "tools": "Tools", + "favorites": "Favorites", + "pinned": "Pinned", + "recent": "Recently Used" + }, + "sections": { + "recentlyUsed": "Recently Used", + "favorites": "Favorites", + "whatsNew": "What's New", + "pinned": "Pinned" + }, + "groups": { + "apps": "Apps" + }, + "tabs": { + "apps": "Apps", + "analytics": "Analytics", + "ui": "Interface" + }, + "analytics": { + "title": "Your data at a glance", + "subtitle": "Counts synced with your account on the server.", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "loadError": "Could not load analytics.", + "retry": "Retry", + "loginHint": "Sign in to see usage analytics and interface preferences.", + "passwordEntries": "Password entries", + "bookmarks": "Bookmarks", + "bookmarkFolders": "Bookmark folders", + "tasksTotal": "Tasks", + "tasksCompleted": "Completed", + "tasksOngoing": "In progress", + "tasksNotStarted": "Not started", + "projects": "Projects", + "nosqlConnections": "DB connections", + "notes": "Notes", + "apiClientCollections": "API collections", + "apiClientEnvironments": "API environments", + "apiClientHistory": "API history entries", + "jsonFormatterDocuments": "JSON formatter documents", + "codeSnippets": "Code snippets", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Interface preferences", + "subtitle": "Theme, language, and which tools appear in the sidebar — from your account settings.", + "loadError": "Could not load preferences.", + "loginHint": "Sign in to see your saved interface preferences.", + "theme": "Theme", + "accent": "Accent color", + "locale": "Language", + "enabledTools": "Visible tools", + "favoriteTools": "Favorite tools", + "toolsWithUsage": "Tools with usage tracked" + }, + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + }, + "toolCard": { + "defaultDescription": "Explore this tool for better functionality.", + "launchTool": "Launch Tool" + }, + "tools": { + "toDo": { + "title": "Tasks", + "description": "Manage your to-do lists efficiently." + }, + "notes": { + "title": "Notes", + "description": "Capture ideas and organize your thoughts." + }, + "passwordManager": { + "title": "Password Manager", + "description": "Securely store and manage your passwords." + }, + "environmentManager": { + "title": "Environment Manager", + "description": "Store .env-style variables by project and environment with tags." + }, + "bookmarks": { + "title": "Bookmarks", + "description": "Manage and organize your bookmarks." + }, + "apiClient": { + "title": "API Client", + "description": "Test and debug HTTP requests." + }, + "httpStatusCodes": { + "title": "HTTP Status Codes", + "description": "Search HTTP status codes with descriptions (1xx–5xx)." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Explore MongoDB and SQL databases." + }, + "emailValidator": { + "title": "Email Validator", + "description": "Verify and validate email addresses." + }, + "jwtDecoder": { + "title": "JWT Decoder", + "description": "Decode JWT header, payload, and expiry." + }, + "certificatePemDecoder": { + "title": "Certificate / PEM Decoder", + "description": "Decode X.509 certificates and CSRs: subject, issuer, validity, SANs." + }, + "sshKeyGenerator": { + "title": "SSH / RSA Key Generator", + "description": "Generate Ed25519 or RSA key pairs. Download private and public keys in OpenSSH and PEM formats." + }, + "encryptionPlayground": { + "title": "Encryption Playground", + "description": "AES-GCM with a raw key or passphrase; JSON bundles, Web Crypto only in your browser." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "jsonSchemaGenerator": { + "title": "JSON Schema Generator", + "description": "Infer JSON Schema and typed models from sample JSON." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL Formatter", + "description": "Format queries with syntax highlighting and a simple query builder." + }, + "yamlFormatter": { + "title": "YAML Formatter", + "description": "Format, validate, and convert YAML to JSON in the browser." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "formatConverter": { + "title": "Format Converter", + "description": "Convert between JSON, YAML, TOML, and XML in any direction. All 12 combinations supported." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "imageToBase64": { + "title": "Image to Base64", + "description": "Convert images to Data URI or raw Base64 strings." + }, + "imageCompressor": { + "title": "Image Compressor", + "description": "Compress JPEG, PNG, and WebP locally with a quality slider." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL Parser", + "description": "Break a URL into protocol, host, path, query params, and hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Secret / API key", + "description": "Cryptographically random strings with configurable alphabet, length, and bulk export." + }, + "qrCodeGenerator": { + "title": "QR Code Generator", + "description": "Create PNG QR codes from text or URLs in your browser." + }, + "ipSubnetCalculator": { + "title": "IP / Subnet Calculator", + "description": "IPv4 and IPv6 CIDR: netmask, ranges, and address counts." + }, + "dnsLookup": { + "title": "DNS Lookup", + "description": "Query DNS records for any domain — A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR." + }, + "hashGenerator": { + "title": "Hash Generator", + "description": "MD5, SHA-1, and SHA-2 digests for text or files." + }, + "hmacGenerator": { + "title": "HMAC Generator", + "description": "Sign webhooks and test API integrations with HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "TOTP / 2FA code generator", + "description": "Paste a Base32 authenticator secret and see the six-digit RFC 6238 code refresh every 30 seconds—handy for testing sign-in and MFA." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Contrast Checker", + "description": "WCAG 2.1 contrast ratio for text and background (AA / AAA)." + }, + "cssGradientBuilder": { + "title": "CSS Gradient Builder", + "description": "Visual CSS gradient builder with angle control and color stops." + }, + "gitignoreGenerator": { + "title": ".gitignore Generator", + "description": "Generate merged .gitignore files instantly from 500+ tech stacks." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Convert CSV or Excel files to JSON and export back to CSV or XLSX." + }, + "snippetManager": { + "title": "Code Snippets", + "description": "Save snippets with syntax highlighting, auto language detection, and formatting." + }, + "markdownPreview": { + "title": "Markdown Preview", + "description": "Live Markdown renderer with HTML export and HTML → Markdown conversion." + }, + "numberBaseConverter": { + "title": "Number Base Converter", + "description": "Convert integers between bases 2–36 (binary, decimal, hex, and more)." + }, + "mockDataGenerator": { + "title": "Mock Data Generator", + "description": "Schema-based random JSON, CSV, SQL, and XML for development and API testing." + }, + "svgOptimizer": { + "title": "SVG Optimizer", + "description": "Paste SVG, strip junk with SVGO, and see UTF-8 size reduction." + }, + "unitConverter": { + "title": "Unit Converter", + "description": "Convert between 323 units across 43 scientific and engineering categories." + }, + "dockerComposeGenerator": { + "title": "Docker Compose Generator", + "description": "Pick databases, caches, queues, proxies, and observability images; export a docker-compose.yml for local stacks." + }, + "game2048": { + "title": "2048", + "description": "Slide and merge tiles to reach 2048. Classic number puzzle game." + }, + "sudoku": { + "title": "Sudoku", + "description": "Classic number puzzle. 3 difficulty levels, 150 stages each." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Browse keys, inspect values, and run commands on any Redis instance." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt Generator & Checker", + "description": "Hash passwords with bcrypt and verify existing hashes." + }, + "chmodCalculator": { + "title": "Chmod Calculator", + "description": "Convert Unix permissions between octal, symbolic, and checkboxes." + }, + "codeScreenshot": { + "title": "Code Screenshot", + "description": "Turn source code into beautiful shareable images." + }, + "cssGenerators": { + "title": "CSS Generators", + "description": "Box-shadow, border-radius, and fluid clamp() generators." + }, + "exifViewer": { + "title": "EXIF Viewer & Remover", + "description": "Inspect and strip photo EXIF metadata locally." + }, + "faviconGenerator": { + "title": "Favicon Generator", + "description": "Generate favicons, app icons and manifest snippets." + }, + "jsonDiff": { + "title": "JSON Diff", + "description": "Semantic, structure-aware diff of two JSON documents." + }, + "jsonpathPlayground": { + "title": "JSONPath Playground", + "description": "Query JSON with JSONPath or JMESPath and see live results." + }, + "keycodeInspector": { + "title": "Keycode Inspector", + "description": "Press a key to see its JavaScript event codes and modifiers." + }, + "markdownTableGenerator": { + "title": "Markdown Table Generator", + "description": "Build Markdown and HTML tables in a spreadsheet-like grid editor." + }, + "timezoneConverter": { + "title": "Timezone Converter", + "description": "Convert a time across many zones with day-shift and working-hours cues." + }, + "tokenCounter": { + "title": "LLM Token Counter", + "description": "Count LLM tokens, visualize chunks, and estimate API cost." + }, + "webhookTester": { + "title": "Webhook Tester", + "description": "Capture and inspect incoming webhook requests in real time." + }, + "websocketTester": { + "title": "WebSocket Tester", + "description": "Connect to WebSocket endpoints and exchange messages in real time." + }, + "whoisLookup": { + "title": "Whois Lookup", + "description": "Look up domain and IP registration details via RDAP." + } + } + }, + "Tasks": { + "page": { + "loading": "Loading...", + "myTasksTitle": "My Tasks", + "statsLine": "{total} tasks • {percent}% complete", + "statsLineDesktop": "{total} total • {percent}% complete" + }, + "filters": { + "all": "All", + "total": "Total", + "allProjects": "All Projects", + "export": "Export", + "kanban": "Kanban", + "list": "List", + "searchPlaceholder": "Search tasks...", + "searchAria": "Search tasks", + "clearSearchAria": "Clear search", + "foundTasks": "Found {count} {count, plural, one {task} other {tasks}}", + "filterByAllAria": "Filter by All", + "filterByStatusAria": "Filter by {status}" + }, + "status": { + "not-started": { + "label": "Not Started", + "description": "Tasks that haven't been started yet" + }, + "ongoing": { + "label": "Ongoing", + "description": "Tasks currently in progress" + }, + "completed": { + "label": "Completed", + "description": "Tasks that are finished" + } + }, + "priorities": { + "high": "High", + "medium": "Medium", + "low": "Low" + }, + "form": { + "addNewTaskLabel": "Add new task", + "multilinePlaceholder": "Add task details... (Shift+Enter for new line)", + "singlelinePlaceholder": "What needs to be done? Press Enter to add", + "taskInputAria": "Task input", + "projectPlaceholder": "Project", + "selectProject": "Select Project", + "errorSelectProject": "Please select a project or create a new one to add a task.", + "hintMultiline": "Shift+Enter for new line, Enter to add", + "hintSingleline": "Enter to add", + "shortcutsAria": "Keyboard shortcuts", + "shortcutsLabel": "Shortcuts", + "shortcutAddTask": "Add task", + "shortcutNewLine": "New line", + "shortcutClear": "Clear", + "addButton": "Add" + }, + "list": { + "ariaLabel": "Task list", + "loading": "Loading your tasks...", + "emptyTitle": "No tasks found", + "emptyPrimary": "Get started by adding your first task above! Press Enter to quickly add tasks.", + "emptySecondary": "Try adjusting your filters or search query." + }, + "mobileDrawer": { + "addTaskSrOnly": "Add Task", + "addNewTaskTitle": "Add New Task", + "addNewTaskDescription": "Create a new task to track your progress.", + "cancel": "Cancel" + }, + "kanban": { + "ariaLabel": "Kanban board", + "loading": "Loading your tasks...", + "dropHere": "Drop task here", + "noTasksYet": "No tasks yet", + "emptyColumnAria": "Empty column", + "columnAria": "{title} column with {count} tasks", + "countInColumnAria": "{count} tasks in {title}" + }, + "taskItem": { + "confirmDelete": "Are you sure you want to delete this task?", + "statusPlaceholder": "Status", + "changeStatusAria": "Change task status", + "taskOptionsAria": "Task options", + "editTask": "Edit Task", + "copyTask": "Copy Task", + "copied": "Copied!", + "deleteTask": "Delete Task", + "statusHeading": "Status", + "editTaskAria": "Edit task" + }, + "editDialog": { + "title": "Edit Task", + "description": "Update task details, add subtasks, set priorities and more.", + "taskTitleLabel": "Task Title *", + "taskTitlePlaceholder": "What needs to be done?", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Add more details about this task...", + "statusLabel": "Status", + "priorityLabel": "Priority", + "dueDateLabel": "Due Date", + "pickDate": "Pick a date", + "clearDate": "Clear Date", + "projectLabel": "Project", + "selectProjectPlaceholder": "Select a project", + "noProject": "No Project", + "timeEstimateLabel": "Time Estimate (minutes)", + "timeEstimatePlaceholder": "How long will this take?", + "tagsLabel": "Tags", + "customTagNamePlaceholder": "Custom tag name", + "subtasksLabel": "Subtasks", + "addSubtaskPlaceholder": "Add a subtask...", + "cancel": "Cancel", + "saving": "Saving...", + "saveChanges": "Save Changes" + }, + "export": { + "title": "Export Tasks", + "description": "Export your tasks to JSON, CSV, or Excel formats.", + "projectLabel": "Project", + "statusLabel": "Status", + "tagLabel": "Tag", + "allProjects": "All Projects", + "allStatuses": "All Statuses", + "allTags": "All Tags", + "exportAsJsonTitle": "Export as JSON", + "exportAsJsonDescription": "Export task data including subtasks, tags, and metadata.", + "exportJson": "Export JSON", + "exportAsCsvExcelTitle": "Export as CSV / Excel", + "exportAsCsvExcelDescription": "Export tasks for spreadsheet applications.", + "exportCsv": "Export CSV", + "exportExcel": "Export Excel", + "close": "Close", + "toastNoTasks": "No tasks to export with current filters", + "toastExportedJson": "Exported {count} tasks to JSON!", + "toastExportedCsv": "Exported {count} tasks to CSV!", + "toastExportedExcel": "Exported {count} tasks to Excel!", + "toastFailedJson": "Failed to export to JSON", + "toastFailedCsv": "Failed to export to CSV", + "toastFailedExcel": "Failed to export to Excel", + "unknownProject": "Unknown Project" + }, + "ack": { + "taskAddedTitle": "Task added successfully", + "taskAddedFailedTitle": "Failed to add task", + "taskUpdatedTitle": "Task updated successfully", + "taskUpdatedFailedTitle": "Failed to update task", + "taskStatusUpdateFailedTitle": "Failed to update task status", + "taskMovedTitle": "Task moved to {status}", + "tryAgain": "Please try again.", + "taskDeletedTitle": "Task \"{text}\" deleted", + "undo": "Undo", + "taskRestoredTitle": "Task restored", + "taskRestoredSuccessTitle": "Task restored successfully", + "taskRestoreFailedTitle": "Failed to restore task. Please try again.", + "taskDeleteFailedTitle": "Failed to delete task. Please try again.", + "tasksImportedTitle": "Successfully imported {count} {count, plural, one {task} other {tasks}}!", + "tasksImportFailedTitle": "Failed to import tasks. Please try again.", + "unknownProject": "Unknown Project", + "projectCreatedTitle": "Project created successfully", + "projectCreateFailedTitle": "Failed to create project", + "projectUpdatedTitle": "Project updated successfully", + "projectUpdateFailedTitle": "Failed to update project", + "projectDeletedTitle": "Project deleted successfully", + "projectDeleteFailedTitle": "Failed to delete project" + } + }, + "Notes": { + "page": { + "loading": "Loading..." + }, + "layout": { + "mobileNavTitle": "Notes Navigation", + "openMenuAria": "Open notes menu" + }, + "sidebar": { + "title": "Notes", + "createNoteAria": "Create note", + "searchPlaceholder": "Search notes...", + "loading": "Loading...", + "noNotesFound": "No notes found.", + "noNotesYet": "No notes yet. Click + to create one.", + "inParent": "in {parentTitle}", + "untitled": "Untitled", + "addSubPage": "Add sub-page", + "delete": "Delete", + "deleteConfirmTitle": "Delete note?", + "deleteConfirmDescription": "Are you sure you want to delete \"{title}\"? This action cannot be undone.", + "cancel": "Cancel", + "confirmDelete": "Delete", + "noteOptionsAria": "Note options" + }, + "editor": { + "emptyState": "Select a note or create a new one to get started.", + "titlePlaceholder": "Untitled", + "saving": "Saving…", + "ready": "Ready", + "saved": "Saved {time}", + "stats": "{words} words · {read} min read", + "template": "Template", + "applyTemplate": "Apply template", + "exportMdLabel": ".md", + "exportMd": "Export as Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Export as HTML", + "focusMode": "Focus mode", + "exitFocusMode": "Exit focus mode", + "save": "Save", + "addTag": "Add tag…", + "removeTag": "Remove tag {tag}", + "loading": "Loading…", + "searchHint": "to search", + "focusHint": "focus mode", + "notePath": "Note path" + }, + "context": { + "authRequiredError": "User not authenticated", + "defaultTitle": "Untitled", + "cloudSyncImageError": "Turn on Cloud Sync to store note images. Text notes stay local." + } + }, + "PasswordManager": { + "page": { + "loading": "Loading...", + "loadFailed": "Failed to load passwords", + "title": "Password Manager" + }, + "vault": { + "toastDecryptFailed": "Failed to decrypt passwords", + "toastLoadFailed": "Failed to load passwords", + "toastUnlockedAuto": "Vault unlocked automatically", + "toastAutoUnlockFailed": "Auto-unlock failed. Please unlock manually.", + "toastConnectFailed": "Failed to connect to server", + "toastAutoUnlockWarning": "Vault unlocked, but auto-unlock may not work on reload", + "errorIncorrectPassword": "Incorrect master password", + "errorUnlockFailed": "Failed to unlock vault", + "errorPasswordsMismatch": "Passwords do not match", + "errorPasswordTooShort": "Password must be at least 8 characters", + "errorSetupFailed": "Failed to set up vault", + "errorConnectFailed": "Failed to connect to server.", + "mobileTitleCreate": "Create your vault", + "mobileTitleWelcome": "Welcome back", + "mobileSubtitleCreate": "Set a master password to protect your credentials", + "mobileSubtitleUnlock": "Enter your master password to unlock", + "placeholderMasterPassword": "Master password", + "placeholderConfirmPassword": "Confirm password", + "labelMasterPassword": "Master password", + "labelConfirmPassword": "Confirm password", + "placeholderEnterMaster": "Enter master password", + "placeholderConfirmMaster": "Confirm master password", + "buttonCreateVault": "Create vault", + "buttonUnlock": "Unlock", + "buttonUnlockVault": "Unlock vault", + "footerCreate": "Your password is never stored and cannot be recovered", + "footerUnlock": "Secured with end-to-end encryption", + "desktopTitleSetup": "Set up password vault", + "desktopTitleUnlock": "Unlock vault", + "desktopDescriptionSetup": "Create a master password to secure your credentials. This password is never stored and cannot be recovered.", + "desktopDescriptionUnlock": "Enter your master password to access your secured credentials." + }, + "list": { + "loading": "Loading your passwords...", + "emptyTitle": "Vault is empty", + "emptyHintMobile": "Tap the + button to add your first password", + "emptyHintDesktop": "You have not stored any passwords yet. Click the \"Add password\" button above to get started.", + "addPassword": "Add password", + "searchPlaceholderMobile": "Search your vault...", + "searchPlaceholderDesktop": "Search vault...", + "importExport": "Import / export", + "lockVault": "Lock vault", + "allPasswordsTitle": "All passwords", + "passwordCount": "{count, plural, one {# password} other {# passwords}}", + "vaultHealthDrawerTitle": "Vault health", + "noResults": "No passwords found matching \"{query}\"", + "tableService": "Service", + "tableUsername": "Username", + "tablePassword": "Password", + "tableUrl": "URL", + "tableActions": "Actions", + "gridViewAria": "Grid view", + "listViewAria": "List view", + "passwordStrengthTitle": "Password strength", + "deleteDialogTitle": "Delete password", + "deleteDialogDescription": "Are you sure you want to delete this password? This action cannot be undone.", + "cancel": "Cancel", + "delete": "Delete", + "edit": "Edit" + }, + "toasts": { + "copiedPassword": "Password copied to clipboard", + "copiedUsername": "Username copied to clipboard", + "passwordDeleted": "Password deleted", + "deleteFailed": "Failed to delete password", + "vaultLocked": "Vault locked", + "importedCount": "Successfully imported {count, plural, one {# password} other {# passwords}}" + }, + "form": { + "addTitle": "Add new password", + "editTitle": "Edit password", + "serviceName": "Service name", + "usernameEmail": "Username / email", + "password": "Password", + "urlOptional": "Website URL (optional)", + "notesOptional": "Notes (optional)", + "tags": "Tags", + "placeholderService": "e.g. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Enter or generate password", + "placeholderUrl": "https://...", + "placeholderNotes": "Additional details...", + "placeholderTags": "Add tags (press Enter or comma)", + "strength": "Strength", + "strengthWeak": "Weak", + "strengthMedium": "Medium", + "strengthStrong": "Strong", + "advancedGenerator": "Advanced generator", + "cancel": "Cancel", + "saving": "Saving...", + "savePassword": "Save password", + "updating": "Updating...", + "updatePassword": "Update password", + "addPasswordSrOnly": "Add password", + "triggerAdd": "Add password", + "toastSaved": "Password saved successfully", + "toastSaveFailed": "Failed to save password", + "toastUpdated": "Password updated successfully", + "toastUpdateFailed": "Failed to update password" + }, + "security": { + "vaultHealth": "Vault health", + "statusLabel": "Status", + "totalPasswords": "Total passwords", + "activeEntries": "Active entries in your vault", + "protected": "Protected", + "weakPasswords": "Weak passwords", + "needingImprovement": "Passwords needing improvement", + "allGood": "All good", + "reusedPasswords": "Reused passwords", + "usedMultipleTimes": "Passwords used multiple times", + "unique": "Unique", + "total": "Total", + "weak": "Weak", + "reused": "Reused", + "totalCountLabel": "{count, plural, one {# password} other {# passwords}}", + "weakCountLabel": "{count} weak", + "allSecure": "All secure", + "scoreExcellent": "Excellent", + "scoreGood": "Good", + "scoreFair": "Fair", + "scoreCritical": "Critical" + }, + "importExport": { + "title": "Import / export vault", + "description": "Manage your password data. You can export your vault to JSON or import from a JSON file.", + "tabExport": "Export", + "tabImport": "Import", + "warningTitle": "Warning", + "warningBody": "Exported files contain your passwords in plain text. Keep this file safe and delete it after use.", + "exportBlurb": "Download your entire vault as a JSON file. This includes all services, usernames, passwords, and notes.", + "downloadJson": "Download JSON export", + "pasteLabel": "Paste JSON data", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Paste the content of a previously exported JSON file here.", + "importing": "Importing...", + "importPasswords": "Import passwords", + "trigger": "Import / export", + "toastExported": "Vault exported successfully", + "toastExportFailed": "Failed to export vault", + "toastImportFailed": "Failed to import: invalid JSON format" + }, + "card": { + "copyUsername": "Copy username", + "openUrl": "Open URL", + "strength": "Strength" + }, + "swipeable": { + "copy": "Copy", + "delete": "Delete", + "copyPassword": "Copy password", + "copyUsername": "Copy username", + "edit": "Edit", + "deleteAction": "Delete" + }, + "floatingMenu": { + "lockVault": "Lock vault", + "importExport": "Import / export", + "addPassword": "Add password" + }, + "generator": { + "tabPassword": "Password", + "tabPassphrase": "Passphrase", + "strength": "Strength", + "lengthLabel": "Length: {length} characters", + "lengthBadge": "{length} chars", + "characterSets": "Character sets", + "uppercase": "Uppercase", + "lowercase": "Lowercase", + "numbers": "Numbers", + "symbols": "Symbols", + "wordCountLabel": "Word count: {count}", + "wordCountBadge": "{count, plural, one {# word} other {# words}}", + "separator": "Separator", + "capitalization": "Capitalization", + "includeNumber": "Include number" + } + }, + "EnvironmentManager": { + "subtitle": "Encrypted environment variables by project and environment.", + "page": { + "loading": "Loading...", + "title": "Environment Manager", + "loadFailed": "Failed to load environment sets" + }, + "list": { + "title": "Environments", + "loading": "Loading your sets...", + "emptyTitle": "No environment sets yet", + "emptyHint": "Create a set for each project and environment (e.g. production API keys). Everything is encrypted in your browser before upload.", + "searchPlaceholder": "Search project, environment, tags, notes...", + "tagFilterPlaceholder": "Filter by tag", + "variableCount": "{count, plural, one {# variable} other {# variables}}", + "updatedAgo": "Updated {time}", + "copyDotEnv": "Copy .env", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "edit": "Edit", + "delete": "Delete", + "cancel": "Cancel", + "confirmDelete": "Delete", + "deleteDialogTitle": "Delete environment set?", + "deleteDialogDescription": "This removes the encrypted blob from your account. You cannot undo this.", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env copied to clipboard", + "deleted": "Environment set deleted", + "deleteFailed": "Failed to delete" + }, + "form": { + "addTitle": "New environment set", + "editTitle": "Edit environment set", + "triggerAdd": "Add environment set", + "addSrOnly": "Add environment set", + "project": "Project", + "environment": "Environment", + "placeholderProject": "e.g. billing-api", + "placeholderEnvironment": "e.g. production", + "pasteEnvLabel": "Paste .env", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Merge pasted lines", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "variables": "Variables", + "addRow": "Add row", + "keyPlaceholder": "KEY", + "valuePlaceholder": "value", + "notesOptional": "Notes (optional)", + "placeholderNotes": "Rotation policy, owner, links...", + "tags": "Tags", + "placeholderTags": "Add tags (Enter or comma)", + "cancel": "Cancel", + "save": "Save", + "saving": "Saving...", + "update": "Update", + "updating": "Updating...", + "toastSaved": "Environment set saved", + "toastSaveFailed": "Failed to save", + "toastUpdated": "Environment set updated", + "toastUpdateFailed": "Failed to update", + "toastPasteEmpty": "No KEY=value lines found", + "toastPasteApplied": "Merged {count} variables from paste", + "toastProjectEnvRequired": "Project and environment are required", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Loading..." + }, + "manager": { + "foldersHeading": "Folders", + "tagsHeading": "Tags", + "tagsMore": "+{count} more", + "tagsShowLess": "Show less", + "searchPlaceholder": "Search your bookmarks...", + "import": "Import", + "export": "Export", + "exportAsHtml": "Export as HTML", + "exportAsJson": "Export as JSON", + "addBookmark": "Add Bookmark", + "matchingQuery": "matching \"{query}\"", + "bookmarkCount": "{count, plural, one {# bookmark} other {# bookmarks}}", + "allBookmarks": "All Bookmarks", + "uncategorized": "Uncategorized", + "unknownFolder": "Unknown" + }, + "card": { + "urlCopied": "URL copied to clipboard", + "bookmarkDeleted": "Bookmark deleted", + "edit": "Edit", + "delete": "Delete", + "copyUrl": "Copy URL", + "deleteConfirmTitle": "Delete bookmark?", + "deleteConfirmDescription": "This will permanently delete \"{title}\".", + "cancel": "Cancel" + }, + "grid": { + "emptyTitle": "No bookmarks yet", + "emptyDescription": "Add your first bookmark or import from your browser to get started." + }, + "folderTree": { + "allBookmarks": "All Bookmarks", + "addSubfolder": "Add Subfolder", + "rename": "Rename", + "deleteFolderTitle": "Delete folder \"{name}\"?", + "deleteFolderDescription": "This will delete the folder and all its subfolders. Bookmarks in these folders will be moved to Uncategorized." + }, + "import": { + "title": "Import Bookmarks", + "description": "Import bookmarks from your browser's exported file.", + "parseError": "Failed to parse bookmark file. Please make sure it's a valid bookmark export.", + "successToast": "Imported {bookmarks} bookmarks and {folders} folders", + "errorToast": "Failed to import bookmarks", + "chromeInstruction": "Menu → Bookmarks → Bookmark Manager → ⋮ → Export Bookmarks", + "firefoxInstruction": "Menu → Bookmarks → Manage Bookmarks → Import and Backup → Export", + "safariInstruction": "File → Export Bookmarks...", + "edgeInstruction": "Menu → Favorites → ⋯ → Export Favorites", + "dropZoneTitle": "Drop your bookmark file here", + "dropZoneHint": "or click to browse (HTML or JSON)", + "selectedFileHint": "Click to select a different file", + "errorTitle": "Error", + "previewTitle": "Import Preview", + "previewBookmarks": "{count, plural, one {# bookmark} other {# bookmarks}}", + "previewFolders": "{count, plural, one {# folder} other {# folders}}", + "importModeLabel": "Import Mode", + "modeMerge": "Merge with existing bookmarks", + "modeReplace": "Replace all existing bookmarks", + "cancel": "Cancel", + "importButton": "Import", + "importButtonWithCount": "Import {count, plural, one {# bookmark} other {# bookmarks}}" + }, + "addBookmark": { + "titleAdd": "Add Bookmark", + "titleEdit": "Edit Bookmark", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Please enter a valid URL", + "titleLabel": "Title", + "titlePlaceholder": "Bookmark title", + "titleRequired": "Please enter a title", + "descriptionLabel": "Description (optional)", + "descriptionPlaceholder": "Add a note about this bookmark", + "folderLabel": "Folder", + "folderPlaceholder": "Select folder", + "noFolder": "No folder", + "tagsLabel": "Tags", + "tagsPlaceholder": "Add tags (press Enter)", + "toastUpdated": "Bookmark updated", + "toastAdded": "Bookmark added", + "cancel": "Cancel", + "submitAdd": "Add Bookmark", + "submitUpdate": "Update Bookmark" + }, + "addFolder": { + "title": "Create Folder", + "nameLabel": "Folder Name", + "namePlaceholder": "My Folder", + "nameRequired": "Please enter a folder name", + "parentLabel": "Parent Folder", + "parentPlaceholder": "Select parent folder", + "rootParent": "Root (No parent)", + "toastCreated": "Folder created", + "cancel": "Cancel", + "submit": "Create Folder" + }, + "editFolder": { + "title": "Rename Folder", + "nameLabel": "Folder Name", + "namePlaceholder": "Folder name", + "nameRequired": "Please enter a folder name", + "toastRenamed": "Folder renamed", + "cancel": "Cancel", + "save": "Save" + } + }, + "EmailValidator": { + "header": { + "title": "Email Validator", + "description": "Verify and validate email addresses for syntax, domain, and deliverability." + }, + "tabs": { + "single": "Single Verification", + "bulk": "Bulk Upload" + }, + "single": { + "cardTitle": "Single Email Verification", + "cardDescription": "Enter an email address to check its deliverability and reputation.", + "placeholder": "Enter email address...", + "clearAria": "Clear email", + "validate": "Validate", + "validating": "Validating...", + "formatError": "Please enter a valid email address" + }, + "toasts": { + "errorTitle": "Error", + "enterEmail": "Please enter an email address", + "invalidFormatTitle": "Invalid Email Format", + "invalidFormatDescription": "Please enter a valid email address", + "validateFailedDescription": "An error occurred while validating the email", + "noEmailsTitle": "No Emails Found", + "noEmailsDescription": "Please ensure your Excel has an 'email' column.", + "fileTooLargeTitle": "File Too Large", + "fileTooLargeDescription": "Found {total} emails. Only the first 4000 will be validated.", + "successTitle": "Success", + "successValidated": "Validated {count} emails successfully.", + "batchFailedTitle": "Batch Validation Failed", + "batchFailedDescription": "An error occurred while processing bulk validation.", + "noExportTitle": "No Data to Export", + "noExportValidOnly": "No valid emails found to export.", + "noExportAll": "No results to export.", + "exportSuccessTitle": "Export Successful", + "exportSuccessExcelAll": "Exported {count} emails to Excel.", + "exportSuccessExcelValid": "Exported {count} valid emails to Excel.", + "exportSuccessCsvAll": "Exported {count} emails to CSV.", + "exportSuccessCsvValid": "Exported {count} valid emails to CSV." + }, + "disposableAlert": { + "title": "Disposable Email Detected", + "description": "This email address belongs to a disposable email provider. It may be temporary or unsafe." + }, + "score": { + "reliabilityTitle": "Reliability Score", + "scoreLabel": "Score" + }, + "details": { + "sectionTitle": "Validation Details" + }, + "validation": { + "syntaxLabel": "Syntax Valid", + "syntaxTooltip": "Checks if the email address follows the correct format (e.g., user@domain.com)", + "domainLabel": "Domain Exists", + "domainTooltip": "Verifies that the domain name in the email address actually exists", + "mxLabel": "MX Records Found", + "mxTooltip": "Checks if the domain has mail exchange (MX) records configured to receive emails", + "mailboxLabel": "Mailbox Exists", + "mailboxTooltip": "Verifies that the specific mailbox/account exists on the mail server", + "disposableLabel": "Disposable Email", + "disposableTooltip": "Indicates if this email belongs to a temporary/disposable email service (may be risky)", + "roleBasedLabel": "Role Based Email", + "roleBasedTooltip": "Identifies if this is a role-based email (e.g., support@, info@) rather than a personal address" + }, + "copy": { + "emailCopied": "Email copied!", + "copyTooltip": "Copy email" + }, + "bulk": { + "dropHere": "Drop your file here", + "dragHere": "Drag and drop your file here", + "uploadHint": "Upload a .xlsx, .xls, or .csv file containing an 'email' column to validate up to 4000 emails.", + "filePrefix": "File:", + "selectFile": "Select File", + "downloadTemplate": "Download Template" + }, + "bulkLoading": { + "title": "Validating Emails...", + "batchProgress": "Processing batch {current} of {total}..." + }, + "stats": { + "totalProcessed": "Total Processed", + "valid": "Valid", + "risky": "Risky", + "invalid": "Invalid" + }, + "results": { + "detailedTitle": "Detailed Results", + "searchPlaceholder": "Search emails...", + "newVerification": "New Verification", + "exportAll": "Export All", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Export Valid ({count})" + }, + "showingResults": "Showing {shown} of {total} results", + "table": { + "email": "Email", + "status": "Status", + "score": "Score", + "checks": "Checks" + }, + "noSearchResults": "No results found matching \"{query}\"", + "checksTooltip": { + "syntax": "Syntax", + "domain": "Domain", + "mx": "MX", + "mailbox": "Mailbox" + }, + "exportColumns": { + "email": "Email", + "status": "Status", + "score": "Score", + "syntax": "Syntax", + "domain": "Domain", + "mx": "MX", + "mailbox": "Mailbox", + "disposable": "Disposable", + "roleBased": "RoleBased", + "valid": "Valid", + "invalid": "Invalid", + "yes": "Yes", + "no": "No" + }, + "sheets": { + "validEmails": "Valid Emails", + "results": "Results", + "template": "Template" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Explorer", + "heroSubtitle": "Connect to your databases to manage collections, execute queries, and visualize your data.", + "featureMultiTitle": "Multi-Connection", + "featureMultiDesc": "Manage multiple database connections simultaneously.", + "featureMongoTitle": "MongoDB Support", + "featureMongoDesc": "Native support for MongoDB with advanced filtering.", + "featureQueryTitle": "Smart Query", + "featureQueryDesc": "Powerful query builder with syntax highlighting.", + "connectCta": "Connect to MongoDB", + "footerNote": "Currently supports {db}. More databases coming soon.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Select a Collection", + "selectCollectionDesc": "Choose a collection from the sidebar to view documents and start exploring your data.", + "dialogAddConnection": "Add Connection", + "toastConnectionAdded": "Connection added", + "toastFetchFailed": "Failed to fetch documents for {collection}", + "toastDocDeleted": "Document deleted", + "deleteDocTitle": "Are you absolutely sure?", + "deleteDocDescription": "This action cannot be undone. This will permanently delete the document from your collection.", + "closeAllTitle": "Close all tabs?", + "closeAllDescription": "Are you sure you want to close all tabs? Any unsaved changes in query filters will be lost.", + "closeAllConfirm": "Close All", + "cancel": "Cancel", + "delete": "Delete" + }, + "connection": { + "titleEdit": "Edit Connection", + "titleConnect": "Connect to MongoDB", + "subtitleEdit": "Update your connection details below.", + "subtitleConnect": "Enter your MongoDB connection string to start exploring your data.", + "labelName": "Name", + "placeholderName": "e.g. Production DB", + "labelConnectionString": "Connection String", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://username:password@host:port/database", + "testConnection": "Test Connection", + "connecting": "Connecting...", + "updateConnect": "Update & Connect", + "connect": "Connect", + "cancelEdit": "Cancel Edit", + "savedConnections": "Saved Connections", + "emptySaved": "No saved connections yet.", + "lastUsed": "Last used {time}", + "toastTestOk": "Connection successful!", + "toastTestFail": "Connection failed: {message}", + "toastUpdated": "Connection updated", + "confirmDelete": "Are you sure you want to delete this saved connection?", + "toastDeletedConn": "Connection deleted", + "toastDeleteFail": "Failed to delete connection", + "copyStringTitle": "Copy connection string", + "toastStringCopied": "Connection string copied!", + "editTitle": "Edit", + "deleteTitle": "Delete" + }, + "sidebar": { + "explorer": "Explorer", + "add": "Add", + "tooltipAdd": "Add new connection", + "tooltipRefresh": "Refresh connections", + "searchPlaceholder": "Search...", + "loadingConnections": "Loading connections...", + "noConnectionsFound": "No connections found", + "toastLoadFail": "Failed to load connections", + "toastConnectFail": "Failed to connect to {name}", + "toastCollectionsFail": "Failed to fetch collections for {name}", + "toastRenamed": "Connection renamed", + "toastRenameFail": "Failed to rename connection", + "confirmDeleteConnection": "Are you sure you want to delete connection \"{name}\"?", + "toastDeleted": "Connection deleted", + "toastDeleteConnFail": "Failed to delete connection", + "confirmDropDb": "Are you sure you want to drop database \"{name}\"? This action cannot be undone.", + "toastDbDropped": "Database {name} dropped", + "confirmDropCollection": "Are you sure you want to drop collection \"{name}\"? This action cannot be undone.", + "toastCollectionDropped": "Collection {name} dropped", + "toastCollectionRenamed": "Collection renamed to {name}", + "toastDatabaseRenamed": "Database renamed to {name}", + "connecting": "Connecting...", + "connectionFailed": "Connection failed", + "noDatabases": "No databases", + "loading": "Loading...", + "noCollections": "No collections", + "renameCollectionTitle": "Rename Collection", + "renameDatabaseTitle": "Rename Database", + "placeholderNewCollection": "New collection name", + "placeholderNewDatabase": "New database name", + "rename": "Rename", + "refresh": "Refresh", + "copyDbName": "Copy DB Name", + "toastDbNameCopied": "Database name copied to clipboard", + "dropDatabase": "Drop Database", + "dropCollection": "Drop Collection", + "menuRename": "Rename", + "menuCopyConnectionString": "Copy Connection String", + "menuDeleteConnection": "Delete Connection", + "toastSidebarStringCopied": "Connection string copied!", + "cancel": "Cancel", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} docs", + "table": "Table", + "json": "JSON", + "tree": "Tree", + "tooltipTable": "Table View", + "tooltipJson": "JSON View", + "tooltipTree": "Tree View", + "schema": "Schema", + "indexes": "Indexes", + "itemsPerPage": "Items per page", + "perPage": "{n} / page", + "prevPage": "Previous Page", + "nextPage": "Next Page", + "expandAll": "Expand All", + "collapseAll": "Collapse All", + "refreshData": "Refresh Data", + "insert": "Insert", + "import": "Import", + "export": "Export", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "actions": "Actions", + "invalidJson": "Invalid JSON content", + "copiedClipboard": "Copied to clipboard", + "docUpdated": "Document updated successfully", + "docInserted": "Document inserted successfully", + "invalidJsonShort": "Invalid JSON", + "docCopied": "Document copied to clipboard", + "openFullEditor": "Open in full editor", + "cellArray": "Array({length})", + "cellObject": "Object", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "No documents match your query", + "emptyFilterHint": "Try adjusting your filter or clearing the query.", + "clearFilter": "Clear Filter", + "emptyCollectionTitle": "This collection is empty", + "emptyCollectionHint": "Insert a document to get started.", + "insertDocument": "Insert Document", + "formatJson": "Format JSON", + "compactJson": "Compact JSON", + "copyClipboard": "Copy to Clipboard", + "copyJson": "Copy JSON", + "duplicate": "Duplicate", + "editDoc": "Edit Document", + "deleteDoc": "Delete Document", + "documentLabel": "Document {n}", + "editDialogTitle": "Edit Document", + "insertDialogTitle": "Insert Document", + "viewValueTitle": "View Value", + "saveChanges": "Save Changes", + "close": "Close", + "cancel": "Cancel", + "insertButton": "Insert" + }, + "tabs": { + "filterActive": "Active filter applied", + "filterActiveTooltip": "Filter active", + "closeAll": "Close All" + }, + "query": { + "migrateHistory": "Migrated query history to cloud", + "saveHistoryFail": "Failed to save query history", + "deleteQueryFail": "Failed to delete query", + "invalidJsonQuery": "Invalid JSON query", + "placeholder": "e.g. { \"status\": \"active\" }", + "clearQuery": "Clear query", + "openAdvanced": "Open Advanced Editor", + "visualBuilder": "Visual Query Builder", + "filterRules": "Filter Rules", + "addRule": "Add Rule", + "noFilters": "No filters applied. Add a rule to filter documents.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "Field", + "opPlaceholder": "Op", + "typePlaceholder": "Type", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Not In", + "opExists": "Exists", + "typeAuto": "Auto", + "typeString": "String", + "typeNumber": "Number", + "typeBoolean": "Boolean", + "typeDate": "Date", + "typeObjectId": "ObjectId", + "true": "True", + "false": "False", + "valuePlaceholder": "Value", + "valueListPlaceholder": "value1, value2", + "clearFilters": "Clear Filters", + "applyFilters": "Apply Filters", + "queryHistory": "Query History", + "noSavedQueries": "No saved queries yet", + "advancedEditorTitle": "Advanced Query Editor", + "templatesPlaceholder": "Templates", + "templateEmptyAgg": "Empty Aggregation", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Join)", + "templateGroup": "$group (Count/Sum)", + "format": "Format", + "formattedJson": "Formatted JSON", + "runQuery": "Run Query" + }, + "export": { + "selectFieldsError": "Please select at least one field to export", + "success": "Successfully exported to {filename}", + "fail": "Failed to export data", + "title": "Export Data", + "formatLabel": "Export Format", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Select Fields", + "selectAll": "Select All", + "fieldsSelected": "{count, plural, one {# field selected} other {# fields selected}}", + "cancel": "Cancel", + "export": "Export", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Type: {type}", + "itemSummary": "{count, plural, one {# item} other {# items}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "New Request", + "importedRequest": "Imported Request", + "untitledRequest": "Untitled Request" + }, + "toasts": { + "curlImported": "cURL imported successfully", + "curlParseFailed": "Failed to parse cURL command", + "invalidJsonBody": "Invalid JSON body", + "requestFailed": "Request failed: {message}", + "curlPasted": "cURL pasted and parsed successfully", + "responseCopied": "Response copied to clipboard", + "codeCopied": "Code copied to clipboard", + "copyFailed": "Failed to copy to clipboard" + }, + "layout": { + "collections": "Collections", + "closeSidebar": "Close sidebar", + "openSidebar": "Open sidebar", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Method", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Sending...", + "send": "Send", + "invalidJsonBodyHelp": "Cannot send: the JSON body is invalid" + }, + "requestTabs": { + "params": "Params", + "headers": "Headers", + "body": "Body", + "auth": "Auth", + "bodyType": "Body type", + "selectType": "Select type", + "bodyNone": "None", + "bodyJson": "JSON", + "bodyText": "Text", + "noBodyHint": "No body content for this request", + "switchToJson": "Switch to JSON body", + "authType": "Auth type", + "noAuth": "No auth", + "bearerToken": "Bearer token", + "basicAuth": "Basic auth", + "apiKey": "API key", + "labelToken": "Token", + "placeholderBearerToken": "Bearer token", + "labelUsername": "Username", + "placeholderUsername": "Username", + "labelPassword": "Password", + "placeholderPassword": "Password", + "labelKey": "Key", + "placeholderApiKeyName": "Key name (e.g. X-Api-Key)", + "labelValue": "Value", + "placeholderValue": "Value", + "addTo": "Add to", + "header": "Header", + "queryParams": "Query params", + "noAuthHint": "This request does not use authentication." + }, + "responsePanel": { + "emptyTitle": "Ready to explore?", + "emptyHint": "Enter a URL and click Send to see the response data here.", + "responsePreviewAlt": "Response preview", + "previewUnavailable": "Preview not available for this content type", + "status": "Status", + "time": "Time", + "size": "Size", + "copyBody": "Copy body", + "downloadResponse": "Download response", + "bodyTab": "Body", + "previewTab": "Preview", + "headersTab": "Headers", + "binaryRawView": "Binary data cannot be displayed in raw view.", + "errorStatusLabel": "Error", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "New tab" + }, + "collectionsSidebar": { + "title": "API resources", + "newCollection": "New collection", + "tabCollections": "Collections", + "tabHistory": "History", + "noCollectionsTitle": "No collections", + "noCollectionsHint": "Create a collection to organize your API requests.", + "createCollection": "Create collection", + "newFolder": "New folder", + "rename": "Rename", + "delete": "Delete", + "emptyCollection": "Empty collection", + "recentRequests": "Recent requests", + "clearAll": "Clear all", + "noHistoryTitle": "No history yet", + "noHistoryHint": "Send a request to see it here.", + "dialogNewFolderTitle": "New folder", + "dialogNewFolderDescription": "Create a new folder to organize your requests.", + "labelFolderName": "Folder name", + "placeholderFolderName": "My folder", + "createFolder": "Create folder", + "dialogNewCollectionTitle": "New collection", + "dialogNewCollectionDescription": "Create a new top-level collection.", + "labelCollectionName": "Collection name", + "placeholderCollectionName": "My collection", + "dialogRenameCollectionTitle": "Rename collection", + "dialogRenameCollectionDescription": "Enter a new name for the collection.", + "placeholderRenameCollection": "Collection name", + "renameAction": "Rename", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "New folder", + "delete": "Delete", + "emptyFolder": "Empty folder" + }, + "environmentManager": { + "noEnvironment": "No environment", + "manageDialogTitle": "Manage environments", + "placeholderNewEnv": "New environment", + "variablesHeading": "{name} variables", + "addVariable": "Add variable", + "columnVariable": "Variable", + "columnValue": "Value", + "placeholderKey": "Key", + "placeholderValue": "Value", + "selectEnvToEdit": "Select an environment to edit" + }, + "codeGenerator": { + "triggerTitle": "Generate code", + "dialogTitle": "Generate code", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Copy", + "errorGenerating": "Error generating code" + }, + "keyValueEditor": { + "columnKey": "Key", + "columnValue": "Value", + "placeholderKey": "Key", + "placeholderValue": "Value", + "addItem": "Add item" + }, + "importCurl": { + "trigger": "Import cURL", + "dialogTitle": "Import cURL", + "dialogDescription": "Paste your cURL command below to import it as a new request.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Import" + }, + "saveRequest": { + "trigger": "Save", + "dialogTitle": "Save request", + "dialogDescription": "Save this request to a collection folder.", + "labelName": "Request name", + "placeholderName": "My request", + "labelFolder": "Folder", + "placeholderFolder": "Select a folder", + "save": "Save" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "importCollection": "Import collection (Postman / HAR)", + "environments": "Environments", + "cookies": "Cookies", + "perfRun": "Performance run", + "fuzzRun": "Fuzz run", + "publicMocks": "Public mocks", + "plugins": "Plugins", + "metrics": "Metrics", + "recorder": "Capture & replay", + "shortcuts": "Keyboard shortcuts", + "extensionImported": "Imported {method} {url} from extension" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Edit Mode", + "viewOnlyMode": "View Only Mode", + "insertComponent": "Insert component", + "addUnorderedList": "Add unordered list", + "addOrderedList": "Add ordered list", + "addTable": "Add table" + }, + "addBlock": "Add block", + "media": { + "popoverTitle": "Add media", + "singleImage": "Single Image", + "singleImageDesc": "Upload one image", + "multipleImages": "Multiple Images", + "multipleImagesDesc": "Upload image grid", + "video": "Video", + "videoDesc": "Upload video file", + "uploadingAlt": "Uploading...", + "uploadFailedAlt": "Upload failed" + }, + "export": { + "fabAria": "Export code", + "title": "Export Code", + "description": "Copy the HTML or JSON output of your editor content", + "preview": "Preview", + "html": "HTML", + "json": "JSON", + "previewOptions": "Preview Options", + "enhanceSpaces": "Enhance Spaces", + "livePreview": "Live preview of rendered HTML", + "htmlTailwind": "HTML with Tailwind CSS classes", + "editorJson": "Editor state as JSON", + "copyHtml": "Copy HTML", + "copyJson": "Copy JSON", + "copied": "Copied!" + }, + "template": { + "fabTitle": "Switch Template", + "title": "Choose a Template", + "description": "Select a template to get started quickly with pre-built layouts and content", + "catAll": "All Templates", + "catProductivity": "Productivity", + "catCreative": "Creative", + "catBusiness": "Business", + "catPersonal": "Personal", + "noTemplates": "No templates found in this category", + "confirmTitle": "Replace Existing Content?", + "confirmDescription": "You have existing content in the editor. Applying this template will replace all current content.", + "cannotUndo": "This action cannot be undone.", + "cancel": "Cancel", + "replaceContent": "Replace Content" + }, + "command": { + "searchPlaceholder": "Search commands...", + "noResults": "No commands found.", + "turnInto": "Turn into", + "h1": { + "label": "Heading 1", + "description": "Big section heading", + "keywords": "h1,heading,title,large" + }, + "h2": { + "label": "Heading 2", + "description": "Medium section heading", + "keywords": "h2,heading,subtitle" + }, + "h3": { + "label": "Heading 3", + "description": "Small section heading", + "keywords": "h3,heading,subheading" + }, + "h4": { + "label": "Heading 4", + "description": "Tiny section heading", + "keywords": "h4,heading" + }, + "h5": { + "label": "Heading 5", + "description": "Smaller heading", + "keywords": "h5,heading" + }, + "h6": { + "label": "Heading 6", + "description": "Smallest heading", + "keywords": "h6,heading" + }, + "p": { + "label": "Paragraph", + "description": "Regular text paragraph", + "keywords": "p,paragraph,text,normal" + }, + "code": { + "label": "Code Block", + "description": "Code snippet", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "Quote", + "description": "Block quote", + "keywords": "quote,blockquote,citation" + }, + "li": { + "label": "Bulleted List", + "description": "Simple list item with bullet", + "keywords": "list,bullet,unordered,ul,li" + }, + "ol": { + "label": "Numbered List", + "description": "Numbered list item", + "keywords": "list,numbered,ordered,ol,li" + }, + "img": { + "label": "Image", + "description": "Upload or embed an image", + "keywords": "image,img,picture,photo,upload" + }, + "video": { + "label": "Video", + "description": "Upload or embed a video", + "keywords": "video,vid,movie,mp4,upload" + }, + "table": { + "label": "Table", + "description": "Create a table", + "keywords": "table,grid,rows,columns,cells" + } + }, + "toasts": { + "tableImported": "Table Imported", + "tableImportedDesc": "Markdown table has been imported successfully", + "mediaPasted": "Media pasted", + "pasteMixed": "{imageCount} image(s) and {videoCount} video(s) pasted successfully.", + "pasteImages": "{count} image(s) pasted successfully.", + "pasteVideos": "{count} video(s) pasted successfully.", + "pasteFailed": "Paste failed", + "unexpectedError": "An unexpected error occurred", + "uploadFailed": "Upload failed" + }, + "blockBackground": { + "menuItem": "Change Background Color", + "dialogTitle": "Block Background Color", + "dialogDescription": "Choose a background color for this block", + "tabPreset": "Preset Colors", + "tabCustom": "Custom Color", + "sectionHeading": "Background Colors", + "apply": "Apply", + "colors": { + "none": "None", + "red": "Red", + "orange": "Orange", + "yellow": "Yellow", + "green": "Green", + "blue": "Blue", + "indigo": "Indigo", + "purple": "Purple", + "pink": "Pink", + "teal": "Teal", + "cyan": "Cyan", + "gray": "Gray" + } + }, + "tableDialog": { + "title": "Create Table", + "description": "Create a new table or import from markdown", + "markdownCheckbox": "I have a markdown table", + "pasteMarkdownLabel": "Paste Markdown Table", + "markdownPlaceholder": "| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "Paste your markdown table above. Must include header and separator rows.", + "rows": "Rows", + "columns": "Columns", + "maxDimensions": "Maximum: 20 rows × 10 columns", + "cancel": "Cancel", + "importTable": "Import Table", + "createTable": "Create Table" + }, + "tableParse": { + "errors": { + "minRows": "Table must have at least a header row and separator row", + "pipeFormat": "Table rows must start and end with |", + "headerMinCol": "Header row must have at least one column", + "separatorRow": "Second row must be a separator (e.g., |---|---|)", + "rowColumnMismatch": "Row {row} has {got} columns, expected {expected}", + "parseFailed": "Failed to parse markdown table" + } + }, + "link": { + "editLink": "Edit Link", + "addLink": "Add Link", + "selectedText": "Selected text: \"{text}\"", + "linkUrl": "Link URL", + "urlPlaceholder": "https://example.com", + "update": "Update", + "addLinkButton": "Add Link", + "toastApplied": "Link Applied", + "toastLinkedTo": "Linked to: {url}", + "toastRemoved": "Link Removed", + "toastRemovedDesc": "Link has been removed from selection" + }, + "customClass": { + "devMode": "Dev Mode", + "toggleDevModeAria": "Toggle dev mode", + "searchPlaceholderDev": "Search classes... (e.g., 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Search styles... (e.g., 'red', 'bold', 'shadow')", + "appliesTitle": "Applies: {className}", + "noMatches": "No classes found matching \"{query}\"", + "toastClassReplaced": "Class Replaced", + "toastClassReplacedDesc": "Replaced \"{replaced}\" with \"{className}\"", + "toastCustomClassApplied": "Custom Class Applied", + "toastAppliedClass": "Applied class: {className}" + }, + "insertComponents": { + "title": "Insert Component", + "description": "Choose a component to insert into your document", + "noComponents": "No components available", + "categories": { + "media": "Media", + "layout": "Layout", + "interactive": "Interactive", + "decoration": "Decoration" + }, + "freeImage": { + "name": "Free Movement Image", + "description": "Add an image that can be positioned anywhere and resized freely" + } + } + }, + "JsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON in text and tree views.", + "documentNameText": "Text document {n}", + "documentNameTree": "Tree document {n}", + "newDocument": "New", + "copy": "Copy", + "save": "Save", + "load": "Load", + "saving": "Saving...", + "leftDocNameLabel": "Left document name", + "rightDocNameLabel": "Right document name", + "toastCopiedText": "Text pane copied", + "toastCopiedTree": "Tree pane copied", + "toastCopyFailed": "Failed to copy JSON", + "toastLoginRequired": "Please sign in to save documents", + "toastSaved": "Document saved", + "toastSaveFailed": "Failed to save document", + "format": "Format", + "compact": "Compact", + "sortKeys": "Sort keys", + "repair": "Repair", + "query": "Query", + "toastInvalidJson": "Invalid JSON", + "toastAlreadyValid": "JSON is already valid", + "toastRepaired": "JSON repaired", + "toastRepairFailed": "Could not repair JSON" + }, + "JwtDecoder": { + "title": "JWT decoder", + "subtitle": "Inspect header and payload. Expiry uses the exp claim. Nothing is sent to a server; the signature is not verified.", + "tabs": { + "decode": "Decode", + "sign": "Generator / signer" + }, + "tokenLabel": "Token", + "clear": "Clear", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Copy", + "expiryLabel": "Expiry (exp)", + "signaturePresent": "Signature present (not verified)", + "noExp": "No exp claim in the payload.", + "status": { + "expired": "Expired", + "valid": "Valid" + }, + "fields": { + "unix": "Unix", + "local": "Local", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Issued at (iat)", + "notBefore": "Not before (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Paste a JWT to decode.", + "segments": "A JWT needs at least a header and payload (two segments).", + "base64": "Could not base64url-decode header or payload." + }, + "relative": { + "future": "in {n} {unit}", + "past": "{n} {unit} ago" + }, + "units": { + "second": "{count, plural, one {second} other {seconds}}", + "minute": "{count, plural, one {minute} other {minutes}}", + "hour": "{count, plural, one {hour} other {hours}}", + "day": "{count, plural, one {day} other {days}}" + }, + "signer": { + "algLabel": "Algorithm (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Secret (HS*)", + "secretPlaceholder": "Paste your HMAC secret…", + "privateKeyLabel": "Private key PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "The \"none\" algorithm creates an unsigned token. This is insecure and should only be used for testing.", + "sign": "Sign", + "verify": "Verify signature", + "outputLabel": "Token (compact JWS)", + "outputPlaceholder": "Click \"Sign\"…", + "copyToken": "Copy token", + "copied": "Copied", + "verifyKeyLabel": "Verification key (optional)", + "verifyKeyPlaceholder": "For RS*: paste the public key (SPKI) PEM. For HS*: leave empty to reuse the secret.", + "verifyKeyHint": "If empty, verification uses the signing key (useful for HS*).", + "verifyStatus": { + "valid": "Signature valid", + "invalid": "Signature invalid" + }, + "securityNote": "Everything runs locally in your browser; keys and tokens are not uploaded." + }, + "signErrors": { + "invalidJson": "Header and payload must be valid JSON.", + "unsupportedAlg": "Unsupported algorithm.", + "missingKey": "A signing key is required for this algorithm.", + "cryptoFailed": "Signing failed. Check your key format and try again." + }, + "verifyErrors": { + "empty": "Enter a JWT to verify.", + "segments": "A JWT must have exactly 3 segments to verify a signature.", + "unsupportedAlg": "Unsupported or missing alg in header.", + "missingKey": "A verification key is required for this algorithm.", + "base64": "Could not decode the JWT header or signature.", + "cryptoFailed": "Verification failed. Check your key format and try again." + } + }, + "CertificatePemDecoder": { + "title": "Certificate / PEM decoder", + "subtitle": "Paste an X.509 certificate (-----BEGIN CERTIFICATE-----) or a CSR (-----BEGIN CERTIFICATE REQUEST-----). Subject, issuer, dates, and SANs are parsed locally; nothing is uploaded.", + "pemLabel": "PEM input", + "clear": "Clear", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Parsing…", + "copyHex": "Copy fingerprint (hex, no colons)", + "errors": { + "empty": "Paste a PEM certificate or CSR to decode.", + "noBlock": "No certificate or CSR PEM block found.", + "parseFailed": "Could not parse this PEM data." + }, + "block": { + "title": "PEM block {index}", + "certificate": "X.509 certificate", + "csr": "Certificate signing request" + }, + "fields": { + "subject": "Subject", + "issuer": "Issuer", + "notBefore": "Not valid before", + "notAfter": "Not valid after", + "serial": "Serial (hex)", + "sha256": "SHA-256 fingerprint", + "sans": "Subject Alternative Names", + "sansNone": "No Subject Alternative Name extension.", + "signatureAlgorithm": "Signature algorithm", + "csrIssuerNote": "CSRs do not include an issuer (that appears on the issued certificate).", + "csrValidityNote": "CSRs do not include validity dates (those appear on the issued certificate)." + }, + "sanType": { + "dns": "DNS", + "dn": "Directory name", + "email": "Email", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Registered ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL formatter", + "subtitle": "Pretty-print SQL for MySQL, PostgreSQL, or SQLite. Runs in your browser; nothing is uploaded.", + "dialectLabel": "Dialect", + "keywordsLabel": "Keywords", + "indentLabel": "Indent", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "UPPER", + "lower": "lower", + "preserve": "Preserve" + }, + "spaces": "{n} spaces", + "format": "Format", + "clear": "Clear", + "counter": "{current} / {max} characters", + "inputPanel": "Input", + "formattedPanel": "Formatted", + "copy": "Copy", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Click \"Format\"…", + "errors": { + "tooLong": "Input is limited to {max} characters.", + "couldNotFormat": "Could not format this SQL." + } + }, + "YamlFormatter": { + "title": "YAML Formatter", + "subtitle": "Format, validate, and convert YAML in your browser. Nothing is uploaded.", + "outputLabel": "Output", + "outputModes": { + "yaml": "YAML (formatted)", + "json": "JSON" + }, + "indentLabel": "Indent", + "spaces": "{n} spaces", + "format": "Format", + "clear": "Clear", + "copy": "Copy", + "counter": "{current} / {max} characters", + "inputPanel": "Input", + "outputPanel": "Output", + "errors": { + "tooLong": "Input is limited to {max} characters.", + "couldNotFormat": "Could not parse this YAML." + } + }, + "GraphqlFormatter": { + "title": "GraphQL formatter", + "subtitle": "Format GraphQL queries, mutations, and subscriptions in your browser. Nothing is uploaded.", + "tabs": { + "format": "Format", + "build": "Query builder" + }, + "outputModeLabel": "Output", + "outputModes": { + "pretty": "Pretty print", + "minify": "Minify" + }, + "format": "Format", + "clear": "Clear", + "counter": "{current} / {max} characters", + "inputPanel": "Input", + "outputPanel": "Output", + "copy": "Copy", + "builderHint": "Compose a document skeleton, then apply it to the input editor. Variable definitions use GraphQL syntax (e.g. $id: ID!)—parentheses are optional.", + "builder": { + "operation": "Operation", + "operationName": "Operation name", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Variable definitions", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Selection set", + "apply": "Apply to input", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Input is limited to {max} characters.", + "couldNotFormat": "Could not parse or print this GraphQL.", + "emptySelection": "Add a selection set before applying." + } + }, + "RegexTester": { + "title": "Regex tester", + "subtitle": "Live JavaScript-style regex against your text. Matches are highlighted in the editor; nothing leaves your browser.", + "patternLabel": "Pattern", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i ignore case", + "m": "m multiline", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(empty)", + "noFlags": "—", + "matches": "{count, plural, one {match} other {matches}}", + "testTextLabel": "Test text", + "counter": "{current} / {max}", + "clear": "Clear", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Text is limited to {max} characters.", + "invalid": "Invalid regular expression.", + "runaway": "Stopped after {max} matches (possible runaway pattern)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Timestamp converter", + "subtitle": "Unix seconds (≤10 digits) or milliseconds (longer integers), ISO-8601, or strings your browser can parse. Everything runs locally.", + "inputLabel": "Input", + "placeholder": "1744200000 or 2025-04-09T12:00:00Z", + "now": "Now", + "copy": "Copy", + "hint": "Paste a value or click Now.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relative", + "human": "Human-readable" + }, + "fields": { + "seconds": "Seconds", + "milliseconds": "Milliseconds", + "utc": "UTC", + "local": "Local", + "distance": "Distance" + }, + "errors": { + "empty": "Enter a Unix timestamp, ISO string, or date text.", + "invalidNumber": "Invalid numeric timestamp.", + "outOfRange": "Out of range for JavaScript Date.", + "couldNotParse": "Could not parse as Unix time, ISO-8601, or browser date string." + } + }, + "HttpStatusCodes": { + "title": "HTTP status codes", + "subtitle": "Search and browse HTTP response status codes (1xx5xx) with short descriptions and RFC references. Runs locally in your browser.", + "header": { + "title": "HTTP Status Codes", + "description": "Searchable list of HTTP response status codes with descriptions and RFC references." + }, + "searchPlaceholder": "Search: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filter class…", + "classFilterAll": "All classes", + "resultsCount": "{count} of {total}", + "table": { + "code": "Code", + "phrase": "Reason phrase", + "description": "Description", + "copy": "Copy" + }, + "noResults": "No matches for “{query}”.", + "specPrefix": "Spec:", + "copied": "Status code copied" + }, + "CronBuilder": { + "title": "Cron expression builder", + "subtitle": "Standard 5-field cron: minute hour day month weekday. Parser uses seconds=0 internally. Optional 6-field (sec min hour dom mon dow) works in the expression editor.", + "tabs": { + "builder": "Visual builder", + "expression": "Expression" + }, + "presetsLabel": "Presets", + "fieldsLabel": "Fields", + "rawExpressionLabel": "Raw expression", + "expressionHint": "Blur the field to parse and sync the builder. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Current expression", + "copy": "Copy", + "quickPick": "Quick pick", + "emptyExpression": "—", + "plainLanguageLabel": "Plain language", + "nextRunsLabel": "Next runs (local time)", + "fields": { + "minute": "Minute", + "hour": "Hour", + "dayOfMonth": "Day of month", + "month": "Month", + "dayOfWeek": "Day of week" + }, + "presets": { + "everyMinute": "Every minute", + "every5Minutes": "Every 5 minutes", + "every15Minutes": "Every 15 minutes", + "everyHour": "Every hour (at :00)", + "every6Hours": "Every 6 hours", + "dailyMidnight": "Daily at midnight", + "daily0900": "Daily at 09:00", + "weekdays0900": "Weekdays at 09:00", + "weeklySunday": "Weekly (Sunday 00:00)", + "monthlyFirst": "Monthly (1st 00:00)", + "yearlyJan1": "Yearly (Jan 1 00:00)" + }, + "picks": { + "minute": { + "every": "every minute" + }, + "hour": { + "every": "every hour" + }, + "dom": { + "any": "any day", + "last": "last day" + }, + "month": { + "every": "every month" + }, + "dow": { + "any": "any weekday", + "weekdays": "weekdays", + "weekend": "weekend" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mar", + "apr": "Apr", + "may": "May", + "jun": "Jun", + "jul": "Jul", + "aug": "Aug", + "sep": "Sep", + "oct": "Oct", + "nov": "Nov", + "dec": "Dec" + }, + "weekdays": { + "sun": "Sun", + "mon": "Mon", + "tue": "Tue", + "wed": "Wed", + "thu": "Thu", + "fri": "Fri", + "sat": "Sat" + }, + "errors": { + "fieldCount": "Expected 5 fields (minute hour day month weekday) or 6 including seconds.", + "invalid": "Invalid cron expression." + } + }, + "JsonSchemaGenerator": { + "title": "JSON Schema Generator", + "subtitle": "Paste valid JSON to generate JSON Schema or typed models. Everything runs in your browser.", + "languageLabel": "Output language", + "inputLabel": "JSON input", + "inputPlaceholder": "Paste JSON here…", + "outputLabel": "Generated output", + "outputPlaceholder": "Generated schema or types will appear here…", + "parseError": "Invalid JSON", + "clear": "Clear", + "copy": "Copy", + "copied": "Copied to clipboard", + "copyFailed": "Could not copy", + "editorLoading": "Loading editor…", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON Schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "detectedFormats": "Detected formats", + "download": "Download" + }, + "SnippetManager": { + "title": "Code Snippet Manager", + "subtitle": "Store snippets in your browser, or sign in to sync them with your account. Switch between view and edit, copy in one click, pick a language or use auto-detect, and format JSON, SQL, or Monaco-supported code.", + "snippets": "Snippets", + "newSnippet": "New snippet", + "newSnippetTitle": "New snippet", + "defaultSnippetTitle": "Welcome", + "defaultSnippetCode": "// Welcome to Code Snippets\n// Try View vs Edit, Copy, Format, and Auto language.\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Search snippets…", + "searchAria": "Search snippets", + "emptySearch": "No snippets match your search.", + "snippetTitle": "Title", + "snippetTitlePlaceholder": "Snippet name", + "language": "Language", + "detectedHint": "Highlighting as {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "View", + "modeEdit": "Edit", + "copy": "Copy", + "copied": "Copied", + "format": "Format", + "cancel": "Cancel", + "deleteTitle": "Delete snippet?", + "deleteDescription": "“{title}” will be removed from this browser.", + "deleteConfirm": "Delete", + "editorAria": "Snippet code editor", + "editorLoading": "Loading editor…", + "toastCopied": "Copied to clipboard", + "toastCopyFailed": "Could not copy", + "toastNothingToCopy": "Nothing to copy", + "toastFormatted": "Formatted", + "toastFormatFailed": "Could not format", + "toastFormatJsonInvalid": "Invalid JSON — fix syntax first", + "toastFormatUnsupported": "No formatter for this language — try JSON or SQL, or use Edit mode", + "toastDeleted": "Snippet deleted", + "toastLoadFailed": "Could not load snippets from the server.", + "toastSyncFailed": "Could not sync with the server.", + "languages": { + "auto": "Auto-detect", + "plaintext": "Plain text", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Upload a CSV or Excel file, edit JSON, then download CSV or XLSX. Tabular data only; Excel date cells become ISO timestamps in JSON.", + "dropTitle": "Drop a CSV or Excel file", + "dropHint": "First worksheet is used for .xlsx / .xls. CSV must include a header row.", + "chooseFile": "Choose file", + "jsonLabel": "JSON (array of objects)", + "jsonPlaceholder": "Paste a JSON array of objects, or import CSV / Excel…", + "loadSample": "Load sample", + "actions": { + "format": "Format JSON", + "clear": "Clear", + "copy": "Copy", + "copied": "Copied", + "downloadCsv": "Download CSV", + "downloadXlsx": "Download Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Data", + "toastLoadedCsv": "CSV loaded into editor", + "toastLoadedExcel": "Excel file loaded into editor", + "toastFormatted": "JSON formatted", + "toastExportedCsv": "CSV downloaded", + "toastExportedXlsx": "Excel file downloaded", + "toastCopied": "Copied to clipboard", + "footnote": "Everything runs in your browser. JSON must be a non-empty array of objects. Nested values are JSON-stringified inside CSV cells.", + "errors": { + "unsupportedFile": "Use a .csv, .xlsx, or .xls file.", + "parseFailed": "Could not parse this file.", + "invalidJson": "Invalid JSON", + "exportFailed": "Export failed", + "copyFailed": "Could not copy" + } + }, + "Base64": { + "title": "Base64 Encoder / Decoder", + "subtitle": "Encode text to Base64 or decode Base64 strings", + "upload": "Upload", + "clear": "Clear", + "modes": { + "encode": "Encode", + "decode": "Decode" + }, + "swapTitle": "Swap input and output", + "panels": { + "plainText": "Plain Text", + "base64Input": "Base64 Input", + "base64Output": "Base64 Output", + "decodedText": "Decoded Text" + }, + "placeholders": { + "encode": "Enter text to encode to Base64…", + "decode": "Paste Base64 string to decode…" + }, + "dropHere": "Drop file here", + "charCount": "{count} chars", + "download": { + "title": "Download output", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Copy to clipboard", + "outputPlaceholder": "Output will appear here…", + "errors": { + "invalidBase64": "Invalid Base64 string. Please check your input.", + "encodeFailed": "Failed to encode the input." + } + }, + "UrlEncode": { + "title": "URL Encoder / Decoder", + "subtitle": "Percent-encode or decode text using encodeURIComponent / decodeURIComponent (UTF-8)", + "upload": "Upload", + "clear": "Clear", + "modes": { + "encode": "Encode", + "decode": "Decode" + }, + "swapTitle": "Swap input and output", + "panels": { + "plainText": "Plain text", + "encodedInput": "Encoded input", + "encodedOutput": "Encoded output", + "decodedText": "Decoded text" + }, + "placeholders": { + "encode": "Enter text to percent-encode (e.g. query string values)…", + "decode": "Paste percent-encoded text to decode…" + }, + "dropHere": "Drop file here", + "charCount": "{count} chars", + "download": { + "title": "Download output", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Copy to clipboard", + "outputPlaceholder": "Output will appear here…", + "errors": { + "invalidEncoding": "Invalid percent-encoding. Check for incomplete % pairs or bad escape sequences.", + "encodeFailed": "Could not encode (invalid Unicode in input)." + } + }, + "UrlParser": { + "title": "URL Parser", + "subtitle": "Break a URL into protocol, host, path, query parameters, and hash (fragment).", + "upload": "Upload", + "clear": "Clear", + "panels": { + "input": "URL input", + "output": "Parsed output" + }, + "placeholders": { + "input": "Paste a URL (e.g. https://example.com/path?foo=bar#section) or a host/path (e.g. example.com/path)…" + }, + "dropHere": "Drop file here", + "charCount": "{count} chars", + "outputPlaceholder": "Parsed components will appear here…", + "fields": { + "protocol": "Protocol", + "host": "Host", + "path": "Path", + "hash": "Hash", + "queryParams": "Query parameters" + }, + "paramCount": "{count} params", + "noParams": "No query parameters.", + "paramKey": "Key", + "paramValue": "Value", + "copyTitle": "Copy to clipboard", + "copyParamTitle": "Copy as key=value", + "download": { + "title": "Download JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Invalid URL. Include a hostname (example.com) and check for illegal characters." + } + }, + "UuidGenerator": { + "title": "UUID / ULID generator", + "subtitle": "Generate RFC 9562 UUIDs or ULIDs in the browser. Bulk export up to {max} per run.", + "format": "Format", + "kindHints": { + "ulid": "Sortable, URL-safe 26-character identifier", + "uuid4": "Random (RFC 9562)", + "uuid7": "Time-ordered, Unix ms + random", + "uuid1": "MAC/time-based (RFC 9562)", + "uuid6": "Reordered Gregorian time-based", + "uuid3": "MD5 hash of name + namespace", + "uuid5": "SHA-1 hash of name + namespace", + "nil": "All-zero nil UUID (RFC 9562)" + }, + "countLabel": "Count (1–{max})", + "nameLabel": "Name (input string)", + "namePlaceholder": "e.g. my.app/resource", + "bulkHint": "Bulk runs append #0, #1, … so each ID is distinct (v3/v5 are deterministic).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Custom UUID" + }, + "customNamespaceLabel": "Namespace UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Generate", + "output": "Output", + "lines": "{count} lines", + "download": { + "title": "Download .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Copy all", + "uppercaseToggle": "Uppercase output", + "hyphensToggle": "Show hyphens", + "copyJsonTitle": "Copy as JSON array", + "outputPlaceholder": "Click \"Generate\"…", + "errors": { + "invalidCustomNamespace": "Custom namespace must be a valid UUID.", + "missingName": "Name is required for UUID v3 and v5.", + "unknown": "Could not generate IDs." + } + }, + "SecretApiKeyGenerator": { + "title": "Secret / API key generator", + "subtitle": "Generate cryptographically random strings with a configurable length and alphabet. Bulk export up to {max} per run. Pairs with the UUID / ULID generator.", + "alphabetLabel": "Alphabet", + "alphabetHint": "Duplicate characters are removed. Each symbol is drawn uniformly using crypto.getRandomValues with rejection sampling (no modulo bias).", + "alphabetCount": "{count} unique symbols", + "presetsLabel": "Presets", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alphanumeric", + "hex": "Hex (lowercase)", + "asciiPrintable": "ASCII printable" + }, + "lengthLabel": "Length per secret (1–{maxLen})", + "countLabel": "Count (1–{maxBulk})", + "generate": "Generate", + "output": "Output", + "lines": "{count} lines", + "download": { + "title": "Download .txt", + "filename": "secrets.txt" + }, + "copyTitle": "Copy all", + "outputPlaceholder": "Click \"Generate\"…", + "entropy": "{bits} bits of entropy", + "strength": { + "weak": "Weak", + "fair": "Fair", + "strong": "Strong" + }, + "excludeAmbiguous": "Exclude ambiguous characters", + "excludeAmbiguousHint": "Removes {chars} — easily misread or shell-hostile characters.", + "prefixLabel": "Key prefix (optional)", + "errors": { + "emptyAlphabet": "Add at least one character to the alphabet.", + "alphabetTooLong": "After removing duplicates, the alphabet can have at most {maxAlpha} characters.", + "lengthOutOfRange": "Length must be between 1 and {maxLen}.", + "countOutOfRange": "Count must be between 1 and {maxBulk}.", + "unknown": "Could not generate secrets." + } + }, + "QrCodeGenerator": { + "title": "QR code generator", + "subtitle": "Turn any text or URL into a QR code with custom styles. Everything is rendered locally in your browser.", + "contentLabel": "Content", + "contentPlaceholder": "https://example.com, plain text, Wi‑Fi string, vCard…", + "styleLabel": "Style", + "dotTypeLabel": "Dot shape", + "dotTypeSquare": "Square", + "dotTypeDots": "Dots", + "dotTypeRounded": "Rounded", + "dotTypeExtraRounded": "Extra rounded", + "dotTypeClassy": "Classy", + "dotTypeClassyRounded": "Classy rounded", + "cornerSquareLabel": "Corner squares", + "cornerSquareDot": "Dot", + "cornerSquareSquare": "Square", + "cornerSquareExtraRounded": "Extra rounded", + "cornerDotLabel": "Corner dots", + "cornerDotDot": "Dot", + "cornerDotSquare": "Square", + "eccLabel": "Error correction", + "eccL": "Low (~7%)", + "eccM": "Medium (~15%)", + "eccQ": "Quartile (~25%)", + "eccH": "High (~30%)", + "marginLabel": "Margin", + "widthLabel": "Image width", + "widthEffective": "Renders at {px} px", + "darkColor": "Dark modules", + "lightColor": "Light modules", + "resetColors": "Reset colors", + "logoLabel": "Center logo", + "logoMarginLabel": "Margin", + "uploadLogo": "Upload image", + "removeLogo": "Remove logo", + "downloadPng": "Download PNG", + "downloadSvg": "Download SVG", + "copyImage": "Copy image", + "copied": "Copied", + "clear": "Clear", + "previewAlt": "QR code preview", + "emptyHint": "Type or paste content to preview your QR code.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "Could not generate this QR code. Try shorter text or lower error correction.", + "copyUnsupported": "Your browser does not support copying images to the clipboard.", + "copyFailed": "Could not copy to the clipboard." + } + }, + "IpSubnetCalculator": { + "title": "IP / Subnet calculator", + "subtitle": "IPv4 and IPv6 CIDR details in your browser—no requests leave this page.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Network", + "placeholderIpv4": "10.0.0.0/24 or 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 or 2001:db8::1/64", + "hintIpv4": "Use CIDR (e.g. /24) or an IPv4 address plus a dotted-decimal netmask.", + "hintIpv6": "Include a prefix length (e.g. /64). If omitted, /128 is assumed.", + "copyValue": "Copy value", + "rows": { + "cidr": "CIDR", + "network": "Network address", + "netmask": "Subnet mask", + "wildcard": "Wildcard mask", + "broadcast": "Broadcast", + "firstHost": "First host", + "lastHost": "Last host", + "totalAddresses": "Total addresses", + "usableHosts": "Usable hosts", + "prefixLength": "Prefix length (bits)", + "networkIpv6": "Network address", + "lastIpv6": "Last address in range", + "totalAddressesIpv6": "Addresses in subnet" + }, + "usableNote": { + "pointToPoint": "/31 point-to-point links use both addresses as hosts (RFC 3021).", + "singleHost": "/32 is a single-host mask." + }, + "ipv6RangeHint": { + "normal": "Usable interface addresses typically exclude the subnet router anycast on some networks; this tool shows the full mathematical range.", + "single": "/128 is a single address.", + "full": "/0 spans all IPv6 addresses." + }, + "errors": { + "invalidIpv4": "Invalid IPv4 network. Try CIDR (10.0.0.0/24) or address + netmask.", + "invalidIpv6": "Invalid IPv6 network. Use address/prefix, for example 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Hash generator", + "subtitle": "MD5, SHA-1, and SHA-2 digests for text or files; bcrypt for passwords (text input). Everything runs locally in your browser.", + "algorithmLabel": "Algorithm", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "bcryptRoundsLabel": "Cost (rounds)", + "bcryptRoundsHint": "Higher values are slower and more resistant to brute force. Common default is 10.", + "modeText": "Text", + "modeFile": "File", + "fileTabDisabledTitle": "Bcrypt hashes passwords from text only, not arbitrary files.", + "inputLabelText": "Input", + "placeholderText": "Type or paste text to hash…", + "placeholderBcrypt": "Type or paste a password to hash…", + "chooseFile": "Choose file", + "clearFile": "Clear", + "fileHint": "Files are read only in memory in this tab; max {maxMb} MB per file. Nothing is uploaded.", + "outputLabel": "Digest (hex)", + "outputLabelBcrypt": "Bcrypt hash", + "copyHash": "Copy digest", + "copyBcrypt": "Copy hash", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch", + "copied": "Copied", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# character} other {# characters}}", + "emptyHint": "Enter text or choose a file to compute a hash.", + "emptyHintBcrypt": "Enter a password to compute a bcrypt hash.", + "securityNote": "MD5 and SHA-1 are not recommended for new security-sensitive uses; prefer SHA-256 or SHA-512.", + "securityNoteBcrypt": "Each bcrypt output includes a random salt, so the same password yields a different string each time. This is expected. Use strong costs for production systems.", + "errors": { + "fileTooLarge": "File is larger than {maxMb} MB.", + "fileReadFailed": "Could not read this file." + } + }, + "EncryptionPlayground": { + "title": "Encryption playground", + "subtitle": "AES-GCM in the browser: encrypt to a JSON bundle with a passphrase or raw key, then decrypt locally. Educational and occasionally practical—nothing is uploaded.", + "tabEncrypt": "Encrypt", + "tabDecrypt": "Decrypt", + "keyModeLabel": "Key material", + "modePassphrase": "Passphrase (PBKDF2 + AES-GCM)", + "modeRawKey": "Raw AES key (hex or Base64)", + "aesBitsLabel": "AES key length", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Raw key encoding", + "encodingAuto": "Auto (hex if it looks hex, else Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Raw key", + "rawKeyPlaceholder": "32-byte key as 64 hex chars, or Base64 for AES-256 (length depends on AES size above).", + "rawKeyPlaceholderDecrypt": "Only needed when the bundle has keyDerivation \"none\".", + "passphraseLabel": "Passphrase", + "passphrasePlaceholder": "Passphrase used only in your browser to derive the key.", + "passphrasePlaceholderDecrypt": "Only needed when the bundle uses PBKDF2-SHA256.", + "pbkdf2IterLabel": "PBKDF2 iterations", + "pbkdf2Hint": "Clamped between 10,000 and 2,000,000. Default matches common vault-style settings.", + "plaintextLabel": "Plaintext", + "plaintextPlaceholder": "Type or paste the message to encrypt…", + "bundleInputLabel": "Encrypted bundle (JSON)", + "bundlePlaceholder": "Paste the JSON from Encrypt (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Encrypted bundle", + "plaintextOutputLabel": "Plaintext", + "encryptButton": "Encrypt", + "decryptButton": "Decrypt", + "copyBundle": "Copy JSON", + "copyPlaintext": "Copy plaintext", + "copied": "Copied", + "emptyBundleHint": "Encrypted JSON appears here after you encrypt.", + "emptyDecryptHint": "Decrypted text appears here.", + "decryptKeyHint": "Use the raw key field if the bundle says keyDerivation \"none\"; use the passphrase if it says PBKDF2-SHA256. The bundle stores salt, iterations, IV, and AES length.", + "securityNote": "Keys and plaintext never leave your browser. Prefer dedicated tools for production secrets.", + "errors": { + "emptyKey": "Enter a raw key that matches the AES size.", + "emptyPassphrase": "Enter a passphrase.", + "badKeyMaterial": "Could not read the key as hex or Base64.", + "wrongKeyLength": "Key length does not match the selected AES size (16 / 24 / 32 bytes).", + "invalidJson": "That is not valid JSON.", + "invalidBundle": "This JSON is not a supported encryption bundle.", + "decryptFailed": "Decryption failed—wrong secret, corrupted bundle, or mismatched parameters.", + "encryptFailed": "Encryption failed. Check Web Crypto support and inputs." + } + }, + "HmacGenerator": { + "title": "HMAC generator", + "subtitle": "For signing webhooks and testing API integrations. HMAC-SHA1/256/384/512 in hex or Base64—computed locally in your browser.", + "secretLabel": "Secret key", + "messageLabel": "Message / payload", + "placeholderSecret": "Paste your signing secret…", + "placeholderMessage": "Paste the raw body or string to authenticate (often the exact webhook body bytes).", + "algorithmLabel": "HMAC algorithm", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Output format", + "formatHex": "Hex (lowercase)", + "formatBase64": "Base64", + "outputLabel": "Signature", + "copyOutput": "Copy signature", + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch", + "copied": "Copied", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8)", + "emptyHint": "Enter a secret key to compute HMAC.", + "securityNote": "Never share your signing secret. This page does not upload data—everything runs in your browser.", + "errors": { + "cryptoFailed": "Could not compute HMAC. Check your browser supports Web Crypto and try again.", + "invalidKey": "Secret key isn't valid for the selected encoding." + } + }, + "TotpGenerator": { + "title": "TOTP / 2FA code generator", + "subtitle": "Paste an authenticator Base32 secret and get the current six-digit RFC 6238 code (SHA-1, 30 s). Runs locally in your browser—useful when testing sign-in or MFA flows.", + "secretLabel": "Secret (Base32)", + "placeholderSecret": "Paste the secret key (letters A–Z and digits 2–7, as shown when you set up an authenticator app)…", + "codeLabel": "Current code", + "copyCode": "Copy code", + "copied": "Copied", + "nextIn": "Refreshes in {seconds} s", + "standardHint": "30-second time step, 6 digits, HMAC-SHA-1 (Google Authenticator–compatible).", + "digitsLabel": "Digits", + "periodLabel": "Period", + "algorithmLabel": "Algorithm", + "periodSeconds": "{seconds} s", + "emptyHint": "Paste a secret to generate a six-digit code.", + "invalidHint": "Fix the secret to see the code.", + "securityNote": "Treat this like a password. The secret never leaves your browser; nothing is uploaded.", + "importLabel": "Import from otpauth:// URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Import", + "qrSectionTitle": "Provisioning QR code", + "qrHint": "Scan with an authenticator app (Google Authenticator, 1Password, Aegis…) to add this secret. The QR encodes the secret — treat it like a password.", + "accountLabel": "Account", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Issuer (optional)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Enter a valid secret to generate a QR code.", + "errors": { + "invalidSecret": "That is not a valid Base32 secret.", + "cryptoFailed": "Could not compute TOTP. Check that Web Crypto is available and try again.", + "invalidUri": "That is not a valid otpauth:// TOTP URI.", + "unsupportedUri": "This URI uses values the tool does not support (digits must be 6 or 8; period 15, 30, or 60 s)." + } + }, + "NumberBaseConverter": { + "title": "Number base converter", + "subtitle": "Convert integers between bases 2–36. Supports optional 0x / 0b / 0o prefixes for hex, binary, and octal. Everything runs locally in your browser.", + "inputBaseLabel": "From base", + "outputBaseLabel": "To base", + "inputLabel": "Input number", + "placeholderInput": "Enter a whole number in the selected input base…", + "outputLabel": "Converted value", + "copyOutput": "Copy result", + "copied": "Copied", + "swap": "Swap bases", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8 input length)", + "emptyHint": "Enter a number to convert.", + "note": "Digits beyond 9 use letters A–Z (case-insensitive). Spaces and underscores are ignored.", + "errors": { + "invalidNumber": "That value is not valid for the selected input base." + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum Generator", + "subtitle": "Generate placeholder Latin text for your layouts and mockups.", + "outputType": "Output type", + "paragraphs": "Paragraphs", + "sentences": "Sentences", + "words": "Words", + "bulletList": "Bullet list", + "count": "Count", + "htmlMarkup": "HTML markup", + "htmlMarkupHint": "Wrap output in paragraphs, or use a list for bullet mode", + "generate": "Generate", + "output": "Output", + "download": "Download", + "copy": "Copy", + "startWith": "Start with 'Lorem ipsum...'", + "outputPlaceholder": "Output will appear here..." + }, + "ColorPicker": { + "title": "Color picker and converter", + "subtitle": "Convert HEX, RGB, and HSL; build harmonic palettes. All processing stays in your browser.", + "nativePicker": "Native picker", + "chooseColor": "Choose color", + "eyeDropper": "Eye dropper", + "invalidHex": "Invalid HEX color.", + "cssValues": "CSS values", + "palettes": "Palettes", + "copied": "Copied", + "clickToCopy": "Click to copy", + "copyAllHex": "Copy all HEX", + "copy": "Copy", + "copyColor": "Copy {color}", + "paletteShades": "Shades", + "paletteComplementary": "Complementary", + "paletteTriadic": "Triadic", + "paletteAnalogous": "Analogous", + "paletteSplit": "Split comp.", + "paletteSquare": "Square" + }, + "ContrastChecker": { + "title": "Contrast checker", + "subtitle": "Compute WCAG 2.1 contrast ratio between foreground and background. Check AA and AAA for normal and large text. Everything runs in your browser.", + "foreground": "Text / foreground", + "background": "Background", + "hexPlaceholder": "#RRGGBB", + "swap": "Swap colors", + "chooseForeground": "Pick foreground color", + "chooseBackground": "Pick background color", + "eyeDropper": "Eye dropper", + "ratio": "Contrast ratio", + "copyRatio": "Copy ratio", + "copied": "Copied", + "previewHeading": "Preview", + "previewBody": "The quick brown fox jumps over the lazy dog. PACK MY BOX WITH FIVE DOZEN LIQUOR JUGS.", + "previewCaption": "Sample body text at default size.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — normal text (4.5:1)", + "levelAALarge": "AA — large text (3:1)", + "levelAAANormal": "AAA — normal text (7:1)", + "levelAAALarge": "AAA — large text (4.5:1)", + "pass": "Pass", + "fail": "Fail", + "invalidHex": "Invalid HEX color.", + "largeTextHint": "Large text: 18pt+ regular, or 14pt+ bold (WCAG definitions)." + }, + "DiffChecker": { + "title": "Diff checker", + "subtitle": "Side-by-side line diff of two texts. Everything stays in your browser.", + "rowsCount": "{count} rows", + "truncatedWarning": "Showing first {max} rows.", + "limitExceeded": "Each side is limited to {max} characters.", + "clearBoth": "Clear both", + "original": "Original", + "modified": "Modified", + "originalPlaceholder": "Paste original text…", + "modifiedPlaceholder": "Paste modified text…", + "comparisonLabel": "Side-by-side comparison", + "emptyState": "Enter text in both panels to compare." + }, + "ImageToBase64": { + "title": "Image to Base64", + "subtitle": "Drop an image, get a data URI or raw Base64 string. Everything stays in your browser.", + "upload": "Upload Image", + "clear": "Clear", + "modes": { + "dataUri": "Data URI", + "rawString": "Raw Base64" + }, + "panels": { + "imageInput": "Image Input", + "output": "Base64 Output" + }, + "dropHere": "Drop image here", + "charCount": "{count} chars", + "placeholders": { + "uploadImage": "Drag and drop an image or click upload to start." + }, + "fileTooLarge": "File is too large. Processing might freeze your browser.", + "copyTitle": "Copy to clipboard", + "download": { + "title": "Download text", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Image Compressor", + "subtitle": "Shrink JPEG, PNG, or WebP output locally with a quality control — nothing is uploaded.", + "upload": "Upload image", + "clear": "Clear", + "dropHere": "Drop image here", + "placeholders": { + "uploadImage": "Drag and drop an image, or click to choose a file." + }, + "fileTooLarge": "File is too large (max {maxMb} MB).", + "unsupportedFormat": "This file type is not supported. Use JPEG, PNG, WebP, GIF, AVIF, or BMP.", + "compressFailed": "Could not compress this image. Try another file or format.", + "panels": { + "original": "Original", + "compressed": "Compressed preview" + }, + "outputFormat": "Output format", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Quality", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG is lossless in the browser; the quality slider applies to JPEG and WebP only.", + "sizes": { + "original": "Original: {size}", + "compressed": "Compressed: {size}", + "saved": "{percent}% smaller" + }, + "largerWarning": "Output is larger than the input — try a different format or quality.", + "download": "Download", + "downloadFilename": "compressed-image", + "processing": "Compressing…", + "emptyCompressed": "Compressed preview appears here." + }, + "CssGradientBuilder": { + "title": "CSS Gradient Builder", + "subtitle": "Visual CSS gradient builder with angle control, color stops, and one-click CSS copy.", + "preview": "Gradient Preview", + "settings": "Settings", + "type": "Type", + "linear": "Linear", + "radial": "Radial", + "angle": "Angle", + "stops": "Color Stops", + "color": "Color", + "position": "Position (%)", + "addStop": "Add Stop", + "deleteStop": "Delete Stop", + "output": "CSS Output", + "copy": "Copy CSS", + "copied": "Copied!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Generator", + "subtitle": "Generate merged .gitignore files instantly for a specific tech stack or combination.", + "searchPlaceholder": "Search stacks (e.g. Node, Python, macOS)...", + "noResults": "No stacks found.", + "selectedStacks": "Selected Stacks", + "output": "Preview Output", + "copy": "Copy to Clipboard", + "copied": "Copied!", + "download": "Download .gitignore", + "downloading": "Downloading...", + "errorFetching": "Failed to fetch top-tal list.", + "errorGenerating": "Failed to generate .gitignore from selected stacks.", + "generating": "Generating...", + "emptyState": "Select one or more tech stacks to generate your .gitignore." + }, + "SqlClient": { + "page": { + "title": "SQL Client", + "dialogTitle": "SQL Connections", + "mobileSheetTitle": "SQL Explorer", + "newTabLabel": "New query tab", + "vaultLockedTitle": "Vault Locked", + "vaultLockedDesc": "Unlock your vault to access saved connections.", + "heroTitle": "SQL Client", + "heroDesc": "Connect to PostgreSQL, MySQL, or MariaDB databases. Credentials are encrypted in your browser and never stored in plaintext.", + "heroAddConnection": "Add Connection", + "selectTableTitle": "Select a table or open a query tab", + "selectTableDesc": "Click a table in the sidebar, or use ⌘↩ to run queries.", + "toastLoadFail": "Failed to load connections." + }, + "connection": { + "titleNew": "New SQL Connection", + "titleEdit": "Edit Connection", + "labelDbType": "Database Type", + "labelName": "Connection Name", + "placeholderName": "My Production DB", + "labelHostPort": "Host & Port", + "labelDatabase": "Database", + "placeholderDatabase": "mydb", + "labelUsername": "Username", + "labelPassword": "Password", + "labelSsl": "Enable SSL / TLS", + "sslDesc": "Encrypt the connection to the server", + "securityNote": "Credentials are AES-256 encrypted in your browser before storage", + "btnTest": "Test Connection", + "btnSave": "Save & Connect", + "btnUpdate": "Update", + "btnCancelEdit": "Cancel editing", + "savedConnections": "Saved Connections", + "emptySaved": "Saved connections will appear here", + "confirmDelete": "Delete this connection?", + "toastFillFields": "Fill in host and database before testing.", + "toastConnectOk": "Connected! {version}", + "toastConnectFail": "Connection failed: {message}", + "toastUnlockVault": "Unlock your vault first.", + "toastFieldsRequired": "Host and database are required.", + "toastUpdated": "Connection updated.", + "toastSaved": "Connection saved.", + "toastSaveFail": "Failed to save: {message}", + "toastDeleted": "Connection deleted.", + "toastDeleteFail": "Failed to delete connection." + }, + "sidebar": { + "title": "SQL Explorer", + "tooltipAddConnection": "Add connection", + "tooltipRefreshSchema": "Refresh schema", + "noConnections": "No connections saved yet", + "toastSchemaFail": "Failed to load schema: {message}" + }, + "editor": { + "btnRun": "Run", + "executing": "Executing query…", + "emptyTitle": "Run a query to see results", + "emptyHint": "Press ⌘↩ or click Run", + "toastNoConnection": "No active connection." + }, + "results": { + "filterPlaceholder": "Filter results…", + "rowCount": "{count} {count, plural, one {row} other {rows}}", + "rowCountFiltered": "{filtered} / {total} rows", + "exportCsv": "CSV", + "noRows": "No rows returned" + } + }, + "MarkdownPreview": { + "title": "Markdown Preview & HTML", + "subtitle": "Live Markdown renderer with HTML export and HTML → Markdown conversion. Everything runs in your browser.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown input", + "htmlInputLabel": "HTML input", + "previewLabel": "Preview", + "htmlOutputLabel": "HTML source", + "mdOutputLabel": "Markdown output", + "inputPlaceholder": "# Hello\\n\\nType your **markdown** here…", + "htmlInputPlaceholder": "Paste your HTML here…", + "copyHtml": "Copy HTML", + "copyMarkdown": "Copy Markdown", + "exportHtml": "Export HTML", + "copied": "Copied", + "charCount": "{count, plural, one {# character} other {# characters}}", + "emptyHint": "Start typing Markdown to see the live preview and HTML output.", + "emptyHintHtmlToMd": "Paste HTML to convert it to Markdown.", + "securityNote": "Everything runs locally in your browser. Nothing is uploaded." + }, + "FormatConverter": { + "title": "Format Converter", + "subtitle": "Convert between JSON, YAML, TOML, and XML in any direction. All 12 combinations. Everything runs in your browser.", + "fromLabel": "From", + "toLabel": "To", + "swap": "Swap", + "swapTitle": "Swap formats and use output as input", + "copy": "Copy", + "copied": "Copied", + "charCount": "{count, plural, one {# character} other {# characters}}", + "emptyHint": "Paste or type your input on the left to see the converted output.", + "localNote": "Everything runs locally in your browser. Nothing is uploaded." + }, + "MimeTypeLookup": { + "title": "MIME type lookup", + "subtitle": "Look up the MIME type (media type) for a file extension or full filename. Everything runs locally in your browser.", + "inputLabel": "Extension or filename", + "placeholder": "e.g. .png, package.json, photo.jpeg", + "hint": "Tip: paste either an extension (with or without a dot) or a full filename.", + "clear": "Clear", + "exampleFilename": "Use example filename", + "exampleExtension": "Use example extension", + "resultLabel": "Result", + "normalizedLabel": "Normalized input:", + "mimeTypeLabel": "MIME type", + "contentTypeLabel": "Content-Type header", + "charsetLabel": "Charset", + "copyMimeType": "Copy MIME type", + "copy": "Copy", + "copied": "Copied", + "emptyHint": "Enter an extension or filename to look up its MIME type.", + "notFound": "No MIME type found for that input.", + "localNote": "Everything runs locally in your browser. Nothing is uploaded." + }, + "MockDataGenerator": { + "title": "Mock Data Generator", + "subtitle": "Define columns, pick data types, and export fixtures for seeds, Postman, or unit tests. Up to {max} rows, all in your browser — nothing is uploaded.", + "mockarooLink": "Mockaroo-style workflow", + "rowsLabel": "Rows", + "rowsHint": "Maximum {max} rows per generation to keep the tab responsive.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "SQL INSERT", + "xml": "XML" + }, + "tableNameLabel": "SQL table name", + "presetsLabel": "Presets", + "presetPlaceholder": "Load preset…", + "presetDefault": "Starter (user)", + "presetUsers": "Users & roles", + "presetProducts": "E-commerce SKUs", + "presetApiLog": "HTTP request log", + "generate": "Generate", + "warningDuplicateNames": "Duplicate column names: later columns overwrite earlier ones in JSON objects. Rename fields for distinct keys.", + "schemaHeading": "Schema", + "addField": "Add column", + "removeField": "Remove column", + "duplicateField": "Duplicate column", + "colName": "Name", + "colType": "Type", + "colOptions": "Options", + "colBlank": "Blank %", + "optMin": "min", + "optMax": "max", + "optDecimals": "dec", + "optDateFrom": "from date", + "optDateTo": "to date", + "optValues": "a, b, c", + "optStep": "step", + "optRowBase": "base", + "output": "Output", + "outputPlaceholder": "Click Generate to build data from your schema…", + "copy": "Copy", + "download": "Download", + "copied": "Copied", + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed", + "groups": { + "personal": "Personal", + "location": "Location", + "internetApi": "Internet & API", + "numbersIds": "Numbers & IDs", + "dateTime": "Date & time", + "textDesign": "Text & design", + "custom": "Custom" + }, + "fieldTypes": { + "first_name": "First name", + "last_name": "Last name", + "full_name": "Full name", + "email": "Email", + "username": "Username", + "password": "Password", + "phone": "Phone", + "company": "Company", + "job_title": "Job title", + "avatar_url": "Avatar URL", + "city": "City", + "country": "Country", + "country_code": "Country code (ISO)", + "street_address": "Street address", + "zip_code": "ZIP / postal code", + "latitude": "Latitude", + "longitude": "Longitude", + "url": "URL", + "domain": "Domain", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Integer", + "float": "Float", + "boolean": "Boolean", + "uuid": "UUID", + "credit_card": "Credit card (test)", + "semver": "SemVer", + "row_index": "Row index", + "sequence": "Sequence", + "date": "Date", + "datetime": "Date & time", + "unix_timestamp": "Unix timestamp", + "timezone": "Timezone", + "word": "Word", + "sentence": "Sentence", + "paragraph": "Paragraph", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Currency code", + "language_code": "Language tag", + "custom_list": "Custom list" + } + }, + "UserAgentParser": { + "title": "User-Agent parser", + "subtitle": "Paste a User-Agent (UA) string and get a browser, OS, engine, device, and CPU breakdown. Everything runs locally in your browser.", + "inputLabel": "User-Agent string", + "placeholder": "Paste your User-Agent here…", + "hint": "Tip: you can copy a UA from request headers, logs, or your browser devtools.", + "clear": "Clear", + "exampleDesktop": "Use desktop example", + "exampleMobile": "Use mobile example", + "resultLabel": "Breakdown", + "browserLabel": "Browser", + "osLabel": "Operating system", + "engineLabel": "Engine", + "deviceTypeLabel": "Device type", + "deviceVendorLabel": "Device vendor", + "deviceModelLabel": "Device model", + "cpuArchLabel": "CPU architecture", + "copyJson": "Copy JSON", + "copy": "Copy", + "copied": "Copied", + "emptyHint": "Paste a User-Agent string to see the breakdown.", + "localNote": "Everything runs locally in your browser. Nothing is uploaded." + }, + "SvgOptimizer": { + "title": "SVG Optimizer", + "subtitle": "Paste SVG, minify with SVGO in your browser, and compare UTF-8 size before and after.", + "inputLabel": "Input", + "outputLabel": "Optimized", + "placeholder": "Paste an snippet or full file…", + "outputPlaceholder": "Optimized SVG appears here…", + "chars": "{count} chars • {bytes} bytes (UTF-8)", + "preview": "Preview", + "localNote": "All processing runs locally in your browser. Nothing is uploaded.", + "stats": { + "title": "Size (UTF-8)", + "before": "Before", + "after": "After", + "savedBytes": "{count} bytes saved ({percent}%)", + "noChange": "No reduction — already minimal or empty output." + }, + "actions": { + "clear": "Clear", + "sample": "Load sample", + "copy": "Copy", + "copied": "Copied", + "download": "Download" + }, + "errors": { + "optimizeFailed": "Could not optimize this SVG." + } + }, + "UnitConverter": { + "title": "Unit Converter", + "subtitle": "Convert between 323 units across 43 categories", + "category": "Category", + "from": "From", + "to": "To", + "value": "Value", + "result": "Result", + "swapUnits": "Swap units", + "placeholder": "Enter value", + "copyResult": "Copy result", + "searchCategory": "Search category...", + "selectCategory": "Select a category", + "noResults": "No category found." + }, + "DockerComposeGenerator": { + "title": "Docker Compose Generator", + "subtitle": "Choose popular databases, caches, queues, proxies, and observability stacks — copy a working docker-compose.yml for local development.", + "searchPlaceholder": "Search (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Services", + "selectedCount": "{count, plural, one {# service} other {# services}}", + "outputTitle": "docker-compose.yml", + "copy": "Copy", + "copied": "Copied!", + "download": "Download", + "emptyState": "Select one or more services to generate compose YAML.", + "hintPorts": "Default host ports are spaced to reduce clashes; change them if Docker reports a bind conflict. Replace demo passwords before any shared environment.", + "categories": { + "databases": "Databases & stores", + "cache": "Cache & in-memory", + "queues": "Queues & messaging", + "search": "Search & logs", + "storage": "Object storage", + "proxy": "Reverse proxies & ingress", + "observability": "Metrics & tracing", + "mail": "Email (local dev)", + "admin": "Admin & BI UIs" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Clear" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (Redis-compatible)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (Kafka-compatible)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (S3-compatible)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (SMTP capture)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (dev server)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "stages completed", + "time": "Time", + "best": "Best", + "difficulty": { + "easy": "Easy", + "medium": "Medium", + "hard": "Hard" + }, + "stage": "Stage", + "solved": "Solved!", + "score": "Score", + "nextStage": "Next stage", + "replay": "Replay", + "erase": "Erase", + "howToPlay": "Click a cell, then tap a number or press 1–9. Backspace or Delete clears the cell. Bold numbers are clues. Fill the grid so every row, column, and 3×3 box contains 1–9 without repeats.", + "pencil": "Pencil", + "undo": "Undo", + "hint": "Hint", + "hintsUsed": "Hints used", + "mistakes": "Mistakes", + "failed": "Failed!", + "mistakeMode": "3-Mistake Mode" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis Connections", + "heroTitle": "Redis Commander", + "heroDesc": "Connect to Redis instances. Browse keys, edit values, run commands. Credentials are encrypted in your browser and never stored in plaintext.", + "heroAddConnection": "Add Connection", + "vaultLockedTitle": "Vault Locked", + "vaultLockedDesc": "Unlock your vault to access saved connections." + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Slide tiles and merge them to reach 2048", + "score": "Score", + "best": "Best", + "newGame": "New Game", + "howToPlay": "Use arrow keys (or swipe on mobile) to move tiles. Tiles with the same number merge. Reach 2048 to win!", + "youWin": "You reached 2048!", + "keepGoing": "Keep Going", + "gameOver": "Game Over", + "tryAgain": "Try Again", + "gameOverMessage": "No more moves left.", + "undo": "Undo" + }, + "Snake": { + "title": "Snake", + "subtitle": "Eat food, grow, avoid walls and yourself", + "score": "Score", + "best": "Best", + "startPrompt": "Press any arrow key", + "startButton": "Start Game", + "gameOver": "Game Over", + "playAgain": "Play Again", + "howToPlay": "Use arrow keys or WASD to move. Eat food to grow. Speed increases every 50 points.", + "diffEasy": "Easy", + "diffNormal": "Normal", + "diffHard": "Hard", + "diffInfinite": "Infinite", + "orTap": "or tap below", + "howToPlayInfinite": "Walls wrap around — no wall deaths. Eat food to grow. Speed increases every 50 points.", + "shieldActive": "Shield Active", + "speedBoost": "Speed Boost", + "powerSpeed": "Speed x2 (10 ticks)", + "powerShield": "Survive one death", + "powerShrink": "Shortens tail" + }, + "Minesweeper": { + "title": "Minesweeper", + "subtitle": "Left click reveal · Right click flag", + "mines": "Mines", + "newGame": "New Game", + "win": "You Win!", + "loss": "Boom!", + "playAgain": "Play Again", + "tryAgain": "Try Again", + "startPrompt": "Click any cell to start", + "difficulty": { + "beginner": "Beginner", + "intermediate": "Intermediate", + "expert": "Expert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Arrow keys · Space = hard drop · P = pause", + "next": "Next", + "score": "Score", + "best": "Best", + "lines": "Lines", + "level": "Level", + "pause": "Pause", + "resume": "Resume", + "start": "Start", + "restart": "Restart", + "paused": "Paused", + "gameOver": "Game Over" + }, + "SshKeyGenerator": { + "title": "SSH / RSA Key Generator", + "subtitle": "Generate Ed25519 or RSA key pairs in your browser. Download private and public keys in OpenSSH and PEM formats. Nothing is uploaded.", + "keyTypeLabel": "Key Type", + "recommended": "recommended", + "ed25519Desc": "Fast, small, modern. Supported by GitHub, GitLab, and all major SSH servers.", + "rsa2048Desc": "Widely compatible. Safe for most uses.", + "rsa4096Desc": "Maximum compatibility and strength for legacy systems.", + "commentLabel": "Comment (optional)", + "commentHint": "Appended to the OpenSSH public key line.", + "generateBtn": "Generate Key Pair", + "generating": "Generating…", + "generatingLong": "Generating (may take a moment)…", + "copy": "Copy", + "download": "Download", + "privateKeyLabel": "Private Key (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Public Key (OpenSSH format)", + "publicKeySpkiLabel": "Public Key (SPKI PEM)", + "browserNote": "All keys generated in your browser. Nothing is sent to any server.", + "errors": { + "generationFailed": "Key generation failed" + } + }, + "DnsLookup": { + "title": "DNS Lookup", + "subtitle": "Query DNS records for any domain — A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR.", + "placeholder": "Enter domain (e.g. github.com)", + "lookupBtn": "Lookup", + "looking": "Looking up…", + "refreshBtn": "Refresh", + "recordTypesLabel": "Record types", + "allBtn": "All", + "tryLabel": "Try:", + "errors": { + "lookupFailed": "DNS lookup failed" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt Generator & Checker", + "subtitle": "Hash passwords with bcrypt and verify hashes — everything stays in your browser", + "tabs": { + "hash": "Hash", + "verify": "Verify" + }, + "showPassword": "Show password", + "hidePassword": "Hide password", + "copy": "Copy", + "copied": "Copied", + "hash": { + "passwordLabel": "Password", + "passwordPlaceholder": "Enter a password to hash…", + "costLabel": "Cost factor", + "costHint": "Higher cost doubles the work per step. 10-12 is a common choice for production.", + "slowWarning": "Cost factors of 13 and above can take several seconds to compute.", + "generate": "Generate hash", + "generating": "Hashing…", + "resultLabel": "Bcrypt hash", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Hash anatomy", + "algorithm": "algorithm", + "cost": "cost", + "salt": "salt (22 chars)", + "hash": "hash (31 chars)" + } + }, + "verify": { + "passwordLabel": "Password", + "passwordPlaceholder": "Enter the password to check…", + "hashLabel": "Bcrypt hash", + "hashPlaceholder": "$2b$10$…", + "idle": "Enter a password and a bcrypt hash to compare them.", + "invalidHash": "This does not look like a valid bcrypt hash.", + "checking": "Comparing…", + "match": "Match — the password produces this hash.", + "noMatch": "No match — the password does not produce this hash." + } + }, + "ChmodCalculator": { + "title": "Chmod Calculator", + "subtitle": "Convert Unix file permissions between checkboxes, octal, and symbolic notation", + "grid": { + "title": "Permissions", + "read": "Read", + "write": "Write", + "execute": "Execute", + "owner": "Owner", + "group": "Group", + "others": "Others" + }, + "special": { + "title": "Special bits", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Octal", + "placeholder": "755 or 4755", + "invalid": "Enter 3 or 4 octal digits (0-7)." + }, + "symbolic": { + "label": "Symbolic", + "placeholder": "rwxr-xr-x", + "invalid": "Enter 9 characters like rwxr-xr-x (s/S, t/T allowed)." + }, + "output": { + "title": "Command", + "copy": "Copy", + "copied": "Copied" + }, + "explain": { + "line": "{cls} can {perms}.", + "none": "{cls} has no permissions.", + "setuid": "Setuid: the file runs with its owner's privileges.", + "setgid": "Setgid: the file runs with its group's privileges (new files in a directory inherit its group).", + "sticky": "Sticky: only a file's owner can delete or rename it inside this directory." + }, + "presets": { + "title": "Common presets", + "octal": "Octal", + "permissions": "Permissions", + "description": "Typical use", + "p644": "Regular files: owner writes, everyone reads.", + "p755": "Executables and directories: everyone can read and enter.", + "p600": "Private files: only the owner reads and writes.", + "p700": "Private executables and directories: owner only.", + "p664": "Group-writable files.", + "p775": "Group-writable directories and executables.", + "p400": "Read-only for the owner (e.g. SSH private keys)." + } + }, + "CodeScreenshot": { + "title": "Code Screenshot", + "subtitle": "Turn code into beautiful, shareable images", + "languageLabel": "Language", + "autoDetect": "Auto-detect", + "backgroundLabel": "Background", + "paddingLabel": "Padding", + "filenameLabel": "Filename", + "filenamePlaceholder": "example.ts", + "windowLabel": "Window dots", + "themeLabel": "Window theme", + "themeDark": "Dark", + "themeLight": "Light", + "scaleLabel": "Export scale", + "copyImage": "Copy image", + "copied": "Copied", + "exportPng": "Export PNG", + "inputLabel": "Code", + "detected": "Detected: {language}", + "clear": "Clear", + "placeholder": "Paste your code here…", + "previewLabel": "Preview", + "emptyPreview": "Your code preview will appear here", + "exportFailed": "Failed to export image", + "copyUnsupported": "Copying images is not supported in this browser", + "copyFailed": "Failed to copy image", + "backgrounds": { + "ocean": "Ocean", + "sunset": "Sunset", + "candy": "Candy", + "forest": "Forest", + "midnight": "Midnight", + "gold": "Gold", + "aurora": "Aurora", + "slate": "Slate", + "transparent": "Transparent" + } + }, + "CssGenerators": { + "title": "CSS Generators", + "subtitle": "Box-shadow, border-radius, and fluid clamp() — copy the CSS.", + "preview": "Preview", + "output": "CSS", + "copy": "Copy", + "copied": "Copied", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Fluid clamp()" + }, + "boxShadow": { + "presets": "Presets", + "layer": "Layer", + "addLayer": "Add layer", + "removeLayer": "Remove layer", + "x": "X", + "y": "Y", + "blur": "Blur", + "spread": "Spread", + "color": "Color", + "alpha": "Alpha", + "inset": "Inset", + "presetNames": { + "subtleCard": "Subtle card", + "elevated": "Elevated", + "hardOffset": "Hard offset", + "neonGlow": "Neon glow", + "inner": "Inner", + "layeredSmooth": "Layered smooth" + } + }, + "borderRadius": { + "modeLabel": "Mode", + "modes": { + "simple": "Per-corner", + "blob": "Organic blob" + }, + "unit": "Unit", + "linkCorners": "Link corners", + "randomize": "Randomize", + "horizontal": "Horizontal radii", + "vertical": "Vertical radii", + "corners": { + "tl": "Top left", + "tr": "Top right", + "br": "Bottom right", + "bl": "Bottom left" + } + }, + "clamp": { + "minFont": "Min font (px)", + "maxFont": "Max font (px)", + "minViewport": "Min viewport (px)", + "maxViewport": "Max viewport (px)", + "rootFont": "Root font (px)", + "viewportWidth": "Simulated viewport", + "computedSize": "Computed size", + "slope": "Slope", + "intercept": "Intercept", + "previewText": "The quick brown fox", + "invalid": "Enter valid, differing viewport bounds." + } + }, + "ExifViewer": { + "title": "EXIF Viewer & Remover", + "subtitle": "Inspect photo metadata and strip it in your browser", + "dropTitle": "Drop an image here", + "dropHint": "or click to browse — your image never leaves your device", + "browse": "Choose image", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Reading metadata…", + "parseFailed": "Failed to read this image", + "clear": "Remove image", + "gpsWarning": "Contains GPS location", + "openMap": "View on OpenStreetMap", + "noMetadata": "No embedded metadata was found in this image.", + "facts": { + "name": "Name", + "type": "Type", + "dimensions": "Dimensions", + "size": "Size" + }, + "groups": { + "camera": "Camera", + "exposure": "Exposure", + "timestamps": "Timestamps", + "gps": "GPS Location", + "software": "Software & Other" + }, + "gpsCard": { + "latitude": "Latitude", + "longitude": "Longitude", + "altitude": "Altitude" + }, + "raw": { + "title": "All metadata", + "copyJson": "Copy JSON", + "copied": "Copied" + }, + "remove": { + "title": "Remove metadata", + "jpegDescription": "Losslessly remove EXIF, XMP, IPTC and comments without re-compressing the image.", + "reencodeDescription": "Re-encode the image to strip embedded metadata. This re-compresses the pixels.", + "keepIcc": "Keep color profile (ICC)", + "stripButton": "Strip metadata", + "reencodeButton": "Re-encode & strip", + "unsupported": "Metadata removal is not supported for this file type.", + "failed": "Failed to remove metadata", + "before": "Before", + "after": "After", + "download": "Download cleaned image" + } + }, + "FaviconGenerator": { + "title": "Favicon Generator", + "subtitle": "Create favicons and app icons from one image", + "dropTitle": "Drop an image here", + "dropHint": "PNG, JPEG or SVG — click to browse", + "browse": "Choose image", + "clear": "Remove image", + "backgroundLabel": "Background", + "transparent": "Transparent", + "solidColor": "Solid", + "cornerLabel": "Corner shape", + "cornerNone": "Square", + "cornerRounded": "Rounded", + "cornerCircle": "Circle", + "maskPaddingLabel": "Maskable padding", + "maskable": "maskable", + "previewLabel": "Preview", + "copy": "Copy", + "headSnippet": "HTML head snippet", + "downloadIco": "Download favicon.ico", + "downloadZip": "Download all (.zip)" + }, + "JsonDiff": { + "title": "JSON Diff", + "subtitle": "Semantic, structure-aware comparison of two JSON documents — key order and formatting ignored", + "options": { + "ignoreArrayOrder": "Ignore array order" + }, + "loadBothSamples": "Load samples", + "sample": "Sample", + "format": "Format", + "clear": "Clear", + "formatError": "Cannot format: input is not valid JSON", + "placeholder": "Paste or type JSON here…", + "panels": { + "left": "Original (left)", + "right": "Modified (right)", + "results": "Differences" + }, + "summary": { + "added": "{count} added", + "removed": "{count} removed", + "changed": "{count} changed" + }, + "types": { + "added": "Added", + "removed": "Removed", + "changed": "Changed" + }, + "copyChanges": "Copy as JSON", + "copied": "Copied", + "empty": "Paste JSON into both panels to see their differences.", + "identical": "The two documents are semantically identical." + }, + "JsonpathPlayground": { + "title": "JSONPath Playground", + "subtitle": "Query JSON documents with JSONPath or JMESPath and see live results", + "engineLabel": "Engine", + "queryLabel": "Query", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Cheatsheet", + "cheats": { + "jpAllTitles": "All book titles", + "jpAllAuthors": "Every author, anywhere in the document", + "jpFirstBook": "First book", + "jpLastBook": "Last book", + "jpSlice": "First two books (slice)", + "jpFilterPrice": "Books cheaper than 40", + "jpFilterInStock": "Titles of in-stock books", + "jpRecursive": "All values, recursively", + "jmAllTitles": "All book titles", + "jmFirstAuthor": "Author of the first book", + "jmLastBook": "Last book", + "jmSlice": "First two books (slice)", + "jmFilterPrice": "Titles of books cheaper than 40", + "jmCount": "Count in-stock books", + "jmProjection": "Reshape each book into a new object", + "jmSort": "Titles sorted by price" + }, + "panels": { + "input": "JSON input", + "results": "Results" + }, + "sample": "Sample", + "format": "Format", + "clear": "Clear", + "formatError": "Cannot format: input is not valid JSON", + "placeholder": "Paste or type JSON here…", + "outputPlaceholder": "Matches will appear here…", + "matchCount": "{count, plural, one {# match} other {# matches}}", + "copy": "Copy", + "copied": "Copied" + }, + "KeycodeInspector": { + "title": "Keycode Inspector", + "subtitle": "Press any key to inspect its JavaScript keyboard event", + "capture": { + "prompt": "Press any key…", + "ready": "Listening — press any key on your keyboard", + "clickToFocus": "Click here to focus, then press any key" + }, + "hero": { + "spaceKey": "(Space)", + "keyLabel": "event.key", + "repeat": "repeat" + }, + "copyJson": "Copy JSON", + "copiedJson": "Event JSON copied to clipboard", + "deprecated": "deprecated", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standard", + "left": "Left", + "right": "Right", + "numpad": "Numpad", + "unknown": "Unknown" + }, + "history": { + "title": "History", + "clear": "Clear history", + "empty": "Key presses will appear here." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown Table Generator", + "subtitle": "Edit a grid, paste from a spreadsheet, and copy a pretty-aligned Markdown or HTML table", + "addRow": "Add row", + "addColumn": "Add column", + "importLabel": "Import", + "importPlaceholder": "Paste TSV (from Excel / Google Sheets) or CSV here…", + "importButton": "Import into grid", + "pasteClipboard": "Paste from clipboard", + "importEmpty": "Nothing to import — paste some rows first", + "clipboardError": "Could not read from the clipboard", + "clear": "Clear", + "panels": { + "editor": "Table editor", + "output": "Output" + }, + "align": { + "left": "Align left", + "center": "Align center", + "right": "Align right" + }, + "headerPlaceholder": "Header", + "cellPlaceholder": "Cell", + "removeRow": "Remove row", + "removeColumn": "Remove column", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Copy", + "copied": "Copied" + }, + "TimezoneConverter": { + "title": "Timezone Converter", + "subtitle": "Convert one moment across the world for meeting planning.", + "dateTimeLabel": "Date & time", + "now": "Now", + "sourceZoneLabel": "Source timezone", + "selectZone": "Select zone", + "searchZone": "Search timezones...", + "noZone": "No timezone found.", + "scrubLabel": "Shift time", + "reset": "Reset", + "targetsLabel": "Target zones", + "addZone": "Add zone", + "remove": "Remove", + "localBadge": "Local", + "invalidDate": "Enter a valid date and time.", + "bands": { + "work": "Working hours (09:00–18:00)", + "edge": "Edge hours (early / evening)", + "night": "Night" + } + }, + "TokenCounter": { + "title": "LLM Token Counter", + "subtitle": "Count tokens, visualize chunks, and estimate API cost.", + "inputLabel": "Text", + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste text, code, or JSON to count tokens...", + "encoding": "Encoding", + "loading": "Loading tokenizer...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-series)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Characters", + "words": "Words", + "ratio": "Tokens/word", + "ratioSub": "approx" + }, + "claudeRow": "Claude (approximate)", + "estimate": "estimate", + "viz": { + "title": "Token visualization", + "toggle": "Show", + "hint": "Toggle to color each token as a chunk.", + "empty": "Enter text to visualize tokens.", + "truncated": "Showing the first {limit} of {total} tokens." + }, + "cost": { + "title": "Cost calculator", + "disclaimer": "editable defaults — verify current pricing", + "model": "Model", + "expectedOutput": "Expected output tokens", + "customInput": "Input $/MTok", + "customOutput": "Output $/MTok", + "inputCost": "Input cost", + "outputCost": "Output cost", + "total": "Total", + "per1000": "Per 1000 calls" + } + }, + "WebhookTester": { + "title": "Webhook Tester", + "subtitle": "Create a temporary URL and inspect incoming webhook requests live.", + "intro": { + "title": "Create a webhook URL to start capturing requests", + "hint": "You'll get an unguessable URL that records every incoming request. It expires after 24 hours." + }, + "createBtn": "Create webhook URL", + "creating": "Creating…", + "urlLabel": "Your webhook URL", + "expiresIn": "Expires in {countdown}", + "copyUrlBtn": "Copy URL", + "copied": "Copied", + "clearBtn": "Clear", + "newBinBtn": "New URL", + "requestsTitle": "Requests", + "selectPrompt": "Select a request to inspect its details.", + "expiredNotice": "This webhook URL has expired. Create a new one to keep testing.", + "base64Badge": "base64", + "truncatedBadge": "truncated", + "empty": { + "title": "Waiting for requests…", + "hint": "Send a request to your URL and it will appear here instantly. Try this:" + }, + "detail": { + "receivedAt": "Received", + "source": "Source IP", + "contentType": "Content-Type", + "query": "Query params", + "headers": "Headers", + "body": "Body", + "copyBody": "Copy body", + "emptyBody": "No body" + }, + "errors": { + "createFailed": "Failed to create webhook URL. Please try again.", + "clearFailed": "Failed to clear requests. Please try again." + } + }, + "WebsocketTester": { + "title": "WebSocket Tester", + "subtitle": "Connect to a WebSocket endpoint and send, receive, and inspect messages live", + "urlLabel": "Endpoint URL", + "protocolsLabel": "Subprotocols", + "protocolsPlaceholder": "optional, comma separated", + "connect": "Connect", + "disconnect": "Disconnect", + "status": { + "disconnected": "Disconnected", + "connecting": "Connecting", + "connected": "Connected", + "error": "Error" + }, + "messages": { + "connecting": "Connecting to {url}…", + "connected": "Connection established", + "connectedWithProtocol": "Connection established (protocol: {protocol})", + "invalidUrl": "Invalid URL — must start with ws:// or wss://", + "connectFailed": "Failed to open connection", + "socketError": "A connection error occurred", + "notConnected": "Not connected — connect first", + "binaryMessage": "Received a binary message" + }, + "log": { + "title": "Message Log", + "empty": "No messages yet. Connect and start sending.", + "copyAll": "Copy all", + "download": "Download as .txt", + "clear": "Clear log", + "prettyPrint": "Pretty-print JSON", + "showRaw": "Show raw text" + }, + "composer": { + "placeholder": "Type a message to send…", + "send": "Send", + "formatJson": "Format as JSON", + "invalidJson": "Message is not valid JSON", + "hint": "Cmd/Ctrl+Enter to send" + }, + "ping": { + "label": "Auto-ping", + "seconds": "sec", + "intervalTitle": "Ping interval in seconds", + "payloadPlaceholder": "ping payload" + } + }, + "WhoisLookup": { + "title": "Whois Lookup", + "subtitle": "Look up registration details for any domain or IP address via RDAP", + "placeholder": "example.com or 8.8.8.8", + "lookupBtn": "Lookup", + "looking": "Looking up…", + "refreshBtn": "Refresh", + "tryLabel": "Try:", + "copy": "Copy", + "copied": "Copied", + "notAvailable": "Not available", + "summary": { + "registrar": "Registrar", + "network": "Network" + }, + "dates": { + "created": "Created", + "updated": "Updated", + "expires": "Expires" + }, + "sections": { + "status": "Status", + "nameservers": "Nameservers", + "contacts": "Contacts", + "rawRdap": "Raw RDAP" + }, + "contacts": { + "roles": "Roles", + "name": "Name", + "organization": "Organization" + }, + "errors": { + "invalidTarget": "Enter a valid domain name or IP address", + "lookupFailed": "Lookup failed (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/es.json b/apps/desktop-ui/messages/es.json new file mode 100644 index 00000000..7e87ba18 --- /dev/null +++ b/apps/desktop-ui/messages/es.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Tablero", + "settings": "Configuraciones", + "toDo": "Tareas", + "notes": "Notas", + "passwordManager": "Gestor de contraseñas", + "environmentManager": "Gestor de entornos", + "bookmarks": "Marcadores", + "emailValidator": "Validador de correo", + "encryptionPlayground": "Cifrado (prácticas)", + "jwtDecoder": "Decodificador JWT", + "certificatePemDecoder": "Decodificador de certificados / PEM", + "jsonFormatter": "Formateador JSON", + "sqlFormatter": "Formateador SQL", + "graphqlFormatter": "Formateador GraphQL", + "diffChecker": "Comparador de diferencias", + "regexTester": "Probador de expresiones regulares", + "timestampConverter": "Conversor de marcas de tiempo", + "cronBuilder": "Constructor Cron", + "base64Encoder": "Codificador Base64", + "urlEncoder": "Codificador de URL", + "urlParser": "Analizador de URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Secreto / clave API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Selector de color", + "contrastChecker": "Comprobador de contraste", + "apiClient": "Cliente API", + "httpStatusCodes": "Códigos de estado HTTP", + "nosqlExplorer": "Explorador de bases de datos", + "jsonSchemaGenerator": "Generador de esquema JSON", + "imageToBase64": "Imagen a Base64", + "imageCompressor": "Compresor de imágenes", + "cssGradientBuilder": "Generador de degradado CSS", + "gitignoreGenerator": "Generador .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Fragmentos de código", + "qrCodeGenerator": "Generador de códigos QR", + "dnsLookup": "Búsqueda DNS", + "ipSubnetCalculator": "Calculadora IP / subred", + "hashGenerator": "Generador de hash", + "hmacGenerator": "Generador HMAC", + "totpGenerator": "Código TOTP / 2FA", + "markdownPreview": "Vista previa Markdown", + "numberBaseConverter": "Conversor de base", + "formatConverter": "Conversor de formatos", + "mockDataGenerator": "Generador de datos de prueba", + "svgOptimizer": "Optimizador SVG", + "unitConverter": "Convertidor de unidades", + "dockerComposeGenerator": "Generador Docker Compose", + "breakRoom": "Sala de descanso", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Formateador YAML", + "sshKeyGenerator": "Generador de claves SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Calculadora Chmod", + "codeScreenshot": "Captura de código", + "cssGenerators": "Generadores CSS", + "exifViewer": "Visor EXIF", + "faviconGenerator": "Generador de favicon", + "jsonDiff": "Diff JSON", + "jsonpathPlayground": "Playground JSONPath", + "keycodeInspector": "Inspector de keycode", + "markdownTableGenerator": "Tabla Markdown", + "timezoneConverter": "Conversor de zonas horarias", + "tokenCounter": "Contador de tokens", + "webhookTester": "Probador de webhooks", + "websocketTester": "Probador de WebSocket", + "whoisLookup": "Consulta Whois" + }, + "Help": { + "title": "Ayuda y documentación", + "subtitle": "Cómo encaja MyDevTools, qué hace cada app y cómo la seguridad y el cifrado protegen tus datos.", + "tabs": { + "overview": "Resumen", + "apps": "Apps", + "security": "Seguridad", + "tips": "Consejos" + }, + "overview": { + "whatIs": { + "title": "¿Qué es MyDevTools?", + "description": "Un espacio de trabajo para desarrolladores con inicio de sesión: un tablero y un conjunto de herramientas para tareas diarias.", + "p1": "Después de iniciar sesión con tu cuenta, la app establece una sesión con la API de MyDevTools para que las herramientas puedan cargar y guardar tus datos. La barra lateral muestra todas las apps; puedes ocultar las herramientas que no usas desde", + "settingsLink": "Configuraciones", + "p1After": ".", + "p2": "Algunas funciones usan una contraseña maestra (o clave global del cofre) para que secretos como contraseñas y cadenas de conexión se cifren en tu dispositivo antes de llegar al servidor." + }, + "quickLinks": { + "title": "Enlaces rápidos", + "dashboard": "Tablero", + "settings": "Configuraciones" + } + }, + "apps": { + "intro": "Abre cualquier herramienta desde la barra lateral o el tablero. Abajo tienes una guía corta por app.", + "open": "Abrir", + "howItWorks": "Cómo funciona", + "generic": "Usa esta herramienta desde la barra lateral para su flujo de trabajo específico." + }, + "security": { + "session": { + "title": "Inicio de sesión y sesión de API", + "description": "Identidad y cómo el navegador se comunica con tu backend.", + "items": { + "authentication": "Autenticación usa Firebase para iniciar sesión. Después, la app intercambia tu token de ID de Firebase por cookies de la API.", + "accessTokens": "Tokens de acceso son JWT firmados en el servidor (HS256). Identifican al usuario que llama a la API.", + "refreshTokens": "Tokens de refresco se guardan en cookies HttpOnly; el servidor guarda un hash SHA-256 del token, no el token en claro." + } + }, + "encryption": { + "title": "Cifrado que debes conocer", + "description": "Criptografía del lado del cliente para el cofre y datos sensibles de conexión.", + "p1": "Para el gestor de contraseñas, el gestor de entornos y las conexiones guardadas en NoSQL Explorer, la aplicación web usa la Web Crypto API del navegador:", + "items": { + "kdf": "PBKDF2 con SHA-256, 100.000 iteraciones y una sal aleatoria deriva una clave de 256 bits desde tu contraseña maestra.", + "aes": "AES-256-GCM cifra los datos; se usa un IV de 12 bytes aleatorio por cifrado.", + "scope": "La clave derivada se mantiene en el navegador (no extraíble). El servidor recibe cifrado e IVs, no tu contraseña maestra ni secretos en texto plano." + }, + "warning": "Si olvidas la contraseña maestra, los datos cifrados no se pueden recuperar. Guárdala de forma segura." + }, + "logout": { + "title": "Cerrar sesión y datos locales", + "body": "Al cerrar sesión se limpian secretos en memoria, estado del cofre y material de clave maestra de IndexedDB cuando aplica, y se termina la sesión de la API. Lo que solo esté en almacenamiento local del dispositivo puede permanecer hasta que lo borres en el navegador." + } + }, + "tips": { + "title": "Usar MyDevTools de forma segura", + "items": { + "masterPassword": "Usa una contraseña maestra fuerte y única para las funciones del cofre.", + "apiClient": "Trata el Cliente API como cualquier cliente HTTP: evita pegar secretos de producción en entornos compartidos si no sabes quién puede verlos.", + "nosql": "El Explorador de bases de datos se conecta a bases que configuras; usa usuarios con privilegios mínimos y reglas de red cuando sea posible.", + "json": "El Formateador JSON es local al navegador—aun así evita pegar datos muy sensibles en máquinas compartidas.", + "settings": "Personaliza las herramientas visibles en Configuraciones para mantener la barra lateral enfocada." + } + } + }, + "Settings": { + "title": "Configuraciones", + "language": "Idioma", + "spanish": "Español", + "english": "Inglés", + "french": "Francés", + "arabic": "Árabe", + "catalan": "Catalán", + "chineseSimplified": "Chino (simplificado)", + "czech": "Checo", + "greek": "Ελληνικά", + "german": "Alemán", + "danish": "Danés", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ucraniano" + }, + "SettingsPage": { + "title": "Configuraciones", + "subtitle": "Administra la configuración y preferencias de tu cuenta.", + "userProfile": { + "title": "Perfil de usuario", + "description": "Tu información personal y los detalles de tu cuenta.", + "avatarAlt": "Usuario", + "anonymousUser": "Usuario anónimo", + "noEmailProvided": "No se proporcionó correo electrónico", + "notLoggedIn": "No has iniciado sesión." + }, + "tools": { + "title": "Herramientas y funciones", + "description": "Elige qué herramientas aparecen en tu barra lateral de navegación." + }, + "appearance": { + "title": "Apariencia", + "description": "Personaliza cómo se ve la aplicación en tu dispositivo.", + "themePreference": "Preferencia de tema", + "themes": { + "light": "Claro", + "dark": "Oscuro", + "system": "Sistema" + } + }, + "language": { + "title": "Idioma", + "description": "Configura el idioma usado en toda la plataforma.", + "selectLanguage": "Seleccionar idioma", + "selectPlaceholder": "Selecciona un idioma", + "helpText": "Cambiar el idioma actualizará los menús y etiquetas en toda la plataforma.", + "languages": { + "en": "Inglés", + "fr": "Français", + "es": "Español", + "ar": "عربى (Árabe)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština (Checo)", + "el": "Ελληνικά (Griego)", + "de": "Deutsch (Alemán)", + "da": "Dansk (Danés)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italiano", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Buenos días", + "afternoon": "Buenas tardes", + "evening": "Buenas noches" + }, + "commaName": ", {name}", + "welcomeBack": "Bienvenido de nuevo", + "welcomeBackNamed": "Bienvenido de nuevo, {name}", + "tagline": "¿Qué te gustaría crear hoy?", + "brandName": "MyDevTools", + "stats": { + "tools": "Herramientas", + "favorites": "Favoritos", + "pinned": "Fijados", + "recent": "Usadas recientemente" + }, + "sections": { + "recentlyUsed": "Usadas recientemente", + "favorites": "Favoritos", + "whatsNew": "Novedades", + "pinned": "Fijados" + }, + "groups": { + "apps": "Aplicaciones" + }, + "toolCard": { + "defaultDescription": "Explora esta herramienta para sacarle más partido.", + "launchTool": "Abrir herramienta" + }, + "tools": { + "toDo": { + "title": "Tareas", + "description": "Gestiona tus listas de tareas con eficacia." + }, + "notes": { + "title": "Notas", + "description": "Captura ideas y organiza tus pensamientos." + }, + "passwordManager": { + "title": "Gestor de contraseñas", + "description": "Almacena y gestiona tus contraseñas de forma segura." + }, + "environmentManager": { + "title": "Gestor de entornos", + "description": "Guarda variables tipo .env por proyecto y entorno con etiquetas." + }, + "bookmarks": { + "title": "Marcadores", + "description": "Gestiona y organiza tus marcadores." + }, + "apiClient": { + "title": "Cliente API", + "description": "Prueba y depura peticiones HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Explora bases de datos MongoDB." + }, + "emailValidator": { + "title": "Validador de correo", + "description": "Verifica y valida direcciones de correo." + }, + "encryptionPlayground": { + "title": "Cifrado (prácticas)", + "description": "AES-GCM con clave en bruto o frase secreta; paquetes JSON, Web Crypto solo en el navegador." + }, + "jwtDecoder": { + "title": "Decodificador JWT", + "description": "Decodifica la cabecera, la carga útil y la caducidad del JWT." + }, + "certificatePemDecoder": { + "title": "Decodificador de certificados / PEM", + "description": "Decodifica certificados X.509 y CSR: sujeto, emisor, validez y SAN." + }, + "sshKeyGenerator": { + "title": "Generador de claves SSH / RSA", + "description": "Genera pares de claves Ed25519 o RSA. Descarga claves en formatos OpenSSH y PEM." + }, + "jsonFormatter": { + "title": "Formateador JSON", + "description": "Formatea, valida y edita datos JSON." + }, + "sqlFormatter": { + "title": "Formateador SQL", + "description": "Formatea SQL para MySQL, PostgreSQL y SQLite." + }, + "graphqlFormatter": { + "title": "Formateador GraphQL", + "description": "Formatea GraphQL con resaltado de sintaxis y un editor de consultas sencillo." + }, + "yamlFormatter": { + "title": "Formateador YAML", + "description": "Formatea, valida y convierte YAML a JSON en el navegador." + }, + "diffChecker": { + "title": "Comparador de diferencias", + "description": "Diferencias línea a línea entre dos textos en paralelo." + }, + "regexTester": { + "title": "Probador de expresiones regulares", + "description": "Prueba regex con resaltado de coincidencias en vivo." + }, + "timestampConverter": { + "title": "Conversor de marcas de tiempo", + "description": "Conversión entre Unix, ISO y tiempo relativo." + }, + "cronBuilder": { + "title": "Constructor Cron", + "description": "Constructor visual de Cron, plantillas y analizador." + }, + "base64Encoder": { + "title": "Codificador Base64", + "description": "Codifica y decodifica cadenas Base64." + }, + "urlEncoder": { + "title": "Codificador de URL", + "description": "Codifica y decodifica en porcentaje los componentes de URL." + }, + "urlParser": { + "title": "Analizador de URL", + "description": "Divide una URL en protocolo, host, ruta, parámetros de consulta y hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Genera UUID y ULID con exportación masiva." + }, + "secretApiKeyGenerator": { + "title": "Secreto / clave API", + "description": "Cadenas aleatorias criptográficas con alfabeto, longitud y exportación masiva configurables." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Genera texto latino de relleno para maquetas y diseños." + }, + "colorPicker": { + "title": "Selector de color", + "description": "Convierte HEX, RGB, HSL y explora paletas." + }, + "contrastChecker": { + "title": "Comprobador de contraste", + "description": "Ratio de contraste WCAG 2.1 entre texto y fondo (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Generador de esquema JSON", + "description": "Obtén un esquema JSON y modelos tipados a partir de una muestra JSON." + }, + "imageToBase64": { + "title": "Imagen a Base64", + "description": "Convierta imágenes a URI de datos o cadenas Base64 sin formato." + }, + "imageCompressor": { + "title": "Compresor de imágenes", + "description": "Comprime JPEG, PNG y WebP en local con un control de calidad." + }, + "cssGradientBuilder": { + "title": "Generador de degradado CSS", + "description": "Generador de gradientes CSS visual con control de ángulos y paradas de color." + }, + "gitignoreGenerator": { + "title": "Generador .gitignore", + "description": "Genere archivos .gitignore combinados al instante a partir de más de 500 pilas de tecnología." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Convierte archivos CSV o Excel a JSON y exporta de vuelta a CSV o XLSX." + }, + "snippetManager": { + "title": "Fragmentos de código", + "description": "Guarda fragmentos con resaltado de sintaxis, detección automática de idioma y formato." + }, + "qrCodeGenerator": { + "title": "Generador de códigos QR", + "description": "Crea códigos QR en PNG a partir de texto o URL en el navegador." + }, + "ipSubnetCalculator": { + "title": "Calculadora IP / subred", + "description": "IPv4 e IPv6 CIDR: máscara, rangos y recuento de direcciones." + }, + "dnsLookup": { + "title": "Búsqueda DNS", + "description": "Consulta registros DNS de cualquier dominio — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Generador de hash", + "description": "MD5, SHA-1 y SHA-2 para texto o archivos." + }, + "hmacGenerator": { + "title": "Generador HMAC", + "description": "Firma webhooks y prueba integraciones API con HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Generador de código TOTP / 2FA", + "description": "Pega un secreto Base32 del autenticador; el código de seis dígitos se renueva cada 30 s para probar inicio de sesión y MFA." + }, + "markdownPreview": { + "title": "Vista previa Markdown", + "description": "Renderizador Markdown en vivo con exportación HTML y conversión HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Conversor de base numérica", + "description": "Convierte enteros entre bases del 2 al 36." + }, + "formatConverter": { + "title": "Conversor de formatos", + "description": "Convierte entre JSON, YAML, TOML y XML en cualquier dirección. Las 12 combinaciones están soportadas." + }, + "mockDataGenerator": { + "title": "Generador de datos de prueba", + "description": "Define columnas, elige tipos y exporta conjuntos de datos para seeds, Postman o pruebas. Hasta 5000 filas, todo en el navegador: no se sube nada." + }, + "httpStatusCodes": { + "title": "Códigos de estado HTTP", + "description": "Busca códigos de estado HTTP con descripciones (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Optimizador SVG", + "description": "Pega SVG, elimina ruido con SVGO y ve la reducción de tamaño UTF-8." + }, + "unitConverter": { + "title": "Convertidor de unidades", + "description": "Convierte entre 323 unidades en 43 categorías científicas e ingeniería." + }, + "dockerComposeGenerator": { + "title": "Generador Docker Compose", + "description": "Elige bases de datos, cachés, colas, proxies e imágenes de observabilidad; exporta un docker-compose.yml para entornos locales." + }, + "game2048": { + "title": "2048 \t", + "description": "Desliza y fusiona los mosaicos para llegar a 2048. Juego clásico de rompecabezas de números." + }, + "sudoku": { + "title": "Sudoku", + "description": "Rompecabezas de números clásicos. 3 niveles de dificultad, 150 etapas cada uno." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Explora claves, inspecciona valores y ejecuta comandos en cualquier instancia Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Generador y verificador Bcrypt", + "description": "Genera hashes de contraseñas con bcrypt y verifica hashes existentes." + }, + "chmodCalculator": { + "title": "Calculadora Chmod", + "description": "Convierte permisos de Unix entre octal, simbólico y casillas." + }, + "codeScreenshot": { + "title": "Captura de código", + "description": "Convierte tu código fuente en imágenes atractivas y fáciles de compartir." + }, + "cssGenerators": { + "title": "Generadores CSS", + "description": "Generadores de box-shadow, border-radius y clamp() fluido." + }, + "exifViewer": { + "title": "Visor y eliminador EXIF", + "description": "Inspecciona y elimina los metadatos EXIF de las fotos de forma local." + }, + "faviconGenerator": { + "title": "Generador de favicon", + "description": "Genera favicons, iconos de app y fragmentos de manifiesto." + }, + "jsonDiff": { + "title": "Diff JSON", + "description": "Comparación semántica y estructural de dos documentos JSON." + }, + "jsonpathPlayground": { + "title": "Playground JSONPath", + "description": "Consulta JSON con JSONPath o JMESPath y ve los resultados en vivo." + }, + "keycodeInspector": { + "title": "Inspector de keycode", + "description": "Pulsa una tecla para ver sus códigos de evento de JavaScript y sus modificadores." + }, + "markdownTableGenerator": { + "title": "Generador de tablas Markdown", + "description": "Crea tablas Markdown y HTML en un editor de cuadrícula tipo hoja de cálculo." + }, + "timezoneConverter": { + "title": "Conversor de zonas horarias", + "description": "Convierte una hora entre muchas zonas con indicaciones de cambio de día y horario laboral." + }, + "tokenCounter": { + "title": "Contador de tokens de LLM", + "description": "Cuenta tokens de LLM, visualiza fragmentos y estima el coste de la API." + }, + "webhookTester": { + "title": "Probador de webhooks", + "description": "Captura e inspecciona solicitudes de webhook entrantes en tiempo real." + }, + "websocketTester": { + "title": "Probador de WebSocket", + "description": "Conéctate a endpoints WebSocket e intercambia mensajes en tiempo real." + }, + "whoisLookup": { + "title": "Consulta Whois", + "description": "Consulta los datos de registro de un dominio o IP mediante RDAP." + } + }, + "tabs": { + "apps": "Aplicaciones", + "analytics": "Análisis", + "ui": "Interfaz" + }, + "analytics": { + "title": "Tus datos de un vistazo", + "subtitle": "Recuentos sincronizados con tu cuenta en el servidor.", + "loadError": "No se pudieron cargar las estadísticas.", + "retry": "Reintentar", + "loginHint": "Inicia sesión para ver el análisis de uso y las preferencias de interfaz.", + "passwordEntries": "Entradas de contraseñas", + "bookmarks": "Marcadores", + "bookmarkFolders": "Carpetas de marcadores", + "tasksTotal": "Tareas", + "tasksCompleted": "Completadas", + "tasksOngoing": "En curso", + "tasksNotStarted": "Sin empezar", + "projects": "Proyectos", + "nosqlConnections": "Conexiones a BD", + "notes": "Notas", + "apiClientCollections": "Colecciones API", + "apiClientEnvironments": "Entornos API", + "apiClientHistory": "Entradas del historial API", + "jsonFormatterDocuments": "Documentos del formateador JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Fragmentos de código", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Preferencias de interfaz", + "subtitle": "Tema, idioma y herramientas visibles en la barra lateral — desde la configuración de tu cuenta.", + "loadError": "No se pudieron cargar las preferencias.", + "loginHint": "Inicia sesión para ver tus preferencias de interfaz guardadas.", + "theme": "Tema", + "accent": "Color de acento", + "locale": "Idioma", + "enabledTools": "Herramientas visibles", + "favoriteTools": "Herramientas favoritas", + "toolsWithUsage": "Herramientas con uso registrado" + }, + "signIn": "Iniciar sesión", + "noResults": "Ninguna herramienta coincide con tu búsqueda", + "noResultsHint": "Prueba con otra palabra clave o elimina el filtro.", + "filterAll": "Todo", + "loginCta": { + "description": "Inicia sesión para rastrear tus herramientas usadas recientemente." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Cargando...", + "myTasksTitle": "Mis tareas", + "statsLine": "{total} tareas • {percent}% completadas", + "statsLineDesktop": "{total} en total • {percent}% completadas" + }, + "filters": { + "all": "Todas", + "total": "Total", + "allProjects": "Todos los proyectos", + "export": "Exportar", + "kanban": "Kanban", + "list": "Lista", + "searchPlaceholder": "Buscar tareas...", + "searchAria": "Buscar tareas", + "clearSearchAria": "Borrar búsqueda", + "foundTasks": "Se encontraron {count} {count, plural, one {tarea} other {tareas}}", + "filterByAllAria": "Filtrar por Todas", + "filterByStatusAria": "Filtrar por {status}" + }, + "status": { + "not-started": { + "label": "Sin iniciar", + "description": "Tareas que aún no han comenzado" + }, + "ongoing": { + "label": "En curso", + "description": "Tareas actualmente en progreso" + }, + "completed": { + "label": "Completadas", + "description": "Tareas que ya terminaron" + } + }, + "priorities": { + "high": "Alta", + "medium": "Media", + "low": "Baja" + }, + "form": { + "addNewTaskLabel": "Agregar nueva tarea", + "multilinePlaceholder": "Agrega detalles de la tarea... (Shift+Enter para nueva línea)", + "singlelinePlaceholder": "¿Qué hay que hacer? Presiona Enter para agregar", + "taskInputAria": "Entrada de tarea", + "projectPlaceholder": "Proyecto", + "selectProject": "Seleccionar proyecto", + "errorSelectProject": "Selecciona un proyecto o crea uno nuevo para agregar una tarea.", + "hintMultiline": "Shift+Enter para nueva línea, Enter para agregar", + "hintSingleline": "Enter para agregar", + "shortcutsAria": "Atajos de teclado", + "shortcutsLabel": "Atajos", + "shortcutAddTask": "Agregar tarea", + "shortcutNewLine": "Nueva línea", + "shortcutClear": "Borrar", + "addButton": "Agregar" + }, + "list": { + "ariaLabel": "Lista de tareas", + "loading": "Cargando tus tareas...", + "emptyTitle": "No se encontraron tareas", + "emptyPrimary": "Empieza agregando tu primera tarea arriba. Presiona Enter para agregarlas rápidamente.", + "emptySecondary": "Prueba ajustar tus filtros o tu búsqueda." + }, + "mobileDrawer": { + "addTaskSrOnly": "Agregar tarea", + "addNewTaskTitle": "Agregar nueva tarea", + "addNewTaskDescription": "Crea una nueva tarea para seguir tu progreso.", + "cancel": "Cancelar" + }, + "kanban": { + "ariaLabel": "Tablero Kanban", + "loading": "Cargando tus tareas...", + "dropHere": "Suelta la tarea aquí", + "noTasksYet": "Aún no hay tareas", + "emptyColumnAria": "Columna vacía", + "columnAria": "Columna {title} con {count} tareas", + "countInColumnAria": "{count} tareas en {title}" + }, + "taskItem": { + "confirmDelete": "¿Seguro que quieres eliminar esta tarea?", + "statusPlaceholder": "Estado", + "changeStatusAria": "Cambiar estado de la tarea", + "taskOptionsAria": "Opciones de la tarea", + "editTask": "Editar tarea", + "copyTask": "Copiar tarea", + "copied": "¡Copiado!", + "deleteTask": "Eliminar tarea", + "statusHeading": "Estado", + "editTaskAria": "Editar tarea" + }, + "editDialog": { + "title": "Editar tarea", + "description": "Actualiza detalles, agrega subtareas, define prioridades y más.", + "taskTitleLabel": "Título de la tarea *", + "taskTitlePlaceholder": "¿Qué hay que hacer?", + "descriptionLabel": "Descripción", + "descriptionPlaceholder": "Agrega más detalles sobre esta tarea...", + "statusLabel": "Estado", + "priorityLabel": "Prioridad", + "dueDateLabel": "Fecha límite", + "pickDate": "Elige una fecha", + "clearDate": "Quitar fecha", + "projectLabel": "Proyecto", + "selectProjectPlaceholder": "Selecciona un proyecto", + "noProject": "Sin proyecto", + "timeEstimateLabel": "Estimación de tiempo (minutos)", + "timeEstimatePlaceholder": "¿Cuánto tardará esto?", + "tagsLabel": "Etiquetas", + "customTagNamePlaceholder": "Nombre de etiqueta personalizada", + "subtasksLabel": "Subtareas", + "addSubtaskPlaceholder": "Agregar una subtarea...", + "cancel": "Cancelar", + "saving": "Guardando...", + "saveChanges": "Guardar cambios" + }, + "export": { + "title": "Exportar tareas", + "description": "Exporta tus tareas a formatos JSON, CSV o Excel.", + "projectLabel": "Proyecto", + "statusLabel": "Estado", + "tagLabel": "Etiqueta", + "allProjects": "Todos los proyectos", + "allStatuses": "Todos los estados", + "allTags": "Todas las etiquetas", + "exportAsJsonTitle": "Exportar como JSON", + "exportAsJsonDescription": "Exporta datos de tareas incluyendo subtareas, etiquetas y metadatos.", + "exportJson": "Exportar JSON", + "exportAsCsvExcelTitle": "Exportar como CSV / Excel", + "exportAsCsvExcelDescription": "Exporta tareas para aplicaciones de hojas de cálculo.", + "exportCsv": "Exportar CSV", + "exportExcel": "Exportar Excel", + "close": "Cerrar", + "toastNoTasks": "No hay tareas para exportar con los filtros actuales", + "toastExportedJson": "¡Se exportaron {count} tareas a JSON!", + "toastExportedCsv": "¡Se exportaron {count} tareas a CSV!", + "toastExportedExcel": "¡Se exportaron {count} tareas a Excel!", + "toastFailedJson": "No se pudo exportar a JSON", + "toastFailedCsv": "No se pudo exportar a CSV", + "toastFailedExcel": "No se pudo exportar a Excel", + "unknownProject": "Proyecto desconocido" + }, + "ack": { + "taskAddedTitle": "Tarea agregada correctamente", + "taskAddedFailedTitle": "No se pudo agregar la tarea", + "taskUpdatedTitle": "Tarea actualizada correctamente", + "taskUpdatedFailedTitle": "No se pudo actualizar la tarea", + "taskStatusUpdateFailedTitle": "No se pudo actualizar el estado de la tarea", + "taskMovedTitle": "Tarea movida a {status}", + "tryAgain": "Inténtalo de nuevo.", + "taskDeletedTitle": "Tarea \"{text}\" eliminada", + "undo": "Deshacer", + "taskRestoredTitle": "Tarea restaurada", + "taskRestoredSuccessTitle": "Tarea restaurada correctamente", + "taskRestoreFailedTitle": "No se pudo restaurar la tarea. Inténtalo de nuevo.", + "taskDeleteFailedTitle": "No se pudo eliminar la tarea. Inténtalo de nuevo.", + "tasksImportedTitle": "¡Se importaron {count} {count, plural, one {tarea} other {tareas}} correctamente!", + "tasksImportFailedTitle": "No se pudieron importar las tareas. Inténtalo de nuevo.", + "unknownProject": "Proyecto desconocido", + "projectCreatedTitle": "Proyecto creado correctamente", + "projectCreateFailedTitle": "No se pudo crear el proyecto", + "projectUpdatedTitle": "Proyecto actualizado correctamente", + "projectUpdateFailedTitle": "No se pudo actualizar el proyecto", + "projectDeletedTitle": "Proyecto eliminado correctamente", + "projectDeleteFailedTitle": "No se pudo eliminar el proyecto" + } + }, + "Notes": { + "page": { + "loading": "Cargando..." + }, + "layout": { + "mobileNavTitle": "Navegación de notas", + "openMenuAria": "Abrir menú de notas" + }, + "sidebar": { + "title": "Notas", + "createNoteAria": "Crear nota", + "searchPlaceholder": "Buscar notas...", + "loading": "Cargando...", + "noNotesFound": "No se encontraron notas.", + "noNotesYet": "Aún no hay notas. Haz clic en + para crear una.", + "inParent": "en {parentTitle}", + "untitled": "Sin título", + "addSubPage": "Agregar subpágina", + "delete": "Eliminar", + "deleteConfirmTitle": "¿Eliminar nota?", + "deleteConfirmDescription": "¿Seguro que quieres eliminar \"{title}\"? Esta acción no se puede deshacer.", + "cancel": "Cancelar", + "confirmDelete": "Eliminar", + "noteOptionsAria": "Opciones de la nota" + }, + "editor": { + "emptyState": "Selecciona una nota o crea una nueva para comenzar.", + "titlePlaceholder": "Sin título", + "saving": "Guardando…", + "ready": "Listo", + "saved": "Guardado {time}", + "stats": "{words} palabras · {read} min de lectura", + "template": "Plantilla", + "applyTemplate": "Aplicar plantilla", + "exportMdLabel": ".md", + "exportMd": "Exportar como Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Exportar como HTML", + "focusMode": "Modo concentración", + "exitFocusMode": "Salir del modo concentración", + "save": "Guardar", + "addTag": "Añadir etiqueta…", + "removeTag": "Eliminar etiqueta {tag}", + "loading": "Cargando…", + "searchHint": "para buscar", + "focusHint": "modo concentración", + "notePath": "Ruta de la nota" + }, + "context": { + "authRequiredError": "Usuario no autenticado", + "defaultTitle": "Sin título", + "cloudSyncImageError": "Activa la sincronización en la nube para guardar imágenes de notas. Las notas de texto permanecen locales." + } + }, + "PasswordManager": { + "page": { + "loading": "Cargando...", + "title": "Gestor de contraseñas", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "No se pudieron descifrar las contraseñas", + "toastLoadFailed": "No se pudieron cargar las contraseñas", + "toastUnlockedAuto": "Bóveda desbloqueada automáticamente", + "toastAutoUnlockFailed": "El desbloqueo automático falló. Desbloquea manualmente.", + "toastConnectFailed": "No se pudo conectar con el servidor", + "toastAutoUnlockWarning": "Bóveda desbloqueada, pero el desbloqueo automático puede no funcionar al recargar", + "errorIncorrectPassword": "Contraseña maestra incorrecta", + "errorUnlockFailed": "No se pudo desbloquear la bóveda", + "errorPasswordsMismatch": "Las contraseñas no coinciden", + "errorPasswordTooShort": "La contraseña debe tener al menos 8 caracteres", + "errorSetupFailed": "No se pudo configurar la bóveda", + "errorConnectFailed": "No se pudo conectar con el servidor.", + "mobileTitleCreate": "Crea tu bóveda", + "mobileTitleWelcome": "Bienvenido de nuevo", + "mobileSubtitleCreate": "Establece una contraseña maestra para proteger tus credenciales", + "mobileSubtitleUnlock": "Introduce tu contraseña maestra para desbloquear", + "placeholderMasterPassword": "Contraseña maestra", + "placeholderConfirmPassword": "Confirmar contraseña", + "labelMasterPassword": "Contraseña maestra", + "labelConfirmPassword": "Confirmar contraseña", + "placeholderEnterMaster": "Introduce la contraseña maestra", + "placeholderConfirmMaster": "Confirma la contraseña maestra", + "buttonCreateVault": "Crear bóveda", + "buttonUnlock": "Desbloquear", + "buttonUnlockVault": "Desbloquear bóveda", + "footerCreate": "Tu contraseña nunca se guarda y no se puede recuperar", + "footerUnlock": "Protegido con cifrado de extremo a extremo", + "desktopTitleSetup": "Configurar bóveda de contraseñas", + "desktopTitleUnlock": "Desbloquear bóveda", + "desktopDescriptionSetup": "Crea una contraseña maestra para proteger tus credenciales. Esta contraseña nunca se guarda y no se puede recuperar.", + "desktopDescriptionUnlock": "Introduce tu contraseña maestra para acceder a tus credenciales protegidas." + }, + "list": { + "loading": "Cargando tus contraseñas...", + "emptyTitle": "La bóveda está vacía", + "emptyHintMobile": "Toca el botón + para añadir tu primera contraseña", + "emptyHintDesktop": "Aún no has guardado contraseñas. Haz clic en \"Añadir contraseña\" arriba para empezar.", + "addPassword": "Añadir contraseña", + "searchPlaceholderMobile": "Buscar en tu bóveda...", + "searchPlaceholderDesktop": "Buscar en la bóveda...", + "importExport": "Importar / exportar", + "lockVault": "Bloquear bóveda", + "allPasswordsTitle": "Todas las contraseñas", + "passwordCount": "{count, plural, one {# contraseña} other {# contraseñas}}", + "vaultHealthDrawerTitle": "Estado de la bóveda", + "noResults": "No hay contraseñas que coincidan con \"{query}\"", + "tableService": "Servicio", + "tableUsername": "Usuario", + "tablePassword": "Contraseña", + "tableUrl": "URL", + "tableActions": "Acciones", + "gridViewAria": "Vista de cuadrícula", + "listViewAria": "Vista de lista", + "passwordStrengthTitle": "Fortaleza de la contraseña", + "deleteDialogTitle": "Eliminar contraseña", + "deleteDialogDescription": "¿Seguro que quieres eliminar esta contraseña? Esta acción no se puede deshacer.", + "cancel": "Cancelar", + "delete": "Eliminar", + "edit": "Editar" + }, + "toasts": { + "copiedPassword": "Contraseña copiada al portapapeles", + "copiedUsername": "Usuario copiado al portapapeles", + "passwordDeleted": "Contraseña eliminada", + "deleteFailed": "No se pudo eliminar la contraseña", + "vaultLocked": "Bóveda bloqueada", + "importedCount": "Se importaron {count, plural, one {# contraseña} other {# contraseñas}} correctamente" + }, + "form": { + "addTitle": "Añadir contraseña", + "editTitle": "Editar contraseña", + "serviceName": "Nombre del servicio", + "usernameEmail": "Usuario / correo", + "password": "Contraseña", + "urlOptional": "URL del sitio (opcional)", + "notesOptional": "Notas (opcional)", + "tags": "Etiquetas", + "placeholderService": "p. ej. Netflix", + "placeholderUsername": "usuario@ejemplo.com", + "placeholderPassword": "Introduce o genera una contraseña", + "placeholderUrl": "https://...", + "placeholderNotes": "Detalles adicionales...", + "placeholderTags": "Añade etiquetas (Enter o coma)", + "strength": "Fortaleza", + "strengthWeak": "Débil", + "strengthMedium": "Media", + "strengthStrong": "Fuerte", + "advancedGenerator": "Generador avanzado", + "cancel": "Cancelar", + "saving": "Guardando...", + "savePassword": "Guardar contraseña", + "updating": "Actualizando...", + "updatePassword": "Actualizar contraseña", + "addPasswordSrOnly": "Añadir contraseña", + "triggerAdd": "Añadir contraseña", + "toastSaved": "Contraseña guardada correctamente", + "toastSaveFailed": "No se pudo guardar la contraseña", + "toastUpdated": "Contraseña actualizada correctamente", + "toastUpdateFailed": "No se pudo actualizar la contraseña" + }, + "security": { + "vaultHealth": "Estado de la bóveda", + "statusLabel": "Estado", + "totalPasswords": "Contraseñas totales", + "activeEntries": "Entradas activas en tu bóveda", + "protected": "Protegidas", + "weakPasswords": "Contraseñas débiles", + "needingImprovement": "Contraseñas que necesitan mejorarse", + "allGood": "Todo bien", + "reusedPasswords": "Contraseñas reutilizadas", + "usedMultipleTimes": "Contraseñas usadas varias veces", + "unique": "Únicas", + "total": "Total", + "weak": "Débiles", + "reused": "Reutilizadas", + "totalCountLabel": "{count, plural, one {# contraseña} other {# contraseñas}}", + "weakCountLabel": "{count} débiles", + "allSecure": "Todas seguras", + "scoreExcellent": "Excelente", + "scoreGood": "Buena", + "scoreFair": "Regular", + "scoreCritical": "Crítica" + }, + "importExport": { + "title": "Importar / exportar bóveda", + "description": "Administra tus contraseñas. Puedes exportar la bóveda a JSON o importar desde un archivo JSON.", + "tabExport": "Exportar", + "tabImport": "Importar", + "warningTitle": "Advertencia", + "warningBody": "Los archivos exportados contienen tus contraseñas en texto plano. Guárdalos en un lugar seguro y bórralos después de usarlos.", + "exportBlurb": "Descarga toda tu bóveda como archivo JSON. Incluye servicios, usuarios, contraseñas y notas.", + "downloadJson": "Descargar exportación JSON", + "pasteLabel": "Pegar datos JSON", + "pastePlaceholder": "[{\"service\": \"Ejemplo\", \"username\": \"usuario\", \"password\": \"123\", ...}]", + "pasteHint": "Pega aquí el contenido de un archivo JSON exportado previamente.", + "importing": "Importando...", + "importPasswords": "Importar contraseñas", + "trigger": "Importar / exportar", + "toastExported": "Bóveda exportada correctamente", + "toastExportFailed": "No se pudo exportar la bóveda", + "toastImportFailed": "Error al importar: formato JSON no válido" + }, + "card": { + "copyUsername": "Copiar usuario", + "openUrl": "Abrir URL", + "strength": "Fortaleza" + }, + "swipeable": { + "copy": "Copiar", + "delete": "Eliminar", + "copyPassword": "Copiar contraseña", + "copyUsername": "Copiar usuario", + "edit": "Editar", + "deleteAction": "Eliminar" + }, + "floatingMenu": { + "lockVault": "Bloquear bóveda", + "importExport": "Importar / exportar", + "addPassword": "Añadir contraseña" + }, + "generator": { + "tabPassword": "Contraseña", + "tabPassphrase": "Frase secreta", + "strength": "Fortaleza", + "lengthLabel": "Longitud: {length} caracteres", + "lengthBadge": "{length} car.", + "characterSets": "Conjuntos de caracteres", + "uppercase": "Mayúsculas", + "lowercase": "Minúsculas", + "numbers": "Números", + "symbols": "Símbolos", + "wordCountLabel": "Número de palabras: {count}", + "wordCountBadge": "{count, plural, one {# palabra} other {# palabras}}", + "separator": "Separador", + "capitalization": "Mayúsculas", + "includeNumber": "Incluir número" + } + }, + "EnvironmentManager": { + "subtitle": "Variables de entorno cifradas por proyecto y entorno.", + "page": { + "loading": "Cargando…", + "title": "Gestor de entornos", + "loadFailed": "No se pudieron cargar los conjuntos de entorno" + }, + "list": { + "title": "Entornos", + "loading": "Cargando tus conjuntos…", + "emptyTitle": "Aún no hay conjuntos de entorno", + "emptyHint": "Crea un conjunto por proyecto y entorno (p. ej. claves de API de producción). Todo se cifra en tu navegador antes de subirlo.", + "searchPlaceholder": "Buscar proyecto, entorno, etiquetas, notas…", + "tagFilterPlaceholder": "Filtrar por etiqueta", + "variableCount": "{count, plural, one {# variable} other {# variables}}", + "updatedAgo": "Actualizado {time}", + "copyDotEnv": "Copiar .env", + "edit": "Editar", + "delete": "Eliminar", + "cancel": "Cancelar", + "confirmDelete": "Eliminar", + "deleteDialogTitle": "¿Eliminar conjunto de entorno?", + "deleteDialogDescription": "Esto elimina el blob cifrado de tu cuenta. No se puede deshacer.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env copiado al portapapeles", + "deleted": "Conjunto de entorno eliminado", + "deleteFailed": "No se pudo eliminar" + }, + "form": { + "addTitle": "Nuevo conjunto de entorno", + "editTitle": "Editar conjunto de entorno", + "triggerAdd": "Añadir conjunto de entorno", + "addSrOnly": "Añadir conjunto de entorno", + "project": "Proyecto", + "environment": "Entorno", + "placeholderProject": "p. ej. billing-api", + "placeholderEnvironment": "p. ej. production", + "pasteEnvLabel": "Pegar .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Fusionar líneas pegadas", + "variables": "Variables", + "addRow": "Añadir fila", + "keyPlaceholder": "CLAVE", + "valuePlaceholder": "valor", + "notesOptional": "Notas (opcional)", + "placeholderNotes": "Política de rotación, responsable, enlaces…", + "tags": "Etiquetas", + "placeholderTags": "Añadir etiquetas (Enter o coma)", + "cancel": "Cancelar", + "save": "Guardar", + "saving": "Guardando…", + "update": "Actualizar", + "updating": "Actualizando…", + "toastSaved": "Conjunto de entorno guardado", + "toastSaveFailed": "No se pudo guardar", + "toastUpdated": "Conjunto de entorno actualizado", + "toastUpdateFailed": "No se pudo actualizar", + "toastPasteEmpty": "No se encontraron líneas CLAVE=valor", + "toastPasteApplied": "Se fusionaron {count} variables del pegado", + "toastProjectEnvRequired": "El proyecto y el entorno son obligatorios", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Cargando..." + }, + "manager": { + "foldersHeading": "Carpetas", + "tagsHeading": "Etiquetas", + "tagsMore": "+{count} más", + "tagsShowLess": "Show less", + "searchPlaceholder": "Buscar en tus marcadores...", + "import": "Importar", + "export": "Exportar", + "exportAsHtml": "Exportar como HTML", + "exportAsJson": "Exportar como JSON", + "addBookmark": "Añadir marcador", + "matchingQuery": "coinciden con \"{query}\"", + "bookmarkCount": "{count, plural, one {# marcador} other {# marcadores}}", + "allBookmarks": "Todos los marcadores", + "uncategorized": "Sin categoría", + "unknownFolder": "Desconocido" + }, + "card": { + "urlCopied": "URL copiada al portapapeles", + "bookmarkDeleted": "Marcador eliminado", + "edit": "Editar", + "delete": "Eliminar", + "copyUrl": "Copiar URL", + "deleteConfirmTitle": "¿Eliminar marcador?", + "deleteConfirmDescription": "Se eliminará permanentemente \"{title}\".", + "cancel": "Cancelar" + }, + "grid": { + "emptyTitle": "Aún no hay marcadores", + "emptyDescription": "Añade tu primer marcador o importa desde el navegador para empezar." + }, + "folderTree": { + "allBookmarks": "Todos los marcadores", + "addSubfolder": "Añadir subcarpeta", + "rename": "Renombrar", + "deleteFolderTitle": "¿Eliminar carpeta \"{name}\"?", + "deleteFolderDescription": "Se eliminará la carpeta y todas sus subcarpetas. Los marcadores pasarán a Sin categoría." + }, + "import": { + "title": "Importar marcadores", + "description": "Importa marcadores desde el archivo exportado por tu navegador.", + "parseError": "No se pudo analizar el archivo. Asegúrate de que sea una exportación de marcadores válida.", + "successToast": "Se importaron {bookmarks} marcadores y {folders} carpetas", + "errorToast": "No se pudieron importar los marcadores", + "chromeInstruction": "Menú → Marcadores → Administrador de marcadores → ⋮ → Exportar marcadores", + "firefoxInstruction": "Menú → Marcadores → Gestionar marcadores → Importar y respaldar → Exportar", + "safariInstruction": "Archivo → Exportar marcadores...", + "edgeInstruction": "Menú → Favoritos → ⋯ → Exportar favoritos", + "dropZoneTitle": "Suelta aquí tu archivo de marcadores", + "dropZoneHint": "o haz clic para buscar (HTML o JSON)", + "selectedFileHint": "Haz clic para elegir otro archivo", + "errorTitle": "Error", + "previewTitle": "Vista previa de importación", + "previewBookmarks": "{count, plural, one {# marcador} other {# marcadores}}", + "previewFolders": "{count, plural, one {# carpeta} other {# carpetas}}", + "importModeLabel": "Modo de importación", + "modeMerge": "Combinar con los marcadores existentes", + "modeReplace": "Reemplazar todos los marcadores existentes", + "cancel": "Cancelar", + "importButton": "Importar", + "importButtonWithCount": "Importar {count, plural, one {# marcador} other {# marcadores}}" + }, + "addBookmark": { + "titleAdd": "Añadir marcador", + "titleEdit": "Editar marcador", + "urlLabel": "URL", + "urlPlaceholder": "https://ejemplo.com", + "urlInvalid": "Introduce una URL válida", + "titleLabel": "Título", + "titlePlaceholder": "Título del marcador", + "titleRequired": "Introduce un título", + "descriptionLabel": "Descripción (opcional)", + "descriptionPlaceholder": "Añade una nota sobre este marcador", + "folderLabel": "Carpeta", + "folderPlaceholder": "Seleccionar carpeta", + "noFolder": "Sin carpeta", + "tagsLabel": "Etiquetas", + "tagsPlaceholder": "Añadir etiquetas (pulsa Intro)", + "toastUpdated": "Marcador actualizado", + "toastAdded": "Marcador añadido", + "cancel": "Cancelar", + "submitAdd": "Añadir marcador", + "submitUpdate": "Actualizar marcador" + }, + "addFolder": { + "title": "Crear carpeta", + "nameLabel": "Nombre de la carpeta", + "namePlaceholder": "Mi carpeta", + "nameRequired": "Introduce un nombre de carpeta", + "parentLabel": "Carpeta superior", + "parentPlaceholder": "Seleccionar carpeta superior", + "rootParent": "Raíz (sin superior)", + "toastCreated": "Carpeta creada", + "cancel": "Cancelar", + "submit": "Crear carpeta" + }, + "editFolder": { + "title": "Renombrar carpeta", + "nameLabel": "Nombre de la carpeta", + "namePlaceholder": "Nombre de la carpeta", + "nameRequired": "Introduce un nombre de carpeta", + "toastRenamed": "Carpeta renombrada", + "cancel": "Cancelar", + "save": "Guardar" + } + }, + "EmailValidator": { + "header": { + "title": "Validador de correo", + "description": "Verifica y valida direcciones de correo: sintaxis, dominio y entregabilidad." + }, + "tabs": { + "single": "Verificación individual", + "bulk": "Carga masiva" + }, + "single": { + "cardTitle": "Verificación de un correo", + "cardDescription": "Introduce una dirección para comprobar entregabilidad y reputación.", + "placeholder": "Introduce el correo...", + "clearAria": "Borrar correo", + "validate": "Validar", + "validating": "Validando...", + "formatError": "Introduce una dirección de correo válida" + }, + "toasts": { + "errorTitle": "Error", + "enterEmail": "Introduce una dirección de correo", + "invalidFormatTitle": "Formato de correo no válido", + "invalidFormatDescription": "Introduce una dirección de correo válida", + "validateFailedDescription": "Ocurrió un error al validar el correo", + "noEmailsTitle": "No se encontraron correos", + "noEmailsDescription": "Asegúrate de que tu Excel tenga una columna «email».", + "fileTooLargeTitle": "Archivo demasiado grande", + "fileTooLargeDescription": "Se encontraron {total} correos. Solo se validarán los primeros 4000.", + "successTitle": "Correcto", + "successValidated": "Se validaron {count} correos correctamente.", + "batchFailedTitle": "Falló la validación masiva", + "batchFailedDescription": "Ocurrió un error al procesar la validación masiva.", + "noExportTitle": "No hay datos para exportar", + "noExportValidOnly": "No hay correos válidos para exportar.", + "noExportAll": "No hay resultados para exportar.", + "exportSuccessTitle": "Exportación correcta", + "exportSuccessExcelAll": "Se exportaron {count} correos a Excel.", + "exportSuccessExcelValid": "Se exportaron {count} correos válidos a Excel.", + "exportSuccessCsvAll": "Se exportaron {count} correos a CSV.", + "exportSuccessCsvValid": "Se exportaron {count} correos válidos a CSV." + }, + "disposableAlert": { + "title": "Correo desechable detectado", + "description": "Esta dirección pertenece a un proveedor de correo temporal. Puede ser insegura o de un solo uso." + }, + "score": { + "reliabilityTitle": "Puntuación de fiabilidad", + "scoreLabel": "Puntuación" + }, + "details": { + "sectionTitle": "Detalles de validación" + }, + "validation": { + "syntaxLabel": "Sintaxis válida", + "syntaxTooltip": "Comprueba si el correo tiene el formato correcto (p. ej. usuario@dominio.com)", + "domainLabel": "Dominio existe", + "domainTooltip": "Verifica que el dominio del correo exista", + "mxLabel": "Registros MX", + "mxTooltip": "Comprueba si el dominio tiene registros MX para recibir correo", + "mailboxLabel": "Buzón existe", + "mailboxTooltip": "Verifica que el buzón o cuenta exista en el servidor de correo", + "disposableLabel": "Correo desechable", + "disposableTooltip": "Indica si es un servicio de correo temporal (puede ser arriesgado)", + "roleBasedLabel": "Correo genérico", + "roleBasedTooltip": "Indica si es un correo de rol (p. ej. soporte@, info@) en lugar de una cuenta personal" + }, + "copy": { + "emailCopied": "¡Correo copiado!", + "copyTooltip": "Copiar correo" + }, + "bulk": { + "dropHere": "Suelta el archivo aquí", + "dragHere": "Arrastra y suelta el archivo aquí", + "uploadHint": "Sube un .xlsx, .xls o .csv con una columna «email» para validar hasta 4000 correos.", + "filePrefix": "Archivo:", + "selectFile": "Elegir archivo", + "downloadTemplate": "Descargar plantilla" + }, + "bulkLoading": { + "title": "Validando correos...", + "batchProgress": "Procesando lote {current} de {total}..." + }, + "stats": { + "totalProcessed": "Total procesados", + "valid": "Válidos", + "risky": "Riesgo", + "invalid": "No válidos" + }, + "results": { + "detailedTitle": "Resultados detallados", + "searchPlaceholder": "Buscar correos...", + "newVerification": "Nueva verificación", + "exportAll": "Exportar todo", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Exportar válidos ({count})" + }, + "showingResults": "Mostrando {shown} de {total} resultados", + "table": { + "email": "Correo", + "status": "Estado", + "score": "Puntuación", + "checks": "Comprobaciones" + }, + "noSearchResults": "No hay resultados para \"{query}\"", + "checksTooltip": { + "syntax": "Sintaxis", + "domain": "Dominio", + "mx": "MX", + "mailbox": "Buzón" + }, + "exportColumns": { + "email": "Correo", + "status": "Estado", + "score": "Puntuación", + "syntax": "Sintaxis", + "domain": "Dominio", + "mx": "MX", + "mailbox": "Buzón", + "disposable": "Desechable", + "roleBased": "Rol", + "valid": "Válido", + "invalid": "No válido", + "yes": "Sí", + "no": "No" + }, + "sheets": { + "validEmails": "Correos válidos", + "results": "Resultados", + "template": "Plantilla" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "Explorador NoSQL", + "heroSubtitle": "Conéctate a tus bases de datos para gestionar colecciones, ejecutar consultas y visualizar datos.", + "featureMultiTitle": "Multiconexión", + "featureMultiDesc": "Gestiona varias conexiones a la vez.", + "featureMongoTitle": "Soporte MongoDB", + "featureMongoDesc": "Soporte nativo de MongoDB con filtros avanzados.", + "featureQueryTitle": "Consultas inteligentes", + "featureQueryDesc": "Constructor de consultas potente con resaltado de sintaxis.", + "connectCta": "Conectar a MongoDB", + "footerNote": "Por ahora solo {db}. Pronto habrá más bases de datos.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Elige una colección", + "selectCollectionDesc": "Selecciona una colección en la barra lateral para ver documentos y explorar tus datos.", + "dialogAddConnection": "Añadir conexión", + "toastConnectionAdded": "Conexión añadida", + "toastFetchFailed": "No se pudieron cargar documentos de {collection}", + "toastDocDeleted": "Documento eliminado", + "deleteDocTitle": "¿Seguro?", + "deleteDocDescription": "Esta acción no se puede deshacer. Se eliminará el documento de la colección de forma permanente.", + "closeAllTitle": "¿Cerrar todas las pestañas?", + "closeAllDescription": "¿Quieres cerrar todas las pestañas? Se perderán los filtros de consulta no guardados.", + "closeAllConfirm": "Cerrar todas", + "cancel": "Cancelar", + "delete": "Eliminar" + }, + "connection": { + "titleEdit": "Editar conexión", + "titleConnect": "Conectar a MongoDB", + "subtitleEdit": "Actualiza los datos de conexión abajo.", + "subtitleConnect": "Introduce la cadena de conexión de MongoDB para empezar.", + "labelName": "Nombre", + "placeholderName": "p. ej. Producción", + "labelConnectionString": "Cadena de conexión", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Formato: mongodb://usuario:contraseña@host:puerto/base", + "testConnection": "Probar conexión", + "connecting": "Conectando...", + "updateConnect": "Actualizar y conectar", + "connect": "Conectar", + "cancelEdit": "Cancelar edición", + "savedConnections": "Conexiones guardadas", + "emptySaved": "Aún no hay conexiones guardadas.", + "lastUsed": "Último uso: {time}", + "toastTestOk": "¡Conexión correcta!", + "toastTestFail": "Error de conexión: {message}", + "toastUpdated": "Conexión actualizada", + "confirmDelete": "¿Eliminar esta conexión guardada?", + "toastDeletedConn": "Conexión eliminada", + "toastDeleteFail": "No se pudo eliminar la conexión", + "copyStringTitle": "Copiar cadena de conexión", + "toastStringCopied": "¡Cadena copiada!", + "editTitle": "Editar", + "deleteTitle": "Eliminar" + }, + "sidebar": { + "explorer": "Explorador", + "add": "Añadir", + "tooltipAdd": "Nueva conexión", + "tooltipRefresh": "Actualizar conexiones", + "searchPlaceholder": "Buscar...", + "loadingConnections": "Cargando conexiones...", + "noConnectionsFound": "No hay conexiones", + "toastLoadFail": "No se pudieron cargar las conexiones", + "toastConnectFail": "No se pudo conectar a {name}", + "toastCollectionsFail": "No se pudieron cargar colecciones de {name}", + "toastRenamed": "Conexión renombrada", + "toastRenameFail": "No se pudo renombrar la conexión", + "confirmDeleteConnection": "¿Eliminar la conexión «{name}»?", + "toastDeleted": "Conexión eliminada", + "toastDeleteConnFail": "No se pudo eliminar la conexión", + "confirmDropDb": "¿Eliminar la base de datos «{name}»? No se puede deshacer.", + "toastDbDropped": "Base de datos {name} eliminada", + "confirmDropCollection": "¿Eliminar la colección «{name}»? No se puede deshacer.", + "toastCollectionDropped": "Colección {name} eliminada", + "toastCollectionRenamed": "Colección renombrada a {name}", + "toastDatabaseRenamed": "Base de datos renombrada a {name}", + "connecting": "Conectando...", + "connectionFailed": "Conexión fallida", + "noDatabases": "Sin bases de datos", + "loading": "Cargando...", + "noCollections": "Sin colecciones", + "renameCollectionTitle": "Renombrar colección", + "renameDatabaseTitle": "Renombrar base de datos", + "placeholderNewCollection": "Nuevo nombre de colección", + "placeholderNewDatabase": "Nuevo nombre de base de datos", + "rename": "Renombrar", + "refresh": "Actualizar", + "copyDbName": "Copiar nombre de BD", + "toastDbNameCopied": "Nombre copiado al portapapeles", + "dropDatabase": "Eliminar base de datos", + "dropCollection": "Eliminar colección", + "menuRename": "Renombrar", + "menuCopyConnectionString": "Copiar cadena de conexión", + "menuDeleteConnection": "Eliminar conexión", + "toastSidebarStringCopied": "¡Cadena de conexión copiada!", + "cancel": "Cancelar", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} documentos", + "table": "Tabla", + "json": "JSON", + "tree": "Árbol", + "tooltipTable": "Vista tabla", + "tooltipJson": "Vista JSON", + "tooltipTree": "Vista árbol", + "itemsPerPage": "Elementos por página", + "perPage": "{n} / pág.", + "prevPage": "Página anterior", + "nextPage": "Página siguiente", + "expandAll": "Expandir todo", + "collapseAll": "Contraer todo", + "refreshData": "Actualizar datos", + "insert": "Insertar", + "export": "Exportar", + "actions": "Acciones", + "invalidJson": "JSON no válido", + "copiedClipboard": "Copiado al portapapeles", + "docUpdated": "Documento actualizado", + "docInserted": "Documento insertado", + "invalidJsonShort": "JSON no válido", + "docCopied": "Documento copiado", + "openFullEditor": "Abrir en editor completo", + "cellArray": "Array({length})", + "cellObject": "Objeto", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Ningún documento coincide", + "emptyFilterHint": "Prueba a ajustar el filtro o borrar la consulta.", + "clearFilter": "Quitar filtro", + "emptyCollectionTitle": "Colección vacía", + "emptyCollectionHint": "Inserta un documento para empezar.", + "insertDocument": "Insertar documento", + "formatJson": "Formatear JSON", + "compactJson": "Compactar JSON", + "copyClipboard": "Copiar al portapapeles", + "copyJson": "Copiar JSON", + "duplicate": "Duplicar", + "editDoc": "Editar documento", + "deleteDoc": "Eliminar documento", + "documentLabel": "Documento {n}", + "editDialogTitle": "Editar documento", + "insertDialogTitle": "Insertar documento", + "viewValueTitle": "Ver valor", + "saveChanges": "Guardar cambios", + "close": "Cerrar", + "cancel": "Cancelar", + "insertButton": "Insertar", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Filtro activo", + "filterActiveTooltip": "Filtro activo", + "closeAll": "Cerrar todas" + }, + "query": { + "migrateHistory": "Historial de consultas migrado a la nube", + "saveHistoryFail": "No se pudo guardar el historial", + "deleteQueryFail": "No se pudo eliminar la consulta", + "invalidJsonQuery": "Consulta JSON no válida", + "placeholder": "p. ej. { \"status\": \"active\" }", + "clearQuery": "Borrar consulta", + "openAdvanced": "Abrir editor avanzado", + "visualBuilder": "Constructor visual", + "filterRules": "Reglas de filtro", + "addRule": "Añadir regla", + "noFilters": "Sin filtros. Añade una regla.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "Campo", + "opPlaceholder": "Op.", + "typePlaceholder": "Tipo", + "opRegex": "Regex", + "opIn": "En", + "opNin": "No en", + "opExists": "Existe", + "typeAuto": "Auto", + "typeString": "Texto", + "typeNumber": "Número", + "typeBoolean": "Booleano", + "typeDate": "Fecha", + "typeObjectId": "ObjectId", + "true": "Verdadero", + "false": "Falso", + "valuePlaceholder": "Valor", + "valueListPlaceholder": "valor1, valor2", + "clearFilters": "Quitar filtros", + "applyFilters": "Aplicar filtros", + "queryHistory": "Historial de consultas", + "noSavedQueries": "Aún no hay consultas guardadas", + "advancedEditorTitle": "Editor de consulta avanzado", + "templatesPlaceholder": "Plantillas", + "templateEmptyAgg": "Agregación vacía", + "templateMatch": "$match (filtro)", + "templateLookup": "$lookup (join)", + "templateGroup": "$group (conteo/suma)", + "format": "Formatear", + "formattedJson": "JSON formateado", + "runQuery": "Ejecutar consulta" + }, + "export": { + "selectFieldsError": "Selecciona al menos un campo", + "success": "Exportado como {filename}", + "fail": "Error al exportar", + "title": "Exportar datos", + "formatLabel": "Formato", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Campos", + "selectAll": "Seleccionar todo", + "fieldsSelected": "{count, plural, one {# campo seleccionado} other {# campos seleccionados}}", + "cancel": "Cancelar", + "export": "Exportar", + "sheetName": "Datos" + }, + "jsonTree": { + "typeLabel": "Tipo: {type}", + "itemSummary": "{count, plural, one {# elemento} other {# elementos}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Nueva solicitud", + "importedRequest": "Solicitud importada", + "untitledRequest": "Sin título" + }, + "toasts": { + "curlImported": "cURL importado correctamente", + "curlParseFailed": "No se pudo analizar el comando cURL", + "invalidJsonBody": "Cuerpo JSON no válido", + "requestFailed": "Error en la solicitud: {message}", + "curlPasted": "cURL pegado y analizado correctamente", + "responseCopied": "Respuesta copiada al portapapeles", + "codeCopied": "Código copiado al portapapeles", + "copyFailed": "Error al copiar al portapapeles" + }, + "layout": { + "collections": "Colecciones", + "closeSidebar": "Cerrar barra lateral", + "openSidebar": "Abrir barra lateral", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Método", + "urlPlaceholder": "https://api.ejemplo.com/v1/...", + "sending": "Enviando...", + "send": "Enviar", + "invalidJsonBodyHelp": "No se puede enviar: el cuerpo JSON no es válido" + }, + "requestTabs": { + "params": "Parámetros", + "headers": "Cabeceras", + "body": "Cuerpo", + "auth": "Autenticación", + "bodyType": "Tipo de cuerpo", + "selectType": "Seleccionar tipo", + "bodyNone": "Ninguno", + "bodyJson": "JSON", + "bodyText": "Texto", + "noBodyHint": "Sin cuerpo para esta solicitud", + "switchToJson": "Usar cuerpo JSON", + "authType": "Tipo de autenticación", + "noAuth": "Sin autenticación", + "bearerToken": "Token Bearer", + "basicAuth": "Autenticación básica", + "apiKey": "Clave API", + "labelToken": "Token", + "placeholderBearerToken": "Token Bearer", + "labelUsername": "Usuario", + "placeholderUsername": "Usuario", + "labelPassword": "Contraseña", + "placeholderPassword": "Contraseña", + "labelKey": "Clave", + "placeholderApiKeyName": "Nombre de la clave (p. ej. X-Api-Key)", + "labelValue": "Valor", + "placeholderValue": "Valor", + "addTo": "Añadir a", + "header": "Cabecera", + "queryParams": "Parámetros de consulta", + "noAuthHint": "Esta solicitud no usa autenticación." + }, + "responsePanel": { + "emptyTitle": "¿Listo para explorar?", + "emptyHint": "Introduce una URL y pulsa Enviar para ver la respuesta aquí.", + "responsePreviewAlt": "Vista previa de la respuesta", + "previewUnavailable": "Vista previa no disponible para este tipo de contenido", + "status": "Estado", + "time": "Tiempo", + "size": "Tamaño", + "copyBody": "Copiar cuerpo", + "downloadResponse": "Descargar respuesta", + "bodyTab": "Cuerpo", + "previewTab": "Vista previa", + "headersTab": "Cabeceras", + "binaryRawView": "Los datos binarios no se pueden mostrar en vista en bruto.", + "errorStatusLabel": "Error", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Nueva pestaña" + }, + "collectionsSidebar": { + "title": "Recursos API", + "newCollection": "Nueva colección", + "tabCollections": "Colecciones", + "tabHistory": "Historial", + "noCollectionsTitle": "Sin colecciones", + "noCollectionsHint": "Crea una colección para organizar tus solicitudes API.", + "createCollection": "Crear colección", + "newFolder": "Nueva carpeta", + "rename": "Renombrar", + "delete": "Eliminar", + "emptyCollection": "Colección vacía", + "recentRequests": "Solicitudes recientes", + "clearAll": "Borrar todo", + "noHistoryTitle": "Aún no hay historial", + "noHistoryHint": "Envía una solicitud para verla aquí.", + "dialogNewFolderTitle": "Nueva carpeta", + "dialogNewFolderDescription": "Crea una carpeta para organizar tus solicitudes.", + "labelFolderName": "Nombre de la carpeta", + "placeholderFolderName": "Mi carpeta", + "createFolder": "Crear carpeta", + "dialogNewCollectionTitle": "Nueva colección", + "dialogNewCollectionDescription": "Crea una colección de nivel superior.", + "labelCollectionName": "Nombre de la colección", + "placeholderCollectionName": "Mi colección", + "dialogRenameCollectionTitle": "Renombrar colección", + "dialogRenameCollectionDescription": "Introduce un nuevo nombre para la colección.", + "placeholderRenameCollection": "Nombre de la colección", + "renameAction": "Renombrar", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Nueva carpeta", + "delete": "Eliminar", + "emptyFolder": "Carpeta vacía" + }, + "environmentManager": { + "noEnvironment": "Sin entorno", + "manageDialogTitle": "Gestionar entornos", + "placeholderNewEnv": "Nuevo entorno", + "variablesHeading": "Variables de {name}", + "addVariable": "Añadir variable", + "columnVariable": "Variable", + "columnValue": "Valor", + "placeholderKey": "Clave", + "placeholderValue": "Valor", + "selectEnvToEdit": "Selecciona un entorno para editar" + }, + "codeGenerator": { + "triggerTitle": "Generar código", + "dialogTitle": "Generar código", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Copiar", + "errorGenerating": "Error al generar código" + }, + "keyValueEditor": { + "columnKey": "Clave", + "columnValue": "Valor", + "placeholderKey": "Clave", + "placeholderValue": "Valor", + "addItem": "Añadir fila" + }, + "importCurl": { + "trigger": "Importar cURL", + "dialogTitle": "Importar cURL", + "dialogDescription": "Pega tu comando cURL para importarlo como una nueva solicitud.", + "placeholder": "curl -X POST https://api.ejemplo.com/datos -d '...'", + "import": "Importar" + }, + "saveRequest": { + "trigger": "Guardar", + "dialogTitle": "Guardar solicitud", + "dialogDescription": "Guarda esta solicitud en una carpeta de la colección.", + "labelName": "Nombre de la solicitud", + "placeholderName": "Mi solicitud", + "labelFolder": "Carpeta", + "placeholderFolder": "Selecciona una carpeta", + "save": "Guardar" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Modo edición", + "viewOnlyMode": "Solo lectura", + "insertComponent": "Insertar componente", + "addUnorderedList": "Lista con viñetas", + "addOrderedList": "Lista numerada", + "addTable": "Agregar tabla" + }, + "addBlock": "Agregar bloque", + "media": { + "popoverTitle": "Agregar multimedia", + "singleImage": "Una imagen", + "singleImageDesc": "Subir una imagen", + "multipleImages": "Varias imágenes", + "multipleImagesDesc": "Subir cuadrícula de imágenes", + "video": "Vídeo", + "videoDesc": "Subir archivo de vídeo", + "uploadingAlt": "Subiendo...", + "uploadFailedAlt": "Error al subir" + }, + "export": { + "fabAria": "Exportar código", + "title": "Exportar código", + "description": "Copia la salida HTML o JSON del contenido del editor", + "preview": "Vista previa", + "html": "HTML", + "json": "JSON", + "previewOptions": "Opciones de vista previa", + "enhanceSpaces": "Mejorar espacios", + "livePreview": "Vista previa en vivo del HTML renderizado", + "htmlTailwind": "HTML con clases Tailwind CSS", + "editorJson": "Estado del editor como JSON", + "copyHtml": "Copiar HTML", + "copyJson": "Copiar JSON", + "copied": "¡Copiado!" + }, + "template": { + "fabTitle": "Cambiar plantilla", + "title": "Elegir una plantilla", + "description": "Selecciona una plantilla para empezar rápido con diseños y contenido predefinidos", + "catAll": "Todas las plantillas", + "catProductivity": "Productividad", + "catCreative": "Creativo", + "catBusiness": "Negocios", + "catPersonal": "Personal", + "noTemplates": "No hay plantillas en esta categoría", + "confirmTitle": "¿Reemplazar el contenido existente?", + "confirmDescription": "Ya hay contenido en el editor. Aplicar esta plantilla reemplazará todo el contenido actual.", + "cannotUndo": "Esta acción no se puede deshacer.", + "cancel": "Cancelar", + "replaceContent": "Reemplazar contenido" + }, + "command": { + "searchPlaceholder": "Buscar comandos...", + "noResults": "No se encontraron comandos.", + "turnInto": "Convertir en", + "h1": { + "label": "Título 1", + "description": "Título de sección grande", + "keywords": "h1,título,encabezado,grande" + }, + "h2": { + "label": "Título 2", + "description": "Título de sección mediano", + "keywords": "h2,título,subtítulo" + }, + "h3": { + "label": "Título 3", + "description": "Título de sección pequeño", + "keywords": "h3,título,subtítulo" + }, + "h4": { + "label": "Título 4", + "description": "Título de sección muy pequeño", + "keywords": "h4,título" + }, + "h5": { + "label": "Título 5", + "description": "Título más pequeño", + "keywords": "h5,título" + }, + "h6": { + "label": "Título 6", + "description": "Título de sección mínimo", + "keywords": "h6,título" + }, + "p": { + "label": "Párrafo", + "description": "Párrafo de texto normal", + "keywords": "p,párrafo,texto,normal" + }, + "code": { + "label": "Bloque de código", + "description": "Fragmento de código", + "keywords": "código,codigo,snippet,pre" + }, + "blockquote": { + "label": "Cita", + "description": "Cita en bloque", + "keywords": "cita,cita en bloque" + }, + "li": { + "label": "Lista con viñetas", + "description": "Elemento de lista con viñeta", + "keywords": "lista,viñeta,desordenada,ul,li" + }, + "ol": { + "label": "Lista numerada", + "description": "Elemento de lista numerada", + "keywords": "lista,numerada,ordenada,ol,li" + }, + "img": { + "label": "Imagen", + "description": "Subir o insertar una imagen", + "keywords": "imagen,foto,subir" + }, + "video": { + "label": "Vídeo", + "description": "Subir o insertar un vídeo", + "keywords": "vídeo,video,mp4,subir" + }, + "table": { + "label": "Tabla", + "description": "Crear una tabla", + "keywords": "tabla,cuadrícula,filas,columnas" + } + }, + "toasts": { + "tableImported": "Tabla importada", + "tableImportedDesc": "La tabla Markdown se importó correctamente", + "mediaPasted": "Multimedia pegada", + "pasteMixed": "{imageCount} imagen(es) y {videoCount} vídeo(s) pegados correctamente.", + "pasteImages": "{count} imagen(es) pegada(s) correctamente.", + "pasteVideos": "{count} vídeo(s) pegado(s) correctamente.", + "pasteFailed": "Error al pegar", + "unexpectedError": "Ocurrió un error inesperado", + "uploadFailed": "Error al subir" + }, + "blockBackground": { + "menuItem": "Cambiar color de fondo", + "dialogTitle": "Color de fondo del bloque", + "dialogDescription": "Elige un color de fondo para este bloque", + "tabPreset": "Colores predefinidos", + "tabCustom": "Color personalizado", + "sectionHeading": "Colores de fondo", + "apply": "Aplicar", + "colors": { + "none": "Ninguno", + "red": "Rojo", + "orange": "Naranja", + "yellow": "Amarillo", + "green": "Verde", + "blue": "Azul", + "indigo": "Índigo", + "purple": "Morado", + "pink": "Rosa", + "teal": "Verde azulado", + "cyan": "Cian", + "gray": "Gris" + } + }, + "tableDialog": { + "title": "Crear tabla", + "description": "Crea una tabla nueva o importa desde Markdown", + "markdownCheckbox": "Tengo una tabla en Markdown", + "pasteMarkdownLabel": "Pegar tabla Markdown", + "markdownPlaceholder": "| Encabezado 1 | Encabezado 2 | Encabezado 3 |\n|--------------|--------------|--------------|\n| Celda 1 | Celda 2 | Celda 3 |\n| Celda 4 | Celda 5 | Celda 6 |", + "markdownHint": "Pega tu tabla Markdown arriba. Debe incluir fila de encabezado y fila separadora.", + "rows": "Filas", + "columns": "Columnas", + "maxDimensions": "Máximo: 20 filas × 10 columnas", + "cancel": "Cancelar", + "importTable": "Importar tabla", + "createTable": "Crear tabla" + }, + "tableParse": { + "errors": { + "minRows": "La tabla debe tener al menos una fila de encabezado y una fila separadora", + "pipeFormat": "Las filas deben empezar y terminar con |", + "headerMinCol": "La fila de encabezado debe tener al menos una columna", + "separatorRow": "La segunda fila debe ser un separador (p. ej., |---|---|)", + "rowColumnMismatch": "La fila {row} tiene {got} columnas; se esperaban {expected}", + "parseFailed": "No se pudo analizar la tabla Markdown" + } + }, + "link": { + "editLink": "Editar enlace", + "addLink": "Añadir enlace", + "selectedText": "Texto seleccionado: \"{text}\"", + "linkUrl": "URL del enlace", + "urlPlaceholder": "https://ejemplo.com", + "update": "Actualizar", + "addLinkButton": "Añadir enlace", + "toastApplied": "Enlace aplicado", + "toastLinkedTo": "Enlazado a: {url}", + "toastRemoved": "Enlace eliminado", + "toastRemovedDesc": "Se ha quitado el enlace de la selección" + }, + "customClass": { + "devMode": "Modo desarrollador", + "toggleDevModeAria": "Activar o desactivar modo desarrollador", + "searchPlaceholderDev": "Buscar clases... (p. ej. 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Buscar estilos... (p. ej. 'rojo', 'negrita', 'sombra')", + "appliesTitle": "Aplica: {className}", + "noMatches": "No hay clases que coincidan con \"{query}\"", + "toastClassReplaced": "Clase reemplazada", + "toastClassReplacedDesc": "Se reemplazó \"{replaced}\" por \"{className}\"", + "toastCustomClassApplied": "Clase personalizada aplicada", + "toastAppliedClass": "Clase aplicada: {className}" + }, + "insertComponents": { + "title": "Insertar componente", + "description": "Elige un componente para insertar en el documento", + "noComponents": "No hay componentes disponibles", + "categories": { + "media": "Multimedia", + "layout": "Diseño", + "interactive": "Interactivo", + "decoration": "Decoración" + }, + "freeImage": { + "name": "Imagen libre", + "description": "Añade una imagen que puedes colocar y redimensionar libremente" + } + } + }, + "JsonFormatter": { + "title": "Formateador JSON", + "description": "Formatea, valida y edita JSON en vista de texto y árbol.", + "documentNameText": "Documento de texto {n}", + "documentNameTree": "Documento en árbol {n}", + "newDocument": "Nuevo", + "copy": "Copiar", + "save": "Guardar", + "load": "Cargar", + "saving": "Guardando...", + "leftDocNameLabel": "Nombre del documento izquierdo", + "rightDocNameLabel": "Nombre del documento derecho", + "toastCopiedText": "Panel de texto copiado", + "toastCopiedTree": "Panel de árbol copiado", + "toastCopyFailed": "Error al copiar JSON", + "toastLoginRequired": "Inicia sesión para guardar documentos", + "toastSaved": "Documento guardado", + "toastSaveFailed": "Error al guardar el documento", + "format": "Formatear", + "compact": "Compactar", + "sortKeys": "Ordenar claves", + "repair": "Reparar", + "query": "Consulta", + "toastInvalidJson": "JSON no válido", + "toastAlreadyValid": "El JSON ya es válido", + "toastRepaired": "JSON reparado", + "toastRepairFailed": "No se pudo reparar el JSON" + }, + "JwtDecoder": { + "title": "Decodificador JWT", + "subtitle": "Inspecciona la cabecera y la carga útil. La caducidad usa la reclamación exp. No se envía nada a un servidor; la firma no se verifica.", + "tokenLabel": "Token", + "clear": "Limpiar", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Copiar", + "expiryLabel": "Caducidad (exp)", + "signaturePresent": "Firma presente (no verificada)", + "noExp": "No hay reclamación exp en la carga útil.", + "status": { + "expired": "Caducado", + "valid": "Válido" + }, + "fields": { + "unix": "Unix", + "local": "Local", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Emitido en (iat)", + "notBefore": "No antes de (nbf)" + }, + "panels": { + "header": "Cabecera", + "payload": "Carga útil" + }, + "errors": { + "empty": "Pega un JWT para decodificar.", + "segments": "Un JWT necesita al menos una cabecera y una carga útil (dos segmentos).", + "base64": "No se pudo decodificar en base64url la cabecera o la carga útil." + }, + "relative": { + "future": "en {n} {unit}", + "past": "hace {n} {unit}" + }, + "units": { + "second": "{count, plural, one {segundo} other {segundos}}", + "minute": "{count, plural, one {minuto} other {minutos}}", + "hour": "{count, plural, one {hora} other {horas}}", + "day": "{count, plural, one {día} other {días}}" + }, + "tabs": { + "decode": "Decodificar", + "sign": "Generador / firmador" + }, + "signer": { + "algLabel": "Algoritmo (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "JSON del payload", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON del header", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Secreto (HS*)", + "secretPlaceholder": "Pega tu secreto HMAC…", + "privateKeyLabel": "Clave privada PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "El algoritmo \"none\" crea un token sin firmar. Es inseguro y solo debe usarse para pruebas.", + "sign": "Firmar", + "verify": "Verificar firma", + "outputLabel": "Token (JWS compacto)", + "outputPlaceholder": "Haz clic en \"Firmar\"…", + "copyToken": "Copiar token", + "copied": "Copiado", + "verifyKeyLabel": "Clave de verificación (opcional)", + "verifyKeyPlaceholder": "Para RS*: pega la clave pública (SPKI) PEM. Para HS*: déjalo vacío para reutilizar el secreto.", + "verifyKeyHint": "Si está vacío, la verificación usa la clave de firma (útil para HS*).", + "verifyStatus": { + "valid": "Firma válida", + "invalid": "Firma no válida" + }, + "securityNote": "Todo se ejecuta localmente en tu navegador; no se suben claves ni tokens." + }, + "signErrors": { + "invalidJson": "El header y el payload deben ser JSON válido.", + "unsupportedAlg": "Algoritmo no compatible.", + "missingKey": "Se requiere una clave de firma para este algoritmo.", + "cryptoFailed": "Falló la firma. Comprueba el formato de la clave e inténtalo de nuevo." + }, + "verifyErrors": { + "empty": "Introduce un JWT para verificar.", + "segments": "Un JWT debe tener exactamente 3 segmentos para verificar una firma.", + "unsupportedAlg": "Algoritmo no compatible o falta alg en el header.", + "missingKey": "Se requiere una clave de verificación para este algoritmo.", + "base64": "No se pudo decodificar el header o la firma del JWT.", + "cryptoFailed": "Falló la verificación. Comprueba el formato de la clave e inténtalo de nuevo." + } + }, + "CertificatePemDecoder": { + "title": "Decodificador de certificados / PEM", + "subtitle": "Pega un certificado X.509 (-----BEGIN CERTIFICATE-----) o una CSR (-----BEGIN CERTIFICATE REQUEST-----). Sujeto, emisor, fechas y SAN se analizan en local; no se sube nada.", + "pemLabel": "Entrada PEM", + "clear": "Borrar", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Analizando…", + "copyHex": "Copiar huella (hex sin dos puntos)", + "errors": { + "empty": "Pega un certificado PEM o una CSR para decodificar.", + "noBlock": "No se encontró ningún bloque PEM de certificado o CSR.", + "parseFailed": "No se pudieron analizar estos datos PEM." + }, + "block": { + "title": "Bloque PEM {index}", + "certificate": "Certificado X.509", + "csr": "Solicitud de firma de certificado" + }, + "fields": { + "subject": "Sujeto", + "issuer": "Emisor", + "notBefore": "No válido antes de", + "notAfter": "No válido después de", + "serial": "Número de serie (hex)", + "sha256": "Huella SHA-256", + "sans": "Nombres alternativos del sujeto", + "sansNone": "Sin extensión SAN.", + "signatureAlgorithm": "Algoritmo de firma", + "csrIssuerNote": "Las CSR no incluyen emisor (aparece en el certificado emitido).", + "csrValidityNote": "Las CSR no incluyen fechas de validez (aparecen en el certificado emitido)." + }, + "sanType": { + "dns": "DNS", + "dn": "Nombre de directorio", + "email": "Correo", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID registrado", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Formateador SQL", + "subtitle": "Da formato a SQL para MySQL, PostgreSQL o SQLite. Se ejecuta en tu navegador; no se sube nada.", + "dialectLabel": "Dialecto", + "keywordsLabel": "Palabras clave", + "indentLabel": "Sangría", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "MAYÚSCULAS", + "lower": "minúsculas", + "preserve": "Conservar" + }, + "spaces": "{n} espacios", + "format": "Formatear", + "clear": "Limpiar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "formattedPanel": "Formateado", + "copy": "Copiar", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Haz clic en «Formatear»…", + "errors": { + "tooLong": "La entrada está limitada a {max} caracteres.", + "couldNotFormat": "No se pudo formatear este SQL." + } + }, + "YamlFormatter": { + "title": "Formateador YAML", + "subtitle": "Formatea, valida y convierte YAML en tu navegador. Nada se sube.", + "outputLabel": "Salida", + "outputModes": { + "yaml": "YAML (formateado)", + "json": "JSON" + }, + "indentLabel": "Sangría", + "spaces": "{n} espacios", + "format": "Formatear", + "clear": "Limpiar", + "copy": "Copiar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "outputPanel": "Salida", + "errors": { + "tooLong": "La entrada está limitada a {max} caracteres.", + "couldNotFormat": "No se pudo analizar este YAML." + } + }, + "GraphqlFormatter": { + "title": "Formateador GraphQL", + "subtitle": "Formatea consultas, mutaciones y suscripciones GraphQL en el navegador. No se sube nada.", + "tabs": { + "format": "Formatear", + "build": "Editor de consulta" + }, + "outputModeLabel": "Salida", + "outputModes": { + "pretty": "Impresión legible", + "minify": "Minificar" + }, + "format": "Formatear", + "clear": "Borrar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "outputPanel": "Salida", + "copy": "Copiar", + "builderHint": "Compón un esqueleto y aplícalo al editor. Las definiciones de variables usan sintaxis GraphQL (p. ej. $id: ID!) — los paréntesis son opcionales.", + "builder": { + "operation": "Operación", + "operationName": "Nombre de la operación", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definiciones de variables", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Conjunto de selección", + "apply": "Aplicar a la entrada", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "La entrada está limitada a {max} caracteres.", + "couldNotFormat": "No se pudo analizar ni volcar este GraphQL.", + "emptySelection": "Añade un conjunto de selección antes de aplicar." + } + }, + "RegexTester": { + "title": "Probador de regex", + "subtitle": "Regex estilo JavaScript en vivo sobre tu texto. Las coincidencias se resaltan; nada sale de tu navegador.", + "patternLabel": "Patrón", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i sin distinción mayús/minús", + "m": "m multilínea", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(vacío)", + "noFlags": "—", + "matches": "{count, plural, one {coincidencia} other {coincidencias}}", + "testTextLabel": "Texto de prueba", + "counter": "{current} / {max}", + "clear": "Limpiar", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "El texto está limitado a {max} caracteres.", + "invalid": "Expresión regular no válida.", + "runaway": "Se detuvo tras {max} coincidencias (posible patrón descontrolado)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Conversor de marcas de tiempo", + "subtitle": "Segundos Unix (≤10 dígitos) o milisegundos (enteros más largos), ISO-8601 o cadenas que tu navegador pueda interpretar. Todo se ejecuta localmente.", + "inputLabel": "Entrada", + "placeholder": "1744200000 o 2025-04-09T12:00:00Z", + "now": "Ahora", + "copy": "Copiar", + "hint": "Pega un valor o haz clic en Ahora.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativo", + "human": "Legible" + }, + "fields": { + "seconds": "Segundos", + "milliseconds": "Milisegundos", + "utc": "UTC", + "local": "Local", + "distance": "Distancia" + }, + "errors": { + "empty": "Introduce un timestamp Unix, una cadena ISO o un texto de fecha.", + "invalidNumber": "Timestamp numérico no válido.", + "outOfRange": "Fuera de rango para Date de JavaScript.", + "couldNotParse": "No se pudo interpretar como Unix, ISO-8601 o fecha del navegador." + } + }, + "HttpStatusCodes": { + "title": "Códigos de estado HTTP", + "subtitle": "Busca y explora códigos de estado de respuestas HTTP (1xx5xx) con descripciones breves y referencias RFC. Se ejecuta localmente.", + "header": { + "title": "Códigos de estado HTTP", + "description": "Referencia con búsqueda de códigos de respuesta HTTP con descripciones y referencias RFC." + }, + "searchPlaceholder": "Buscar: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtrar clase…", + "classFilterAll": "Todas las clases", + "resultsCount": "{count} de {total}", + "table": { + "code": "Code", + "phrase": "Frase", + "description": "Description", + "copy": "Copiar" + }, + "noResults": "No hay resultados para “{query}”.", + "specPrefix": "Espec.:", + "copied": "Código copiado" + }, + "CronBuilder": { + "title": "Constructor de expresiones Cron", + "subtitle": "Cron estándar de 5 campos: minuto hora día mes día_semana. El analizador usa seconds=0 internamente. El formato opcional de 6 campos (seg min hora dom mes dow) funciona en el editor.", + "tabs": { + "builder": "Constructor visual", + "expression": "Expresión" + }, + "presetsLabel": "Preajustes", + "fieldsLabel": "Campos", + "rawExpressionLabel": "Expresión cruda", + "expressionHint": "Quita el foco del campo para analizar y sincronizar el constructor. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Expresión actual", + "copy": "Copiar", + "quickPick": "Selección rápida", + "emptyExpression": "—", + "plainLanguageLabel": "Lenguaje natural", + "nextRunsLabel": "Próximas ejecuciones (hora local)", + "fields": { + "minute": "Minuto", + "hour": "Hora", + "dayOfMonth": "Día del mes", + "month": "Mes", + "dayOfWeek": "Día de la semana" + }, + "presets": { + "everyMinute": "Cada minuto", + "every5Minutes": "Cada 5 minutos", + "every15Minutes": "Cada 15 minutos", + "everyHour": "Cada hora (en :00)", + "every6Hours": "Cada 6 horas", + "dailyMidnight": "Diario a medianoche", + "daily0900": "Diario a las 09:00", + "weekdays0900": "Laborables a las 09:00", + "weeklySunday": "Semanal (domingo 00:00)", + "monthlyFirst": "Mensual (día 1 00:00)", + "yearlyJan1": "Anual (1 ene 00:00)" + }, + "picks": { + "minute": { + "every": "cada minuto" + }, + "hour": { + "every": "cada hora" + }, + "dom": { + "any": "cualquier día", + "last": "último día" + }, + "month": { + "every": "cada mes" + }, + "dow": { + "any": "cualquier día", + "weekdays": "laborables", + "weekend": "fin de semana" + } + }, + "months": { + "jan": "Ene", + "feb": "Feb", + "mar": "Mar", + "apr": "Abr", + "may": "May", + "jun": "Jun", + "jul": "Jul", + "aug": "Ago", + "sep": "Sep", + "oct": "Oct", + "nov": "Nov", + "dec": "Dic" + }, + "weekdays": { + "sun": "Dom", + "mon": "Lun", + "tue": "Mar", + "wed": "Mié", + "thu": "Jue", + "fri": "Vie", + "sat": "Sáb" + }, + "errors": { + "fieldCount": "Se esperaban 5 campos (minuto hora día mes día_semana) o 6 incluyendo segundos.", + "invalid": "Expresión cron no válida." + } + }, + "Base64": { + "title": "Codificador / decodificador Base64", + "subtitle": "Codifica texto en Base64 o decodifica cadenas Base64", + "upload": "Subir", + "clear": "Limpiar", + "modes": { + "encode": "Codificar", + "decode": "Decodificar" + }, + "swapTitle": "Intercambiar entrada y salida", + "panels": { + "plainText": "Texto plano", + "base64Input": "Entrada Base64", + "base64Output": "Salida Base64", + "decodedText": "Texto decodificado" + }, + "placeholders": { + "encode": "Introduce texto para codificar en Base64…", + "decode": "Pega una cadena Base64 para decodificar…" + }, + "dropHere": "Suelta el archivo aquí", + "charCount": "{count} caracteres", + "download": { + "title": "Descargar salida", + "encodedFilename": "codificado.b64", + "decodedFilename": "decodificado.txt" + }, + "copyTitle": "Copiar al portapapeles", + "outputPlaceholder": "La salida aparecerá aquí…", + "errors": { + "invalidBase64": "Cadena Base64 no válida. Revisa tu entrada.", + "encodeFailed": "No se pudo codificar la entrada." + } + }, + "UrlEncode": { + "title": "Codificador / decodificador de URL", + "subtitle": "Codifica/decodifica en porcentaje con encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Subir", + "clear": "Limpiar", + "modes": { + "encode": "Codificar", + "decode": "Decodificar" + }, + "swapTitle": "Intercambiar entrada y salida", + "panels": { + "plainText": "Texto plano", + "encodedInput": "Entrada codificada", + "encodedOutput": "Salida codificada", + "decodedText": "Texto decodificado" + }, + "placeholders": { + "encode": "Introduce texto para codificar (p. ej., valores de query string)…", + "decode": "Pega texto codificado en porcentaje para decodificar…" + }, + "dropHere": "Suelta el archivo aquí", + "charCount": "{count} caracteres", + "download": { + "title": "Descargar salida", + "encodedFilename": "url-codificado.txt", + "decodedFilename": "url-decodificado.txt" + }, + "copyTitle": "Copiar al portapapeles", + "outputPlaceholder": "La salida aparecerá aquí…", + "errors": { + "invalidEncoding": "Codificación porcentual no válida. Revisa pares % incompletos o secuencias de escape.", + "encodeFailed": "No se pudo codificar (Unicode no válido en la entrada)." + } + }, + "UrlParser": { + "title": "Analizador de URL", + "subtitle": "Divide una URL en protocolo, host, ruta, parámetros de consulta y hash (fragmento).", + "upload": "Subir", + "clear": "Limpiar", + "panels": { + "input": "Entrada URL", + "output": "Resultado" + }, + "placeholders": { + "input": "Pega una URL (p. ej., https://example.com/path?foo=bar#section) o un host/ruta (p. ej., example.com/path)…" + }, + "dropHere": "Suelta el archivo aquí", + "charCount": "{count} caracteres", + "outputPlaceholder": "Los componentes analizados aparecerán aquí…", + "fields": { + "protocol": "Protocolo", + "host": "Host", + "path": "Ruta", + "hash": "Hash", + "queryParams": "Parámetros de consulta" + }, + "paramCount": "{count} parámetros", + "noParams": "Sin parámetros de consulta.", + "paramKey": "Clave", + "paramValue": "Valor", + "copyTitle": "Copiar al portapapeles", + "copyParamTitle": "Copiar como key=value", + "download": { + "title": "Descargar JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL no válida. Incluye un hostname (example.com) y revisa caracteres no permitidos." + } + }, + "UuidGenerator": { + "title": "Generador de UUID / ULID", + "subtitle": "Genera UUID (RFC 9562) o ULID en el navegador. Exportación masiva hasta {max} por ejecución.", + "format": "Formato", + "kindHints": { + "ulid": "Identificador ordenable, seguro para URL, 26 caracteres", + "uuid4": "Aleatorio (RFC 9562)", + "uuid7": "Ordenado por tiempo, ms Unix + aleatorio", + "uuid1": "Basado en MAC/tiempo (RFC 9562)", + "uuid6": "Tiempo gregoriano reordenado", + "uuid3": "Hash MD5 de nombre + espacio de nombres", + "uuid5": "Hash SHA‑1 de nombre + espacio de nombres", + "nil": "UUID nulo (todo ceros, RFC 9562)" + }, + "countLabel": "Cantidad (1–{max})", + "nameLabel": "Nombre (cadena de entrada)", + "namePlaceholder": "p. ej. mi.app/recurso", + "bulkHint": "En lotes se añade #0, #1, … para que cada ID sea distinto (v3/v5 son deterministas).", + "namespace": "Espacio de nombres", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID personalizado" + }, + "customNamespaceLabel": "UUID de espacio de nombres", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Generar", + "output": "Salida", + "lines": "{count} líneas", + "download": { + "title": "Descargar .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Copiar todo", + "uppercaseToggle": "Salida en mayúsculas", + "hyphensToggle": "Mostrar guiones", + "copyJsonTitle": "Copiar como array JSON", + "outputPlaceholder": "Haz clic en \"Generar\"…", + "errors": { + "invalidCustomNamespace": "El espacio de nombres personalizado debe ser un UUID válido.", + "missingName": "El nombre es obligatorio para UUID v3 y v5.", + "unknown": "No se pudieron generar los IDs." + } + }, + "SecretApiKeyGenerator": { + "title": "Generador de secreto / clave API", + "subtitle": "Genera cadenas aleatorias criptográficas con longitud y alfabeto configurables. Exportación masiva de hasta {max} por ejecución. Complementa el generador UUID / ULID.", + "alphabetLabel": "Alfabeto", + "alphabetHint": "Se eliminan caracteres duplicados. Cada símbolo se elige de forma uniforme con crypto.getRandomValues y muestreo por rechazo (sin sesgo de módulo).", + "alphabetCount": "{count} símbolos únicos", + "presetsLabel": "Preajustes", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumérico", + "hex": "Hexadecimal (minúsculas)", + "asciiPrintable": "ASCII imprimible" + }, + "lengthLabel": "Longitud por secreto (1–{maxLen})", + "countLabel": "Cantidad (1–{maxBulk})", + "generate": "Generar", + "output": "Salida", + "lines": "{count} líneas", + "download": { + "title": "Descargar .txt", + "filename": "secretos.txt" + }, + "copyTitle": "Copiar todo", + "outputPlaceholder": "Haz clic en \"Generar\"…", + "entropy": "{bits} bits de entropía", + "strength": { + "weak": "Débil", + "fair": "Aceptable", + "strong": "Fuerte" + }, + "excludeAmbiguous": "Excluir caracteres ambiguos", + "excludeAmbiguousHint": "Elimina {chars} — caracteres fáciles de confundir u hostiles para la shell.", + "prefixLabel": "Prefijo de la clave (opcional)", + "errors": { + "emptyAlphabet": "Añade al menos un carácter al alfabeto.", + "alphabetTooLong": "Tras quitar duplicados, el alfabeto puede tener como máximo {maxAlpha} caracteres.", + "lengthOutOfRange": "La longitud debe estar entre 1 y {maxLen}.", + "countOutOfRange": "La cantidad debe estar entre 1 y {maxBulk}.", + "unknown": "No se pudieron generar los secretos." + } + }, + "LoremIpsum": { + "title": "Generador de Lorem Ipsum", + "subtitle": "Genera texto latino de marcador de posición para tus diseños y maquetas.", + "paragraphs": "Párrafos", + "sentences": "Oraciones", + "words": "Palabras", + "count": "Cantidad", + "generate": "Generar", + "copy": "Copiar", + "startWith": "Empezar con 'Lorem ipsum...'", + "outputPlaceholder": "La salida aparecerá aquí...", + "outputType": "Tipo de salida", + "bulletList": "Lista de viñetas", + "htmlMarkup": "Marcado HTML", + "htmlMarkupHint": "Envuelve la salida en párrafos, o usa una lista para el modo de viñetas", + "output": "Salida", + "download": "Descargar" + }, + "ColorPicker": { + "title": "Selector y conversor de color", + "subtitle": "Convierte HEX, RGB y HSL; crea paletas armónicas. Todo el procesamiento ocurre en tu navegador.", + "nativePicker": "Selector nativo", + "chooseColor": "Elige un color", + "eyeDropper": "Cuentagotas", + "invalidHex": "Color HEX no válido.", + "cssValues": "Valores CSS", + "palettes": "Paletas", + "copied": "Copiado", + "clickToCopy": "Clic para copiar", + "copyAllHex": "Copiar todos los HEX", + "copy": "Copiar", + "copyColor": "Copiar {color}", + "paletteShades": "Tonos", + "paletteComplementary": "Complementario", + "paletteTriadic": "Triádico", + "paletteAnalogous": "Análogo", + "paletteSplit": "Comp. dividido", + "paletteSquare": "Cuadrado" + }, + "ContrastChecker": { + "title": "Comprobador de contraste", + "subtitle": "Calcula el ratio de contraste WCAG 2.1 entre primer plano y fondo. Comprueba AA y AAA para texto normal y grande. Todo se ejecuta en tu navegador.", + "foreground": "Texto / primer plano", + "background": "Fondo", + "hexPlaceholder": "#RRGGBB", + "swap": "Intercambiar colores", + "chooseForeground": "Elegir color de primer plano", + "chooseBackground": "Elegir color de fondo", + "eyeDropper": "Cuentagotas", + "ratio": "Ratio de contraste", + "copyRatio": "Copiar ratio", + "copied": "Copiado", + "previewHeading": "Vista previa", + "previewBody": "El veloz murciélago hindú comía feliz cardillo y kiwi. La cigüeña tocaba el saxofón detrás del palenque de paja.", + "previewCaption": "Texto de ejemplo a tamaño predeterminado.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — texto normal (4,5:1)", + "levelAALarge": "AA — texto grande (3:1)", + "levelAAANormal": "AAA — texto normal (7:1)", + "levelAAALarge": "AAA — texto grande (4,5:1)", + "pass": "Cumple", + "fail": "No cumple", + "invalidHex": "Color HEX no válido.", + "largeTextHint": "Texto grande: 18 pt+ regular o 14 pt+ en negrita (definiciones WCAG)." + }, + "DiffChecker": { + "title": "Comparador de diferencias", + "subtitle": "Comparación de líneas entre dos textos lado a lado. Todo se queda en tu navegador.", + "rowsCount": "{count} filas", + "truncatedWarning": "Mostrando las primeras {max} filas.", + "limitExceeded": "Cada lado está limitado a {max} caracteres.", + "clearBoth": "Limpiar ambos", + "original": "Original", + "modified": "Modificado", + "originalPlaceholder": "Pega el texto original…", + "modifiedPlaceholder": "Pega el texto modificado…", + "comparisonLabel": "Comparación lado a lado", + "emptyState": "Introduce texto en ambos paneles para comparar." + }, + "JsonSchemaGenerator": { + "title": "Generador de esquema JSON", + "subtitle": "Pega JSON válido para generar un esquema JSON o modelos tipados. Todo se ejecuta en tu navegador.", + "languageLabel": "Idioma de salida", + "inputLabel": "Entrada JSON", + "inputPlaceholder": "Pega el JSON aquí…", + "outputLabel": "Salida generada", + "outputPlaceholder": "El esquema o los tipos generados aparecerán aquí…", + "parseError": "JSON no válido", + "clear": "Borrar", + "copy": "Copiar", + "copied": "Copiado al portapapeles", + "copyFailed": "No se pudo copiar", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Esquema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Cargando el editor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Imagen a Base64", + "subtitle": "Suelte una imagen, obtenga un URI de datos o una cadena Base64 sin formato. Todo permanece en tu navegador.", + "upload": "Subir imagen", + "clear": "Claro", + "modes": { + "dataUri": "URI de datos", + "rawString": "Base cruda64" + }, + "panels": { + "imageInput": "Entrada de imagen", + "output": "Salida Base64" + }, + "dropHere": "Suelta imagen aquí", + "charCount": "{contar} caracteres", + "placeholders": { + "uploadImage": "Arrastre y suelte una imagen o haga clic en cargar para comenzar." + }, + "fileTooLarge": "El archivo es demasiado grande. El procesamiento puede congelar su navegador.", + "copyTitle": "Copiar al portapapeles", + "download": { + "title": "Descargar texto", + "filename": "imagen-base64.txt" + } + }, + "ImageCompressor": { + "title": "Compresor de imágenes", + "subtitle": "Reduce la salida JPEG, PNG o WebP en local con control de calidad: no se sube nada.", + "upload": "Subir imagen", + "clear": "Borrar", + "dropHere": "Suelta la imagen aquí", + "placeholders": { + "uploadImage": "Arrastra y suelta una imagen, o haz clic para elegir un archivo." + }, + "fileTooLarge": "El archivo es demasiado grande (máx. {maxMb} MB).", + "unsupportedFormat": "Tipo de archivo no compatible. Usa JPEG, PNG, WebP, GIF, AVIF o BMP.", + "compressFailed": "No se pudo comprimir la imagen. Prueba otro archivo o formato.", + "panels": { + "original": "Original", + "compressed": "Vista previa comprimida" + }, + "outputFormat": "Formato de salida", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Calidad", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG es sin pérdidas en el navegador; el control de calidad solo afecta a JPEG y WebP.", + "sizes": { + "original": "Original: {size}", + "compressed": "Comprimido: {size}", + "saved": "{percent}% más pequeño" + }, + "largerWarning": "La salida es mayor que la entrada: prueba otro formato o calidad.", + "download": "Descargar", + "downloadFilename": "imagen-comprimida", + "processing": "Comprimiendo…", + "emptyCompressed": "La vista previa comprimida aparece aquí." + }, + "CssGradientBuilder": { + "title": "Generador de degradado CSS", + "subtitle": "Generador visual de degradados CSS con control de ángulo, paradas de color y copia CSS con un solo clic.", + "preview": "Vista previa de degradado", + "settings": "Ajustes", + "type": "Tipo", + "linear": "Lineal", + "radial": "Radial", + "angle": "Ángulo", + "stops": "Paradas de color", + "color": "Color", + "position": "Posición (%)", + "addStop": "Agregar parada", + "deleteStop": "Eliminar Detener", + "output": "Salida CSS", + "copy": "Copiar CSS", + "copied": "¡Copiado!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Generador .gitignore", + "subtitle": "Genere archivos .gitignore combinados al instante para una combinación o pila de tecnología específica.", + "searchPlaceholder": "Pilas de búsqueda (por ejemplo, Node, Python, macOS)...", + "noResults": "No se encontraron pilas.", + "selectedStacks": "Pilas seleccionadas", + "output": "Vista previa de salida", + "copy": "Copiar al portapapeles", + "copied": "¡Copiado!", + "download": "Descargar .gitignore", + "downloading": "Descargando...", + "errorFetching": "No se pudo recuperar la lista principal.", + "errorGenerating": "No se pudo generar .gitignore a partir de pilas seleccionadas.", + "generating": "Generando...", + "emptyState": "Seleccione una o más pilas de tecnología para generar su .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Sube un CSV o Excel, edita el JSON y descarga CSV o XLSX. Solo datos tabulares; las fechas de Excel pasan a marcas de tiempo ISO en JSON.", + "dropTitle": "Suelta un CSV o Excel", + "dropHint": "Se usa la primera hoja en .xlsx / .xls. El CSV debe tener fila de cabecera.", + "chooseFile": "Elegir archivo", + "jsonLabel": "JSON (matriz de objetos)", + "jsonPlaceholder": "Pega un array JSON de objetos o importa CSV / Excel…", + "loadSample": "Cargar ejemplo", + "actions": { + "format": "Formatear JSON", + "clear": "Borrar", + "copy": "Copiar", + "copied": "Copiado", + "downloadCsv": "Descargar CSV", + "downloadXlsx": "Descargar Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Datos", + "toastLoadedCsv": "CSV cargado en el editor", + "toastLoadedExcel": "Excel cargado en el editor", + "toastFormatted": "JSON formateado", + "toastExportedCsv": "CSV descargado", + "toastExportedXlsx": "Excel descargado", + "toastCopied": "Copiado al portapapeles", + "footnote": "Todo se ejecuta en tu navegador. El JSON debe ser un array no vacío de objetos. Los valores anidados se serializan como JSON en las celdas CSV.", + "errors": { + "unsupportedFile": "Usa un archivo .csv, .xlsx o .xls.", + "parseFailed": "No se pudo analizar el archivo.", + "invalidJson": "JSON no válido", + "exportFailed": "Error al exportar", + "copyFailed": "No se pudo copiar" + } + }, + "SnippetManager": { + "title": "Administrador de fragmentos de código", + "subtitle": "Guarda fragmentos en tu navegador o inicia sesión para sincronizarlos con tu cuenta. Cambia entre ver y editar, copia con un clic, elige idioma o detección automática y formatea JSON, SQL o código compatible con Monaco.", + "snippets": "Fragmentos", + "newSnippet": "Nuevo fragmento", + "newSnippetTitle": "Nuevo fragmento", + "defaultSnippetTitle": "Bienvenido", + "defaultSnippetCode": "// Bienvenido a Fragmentos de código\n// Prueba Ver vs Editar, Copiar, Formatear y idioma automático\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Buscar fragmentos…", + "searchAria": "Buscar fragmentos", + "emptySearch": "Ningún fragmento coincide con tu búsqueda.", + "snippetTitle": "Título", + "snippetTitlePlaceholder": "Nombre del fragmento", + "language": "Lenguaje", + "detectedHint": "Resaltado como {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Ver", + "modeEdit": "Editar", + "copy": "Copiar", + "copied": "Copiado", + "format": "Formatear", + "cancel": "Cancelar", + "deleteTitle": "¿Eliminar fragmento?", + "deleteDescription": "«{title}» se eliminará de este navegador.", + "deleteConfirm": "Eliminar", + "editorAria": "Editor de código del fragmento", + "editorLoading": "Cargando editor…", + "toastCopied": "Copiado al portapapeles", + "toastCopyFailed": "No se pudo copiar", + "toastNothingToCopy": "Nada que copiar", + "toastFormatted": "Formateado", + "toastFormatFailed": "No se pudo formatear", + "toastFormatJsonInvalid": "JSON no válido — corrige la sintaxis primero", + "toastFormatUnsupported": "Sin formateador para este lenguaje — prueba JSON o SQL, o modo edición", + "toastDeleted": "Fragmento eliminado", + "toastLoadFailed": "No se pudieron cargar los fragmentos desde el servidor.", + "toastSyncFailed": "No se pudo sincronizar con el servidor.", + "languages": { + "auto": "Detección automática", + "plaintext": "Texto sin formato", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Cliente SQL", + "dialogTitle": "Conexiones SQL", + "mobileSheetTitle": "Explorador SQL", + "newTabLabel": "Nueva pestaña", + "vaultLockedTitle": "Bóveda bloqueada", + "vaultLockedDesc": "Desbloquea tu bóveda para acceder a las conexiones guardadas.", + "heroTitle": "Cliente SQL", + "heroDesc": "Conéctate a PostgreSQL, MySQL o MariaDB. Las credenciales se cifran en tu navegador.", + "heroAddConnection": "Agregar conexión", + "selectTableTitle": "Selecciona una tabla o abre una pestaña", + "selectTableDesc": "Haz clic en una tabla de la barra lateral o usa ⌘↩.", + "toastLoadFail": "Error al cargar las conexiones." + }, + "connection": { + "titleNew": "Nueva conexión SQL", + "titleEdit": "Editar conexión", + "labelDbType": "Tipo de base de datos", + "labelName": "Nombre de la conexión", + "placeholderName": "Mi base de producción", + "labelHostPort": "Host y Puerto", + "labelDatabase": "Base de datos", + "placeholderDatabase": "mibd", + "labelUsername": "Usuario", + "labelPassword": "Contraseña", + "labelSsl": "Habilitar SSL / TLS", + "sslDesc": "Cifrar la conexión al servidor", + "securityNote": "Las credenciales se cifran AES-256 en tu navegador antes del almacenamiento", + "btnTest": "Probar conexión", + "btnSave": "Guardar y conectar", + "btnUpdate": "Actualizar", + "btnCancelEdit": "Cancelar edición", + "savedConnections": "Conexiones guardadas", + "emptySaved": "Las conexiones guardadas aparecerán aquí", + "confirmDelete": "¿Eliminar esta conexión?", + "toastFillFields": "Rellena el host y la base de datos antes de probar.", + "toastConnectOk": "¡Conectado! {version}", + "toastConnectFail": "Conexión fallida: {message}", + "toastUnlockVault": "Desbloquea tu bóveda primero.", + "toastFieldsRequired": "El host y la base de datos son obligatorios.", + "toastUpdated": "Conexión actualizada.", + "toastSaved": "Conexión guardada.", + "toastSaveFail": "Error al guardar: {message}", + "toastDeleted": "Conexión eliminada.", + "toastDeleteFail": "No se pudo eliminar la conexión." + }, + "sidebar": { + "title": "Explorador SQL", + "tooltipAddConnection": "Agregar conexión", + "tooltipRefreshSchema": "Actualizar esquema", + "noConnections": "No hay conexiones guardadas", + "toastSchemaFail": "Error al cargar el esquema: {message}" + }, + "editor": { + "btnRun": "Ejecutar", + "executing": "Ejecutando consulta…", + "emptyTitle": "Ejecuta una consulta para ver los resultados", + "emptyHint": "Pulsa ⌘↩ o haz clic en Ejecutar", + "toastNoConnection": "No hay conexión activa." + }, + "results": { + "filterPlaceholder": "Filtrar resultados…", + "rowCount": "{count} {count, plural, one {fila} other {filas}}", + "rowCountFiltered": "{filtered} / {total} filas", + "exportCsv": "CSV", + "noRows": "No se devolvieron filas" + } + }, + "QrCodeGenerator": { + "title": "Generador de códigos QR", + "subtitle": "Convierte cualquier texto o URL en un código QR PNG. Todo se renderiza localmente en el navegador.", + "contentLabel": "Contenido", + "contentPlaceholder": "https://ejemplo.com, texto plano, cadena Wi‑Fi, vCard…", + "styleLabel": "Estilo", + "dotTypeLabel": "Forma del punto", + "dotTypeSquare": "Cuadrado", + "dotTypeDots": "Puntos", + "dotTypeRounded": "Redondeado", + "dotTypeExtraRounded": "Extra redondeado", + "dotTypeClassy": "Elegante", + "dotTypeClassyRounded": "Elegante redondeado", + "cornerSquareLabel": "Cuadrados de esquina", + "cornerSquareDot": "Punto", + "cornerSquareSquare": "Cuadrado", + "cornerSquareExtraRounded": "Extra redondeado", + "cornerDotLabel": "Puntos de esquina", + "cornerDotDot": "Punto", + "cornerDotSquare": "Cuadrado", + "eccLabel": "Corrección de errores", + "eccL": "Baja (~7%)", + "eccM": "Media (~15%)", + "eccQ": "Cuartil (~25%)", + "eccH": "Alta (~30%)", + "marginLabel": "Margen (módulos)", + "widthLabel": "Ancho de imagen", + "widthEffective": "Se renderiza a {px} px", + "darkColor": "Módulos oscuros", + "lightColor": "Módulos claros", + "resetColors": "Restablecer colores", + "logoLabel": "Logo central", + "logoMarginLabel": "Margen", + "uploadLogo": "Subir imagen", + "removeLogo": "Quitar logo", + "downloadPng": "Descargar PNG", + "downloadSvg": "Descargar SVG", + "copyImage": "Copiar imagen", + "copied": "Copiado", + "clear": "Borrar", + "previewAlt": "Vista previa del código QR", + "emptyHint": "Escribe o pega contenido para previsualizar el código QR.", + "filename": "codigo-qr.png", + "errors": { + "generationFailed": "No se pudo generar el código QR. Prueba un texto más corto o menos corrección.", + "copyUnsupported": "Tu navegador no admite copiar imágenes al portapapeles.", + "copyFailed": "No se pudo copiar al portapapeles." + } + }, + "IpSubnetCalculator": { + "title": "Calculadora IP / subred", + "subtitle": "Detalles CIDR IPv4 e IPv6 en el navegador—sin peticiones externas.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Red", + "placeholderIpv4": "10.0.0.0/24 o 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 o 2001:db8::1/64", + "hintIpv4": "Use CIDR (p. ej. /24) o IPv4 con máscara decimal.", + "hintIpv6": "Incluya el prefijo (p. ej. /64). Si falta, se asume /128.", + "copyValue": "Copiar valor", + "rows": { + "cidr": "CIDR", + "network": "Dirección de red", + "netmask": "Máscara de subred", + "wildcard": "Máscara comodín", + "broadcast": "Difusión", + "firstHost": "Primer host", + "lastHost": "Último host", + "totalAddresses": "Direcciones totales", + "usableHosts": "Hosts usables", + "prefixLength": "Longitud del prefijo (bits)", + "networkIpv6": "Dirección de red", + "lastIpv6": "Última dirección del rango", + "totalAddressesIpv6": "Direcciones en la subred" + }, + "usableNote": { + "pointToPoint": "Los enlaces /31 punto a punto usan ambas direcciones como hosts (RFC 3021).", + "singleHost": "/32 es una sola dirección de host." + }, + "ipv6RangeHint": { + "normal": "Se muestra el rango matemático completo; las reglas del router pueden variar.", + "single": "/128 es una sola dirección.", + "full": "/0 cubre todo IPv6." + }, + "errors": { + "invalidIpv4": "Red IPv4 no válida. Pruebe CIDR o dirección + máscara.", + "invalidIpv6": "Red IPv6 no válida. Use dirección/prefijo, p. ej. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Generador de hash", + "subtitle": "Resúmenes MD5, SHA-1 y SHA-2 para texto o archivos; bcrypt para contraseñas (entrada de texto). Todo se calcula localmente en el navegador.", + "algorithmLabel": "Algoritmo", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Texto", + "modeFile": "Archivo", + "inputLabelText": "Entrada", + "placeholderText": "Escriba o pegue texto…", + "chooseFile": "Elegir archivo", + "clearFile": "Borrar", + "fileHint": "Los archivos se leen solo en memoria; máx. {maxMb} MB. No se sube nada.", + "outputLabel": "Digest (hex)", + "copyHash": "Copiar digest", + "copied": "Copiado", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# carácter} other {# caracteres}}", + "emptyHint": "Introduzca texto o elija un archivo.", + "securityNote": "MD5 y SHA-1 no se recomiendan para nuevos usos de seguridad; prefiera SHA-256 o SHA-512.", + "errors": { + "fileTooLarge": "El archivo supera {maxMb} MB.", + "fileReadFailed": "No se pudo leer el archivo." + }, + "bcryptRoundsLabel": "Coste (rondas)", + "bcryptRoundsHint": "Valores más altos son más lentos y más resistentes a fuerza bruta. Lo habitual es 10.", + "fileTabDisabledTitle": "bcrypt solo resume contraseñas desde texto, no archivos arbitrarios.", + "placeholderBcrypt": "Escriba o pegue una contraseña para resumir…", + "outputLabelBcrypt": "Hash bcrypt", + "copyBcrypt": "Copiar hash", + "emptyHintBcrypt": "Introduzca una contraseña para calcular el hash bcrypt.", + "securityNoteBcrypt": "Cada salida bcrypt incluye sal aleatoria; la misma contraseña da una cadena distinta cada vez. Es esperado. Use costes altos en producción.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Cifrado (prácticas)", + "subtitle": "AES-GCM en el navegador: cifrar a un paquete JSON con frase secreta o clave en bruto y descifrarlo en local. Educativo y a veces útil: no se sube nada.", + "tabEncrypt": "Cifrar", + "tabDecrypt": "Descifrar", + "keyModeLabel": "Material de clave", + "modePassphrase": "Frase secreta (PBKDF2 + AES-GCM)", + "modeRawKey": "Clave AES en bruto (hex o Base64)", + "aesBitsLabel": "Longitud de clave AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Codificación de la clave en bruto", + "encodingAuto": "Auto (hex si parece hex, si no Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Clave en bruto", + "rawKeyPlaceholder": "Clave de 32 bytes como 64 caracteres hex, o Base64 para AES-256 (la longitud depende del tamaño AES elegido).", + "rawKeyPlaceholderDecrypt": "Solo si el paquete tiene keyDerivation \"none\".", + "passphraseLabel": "Frase secreta", + "passphrasePlaceholder": "Frase secreta usada solo en el navegador para derivar la clave.", + "passphrasePlaceholderDecrypt": "Solo si el paquete usa PBKDF2-SHA256.", + "pbkdf2IterLabel": "Iteraciones PBKDF2", + "pbkdf2Hint": "Entre 10.000 y 2.000.000. El valor por defecto sigue ajustes habituales de almacenes.", + "plaintextLabel": "Texto plano", + "plaintextPlaceholder": "Escriba o pegue el mensaje a cifrar…", + "bundleInputLabel": "Paquete cifrado (JSON)", + "bundlePlaceholder": "Pegue el JSON de Cifrar (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Paquete cifrado", + "plaintextOutputLabel": "Texto plano", + "encryptButton": "Cifrar", + "decryptButton": "Descifrar", + "copyBundle": "Copiar JSON", + "copyPlaintext": "Copiar texto plano", + "copied": "Copiado", + "emptyBundleHint": "El JSON cifrado aparecerá aquí tras cifrar.", + "emptyDecryptHint": "El texto descifrado aparecerá aquí.", + "decryptKeyHint": "Use la clave en bruto si keyDerivation es \"none\"; la frase secreta si es PBKDF2-SHA256. El paquete guarda sal, iteraciones, IV y tamaño AES.", + "securityNote": "Las claves y el texto plano no salen del navegador. Para secretos de producción use herramientas dedicadas.", + "errors": { + "emptyKey": "Introduzca una clave en bruto que coincida con el tamaño AES.", + "emptyPassphrase": "Introduzca una frase secreta.", + "badKeyMaterial": "No se pudo leer la clave como hex ni Base64.", + "wrongKeyLength": "La longitud de clave no coincide con el tamaño AES (16 / 24 / 32 bytes).", + "invalidJson": "No es un JSON válido.", + "invalidBundle": "Este JSON no es un paquete de cifrado compatible.", + "decryptFailed": "Descifrado fallido: secreto incorrecto, paquete dañado o parámetros distintos.", + "encryptFailed": "Cifrado fallido. Compruebe Web Crypto y los datos." + } + }, + "HmacGenerator": { + "title": "Generador HMAC", + "subtitle": "Para firmar webhooks y probar integraciones de API. HMAC-SHA1/256/384/512 en hex o Base64—calculado localmente en el navegador.", + "secretLabel": "Clave secreta", + "messageLabel": "Mensaje / carga útil", + "placeholderSecret": "Pega tu secreto de firma…", + "placeholderMessage": "Pega el cuerpo en bruto o la cadena a autenticar (a menudo los mismos bytes exactos del webhook).", + "algorithmLabel": "Algoritmo HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Formato de salida", + "formatHex": "Hex (minúsculas)", + "formatBase64": "Base64", + "outputLabel": "Firma", + "copyOutput": "Copiar firma", + "copied": "Copiado", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8)", + "emptyHint": "Introduce una clave secreta para calcular HMAC.", + "securityNote": "Nunca compartas tu secreto de firma. Esta página no sube datos; todo se ejecuta en el navegador.", + "errors": { + "cryptoFailed": "No se pudo calcular HMAC. Comprueba el soporte de Web Crypto e inténtalo de nuevo.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Generador de código TOTP / 2FA", + "subtitle": "Pega un secreto Base32 del autenticador y obtén el código actual de seis dígitos RFC 6238 (SHA-1, 30 s). Todo en el navegador—útil para probar inicio de sesión o MFA.", + "secretLabel": "Secreto (Base32)", + "placeholderSecret": "Pega la clave secreta (letras A–Z y dígitos 2–7, como al configurar la app de autenticación)…", + "codeLabel": "Código actual", + "copyCode": "Copiar código", + "copied": "Copiado", + "nextIn": "Se actualiza en {seconds} s", + "standardHint": "Intervalo de 30 s, 6 dígitos, HMAC-SHA-1 (compatible con Google Authenticator).", + "digitsLabel": "Dígitos", + "periodLabel": "Período", + "algorithmLabel": "Algoritmo", + "periodSeconds": "{seconds} s", + "emptyHint": "Pega un secreto para generar un código de seis dígitos.", + "invalidHint": "Corrige el secreto para ver el código.", + "securityNote": "Trátalo como una contraseña. El secreto no sale de tu navegador; no se sube nada.", + "importLabel": "Importar desde un URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importar", + "qrSectionTitle": "Código QR de aprovisionamiento", + "qrHint": "Escanea con una app de autenticación (Google Authenticator, 1Password, Aegis…) para añadir este secreto. El código QR codifica el secreto — trátalo como una contraseña.", + "accountLabel": "Cuenta", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Emisor (opcional)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Introduce un secreto válido para generar un código QR.", + "errors": { + "invalidSecret": "No es un secreto Base32 válido.", + "cryptoFailed": "No se pudo calcular el TOTP. Comprueba Web Crypto e inténtalo de nuevo.", + "invalidUri": "Esto no es un URI otpauth:// TOTP válido.", + "unsupportedUri": "Este URI usa valores que la herramienta no admite (los dígitos deben ser 6 u 8; el período 15, 30 o 60 s)." + } + }, + "MarkdownPreview": { + "title": "Vista previa Markdown & HTML", + "subtitle": "Renderizador Markdown en vivo con exportación HTML y conversión HTML → Markdown. Todo funciona en tu navegador.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Entrada Markdown", + "htmlInputLabel": "Entrada HTML", + "previewLabel": "Vista previa", + "htmlOutputLabel": "HTML fuente", + "mdOutputLabel": "Salida Markdown", + "inputPlaceholder": "# Hola\\n\\nEscribe tu **markdown** aquí…", + "htmlInputPlaceholder": "Pega tu HTML aquí…", + "copyHtml": "Copiar HTML", + "copyMarkdown": "Copiar Markdown", + "exportHtml": "Exportar HTML", + "copied": "Copiado", + "charCount": "{count, plural, one {# carácter} other {# caracteres}}", + "emptyHint": "Empieza a escribir Markdown para ver la vista previa y la salida HTML.", + "emptyHintHtmlToMd": "Pega HTML para convertirlo en Markdown.", + "securityNote": "Todo se ejecuta localmente en tu navegador. Nada se carga." + }, + "NumberBaseConverter": { + "title": "Conversor de base numérica", + "subtitle": "Convierte enteros entre bases 2–36. Prefijos opcionales 0x / 0b / 0o. Todo se calcula localmente en el navegador.", + "inputBaseLabel": "Desde la base", + "outputBaseLabel": "A la base", + "inputLabel": "Número de entrada", + "placeholderInput": "Introduce un entero en la base de entrada seleccionada…", + "outputLabel": "Valor convertido", + "copyOutput": "Copiar resultado", + "copied": "Copiado", + "swap": "Intercambiar bases", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (longitud UTF-8)", + "emptyHint": "Introduce un número para convertir.", + "note": "Los dígitos por encima de 9 usan letras A–Z (sin distinguir mayúsculas). Se ignoran espacios y guiones bajos.", + "errors": { + "invalidNumber": "Ese valor no es válido para la base de entrada seleccionada." + } + }, + "FormatConverter": { + "title": "Conversor de formatos", + "subtitle": "Convierte entre JSON, YAML, TOML y XML en cualquier dirección. Las 12 combinaciones. Todo funciona en tu navegador.", + "fromLabel": "De", + "toLabel": "A", + "swap": "Intercambiar", + "swapTitle": "Intercambiar formatos y usar la salida como entrada", + "copy": "Copiar", + "copied": "Copiado", + "charCount": "{count, plural, one {# carácter} other {# caracteres}}", + "emptyHint": "Pega o escribe tu entrada a la izquierda para ver la salida convertida.", + "localNote": "Todo funciona localmente en tu navegador. No se sube nada." + }, + "MimeTypeLookup": { + "title": "Búsqueda de tipo MIME", + "subtitle": "Busca el tipo MIME (tipo de medio) para una extensión o un nombre de archivo completo. Todo funciona localmente en tu navegador.", + "inputLabel": "Extensión o nombre de archivo", + "placeholder": "p. ej. .png, package.json, photo.jpeg", + "hint": "Consejo: pega una extensión (con o sin punto) o un nombre de archivo completo.", + "clear": "Limpiar", + "exampleFilename": "Usar nombre de archivo de ejemplo", + "exampleExtension": "Usar extensión de ejemplo", + "resultLabel": "Resultado", + "normalizedLabel": "Entrada normalizada:", + "mimeTypeLabel": "Tipo MIME", + "contentTypeLabel": "Encabezado Content-Type", + "charsetLabel": "Conjunto de caracteres", + "copyMimeType": "Copiar tipo MIME", + "copy": "Copiar", + "copied": "Copiado", + "emptyHint": "Introduce una extensión o un nombre de archivo para buscar su tipo MIME.", + "notFound": "No se encontró un tipo MIME para esa entrada.", + "localNote": "Todo funciona localmente en tu navegador. No se sube nada." + }, + "UserAgentParser": { + "title": "Analizador de User-Agent", + "subtitle": "Pega una cadena User-Agent (UA) y obtén un desglose de navegador, SO, motor, dispositivo y CPU. Todo funciona localmente en tu navegador.", + "inputLabel": "Cadena User-Agent", + "placeholder": "Pega tu User-Agent aquí…", + "hint": "Consejo: puedes copiar un UA desde los encabezados de una solicitud, logs o las devtools del navegador.", + "clear": "Limpiar", + "exampleDesktop": "Usar ejemplo de escritorio", + "exampleMobile": "Usar ejemplo móvil", + "resultLabel": "Desglose", + "browserLabel": "Navegador", + "osLabel": "Sistema operativo", + "engineLabel": "Motor", + "deviceTypeLabel": "Tipo de dispositivo", + "deviceVendorLabel": "Fabricante del dispositivo", + "deviceModelLabel": "Modelo del dispositivo", + "cpuArchLabel": "Arquitectura de CPU", + "copyJson": "Copiar JSON", + "copy": "Copiar", + "copied": "Copiado", + "emptyHint": "Pega una cadena User-Agent para ver el desglose.", + "localNote": "Todo funciona localmente en tu navegador. No se sube nada." + }, + "MockDataGenerator": { + "title": "Generador de datos de prueba", + "subtitle": "Define columnas, elige tipos y exporta conjuntos de datos para seeds, Postman o pruebas. Hasta {max} filas, todo en el navegador: no se sube nada.", + "mockarooLink": "Flujo estilo Mockaroo", + "rowsLabel": "Filas", + "rowsHint": "Como máximo {max} filas por generación para mantener la pestaña fluida.", + "formatLabel": "Formato", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nombre de tabla SQL", + "presetsLabel": "Plantillas", + "presetPlaceholder": "Cargar plantilla…", + "presetDefault": "Inicio (usuario)", + "presetUsers": "Usuarios y roles", + "presetProducts": "SKU de comercio electrónico", + "presetApiLog": "Registro de peticiones HTTP", + "generate": "Generar", + "warningDuplicateNames": "Nombres de columna duplicados: las columnas posteriores sobrescriben a las anteriores en objetos JSON. Renómbralas.", + "schemaHeading": "Esquema", + "addField": "Añadir columna", + "removeField": "Eliminar columna", + "duplicateField": "Duplicar columna", + "colName": "Nombre", + "colType": "Tipo", + "colOptions": "Opciones", + "colBlank": "Vacío %", + "optMin": "mín", + "optMax": "máx", + "optDecimals": "dec", + "optDateFrom": "desde", + "optDateTo": "hasta", + "optValues": "a, b, c", + "optStep": "paso", + "optRowBase": "base", + "output": "Salida", + "outputPlaceholder": "Pulsa Generar para crear los datos…", + "copy": "Copiar", + "download": "Descargar", + "copied": "Copiado", + "groups": { + "personal": "Personal", + "location": "Ubicación", + "internetApi": "Internet y API", + "numbersIds": "Números e IDs", + "dateTime": "Fecha y hora", + "textDesign": "Texto y diseño", + "custom": "Personalizado" + }, + "fieldTypes": { + "first_name": "Nombre", + "last_name": "Apellidos", + "full_name": "Nombre completo", + "email": "Email", + "username": "Usuario", + "password": "Contraseña", + "phone": "Teléfono", + "company": "Empresa", + "job_title": "Puesto", + "avatar_url": "URL del avatar", + "city": "Ciudad", + "country": "País", + "country_code": "Código de país (ISO)", + "street_address": "Dirección", + "zip_code": "Código postal", + "latitude": "Latitud", + "longitude": "Longitud", + "url": "URL", + "domain": "Dominio", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Entero", + "float": "Decimal", + "boolean": "Booleano", + "uuid": "UUID", + "credit_card": "Tarjeta (prueba)", + "semver": "SemVer", + "row_index": "Índice de fila", + "sequence": "Secuencia", + "date": "Fecha", + "datetime": "Fecha y hora", + "unix_timestamp": "Marca de tiempo Unix", + "timezone": "Zona horaria", + "word": "Palabra", + "sentence": "Oración", + "paragraph": "Párrafo", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Código de moneda", + "language_code": "Etiqueta de idioma", + "custom_list": "Lista personalizada" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Optimizador SVG", + "subtitle": "Pega SVG, minifica con SVGO en el navegador y compara el tamaño UTF-8 antes y después.", + "inputLabel": "Entrada", + "outputLabel": "Optimizado", + "placeholder": "Pega un fragmento o el archivo completo…", + "outputPlaceholder": "El SVG optimizado aparece aquí…", + "chars": "{count} caracteres • {bytes} bytes (UTF-8)", + "preview": "Vista previa", + "localNote": "Todo se procesa en tu navegador. No se sube nada.", + "stats": { + "title": "Tamaño (UTF-8)", + "before": "Antes", + "after": "Después", + "savedBytes": "{count} bytes ahorrados ({percent}%)", + "noChange": "Sin reducción — ya es mínimo o la salida está vacía." + }, + "actions": { + "clear": "Borrar", + "sample": "Cargar ejemplo", + "copy": "Copiar", + "copied": "Copiado", + "download": "Descargar" + }, + "errors": { + "optimizeFailed": "No se pudo optimizar este SVG." + } + }, + "UnitConverter": { + "title": "Convertidor de unidades", + "subtitle": "Convierte entre 323 unidades en 43 categorías", + "category": "Categoría", + "from": "De", + "to": "A", + "value": "Valor", + "result": "Resultado", + "swapUnits": "Intercambiar unidades", + "placeholder": "Introduce un valor", + "copyResult": "Copiar resultado", + "searchCategory": "Buscar categoría...", + "selectCategory": "Selecciona una categoría", + "noResults": "No se encontró ninguna categoría." + }, + "DockerComposeGenerator": { + "title": "Generador Docker Compose", + "subtitle": "Selecciona bases de datos, cachés, colas, proxies inversos y observabilidad — copia un docker-compose.yml listo para desarrollo local.", + "searchPlaceholder": "Buscar (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Servicios", + "selectedCount": "{count, plural, one {# servicio} other {# servicios}}", + "outputTitle": "docker-compose.yml", + "copy": "Copiar", + "copied": "¡Copiado!", + "download": "Descargar", + "emptyState": "Selecciona uno o más servicios para generar el YAML de Compose.", + "hintPorts": "Los puertos del host por defecto reducen conflictos; cámbialos si Docker indica un puerto en uso. Sustituye las contraseñas de demostración antes de entornos compartidos.", + "categories": { + "databases": "Bases de datos y almacenes", + "cache": "Caché y en memoria", + "queues": "Colas y mensajería", + "search": "Búsqueda y registros", + "storage": "Almacenamiento de objetos", + "proxy": "Proxies inversos e ingress", + "observability": "Métricas y trazas", + "mail": "Correo (desarrollo local)", + "admin": "Interfaces de administración y BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Limpiar" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (compatible con Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (compatible con Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (compatible con S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (captura SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (servidor de desarrollo)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Deslice los mosaicos y combínelos para llegar a 2048", + "score": "Puntuación", + "best": "El mejor", + "newGame": "Nueva partida", + "howToPlay": "Utilice las teclas de flecha (o deslice el dedo en el móvil) para mover los mosaicos. Los mosaicos con el mismo número se fusionan. ¡Llega a 2048 para ganar!", + "youWin": "¡Has llegado a 2048!", + "keepGoing": "Continuar", + "gameOver": "Juego terminado", + "tryAgain": "Intentar de nuevo", + "gameOverMessage": "No hay más movimientos posibles. ", + "undo": "Deshacer" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "etapas completadas", + "time": "Time (tiempo)", + "best": "El mejor", + "difficulty": { + "easy": "Fácil", + "medium": "Medio", + "hard": "Duro" + }, + "stage": "Fase", + "solved": "Concluído", + "score": "Puntuación", + "nextStage": "Siguiente etapa", + "replay": "Volver a reproducir", + "erase": "Borrar", + "howToPlay": "Haz clic en una celda y, a continuación, pulsa un número o pulsa 1–9. Backspace o Delete borra la celda. Los números en negrita son pistas. Rellena la cuadrícula para que cada fila, columna y cuadro de 3×3 contenga del 1 al 9 sin repeticiones.", + "pencil": "Lápiz", + "undo": "Deshacer", + "hint": "Pista", + "hintsUsed": "pistas usadas", + "mistakes": "Errores", + "failed": "¡Fallido!", + "mistakeMode": "Modo 3 errores" + }, + "Snake": { + "title": "Snake", + "subtitle": "Come comida, crece, evita paredes y a ti mismo", + "score": "Puntos", + "best": "Mejor", + "startPrompt": "Pulsa cualquier tecla de flecha", + "startButton": "Iniciar juego", + "gameOver": "Fin del juego", + "playAgain": "Jugar de nuevo", + "howToPlay": "Usa las teclas de flecha o WASD. Come para crecer. La velocidad aumenta cada 50 puntos.", + "diffEasy": "Fácil", + "diffNormal": "Normal", + "diffHard": "Difícil", + "diffInfinite": "Infinito", + "orTap": "o toca abajo", + "howToPlayInfinite": "Las paredes se envuelven — sin muertes por paredes. Come para crecer.", + "shieldActive": "Escudo activo", + "speedBoost": "Aumento de velocidad", + "powerSpeed": "Velocidad x2", + "powerShield": "Sobrevive una muerte", + "powerShrink": "Acorta la cola" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Conexiones Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Conéctate a instancias Redis. Navega por claves, edita valores y ejecuta comandos. Las credenciales se cifran en tu navegador.", + "heroAddConnection": "Agregar conexión", + "vaultLockedTitle": "Bóveda bloqueada", + "vaultLockedDesc": "Desbloquea tu bóveda para acceder a las conexiones guardadas." + } + }, + "Minesweeper": { + "title": "Buscaminas", + "subtitle": "Encuentra todas las casillas seguras sin detonar una mina", + "mines": "Minas", + "newGame": "Nuevo juego", + "win": "¡Ganaste!", + "loss": "¡Perdiste!", + "playAgain": "Jugar de nuevo", + "tryAgain": "Intentar de nuevo", + "startPrompt": "Haz clic en cualquier casilla para empezar", + "difficulty": { + "beginner": "Principiante", + "intermediate": "Intermedio", + "expert": "Experto" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Organiza los bloques que caen para completar filas", + "next": "Siguiente", + "score": "Puntuación", + "best": "Mejor", + "lines": "Líneas", + "level": "Nivel", + "pause": "Pausa", + "resume": "Reanudar", + "start": "Iniciar", + "restart": "Reiniciar", + "paused": "En pausa", + "gameOver": "Fin del juego" + }, + "SshKeyGenerator": { + "title": "Generador de claves SSH / RSA", + "subtitle": "Genera pares de claves Ed25519 o RSA en tu navegador. Descarga las claves privada y pública en formatos OpenSSH y PEM. No se sube nada.", + "keyTypeLabel": "Tipo de clave", + "recommended": "recomendado", + "ed25519Desc": "Rápido, pequeño y moderno. Compatible con GitHub, GitLab y todos los servidores SSH principales.", + "rsa2048Desc": "Ampliamente compatible. Seguro para la mayoría de usos.", + "rsa4096Desc": "Máxima compatibilidad y fortaleza para sistemas legados.", + "commentLabel": "Comentario (opcional)", + "commentHint": "Se añade a la línea de clave pública de OpenSSH.", + "generateBtn": "Generar par de claves", + "generating": "Generando…", + "generatingLong": "Generando (puede tardar un momento)…", + "copy": "Copiar", + "download": "Descargar", + "privateKeyLabel": "Clave privada (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Clave pública (formato OpenSSH)", + "publicKeySpkiLabel": "Clave pública (SPKI PEM)", + "browserNote": "Todas las claves se generan en tu navegador. No se envía nada a ningún servidor.", + "errors": { + "generationFailed": "Error al generar la clave" + } + }, + "DnsLookup": { + "title": "Búsqueda DNS", + "subtitle": "Consulta registros DNS de cualquier dominio — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Introduce un dominio (p. ej. github.com)", + "lookupBtn": "Consultar", + "looking": "Consultando…", + "refreshBtn": "Actualizar", + "recordTypesLabel": "Tipos de registro", + "allBtn": "Todos", + "tryLabel": "Probar:", + "errors": { + "lookupFailed": "Error en la consulta DNS" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Generador y verificador Bcrypt", + "subtitle": "Genera hashes de contraseñas con bcrypt y verifica hashes: todo permanece en tu navegador", + "tabs": { + "hash": "Hashear", + "verify": "Verificar" + }, + "showPassword": "Mostrar contraseña", + "hidePassword": "Ocultar contraseña", + "copy": "Copiar", + "copied": "Copiado", + "hash": { + "passwordLabel": "Contraseña", + "passwordPlaceholder": "Introduce una contraseña para hashear…", + "costLabel": "Factor de coste", + "costHint": "Un coste mayor duplica el trabajo por paso. 10-12 es una elección habitual en producción.", + "slowWarning": "Los factores de coste de 13 o más pueden tardar varios segundos en calcularse.", + "generate": "Generar hash", + "generating": "Hasheando…", + "resultLabel": "Hash bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomía del hash", + "algorithm": "algoritmo", + "cost": "coste", + "salt": "salt (22 caracteres)", + "hash": "hash (31 caracteres)" + } + }, + "verify": { + "passwordLabel": "Contraseña", + "passwordPlaceholder": "Introduce la contraseña a comprobar…", + "hashLabel": "Hash bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Introduce una contraseña y un hash bcrypt para compararlos.", + "invalidHash": "Esto no parece un hash bcrypt válido.", + "checking": "Comparando…", + "match": "Coincide: la contraseña produce este hash.", + "noMatch": "No coincide: la contraseña no produce este hash." + } + }, + "ChmodCalculator": { + "title": "Calculadora Chmod", + "subtitle": "Convierte permisos de archivos Unix entre casillas, octal y notación simbólica", + "grid": { + "title": "Permisos", + "read": "Lectura", + "write": "Escritura", + "execute": "Ejecución", + "owner": "Propietario", + "group": "Grupo", + "others": "Otros" + }, + "special": { + "title": "Bits especiales", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Octal", + "placeholder": "755 o 4755", + "invalid": "Introduce 3 o 4 dígitos octales (0-7)." + }, + "symbolic": { + "label": "Simbólico", + "placeholder": "rwxr-xr-x", + "invalid": "Introduce 9 caracteres como rwxr-xr-x (se permiten s/S, t/T)." + }, + "output": { + "title": "Comando", + "copy": "Copiar", + "copied": "Copiado" + }, + "explain": { + "line": "{cls} puede {perms}.", + "none": "{cls} no tiene permisos.", + "setuid": "Setuid: el archivo se ejecuta con los privilegios de su propietario.", + "setgid": "Setgid: el archivo se ejecuta con los privilegios de su grupo (los archivos nuevos de un directorio heredan su grupo).", + "sticky": "Sticky: solo el propietario de un archivo puede eliminarlo o renombrarlo dentro de este directorio." + }, + "presets": { + "title": "Ajustes habituales", + "octal": "Octal", + "permissions": "Permisos", + "description": "Uso típico", + "p644": "Archivos normales: el propietario escribe, todos leen.", + "p755": "Ejecutables y directorios: todos pueden leer y entrar.", + "p600": "Archivos privados: solo el propietario lee y escribe.", + "p700": "Ejecutables y directorios privados: solo el propietario.", + "p664": "Archivos con escritura para el grupo.", + "p775": "Directorios y ejecutables con escritura para el grupo.", + "p400": "Solo lectura para el propietario (p. ej. claves privadas SSH)." + } + }, + "CodeScreenshot": { + "title": "Captura de código", + "subtitle": "Convierte código en imágenes atractivas y fáciles de compartir", + "languageLabel": "Lenguaje", + "autoDetect": "Detección automática", + "backgroundLabel": "Fondo", + "paddingLabel": "Relleno", + "filenameLabel": "Nombre de archivo", + "filenamePlaceholder": "example.ts", + "windowLabel": "Puntos de ventana", + "themeLabel": "Tema de ventana", + "themeDark": "Oscuro", + "themeLight": "Claro", + "scaleLabel": "Escala de exportación", + "copyImage": "Copiar imagen", + "copied": "Copiado", + "exportPng": "Exportar PNG", + "inputLabel": "Código", + "detected": "Detectado: {language}", + "clear": "Limpiar", + "placeholder": "Pega tu código aquí…", + "previewLabel": "Vista previa", + "emptyPreview": "La vista previa de tu código aparecerá aquí", + "exportFailed": "No se pudo exportar la imagen", + "copyUnsupported": "Copiar imágenes no es compatible con este navegador", + "copyFailed": "No se pudo copiar la imagen", + "backgrounds": { + "ocean": "Océano", + "sunset": "Atardecer", + "candy": "Caramelo", + "forest": "Bosque", + "midnight": "Medianoche", + "gold": "Oro", + "aurora": "Aurora", + "slate": "Pizarra", + "transparent": "Transparente" + } + }, + "CssGenerators": { + "title": "Generadores CSS", + "subtitle": "Box-shadow, border-radius y clamp() fluido: copia el CSS.", + "preview": "Vista previa", + "output": "CSS", + "copy": "Copiar", + "copied": "Copiado", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() fluido" + }, + "boxShadow": { + "presets": "Ajustes", + "layer": "Capa", + "addLayer": "Añadir capa", + "removeLayer": "Quitar capa", + "x": "X", + "y": "Y", + "blur": "Desenfoque", + "spread": "Extensión", + "color": "Color", + "alpha": "Alfa", + "inset": "Interior", + "presetNames": { + "subtleCard": "Tarjeta sutil", + "elevated": "Elevado", + "hardOffset": "Desplazamiento marcado", + "neonGlow": "Brillo neón", + "inner": "Interior", + "layeredSmooth": "Capas suaves" + } + }, + "borderRadius": { + "modeLabel": "Modo", + "modes": { + "simple": "Por esquina", + "blob": "Blob orgánico" + }, + "unit": "Unidad", + "linkCorners": "Vincular esquinas", + "randomize": "Aleatorizar", + "horizontal": "Radios horizontales", + "vertical": "Radios verticales", + "corners": { + "tl": "Superior izquierda", + "tr": "Superior derecha", + "br": "Inferior derecha", + "bl": "Inferior izquierda" + } + }, + "clamp": { + "minFont": "Fuente mín. (px)", + "maxFont": "Fuente máx. (px)", + "minViewport": "Viewport mín. (px)", + "maxViewport": "Viewport máx. (px)", + "rootFont": "Fuente raíz (px)", + "viewportWidth": "Viewport simulado", + "computedSize": "Tamaño calculado", + "slope": "Pendiente", + "intercept": "Intersección", + "previewText": "El veloz murciélago hindú", + "invalid": "Introduce límites de viewport válidos y distintos." + } + }, + "ExifViewer": { + "title": "Visor y eliminador EXIF", + "subtitle": "Inspecciona los metadatos de las fotos y elimínalos en tu navegador", + "dropTitle": "Suelta una imagen aquí", + "dropHint": "o haz clic para buscar: tu imagen nunca sale de tu dispositivo", + "browse": "Elegir imagen", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Leyendo metadatos…", + "parseFailed": "No se pudo leer esta imagen", + "clear": "Quitar imagen", + "gpsWarning": "Contiene ubicación GPS", + "openMap": "Ver en OpenStreetMap", + "noMetadata": "No se encontraron metadatos incrustados en esta imagen.", + "facts": { + "name": "Nombre", + "type": "Tipo", + "dimensions": "Dimensiones", + "size": "Tamaño" + }, + "groups": { + "camera": "Cámara", + "exposure": "Exposición", + "timestamps": "Marcas de tiempo", + "gps": "Ubicación GPS", + "software": "Software y otros" + }, + "gpsCard": { + "latitude": "Latitud", + "longitude": "Longitud", + "altitude": "Altitud" + }, + "raw": { + "title": "Todos los metadatos", + "copyJson": "Copiar JSON", + "copied": "Copiado" + }, + "remove": { + "title": "Eliminar metadatos", + "jpegDescription": "Elimina sin pérdidas EXIF, XMP, IPTC y comentarios sin recomprimir la imagen.", + "reencodeDescription": "Recodifica la imagen para eliminar los metadatos incrustados. Esto recomprime los píxeles.", + "keepIcc": "Conservar el perfil de color (ICC)", + "stripButton": "Eliminar metadatos", + "reencodeButton": "Recodificar y limpiar", + "unsupported": "La eliminación de metadatos no es compatible con este tipo de archivo.", + "failed": "No se pudieron eliminar los metadatos", + "before": "Antes", + "after": "Después", + "download": "Descargar imagen limpia" + } + }, + "FaviconGenerator": { + "title": "Generador de favicon", + "subtitle": "Crea favicons e iconos de app a partir de una sola imagen", + "dropTitle": "Suelta una imagen aquí", + "dropHint": "PNG, JPEG o SVG: haz clic para buscar", + "browse": "Elegir imagen", + "clear": "Quitar imagen", + "backgroundLabel": "Fondo", + "transparent": "Transparente", + "solidColor": "Sólido", + "cornerLabel": "Forma de las esquinas", + "cornerNone": "Cuadrado", + "cornerRounded": "Redondeado", + "cornerCircle": "Círculo", + "maskPaddingLabel": "Relleno maskable", + "maskable": "maskable", + "previewLabel": "Vista previa", + "copy": "Copiar", + "headSnippet": "Fragmento para el head HTML", + "downloadIco": "Descargar favicon.ico", + "downloadZip": "Descargar todo (.zip)" + }, + "JsonDiff": { + "title": "Diff JSON", + "subtitle": "Comparación semántica y estructural de dos documentos JSON: se ignoran el orden de las claves y el formato", + "options": { + "ignoreArrayOrder": "Ignorar el orden de los arrays" + }, + "loadBothSamples": "Cargar ejemplos", + "sample": "Cargar ejemplo", + "format": "Formatear", + "clear": "Limpiar", + "formatError": "No se puede formatear: la entrada no es un JSON válido", + "placeholder": "Pega o escribe JSON aquí…", + "panels": { + "left": "Original (izquierda)", + "right": "Modificado (derecha)", + "results": "Diferencias" + }, + "summary": { + "added": "{count} añadidos", + "removed": "{count} eliminados", + "changed": "{count} modificados" + }, + "types": { + "added": "Añadido", + "removed": "Eliminado", + "changed": "Modificado" + }, + "copyChanges": "Copiar como JSON", + "copied": "Copiado", + "empty": "Pega JSON en ambos paneles para ver sus diferencias.", + "identical": "Los dos documentos son semánticamente idénticos." + }, + "JsonpathPlayground": { + "title": "Playground JSONPath", + "subtitle": "Consulta documentos JSON con JSONPath o JMESPath y ve los resultados en vivo", + "engineLabel": "Motor", + "queryLabel": "Consulta", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Chuleta", + "cheats": { + "jpAllTitles": "Todos los títulos de libros", + "jpAllAuthors": "Cada autor, en cualquier parte del documento", + "jpFirstBook": "Primer libro", + "jpLastBook": "Último libro", + "jpSlice": "Los dos primeros libros (slice)", + "jpFilterPrice": "Libros de menos de 40", + "jpFilterInStock": "Títulos de libros en stock", + "jpRecursive": "Todos los valores, de forma recursiva", + "jmAllTitles": "Todos los títulos de libros", + "jmFirstAuthor": "Autor del primer libro", + "jmLastBook": "Último libro", + "jmSlice": "Los dos primeros libros (slice)", + "jmFilterPrice": "Títulos de libros de menos de 40", + "jmCount": "Contar libros en stock", + "jmProjection": "Reformar cada libro en un nuevo objeto", + "jmSort": "Títulos ordenados por precio" + }, + "panels": { + "input": "Entrada JSON", + "results": "Resultados" + }, + "sample": "Cargar ejemplo", + "format": "Formatear", + "clear": "Limpiar", + "formatError": "No se puede formatear: la entrada no es un JSON válido", + "placeholder": "Pega o escribe JSON aquí…", + "outputPlaceholder": "Las coincidencias aparecerán aquí…", + "matchCount": "{count, plural, one {# coincidencia} other {# coincidencias}}", + "copy": "Copiar", + "copied": "Copiado" + }, + "KeycodeInspector": { + "title": "Inspector de keycode", + "subtitle": "Pulsa cualquier tecla para inspeccionar su evento de teclado de JavaScript", + "capture": { + "prompt": "Pulsa cualquier tecla…", + "ready": "Escuchando: pulsa cualquier tecla de tu teclado", + "clickToFocus": "Haz clic aquí para enfocar y luego pulsa cualquier tecla" + }, + "hero": { + "spaceKey": "(Espacio)", + "keyLabel": "event.key", + "repeat": "repetición" + }, + "copyJson": "Copiar JSON", + "copiedJson": "JSON del evento copiado al portapapeles", + "deprecated": "obsoleto", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Estándar", + "left": "Izquierda", + "right": "Derecha", + "numpad": "Teclado numérico", + "unknown": "Desconocido" + }, + "history": { + "title": "Historial", + "clear": "Borrar historial", + "empty": "Las pulsaciones de teclas aparecerán aquí." + } + }, + "MarkdownTableGenerator": { + "title": "Generador de tablas Markdown", + "subtitle": "Edita una cuadrícula, pega desde una hoja de cálculo y copia una tabla Markdown o HTML bien alineada", + "addRow": "Añadir fila", + "addColumn": "Añadir columna", + "importLabel": "Importar", + "importPlaceholder": "Pega TSV (desde Excel / Google Sheets) o CSV aquí…", + "importButton": "Importar a la cuadrícula", + "pasteClipboard": "Pegar desde el portapapeles", + "importEmpty": "Nada que importar: pega primero algunas filas", + "clipboardError": "No se pudo leer el portapapeles", + "clear": "Limpiar", + "panels": { + "editor": "Editor de tabla", + "output": "Salida" + }, + "align": { + "left": "Alinear a la izquierda", + "center": "Centrar", + "right": "Alinear a la derecha" + }, + "headerPlaceholder": "Encabezado", + "cellPlaceholder": "Celda", + "removeRow": "Quitar fila", + "removeColumn": "Quitar columna", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Copiar", + "copied": "Copiado" + }, + "TimezoneConverter": { + "title": "Conversor de zonas horarias", + "subtitle": "Convierte un mismo momento por todo el mundo para planificar reuniones.", + "dateTimeLabel": "Fecha y hora", + "now": "Ahora", + "sourceZoneLabel": "Zona horaria de origen", + "selectZone": "Seleccionar zona", + "searchZone": "Buscar zonas horarias...", + "noZone": "No se encontró ninguna zona horaria.", + "scrubLabel": "Desplazar la hora", + "reset": "Restablecer", + "targetsLabel": "Zonas de destino", + "addZone": "Añadir zona", + "remove": "Quitar", + "localBadge": "Local", + "invalidDate": "Introduce una fecha y hora válidas.", + "bands": { + "work": "Horario laboral (09:00–18:00)", + "edge": "Horas límite (temprano / noche)", + "night": "Noche" + } + }, + "TokenCounter": { + "title": "Contador de tokens de LLM", + "subtitle": "Cuenta tokens, visualiza fragmentos y estima el coste de la API.", + "inputLabel": "Texto", + "sample": "Cargar ejemplo", + "clear": "Limpiar", + "placeholder": "Pega texto, código o JSON para contar tokens...", + "encoding": "Codificación", + "loading": "Cargando el tokenizador...", + "encodings": { + "o200k": "o200k_base (GPT-4o / serie o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Caracteres", + "words": "Palabras", + "ratio": "Tokens/palabra", + "ratioSub": "aprox." + }, + "claudeRow": "Claude (aproximado)", + "estimate": "estimación", + "viz": { + "title": "Visualización de tokens", + "toggle": "Mostrar", + "hint": "Activa para colorear cada token como un fragmento.", + "empty": "Introduce texto para visualizar los tokens.", + "truncated": "Mostrando los primeros {limit} de {total} tokens." + }, + "cost": { + "title": "Calculadora de costes", + "disclaimer": "valores por defecto editables: verifica los precios actuales", + "model": "Modelo", + "expectedOutput": "Tokens de salida previstos", + "customInput": "Entrada $/MTok", + "customOutput": "Salida $/MTok", + "inputCost": "Coste de entrada", + "outputCost": "Coste de salida", + "total": "Total", + "per1000": "Por cada 1000 llamadas" + } + }, + "WebhookTester": { + "title": "Probador de webhooks", + "subtitle": "Crea una URL temporal e inspecciona las solicitudes de webhook entrantes en vivo.", + "intro": { + "title": "Crea una URL de webhook para empezar a capturar solicitudes", + "hint": "Obtendrás una URL imposible de adivinar que registra cada solicitud entrante. Caduca a las 24 horas." + }, + "createBtn": "Crear URL de webhook", + "creating": "Creando…", + "urlLabel": "Tu URL de webhook", + "expiresIn": "Caduca en {countdown}", + "copyUrlBtn": "Copiar URL", + "copied": "Copiado", + "clearBtn": "Limpiar", + "newBinBtn": "Nueva URL", + "requestsTitle": "Solicitudes", + "selectPrompt": "Selecciona una solicitud para inspeccionar sus detalles.", + "expiredNotice": "Esta URL de webhook ha caducado. Crea una nueva para seguir probando.", + "base64Badge": "base64", + "truncatedBadge": "truncado", + "empty": { + "title": "Esperando solicitudes…", + "hint": "Envía una solicitud a tu URL y aparecerá aquí al instante. Prueba esto:" + }, + "detail": { + "receivedAt": "Recibido", + "source": "IP de origen", + "contentType": "Content-Type", + "query": "Parámetros de consulta", + "headers": "Cabeceras", + "body": "Cuerpo", + "copyBody": "Copiar cuerpo", + "emptyBody": "Sin cuerpo" + }, + "errors": { + "createFailed": "No se pudo crear la URL de webhook. Inténtalo de nuevo.", + "clearFailed": "No se pudieron borrar las solicitudes. Inténtalo de nuevo." + } + }, + "WebsocketTester": { + "title": "Probador de WebSocket", + "subtitle": "Conéctate a un endpoint WebSocket y envía, recibe e inspecciona mensajes en vivo", + "urlLabel": "URL del endpoint", + "protocolsLabel": "Subprotocolos", + "protocolsPlaceholder": "opcional, separados por comas", + "connect": "Conectar", + "disconnect": "Desconectar", + "status": { + "disconnected": "Desconectado", + "connecting": "Conectando", + "connected": "Conectado", + "error": "Error" + }, + "messages": { + "connecting": "Conectando a {url}…", + "connected": "Conexión establecida", + "connectedWithProtocol": "Conexión establecida (protocolo: {protocol})", + "invalidUrl": "URL no válida: debe empezar por ws:// o wss://", + "connectFailed": "No se pudo abrir la conexión", + "socketError": "Se produjo un error de conexión", + "notConnected": "No conectado: conéctate primero", + "binaryMessage": "Se recibió un mensaje binario" + }, + "log": { + "title": "Registro de mensajes", + "empty": "Todavía no hay mensajes. Conéctate y empieza a enviar.", + "copyAll": "Copiar todo", + "download": "Descargar como .txt", + "clear": "Borrar registro", + "prettyPrint": "Formatear JSON", + "showRaw": "Mostrar texto sin formato" + }, + "composer": { + "placeholder": "Escribe un mensaje para enviar…", + "send": "Enviar", + "formatJson": "Formatear como JSON", + "invalidJson": "El mensaje no es un JSON válido", + "hint": "Cmd/Ctrl+Intro para enviar" + }, + "ping": { + "label": "Auto-ping", + "seconds": "s", + "intervalTitle": "Intervalo de ping en segundos", + "payloadPlaceholder": "carga útil del ping" + } + }, + "WhoisLookup": { + "title": "Consulta Whois", + "subtitle": "Consulta los datos de registro de cualquier dominio o dirección IP mediante RDAP", + "placeholder": "example.com o 8.8.8.8", + "lookupBtn": "Consultar", + "looking": "Consultando…", + "refreshBtn": "Actualizar", + "tryLabel": "Prueba:", + "copy": "Copiar", + "copied": "Copiado", + "notAvailable": "No disponible", + "summary": { + "registrar": "Registrador", + "network": "Red" + }, + "dates": { + "created": "Creado", + "updated": "Actualizado", + "expires": "Caduca" + }, + "sections": { + "status": "Estado", + "nameservers": "Servidores de nombres", + "contacts": "Contactos", + "rawRdap": "RDAP sin procesar" + }, + "contacts": { + "roles": "Roles", + "name": "Nombre", + "organization": "Organización" + }, + "errors": { + "invalidTarget": "Introduce un nombre de dominio o dirección IP válidos", + "lookupFailed": "La consulta falló (estado {status})" + } + } +} diff --git a/apps/desktop-ui/messages/fa.json b/apps/desktop-ui/messages/fa.json new file mode 100644 index 00000000..75a90fe8 --- /dev/null +++ b/apps/desktop-ui/messages/fa.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "داشبورد", + "settings": "تنظیمات", + "apiClient": "سرویس گیرنده API", + "httpStatusCodes": "کدهای وضعیت HTTP", + "passwordManager": "مدیر رمز عبور", + "environmentManager": "مدیر محیط", + "bookmarks": "نشانک ها", + "jsonFormatter": "قالب‌ده JSON", + "emailValidator": "اعتبار سنجی ایمیل", + "nosqlExplorer": "کاوشگر پایگاه داده", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "زمین بازی رمزنگاری", + "jwtDecoder": "رمزگشای JWT", + "certificatePemDecoder": "رمزگشای گواهی / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "قالب‌بند GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "تجزیه‌گر URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "رمز / کلید API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "بررسی کنتراست", + "jsonSchemaGenerator": "تولیدکنندهٔ طرح JSON", + "imageToBase64": "تصویر به Base64", + "imageCompressor": "فشرده‌ساز تصویر", + "cssGradientBuilder": "CSS Gradient Builder", + "gitignoreGenerator": ".gitignore Generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "قطعه‌کدها", + "qrCodeGenerator": "تولیدکنندهٔ QR", + "dnsLookup": "جستجوی DNS", + "ipSubnetCalculator": "ماشین‌حساب IP / زیرشبکه", + "hashGenerator": "تولیدکنندهٔ هش", + "hmacGenerator": "تولیدکنندهٔ HMAC", + "totpGenerator": "کد TOTP / 2FA", + "markdownPreview": "پیش‌نمایش Markdown", + "numberBaseConverter": "مبدّل مبنا", + "formatConverter": "مبدّل فرمت", + "mockDataGenerator": "مولد دادهٔ آزمایشی", + "svgOptimizer": "بهینه‌ساز SVG", + "unitConverter": "مبدل واحد", + "dockerComposeGenerator": "تولیدکننده Docker Compose", + "breakRoom": "اتاق استراحت؛", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "قالب‌بندی YAML", + "sshKeyGenerator": "تولیدکننده کلید SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "ماشین‌حساب Chmod", + "codeScreenshot": "اسکرین‌شات کد", + "cssGenerators": "مولدهای CSS", + "exifViewer": "نمایشگر EXIF", + "faviconGenerator": "مولد Favicon", + "jsonDiff": "تفاوت JSON", + "jsonpathPlayground": "زمین بازی JSONPath", + "keycodeInspector": "بازرس کدِ کلید", + "markdownTableGenerator": "جدول Markdown", + "timezoneConverter": "مبدل منطقه زمانی", + "tokenCounter": "شمارنده توکن", + "webhookTester": "آزمایشگر Webhook", + "websocketTester": "آزمایشگر WebSocket", + "whoisLookup": "جست‌وجوی Whois" + }, + "Help": { + "title": "راهنما و مستندات", + "subtitle": "این‌که MyDevTools چگونه کنار هم کار می‌کند، هر برنامه چه کاری انجام می‌دهد و امنیت و رمزنگاری چگونه از داده‌های شما محافظت می‌کنند.", + "tabs": { + "overview": "نمای کلی", + "apps": "برنامه‌ها", + "security": "امنیت", + "tips": "نکته‌ها" + }, + "overview": { + "whatIs": { + "title": "MyDevTools چیست؟", + "description": "یک فضای کاری توسعه‌دهنده با ورود: داشبورد و مجموعه‌ای از ابزارهای روزمره.", + "p1": "پس از ورود، برنامه یک نشست با API MyDevTools برقرار می‌کند تا ابزارها بتوانند داده‌های شما را بارگیری و ذخیره کنند. نوار کناری همه برنامه‌ها را نشان می‌دهد؛ می‌توانید ابزارهایی را که استفاده نمی‌کنید از", + "settingsLink": "تنظیمات", + "p1After": "مخفی کنید.", + "p2": "برخی قابلیت‌ها از رمز اصلی (یا کلید کلی خزانه) استفاده می‌کنند تا اطلاعات حساسی مثل گذرواژه‌ها و رشته‌های اتصال، قبل از رسیدن به سرور روی دستگاه شما رمزنگاری شوند." + }, + "quickLinks": { + "title": "پیوندهای سریع", + "dashboard": "داشبورد", + "settings": "تنظیمات" + } + }, + "apps": { + "intro": "هر ابزار را از نوار کناری یا داشبورد باز کنید. در ادامه، راهنمای کوتاهی برای هر برنامه آمده است.", + "open": "باز کردن", + "howItWorks": "نحوه کار", + "generic": "این ابزار را از نوار کناری و مطابق گردش‌کار اختصاصی خودش استفاده کنید." + }, + "security": { + "session": { + "title": "ورود و نشست API", + "description": "هویت و این‌که مرورگر چگونه با بک‌اند ارتباط برقرار می‌کند.", + "items": { + "authentication": "احراز هویت از Firebase برای ورود استفاده می‌کند. پس از ورود، برنامه توکن شناسه Firebase را با کوکی‌های API مبادله می‌کند.", + "accessTokens": "توکن‌های دسترسی JWTهای امضاشده در سرور (HS256) هستند و مشخص می‌کنند کدام کاربر API را فراخوانی می‌کند.", + "refreshTokens": "توکن‌های نوسازی در کوکی‌های HttpOnly ذخیره می‌شوند؛ سرور هش SHA-256 توکن را نگه می‌دارد، نه خود توکن خام را." + } + }, + "encryption": { + "title": "رمزنگاری مهم", + "description": "رمزنگاری سمت کلاینت برای خزانه و داده‌های اتصال حساس.", + "p1": "برای مدیر رمز عبور، مدیر محیط و اتصال‌های ذخیره‌شده در NoSQL Explorer، برنامه وب از Web Crypto API مرورگر استفاده می‌کند:", + "items": { + "kdf": "PBKDF2 با SHA-256، 100,000 تکرار و salt تصادفی، یک کلید 256 بیتی را از رمز اصلی مشتق می‌کند.", + "aes": "AES-256-GCM داده‌ها را رمزنگاری می‌کند؛ برای هر رمزنگاری از IV 12 بایتی تصادفی استفاده می‌شود.", + "scope": "کلید مشتق‌شده داخل مرورگر می‌ماند (غیرقابل استخراج). سرور فقط متن رمز و IV را دریافت می‌کند، نه رمز اصلی یا داده‌های محرمانه به‌صورت متن ساده." + }, + "warning": "اگر رمز اصلی را فراموش کنید، داده‌های رمزنگاری‌شده قابل بازیابی نیستند. آن را امن نگه دارید." + }, + "logout": { + "title": "خروج و داده‌های محلی", + "body": "خروج، اطلاعات حساسِ حافظه، وضعیت خزانه و (در صورت وجود) مواد کلید اصلی را از IndexedDB پاک می‌کند و نشست API را پایان می‌دهد. داده‌هایی که فقط در ذخیره‌سازی محلی دستگاه هستند ممکن است تا زمانی که در مرورگر پاکشان کنید باقی بمانند." + } + }, + "tips": { + "title": "استفاده امن از MyDevTools", + "items": { + "masterPassword": "برای قابلیت‌های خزانه از یک رمز اصلی قوی و منحصربه‌فرد استفاده کنید.", + "apiClient": "با API Client مثل هر کلاینت HTTP رفتار کنید: اگر نمی‌دانید چه کسی می‌تواند ببیند، اسرار تولید را در محیط‌های مشترک وارد نکنید.", + "nosql": "کاوشگر پایگاه داده به پایگاه‌های داده‌ای که شما پیکربندی می‌کنید متصل می‌شود؛ در صورت امکان از کاربران حداقل‌دسترسی و قوانین شبکه استفاده کنید.", + "json": "قالب‌ده JSON به‌صورت محلی در مرورگر اجرا می‌شود—با این حال در دستگاه‌های مشترک از چسباندن داده‌های بسیار حساس خودداری کنید.", + "settings": "در تنظیمات ابزارهای قابل مشاهده را شخصی‌سازی کنید تا نوار کناری متمرکز بماند." + } + } + }, + "Settings": { + "title": "تنظیمات", + "language": "زبان", + "spanish": "اسپانیایی", + "english": "انگلیسی", + "french": "فرانسه", + "arabic": "عربی", + "catalan": "کاتالان", + "chineseSimplified": "چینی (ساده شده)", + "czech": "Čeština", + "greek": "زبان", + "german": "آلمانی", + "danish": "دانمارکی", + "afrikaans": "آفریقایی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "اوکراینی", + "persian": "فارسی" + }, + "SettingsPage": { + "title": "تنظیمات", + "subtitle": "تنظیمات و تنظیمات برگزیده حساب خود را مدیریت کنید.", + "userProfile": { + "title": "نمایه کاربر", + "description": "اطلاعات شخصی و جزئیات حساب شما.", + "avatarAlt": "کاربر", + "anonymousUser": "کاربر ناشناس", + "noEmailProvided": "ایمیلی ارائه نشده است", + "notLoggedIn": "وارد نشده اید." + }, + "tools": { + "title": "ابزارها و ویژگی ها", + "description": "ابزارهایی را انتخاب کنید که در نوار کناری پیمایش شما ظاهر شوند." + }, + "appearance": { + "title": "ظاهر", + "description": "نحوه ظاهر برنامه را در دستگاه خود سفارشی کنید.", + "themePreference": "اولویت تم", + "themes": { + "light": "نور", + "dark": "تاریک", + "system": "سیستم" + } + }, + "language": { + "title": "زبان", + "description": "زبان مورد استفاده در پلتفرم را تنظیم کنید.", + "selectLanguage": "زبان را انتخاب کنید", + "selectPlaceholder": "یک زبان را انتخاب کنید", + "helpText": "با تغییر زبان، منوها و برچسب‌ها در سراسر پلتفرم به‌روزرسانی می‌شوند.", + "languages": { + "en": "انگلیسی", + "fr": "فرانسه", + "es": "اسپانیایی (اسپانیایی)", + "ar": "عربی (عربی)", + "ca": "کاتالا", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (یونانی)", + "de": "دویچ (آلمانی)", + "da": "Dansk (دانمارکی)", + "af": "آفریقایی", + "id": "Bahasa Indonesia", + "it": "ایتالیایی (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська", + "fa": "فارسی" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "صبح بخیر", + "afternoon": "ظهر بخیر", + "evening": "عصر بخیر" + }, + "commaName": "، {name}", + "welcomeBack": "خوش آمدید", + "welcomeBackNamed": "خوش آمدید، {name}", + "tagline": "دوست دارید امروز چه چیزی بسازید؟", + "brandName": "MyDevTools", + "stats": { + "tools": "ابزار", + "favorites": "موارد دلخواه", + "pinned": "سنجاق‌شده", + "recent": "اخیرا استفاده شده است" + }, + "sections": { + "recentlyUsed": "اخیرا استفاده شده است", + "favorites": "موارد دلخواه", + "whatsNew": "تازه‌ها", + "pinned": "سنجاق‌شده" + }, + "groups": { + "apps": "برنامه ها" + }, + "toolCard": { + "defaultDescription": "برای عملکرد بهتر این ابزار را کاوش کنید.", + "launchTool": "راه اندازی ابزار" + }, + "tools": { + "toDo": { + "title": "وظایف", + "description": "لیست کارهای خود را به طور موثر مدیریت کنید." + }, + "notes": { + "title": "یادداشت ها", + "description": "ایده ها را جذب کنید و افکار خود را سازماندهی کنید." + }, + "passwordManager": { + "title": "مدیر رمز عبور", + "description": "رمزهای عبور خود را ایمن ذخیره و مدیریت کنید." + }, + "environmentManager": { + "title": "مدیر محیط", + "description": "متغیرهای شبیه .env را به‌ازای هر پروژه و محیط با برچسب ذخیره کنید." + }, + "bookmarks": { + "title": "نشانک ها", + "description": "بوک مارک های خود را مدیریت و سازماندهی کنید." + }, + "apiClient": { + "title": "سرویس گیرنده API", + "description": "درخواست های HTTP را تست و اشکال زدایی کنید." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "پایگاه داده های MongoDB را کاوش کنید." + }, + "emailValidator": { + "title": "اعتبار سنجی ایمیل", + "description": "آدرس های ایمیل را تأیید و تأیید کنید." + }, + "encryptionPlayground": { + "title": "زمین بازی رمزنگاری", + "description": "AES-GCM با کلید خام یا عبارت عبور؛ بسته‌های JSON، Web Crypto فقط در مرورگر." + }, + "jwtDecoder": { + "title": "رمزگشای JWT", + "description": "هدر، بار داده و زمان انقضای JWT را رمزگشایی کنید." + }, + "certificatePemDecoder": { + "title": "رمزگشای گواهی / PEM", + "description": "رمزگشایی گواهی‌های X.509 و CSR: موضوع، صادرکننده، اعتبار و SAN." + }, + "sshKeyGenerator": { + "title": "مولد کلید SSH / RSA", + "description": "در مرورگر جفت کلید Ed25519 یا RSA تولید کنید. در قالب‌های OpenSSH و PEM دانلود کنید." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "قالب‌بند GraphQL", + "description": "GraphQL را با برجسته‌سازی نحو و ویرایشگر سادهٔ پرس‌وجو قالب‌بندی کنید." + }, + "yamlFormatter": { + "title": "قالب‌بند YAML", + "description": "قالب‌بندی، اعتبارسنجی و تبدیل YAML به JSON در مرورگر." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "تجزیه‌گر URL", + "description": "یک URL را به پروتکل، میزبان، مسیر، پارامترهای پرس‌وجو و هش تقسیم کنید." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "رمز / کلید API", + "description": "رشته‌های تصادفی رمزنگاری‌شده با الفبا، طول و خروجی انبوه قابل تنظیم." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "بررسی کنتراست", + "description": "نسبت کنتراست WCAG 2.1 متن و پس‌زمینه (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "تولیدکنندهٔ طرح JSON", + "description": "از نمونهٔ JSON، طرح JSON و مدل‌های نوع‌دار استنتاج کنید." + }, + "imageToBase64": { + "title": "تصویر به Base64", + "description": "تصاویر را به Data URI یا رشته های Base64 خام تبدیل کنید." + }, + "imageCompressor": { + "title": "فشرده‌ساز تصویر", + "description": "فشرده‌سازی JPEG و PNG و WebP به‌صورت محلی با لغزنده کیفیت." + }, + "cssGradientBuilder": { + "title": "CSS Gradient Builder", + "description": "سازنده گرادیان CSS ویژوال با کنترل زاویه و توقف رنگ." + }, + "gitignoreGenerator": { + "title": ".gitignore Generator", + "description": "فایل‌های gitignore. ادغام شده را فوراً از بیش از 500 پشته فناوری ایجاد کنید." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "فایل‌های CSV یا Excel را به JSON تبدیل کنید و دوباره به CSV یا XLSX صادر کنید." + }, + "snippetManager": { + "title": "قطعه‌کدها", + "description": "قطعه‌کدها را با برجسته‌سازی نحو، تشخیص خودکار زبان و قالب‌بندی ذخیره کنید." + }, + "qrCodeGenerator": { + "title": "تولیدکنندهٔ QR", + "description": "ساخت PNG QR از متن یا نشانی در مرورگر." + }, + "ipSubnetCalculator": { + "title": "ماشین‌حساب IP / زیرشبکه", + "description": "IPv4 و IPv6 CIDR: ماسک، ناحیه، و تعداد آدرس." + }, + "dnsLookup": { + "title": "جستجوی DNS", + "description": "رکوردهای DNS هر دامنه را جستجو کنید — A، AAAA، MX، TXT، NS، CNAME." + }, + "hashGenerator": { + "title": "تولیدکنندهٔ هش", + "description": "MD5، SHA-1 و SHA-2 برای متن یا فایل." + }, + "hmacGenerator": { + "title": "تولیدکنندهٔ HMAC", + "description": "امضای وب‌هوک و آزمایش یکپارچگی API با HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "تولیدکنندهٔ کد TOTP / 2FA", + "description": "راز Base32 برنامهٔ احراز هویت را بچسبانید؛ کد شش‌رقمی هر ۳۰ ثانیه تازه می‌شود—برای آزمایش ورود و MFA مفید است." + }, + "markdownPreview": { + "title": "پیش‌نمایش Markdown", + "description": "رندر زنده Markdown با صادرکردن HTML و تبدیل HTML → Markdown." + }, + "numberBaseConverter": { + "title": "مبدّل مبنای عددی", + "description": "تبدیل اعداد صحیح بین مبناهای ۲ تا ۳۶." + }, + "formatConverter": { + "title": "مبدّل فرمت", + "description": "تبدیل بین JSON، YAML، TOML و XML در هر جهت. هر ۱۲ ترکیب پشتیبانی می‌شود." + }, + "mockDataGenerator": { + "title": "مولد دادهٔ آزمایشی", + "description": "ستون‌ها را تعریف کنید، نوع داده را انتخاب کنید و مجموعه‌ها را برای seed، Postman یا آزمون برون‌بری کنید. تا 5000 سطر، همه در مرورگر — بدون بارگذاری." + }, + "httpStatusCodes": { + "title": "کدهای وضعیت HTTP", + "description": "جستجوی کدهای وضعیت HTTP با توضیحات (1xx–5xx)." + }, + "svgOptimizer": { + "title": "بهینه‌ساز SVG", + "description": "SVG را بچسبانید، با SVGO زوائد را حذف کنید و کاهش حجم UTF-8 را ببینید." + }, + "unitConverter": { + "title": "مبدل واحد", + "description": "تبدیل بین 323 واحد در 43 دسته علمی و مهندسی." + }, + "dockerComposeGenerator": { + "title": "تولیدکننده Docker Compose", + "description": "پایگاه‌داده، کش، صف، پروکسی و تصاویر قابلیت مشاهده را انتخاب کنید؛ docker-compose.yml را برای پشته‌های محلی صادر کنید." + }, + "game2048": { + "title": "2048", + "description": "کاشی‌ها را بلغزانید و ادغام کنید تا به عدد ۲۰۴۸ برسید. بازی پازل اعداد کلاسیک." + }, + "sudoku": { + "title": "سودوکو", + "description": "معمای اعداد کلاسیک. ۳ سطح دشواری، هر کدام ۱۵۰ مرحله." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "کلیدها را مرور کنید، مقادیر را بررسی کنید و دستورات را روی هر نمونه Redis اجرا کنید." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "مولد و بررسی‌کننده Bcrypt", + "description": "گذرواژه‌ها را با bcrypt هش کنید و هش‌های موجود را تأیید کنید." + }, + "chmodCalculator": { + "title": "ماشین‌حساب Chmod", + "description": "مجوزهای یونیکس را بین هشت‌هشتی، نمادین و چک‌باکس تبدیل کنید." + }, + "codeScreenshot": { + "title": "اسکرین‌شات کد", + "description": "کد منبع را به تصاویر زیبا و قابل‌اشتراک تبدیل کنید." + }, + "cssGenerators": { + "title": "مولدهای CSS", + "description": "مولدهای box-shadow، border-radius و clamp() سیّال." + }, + "exifViewer": { + "title": "نمایشگر و حذف‌کننده EXIF", + "description": "متادیتای EXIF عکس را به‌صورت محلی بررسی و حذف کنید." + }, + "faviconGenerator": { + "title": "مولد Favicon", + "description": "favicon، آیکون‌های اپ و قطعه‌کدهای manifest بسازید." + }, + "jsonDiff": { + "title": "تفاوت JSON", + "description": "مقایسه معنایی و ساختارآگاه دو سند JSON." + }, + "jsonpathPlayground": { + "title": "زمین بازی JSONPath", + "description": "JSON را با JSONPath یا JMESPath پرس‌وجو کنید و نتایج زنده را ببینید." + }, + "keycodeInspector": { + "title": "بازرس کدِ کلید", + "description": "کلیدی را فشار دهید تا کدهای رویداد JavaScript و کلیدهای تغییردهنده آن را ببینید." + }, + "markdownTableGenerator": { + "title": "مولد جدول Markdown", + "description": "جدول‌های Markdown و HTML را در ویرایشگر شبکه‌ای شبیه صفحه‌گسترده بسازید." + }, + "timezoneConverter": { + "title": "مبدل منطقه زمانی", + "description": "زمانی را در چند منطقه با نشانه‌های تغییر روز و ساعات کاری تبدیل کنید." + }, + "tokenCounter": { + "title": "شمارنده توکن LLM", + "description": "توکن‌های LLM را بشمارید، تکه‌ها را تجسم کنید و هزینه API را تخمین بزنید." + }, + "webhookTester": { + "title": "آزمایشگر Webhook", + "description": "درخواست‌های ورودی webhook را در لحظه ثبت و بررسی کنید." + }, + "websocketTester": { + "title": "آزمایشگر WebSocket", + "description": "به نقاط پایانی WebSocket متصل شوید و پیام‌ها را در لحظه رد و بدل کنید." + }, + "whoisLookup": { + "title": "جست‌وجوی Whois", + "description": "جزئیات ثبت دامنه و IP را از طریق RDAP جست‌وجو کنید." + } + }, + "tabs": { + "apps": "برنامه‌ها", + "analytics": "تحلیل", + "ui": "رابط کاربری" + }, + "analytics": { + "title": "داده‌های شما در یک نگاه", + "subtitle": "شمارش‌ها همگام‌شده با حساب شما روی سرور.", + "loadError": "بارگذاری تحلیل ممکن نشد.", + "retry": "تلاش دوباره", + "loginHint": "برای دیدن تحلیل استفاده و ترجیحات رابط وارد شوید.", + "passwordEntries": "ورودی‌های گذرواژه", + "bookmarks": "نشانک‌ها", + "bookmarkFolders": "پوشه‌های نشانک", + "tasksTotal": "وظایف", + "tasksCompleted": "تکمیل‌شده", + "tasksOngoing": "در حال انجام", + "tasksNotStarted": "شروع‌نشده", + "projects": "پروژه‌ها", + "nosqlConnections": "اتصالات پایگاه داده", + "notes": "یادداشت‌ها", + "apiClientCollections": "مجموعه‌های API", + "apiClientEnvironments": "محیط‌های API", + "apiClientHistory": "ورودی‌های تاریخچه API", + "jsonFormatterDocuments": "اسناد قالب‌ده JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "قطعه‌کدهای برنامه", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "ترجیحات رابط", + "subtitle": "پوسته، زبان و ابزارهای نوار کناری — از تنظیمات حساب.", + "loadError": "بارگذاری ترجیحات ممکن نشد.", + "loginHint": "برای دیدن ترجیحات ذخیره‌شده وارد شوید.", + "theme": "پوسته", + "accent": "رنگ تأکید", + "locale": "زبان", + "enabledTools": "ابزارهای قابل مشاهده", + "favoriteTools": "ابزارهای مورد علاقه", + "toolsWithUsage": "ابزارها با آمار استفاده" + }, + "signIn": "ورود", + "noResults": "هیچ ابزاری با جستجوی شما مطابقت ندارد", + "noResultsHint": "یک کلیدواژه متفاوت امتحان کنید یا فیلتر را پاک کنید.", + "filterAll": "همه", + "loginCta": { + "description": "وارد شوید تا ابزارهای اخیراً استفاده شده خود را دنبال کنید." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "در حال بارگیری...", + "myTasksTitle": "وظایف من", + "statsLine": "{total} وظایف • {percent}% کامل شده است", + "statsLineDesktop": "{total} کل • {percent}% کامل شده است" + }, + "filters": { + "all": "همه", + "total": "مجموع", + "allProjects": "همه پروژه ها", + "export": "صادرات", + "kanban": "کانبان", + "list": "فهرست کنید", + "searchPlaceholder": "جستجوی وظایف...", + "searchAria": "جستجوی وظایف", + "clearSearchAria": "جستجو را پاک کنید", + "foundTasks": "پیدا شد {count} {count, plural, one {task} other {tasks}}", + "filterByAllAria": "فیلتر بر اساس همه", + "filterByStatusAria": "فیلتر بر اساس {status}" + }, + "status": { + "not-started": { + "label": "شروع نشده است", + "description": "کارهایی که هنوز شروع نشده اند" + }, + "ongoing": { + "label": "در حال انجام است", + "description": "وظایف در حال حاضر در حال انجام است" + }, + "completed": { + "label": "تکمیل شد", + "description": "کارهایی که تمام شده اند" + } + }, + "priorities": { + "high": "بالا", + "medium": "متوسط", + "low": "کم" + }, + "form": { + "addNewTaskLabel": "کار جدید اضافه کنید", + "multilinePlaceholder": "افزودن جزئیات کار... (Shift+Enter برای خط جدید)", + "singlelinePlaceholder": "چه کاری باید انجام شود؟ برای افزودن اینتر را فشار دهید", + "taskInputAria": "ورودی وظیفه", + "projectPlaceholder": "پروژه", + "selectProject": "پروژه را انتخاب کنید", + "errorSelectProject": "لطفاً یک پروژه را انتخاب کنید یا یک پروژه جدید برای اضافه کردن یک کار ایجاد کنید.", + "hintMultiline": "Shift+Enter برای خط جدید، Enter برای افزودن", + "hintSingleline": "برای افزودن وارد کنید", + "shortcutsAria": "میانبرهای صفحه کلید", + "shortcutsLabel": "میانبرها", + "shortcutAddTask": "وظیفه اضافه کنید", + "shortcutNewLine": "خط جدید", + "shortcutClear": "پاک کردن", + "addButton": "اضافه کنید" + }, + "list": { + "ariaLabel": "لیست وظایف", + "loading": "در حال بارگیری وظایف شما...", + "emptyTitle": "هیچ کاری پیدا نشد", + "emptyPrimary": "با اضافه کردن اولین کار خود در بالا شروع کنید! برای افزودن سریع وظایف، Enter را فشار دهید.", + "emptySecondary": "فیلترها یا عبارت جستجو را تنظیم کنید." + }, + "mobileDrawer": { + "addTaskSrOnly": "Task را اضافه کنید", + "addNewTaskTitle": "کار جدید اضافه کنید", + "addNewTaskDescription": "یک کار جدید برای پیگیری پیشرفت خود ایجاد کنید.", + "cancel": "لغو کنید" + }, + "kanban": { + "ariaLabel": "تابلوی کانبان", + "loading": "در حال بارگیری وظایف شما...", + "dropHere": "وظیفه را اینجا رها کنید", + "noTasksYet": "هنوز تکلیفی وجود ندارد", + "emptyColumnAria": "ستون خالی", + "columnAria": "ستون {title} با وظایف {count}", + "countInColumnAria": "{count} وظایف در {title}" + }, + "taskItem": { + "confirmDelete": "آیا مطمئن هستید که می خواهید این کار را حذف کنید؟", + "statusPlaceholder": "وضعیت", + "changeStatusAria": "تغییر وضعیت کار", + "taskOptionsAria": "گزینه های وظیفه", + "editTask": "ویرایش وظیفه", + "copyTask": "وظیفه کپی کردن", + "copied": "کپی شده!", + "deleteTask": "Task را حذف کنید", + "statusHeading": "وضعیت", + "editTaskAria": "ویرایش کار" + }, + "editDialog": { + "title": "ویرایش وظیفه", + "description": "جزئیات کار را به‌روزرسانی کنید، وظایف فرعی اضافه کنید، اولویت‌ها را تنظیم کنید و موارد دیگر.", + "taskTitleLabel": "عنوان وظیفه *", + "taskTitlePlaceholder": "چه کاری باید انجام شود؟", + "descriptionLabel": "توضیحات", + "descriptionPlaceholder": "جزئیات بیشتری در مورد این کار اضافه کنید...", + "statusLabel": "وضعیت", + "priorityLabel": "اولویت", + "dueDateLabel": "سررسید", + "pickDate": "یک تاریخ انتخاب کنید", + "clearDate": "تاریخ پاک کردن", + "projectLabel": "پروژه", + "selectProjectPlaceholder": "یک پروژه را انتخاب کنید", + "noProject": "بدون پروژه", + "timeEstimateLabel": "تخمین زمان (دقیقه)", + "timeEstimatePlaceholder": "چقدر طول می کشد؟", + "tagsLabel": "برچسب ها", + "customTagNamePlaceholder": "نام تگ سفارشی", + "subtasksLabel": "وظایف فرعی", + "addSubtaskPlaceholder": "اضافه کردن یک کار فرعی...", + "cancel": "لغو کنید", + "saving": "در حال ذخیره...", + "saveChanges": "ذخیره تغییرات" + }, + "export": { + "title": "صادرات وظایف", + "description": "وظایف خود را به فرمت های JSON، CSV یا Excel صادر کنید.", + "projectLabel": "پروژه", + "statusLabel": "وضعیت", + "tagLabel": "برچسب بزنید", + "allProjects": "همه پروژه ها", + "allStatuses": "همه وضعیت ها", + "allTags": "همه برچسب ها", + "exportAsJsonTitle": "صادرات به عنوان JSON", + "exportAsJsonDescription": "داده های وظیفه شامل وظایف فرعی، برچسب ها و ابرداده ها را صادر کنید.", + "exportJson": "صادرات JSON", + "exportAsCsvExcelTitle": "صادرات به عنوان CSV / Excel", + "exportAsCsvExcelDescription": "وظایف را برای برنامه های صفحه گسترده صادر کنید.", + "exportCsv": "صادرات CSV", + "exportExcel": "صادرات اکسل", + "close": "بستن", + "toastNoTasks": "هیچ کاری برای صادرات با فیلترهای فعلی وجود ندارد", + "toastExportedJson": "{count} کار به JSON صادر شد!", + "toastExportedCsv": "{count} کار به CSV صادر شد!", + "toastExportedExcel": "{count} کار به اکسل صادر شد!", + "toastFailedJson": "صادر کردن به JSON انجام نشد", + "toastFailedCsv": "صادر کردن به CSV انجام نشد", + "toastFailedExcel": "صادر کردن به اکسل انجام نشد", + "unknownProject": "پروژه ناشناخته" + }, + "ack": { + "taskAddedTitle": "کار با موفقیت اضافه شد", + "taskAddedFailedTitle": "کار اضافه نشد", + "taskUpdatedTitle": "کار با موفقیت به روز شد", + "taskUpdatedFailedTitle": "کار به روز نشد", + "taskStatusUpdateFailedTitle": "وضعیت کار به روز نشد", + "taskMovedTitle": "کار به {status} منتقل شد", + "tryAgain": "لطفا دوباره امتحان کنید.", + "taskDeletedTitle": "کار \"{text}\" حذف شد", + "undo": "واگرد", + "taskRestoredTitle": "وظیفه بازیابی شد", + "taskRestoredSuccessTitle": "کار با موفقیت بازیابی شد", + "taskRestoreFailedTitle": "کار بازیابی نشد. لطفا دوباره امتحان کنید.", + "taskDeleteFailedTitle": "وظیفه حذف نشد. لطفا دوباره امتحان کنید.", + "tasksImportedTitle": "{count} {count, plural, one {task} other {tasks}} با موفقیت وارد شد!", + "tasksImportFailedTitle": "وارد کردن وظایف ناموفق بود. لطفا دوباره امتحان کنید.", + "unknownProject": "پروژه ناشناخته", + "projectCreatedTitle": "پروژه با موفقیت ایجاد شد", + "projectCreateFailedTitle": "پروژه ایجاد نشد", + "projectUpdatedTitle": "پروژه با موفقیت به روز شد", + "projectUpdateFailedTitle": "پروژه به روز رسانی نشد", + "projectDeletedTitle": "پروژه با موفقیت حذف شد", + "projectDeleteFailedTitle": "پروژه حذف نشد" + } + }, + "Notes": { + "page": { + "loading": "در حال بارگیری..." + }, + "layout": { + "mobileNavTitle": "پیمایش یادداشت ها", + "openMenuAria": "منوی یادداشت ها را باز کنید" + }, + "sidebar": { + "title": "یادداشت ها", + "createNoteAria": "یادداشت ایجاد کنید", + "searchPlaceholder": "جستجوی یادداشت ها...", + "loading": "در حال بارگیری...", + "noNotesFound": "هیچ یادداشتی یافت نشد", + "noNotesYet": "هنوز یادداشتی وجود ندارد. برای ایجاد یکی روی + کلیک کنید.", + "inParent": "در {parentTitle}", + "untitled": "بدون عنوان", + "addSubPage": "افزودن صفحه فرعی", + "delete": "حذف کنید", + "deleteConfirmTitle": "یادداشت حذف شود؟", + "deleteConfirmDescription": "آیا مطمئنید که می خواهید \"{title}\" را حذف کنید؟ این عمل قابل لغو نیست.", + "cancel": "لغو کنید", + "confirmDelete": "حذف کنید", + "noteOptionsAria": "گزینه های یادداشت" + }, + "editor": { + "emptyState": "برای شروع یادداشتی را انتخاب کنید یا یادداشت جدیدی ایجاد کنید.", + "titlePlaceholder": "بدون عنوان", + "saving": "در حال ذخیره…", + "ready": "آماده", + "saved": "ذخیره شد {time}", + "stats": "{words} کلمه · {read} دقیقه مطالعه", + "template": "قالب", + "applyTemplate": "اعمال قالب", + "exportMdLabel": ".md", + "exportMd": "خروجی به‌صورت Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "خروجی به‌صورت HTML", + "focusMode": "حالت تمرکز", + "exitFocusMode": "خروج از حالت تمرکز", + "save": "ذخیره", + "addTag": "افزودن برچسب…", + "removeTag": "حذف برچسب {tag}", + "loading": "در حال بارگذاری…", + "searchHint": "برای جستجو", + "focusHint": "حالت تمرکز", + "notePath": "مسیر یادداشت" + }, + "context": { + "authRequiredError": "کاربر احراز هویت نشده است", + "defaultTitle": "بدون عنوان", + "cloudSyncImageError": "برای ذخیره تصاویر یادداشت، همگام‌سازی ابری را روشن کنید. یادداشت‌های متنی محلی می‌مانند." + } + }, + "PasswordManager": { + "page": { + "loading": "در حال بارگیری...", + "title": "مدیر رمز عبور", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "رمزگشایی رمز عبور ناموفق بود", + "toastLoadFailed": "بارگیری رمزهای عبور انجام نشد", + "toastUnlockedAuto": "قفل خرک به طور خودکار باز شد", + "toastAutoUnlockFailed": "باز کردن قفل خودکار انجام نشد. لطفا به صورت دستی قفل را باز کنید.", + "toastConnectFailed": "اتصال به سرور انجام نشد", + "toastAutoUnlockWarning": "قفل خرک باز شد، اما باز کردن قفل خودکار ممکن است در بارگیری مجدد کار نکند", + "errorIncorrectPassword": "رمز عبور اصلی اشتباه است", + "errorUnlockFailed": "باز کردن قفل طاق انجام نشد", + "errorPasswordsMismatch": "گذرواژه ها مطابقت ندارند", + "errorPasswordTooShort": "رمز عبور باید حداقل 8 کاراکتر باشد", + "errorSetupFailed": "طاق راه اندازی نشد", + "errorConnectFailed": "اتصال به سرور انجام نشد.", + "mobileTitleCreate": "طاق خود را ایجاد کنید", + "mobileTitleWelcome": "خوش آمدید", + "mobileSubtitleCreate": "یک رمز عبور اصلی برای محافظت از اعتبار خود تنظیم کنید", + "mobileSubtitleUnlock": "رمز اصلی خود را برای باز کردن قفل وارد کنید", + "placeholderMasterPassword": "رمز اصلی", + "placeholderConfirmPassword": "رمز عبور را تایید کنید", + "labelMasterPassword": "رمز اصلی", + "labelConfirmPassword": "رمز عبور را تایید کنید", + "placeholderEnterMaster": "رمز اصلی را وارد کنید", + "placeholderConfirmMaster": "رمز عبور اصلی را تأیید کنید", + "buttonCreateVault": "طاق ایجاد کنید", + "buttonUnlock": "باز کردن قفل", + "buttonUnlockVault": "قفل طاق را باز کنید", + "footerCreate": "رمز عبور شما هرگز ذخیره نمی شود و قابل بازیابی نیست", + "footerUnlock": "با رمزگذاری سرتاسر ایمن شده است", + "desktopTitleSetup": "طاق رمز عبور را راه اندازی کنید", + "desktopTitleUnlock": "قفل طاق را باز کنید", + "desktopDescriptionSetup": "یک رمز عبور اصلی برای ایمن کردن اعتبار خود ایجاد کنید. این رمز عبور هرگز ذخیره نمی شود و قابل بازیابی نیست.", + "desktopDescriptionUnlock": "رمز عبور اصلی خود را برای دسترسی به اطلاعات کاربری ایمن خود وارد کنید." + }, + "list": { + "loading": "در حال بارگیری رمزهای عبور...", + "emptyTitle": "خزانه خالی است", + "emptyHintMobile": "روی دکمه + ضربه بزنید تا اولین رمز عبور خود را اضافه کنید", + "emptyHintDesktop": "شما هنوز هیچ رمز عبوری را ذخیره نکرده اید. برای شروع روی دکمه «افزودن رمز عبور» در بالا کلیک کنید.", + "addPassword": "اضافه کردن رمز عبور", + "searchPlaceholderMobile": "خزانه خود را جستجو کنید ...", + "searchPlaceholderDesktop": "صندوق جستجو...", + "importExport": "واردات / صادرات", + "lockVault": "طاق قفل", + "allPasswordsTitle": "همه پسوردها", + "passwordCount": "{count, plural, one {# password} other {# passwords}}", + "vaultHealthDrawerTitle": "سلامت خرک", + "noResults": "هیچ رمز عبوری مطابق با \"{query}\" یافت نشد", + "tableService": "خدمات", + "tableUsername": "نام کاربری", + "tablePassword": "رمز عبور", + "tableUrl": "URL", + "tableActions": "اقدامات", + "gridViewAria": "نمای شبکه ای", + "listViewAria": "نمای فهرستی", + "passwordStrengthTitle": "قدرت رمز عبور", + "deleteDialogTitle": "رمز عبور را حذف کنید", + "deleteDialogDescription": "آیا مطمئن هستید که می خواهید این رمز عبور را حذف کنید؟ این عمل قابل لغو نیست.", + "cancel": "لغو کنید", + "delete": "حذف کنید", + "edit": "ویرایش کنید" + }, + "toasts": { + "copiedPassword": "رمز عبور در کلیپ بورد کپی شد", + "copiedUsername": "نام کاربری در کلیپ بورد کپی شد", + "passwordDeleted": "رمز عبور حذف شد", + "deleteFailed": "رمز عبور حذف نشد", + "vaultLocked": "طاق قفل شده", + "importedCount": "با موفقیت وارد شد {count, plural, one {# password} other {# passwords}}" + }, + "form": { + "addTitle": "رمز عبور جدید اضافه کنید", + "editTitle": "ویرایش رمز عبور", + "serviceName": "نام سرویس", + "usernameEmail": "نام کاربری / ایمیل", + "password": "رمز عبور", + "urlOptional": "آدرس وب سایت (اختیاری)", + "notesOptional": "یادداشت ها (اختیاری)", + "tags": "برچسب ها", + "placeholderService": "به عنوان مثال نتفلیکس", + "placeholderUsername": "user@example.com", + "placeholderPassword": "رمز عبور را وارد یا ایجاد کنید", + "placeholderUrl": "https://...", + "placeholderNotes": "توضیحات تکمیلی...", + "placeholderTags": "اضافه کردن برچسب (Enter یا کاما را فشار دهید)", + "strength": "قدرت", + "strengthWeak": "ضعیف", + "strengthMedium": "متوسط", + "strengthStrong": "قوی", + "advancedGenerator": "ژنراتور پیشرفته", + "cancel": "لغو کنید", + "saving": "در حال ذخیره...", + "savePassword": "ذخیره رمز عبور", + "updating": "در حال به روز رسانی...", + "updatePassword": "رمز عبور را به روز کنید", + "addPasswordSrOnly": "اضافه کردن رمز عبور", + "triggerAdd": "اضافه کردن رمز عبور", + "toastSaved": "رمز عبور با موفقیت ذخیره شد", + "toastSaveFailed": "رمز عبور ذخیره نشد", + "toastUpdated": "رمز عبور با موفقیت به روز شد", + "toastUpdateFailed": "رمز عبور به روز نشد" + }, + "security": { + "vaultHealth": "سلامت خرک", + "statusLabel": "وضعیت", + "totalPasswords": "کل رمزهای عبور", + "activeEntries": "ورودی های فعال در خزانه شما", + "protected": "محافظت شده است", + "weakPasswords": "رمزهای عبور ضعیف", + "needingImprovement": "رمزهای عبور نیاز به بهبود دارند", + "allGood": "همه خوبن", + "reusedPasswords": "پسوردهای استفاده مجدد", + "usedMultipleTimes": "رمزهای عبور چندین بار استفاده شده", + "unique": "منحصر به فرد", + "total": "مجموع", + "weak": "ضعیف", + "reused": "استفاده مجدد", + "totalCountLabel": "{count, plural, one {# password} other {# passwords}}", + "weakCountLabel": "{count} ضعیف", + "allSecure": "همه امن", + "scoreExcellent": "عالی", + "scoreGood": "خوب", + "scoreFair": "منصفانه", + "scoreCritical": "انتقادی" + }, + "importExport": { + "title": "صندوق واردات / صادرات", + "description": "اطلاعات رمز عبور خود را مدیریت کنید می توانید صندوق خود را به JSON صادر کنید یا از یک فایل JSON وارد کنید.", + "tabExport": "صادرات", + "tabImport": "واردات", + "warningTitle": "هشدار", + "warningBody": "فایل های صادر شده حاوی رمزهای عبور شما به صورت متن ساده هستند. این فایل را ایمن نگه دارید و پس از استفاده آن را حذف کنید.", + "exportBlurb": "کل صندوق خود را به عنوان یک فایل JSON دانلود کنید. این شامل تمام خدمات، نام‌های کاربری، رمز عبور و یادداشت‌ها می‌شود.", + "downloadJson": "صادرات JSON را دانلود کنید", + "pasteLabel": "داده های JSON را جایگذاری کنید", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "محتوای یک فایل JSON قبلا صادر شده را در اینجا جای‌گذاری کنید.", + "importing": "واردات...", + "importPasswords": "وارد کردن رمزهای عبور", + "trigger": "واردات / صادرات", + "toastExported": "خرک با موفقیت صادر شد", + "toastExportFailed": "صندوق صادرات انجام نشد", + "toastImportFailed": "وارد کردن انجام نشد: قالب JSON نامعتبر است" + }, + "card": { + "copyUsername": "نام کاربری را کپی کنید", + "openUrl": "URL را باز کنید", + "strength": "قدرت" + }, + "swipeable": { + "copy": "کپی کنید", + "delete": "حذف کنید", + "copyPassword": "رمز عبور را کپی کنید", + "copyUsername": "نام کاربری را کپی کنید", + "edit": "ویرایش کنید", + "deleteAction": "حذف کنید" + }, + "floatingMenu": { + "lockVault": "طاق قفل", + "importExport": "واردات / صادرات", + "addPassword": "اضافه کردن رمز عبور" + }, + "generator": { + "tabPassword": "رمز عبور", + "tabPassphrase": "عبارت عبور", + "strength": "قدرت", + "lengthLabel": "طول: {length} کاراکتر", + "lengthBadge": "{length} کاراکتر", + "characterSets": "مجموعه شخصیت ها", + "uppercase": "حروف بزرگ", + "lowercase": "حروف کوچک", + "numbers": "اعداد", + "symbols": "نمادها", + "wordCountLabel": "تعداد کلمات: {count}", + "wordCountBadge": "{count, plural, one {# word} other {# words}}", + "separator": "جداکننده", + "capitalization": "حروف بزرگ", + "includeNumber": "شامل شماره" + } + }, + "EnvironmentManager": { + "subtitle": "متغیرهای محیط رمزگذاری‌شده به‌ازای هر پروژه و محیط.", + "page": { + "loading": "در حال بارگذاری…", + "title": "مدیر محیط", + "loadFailed": "بارگذاری مجموعه‌های محیط ناموفق بود" + }, + "list": { + "title": "محیط‌ها", + "loading": "در حال بارگذاری مجموعه‌ها…", + "emptyTitle": "هنوز مجموعه محیطی وجود ندارد", + "emptyHint": "برای هر پروژه و محیط یک مجموعه بسازید (مثلاً کلیدهای API تولید). همه قبل از آپلود در مرورگر شما رمزگذاری می‌شوند.", + "searchPlaceholder": "جستجوی پروژه، محیط، برچسب، یادداشت…", + "tagFilterPlaceholder": "فیلتر با برچسب", + "variableCount": "{count, plural, one {# متغیر} other {# متغیر}}", + "updatedAgo": "به‌روزرسانی {time}", + "copyDotEnv": "کپی .env", + "edit": "ویرایش", + "delete": "حذف", + "cancel": "لغو", + "confirmDelete": "حذف", + "deleteDialogTitle": "حذف مجموعه محیط؟", + "deleteDialogDescription": "بلاب رمزگذاری‌شده از حساب شما حذف می‌شود. قابل بازگشت نیست.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env در کلیپ‌بورد کپی شد", + "deleted": "مجموعه محیط حذف شد", + "deleteFailed": "حذف ناموفق بود" + }, + "form": { + "addTitle": "مجموعه محیط جدید", + "editTitle": "ویرایش مجموعه محیط", + "triggerAdd": "افزودن مجموعه محیط", + "addSrOnly": "افزودن مجموعه محیط", + "project": "پروژه", + "environment": "محیط", + "placeholderProject": "مثال billing-api", + "placeholderEnvironment": "مثال production", + "pasteEnvLabel": "چسباندن .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "ادغام خطوط چسبانده‌شده", + "variables": "متغیرها", + "addRow": "افزودن سطر", + "keyPlaceholder": "کلید", + "valuePlaceholder": "مقدار", + "notesOptional": "یادداشت (اختیاری)", + "placeholderNotes": "سیاست چرخش، مالک، پیوندها…", + "tags": "برچسب‌ها", + "placeholderTags": "افزودن برچسب (Enter یا ویرگول)", + "cancel": "لغو", + "save": "ذخیره", + "saving": "در حال ذخیره…", + "update": "به‌روزرسانی", + "updating": "در حال به‌روزرسانی…", + "toastSaved": "مجموعه محیط ذخیره شد", + "toastSaveFailed": "ذخیره ناموفق بود", + "toastUpdated": "مجموعه محیط به‌روز شد", + "toastUpdateFailed": "به‌روزرسانی ناموفق بود", + "toastPasteEmpty": "خط کلید=مقدار یافت نشد", + "toastPasteApplied": "{count} متغیر از چسباندن ادغام شد", + "toastProjectEnvRequired": "پروژه و محیط الزامی است", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "در حال بارگیری..." + }, + "manager": { + "foldersHeading": "پوشه ها", + "tagsHeading": "برچسب ها", + "tagsMore": "+{count} بیشتر", + "tagsShowLess": "Show less", + "searchPlaceholder": "بوک مارک های خود را جستجو کنید...", + "import": "واردات", + "export": "صادرات", + "exportAsHtml": "صادرات به عنوان HTML", + "exportAsJson": "صادرات به عنوان JSON", + "addBookmark": "اضافه کردن نشانک", + "matchingQuery": "مطابق با \"{query}\"", + "bookmarkCount": "{count, plural, one {# bookmark} other {# bookmarks}}", + "allBookmarks": "همه نشانک ها", + "uncategorized": "دسته بندی نشده", + "unknownFolder": "ناشناس" + }, + "card": { + "urlCopied": "URL در کلیپ بورد کپی شد", + "bookmarkDeleted": "نشانک حذف شد", + "edit": "ویرایش کنید", + "delete": "حذف کنید", + "copyUrl": "URL را کپی کنید", + "deleteConfirmTitle": "نشانک حذف شود؟", + "deleteConfirmDescription": "با این کار \"{title}\" برای همیشه حذف می شود.", + "cancel": "لغو کنید" + }, + "grid": { + "emptyTitle": "هنوز هیچ نشانکی وجود ندارد", + "emptyDescription": "برای شروع، اولین نشانک خود را اضافه کنید یا از مرورگر خود وارد کنید." + }, + "folderTree": { + "allBookmarks": "همه نشانک ها", + "addSubfolder": "افزودن زیر پوشه", + "rename": "تغییر نام دهید", + "deleteFolderTitle": "پوشه \"{name}\" حذف شود؟", + "deleteFolderDescription": "با این کار پوشه و تمام زیر پوشه های آن حذف می شود. نشانک‌های موجود در این پوشه‌ها به دسته‌بندی نشده منتقل می‌شوند." + }, + "import": { + "title": "وارد کردن نشانک ها", + "description": "نشانک ها را از فایل صادر شده مرورگر خود وارد کنید.", + "parseError": "فایل نشانک تجزیه نشد. لطفاً مطمئن شوید که صادر کردن نشانک معتبر است.", + "successToast": "{bookmarks} نشانک و {folders} پوشه وارد شده است", + "errorToast": "وارد کردن نشانک ها انجام نشد", + "chromeInstruction": "منو → نشانک ها → مدیر نشانک → ⋮ → صادرات نشانک ها", + "firefoxInstruction": "منو → نشانک ها → مدیریت نشانک ها → وارد کردن و پشتیبان گیری → صادرات", + "safariInstruction": "فایل → صادرات نشانک‌ها...", + "edgeInstruction": "منو → موارد دلخواه → ⋯ → موارد دلخواه صادر کنید", + "dropZoneTitle": "فایل نشانک خود را اینجا رها کنید", + "dropZoneHint": "یا برای مرور کلیک کنید (HTML یا JSON)", + "selectedFileHint": "برای انتخاب فایل دیگری کلیک کنید", + "errorTitle": "خطا", + "previewTitle": "پیش نمایش واردات", + "previewBookmarks": "{count, plural, one {# bookmark} other {# bookmarks}}", + "previewFolders": "{count, plural, one {# folder} other {# folders}}", + "importModeLabel": "حالت واردات", + "modeMerge": "ادغام با نشانک های موجود", + "modeReplace": "همه نشانک های موجود را جایگزین کنید", + "cancel": "لغو کنید", + "importButton": "واردات", + "importButtonWithCount": "واردات {count, plural, one {# bookmark} other {# bookmarks}}" + }, + "addBookmark": { + "titleAdd": "اضافه کردن نشانک", + "titleEdit": "ویرایش نشانک", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "لطفاً یک URL معتبر وارد کنید", + "titleLabel": "عنوان", + "titlePlaceholder": "عنوان نشانک", + "titleRequired": "لطفا یک عنوان وارد کنید", + "descriptionLabel": "توضیحات (اختیاری)", + "descriptionPlaceholder": "یک یادداشت در مورد این نشانک اضافه کنید", + "folderLabel": "پوشه", + "folderPlaceholder": "پوشه را انتخاب کنید", + "noFolder": "پوشه ای وجود ندارد", + "tagsLabel": "برچسب ها", + "tagsPlaceholder": "افزودن برچسب (Enter را فشار دهید)", + "toastUpdated": "نشانک به روز شد", + "toastAdded": "نشانک اضافه شد", + "cancel": "لغو کنید", + "submitAdd": "اضافه کردن نشانک", + "submitUpdate": "به روز رسانی نشانک" + }, + "addFolder": { + "title": "پوشه ایجاد کنید", + "nameLabel": "نام پوشه", + "namePlaceholder": "پوشه من", + "nameRequired": "لطفا یک نام پوشه وارد کنید", + "parentLabel": "پوشه والد", + "parentPlaceholder": "پوشه والد را انتخاب کنید", + "rootParent": "ریشه (بدون والد)", + "toastCreated": "پوشه ایجاد شد", + "cancel": "لغو کنید", + "submit": "پوشه ایجاد کنید" + }, + "editFolder": { + "title": "تغییر نام پوشه", + "nameLabel": "نام پوشه", + "namePlaceholder": "نام پوشه", + "nameRequired": "لطفا یک نام پوشه وارد کنید", + "toastRenamed": "پوشه تغییر نام داد", + "cancel": "لغو کنید", + "save": "ذخیره کنید" + } + }, + "EmailValidator": { + "header": { + "title": "اعتبار سنجی ایمیل", + "description": "آدرس‌های ایمیل را برای نحو، دامنه و قابلیت تحویل تأیید و تأیید کنید." + }, + "tabs": { + "single": "تایید تک", + "bulk": "آپلود انبوه" + }, + "single": { + "cardTitle": "تایید تک ایمیل", + "cardDescription": "یک آدرس ایمیل برای بررسی قابلیت تحویل و شهرت آن وارد کنید.", + "placeholder": "آدرس ایمیل را وارد کنید...", + "clearAria": "ایمیل را پاک کنید", + "validate": "اعتبار سنجی", + "validating": "در حال اعتبارسنجی...", + "formatError": "لطفا یک آدرس ایمیل معتبر وارد کنید" + }, + "toasts": { + "errorTitle": "خطا", + "enterEmail": "لطفا یک آدرس ایمیل وارد کنید", + "invalidFormatTitle": "قالب ایمیل نامعتبر است", + "invalidFormatDescription": "لطفا یک آدرس ایمیل معتبر وارد کنید", + "validateFailedDescription": "هنگام تأیید اعتبار ایمیل خطایی رخ داد", + "noEmailsTitle": "هیچ ایمیلی یافت نشد", + "noEmailsDescription": "لطفاً مطمئن شوید که اکسل شما دارای ستون \"ایمیل\" است.", + "fileTooLargeTitle": "فایل خیلی بزرگ است", + "fileTooLargeDescription": "{total} ایمیل پیدا شد. فقط 4000 مورد اول تایید خواهند شد.", + "successTitle": "موفقیت", + "successValidated": "{count} ایمیل با موفقیت تایید شد.", + "batchFailedTitle": "اعتبار سنجی دسته ای ناموفق بود", + "batchFailedDescription": "هنگام پردازش اعتبارسنجی انبوه خطایی روی داد.", + "noExportTitle": "داده ای برای صادرات وجود ندارد", + "noExportValidOnly": "هیچ ایمیل معتبری برای صادرات یافت نشد.", + "noExportAll": "هیچ نتیجه ای برای صادرات وجود ندارد.", + "exportSuccessTitle": "صادرات با موفقیت انجام شد", + "exportSuccessExcelAll": "{count} ایمیل به اکسل صادر شد.", + "exportSuccessExcelValid": "{count} ایمیل معتبر به اکسل صادر شد.", + "exportSuccessCsvAll": "{count} ایمیل به CSV صادر شد.", + "exportSuccessCsvValid": "{count} ایمیل معتبر به CSV صادر شد." + }, + "disposableAlert": { + "title": "ایمیل یکبار مصرف شناسایی شد", + "description": "این آدرس ایمیل متعلق به یک ارائه دهنده ایمیل یکبار مصرف است. ممکن است موقت یا ناامن باشد." + }, + "score": { + "reliabilityTitle": "امتیاز قابلیت اطمینان", + "scoreLabel": "امتیاز" + }, + "details": { + "sectionTitle": "جزئیات اعتبار سنجی" + }, + "validation": { + "syntaxLabel": "نحو معتبر", + "syntaxTooltip": "بررسی می کند که آیا آدرس ایمیل از قالب صحیح پیروی می کند (به عنوان مثال، user@domain.com)", + "domainLabel": "دامنه وجود دارد", + "domainTooltip": "تأیید می کند که نام دامنه در آدرس ایمیل واقعاً وجود دارد", + "mxLabel": "رکوردهای MX پیدا شد", + "mxTooltip": "بررسی می‌کند که آیا دامنه رکوردهای تبادل ایمیل (MX) برای دریافت ایمیل‌ها پیکربندی شده است یا خیر", + "mailboxLabel": "صندوق پستی موجود است", + "mailboxTooltip": "بررسی می کند که صندوق پستی/حساب خاص در سرور ایمیل وجود دارد", + "disposableLabel": "ایمیل یکبار مصرف", + "disposableTooltip": "نشان می دهد که آیا این ایمیل متعلق به یک سرویس ایمیل موقت/یکبار مصرف است (ممکن است خطرناک باشد)", + "roleBasedLabel": "ایمیل مبتنی بر نقش", + "roleBasedTooltip": "تشخیص می دهد که آیا این ایمیل مبتنی بر نقش است (به عنوان مثال، support@، info@) به جای یک آدرس شخصی" + }, + "copy": { + "emailCopied": "ایمیل کپی شد!", + "copyTooltip": "ایمیل را کپی کنید" + }, + "bulk": { + "dropHere": "فایل خود را اینجا رها کنید", + "dragHere": "فایل خود را در اینجا بکشید و رها کنید", + "uploadHint": "برای تأیید اعتبار حداکثر 4000 ایمیل، یک فایل xlsx.، .xls.، یا .csv. حاوی یک ستون «ایمیل» آپلود کنید.", + "filePrefix": "فایل:", + "selectFile": "فایل را انتخاب کنید", + "downloadTemplate": "دانلود قالب" + }, + "bulkLoading": { + "title": "در حال تایید ایمیل ها...", + "batchProgress": "در حال پردازش دسته {current} از {total}..." + }, + "stats": { + "totalProcessed": "کل پردازش شده", + "valid": "معتبر است", + "risky": "مخاطره آمیز", + "invalid": "نامعتبر است" + }, + "results": { + "detailedTitle": "نتایج تفصیلی", + "searchPlaceholder": "جستجو در ایمیل ها...", + "newVerification": "تأیید جدید", + "exportAll": "صادرات همه", + "exportExcel": "Excel (xlsx.)", + "exportCsv": "CSV (csv.)", + "exportValid": "صادر کردن معتبر ({count})" + }, + "showingResults": "نمایش {shown} از {total} نتایج", + "table": { + "email": "ایمیل", + "status": "وضعیت", + "score": "امتیاز", + "checks": "چک ها" + }, + "noSearchResults": "هیچ نتیجه ای مطابق با \"{query}\" یافت نشد", + "checksTooltip": { + "syntax": "نحو", + "domain": "دامنه", + "mx": "MX", + "mailbox": "صندوق پستی" + }, + "exportColumns": { + "email": "ایمیل", + "status": "وضعیت", + "score": "امتیاز", + "syntax": "نحو", + "domain": "دامنه", + "mx": "MX", + "mailbox": "صندوق پستی", + "disposable": "یکبار مصرف", + "roleBased": "مبتنی بر نقش", + "valid": "معتبر است", + "invalid": "نامعتبر است", + "yes": "بله", + "no": "خیر" + }, + "sheets": { + "validEmails": "ایمیل های معتبر", + "results": "نتایج", + "template": "الگو" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Explorer", + "heroSubtitle": "برای مدیریت مجموعه ها، اجرای پرس و جو و تجسم داده های خود به پایگاه داده خود متصل شوید.", + "featureMultiTitle": "چند اتصال", + "featureMultiDesc": "چندین اتصال پایگاه داده را به طور همزمان مدیریت کنید.", + "featureMongoTitle": "پشتیبانی MongoDB", + "featureMongoDesc": "پشتیبانی بومی برای MongoDB با فیلتر پیشرفته.", + "featureQueryTitle": "پرس و جو هوشمند", + "featureQueryDesc": "سازنده پرس و جو قدرتمند با برجسته سازی نحو.", + "connectCta": "به MongoDB متصل شوید", + "footerNote": "در حال حاضر از {db} پشتیبانی می کند. پایگاه داده های بیشتر به زودی.", + "footerDb": "MongoDB", + "selectCollectionTitle": "یک مجموعه را انتخاب کنید", + "selectCollectionDesc": "مجموعه ای را از نوار کناری برای مشاهده اسناد انتخاب کنید و شروع به کاوش در داده های خود کنید.", + "dialogAddConnection": "افزودن اتصال", + "toastConnectionAdded": "اتصال اضافه شد", + "toastFetchFailed": "اسناد برای {collection} واکشی نشد", + "toastDocDeleted": "سند حذف شد", + "deleteDocTitle": "آیا کاملا مطمئن هستید؟", + "deleteDocDescription": "این عمل قابل لغو نیست. با این کار سند برای همیشه از مجموعه شما حذف می شود.", + "closeAllTitle": "همه برگه ها را ببندید؟", + "closeAllDescription": "آیا مطمئن هستید که می خواهید همه برگه ها را ببندید؟ هر گونه تغییر ذخیره نشده در فیلترهای پرس و جو از بین خواهد رفت.", + "closeAllConfirm": "بستن همه", + "cancel": "لغو کنید", + "delete": "حذف کنید" + }, + "connection": { + "titleEdit": "ویرایش اتصال", + "titleConnect": "به MongoDB متصل شوید", + "subtitleEdit": "جزئیات اتصال خود را در زیر به روز کنید.", + "subtitleConnect": "رشته اتصال MongoDB خود را وارد کنید تا کاوش داده های خود را شروع کنید.", + "labelName": "نام", + "placeholderName": "به عنوان مثال دی بی تولید", + "labelConnectionString": "رشته اتصال", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "قالب: mongodb://username:password@host:port/database", + "testConnection": "تست اتصال", + "connecting": "در حال اتصال...", + "updateConnect": "به روز رسانی و اتصال", + "connect": "اتصال", + "cancelEdit": "لغو ویرایش", + "savedConnections": "اتصالات ذخیره شده", + "emptySaved": "هنوز هیچ اتصال ذخیره شده ای وجود ندارد.", + "lastUsed": "آخرین استفاده {time}", + "toastTestOk": "اتصال موفق شد!", + "toastTestFail": "اتصال ناموفق بود: {message}", + "toastUpdated": "اتصال به روز شد", + "confirmDelete": "آیا مطمئنید که می خواهید این اتصال ذخیره شده را حذف کنید؟", + "toastDeletedConn": "اتصال حذف شد", + "toastDeleteFail": "اتصال حذف نشد", + "copyStringTitle": "رشته اتصال را کپی کنید", + "toastStringCopied": "رشته اتصال کپی شد!", + "editTitle": "ویرایش کنید", + "deleteTitle": "حذف کنید" + }, + "sidebar": { + "explorer": "کاوشگر", + "add": "اضافه کنید", + "tooltipAdd": "افزودن اتصال جدید", + "tooltipRefresh": "اتصالات را تازه کنید", + "searchPlaceholder": "جستجو...", + "loadingConnections": "در حال بارگیری اتصالات...", + "noConnectionsFound": "هیچ اتصالی یافت نشد", + "toastLoadFail": "اتصالات بارگیری نشد", + "toastConnectFail": "اتصال به {name} انجام نشد", + "toastCollectionsFail": "واکشی مجموعه‌ها برای {name} انجام نشد", + "toastRenamed": "نام اتصال تغییر کرد", + "toastRenameFail": "تغییر نام اتصال انجام نشد", + "confirmDeleteConnection": "آیا مطمئن هستید که می خواهید اتصال \"{name}\" را حذف کنید؟", + "toastDeleted": "اتصال حذف شد", + "toastDeleteConnFail": "اتصال حذف نشد", + "confirmDropDb": "آیا مطمئن هستید که می خواهید پایگاه داده \"{name}\" را حذف کنید؟ این عمل قابل لغو نیست.", + "toastDbDropped": "پایگاه داده {name} حذف شد", + "confirmDropCollection": "آیا مطمئنید که می خواهید مجموعه \"{name}\" را رها کنید؟ این عمل قابل لغو نیست.", + "toastCollectionDropped": "مجموعه {name} حذف شد", + "toastCollectionRenamed": "مجموعه به {name} تغییر نام داد", + "toastDatabaseRenamed": "پایگاه داده به {name} تغییر نام داد", + "connecting": "در حال اتصال...", + "connectionFailed": "اتصال ناموفق بود", + "noDatabases": "بدون پایگاه داده", + "loading": "در حال بارگیری...", + "noCollections": "بدون مجموعه", + "renameCollectionTitle": "تغییر نام مجموعه", + "renameDatabaseTitle": "تغییر نام پایگاه داده", + "placeholderNewCollection": "نام مجموعه جدید", + "placeholderNewDatabase": "نام پایگاه داده جدید", + "rename": "تغییر نام دهید", + "refresh": "تازه کردن", + "copyDbName": "نام DB را کپی کنید", + "toastDbNameCopied": "نام پایگاه داده در کلیپ بورد کپی شد", + "dropDatabase": "پایگاه داده را رها کنید", + "dropCollection": "رها کردن مجموعه", + "menuRename": "تغییر نام دهید", + "menuCopyConnectionString": "رشته اتصال را کپی کنید", + "menuDeleteConnection": "اتصال را حذف کنید", + "toastSidebarStringCopied": "رشته اتصال کپی شد!", + "cancel": "لغو کنید", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} اسناد", + "table": "جدول", + "json": "JSON", + "tree": "درخت", + "tooltipTable": "نمای جدول", + "tooltipJson": "نمایش JSON", + "tooltipTree": "نمای درخت", + "itemsPerPage": "موارد در هر صفحه", + "perPage": "{n} / صفحه", + "prevPage": "صفحه قبلی", + "nextPage": "صفحه بعدی", + "expandAll": "همه را گسترش دهید", + "collapseAll": "جمع کردن همه", + "refreshData": "Refresh Data", + "insert": "درج کنید", + "export": "صادرات", + "actions": "اقدامات", + "invalidJson": "محتوای JSON نامعتبر است", + "copiedClipboard": "در کلیپ بورد کپی شد", + "docUpdated": "سند با موفقیت به روز شد", + "docInserted": "سند با موفقیت درج شد", + "invalidJsonShort": "JSON نامعتبر است", + "docCopied": "سند در کلیپ بورد کپی شد", + "openFullEditor": "در ویرایشگر کامل باز کنید", + "cellArray": "آرایه ({length})", + "cellObject": "شیء", + "cellArrayBracket": "آرایه[{length}]", + "emptyFilterTitle": "هیچ سندی با درخواست شما مطابقت ندارد", + "emptyFilterHint": "سعی کنید فیلتر خود را تنظیم کنید یا درخواست را پاک کنید.", + "clearFilter": "فیلتر را پاک کنید", + "emptyCollectionTitle": "این مجموعه خالی است", + "emptyCollectionHint": "برای شروع یک سند وارد کنید.", + "insertDocument": "درج سند", + "formatJson": "فرمت JSON", + "compactJson": "JSON فشرده", + "copyClipboard": "در کلیپ بورد کپی کنید", + "copyJson": "JSON را کپی کنید", + "duplicate": "تکراری", + "editDoc": "ویرایش سند", + "deleteDoc": "حذف سند", + "documentLabel": "سند {n}", + "editDialogTitle": "ویرایش سند", + "insertDialogTitle": "درج سند", + "viewValueTitle": "مشاهده ارزش", + "saveChanges": "ذخیره تغییرات", + "close": "بستن", + "cancel": "لغو کنید", + "insertButton": "درج کنید", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "فیلتر فعال اعمال شد", + "filterActiveTooltip": "فیلتر فعال است", + "closeAll": "بستن همه" + }, + "query": { + "migrateHistory": "سابقه پرس و جو به ابر منتقل شد", + "saveHistoryFail": "تاریخچه پرس و جو ذخیره نشد", + "deleteQueryFail": "درخواست حذف نشد", + "invalidJsonQuery": "جستار JSON نامعتبر است", + "placeholder": "به عنوان مثال { \"status\": \"active\" }", + "clearQuery": "پرس و جو را پاک کنید", + "openAdvanced": "ویرایشگر پیشرفته را باز کنید", + "visualBuilder": "Visual Query Builder", + "filterRules": "قوانین فیلتر", + "addRule": "قانون اضافه کنید", + "noFilters": "هیچ فیلتری اعمال نشده است. یک قانون برای فیلتر کردن اسناد اضافه کنید.", + "where": "کجا", + "and": "و", + "fieldPlaceholder": "میدان", + "opPlaceholder": "Op", + "typePlaceholder": "تایپ کنید", + "opRegex": "Regex", + "opIn": "در", + "opNin": "نه در", + "opExists": "وجود دارد", + "typeAuto": "خودکار", + "typeString": "رشته", + "typeNumber": "شماره", + "typeBoolean": "بولی", + "typeDate": "تاریخ", + "typeObjectId": "ObjectId", + "true": "درست است", + "false": "نادرست", + "valuePlaceholder": "ارزش", + "valueListPlaceholder": "ارزش 1، ارزش 2", + "clearFilters": "پاک کردن فیلترها", + "applyFilters": "اعمال فیلترها", + "queryHistory": "تاریخچه پرس و جو", + "noSavedQueries": "هنوز درخواستی ذخیره نشده است", + "advancedEditorTitle": "ویرایشگر پرس و جو پیشرفته", + "templatesPlaceholder": "قالب ها", + "templateEmptyAgg": "تجمع خالی", + "templateMatch": "$match (فیلتر)", + "templateLookup": "$lookup (پیوستن)", + "templateGroup": "$group (Count/Sum)", + "format": "قالب", + "formattedJson": "JSON فرمت شده", + "runQuery": "Query را اجرا کنید" + }, + "export": { + "selectFieldsError": "لطفاً حداقل یک فیلد را برای صادرات انتخاب کنید", + "success": "با موفقیت به {filename} صادر شد", + "fail": "داده‌ها صادر نشد", + "title": "صادرات داده ها", + "formatLabel": "فرمت صادرات", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "فیلدها را انتخاب کنید", + "selectAll": "همه را انتخاب کنید", + "fieldsSelected": "{count, plural, one {# field selected} other {# fields selected}}", + "cancel": "لغو کنید", + "export": "صادرات", + "sheetName": "داده ها" + }, + "jsonTree": { + "typeLabel": "نوع: {type}", + "itemSummary": "{count, plural, one {# item} other {# items}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "درخواست جدید", + "importedRequest": "درخواست وارداتی", + "untitledRequest": "درخواست بدون عنوان" + }, + "toasts": { + "curlImported": "cURL با موفقیت وارد شد", + "curlParseFailed": "دستور cURL تجزیه نشد", + "invalidJsonBody": "متن JSON نامعتبر است", + "requestFailed": "درخواست ناموفق بود: {message}", + "curlPasted": "cURL با موفقیت جای‌گذاری و تجزیه شد", + "responseCopied": "پاسخ در کلیپ بورد کپی شد", + "codeCopied": "کد در کلیپ بورد کپی شد", + "copyFailed": "کپی در کلیپ‌بورد ناموفق بود" + }, + "layout": { + "collections": "مجموعه ها", + "closeSidebar": "نوار کناری را ببندید", + "openSidebar": "نوار کناری را باز کنید", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "روش", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "ارسال...", + "send": "ارسال کنید", + "invalidJsonBodyHelp": "ارسال امکان‌پذیر نیست: بدنه JSON نامعتبر است" + }, + "requestTabs": { + "params": "پارامترها", + "headers": "سرصفحه ها", + "body": "بدن", + "auth": "احراز هویت", + "bodyType": "نوع بدن", + "selectType": "نوع را انتخاب کنید", + "bodyNone": "هیچ کدام", + "bodyJson": "JSON", + "bodyText": "متن", + "noBodyHint": "هیچ محتوایی برای این درخواست وجود ندارد", + "switchToJson": "به بدنه JSON بروید", + "authType": "نوع احراز هویت", + "noAuth": "بدون احراز هویت", + "bearerToken": "نشان حامل", + "basicAuth": "اعتبار پایه", + "apiKey": "کلید API", + "labelToken": "رمز", + "placeholderBearerToken": "نشان حامل", + "labelUsername": "نام کاربری", + "placeholderUsername": "نام کاربری", + "labelPassword": "رمز عبور", + "placeholderPassword": "رمز عبور", + "labelKey": "کلید", + "placeholderApiKeyName": "نام کلید (به عنوان مثال X-Api-Key)", + "labelValue": "ارزش", + "placeholderValue": "ارزش", + "addTo": "اضافه کردن به", + "header": "سربرگ", + "queryParams": "پارامترهای پرس و جو", + "noAuthHint": "این درخواست از احراز هویت استفاده نمی کند." + }, + "responsePanel": { + "emptyTitle": "آماده کاوش هستید؟", + "emptyHint": "یک URL وارد کنید و روی Send کلیک کنید تا داده‌های پاسخ را در اینجا ببینید.", + "responsePreviewAlt": "پیش نمایش پاسخ", + "previewUnavailable": "پیش نمایش برای این نوع محتوا در دسترس نیست", + "status": "وضعیت", + "time": "زمان", + "size": "اندازه", + "copyBody": "کپی بدنه", + "downloadResponse": "پاسخ را دانلود کنید", + "bodyTab": "بدن", + "previewTab": "پیش نمایش", + "headersTab": "سرصفحه ها", + "binaryRawView": "داده های باینری را نمی توان در نمای خام نمایش داد.", + "errorStatusLabel": "خطا", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "برگه جدید" + }, + "collectionsSidebar": { + "title": "منابع API", + "newCollection": "مجموعه جدید", + "tabCollections": "مجموعه ها", + "tabHistory": "تاریخچه", + "noCollectionsTitle": "بدون مجموعه", + "noCollectionsHint": "مجموعه ای برای سازماندهی درخواست های API خود ایجاد کنید.", + "createCollection": "مجموعه ایجاد کنید", + "newFolder": "پوشه جدید", + "rename": "تغییر نام دهید", + "delete": "حذف کنید", + "emptyCollection": "مجموعه خالی", + "recentRequests": "درخواست های اخیر", + "clearAll": "پاک کردن همه", + "noHistoryTitle": "هنوز سابقه ندارد", + "noHistoryHint": "برای دیدن آن اینجا درخواست ارسال کنید.", + "dialogNewFolderTitle": "پوشه جدید", + "dialogNewFolderDescription": "یک پوشه جدید برای سازماندهی درخواست های خود ایجاد کنید.", + "labelFolderName": "نام پوشه", + "placeholderFolderName": "پوشه من", + "createFolder": "پوشه ایجاد کنید", + "dialogNewCollectionTitle": "مجموعه جدید", + "dialogNewCollectionDescription": "یک مجموعه جدید در سطح بالا ایجاد کنید.", + "labelCollectionName": "نام مجموعه", + "placeholderCollectionName": "مجموعه من", + "dialogRenameCollectionTitle": "تغییر نام مجموعه", + "dialogRenameCollectionDescription": "یک نام جدید برای مجموعه وارد کنید.", + "placeholderRenameCollection": "نام مجموعه", + "renameAction": "تغییر نام دهید", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "پوشه جدید", + "delete": "حذف کنید", + "emptyFolder": "پوشه خالی" + }, + "environmentManager": { + "noEnvironment": "بدون محیط", + "manageDialogTitle": "مدیریت محیط ها", + "placeholderNewEnv": "محیط جدید", + "variablesHeading": "{name} متغیرها", + "addVariable": "متغیر اضافه کنید", + "columnVariable": "متغیر", + "columnValue": "ارزش", + "placeholderKey": "کلید", + "placeholderValue": "ارزش", + "selectEnvToEdit": "محیطی را برای ویرایش انتخاب کنید" + }, + "codeGenerator": { + "triggerTitle": "کد تولید کنید", + "dialogTitle": "کد تولید کنید", + "languageCurl": "CURL", + "languageJavaScript": "جاوا اسکریپت (واکشی)", + "languagePython": "پایتون (درخواست ها)", + "languageGo": "برو", + "copy": "کپی کنید", + "errorGenerating": "خطا در تولید کد" + }, + "keyValueEditor": { + "columnKey": "کلید", + "columnValue": "ارزش", + "placeholderKey": "کلید", + "placeholderValue": "ارزش", + "addItem": "مورد را اضافه کنید" + }, + "importCurl": { + "trigger": "وارد کردن cURL", + "dialogTitle": "وارد کردن cURL", + "dialogDescription": "دستور cURL خود را در زیر قرار دهید تا آن را به عنوان یک درخواست جدید وارد کنید.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "واردات" + }, + "saveRequest": { + "trigger": "ذخیره کنید", + "dialogTitle": "ذخیره درخواست", + "dialogDescription": "این درخواست را در یک پوشه مجموعه ذخیره کنید.", + "labelName": "درخواست نام", + "placeholderName": "درخواست من", + "labelFolder": "پوشه", + "placeholderFolder": "یک پوشه را انتخاب کنید", + "save": "ذخیره کنید" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "حالت ویرایش", + "viewOnlyMode": "مشاهده فقط حالت", + "insertComponent": "درج جزء", + "addUnorderedList": "لیست نامرتب را اضافه کنید", + "addOrderedList": "اضافه کردن لیست سفارش داده شده", + "addTable": "جدول اضافه کنید" + }, + "addBlock": "اضافه کردن بلوک", + "media": { + "popoverTitle": "رسانه اضافه کنید", + "singleImage": "تک تصویر", + "singleImageDesc": "یک تصویر آپلود کنید", + "multipleImages": "تصاویر متعدد", + "multipleImagesDesc": "شبکه تصویر بارگذاری کنید", + "video": "ویدئو", + "videoDesc": "آپلود فایل ویدیویی", + "uploadingAlt": "در حال آپلود...", + "uploadFailedAlt": "آپلود انجام نشد" + }, + "export": { + "fabAria": "کد صادراتی", + "title": "کد صادرات", + "description": "خروجی HTML یا JSON محتوای ویرایشگر خود را کپی کنید", + "preview": "پیش نمایش", + "html": "HTML", + "json": "JSON", + "previewOptions": "گزینه های پیش نمایش", + "enhanceSpaces": "افزایش فضاها", + "livePreview": "پیش نمایش زنده HTML ارائه شده", + "htmlTailwind": "HTML با کلاس های Tailwind CSS", + "editorJson": "حالت ویرایشگر به عنوان JSON", + "copyHtml": "HTML را کپی کنید", + "copyJson": "JSON را کپی کنید", + "copied": "کپی شده!" + }, + "template": { + "fabTitle": "تغییر الگو", + "title": "یک الگو را انتخاب کنید", + "description": "برای شروع سریع با طرح‌بندی‌ها و محتوای از پیش ساخته شده، یک الگو انتخاب کنید", + "catAll": "همه قالب ها", + "catProductivity": "بهره وری", + "catCreative": "خلاق", + "catBusiness": "تجارت", + "catPersonal": "شخصی", + "noTemplates": "هیچ قالبی در این دسته یافت نشد", + "confirmTitle": "محتوای موجود جایگزین شود؟", + "confirmDescription": "شما محتوای موجود در ویرایشگر دارید. اعمال این الگو جایگزین تمام محتوای فعلی می شود.", + "cannotUndo": "این عمل قابل لغو نیست.", + "cancel": "لغو کنید", + "replaceContent": "جایگزینی محتوا" + }, + "command": { + "searchPlaceholder": "دستورات جستجو...", + "noResults": "هیچ فرمانی یافت نشد", + "turnInto": "تبدیل به", + "h1": { + "label": "سرفصل 1", + "description": "عنوان بخش بزرگ", + "keywords": "h1، عنوان، عنوان، بزرگ" + }, + "h2": { + "label": "سرفصل 2", + "description": "عنوان بخش متوسط", + "keywords": "h2، عنوان، زیرنویس" + }, + "h3": { + "label": "سرفصل 3", + "description": "عنوان بخش کوچک", + "keywords": "h3، عنوان، عنوان فرعی" + }, + "h4": { + "label": "سرفصل 4", + "description": "عنوان بخش کوچک", + "keywords": "h4، عنوان" + }, + "h5": { + "label": "سرفصل 5", + "description": "عنوان کوچکتر", + "keywords": "h5، عنوان" + }, + "h6": { + "label": "سرفصل 6", + "description": "کوچکترین عنوان", + "keywords": "h6، عنوان" + }, + "p": { + "label": "پاراگراف", + "description": "پاراگراف متنی معمولی", + "keywords": "p، پاراگراف، متن، عادی" + }, + "code": { + "label": "بلوک کد", + "description": "قطعه کد", + "keywords": "کد، بلوک کد، قطعه، پیش" + }, + "blockquote": { + "label": "نقل قول", + "description": "بلوک نقل قول", + "keywords": "نقل قول، نقل قول، نقل قول" + }, + "li": { + "label": "لیست گلوله شده", + "description": "آیتم لیست ساده با گلوله", + "keywords": "لیست، گلوله، نامرتب، اول، لی" + }, + "ol": { + "label": "لیست شماره گذاری شده", + "description": "مورد فهرست شماره گذاری شده", + "keywords": "لیست، شماره گذاری شده، مرتب شده، ol،li" + }, + "img": { + "label": "تصویر", + "description": "یک تصویر را آپلود یا جاسازی کنید", + "keywords": "تصویر، ایمگ، تصویر، عکس، آپلود" + }, + "video": { + "label": "ویدئو", + "description": "یک ویدیو را آپلود یا جاسازی کنید", + "keywords": "ویدیو، ویدیو، فیلم، mp4، آپلود" + }, + "table": { + "label": "جدول", + "description": "یک جدول ایجاد کنید", + "keywords": "جدول، شبکه، ردیف، ستون، سلول" + } + }, + "toasts": { + "tableImported": "جدول وارد شده است", + "tableImportedDesc": "جدول Markdown با موفقیت وارد شد", + "mediaPasted": "رسانه چسبانده شد", + "pasteMixed": "{imageCount} تصویر(ها) و {videoCount} ویدئو(ها) با موفقیت جایگذاری شدند.", + "pasteImages": "{count} تصویر(های) با موفقیت جایگذاری شد.", + "pasteVideos": "{count} ویدیو(های) با موفقیت جایگذاری شد.", + "pasteFailed": "چسباندن ناموفق بود", + "unexpectedError": "یک خطای غیرمنتظره رخ داد", + "uploadFailed": "آپلود انجام نشد" + }, + "blockBackground": { + "menuItem": "تغییر رنگ پس زمینه", + "dialogTitle": "بلوک رنگ پس زمینه", + "dialogDescription": "یک رنگ پس زمینه برای این بلوک انتخاب کنید", + "tabPreset": "رنگ های از پیش تعیین شده", + "tabCustom": "رنگ سفارشی", + "sectionHeading": "رنگ های پس زمینه", + "apply": "درخواست کنید", + "colors": { + "none": "هیچ کدام", + "red": "قرمز", + "orange": "نارنجی", + "yellow": "زرد", + "green": "سبز", + "blue": "آبی", + "indigo": "نیل", + "purple": "بنفش", + "pink": "صورتی", + "teal": "آبی رنگ", + "cyan": "فیروزه ای", + "gray": "خاکستری" + } + }, + "tableDialog": { + "title": "جدول ایجاد کنید", + "description": "یک جدول جدید ایجاد کنید یا از علامت گذاری وارد کنید", + "markdownCheckbox": "من یک جدول علامت گذاری دارم", + "pasteMarkdownLabel": "جدول Markdown را بچسبانید", + "markdownPlaceholder": "| سربرگ 1 | سربرگ 2 | سربرگ 3 |\n|----------|----------|----------|\n| سلول 1 | سلول 2 | سلول 3 |\n| سلول 4 | سلول 5 | سلول 6 |", + "markdownHint": "جدول علامت گذاری خود را در بالا جایگذاری کنید. باید شامل ردیف‌های سرصفحه و جداکننده باشد.", + "rows": "ردیف ها", + "columns": "ستون ها", + "maxDimensions": "حداکثر: 20 سطر × 10 ستون", + "cancel": "لغو کنید", + "importTable": "جدول واردات", + "createTable": "جدول ایجاد کنید" + }, + "tableParse": { + "errors": { + "minRows": "جدول باید حداقل یک ردیف سرصفحه و ردیف جداکننده داشته باشد", + "pipeFormat": "سطرهای جدول باید با | شروع و ختم شوند", + "headerMinCol": "ردیف سرصفحه باید حداقل یک ستون داشته باشد", + "separatorRow": "ردیف دوم باید جداکننده باشد (به عنوان مثال، |---|---|)", + "rowColumnMismatch": "ردیف {row} دارای {got} ستون است، مورد انتظار {expected}", + "parseFailed": "تجزیه جدول نشانه گذاری انجام نشد" + } + }, + "link": { + "editLink": "ویرایش لینک", + "addLink": "پیوند اضافه کنید", + "selectedText": "متن انتخاب شده: \"{text}\"", + "linkUrl": "لینک URL", + "urlPlaceholder": "https://example.com", + "update": "به روز رسانی", + "addLinkButton": "پیوند اضافه کنید", + "toastApplied": "پیوند اعمال شد", + "toastLinkedTo": "پیوند به: {url}", + "toastRemoved": "پیوند حذف شد", + "toastRemovedDesc": "پیوند از قسمت انتخابی حذف شده است" + }, + "customClass": { + "devMode": "حالت برنامه نویس", + "toggleDevModeAria": "حالت برنامه نویس را تغییر دهید", + "searchPlaceholderDev": "جستجوی کلاس‌ها... (به عنوان مثال، \"text\"، \"bg\"، \"flex\")", + "searchPlaceholderUser": "سبک های جستجو... (به عنوان مثال، \"قرمز\"، \"پررنگ\"، \"سایه\")", + "appliesTitle": "اعمال می شود: {className}", + "noMatches": "هیچ کلاسی مطابق با \"{query}\" یافت نشد", + "toastClassReplaced": "کلاس تعویض شد", + "toastClassReplacedDesc": "جایگزین \"{replaced}\" با \"{className}\" شد", + "toastCustomClassApplied": "کلاس سفارشی اعمال شد", + "toastAppliedClass": "کلاس کاربردی: {className}" + }, + "insertComponents": { + "title": "درج کامپوننت", + "description": "یک جزء را برای درج در سند خود انتخاب کنید", + "noComponents": "هیچ مؤلفه ای در دسترس نیست", + "categories": { + "media": "رسانه ها", + "layout": "طرح بندی", + "interactive": "تعاملی", + "decoration": "دکوراسیون" + }, + "freeImage": { + "name": "تصویر حرکت آزاد", + "description": "تصویری را اضافه کنید که بتوان آن را در هر جایی قرار داد و آزادانه اندازه آن را تغییر داد" + } + } + }, + "JsonFormatter": { + "title": "قالب‌ده JSON", + "description": "JSON را در نمای متنی و درختی قالب‌بندی، اعتبارسنجی و ویرایش کنید.", + "documentNameText": "سند متنی {n}", + "documentNameTree": "سند درختی {n}", + "newDocument": "جدید", + "copy": "کپی", + "save": "ذخیره", + "load": "بارگذاری", + "saving": "در حال ذخیره...", + "leftDocNameLabel": "نام سند سمت چپ", + "rightDocNameLabel": "نام سند سمت راست", + "toastCopiedText": "پنل متن کپی شد", + "toastCopiedTree": "پنل درختی کپی شد", + "toastCopyFailed": "کپی JSON ناموفق بود", + "toastLoginRequired": "برای ذخیره اسناد وارد شوید", + "toastSaved": "سند ذخیره شد", + "toastSaveFailed": "ذخیره سند ناموفق بود", + "format": "قالب‌بندی", + "compact": "فشرده‌سازی", + "sortKeys": "مرتب‌سازی کلیدها", + "repair": "تعمیر", + "query": "پرس‌وجو", + "toastInvalidJson": "JSON نامعتبر", + "toastAlreadyValid": "JSON از قبل معتبر است", + "toastRepaired": "JSON تعمیر شد", + "toastRepairFailed": "JSON قابل تعمیر نیست" + }, + "JwtDecoder": { + "title": "رمزگشای JWT", + "subtitle": "سربرگ و بار داده را بررسی کنید. انقضا از ادعای exp استفاده می‌کند. چیزی به سرور ارسال نمی‌شود؛ امضا تأیید نمی‌شود.", + "tokenLabel": "توکن", + "clear": "پاک کردن", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "کپی", + "expiryLabel": "انقضا (exp)", + "signaturePresent": "امضا موجود است (تأیید نمی‌شود)", + "noExp": "ادعای exp در بار داده وجود ندارد.", + "status": { + "expired": "منقضی شده", + "valid": "معتبر" + }, + "fields": { + "unix": "یونیکس", + "local": "محلی", + "utc": "UTC" + }, + "claims": { + "issuedAt": "زمان صدور (iat)", + "notBefore": "نه قبل از (nbf)" + }, + "panels": { + "header": "سربرگ", + "payload": "بار داده" + }, + "errors": { + "empty": "یک JWT برای رمزگشایی جای‌گذاری کنید.", + "segments": "JWT باید حداقل سربرگ و بار داده داشته باشد (دو بخش).", + "base64": "رمزگشایی base64url سربرگ یا بار داده ممکن نبود." + }, + "relative": { + "future": "در {n} {unit}", + "past": "{n} {unit} پیش" + }, + "units": { + "second": "{count, plural, one {ثانیه} other {ثانیه}}", + "minute": "{count, plural, one {دقیقه} other {دقیقه}}", + "hour": "{count, plural, one {ساعت} other {ساعت}}", + "day": "{count, plural, one {روز} other {روز}}" + }, + "tabs": { + "decode": "رمزگشایی", + "sign": "سازنده / امضاکننده" + }, + "signer": { + "algLabel": "الگوریتم (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "راز (HS*)", + "secretPlaceholder": "راز HMAC خود را بچسبانید…", + "privateKeyLabel": "کلید خصوصی PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "الگوریتم \"none\" یک توکن بدون امضا ایجاد می‌کند. این ناامن است و فقط برای تست استفاده شود.", + "sign": "امضا", + "verify": "بررسی امضا", + "outputLabel": "توکن (JWS فشرده)", + "outputPlaceholder": "روی «امضا» کلیک کنید…", + "copyToken": "کپی توکن", + "copied": "کپی شد", + "verifyKeyLabel": "کلید تأیید (اختیاری)", + "verifyKeyPlaceholder": "برای RS*: کلید عمومی (SPKI) PEM را بچسبانید. برای HS*: خالی بگذارید تا از راز استفاده شود.", + "verifyKeyHint": "اگر خالی باشد، تأیید از کلید امضا استفاده می‌کند (مفید برای HS*).", + "verifyStatus": { + "valid": "امضا معتبر است", + "invalid": "امضا نامعتبر است" + }, + "securityNote": "همه چیز به‌صورت محلی در مرورگر اجرا می‌شود؛ کلیدها و توکن‌ها آپلود نمی‌شوند." + }, + "signErrors": { + "invalidJson": "Header و payload باید JSON معتبر باشند.", + "unsupportedAlg": "الگوریتم پشتیبانی نمی‌شود.", + "missingKey": "برای این الگوریتم کلید امضا لازم است.", + "cryptoFailed": "امضا ناموفق بود. فرمت کلید را بررسی کرده و دوباره تلاش کنید." + }, + "verifyErrors": { + "empty": "برای تأیید یک JWT وارد کنید.", + "segments": "JWT برای تأیید امضا باید دقیقاً ۳ بخش داشته باشد.", + "unsupportedAlg": "الگوریتم پشتیبانی نمی‌شود یا alg در header وجود ندارد.", + "missingKey": "برای این الگوریتم کلید تأیید لازم است.", + "base64": "امکان رمزگشایی header یا امضای JWT نبود.", + "cryptoFailed": "تأیید ناموفق بود. فرمت کلید را بررسی کرده و دوباره تلاش کنید." + } + }, + "CertificatePemDecoder": { + "title": "رمزگشای گواهی / PEM", + "subtitle": "یک گواهی X.509 (-----BEGIN CERTIFICATE-----) یا CSR (-----BEGIN CERTIFICATE REQUEST-----) بچسبانید. موضوع، صادرکننده، تاریخ‌ها و SAN به‌صورت محلی تجزیه می‌شود؛ چیزی آپلود نمی‌شود.", + "pemLabel": "ورودی PEM", + "clear": "پاک کردن", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "در حال تجزیه…", + "copyHex": "کپی اثرانگشت (هگز بدون دونقطه)", + "errors": { + "empty": "برای رمزگشایی یک گواهی PEM یا CSR بچسبانید.", + "noBlock": "بلوک PEM گواهی یا CSR یافت نشد.", + "parseFailed": "تجزیه این داده‌های PEM ممکن نشد." + }, + "block": { + "title": "بلوک PEM {index}", + "certificate": "گواهی X.509", + "csr": "درخواست امضای گواهی" + }, + "fields": { + "subject": "موضوع", + "issuer": "صادرکننده", + "notBefore": "نامعتبر قبل از", + "notAfter": "نامعتبر پس از", + "serial": "شماره سریال (هگز)", + "sha256": "اثر انگشت SHA-256", + "sans": "نام‌های جایگزین موضوع", + "sansNone": "افزونه SAN وجود ندارد.", + "signatureAlgorithm": "الگوریتم امضا", + "csrIssuerNote": "CSR صادرکننده ندارد (روی گواهی صادرشده می‌آید).", + "csrValidityNote": "CSR تاریخ اعتبار ندارد (روی گواهی صادرشده می‌آید)." + }, + "sanType": { + "dns": "DNS", + "dn": "نام دایرکتوری", + "email": "ایمیل", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "شناسه ثبت‌شده", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "فرمت‌کننده SQL", + "subtitle": "SQL را برای MySQL، PostgreSQL یا SQLite مرتب کنید. در مرورگر اجرا می‌شود؛ چیزی آپلود نمی‌شود.", + "dialectLabel": "گویش", + "keywordsLabel": "کلیدواژه‌ها", + "indentLabel": "تورفتگی", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "حروف بزرگ", + "lower": "حروف کوچک", + "preserve": "حفظ" + }, + "spaces": "{n} فاصله", + "format": "فرمت", + "clear": "پاک کردن", + "counter": "{current} / {max} نویسه", + "inputPanel": "ورودی", + "formattedPanel": "فرمت‌شده", + "copy": "کپی", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "روی \"فرمت\" کلیک کنید…", + "errors": { + "tooLong": "ورودی به {max} نویسه محدود است.", + "couldNotFormat": "فرمت‌کردن این SQL ممکن نبود." + } + }, + "YamlFormatter": { + "title": "قالب‌بند YAML", + "subtitle": "YAML را در مرورگر خود قالب‌بندی، اعتبارسنجی و تبدیل کنید. هیچ چیز آپلود نمی‌شود.", + "outputLabel": "خروجی", + "outputModes": { + "yaml": "YAML (قالب‌بندی‌شده)", + "json": "JSON" + }, + "indentLabel": "تورفتگی", + "spaces": "{n} فاصله", + "format": "قالب‌بندی", + "clear": "پاک کردن", + "copy": "کپی", + "counter": "{current} / {max} کاراکتر", + "inputPanel": "ورودی", + "outputPanel": "خروجی", + "errors": { + "tooLong": "ورودی محدود به {max} کاراکتر است.", + "couldNotFormat": "این YAML قابل تجزیه نیست." + } + }, + "GraphqlFormatter": { + "title": "قالب‌بند GraphQL", + "subtitle": "پرس‌وجوها، جهش‌ها و اشتراک‌های GraphQL را در مرورگر قالب‌بندی کنید. چیزی آپلود نمی‌شود.", + "tabs": { + "format": "قالب‌بندی", + "build": "سازندهٔ پرس‌وجو" + }, + "outputModeLabel": "خروجی", + "outputModes": { + "pretty": "چاپ خوانا", + "minify": "فشرده‌سازی" + }, + "format": "قالب‌بندی", + "clear": "پاک‌سازی", + "counter": "{current} / {max} نویسه", + "inputPanel": "ورودی", + "outputPanel": "خروجی", + "copy": "رونوشت", + "builderHint": "اسکلت سند را بسازید و روی ویراستار اعمال کنید. تعریف متغیرها با نحو GraphQL (مثلاً $id: ID!) — پرانتز اختیاری است.", + "builder": { + "operation": "عملیات", + "operationName": "نام عملیات", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "تعریف متغیرها", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "مجموعهٔ گزینش", + "apply": "اعمال روی ورودی", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "ورودی حداکثر {max} نویسه است.", + "couldNotFormat": "تجزیه یا چاپ این GraphQL ممکن نشد.", + "emptySelection": "قبل از اعمال، مجموعهٔ گزینش را اضافه کنید." + } + }, + "RegexTester": { + "title": "آزمایشگر Regex", + "subtitle": "Regex به سبک JavaScript به‌صورت زنده روی متن شما. تطبیق‌ها هایلایت می‌شوند و چیزی از مرورگر خارج نمی‌شود.", + "patternLabel": "الگو", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g سراسری", + "i": "i بدون حساسیت به حروف", + "m": "m چندخطی", + "s": "s dotAll", + "u": "u یونیکد", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(خالی)", + "noFlags": "—", + "matches": "{count, plural, one {تطبیق} other {تطبیق‌ها}}", + "testTextLabel": "متن آزمایش", + "counter": "{current} / {max}", + "clear": "پاک کردن", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "متن به {max} نویسه محدود است.", + "invalid": "عبارت باقاعده نامعتبر است.", + "runaway": "پس از {max} تطبیق متوقف شد (احتمال الگوی runaway)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "مبدل تایم‌استمپ", + "subtitle": "ثانیهٔ یونیکس (≤۱۰ رقم) یا میلی‌ثانیه (اعداد بلندتر)، ISO-8601 یا رشته‌هایی که مرورگر می‌تواند تحلیل کند. همه چیز محلی اجرا می‌شود.", + "inputLabel": "ورودی", + "placeholder": "1744200000 یا 2025-04-09T12:00:00Z", + "now": "اکنون", + "copy": "کپی", + "hint": "یک مقدار بچسبانید یا روی اکنون کلیک کنید.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "نسبی", + "human": "خوانا" + }, + "fields": { + "seconds": "ثانیه", + "milliseconds": "میلی‌ثانیه", + "utc": "UTC", + "local": "محلی", + "distance": "فاصله" + }, + "errors": { + "empty": "یک تایم‌استمپ یونیکس، رشتهٔ ISO یا متن تاریخ وارد کنید.", + "invalidNumber": "تایم‌استمپ عددی نامعتبر است.", + "outOfRange": "خارج از محدودهٔ JavaScript Date.", + "couldNotParse": "به‌عنوان Unix، ISO-8601 یا تاریخ قابل‌تحلیل مرورگر قابل پردازش نیست." + } + }, + "HttpStatusCodes": { + "title": "کدهای وضعیت HTTP", + "subtitle": "کدهای وضعیت پاسخ‌های HTTP را جستجو و مرور کنید (1xx5xx) همراه با توضیحات کوتاه و ارجاع‌های RFC. به‌صورت محلی اجرا می‌شود.", + "header": { + "title": "کدهای وضعیت HTTP", + "description": "فهرست قابل جستجو از کدهای پاسخ HTTP همراه با توضیحات و ارجاع‌های RFC." + }, + "searchPlaceholder": "جستجو: 404، not found، rate limit، gateway…", + "classFilterPlaceholder": "فیلتر کلاس…", + "classFilterAll": "همهٔ کلاس‌ها", + "resultsCount": "{count} از {total}", + "table": { + "code": "Code", + "phrase": "عبارت", + "description": "توضیحات", + "copy": "کپی" + }, + "noResults": "نتیجه‌ای برای «{query}» پیدا نشد.", + "specPrefix": "مشخصات:", + "copied": "کد کپی شد" + }, + "CronBuilder": { + "title": "سازندهٔ عبارت Cron", + "subtitle": "کرون استاندارد ۵ فیلدی: minute hour day month weekday. مفسر به‌صورت داخلی seconds=0 را استفاده می‌کند. فرمت اختیاری ۶ فیلدی (sec min hour dom mon dow) در ویرایشگر عبارت کار می‌کند.", + "tabs": { + "builder": "سازندهٔ بصری", + "expression": "عبارت" + }, + "presetsLabel": "پیش‌فرض‌ها", + "fieldsLabel": "فیلدها", + "rawExpressionLabel": "عبارت خام", + "expressionHint": "برای تجزیه و همگام‌سازی، از فیلد خارج شوید. {count} {count, plural, one {توکن} other {توکن}}.", + "currentExpressionLabel": "عبارت فعلی", + "copy": "کپی", + "quickPick": "انتخاب سریع", + "emptyExpression": "—", + "plainLanguageLabel": "توضیح متنی", + "nextRunsLabel": "اجراهای بعدی (زمان محلی)", + "fields": { + "minute": "دقیقه", + "hour": "ساعت", + "dayOfMonth": "روز ماه", + "month": "ماه", + "dayOfWeek": "روز هفته" + }, + "presets": { + "everyMinute": "هر دقیقه", + "every5Minutes": "هر ۵ دقیقه", + "every15Minutes": "هر ۱۵ دقیقه", + "everyHour": "هر ساعت (در :00)", + "every6Hours": "هر ۶ ساعت", + "dailyMidnight": "روزانه در نیمه‌شب", + "daily0900": "روزانه در 09:00", + "weekdays0900": "روزهای کاری در 09:00", + "weeklySunday": "هفتگی (یکشنبه 00:00)", + "monthlyFirst": "ماهانه (روز 1، 00:00)", + "yearlyJan1": "سالانه (1 ژانویه 00:00)" + }, + "picks": { + "minute": { + "every": "هر دقیقه" + }, + "hour": { + "every": "هر ساعت" + }, + "dom": { + "any": "هر روز", + "last": "آخرین روز" + }, + "month": { + "every": "هر ماه" + }, + "dow": { + "any": "هر روز", + "weekdays": "روزهای کاری", + "weekend": "آخر هفته" + } + }, + "months": { + "jan": "ژان", + "feb": "فور", + "mar": "مار", + "apr": "آپر", + "may": "مه", + "jun": "ژوئن", + "jul": "ژوئیه", + "aug": "اوت", + "sep": "سپت", + "oct": "اکت", + "nov": "نوا", + "dec": "دسا" + }, + "weekdays": { + "sun": "یک", + "mon": "دو", + "tue": "سه", + "wed": "چه", + "thu": "پنج", + "fri": "جمع", + "sat": "شنبه" + }, + "errors": { + "fieldCount": "انتظار می‌رود ۵ فیلد (minute hour day month weekday) یا ۶ فیلد با ثانیه باشد.", + "invalid": "عبارت cron نامعتبر است." + } + }, + "Base64": { + "title": "رمزگذار / رمزگشای Base64", + "subtitle": "متن را به Base64 رمزگذاری کنید یا رشته‌های Base64 را رمزگشایی کنید", + "upload": "آپلود", + "clear": "پاک کردن", + "modes": { + "encode": "رمزگذاری", + "decode": "رمزگشایی" + }, + "swapTitle": "جابه‌جایی ورودی و خروجی", + "panels": { + "plainText": "متن ساده", + "base64Input": "ورودی Base64", + "base64Output": "خروجی Base64", + "decodedText": "متن رمزگشایی‌شده" + }, + "placeholders": { + "encode": "متن را برای رمزگذاری Base64 وارد کنید…", + "decode": "رشتهٔ Base64 را برای رمزگشایی بچسبانید…" + }, + "dropHere": "فایل را اینجا رها کنید", + "charCount": "{count} نویسه", + "download": { + "title": "دانلود خروجی", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "کپی در کلیپ‌بورد", + "outputPlaceholder": "خروجی اینجا نمایش داده می‌شود…", + "errors": { + "invalidBase64": "رشتهٔ Base64 نامعتبر است. ورودی را بررسی کنید.", + "encodeFailed": "رمزگذاری ورودی ناموفق بود." + } + }, + "UrlEncode": { + "title": "رمزگذار / رمزگشای URL", + "subtitle": "رمزگذاری/رمزگشایی درصدی با encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "آپلود", + "clear": "پاک کردن", + "modes": { + "encode": "رمزگذاری", + "decode": "رمزگشایی" + }, + "swapTitle": "جابه‌جایی ورودی و خروجی", + "panels": { + "plainText": "متن ساده", + "encodedInput": "ورودی کدشده", + "encodedOutput": "خروجی کدشده", + "decodedText": "متن رمزگشایی‌شده" + }, + "placeholders": { + "encode": "متن را برای درصد-کدگذاری وارد کنید…", + "decode": "متن percent-encoded را برای رمزگشایی بچسبانید…" + }, + "dropHere": "فایل را اینجا رها کنید", + "charCount": "{count} نویسه", + "download": { + "title": "دانلود خروجی", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "کپی در کلیپ‌بورد", + "outputPlaceholder": "خروجی اینجا نمایش داده می‌شود…", + "errors": { + "invalidEncoding": "کدگذاری درصدی نامعتبر است. جفت‌های % ناقص یا escapeهای نامعتبر را بررسی کنید.", + "encodeFailed": "رمزگذاری ممکن نیست (Unicode نامعتبر در ورودی)." + } + }, + "UrlParser": { + "title": "تجزیه‌گر URL", + "subtitle": "یک URL را به پروتکل، میزبان، مسیر، پارامترهای پرس‌وجو و هش (بخش) تقسیم کنید.", + "upload": "بارگذاری", + "clear": "پاک کردن", + "panels": { + "input": "ورودی URL", + "output": "نتیجه" + }, + "placeholders": { + "input": "یک URL بچسبانید (مثال https://example.com/path?foo=bar#section) یا میزبان/مسیر (مثال example.com/path)…" + }, + "dropHere": "فایل را اینجا رها کنید", + "charCount": "{count} نویسه", + "outputPlaceholder": "اجزای تجزیه‌شده اینجا نمایش داده می‌شوند…", + "fields": { + "protocol": "پروتکل", + "host": "میزبان", + "path": "مسیر", + "hash": "هش", + "queryParams": "پارامترهای پرس‌وجو" + }, + "paramCount": "{count} پارامتر", + "noParams": "پارامتر پرس‌وجویی وجود ندارد.", + "paramKey": "کلید", + "paramValue": "مقدار", + "copyTitle": "کپی در کلیپ‌بورد", + "copyParamTitle": "کپی به‌صورت key=value", + "download": { + "title": "دانلود JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL نامعتبر است. نام میزبان (example.com) را بگنجانید و نویسه‌ها را بررسی کنید." + } + }, + "UuidGenerator": { + "title": "مولد UUID / ULID", + "subtitle": "UUIDهای RFC 9562 یا ULID را در مرورگر تولید کنید. خروجی گروهی تا {max} در هر اجرا.", + "format": "فرمت", + "kindHints": { + "ulid": "شناسهٔ ۲۶ کاراکتری قابل مرتب‌سازی و امن برای URL", + "uuid4": "تصادفی (RFC 9562)", + "uuid7": "مرتب‌شده بر اساس زمان، میلی‌ثانیهٔ یونیکس + تصادفی", + "uuid1": "مبتنی بر MAC/زمان (RFC 9562)", + "uuid6": "زمان گریگوری بازآرایی‌شده", + "uuid3": "هش MD5 از نام + فضای نام", + "uuid5": "هش SHA‑1 از نام + فضای نام", + "nil": "UUID صفر (همه صفر، RFC 9562)" + }, + "countLabel": "تعداد (۱–{max})", + "nameLabel": "نام (رشتهٔ ورودی)", + "namePlaceholder": "مثلاً my.app/resource", + "bulkHint": "در حالت گروهی #0، #1، … اضافه می‌شود تا هر ID یکتا باشد (v3/v5 قطعی هستند).", + "namespace": "فضای نام", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID سفارشی" + }, + "customNamespaceLabel": "UUID فضای نام", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "تولید", + "output": "خروجی", + "lines": "{count} خط", + "download": { + "title": "دانلود .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "کپی همه", + "uppercaseToggle": "خروجی با حروف بزرگ", + "hyphensToggle": "نمایش خط‌تیره‌ها", + "copyJsonTitle": "کپی به‌صورت آرایه JSON", + "outputPlaceholder": "روی «تولید» کلیک کنید…", + "errors": { + "invalidCustomNamespace": "فضای نام سفارشی باید یک UUID معتبر باشد.", + "missingName": "برای UUID v3 و v5 وارد کردن نام لازم است.", + "unknown": "امکان تولید شناسه‌ها وجود ندارد." + } + }, + "SecretApiKeyGenerator": { + "title": "مولد رمز / کلید API", + "subtitle": "رشته‌های تصادفی رمزنگاری‌شده با طول و الفبای قابل تنظیم بسازید. خروجی انبوه تا {max} در هر اجرا. مکمل مولد UUID / ULID.", + "alphabetLabel": "الفبا", + "alphabetHint": "نویسه‌های تکراری حذف می‌شوند. هر نماد به‌صورت یکنواخت با crypto.getRandomValues و نمونه‌گیری ردّی انتخاب می‌شود (بدون سوگیری پیمانه).", + "alphabetCount": "{count} نماد یکتا", + "presetsLabel": "پیش‌تنظیم‌ها", + "preset": { + "base64url": "Base64url", + "alphanumeric": "الفبایی-عددی", + "hex": "شانزده‌شانزدهی (حروف کوچک)", + "asciiPrintable": "ASCII قابل چاپ" + }, + "lengthLabel": "طول هر رمز (۱–{maxLen})", + "countLabel": "تعداد (۱–{maxBulk})", + "generate": "تولید", + "output": "خروجی", + "lines": "{count} خط", + "download": { + "title": "بارگیری .txt", + "filename": "اسرار.txt" + }, + "copyTitle": "کپی همه", + "outputPlaceholder": "روی «تولید» کلیک کنید…", + "entropy": "{bits} بیت آنتروپی", + "strength": { + "weak": "ضعیف", + "fair": "متوسط", + "strong": "قوی" + }, + "excludeAmbiguous": "حذف نویسه‌های مبهم", + "excludeAmbiguousHint": "نویسه‌های {chars} را حذف می‌کند — نویسه‌هایی که به‌راحتی اشتباه خوانده می‌شوند یا برای شل مخرب هستند.", + "prefixLabel": "پیشوند کلید (اختیاری)", + "errors": { + "emptyAlphabet": "حداقل یک نویسه به الفبا اضافه کنید.", + "alphabetTooLong": "پس از حذف تکراری‌ها، الفبا حداکثر {maxAlpha} نویسه می‌تواند داشته باشد.", + "lengthOutOfRange": "طول باید بین ۱ و {maxLen} باشد.", + "countOutOfRange": "تعداد باید بین ۱ و {maxBulk} باشد.", + "unknown": "تولید اسرار ممکن نشد." + } + }, + "LoremIpsum": { + "title": "مولد متن لورم ایپسوم", + "subtitle": "متن لاتین موقت برای طرح‌بندی‌ها و ماکت‌های خود تولید کنید.", + "paragraphs": "پاراگراف‌ها", + "sentences": "جملات", + "words": "کلمات", + "count": "تعداد", + "generate": "تولید", + "copy": "کپی", + "startWith": "شروع با 'Lorem ipsum...'", + "outputPlaceholder": "خروجی اینجا نمایش داده می‌شود...", + "outputType": "نوع خروجی", + "bulletList": "لیست گلوله‌ای", + "htmlMarkup": "نشانه‌گذاری HTML", + "htmlMarkupHint": "خروجی را در پاراگراف‌ها قرار دهید یا از لیست برای حالت گلوله استفاده کنید", + "output": "خروجی", + "download": "دانلود" + }, + "ColorPicker": { + "title": "انتخابگر و مبدل رنگ", + "subtitle": "HEX، RGB و HSL را تبدیل کنید؛ پالت‌های هارمونیک بسازید. تمام پردازش در مرورگر شما انجام می‌شود.", + "nativePicker": "انتخابگر بومی", + "chooseColor": "انتخاب رنگ", + "eyeDropper": "قطره‌چکان", + "invalidHex": "رنگ HEX نامعتبر است.", + "cssValues": "مقادیر CSS", + "palettes": "پالت‌ها", + "copied": "کپی شد", + "clickToCopy": "برای کپی کلیک کنید", + "copyAllHex": "کپی تمام HEX", + "copy": "کپی", + "copyColor": "کپی {color}", + "paletteShades": "سایه‌ها", + "paletteComplementary": "مکمل", + "paletteTriadic": "سه‌گانه", + "paletteAnalogous": "مشابه", + "paletteSplit": "مکمل منقسم", + "paletteSquare": "مربع" + }, + "ContrastChecker": { + "title": "بررسی کنتراست", + "subtitle": "نسبت کنتراست WCAG 2.1 بین پیش‌زمینه و پس‌زمینه را محاسبه می‌کند. انطباق AA و AAA را برای متن معمولی و بزرگ نشان می‌دهد. همه در مرورگر شما.", + "foreground": "متن / پیش‌زمینه", + "background": "پس‌زمینه", + "hexPlaceholder": "#RRGGBB", + "swap": "جابه‌جایی رنگ‌ها", + "chooseForeground": "انتخاب رنگ پیش‌زمینه", + "chooseBackground": "انتخاب رنگ پس‌زمینه", + "eyeDropper": "قطره‌چکان", + "ratio": "نسبت کنتراست", + "copyRatio": "کپی نسبت", + "copied": "کپی شد", + "previewHeading": "پیش‌نمایش", + "previewBody": "می‌خواهیم زندگی بهتر و آزادی بیشتری داشته باشیم. پَرَستُشِ گَردِشِ چُرخِ فَلَک، بَر کَسَم نَرَسَد اَز دَهر.", + "previewCaption": "نمونه متن در اندازه پیش‌فرض.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — متن معمولی (۴٫۵:۱)", + "levelAALarge": "AA — متن بزرگ (۳:۱)", + "levelAAANormal": "AAA — متن معمولی (۷:۱)", + "levelAAALarge": "AAA — متن بزرگ (۴٫۵:۱)", + "pass": "مطابق", + "fail": "نامطابق", + "invalidHex": "رنگ HEX نامعتبر است.", + "largeTextHint": "متن بزرگ: ۱۸ پوینت به بالا معمولی یا ۱۴ پوینت به بالا پررنگ (تعاریف WCAG)." + }, + "DiffChecker": { + "title": "مقایسه تفاوت‌ها", + "subtitle": "مقایسه خط‌به‌خط دو متن در کنار هم. همه چیز در مرورگر شما می‌ماند.", + "rowsCount": "{count} ردیف", + "truncatedWarning": "نمایش {max} ردیف اول.", + "limitExceeded": "هر طرف محدود به {max} کاراکتر است.", + "clearBoth": "پاک کردن هر دو", + "original": "اصلی", + "modified": "تغییریافته", + "originalPlaceholder": "متن اصلی را جایگذاری کنید…", + "modifiedPlaceholder": "متن تغییریافته را جایگذاری کنید…", + "comparisonLabel": "مقایسه جنب‌به‌جنب", + "emptyState": "متن را در هر دو پنل وارد کنید تا مقایسه شود." + }, + "JsonSchemaGenerator": { + "title": "تولیدکنندهٔ طرح JSON", + "subtitle": "JSON معتبر بچسبانید تا طرح JSON یا مدل‌های نوع‌دار بسازید. همه‌چیز در مرورگر شما اجرا می‌شود.", + "languageLabel": "زبان خروجی", + "inputLabel": "ورودی JSON", + "inputPlaceholder": "JSON را اینجا بچسبانید…", + "outputLabel": "خروجی تولیدشده", + "outputPlaceholder": "طرح یا انواع تولیدشده اینجا نمایش داده می‌شود…", + "parseError": "JSON نامعتبر", + "clear": "پاک کردن", + "copy": "رونوشت", + "copied": "در بریده‌دان رونوشت شد", + "copyFailed": "رونوشت ممکن نشد", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "طرح JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "در حال بارگذاری ویرایشگر…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "تصویر به Base64", + "subtitle": "یک تصویر رها کنید، یک URI داده یا رشته خام Base64 دریافت کنید. همه چیز در مرورگر شما باقی می ماند.", + "upload": "آپلود تصویر", + "clear": "پاک کردن", + "modes": { + "dataUri": "URI داده", + "rawString": "پایه خام 64" + }, + "panels": { + "imageInput": "ورودی تصویر", + "output": "خروجی Base64" + }, + "dropHere": "تصویر را اینجا رها کنید", + "charCount": "{count} کاراکتر", + "placeholders": { + "uploadImage": "یک تصویر را بکشید و رها کنید یا برای شروع روی آپلود کلیک کنید." + }, + "fileTooLarge": "فایل خیلی بزرگ است پردازش ممکن است مرورگر شما را مسدود کند.", + "copyTitle": "در کلیپ بورد کپی کنید", + "download": { + "title": "دانلود متن", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "فشرده‌ساز تصویر", + "subtitle": "خروجی JPEG یا PNG یا WebP را محلی با کنترل کیفیت کوچک کنید — چیزی آپلود نمی‌شود.", + "upload": "بارگذاری تصویر", + "clear": "پاک کردن", + "dropHere": "تصویر را اینجا رها کنید", + "placeholders": { + "uploadImage": "تصویر را بکشید و رها کنید، یا برای انتخاب فایل کلیک کنید." + }, + "fileTooLarge": "فایل خیلی بزرگ است (حداکثر {maxMb} مگابایت).", + "unsupportedFormat": "این نوع فایل پشتیبانی نمی‌شود. از JPEG، PNG، WebP، GIF، AVIF یا BMP استفاده کنید.", + "compressFailed": "فشرده‌سازی ممکن نشد. فایل یا قالب دیگری امتحان کنید.", + "panels": { + "original": "اصلی", + "compressed": "پیش‌نمایش فشرده" + }, + "outputFormat": "قالب خروجی", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "کیفیت", + "qualityPercent": "{value}٪", + "qualityPngHint": "PNG در مرورگر بدون اتلاف است؛ لغزنده کیفیت فقط برای JPEG و WebP است.", + "sizes": { + "original": "اصلی: {size}", + "compressed": "فشرده: {size}", + "saved": "{percent}٪ کوچک‌تر" + }, + "largerWarning": "خروجی از ورودی بزرگ‌تر است — قالب یا کیفیت دیگری امتحان کنید.", + "download": "دانلود", + "downloadFilename": "تصویر-فشرده", + "processing": "در حال فشرده‌سازی…", + "emptyCompressed": "پیش‌نمایش فشرده اینجا نمایش داده می‌شود." + }, + "CssGradientBuilder": { + "title": "CSS Gradient Builder", + "subtitle": "سازنده گرادیان CSS ویژوال با کنترل زاویه، توقف رنگ و کپی CSS با یک کلیک.", + "preview": "پیش نمایش گرادیان", + "settings": "تنظیمات", + "type": "تایپ کنید", + "linear": "خطی", + "radial": "شعاعی", + "angle": "زاویه", + "stops": "رنگ متوقف می شود", + "color": "رنگ", + "position": "موقعیت (%)", + "addStop": "Stop را اضافه کنید", + "deleteStop": "Stop را حذف کنید", + "output": "خروجی CSS", + "copy": "CSS را کپی کنید", + "copied": "کپی شده!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Generator", + "subtitle": "فایل‌های .gitignore ادغام شده را فوراً برای یک پشته یا ترکیب فنی خاص ایجاد کنید.", + "searchPlaceholder": "پشته های جستجو (به عنوان مثال Node، Python، macOS)...", + "noResults": "پشته ای پیدا نشد.", + "selectedStacks": "پشته های انتخاب شده", + "output": "پیش نمایش خروجی", + "copy": "در کلیپ بورد کپی کنید", + "copied": "کپی شده!", + "download": "gitignore. را دانلود کنید", + "downloading": "در حال دانلود...", + "errorFetching": "واکشی فهرست برتر ناموفق بود.", + "errorGenerating": "gitignore. از پشته های انتخابی ایجاد نشد.", + "generating": "در حال تولید...", + "emptyState": "برای ایجاد .gitignore خود یک یا چند پشته فناوری انتخاب کنید." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV یا Excel را بارگذاری کنید، JSON را ویرایش کنید، سپس CSV یا XLSX را دانلود کنید. فقط داده جدولی؛ سلول‌های تاریخ Excel در JSON به برچسب زمانی ISO تبدیل می‌شوند.", + "dropTitle": "فایل CSV یا Excel را رها کنید", + "dropHint": "برای .xlsx / .xls از اولین برگه استفاده می‌شود. CSV باید سطر سرستون داشته باشد.", + "chooseFile": "انتخاب فایل", + "jsonLabel": "JSON (آرایه‌ای از شیءها)", + "jsonPlaceholder": "آرایه JSON از شیءها را بچسبانید یا CSV / Excel وارد کنید…", + "loadSample": "بارگذاری نمونه", + "actions": { + "format": "قالب‌بندی JSON", + "clear": "پاک کردن", + "copy": "کپی", + "copied": "کپی شد", + "downloadCsv": "دانلود CSV", + "downloadXlsx": "دانلود Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "داده", + "toastLoadedCsv": "CSV در ویرایشگر بارگذاری شد", + "toastLoadedExcel": "Excel در ویرایشگر بارگذاری شد", + "toastFormatted": "JSON قالب‌بندی شد", + "toastExportedCsv": "CSV دانلود شد", + "toastExportedXlsx": "Excel دانلود شد", + "toastCopied": "در کلیپ‌بورد کپی شد", + "footnote": "همه‌چیز در مرورگر اجرا می‌شود. JSON باید آرایه‌ای غیرخالی از شیءها باشد. مقادیر تودرتو در سلول‌های CSV به صورت JSON سریال می‌شوند.", + "errors": { + "unsupportedFile": "از فایل .csv، .xlsx یا .xls استفاده کنید.", + "parseFailed": "این فایل قابل تجزیه نیست.", + "invalidJson": "JSON نامعتبر", + "exportFailed": "صادرات ناموفق", + "copyFailed": "کپی نشد" + } + }, + "SnippetManager": { + "title": "مدیریت قطعه‌کد", + "subtitle": "قطعه‌کدها را در مرورگر نگه دارید یا برای همگام‌سازی با حساب وارد شوید. بین مشاهده و ویرایش جابه‌جا شوید، با یک کلیک کپی کنید، زبان را انتخاب یا خودکار تشخیص دهید و JSON، SQL یا کد پشتیبانی‌شده توسط Monaco را قالب‌بندی کنید.", + "snippets": "قطعه‌کدها", + "newSnippet": "قطعه‌کد جدید", + "newSnippetTitle": "قطعه‌کد جدید", + "defaultSnippetTitle": "خوش آمدید", + "defaultSnippetCode": "// به قطعه‌کدها خوش آمدید\n// حالت مشاهده در برابر ویرایش، رونوشت، قالب‌بندی و زبان خودکار را امتحان کنید\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "جستجوی قطعه‌کدها…", + "searchAria": "جستجوی قطعه‌کدها", + "emptySearch": "هیچ قطعه‌ای با جستجو هم‌خوان نیست.", + "snippetTitle": "عنوان", + "snippetTitlePlaceholder": "نام قطعه", + "language": "زبان", + "detectedHint": "برجسته‌سازی به‌صورت {lang}", + "badgeAuto": "خودکار · {lang}", + "modeView": "مشاهده", + "modeEdit": "ویرایش", + "copy": "رونوشت", + "copied": "رونوشت شد", + "format": "قالب‌بندی", + "cancel": "لغو", + "deleteTitle": "قطعه‌کد حذف شود؟", + "deleteDescription": "«{title}» از این مرورگر حذف می‌شود.", + "deleteConfirm": "حذف", + "editorAria": "ویرایشگر کد قطعه", + "editorLoading": "در حال بارگذاری ویرایشگر…", + "toastCopied": "در بریده‌دان رونوشت شد", + "toastCopyFailed": "رونوشت ممکن نشد", + "toastNothingToCopy": "چیزی برای رونوشت نیست", + "toastFormatted": "قالب‌بندی شد", + "toastFormatFailed": "قالب‌بندی ممکن نشد", + "toastFormatJsonInvalid": "JSON نامعتبر — نخست نحو را درست کنید", + "toastFormatUnsupported": "قالب‌بند برای این زبان نیست — JSON یا SQL یا حالت ویرایش را امتحان کنید", + "toastDeleted": "قطعه حذف شد", + "toastLoadFailed": "بارگیری قطعه‌کدها از سرور انجام نشد.", + "toastSyncFailed": "همگام‌سازی با سرور انجام نشد.", + "languages": { + "auto": "تشخیص خودکار", + "plaintext": "متن ساده", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "کلاینت SQL", + "dialogTitle": "اتصالات SQL", + "mobileSheetTitle": "مرورگر SQL", + "newTabLabel": "تب پرس‌وجوی جدید", + "vaultLockedTitle": "خزانه قفل است", + "vaultLockedDesc": "خزانه را برای دسترسی به اتصالات ذخیره‌شده باز کنید.", + "heroTitle": "کلاینت SQL", + "heroDesc": "به PostgreSQL، MySQL یا MariaDB متصل شوید. اعتبارنامه‌ها در مرورگر رمزگذاری می‌شوند.", + "heroAddConnection": "افزودن اتصال", + "selectTableTitle": "یک جدول انتخاب کنید یا تب پرس‌وجو باز کنید", + "selectTableDesc": "روی جدولی در نوار کناری کلیک کنید یا از ⌘↩ استفاده کنید.", + "toastLoadFail": "بارگذاری اتصالات ناموفق بود." + }, + "connection": { + "titleNew": "اتصال SQL جدید", + "titleEdit": "ویرایش اتصال", + "labelDbType": "نوع پایگاه داده", + "labelName": "نام اتصال", + "placeholderName": "پایگاه داده تولید من", + "labelHostPort": "هاست و پورت", + "labelDatabase": "پایگاه داده", + "placeholderDatabase": "mydb", + "labelUsername": "نام کاربری", + "labelPassword": "رمز عبور", + "labelSsl": "فعال‌سازی SSL / TLS", + "sslDesc": "رمزگذاری اتصال به سرور", + "securityNote": "اعتبارنامه‌ها قبل از ذخیره‌سازی در مرورگر با AES-256 رمزگذاری می‌شوند", + "btnTest": "آزمایش اتصال", + "btnSave": "ذخیره و اتصال", + "btnUpdate": "به‌روزرسانی", + "btnCancelEdit": "لغو ویرایش", + "savedConnections": "اتصالات ذخیره‌شده", + "emptySaved": "اتصالات ذخیره‌شده اینجا نمایش داده می‌شوند", + "confirmDelete": "این اتصال حذف شود؟", + "toastFillFields": "قبل از آزمایش هاست و پایگاه داده را پر کنید.", + "toastConnectOk": "متصل شد! {version}", + "toastConnectFail": "اتصال ناموفق: {message}", + "toastUnlockVault": "ابتدا خزانه را باز کنید.", + "toastFieldsRequired": "هاست و پایگاه داده اجباری هستند.", + "toastUpdated": "اتصال به‌روز شد.", + "toastSaved": "اتصال ذخیره شد.", + "toastSaveFail": "ذخیره‌سازی ناموفق: {message}", + "toastDeleted": "اتصال حذف شد.", + "toastDeleteFail": "حذف اتصال ناموفق بود." + }, + "sidebar": { + "title": "مرورگر SQL", + "tooltipAddConnection": "افزودن اتصال", + "tooltipRefreshSchema": "به‌روزرسانی طرح‌واره", + "noConnections": "هنوز اتصالی ذخیره نشده", + "toastSchemaFail": "بارگذاری طرح‌واره ناموفق: {message}" + }, + "editor": { + "btnRun": "اجرا", + "executing": "در حال اجرای پرس‌وجو…", + "emptyTitle": "یک پرس‌وجو اجرا کنید تا نتایج را ببینید", + "emptyHint": "⌘↩ را فشار دهید یا روی اجرا کلیک کنید", + "toastNoConnection": "هیچ اتصال فعالی وجود ندارد." + }, + "results": { + "filterPlaceholder": "فیلتر نتایج…", + "rowCount": "{count} ردیف", + "rowCountFiltered": "{filtered} / {total} ردیف", + "exportCsv": "CSV", + "noRows": "هیچ ردیفی بازگردانده نشد" + } + }, + "QrCodeGenerator": { + "title": "تولیدکنندهٔ QR", + "subtitle": "هر متن یا URL را به PNG QR تبدیل کنید. همه‌چیز به‌صورت محلی در مرورگر رندر می‌شود.", + "contentLabel": "محتوا", + "contentPlaceholder": "https://example.com، متن ساده، رشتهٔ Wi‑Fi، vCard…", + "styleLabel": "سبک", + "dotTypeLabel": "شکل نقطه", + "dotTypeSquare": "مربع", + "dotTypeDots": "نقطه‌ها", + "dotTypeRounded": "گرد", + "dotTypeExtraRounded": "بسیار گرد", + "dotTypeClassy": "شیک", + "dotTypeClassyRounded": "شیک گرد", + "cornerSquareLabel": "مربع‌های گوشه", + "cornerSquareDot": "نقطه", + "cornerSquareSquare": "مربع", + "cornerSquareExtraRounded": "بسیار گرد", + "cornerDotLabel": "نقطه‌های گوشه", + "cornerDotDot": "نقطه", + "cornerDotSquare": "مربع", + "eccLabel": "تصحیح خطا", + "eccL": "پایین (~۷٪)", + "eccM": "متوسط (~۱۵٪)", + "eccQ": "چارک (~۲۵٪)", + "eccH": "بالا (~۳۰٪)", + "marginLabel": "حاشیه (ماژول)", + "widthLabel": "عرض تصویر", + "widthEffective": "با {px} پیکسل رندر می‌شود", + "darkColor": "ماژول‌های تیره", + "lightColor": "ماژول‌های روشن", + "resetColors": "بازنشانی رنگ‌ها", + "logoLabel": "لوگوی مرکزی", + "logoMarginLabel": "حاشیه", + "uploadLogo": "بارگذاری تصویر", + "removeLogo": "حذف لوگو", + "downloadPng": "دانلود PNG", + "downloadSvg": "دانلود SVG", + "copyImage": "کپی تصویر", + "copied": "کپی شد", + "clear": "پاک کردن", + "previewAlt": "پیش‌نمایش QR", + "emptyHint": "متن را وارد یا بچسبانید تا پیش‌نمایش را ببینید.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "تولید QR ممکن نشد. متن کوتاه‌تر یا تصحیح خطای کمتر امتحان کنید.", + "copyUnsupported": "مرورگر از کپی تصویر در کلیپ‌بورد پشتیبانی نمی‌کند.", + "copyFailed": "کپی در کلیپ‌بورد انجام نشد." + } + }, + "IpSubnetCalculator": { + "title": "ماشین‌حساب IP / زیرشبکه", + "subtitle": "جزئیات CIDR برای IPv4 و IPv6 در مرورگر—بدون درخواست شبکهٔ خارجی.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "شبکه", + "placeholderIpv4": "10.0.0.0/24 یا 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 یا 2001:db8::1/64", + "hintIpv4": "از CIDR (مثلاً /24) یا IPv4 به‌همراه ماسک نقطه‌دار استفاده کنید.", + "hintIpv6": "طول پیشوند را بیاورید (مثلاً /64). بدون آن /128 فرض می‌شود.", + "copyValue": "کپی مقدار", + "rows": { + "cidr": "CIDR", + "network": "آدرس شبکه", + "netmask": "ماسک زیرشبکه", + "wildcard": "ماسک wildcard", + "broadcast": "Broadcast", + "firstHost": "اولین میزبان", + "lastHost": "آخرین میزبان", + "totalAddresses": "آدرس‌های کل", + "usableHosts": "میزبان‌های قابل استفاده", + "prefixLength": "طول پیشوند (بیت)", + "networkIpv6": "آدرس شبکه", + "lastIpv6": "آخرین آدرس در محدوده", + "totalAddressesIpv6": "آدرس در زیرشبکه" + }, + "usableNote": { + "pointToPoint": "پیوندهای /31 نقطه‌به‌نقطه هر دو آدرس را به‌عنوان میزبان به‌کار می‌برند (RFC 3021).", + "singleHost": "/32 برای یک میزبان است." + }, + "ipv6RangeHint": { + "normal": "محدودهٔ ریاضی کامل نشان داده می‌شود؛ قوانین مسیریاب ممکن است متفاوت باشد.", + "single": "/128 یک آدرس واحد است.", + "full": "/0 تمام IPv6 را پوشش می‌دهد." + }, + "errors": { + "invalidIpv4": "شبکهٔ IPv4 نامعتبر. CIDR یا آدرس + ماسک امتحان کنید.", + "invalidIpv6": "شبکهٔ IPv6 نامعتبر. از آدرس/پیشوند مثل 2001:db8::/48 استفاده کنید." + } + }, + "HashGenerator": { + "title": "تولیدکنندهٔ هش", + "subtitle": "چکسام‌های MD5 و SHA-1 و SHA-2 برای متن یا فایل؛ bcrypt برای گذرواژه‌ها (ورودی متنی). همه‌چیز به‌صورت محلی در مرورگر.", + "algorithmLabel": "الگوریتم", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "متن", + "modeFile": "فایل", + "inputLabelText": "ورودی", + "placeholderText": "متن را وارد یا بچسبانید…", + "chooseFile": "انتخاب فایل", + "clearFile": "پاک کردن", + "fileHint": "فقط در حافظه خوانده می‌شود؛ حداکثر {maxMb} مگابایت. آپلود نمی‌شود.", + "outputLabel": "چکیده (hex)", + "copyHash": "کپی چکیده", + "copied": "کپی شد", + "byteCount": "{count, plural, one {# بایت} other {# بایت}}", + "charCount": "{count, plural, one {# نویسه} other {# نویسه}}", + "emptyHint": "متن وارد کنید یا فایلی انتخاب کنید.", + "securityNote": "برای کاربردهای امنیتی جدید MD5 و SHA-1 توصیه نمی‌شود؛ SHA-256 یا SHA-512 بهتر است.", + "errors": { + "fileTooLarge": "فایل بزرگ‌تر از {maxMb} مگابایت است.", + "fileReadFailed": "خواندن فایل ممکن نشد." + }, + "bcryptRoundsLabel": "هزینه (گردها)", + "bcryptRoundsHint": "مقادیر بالاتر کندتر و مقاوم‌تر در برابر حملهٔ brute-force هستند. پیش‌فرض رایج ۱۰ است.", + "fileTabDisabledTitle": "bcrypt فقط گذرواژه را از متن هش می‌کند، نه فایل‌های دلخواه.", + "placeholderBcrypt": "گذرواژه را برای هش کردن تایپ یا جای‌گذاری کنید…", + "outputLabelBcrypt": "هش bcrypt", + "copyBcrypt": "کپی هش", + "emptyHintBcrypt": "برای محاسبهٔ هش bcrypt گذرواژه وارد کنید.", + "securityNoteBcrypt": "هر خروجی bcrypt نمک تصادفی دارد؛ همان گذرواژه هر بار رشتهٔ متفاوتی می‌دهد. این طبیعی است. در تولید هزینهٔ بالا بگذارید.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "زمین بازی رمزنگاری", + "subtitle": "AES-GCM در مرورگر: رمزنگاری به بسته JSON با عبارت عبور یا کلید خام و رمزگشایی محلی. آموزشی و گاهی کاربردی — چیزی آپلود نمی‌شود.", + "tabEncrypt": "رمزنگاری", + "tabDecrypt": "رمزگشایی", + "keyModeLabel": "ماده کلیدی", + "modePassphrase": "عبارت عبور (PBKDF2 + AES-GCM)", + "modeRawKey": "کلید خام AES (هگز یا Base64)", + "aesBitsLabel": "طول کلید AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "کدگذاری کلید خام", + "encodingAuto": "خودکار (هگز اگر شبیه هگز باشد، وگرنه Base64)", + "encodingHex": "هگز", + "encodingBase64": "Base64", + "rawKeyLabel": "کلید خام", + "rawKeyPlaceholder": "کلید ۳۲ بایتی به صورت ۶۴ نویسه هگز یا Base64 برای AES-۲۵۶ (طول به اندازه AES بالا بستگی دارد).", + "rawKeyPlaceholderDecrypt": "فقط وقتی لازم است که بسته keyDerivation برابر \"none\" داشته باشد.", + "passphraseLabel": "عبارت عبور", + "passphrasePlaceholder": "عبارت عبور فقط در مرورگر برای استخراج کلید استفاده می‌شود.", + "passphrasePlaceholderDecrypt": "فقط وقتی لازم است که بسته از PBKDF2-SHA256 استفاده کند.", + "pbkdf2IterLabel": "تکرارهای PBKDF2", + "pbkdf2Hint": "بین ۱۰٬۰۰۰ و ۲٬۰۰۰٬۰۰۰. پیش‌فرض با تنظیمات رایج گاوصندوق هم‌خوان است.", + "plaintextLabel": "متن آشکار", + "plaintextPlaceholder": "پیام را برای رمزنگاری تایپ یا جای‌گذاری کنید…", + "bundleInputLabel": "بسته رمزنگاری‌شده (JSON)", + "bundlePlaceholder": "JSON حاصل از رمزنگاری را بچسبانید (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "بسته رمزنگاری‌شده", + "plaintextOutputLabel": "متن آشکار", + "encryptButton": "رمزنگاری", + "decryptButton": "رمزگشایی", + "copyBundle": "کپی JSON", + "copyPlaintext": "کپی متن آشکار", + "copied": "کپی شد", + "emptyBundleHint": "JSON رمزنگاری‌شده پس از رمزنگاری اینجا نمایش داده می‌شود.", + "emptyDecryptHint": "متن رمزگشایی‌شده اینجا نمایش داده می‌شود.", + "decryptKeyHint": "اگر keyDerivation برابر \"none\" است از کلید خام؛ برای PBKDF2-SHA256 از عبارت عبور استفاده کنید. بسته نمک، تکرارها، IV و طول AES را نگه می‌دارد.", + "securityNote": "کلیدها و متن آشکار مرورگر شما را ترک نمی‌کنند. برای اسرار تولیدی از ابزارهای تخصصی استفاده کنید.", + "errors": { + "emptyKey": "کلید خام متناسب با اندازه AES وارد کنید.", + "emptyPassphrase": "عبارت عبور را وارد کنید.", + "badKeyMaterial": "کلید به صورت هگز یا Base64 خوانده نشد.", + "wrongKeyLength": "طول کلید با AES انتخاب‌شده مطابقت ندارد (۱۶ / ۲۴ / ۳۲ بایت).", + "invalidJson": "JSON معتبر نیست.", + "invalidBundle": "این JSON بسته رمزنگاری پشتیبانی‌شده نیست.", + "decryptFailed": "رمزگشایی ناموفق — راز اشتباه، بسته آسیب‌دیده یا پارامترهای نادرست.", + "encryptFailed": "رمزنگاری ناموفق. Web Crypto و ورودی‌ها را بررسی کنید." + } + }, + "HmacGenerator": { + "title": "تولیدکنندهٔ HMAC", + "subtitle": "برای امضای وب‌هوک‌ها و آزمایش یکپارچگی API. HMAC-SHA1/256/384/512 به صورت hex یا Base64—به‌صورت محلی در مرورگر.", + "secretLabel": "کلید مخفی", + "messageLabel": "پیام / بار مفید", + "placeholderSecret": "رمز امضا را بچسبانید…", + "placeholderMessage": "بدنهٔ خام یا رشتهٔ احراز هویت را بچسبانید (معمولاً همان بایت‌های دقیق وب‌هوک).", + "algorithmLabel": "الگوریتم HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "قالب خروجی", + "formatHex": "Hex (حروف کوچک)", + "formatBase64": "Base64", + "outputLabel": "امضا", + "copyOutput": "کپی امضا", + "copied": "کپی شد", + "byteCountHint": "{count, plural, one {# بایت} other {# بایت}} (UTF-8)", + "emptyHint": "برای محاسبهٔ HMAC کلید مخفی وارد کنید.", + "securityNote": "هرگز رمز امضا را به اشتراک نگذارید. این صفحه داده‌ای آپلود نمی‌کند؛ همه‌چیز در مرورگر اجرا می‌شود.", + "errors": { + "cryptoFailed": "محاسبهٔ HMAC ممکن نشد. پشتیبانی Web Crypto را بررسی و دوباره امتحان کنید.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "تولیدکنندهٔ کد TOTP / 2FA", + "subtitle": "راز Base32 را بچسبانید و کد شش‌رقمی فعلی RFC 6238 (SHA-1، ۳۰ ث) را بگیرید. همه‌چیز محلی در مرورگر—مفید برای آزمایش ورود یا MFA.", + "secretLabel": "راز (Base32)", + "placeholderSecret": "کلید مخفی را بچسبانید (حروف A–Z و ارقام ۲–۷، همان‌طور که در راه‌اندازی نمایش داده می‌شود)…", + "codeLabel": "کد فعلی", + "copyCode": "کپی کد", + "copied": "کپی شد", + "nextIn": "تازه‌سازی تا {seconds} ث", + "standardHint": "گام زمانی ۳۰ ث، ۶ رقم، HMAC-SHA-1 (سازگار با Google Authenticator).", + "digitsLabel": "ارقام", + "periodLabel": "دوره", + "algorithmLabel": "الگوریتم", + "periodSeconds": "{seconds} ث", + "emptyHint": "برای ساخت کد شش‌رقمی یک راز بچسبانید.", + "invalidHint": "راز را اصلاح کنید تا کد دیده شود.", + "securityNote": "مثل رمز عبور با آن رفتار کنید. راز مرورگر را ترک نمی‌کند؛ چیزی آپلود نمی‌شود.", + "importLabel": "درون‌ریزی از URI با فرمت otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "درون‌ریزی", + "qrSectionTitle": "کد QR فراهم‌سازی", + "qrHint": "با یک برنامه احراز هویت (Google Authenticator، 1Password، Aegis…) اسکن کنید تا این کلید مخفی اضافه شود. کد QR کلید مخفی را رمزگذاری می‌کند — با آن مانند یک رمز عبور رفتار کنید.", + "accountLabel": "حساب", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "صادرکننده (اختیاری)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "برای تولید کد QR یک کلید مخفی معتبر وارد کنید.", + "errors": { + "invalidSecret": "این یک راز Base32 معتبر نیست.", + "cryptoFailed": "محاسبهٔ TOTP ممکن نشد. Web Crypto را بررسی و دوباره امتحان کنید.", + "invalidUri": "این یک URI معتبر otpauth:// TOTP نیست.", + "unsupportedUri": "این URI از مقادیری استفاده می‌کند که ابزار پشتیبانی نمی‌کند (رقم‌ها باید ۶ یا ۸ باشند؛ دوره ۱۵، ۳۰ یا ۶۰ ثانیه)." + } + }, + "MarkdownPreview": { + "title": "پیش‌نمایش Markdown و HTML", + "subtitle": "رندر زنده Markdown با صادرکردن HTML و تبدیل HTML → Markdown. همه چیز در مرورگر شما اجرا می‌شود.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "ورودی Markdown", + "htmlInputLabel": "ورودی HTML", + "previewLabel": "پیش‌نمایش", + "htmlOutputLabel": "منبع HTML", + "mdOutputLabel": "خروجی Markdown", + "inputPlaceholder": "# سلام\\n\\n**markdown** خود را اینجا بنویسید…", + "htmlInputPlaceholder": "HTML خود را اینجا بچسبانید…", + "copyHtml": "کپی HTML", + "copyMarkdown": "کپی Markdown", + "exportHtml": "خروجی HTML", + "copied": "کپی شد", + "charCount": "{count, plural, other {# کاراکتر}}", + "emptyHint": "برای دیدن پیش‌نمایش زنده و خروجی HTML، Markdown تایپ کنید.", + "emptyHintHtmlToMd": "HTML را برای تبدیل به Markdown بچسبانید.", + "securityNote": "همه چیز به صورت محلی در مرورگر شما اجرا می‌شود. هیچ چیزی آپلود نمی‌شود." + }, + "NumberBaseConverter": { + "title": "مبدّل مبنای عددی", + "subtitle": "تبدیل اعداد صحیح بین مبناهای ۲–۳۶. پیشوندهای اختیاری 0x / 0b / 0o. همه‌چیز به‌صورت محلی در مرورگر.", + "inputBaseLabel": "از مبنا", + "outputBaseLabel": "به مبنا", + "inputLabel": "عدد ورودی", + "placeholderInput": "یک عدد صحیح در مبنای ورودی انتخاب‌شده وارد کنید…", + "outputLabel": "مقدار تبدیل‌شده", + "copyOutput": "کپی نتیجه", + "copied": "کپی شد", + "swap": "جابه‌جایی مبناها", + "byteCountHint": "{count, plural, one {# بایت} other {# بایت}} (طول ورودی UTF-8)", + "emptyHint": "برای تبدیل عددی وارد کنید.", + "note": "ارقام بالاتر از ۹ با حروف A–Z (بدون حساسیت به حروف بزرگ/کوچک). فاصله و زیرخط نادیده گرفته می‌شوند.", + "errors": { + "invalidNumber": "این مقدار برای مبنای ورودی انتخاب‌شده معتبر نیست." + } + }, + "FormatConverter": { + "title": "مبدّل فرمت", + "subtitle": "بین JSON، YAML، TOML و XML در هر جهتی تبدیل کنید. تمام ۱۲ ترکیب. همه چیز در مرورگر شما اجرا می‌شود.", + "fromLabel": "از", + "toLabel": "به", + "swap": "تعویض", + "swapTitle": "فرمت‌ها را تعویض کنید و خروجی را به عنوان ورودی استفاده کنید", + "copy": "کپی", + "copied": "کپی شد", + "charCount": "{count, plural, one {# کاراکتر} other {# کاراکتر}}", + "emptyHint": "ورودی خود را در سمت چپ وارد کنید تا خروجی تبدیل‌شده را ببینید.", + "localNote": "همه چیز به صورت محلی در مرورگر شما اجرا می‌شود. چیزی آپلود نمی‌شود." + }, + "MimeTypeLookup": { + "title": "جستجوی نوع MIME", + "subtitle": "نوع MIME (media type) را بر اساس پسوند یا نام کامل فایل پیدا کنید. همه چیز به صورت محلی در مرورگر شما اجرا می‌شود.", + "inputLabel": "پسوند یا نام فایل", + "placeholder": "مثلاً .png، package.json، photo.jpeg", + "hint": "نکته: می‌توانید پسوند (با یا بدون نقطه) یا نام کامل فایل را وارد کنید.", + "clear": "پاک کردن", + "exampleFilename": "استفاده از نام فایل نمونه", + "exampleExtension": "استفاده از پسوند نمونه", + "resultLabel": "نتیجه", + "normalizedLabel": "ورودی نرمال‌شده:", + "mimeTypeLabel": "نوع MIME", + "contentTypeLabel": "هدر Content-Type", + "charsetLabel": "کدگذاری", + "copyMimeType": "کپی نوع MIME", + "copy": "کپی", + "copied": "کپی شد", + "emptyHint": "برای جستجوی نوع MIME یک پسوند یا نام فایل وارد کنید.", + "notFound": "برای این ورودی نوع MIME یافت نشد.", + "localNote": "همه چیز به صورت محلی در مرورگر شما اجرا می‌شود. چیزی آپلود نمی‌شود." + }, + "UserAgentParser": { + "title": "تحلیلگر User-Agent", + "subtitle": "یک رشته User-Agent (UA) را وارد کنید تا جزئیات مرورگر، سیستم‌عامل، موتور، دستگاه و CPU را ببینید. همه چیز به صورت محلی در مرورگر شما اجرا می‌شود.", + "inputLabel": "رشته User-Agent", + "placeholder": "User-Agent را اینجا بچسبانید…", + "hint": "نکته: می‌توانید UA را از هدرهای درخواست، لاگ‌ها یا devtools مرورگر کپی کنید.", + "clear": "پاک کردن", + "exampleDesktop": "استفاده از مثال دسکتاپ", + "exampleMobile": "استفاده از مثال موبایل", + "resultLabel": "جزئیات", + "browserLabel": "مرورگر", + "osLabel": "سیستم‌عامل", + "engineLabel": "موتور", + "deviceTypeLabel": "نوع دستگاه", + "deviceVendorLabel": "سازنده", + "deviceModelLabel": "مدل", + "cpuArchLabel": "معماری CPU", + "copyJson": "کپی JSON", + "copy": "کپی", + "copied": "کپی شد", + "emptyHint": "برای مشاهده جزئیات، یک رشته User-Agent را بچسبانید.", + "localNote": "همه چیز به صورت محلی در مرورگر شما اجرا می‌شود. چیزی آپلود نمی‌شود." + }, + "MockDataGenerator": { + "title": "مولد دادهٔ آزمایشی", + "subtitle": "ستون‌ها را تعریف کنید، نوع داده را انتخاب کنید و مجموعه‌ها را برای seed، Postman یا آزمون برون‌بری کنید. تا {max} سطر، همه در مرورگر — بدون بارگذاری.", + "mockarooLink": "گردش کار شبیه Mockaroo", + "rowsLabel": "سطرها", + "rowsHint": "حداکثر {max} سطر در هر تولید برای سبک ماندن زبانه.", + "formatLabel": "قالب", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "نام جدول SQL", + "presetsLabel": "الگوها", + "presetPlaceholder": "بارگذاری الگو…", + "presetDefault": "شروع (کاربر)", + "presetUsers": "کاربران و نقش‌ها", + "presetProducts": "SKU فروشگاه آنلاین", + "presetApiLog": "گزارش درخواست HTTP", + "generate": "تولید", + "warningDuplicateNames": "نام ستون تکراری: ستون‌های بعدی در شیء JSON قبلی را جایگزین می‌کنند. نام را عوض کنید.", + "schemaHeading": "طرح", + "addField": "افزودن ستون", + "removeField": "حذف ستون", + "duplicateField": "رونوشت ستون", + "colName": "نام", + "colType": "نوع", + "colOptions": "گزینه‌ها", + "colBlank": "خالی %", + "optMin": "حداقل", + "optMax": "حداکثر", + "optDecimals": "اعشار", + "optDateFrom": "از تاریخ", + "optDateTo": "تا تاریخ", + "optValues": "a, b, c", + "optStep": "گام", + "optRowBase": "پایه", + "output": "خروجی", + "outputPlaceholder": "برای ساخت داده روی «تولید» کلیک کنید…", + "copy": "رونوشت", + "download": "بارگیری", + "copied": "رونوشت شد", + "groups": { + "personal": "شخصی", + "location": "مکان", + "internetApi": "اینترنت و API", + "numbersIds": "اعداد و شناسه‌ها", + "dateTime": "تاریخ و زمان", + "textDesign": "متن و طراحی", + "custom": "سفارشی" + }, + "fieldTypes": { + "first_name": "نام", + "last_name": "نام خانوادگی", + "full_name": "نام کامل", + "email": "Email", + "username": "نام کاربری", + "password": "گذرواژه", + "phone": "تلفن", + "company": "شرکت", + "job_title": "سمت", + "avatar_url": "نشانی آواتار", + "city": "شهر", + "country": "کشور", + "country_code": "کد کشور (ISO)", + "street_address": "نشانی", + "zip_code": "کد پستی", + "latitude": "عرض جغرافیایی", + "longitude": "طول جغرافیایی", + "url": "URL", + "domain": "دامنه", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "عدد صحیح", + "float": "اعشاری", + "boolean": "بولی", + "uuid": "UUID", + "credit_card": "کارت (آزمایشی)", + "semver": "SemVer", + "row_index": "شاخص سطر", + "sequence": "توالی", + "date": "تاریخ", + "datetime": "تاریخ و زمان", + "unix_timestamp": "برچسب زمانی یونیکس", + "timezone": "منطقهٔ زمانی", + "word": "کلمه", + "sentence": "جمله", + "paragraph": "پاراگراف", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "کد ارز", + "language_code": "برچسب زبان", + "custom_list": "فهرست سفارشی" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "بهینه‌ساز SVG", + "subtitle": "SVG را بچسبانید، در مرورگر با SVGO فشرده کنید و اندازه UTF-8 را قبل و بعد مقایسه کنید.", + "inputLabel": "ورودی", + "outputLabel": "بهینه‌شده", + "placeholder": "قطعه یا کل فایل را بچسبانید…", + "outputPlaceholder": "SVG بهینه‌شده اینجا نمایش داده می‌شود…", + "chars": "{count} نویسه • {bytes} بایت (UTF-8)", + "preview": "پیش‌نمایش", + "localNote": "همه پردازش‌ها به‌صورت محلی در مرورگر است. چیزی آپلود نمی‌شود.", + "stats": { + "title": "اندازه (UTF-8)", + "before": "قبل", + "after": "بعد", + "savedBytes": "{count} بایت صرفه‌جویی ({percent}%)", + "noChange": "کاهشی نیست — از قبل حداقل است یا خروجی خالی است." + }, + "actions": { + "clear": "پاک کردن", + "sample": "بارگذاری نمونه", + "copy": "رونوشت", + "copied": "رونوشت شد", + "download": "بارگیری" + }, + "errors": { + "optimizeFailed": "بهینه‌سازی این SVG ممکن نشد." + } + }, + "UnitConverter": { + "title": "مبدل واحد", + "subtitle": "تبدیل بین 323 واحد در 43 دسته", + "category": "دسته‌بندی", + "from": "از", + "to": "به", + "value": "مقدار", + "result": "نتیجه", + "swapUnits": "تعویض واحدها", + "placeholder": "مقدار را وارد کنید", + "copyResult": "کپی نتیجه", + "searchCategory": "جستجوی دسته‌بندی...", + "selectCategory": "یک دسته انتخاب کنید", + "noResults": "دسته‌بندی‌ای یافت نشد." + }, + "DockerComposeGenerator": { + "title": "تولیدکننده Docker Compose", + "subtitle": "پایگاه‌داده‌های رایج، کش، صف پیام، پروکسی معکوس و قابلیت مشاهده را انتخاب کنید — docker-compose.yml آماده توسعه محلی را کپی کنید.", + "searchPlaceholder": "جستجو (postgres، redis، kafka، nginx، grafana…)", + "selectedLabel": "سرویس‌ها", + "selectedCount": "{count, plural, one {# سرویس} other {# سرویس}}", + "outputTitle": "docker-compose.yml", + "copy": "کپی", + "copied": "کپی شد!", + "download": "دانلود", + "emptyState": "برای تولید YAML Compose یک یا چند سرویس انتخاب کنید.", + "hintPorts": "پورت‌های میزبان پیش‌فرض برای کاهش تداخل فاصله دارند؛ در صورت خطای اتصال تغییر دهید. قبل از محیط‌های اشتراکی رمزهای نمایشی را عوض کنید.", + "categories": { + "databases": "پایگاه‌داده و ذخیره‌سازی", + "cache": "کش و درون‌حافظه", + "queues": "صف و پیام‌رسانی", + "search": "جستجو و گزارش‌ها", + "storage": "ذخیره‌سازی شیء", + "proxy": "پروکسی معکوس و ingress", + "observability": "معیارها و ردیابی", + "mail": "ایمیل (توسعه محلی)", + "admin": "رابط‌های مدیریت و BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "پاک کردن" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (سازگار با Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (سازگار با Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (سازگار با S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (ضبط SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (سرور توسعه)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "کاشی‌ها را بلغزانید و آنها را ادغام کنید تا به عدد 2048 برسید", + "score": "نمره", + "best": "بهترین", + "newGame": "بازی جدید", + "howToPlay": "برای جابجایی کاشی‌ها از کلیدهای جهت‌نما (یا کشیدن انگشت در موبایل) استفاده کنید. کاشی‌هایی که عدد یکسانی دارند با هم ادغام می‌شوند. برای برنده شدن به عدد 2048 برسید!", + "youWin": "شما به عدد ۲۰۴۸ رسیدید!", + "keepGoing": "ادامه بدید", + "gameOver": "بازی", + "tryAgain": "دوباره امتحان کنید", + "gameOverMessage": "جُم نخورید.", + "undo": "واگرد" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "مراحل تکمیل شده", + "time": "زمان", + "best": "بهترین", + "difficulty": { + "easy": "آسان", + "medium": "متوسط", + "hard": "سخت" + }, + "stage": ".", + "solved": "پاسخ داده شده", + "score": "نمره", + "nextStage": "مرحله بعد", + "replay": "بازپخش", + "erase": "پاک کردن", + "howToPlay": "روی یک سلول کلیک کنید، سپس روی یک عدد ضربه بزنید یا ۱ تا ۹ را فشار دهید. کلید Backspace یا Delete سلول را پاک می‌کند. اعداد پررنگ سرنخ هستند. جدول را طوری پر کنید که هر سطر، ستون و کادر ۳×۳ شامل اعداد ۱ تا ۹ بدون تکرار باشد.", + "pencil": "Pencil", + "undo": "Undo", + "hint": "Hint", + "hintsUsed": "hints used", + "mistakes": "Mistakes", + "failed": "Failed!", + "mistakeMode": "3-mistake mode" + }, + "Snake": { + "title": "مار", + "subtitle": "غذا بخور، رشد کن، از دیوارها و خودت دوری کن", + "score": "امتیاز", + "best": "بهترین", + "startPrompt": "یک کلید جهت را فشار دهید", + "startButton": "شروع بازی", + "gameOver": "بازی تمام شد", + "playAgain": "دوباره بازی کن", + "howToPlay": "از کلیدهای جهت یا WASD استفاده کن. غذا بخور تا رشد کنی. هر ۵۰ امتیاز سرعت افزایش می‌یابد.", + "diffEasy": "آسان", + "diffNormal": "معمولی", + "diffHard": "سخت", + "diffInfinite": "بی‌نهایت", + "orTap": "یا زیر را لمس کنید", + "howToPlayInfinite": "دیوارها تکرار می‌شوند — بدون مرگ دیواری. غذا بخور تا رشد کنی.", + "shieldActive": "Shield Active", + "speedBoost": "Speed Boost", + "powerSpeed": "Speed x2 (10 ticks)", + "powerShield": "Survive one death", + "powerShrink": "Shortens tail" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "اتصالات Redis", + "heroTitle": "Redis Commander", + "heroDesc": "به نمونه‌های Redis متصل شوید. کلیدها را مرور کنید، مقادیر را ویرایش کنید و دستورات را اجرا کنید. اعتبارنامه‌ها در مرورگر شما رمزگذاری می‌شوند.", + "heroAddConnection": "افزودن اتصال", + "vaultLockedTitle": "صندوق قفل است", + "vaultLockedDesc": "صندوق را باز کنید تا به اتصالات ذخیره شده دسترسی داشته باشید." + } + }, + "Minesweeper": { + "title": "مین‌یاب", + "subtitle": "همه خانه‌های امن را بدون انفجار مین پیدا کنید", + "mines": "مین‌ها", + "newGame": "بازی جدید", + "win": "شما برنده شدید!", + "loss": "باختید!", + "playAgain": "دوباره بازی کنید", + "tryAgain": "دوباره تلاش کنید", + "startPrompt": "برای شروع روی هر خانه‌ای کلیک کنید", + "difficulty": { + "beginner": "مبتدی", + "intermediate": "متوسط", + "expert": "حرفه‌ای" + } + }, + "Tetris": { + "title": "تتریس", + "subtitle": "بلوک‌های در حال سقوط را برای تکمیل ردیف‌ها مرتب کنید", + "next": "بعدی", + "score": "امتیاز", + "best": "بهترین", + "lines": "خطوط", + "level": "سطح", + "pause": "مکث", + "resume": "ادامه", + "start": "شروع", + "restart": "شروع مجدد", + "paused": "متوقف شده", + "gameOver": "پایان بازی" + }, + "SshKeyGenerator": { + "title": "تولیدکننده کلید SSH / RSA", + "subtitle": "جفت کلیدهای Ed25519 یا RSA را در مرورگرتان تولید کنید. کلیدهای خصوصی و عمومی را در قالب‌های OpenSSH و PEM دانلود کنید. هیچ چیزی آپلود نمی‌شود.", + "keyTypeLabel": "نوع کلید", + "recommended": "پیشنهادی", + "ed25519Desc": "سریع، کوچک و مدرن. پشتیبانی‌شده توسط GitHub، GitLab و تمام سرورهای SSH اصلی.", + "rsa2048Desc": "سازگاری گسترده. ایمن برای اکثر کاربردها.", + "rsa4096Desc": "حداکثر سازگاری و قدرت برای سیستم‌های قدیمی.", + "commentLabel": "توضیح (اختیاری)", + "commentHint": "به خط کلید عمومی OpenSSH اضافه می‌شود.", + "generateBtn": "تولید جفت کلید", + "generating": "در حال تولید…", + "generatingLong": "در حال تولید (ممکن است کمی طول بکشد)…", + "copy": "کپی", + "download": "دانلود", + "privateKeyLabel": "کلید خصوصی (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "کلید عمومی (فرمت OpenSSH)", + "publicKeySpkiLabel": "کلید عمومی (SPKI PEM)", + "browserNote": "تمام کلیدها در مرورگر شما تولید می‌شوند. هیچ چیزی به هیچ سروری ارسال نمی‌شود.", + "errors": { + "generationFailed": "تولید کلید ناموفق بود" + } + }, + "DnsLookup": { + "title": "جستجوی DNS", + "subtitle": "رکوردهای DNS هر دامنه را جستجو کنید — A، AAAA، MX، TXT، NS، CNAME.", + "placeholder": "دامنه را وارد کنید (مثلاً github.com)", + "lookupBtn": "جستجو", + "looking": "در حال جستجو…", + "refreshBtn": "تازه‌سازی", + "recordTypesLabel": "انواع رکورد", + "allBtn": "همه", + "tryLabel": "امتحان کنید:", + "errors": { + "lookupFailed": "جستجوی DNS ناموفق بود" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "مولد و بررسی‌کننده Bcrypt", + "subtitle": "گذرواژه‌ها را با bcrypt هش کنید و هش‌ها را تأیید کنید — همه‌چیز در مرورگر شما می‌ماند", + "tabs": { + "hash": "هش", + "verify": "تأیید" + }, + "showPassword": "نمایش گذرواژه", + "hidePassword": "پنهان‌کردن گذرواژه", + "copy": "کپی", + "copied": "کپی شد", + "hash": { + "passwordLabel": "گذرواژه", + "passwordPlaceholder": "گذرواژه‌ای برای هش‌کردن وارد کنید…", + "costLabel": "ضریب هزینه", + "costHint": "هزینه بالاتر کار هر مرحله را دو برابر می‌کند. ۱۰ تا ۱۲ انتخابی رایج برای محیط تولید است.", + "slowWarning": "ضریب هزینه ۱۳ به بالا ممکن است چند ثانیه برای محاسبه طول بکشد.", + "generate": "تولید هش", + "generating": "در حال هش‌کردن…", + "resultLabel": "هش Bcrypt", + "timeTaken": "{ms} میلی‌ثانیه", + "anatomy": { + "title": "ساختار هش", + "algorithm": "الگوریتم", + "cost": "هزینه", + "salt": "salt (۲۲ کاراکتر)", + "hash": "هش (۳۱ کاراکتر)" + } + }, + "verify": { + "passwordLabel": "گذرواژه", + "passwordPlaceholder": "گذرواژه‌ای برای بررسی وارد کنید…", + "hashLabel": "هش Bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "یک گذرواژه و یک هش bcrypt وارد کنید تا مقایسه شوند.", + "invalidHash": "این به‌نظر یک هش bcrypt معتبر نمی‌آید.", + "checking": "در حال مقایسه…", + "match": "منطبق است — گذرواژه این هش را تولید می‌کند.", + "noMatch": "منطبق نیست — گذرواژه این هش را تولید نمی‌کند." + } + }, + "ChmodCalculator": { + "title": "ماشین‌حساب Chmod", + "subtitle": "مجوزهای فایل یونیکس را بین چک‌باکس، هشت‌هشتی و نماد نمادین تبدیل کنید", + "grid": { + "title": "مجوزها", + "read": "خواندن", + "write": "نوشتن", + "execute": "اجرا", + "owner": "مالک", + "group": "گروه", + "others": "دیگران" + }, + "special": { + "title": "بیت‌های ویژه", + "setuid": "setuid", + "setgid": "setgid", + "sticky": "sticky" + }, + "octal": { + "label": "هشت‌هشتی", + "placeholder": "755 یا 4755", + "invalid": "۳ یا ۴ رقم هشت‌هشتی (۰-۷) وارد کنید." + }, + "symbolic": { + "label": "نمادین", + "placeholder": "rwxr-xr-x", + "invalid": "۹ کاراکتر مانند rwxr-xr-x وارد کنید (s/S و t/T مجاز است)." + }, + "output": { + "title": "دستور", + "copy": "کپی", + "copied": "کپی شد" + }, + "explain": { + "line": "{cls} می‌تواند {perms}.", + "none": "{cls} هیچ مجوزی ندارد.", + "setuid": "setuid: فایل با امتیازات مالک خود اجرا می‌شود.", + "setgid": "setgid: فایل با امتیازات گروه خود اجرا می‌شود (فایل‌های جدید در یک دایرکتوری گروه آن را به ارث می‌برند).", + "sticky": "sticky: فقط مالک فایل می‌تواند آن را در این دایرکتوری حذف یا تغییرنام دهد." + }, + "presets": { + "title": "پیش‌تنظیم‌های رایج", + "octal": "هشت‌هشتی", + "permissions": "مجوزها", + "description": "کاربرد معمول", + "p644": "فایل‌های معمولی: مالک می‌نویسد، همه می‌خوانند.", + "p755": "فایل‌های اجرایی و دایرکتوری‌ها: همه می‌توانند بخوانند و وارد شوند.", + "p600": "فایل‌های خصوصی: فقط مالک می‌خواند و می‌نویسد.", + "p700": "فایل‌های اجرایی و دایرکتوری‌های خصوصی: فقط مالک.", + "p664": "فایل‌های قابل‌نوشتن توسط گروه.", + "p775": "دایرکتوری‌ها و فایل‌های اجرایی قابل‌نوشتن توسط گروه.", + "p400": "فقط‌خواندنی برای مالک (مثلاً کلیدهای خصوصی SSH)." + } + }, + "CodeScreenshot": { + "title": "اسکرین‌شات کد", + "subtitle": "کد را به تصاویر زیبا و قابل‌اشتراک تبدیل کنید", + "languageLabel": "زبان", + "autoDetect": "تشخیص خودکار", + "backgroundLabel": "پس‌زمینه", + "paddingLabel": "فاصله داخلی", + "filenameLabel": "نام فایل", + "filenamePlaceholder": "example.ts", + "windowLabel": "نقطه‌های پنجره", + "themeLabel": "پوسته پنجره", + "themeDark": "تیره", + "themeLight": "روشن", + "scaleLabel": "مقیاس خروجی", + "copyImage": "کپی تصویر", + "copied": "کپی شد", + "exportPng": "خروجی PNG", + "inputLabel": "کد", + "detected": "تشخیص‌داده‌شده: {language}", + "clear": "پاک کردن", + "placeholder": "کد خود را اینجا بچسبانید…", + "previewLabel": "پیش‌نمایش", + "emptyPreview": "پیش‌نمایش کد شما اینجا ظاهر می‌شود", + "exportFailed": "خروجی‌گرفتن از تصویر ناموفق بود", + "copyUnsupported": "کپی تصاویر در این مرورگر پشتیبانی نمی‌شود", + "copyFailed": "کپی تصویر ناموفق بود", + "backgrounds": { + "ocean": "اقیانوس", + "sunset": "غروب", + "candy": "آب‌نبات", + "forest": "جنگل", + "midnight": "نیمه‌شب", + "gold": "طلایی", + "aurora": "شفق", + "slate": "سنگی", + "transparent": "شفاف" + } + }, + "CssGenerators": { + "title": "مولدهای CSS", + "subtitle": "box-shadow، border-radius و clamp() سیّال — کد CSS را کپی کنید.", + "preview": "پیش‌نمایش", + "output": "CSS", + "copy": "کپی", + "copied": "کپی شد", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() سیّال" + }, + "boxShadow": { + "presets": "پیش‌تنظیم‌ها", + "layer": "لایه", + "addLayer": "افزودن لایه", + "removeLayer": "حذف لایه", + "x": "X", + "y": "Y", + "blur": "محو", + "spread": "گسترش", + "color": "رنگ", + "alpha": "آلفا", + "inset": "داخلی", + "presetNames": { + "subtleCard": "کارت ملایم", + "elevated": "برجسته", + "hardOffset": "افست تیز", + "neonGlow": "درخشش نئون", + "inner": "درونی", + "layeredSmooth": "چندلایه نرم" + } + }, + "borderRadius": { + "modeLabel": "حالت", + "modes": { + "simple": "برای هر گوشه", + "blob": "شکل ارگانیک" + }, + "unit": "واحد", + "linkCorners": "پیوند گوشه‌ها", + "randomize": "تصادفی", + "horizontal": "شعاع‌های افقی", + "vertical": "شعاع‌های عمودی", + "corners": { + "tl": "بالا چپ", + "tr": "بالا راست", + "br": "پایین راست", + "bl": "پایین چپ" + } + }, + "clamp": { + "minFont": "کمینه فونت (px)", + "maxFont": "بیشینه فونت (px)", + "minViewport": "کمینه viewport (px)", + "maxViewport": "بیشینه viewport (px)", + "rootFont": "فونت ریشه (px)", + "viewportWidth": "viewport شبیه‌سازی‌شده", + "computedSize": "اندازه محاسبه‌شده", + "slope": "شیب", + "intercept": "عرض از مبدأ", + "previewText": "متن نمونه برای پیش‌نمایش", + "invalid": "کران‌های معتبر و متفاوت viewport وارد کنید." + } + }, + "ExifViewer": { + "title": "نمایشگر و حذف‌کننده EXIF", + "subtitle": "متادیتای عکس را بررسی و در مرورگر خود حذف کنید", + "dropTitle": "تصویری را اینجا رها کنید", + "dropHint": "یا برای مرور کلیک کنید — تصویر شما هرگز دستگاهتان را ترک نمی‌کند", + "browse": "انتخاب تصویر", + "formats": "JPEG، PNG، WebP، HEIC، TIFF", + "parsing": "در حال خواندن متادیتا…", + "parseFailed": "خواندن این تصویر ناموفق بود", + "clear": "حذف تصویر", + "gpsWarning": "حاوی موقعیت GPS است", + "openMap": "نمایش روی OpenStreetMap", + "noMetadata": "هیچ متادیتای تعبیه‌شده‌ای در این تصویر یافت نشد.", + "facts": { + "name": "نام", + "type": "نوع", + "dimensions": "ابعاد", + "size": "اندازه" + }, + "groups": { + "camera": "دوربین", + "exposure": "نوردهی", + "timestamps": "برچسب‌های زمانی", + "gps": "موقعیت GPS", + "software": "نرم‌افزار و سایر" + }, + "gpsCard": { + "latitude": "عرض جغرافیایی", + "longitude": "طول جغرافیایی", + "altitude": "ارتفاع" + }, + "raw": { + "title": "همه متادیتا", + "copyJson": "کپی JSON", + "copied": "کپی شد" + }, + "remove": { + "title": "حذف متادیتا", + "jpegDescription": "EXIF، XMP، IPTC و کامنت‌ها را بدون افت کیفیت و بدون فشرده‌سازی مجدد تصویر حذف کنید.", + "reencodeDescription": "تصویر را دوباره کدگذاری کنید تا متادیتای تعبیه‌شده حذف شود. این کار پیکسل‌ها را دوباره فشرده می‌کند.", + "keepIcc": "حفظ پروفایل رنگ (ICC)", + "stripButton": "حذف متادیتا", + "reencodeButton": "کدگذاری مجدد و حذف", + "unsupported": "حذف متادیتا برای این نوع فایل پشتیبانی نمی‌شود.", + "failed": "حذف متادیتا ناموفق بود", + "before": "قبل", + "after": "بعد", + "download": "دانلود تصویر پاک‌شده" + } + }, + "FaviconGenerator": { + "title": "مولد Favicon", + "subtitle": "از یک تصویر، favicon و آیکون‌های اپ بسازید", + "dropTitle": "تصویری را اینجا رها کنید", + "dropHint": "PNG، JPEG یا SVG — برای مرور کلیک کنید", + "browse": "انتخاب تصویر", + "clear": "حذف تصویر", + "backgroundLabel": "پس‌زمینه", + "transparent": "شفاف", + "solidColor": "یک‌رنگ", + "cornerLabel": "شکل گوشه", + "cornerNone": "مربع", + "cornerRounded": "گِرد", + "cornerCircle": "دایره", + "maskPaddingLabel": "فاصله داخلی maskable", + "maskable": "maskable", + "previewLabel": "پیش‌نمایش", + "copy": "کپی", + "headSnippet": "قطعه‌کد HTML head", + "downloadIco": "دانلود favicon.ico", + "downloadZip": "دانلود همه (.zip)" + }, + "JsonDiff": { + "title": "تفاوت JSON", + "subtitle": "مقایسه معنایی و ساختارآگاه دو سند JSON — ترتیب کلیدها و قالب‌بندی نادیده گرفته می‌شود", + "options": { + "ignoreArrayOrder": "نادیده‌گرفتن ترتیب آرایه" + }, + "loadBothSamples": "بارگذاری نمونه‌ها", + "sample": "نمونه", + "format": "قالب", + "clear": "پاک کردن", + "formatError": "قالب‌بندی ممکن نیست: ورودی JSON معتبر نیست", + "placeholder": "JSON را اینجا بچسبانید یا تایپ کنید…", + "panels": { + "left": "اصلی (چپ)", + "right": "تغییریافته (راست)", + "results": "تفاوت‌ها" + }, + "summary": { + "added": "{count} افزوده", + "removed": "{count} حذف‌شده", + "changed": "{count} تغییریافته" + }, + "types": { + "added": "افزوده", + "removed": "حذف‌شده", + "changed": "تغییریافته" + }, + "copyChanges": "کپی به‌صورت JSON", + "copied": "کپی شد", + "empty": "JSON را در هر دو پنل بچسبانید تا تفاوت‌هایشان را ببینید.", + "identical": "دو سند از نظر معنایی یکسان هستند." + }, + "JsonpathPlayground": { + "title": "زمین بازی JSONPath", + "subtitle": "اسناد JSON را با JSONPath یا JMESPath پرس‌وجو کنید و نتایج زنده را ببینید", + "engineLabel": "موتور", + "queryLabel": "پرس‌وجو", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "برگه تقلب", + "cheats": { + "jpAllTitles": "همه عنوان‌های کتاب", + "jpAllAuthors": "هر نویسنده، در هر جای سند", + "jpFirstBook": "اولین کتاب", + "jpLastBook": "آخرین کتاب", + "jpSlice": "دو کتاب اول (برش)", + "jpFilterPrice": "کتاب‌های ارزان‌تر از ۴۰", + "jpFilterInStock": "عنوان کتاب‌های موجود", + "jpRecursive": "همه مقادیر، به‌صورت بازگشتی", + "jmAllTitles": "همه عنوان‌های کتاب", + "jmFirstAuthor": "نویسنده اولین کتاب", + "jmLastBook": "آخرین کتاب", + "jmSlice": "دو کتاب اول (برش)", + "jmFilterPrice": "عنوان کتاب‌های ارزان‌تر از ۴۰", + "jmCount": "شمارش کتاب‌های موجود", + "jmProjection": "بازآرایی هر کتاب به یک شیء جدید", + "jmSort": "عنوان‌ها مرتب‌شده بر اساس قیمت" + }, + "panels": { + "input": "ورودی JSON", + "results": "نتایج" + }, + "sample": "نمونه", + "format": "قالب", + "clear": "پاک کردن", + "formatError": "قالب‌بندی ممکن نیست: ورودی JSON معتبر نیست", + "placeholder": "JSON را اینجا بچسبانید یا تایپ کنید…", + "outputPlaceholder": "تطبیق‌ها اینجا ظاهر می‌شوند…", + "matchCount": "{count, plural, one {# تطبیق} other {# تطبیق}}", + "copy": "کپی", + "copied": "کپی شد" + }, + "KeycodeInspector": { + "title": "بازرس کدِ کلید", + "subtitle": "هر کلیدی را فشار دهید تا رویداد صفحه‌کلید JavaScript آن را بررسی کنید", + "capture": { + "prompt": "هر کلیدی را فشار دهید…", + "ready": "در حال گوش‌دادن — هر کلیدی روی صفحه‌کلید خود فشار دهید", + "clickToFocus": "برای فوکوس اینجا کلیک کنید، سپس هر کلیدی را فشار دهید" + }, + "hero": { + "spaceKey": "(فاصله)", + "keyLabel": "event.key", + "repeat": "تکرار" + }, + "copyJson": "کپی JSON", + "copiedJson": "JSON رویداد در کلیپ‌بورد کپی شد", + "deprecated": "منسوخ", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "استاندارد", + "left": "چپ", + "right": "راست", + "numpad": "صفحه‌کلید عددی", + "unknown": "نامشخص" + }, + "history": { + "title": "تاریخچه", + "clear": "پاک‌کردن تاریخچه", + "empty": "فشردن کلیدها اینجا ظاهر می‌شود." + } + }, + "MarkdownTableGenerator": { + "title": "مولد جدول Markdown", + "subtitle": "یک شبکه را ویرایش کنید، از صفحه‌گسترده بچسبانید و جدول Markdown یا HTML با تراز زیبا کپی کنید", + "addRow": "افزودن ردیف", + "addColumn": "افزودن ستون", + "importLabel": "درون‌ریزی", + "importPlaceholder": "TSV (از Excel / Google Sheets) یا CSV را اینجا بچسبانید…", + "importButton": "درون‌ریزی به شبکه", + "pasteClipboard": "چسباندن از کلیپ‌بورد", + "importEmpty": "چیزی برای درون‌ریزی نیست — ابتدا چند ردیف بچسبانید", + "clipboardError": "خواندن از کلیپ‌بورد ممکن نشد", + "clear": "پاک کردن", + "panels": { + "editor": "ویرایشگر جدول", + "output": "خروجی" + }, + "align": { + "left": "تراز چپ", + "center": "تراز وسط", + "right": "تراز راست" + }, + "headerPlaceholder": "سرستون", + "cellPlaceholder": "سلول", + "removeRow": "حذف ردیف", + "removeColumn": "حذف ستون", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "کپی", + "copied": "کپی شد" + }, + "TimezoneConverter": { + "title": "مبدل منطقه زمانی", + "subtitle": "یک لحظه را در سراسر جهان برای برنامه‌ریزی جلسات تبدیل کنید.", + "dateTimeLabel": "تاریخ و زمان", + "now": "اکنون", + "sourceZoneLabel": "منطقه زمانی مبدأ", + "selectZone": "انتخاب منطقه", + "searchZone": "جست‌وجوی مناطق زمانی...", + "noZone": "منطقه زمانی‌ای یافت نشد.", + "scrubLabel": "جابه‌جایی زمان", + "reset": "بازنشانی", + "targetsLabel": "مناطق مقصد", + "addZone": "افزودن منطقه", + "remove": "حذف", + "localBadge": "محلی", + "invalidDate": "تاریخ و زمان معتبری وارد کنید.", + "bands": { + "work": "ساعات کاری (۰۹:۰۰–۱۸:۰۰)", + "edge": "ساعات حاشیه‌ای (صبح زود / عصر)", + "night": "شب" + } + }, + "TokenCounter": { + "title": "شمارنده توکن LLM", + "subtitle": "توکن‌ها را بشمارید، تکه‌ها را تجسم کنید و هزینه API را تخمین بزنید.", + "inputLabel": "متن", + "sample": "نمونه", + "clear": "پاک کردن", + "placeholder": "متن، کد یا JSON را برای شمارش توکن بچسبانید...", + "encoding": "کدگذاری", + "loading": "در حال بارگذاری توکنایزر...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-series)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "توکن‌ها", + "characters": "کاراکترها", + "words": "کلمات", + "ratio": "توکن/کلمه", + "ratioSub": "تقریبی" + }, + "claudeRow": "Claude (تقریبی)", + "estimate": "تخمین", + "viz": { + "title": "تجسم توکن", + "toggle": "نمایش", + "hint": "برای رنگ‌آمیزی هر توکن به‌عنوان یک تکه، تغییر وضعیت دهید.", + "empty": "برای تجسم توکن‌ها متن وارد کنید.", + "truncated": "نمایش {limit} توکن اول از {total} توکن." + }, + "cost": { + "title": "ماشین‌حساب هزینه", + "disclaimer": "مقادیر پیش‌فرض قابل‌ویرایش — قیمت‌گذاری فعلی را بررسی کنید", + "model": "مدل", + "expectedOutput": "توکن‌های خروجی موردانتظار", + "customInput": "ورودی $/MTok", + "customOutput": "خروجی $/MTok", + "inputCost": "هزینه ورودی", + "outputCost": "هزینه خروجی", + "total": "کل", + "per1000": "به‌ازای هر ۱۰۰۰ فراخوانی" + } + }, + "WebhookTester": { + "title": "آزمایشگر Webhook", + "subtitle": "یک URL موقت بسازید و درخواست‌های ورودی webhook را زنده بررسی کنید.", + "intro": { + "title": "برای شروع ثبت درخواست‌ها یک URL webhook بسازید", + "hint": "یک URL غیرقابل‌حدس دریافت می‌کنید که هر درخواست ورودی را ثبت می‌کند. پس از ۲۴ ساعت منقضی می‌شود." + }, + "createBtn": "ساخت URL webhook", + "creating": "در حال ساخت…", + "urlLabel": "URL webhook شما", + "expiresIn": "انقضا در {countdown}", + "copyUrlBtn": "کپی URL", + "copied": "کپی شد", + "clearBtn": "پاک کردن", + "newBinBtn": "URL جدید", + "requestsTitle": "درخواست‌ها", + "selectPrompt": "یک درخواست را برای بررسی جزئیاتش انتخاب کنید.", + "expiredNotice": "این URL webhook منقضی شده است. برای ادامه آزمایش یک URL جدید بسازید.", + "base64Badge": "base64", + "truncatedBadge": "کوتاه‌شده", + "empty": { + "title": "در انتظار درخواست‌ها…", + "hint": "درخواستی به URL خود بفرستید تا فوراً اینجا ظاهر شود. این را امتحان کنید:" + }, + "detail": { + "receivedAt": "دریافت‌شده", + "source": "IP مبدأ", + "contentType": "Content-Type", + "query": "پارامترهای پرس‌وجو", + "headers": "هدرها", + "body": "بدنه", + "copyBody": "کپی بدنه", + "emptyBody": "بدون بدنه" + }, + "errors": { + "createFailed": "ساخت URL webhook ناموفق بود. لطفاً دوباره تلاش کنید.", + "clearFailed": "پاک‌کردن درخواست‌ها ناموفق بود. لطفاً دوباره تلاش کنید." + } + }, + "WebsocketTester": { + "title": "آزمایشگر WebSocket", + "subtitle": "به یک نقطه پایانی WebSocket متصل شوید و پیام‌ها را زنده بفرستید، دریافت و بررسی کنید", + "urlLabel": "URL نقطه پایانی", + "protocolsLabel": "زیرپروتکل‌ها", + "protocolsPlaceholder": "اختیاری، جداشده با کاما", + "connect": "اتصال", + "disconnect": "قطع اتصال", + "status": { + "disconnected": "قطع‌شده", + "connecting": "در حال اتصال", + "connected": "متصل", + "error": "خطا" + }, + "messages": { + "connecting": "در حال اتصال به {url}…", + "connected": "اتصال برقرار شد", + "connectedWithProtocol": "اتصال برقرار شد (پروتکل: {protocol})", + "invalidUrl": "URL نامعتبر — باید با ws:// یا wss:// شروع شود", + "connectFailed": "بازکردن اتصال ناموفق بود", + "socketError": "خطایی در اتصال رخ داد", + "notConnected": "متصل نیست — ابتدا متصل شوید", + "binaryMessage": "یک پیام باینری دریافت شد" + }, + "log": { + "title": "گزارش پیام‌ها", + "empty": "هنوز پیامی نیست. متصل شوید و ارسال را آغاز کنید.", + "copyAll": "کپی همه", + "download": "دانلود به‌صورت .txt", + "clear": "پاک‌کردن گزارش", + "prettyPrint": "قالب‌بندی JSON", + "showRaw": "نمایش متن خام" + }, + "composer": { + "placeholder": "پیامی برای ارسال بنویسید…", + "send": "ارسال", + "formatJson": "قالب‌بندی به‌صورت JSON", + "invalidJson": "پیام JSON معتبر نیست", + "hint": "Cmd/Ctrl+Enter برای ارسال" + }, + "ping": { + "label": "ping خودکار", + "seconds": "ثانیه", + "intervalTitle": "فاصله ping به ثانیه", + "payloadPlaceholder": "محموله ping" + } + }, + "WhoisLookup": { + "title": "جست‌وجوی Whois", + "subtitle": "جزئیات ثبت هر دامنه یا آدرس IP را از طریق RDAP جست‌وجو کنید", + "placeholder": "example.com یا 8.8.8.8", + "lookupBtn": "جست‌وجو", + "looking": "در حال جست‌وجو…", + "refreshBtn": "تازه‌سازی", + "tryLabel": "امتحان کنید:", + "copy": "کپی", + "copied": "کپی شد", + "notAvailable": "در دسترس نیست", + "summary": { + "registrar": "ثبت‌کننده", + "network": "شبکه" + }, + "dates": { + "created": "ایجادشده", + "updated": "به‌روزشده", + "expires": "انقضا" + }, + "sections": { + "status": "وضعیت", + "nameservers": "سرورهای نام", + "contacts": "تماس‌ها", + "rawRdap": "RDAP خام" + }, + "contacts": { + "roles": "نقش‌ها", + "name": "نام", + "organization": "سازمان" + }, + "errors": { + "invalidTarget": "یک نام دامنه یا آدرس IP معتبر وارد کنید", + "lookupFailed": "جست‌وجو ناموفق بود (وضعیت {status})" + } + } +} diff --git a/apps/desktop-ui/messages/fr.json b/apps/desktop-ui/messages/fr.json new file mode 100644 index 00000000..71cc46cd --- /dev/null +++ b/apps/desktop-ui/messages/fr.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Tableau de bord", + "settings": "Paramètres", + "toDo": "Tâches", + "notes": "Notes", + "passwordManager": "Gestionnaire de mots de passe", + "environmentManager": "Gestionnaire d'environnement", + "bookmarks": "Signets", + "emailValidator": "Validateur d’e-mail", + "encryptionPlayground": "Bac à sable chiffrement", + "jwtDecoder": "Décodeur JWT", + "certificatePemDecoder": "Décodeur de certificat / PEM", + "jsonFormatter": "Formateur JSON", + "sqlFormatter": "Formateur SQL", + "graphqlFormatter": "Formateur GraphQL", + "diffChecker": "Comparateur de différences", + "regexTester": "Testeur d’expressions régulières", + "timestampConverter": "Convertisseur d’horodatage", + "cronBuilder": "Constructeur Cron", + "base64Encoder": "Encodeur Base64", + "urlEncoder": "Encodeur d’URL", + "urlParser": "Analyseur d’URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Secret / clé API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Sélecteur de couleurs", + "contrastChecker": "Vérificateur de contraste", + "apiClient": "Client API", + "httpStatusCodes": "Codes d’état HTTP", + "nosqlExplorer": "Explorateur de base de données", + "jsonSchemaGenerator": "Générateur de schéma JSON", + "imageToBase64": "Image vers Base64", + "imageCompressor": "Compresseur d’images", + "cssGradientBuilder": "Générateur de dégradé CSS", + "gitignoreGenerator": "Générateur .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Extraits de code", + "qrCodeGenerator": "Générateur de codes QR", + "dnsLookup": "Recherche DNS", + "ipSubnetCalculator": "Calculateur IP / sous-réseau", + "hashGenerator": "Générateur de hash", + "hmacGenerator": "Générateur HMAC", + "totpGenerator": "Code TOTP / 2FA", + "markdownPreview": "Aperçu Markdown", + "numberBaseConverter": "Convertisseur de base", + "formatConverter": "Convertisseur de formats", + "mockDataGenerator": "Générateur de données fictives", + "svgOptimizer": "Optimiseur SVG", + "unitConverter": "Convertisseur d'unités", + "dockerComposeGenerator": "Générateur Docker Compose", + "breakRoom": "Salle de pause", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Formateur YAML", + "sshKeyGenerator": "Générateur de clés SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Calculateur Chmod", + "codeScreenshot": "Capture de code", + "cssGenerators": "Générateurs CSS", + "exifViewer": "Visionneuse EXIF", + "faviconGenerator": "Générateur de favicon", + "jsonDiff": "Diff JSON", + "jsonpathPlayground": "Playground JSONPath", + "keycodeInspector": "Inspecteur de keycode", + "markdownTableGenerator": "Tableau Markdown", + "timezoneConverter": "Convertisseur de fuseaux", + "tokenCounter": "Compteur de tokens", + "webhookTester": "Testeur de webhook", + "websocketTester": "Testeur WebSocket", + "whoisLookup": "Recherche Whois" + }, + "Help": { + "title": "Aide et documentation", + "subtitle": "Comment MyDevTools s’articule, ce que fait chaque app et comment la sécurité et le chiffrement protègent vos données.", + "tabs": { + "overview": "Aperçu", + "apps": "Apps", + "security": "Sécurité", + "tips": "Conseils" + }, + "overview": { + "whatIs": { + "title": "Qu’est-ce que MyDevTools ?", + "description": "Un espace de travail développeur avec connexion : un tableau de bord et des outils pour le quotidien.", + "p1": "Après connexion, l’app établit une session avec l’API MyDevTools pour charger et enregistrer vos données. La barre latérale liste toutes les apps ; vous pouvez masquer celles que vous n’utilisez pas depuis", + "settingsLink": "Paramètres", + "p1After": ".", + "p2": "Certaines fonctionnalités utilisent un mot de passe maître (ou une clé de coffre globale) afin que des secrets comme les mots de passe et chaînes de connexion soient chiffrés sur votre appareil avant d’atteindre le serveur." + }, + "quickLinks": { + "title": "Liens rapides", + "dashboard": "Tableau de bord", + "settings": "Paramètres" + } + }, + "apps": { + "intro": "Ouvrez un outil depuis la barre latérale ou le tableau de bord. Voici un guide rapide par app.", + "open": "Ouvrir", + "howItWorks": "Fonctionnement", + "generic": "Utilisez cet outil depuis la barre latérale pour son flux de travail dédié." + }, + "security": { + "session": { + "title": "Connexion et session API", + "description": "Identité et communication entre le navigateur et votre backend.", + "items": { + "authentication": "Authentification via Firebase. Après connexion, l’app échange votre jeton d’ID Firebase contre des cookies API.", + "accessTokens": "Jetons d’accès : JWT signés côté serveur (HS256) qui prouvent quel utilisateur appelle l’API.", + "refreshTokens": "Jetons de rafraîchissement : stockés en cookies HttpOnly ; le serveur conserve un hash SHA-256 du jeton, pas le jeton brut." + } + }, + "encryption": { + "title": "Chiffrement à connaître", + "description": "Crypto côté client pour le coffre et les données de connexion sensibles.", + "p1": "Pour le gestionnaire de mots de passe, le gestionnaire d'environnement et les connexions enregistrées dans NoSQL Explorer, l'application web utilise la Web Crypto API du navigateur :", + "items": { + "kdf": "PBKDF2 avec SHA-256, 100 000 itérations et un sel aléatoire dérive une clé 256 bits depuis votre mot de passe maître.", + "aes": "AES-256-GCM chiffre les données ; un IV de 12 octets aléatoire est utilisé à chaque chiffrement.", + "scope": "La clé dérivée reste dans le navigateur (non extractible). Le serveur reçoit le texte chiffré et les IV, pas votre mot de passe maître ni des secrets en clair." + }, + "warning": "Si vous oubliez le mot de passe maître, les données chiffrées ne peuvent pas être récupérées. Conservez-le en lieu sûr." + }, + "logout": { + "title": "Déconnexion et données locales", + "body": "La déconnexion efface les secrets en mémoire, l’état du coffre et, si applicable, le matériel de clé maître dans IndexedDB, et termine la session API. Les données uniquement en stockage local peuvent rester jusqu’à suppression dans le navigateur." + } + }, + "tips": { + "title": "Utiliser MyDevTools en toute sécurité", + "items": { + "masterPassword": "Utilisez un mot de passe maître fort et unique pour les fonctions de coffre.", + "apiClient": "Traitez le Client API comme tout client HTTP : évitez de coller des secrets de production dans des environnements partagés sans savoir qui peut les voir.", + "nosql": "L’Explorateur de base de données se connecte à des bases que vous configurez ; utilisez des comptes au moindre privilège et des règles réseau si possible.", + "json": "Le formateur JSON fonctionne localement dans le navigateur—évitez quand même de coller des données très sensibles sur des machines partagées.", + "settings": "Personnalisez les outils visibles dans Paramètres pour garder la barre latérale focalisée." + } + } + }, + "Settings": { + "title": "Paramètres", + "language": "Langue", + "spanish": "Espagnol", + "english": "Anglais", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalan", + "chineseSimplified": "Chinois (simplifié)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Allemand", + "danish": "Danois", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukrainien" + }, + "SettingsPage": { + "title": "Paramètres", + "subtitle": "Gérez les paramètres et préférences de votre compte.", + "userProfile": { + "title": "Profil utilisateur", + "description": "Vos informations personnelles et détails du compte.", + "avatarAlt": "Utilisateur", + "anonymousUser": "Utilisateur anonyme", + "noEmailProvided": "Aucun e-mail fourni", + "notLoggedIn": "Non connecté." + }, + "tools": { + "title": "Outils et fonctionnalités", + "description": "Choisissez les outils affichés dans la barre latérale de navigation." + }, + "appearance": { + "title": "Apparence", + "description": "Personnalisez l’apparence de l’application sur votre appareil.", + "themePreference": "Thème", + "themes": { + "light": "Clair", + "dark": "Sombre", + "system": "Système" + } + }, + "language": { + "title": "Langue", + "description": "Définissez la langue utilisée sur la plateforme.", + "selectLanguage": "Choisir la langue", + "selectPlaceholder": "Sélectionner une langue", + "helpText": "Changer la langue met à jour les menus et libellés sur la plateforme.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spanish)", + "ar": "عربى (Arabic)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Greek)", + "de": "Deutsch (German)", + "da": "Dansk (Danish)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italien (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Bonjour", + "afternoon": "Bon après-midi", + "evening": "Bonsoir" + }, + "commaName": ", {name}", + "welcomeBack": "Bon retour", + "welcomeBackNamed": "Bon retour, {name}", + "tagline": "Que souhaitez-vous créer aujourd’hui ?", + "brandName": "MyDevTools", + "stats": { + "tools": "Outils", + "favorites": "Favoris", + "pinned": "Épinglés", + "recent": "Récemment utilisés" + }, + "sections": { + "recentlyUsed": "Récemment utilisés", + "favorites": "Favoris", + "whatsNew": "Nouveautés", + "pinned": "Épinglés" + }, + "groups": { + "apps": "Applications" + }, + "toolCard": { + "defaultDescription": "Explorez cet outil pour plus de fonctionnalités.", + "launchTool": "Lancer l’outil" + }, + "tools": { + "toDo": { + "title": "Tâches", + "description": "Gérez vos listes de tâches efficacement." + }, + "notes": { + "title": "Notes", + "description": "Capturez des idées et organisez vos pensées." + }, + "passwordManager": { + "title": "Gestionnaire de mots de passe", + "description": "Stockez et gérez vos mots de passe en toute sécurité." + }, + "environmentManager": { + "title": "Gestionnaire d'environnement", + "description": "Stockez des variables type .env par projet et environnement avec des étiquettes." + }, + "bookmarks": { + "title": "Signets", + "description": "Gérez et organisez vos signets." + }, + "apiClient": { + "title": "Client API", + "description": "Testez et déboguez les requêtes HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Explorez les bases de données MongoDB." + }, + "emailValidator": { + "title": "Validateur d’e-mail", + "description": "Vérifiez et validez les adresses e-mail." + }, + "encryptionPlayground": { + "title": "Bac à sable chiffrement", + "description": "AES-GCM avec clé brute ou phrase secrète ; paquets JSON, Web Crypto uniquement dans le navigateur." + }, + "jwtDecoder": { + "title": "Décodeur JWT", + "description": "Décoder l’en-tête, la charge utile et l’expiration des JWT." + }, + "certificatePemDecoder": { + "title": "Décodeur de certificat / PEM", + "description": "Décodez les certificats X.509 et les CSR : sujet, émetteur, validité et SAN." + }, + "sshKeyGenerator": { + "title": "Générateur de clés SSH / RSA", + "description": "Générez des paires de clés Ed25519 ou RSA. Téléchargez les clés en formats OpenSSH et PEM." + }, + "jsonFormatter": { + "title": "Formateur JSON", + "description": "Formater, valider et modifier des données JSON." + }, + "sqlFormatter": { + "title": "Formateur SQL", + "description": "Formater du SQL pour MySQL, PostgreSQL et SQLite." + }, + "graphqlFormatter": { + "title": "Formateur GraphQL", + "description": "Formatez du GraphQL avec coloration syntaxique et un éditeur de requêtes simple." + }, + "yamlFormatter": { + "title": "Formateur YAML", + "description": "Formatez, validez et convertissez du YAML en JSON dans le navigateur." + }, + "diffChecker": { + "title": "Comparateur de différences", + "description": "Différences ligne par ligne côte à côte entre deux textes." + }, + "regexTester": { + "title": "Testeur d’expressions régulières", + "description": "Tester des regex avec surlignage des correspondances en direct." + }, + "timestampConverter": { + "title": "Convertisseur d’horodatage", + "description": "Conversion entre Unix, ISO et temps relatif." + }, + "cronBuilder": { + "title": "Constructeur Cron", + "description": "Constructeur visuel Cron, préréglages et analyseur." + }, + "base64Encoder": { + "title": "Encodeur Base64", + "description": "Encoder et décoder des chaînes Base64." + }, + "urlEncoder": { + "title": "Encodeur d’URL", + "description": "Encoder et décoder en pourcentage les composants d’URL." + }, + "urlParser": { + "title": "Analyseur d’URL", + "description": "Décomposer une URL en protocole, hôte, chemin, paramètres de requête et fragment." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Générer des UUID et ULID avec export en masse." + }, + "secretApiKeyGenerator": { + "title": "Secret / clé API", + "description": "Chaînes aléatoires cryptographiques avec alphabet, longueur et export groupé configurables." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Générer du texte latin factice pour maquettes et mises en page." + }, + "colorPicker": { + "title": "Sélecteur de couleurs", + "description": "Convertir HEX, RVB, TSL et explorer des palettes." + }, + "contrastChecker": { + "title": "Vérificateur de contraste", + "description": "Ratio de contraste WCAG 2.1 entre texte et fond (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Générateur de schéma JSON", + "description": "Déduisez un schéma JSON et des modèles typés à partir d'un exemple JSON." + }, + "imageToBase64": { + "title": "Image vers Base64", + "description": "Convertissez les images en URI de données ou en chaînes Base64 brutes." + }, + "imageCompressor": { + "title": "Compresseur d’images", + "description": "Compressez JPEG, PNG et WebP localement avec un curseur de qualité." + }, + "cssGradientBuilder": { + "title": "Générateur de dégradé CSS", + "description": "Générateur de dégradé visuel CSS avec contrôle d'angle et arrêts de couleur." + }, + "gitignoreGenerator": { + "title": "Générateur .gitignore", + "description": "Générez instantanément des fichiers .gitignore fusionnés à partir de plus de 500 piles technologiques." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Convertir des fichiers CSV ou Excel en JSON et exporter vers CSV ou XLSX." + }, + "snippetManager": { + "title": "Extraits de code", + "description": "Enregistrez des extraits avec coloration syntaxique, détection automatique du langage et formatage." + }, + "qrCodeGenerator": { + "title": "Générateur de codes QR", + "description": "Créez des codes QR PNG à partir de texte ou d’URL dans le navigateur." + }, + "ipSubnetCalculator": { + "title": "Calculateur IP / sous-réseau", + "description": "IPv4 et IPv6 CIDR : masque, plages et nombre d’adresses." + }, + "dnsLookup": { + "title": "Recherche DNS", + "description": "Interrogez les enregistrements DNS d'un domaine — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Générateur de hash", + "description": "MD5, SHA-1 et SHA-2 pour texte ou fichiers." + }, + "hmacGenerator": { + "title": "Générateur HMAC", + "description": "Signer des webhooks et tester des intégrations API avec HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Générateur de code TOTP / 2FA", + "description": "Collez un secret Base32 d’authentificateur ; le code à six chiffres se met à jour toutes les 30 s pour tester la connexion et le MFA." + }, + "markdownPreview": { + "title": "Aperçu Markdown", + "description": "Rendu Markdown en direct avec export HTML et conversion HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Convertisseur de base numérique", + "description": "Convertissez des entiers entre les bases 2 à 36." + }, + "formatConverter": { + "title": "Convertisseur de formats", + "description": "Convertissez entre JSON, YAML, TOML et XML dans tous les sens. Les 12 combinaisons sont prises en charge." + }, + "mockDataGenerator": { + "title": "Générateur de données fictives", + "description": "Définissez des colonnes, choisissez des types et exportez des jeux de données pour les seeds, Postman ou les tests. Jusqu’à 5000 lignes, tout dans le navigat…" + }, + "httpStatusCodes": { + "title": "Codes de statut HTTP", + "description": "Recherchez des codes de statut HTTP avec des descriptions (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Optimiseur SVG", + "description": "Collez du SVG, supprimez le superflu avec SVGO et voyez le gain de taille UTF-8." + }, + "unitConverter": { + "title": "Convertisseur d'unités", + "description": "Convertissez entre 323 unités dans 43 catégories scientifiques et techniques." + }, + "dockerComposeGenerator": { + "title": "Générateur Docker Compose", + "description": "Choisissez bases de données, caches, files, proxys et observabilité ; exportez un docker-compose.yml pour le dev local." + }, + "game2048": { + "title": "2048", + "description": "Faites glisser et fusionnez les carreaux pour atteindre 2048. Jeu de puzzle à numéro classique." + }, + "sudoku": { + "title": "Patience", + "description": "Puzzle à numéro classique. 3 niveaux de difficulté, 150 niveaux chacun." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Parcourez les clés, inspectez les valeurs et exécutez des commandes sur n'importe quelle instance Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Générateur et vérificateur Bcrypt", + "description": "Hachez des mots de passe avec bcrypt et vérifiez des hachages existants." + }, + "chmodCalculator": { + "title": "Calculateur Chmod", + "description": "Convertissez les permissions Unix entre octal, symbolique et cases à cocher." + }, + "codeScreenshot": { + "title": "Capture de code", + "description": "Transformez votre code source en images élégantes et partageables." + }, + "cssGenerators": { + "title": "Générateurs CSS", + "description": "Générateurs de box-shadow, border-radius et clamp() fluide." + }, + "exifViewer": { + "title": "Visionneuse et nettoyeur EXIF", + "description": "Inspectez et supprimez les métadonnées EXIF des photos en local." + }, + "faviconGenerator": { + "title": "Générateur de favicon", + "description": "Générez des favicons, icônes d'application et extraits de manifeste." + }, + "jsonDiff": { + "title": "Diff JSON", + "description": "Comparaison sémantique tenant compte de la structure de deux documents JSON." + }, + "jsonpathPlayground": { + "title": "Playground JSONPath", + "description": "Interrogez du JSON avec JSONPath ou JMESPath et voyez les résultats en direct." + }, + "keycodeInspector": { + "title": "Inspecteur de keycode", + "description": "Appuyez sur une touche pour voir ses codes d'événement JavaScript et ses modificateurs." + }, + "markdownTableGenerator": { + "title": "Générateur de tableaux Markdown", + "description": "Créez des tableaux Markdown et HTML dans un éditeur en grille de type tableur." + }, + "timezoneConverter": { + "title": "Convertisseur de fuseaux horaires", + "description": "Convertissez une heure sur de nombreux fuseaux avec repères de décalage de jour et d'heures ouvrées." + }, + "tokenCounter": { + "title": "Compteur de tokens LLM", + "description": "Comptez les tokens LLM, visualisez les segments et estimez le coût de l'API." + }, + "webhookTester": { + "title": "Testeur de webhook", + "description": "Capturez et inspectez les requêtes webhook entrantes en temps réel." + }, + "websocketTester": { + "title": "Testeur WebSocket", + "description": "Connectez-vous à des points de terminaison WebSocket et échangez des messages en temps réel." + }, + "whoisLookup": { + "title": "Recherche Whois", + "description": "Consultez les détails d'enregistrement d'un domaine ou d'une IP via RDAP." + } + }, + "tabs": { + "apps": "Applications", + "analytics": "Analytique", + "ui": "Interface" + }, + "analytics": { + "title": "Vos données en un coup d’œil", + "subtitle": "Compteurs synchronisés avec votre compte sur le serveur.", + "loadError": "Impossible de charger les statistiques.", + "retry": "Réessayer", + "loginHint": "Connectez-vous pour voir les analyses d’utilisation et les préférences d’interface.", + "passwordEntries": "Entrées de mots de passe", + "bookmarks": "Signets", + "bookmarkFolders": "Dossiers de signets", + "tasksTotal": "Tâches", + "tasksCompleted": "Terminées", + "tasksOngoing": "En cours", + "tasksNotStarted": "Non démarrées", + "projects": "Projets", + "nosqlConnections": "Connexions BD", + "notes": "Notes", + "apiClientCollections": "Collections API", + "apiClientEnvironments": "Environnements API", + "apiClientHistory": "Entrées d’historique API", + "jsonFormatterDocuments": "Documents du formateur JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Extraits de code", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Préférences d’interface", + "subtitle": "Thème, langue et outils visibles dans la barre latérale — depuis les paramètres du compte.", + "loadError": "Impossible de charger les préférences.", + "loginHint": "Connectez-vous pour voir vos préférences d’interface enregistrées.", + "theme": "Thème", + "accent": "Couleur d’accent", + "locale": "Langue", + "enabledTools": "Outils visibles", + "favoriteTools": "Outils favoris", + "toolsWithUsage": "Outils avec suivi d’utilisation" + }, + "signIn": "Se connecter", + "noResults": "Aucun outil ne correspond à votre recherche", + "noResultsHint": "Essayez un autre mot-clé ou effacez le filtre.", + "filterAll": "Tout", + "loginCta": { + "description": "Connectez-vous pour suivre vos outils récemment utilisés." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Chargement…", + "myTasksTitle": "Mes tâches", + "statsLine": "{total} tâches • {percent} % terminées", + "statsLineDesktop": "{total} au total • {percent} % terminées" + }, + "filters": { + "all": "Toutes", + "total": "Total", + "allProjects": "Tous les projets", + "export": "Exporter", + "kanban": "Kanban", + "list": "Liste", + "searchPlaceholder": "Rechercher des tâches…", + "searchAria": "Rechercher des tâches", + "clearSearchAria": "Effacer la recherche", + "foundTasks": "Trouvé {count} {count, plural, one {tâche} other {tâches}}", + "filterByAllAria": "Filtrer par toutes", + "filterByStatusAria": "Filtrer par {status}" + }, + "status": { + "not-started": { + "label": "Non commencé", + "description": "Tâches pas encore commencées" + }, + "ongoing": { + "label": "En cours", + "description": "Tâches en cours" + }, + "completed": { + "label": "Terminé", + "description": "Tâches terminées" + } + }, + "priorities": { + "high": "Haute", + "medium": "Moyenne", + "low": "Basse" + }, + "form": { + "addNewTaskLabel": "Ajouter une tâche", + "multilinePlaceholder": "Détails de la tâche… (Maj+Entrée pour une nouvelle ligne)", + "singlelinePlaceholder": "Que faut-il faire ? Entrée pour ajouter", + "taskInputAria": "Saisie de tâche", + "projectPlaceholder": "Projet", + "selectProject": "Choisir un projet", + "errorSelectProject": "Sélectionnez un projet ou créez-en un pour ajouter une tâche.", + "hintMultiline": "Maj+Entrée pour une nouvelle ligne, Entrée pour ajouter", + "hintSingleline": "Entrée pour ajouter", + "shortcutsAria": "Raccourcis clavier", + "shortcutsLabel": "Raccourcis", + "shortcutAddTask": "Ajouter une tâche", + "shortcutNewLine": "Nouvelle ligne", + "shortcutClear": "Effacer", + "addButton": "Ajouter" + }, + "list": { + "ariaLabel": "Liste des tâches", + "loading": "Chargement de vos tâches…", + "emptyTitle": "Aucune tâche", + "emptyPrimary": "Ajoutez votre première tâche ci-dessus ! Entrée pour ajouter rapidement.", + "emptySecondary": "Modifiez les filtres ou la recherche." + }, + "mobileDrawer": { + "addTaskSrOnly": "Ajouter une tâche", + "addNewTaskTitle": "Nouvelle tâche", + "addNewTaskDescription": "Créez une tâche pour suivre votre avancement.", + "cancel": "Annuler" + }, + "kanban": { + "ariaLabel": "Tableau Kanban", + "loading": "Chargement de vos tâches…", + "dropHere": "Déposer la tâche ici", + "noTasksYet": "Pas encore de tâches", + "emptyColumnAria": "Colonne vide", + "columnAria": "Colonne {title} avec {count, plural, one {# tâche} other {# tâches}}", + "countInColumnAria": "{count, plural, one {# tâche} other {# tâches}} dans {title}" + }, + "taskItem": { + "confirmDelete": "Supprimer cette tâche ?", + "statusPlaceholder": "Statut", + "changeStatusAria": "Changer le statut de la tâche", + "taskOptionsAria": "Options de la tâche", + "editTask": "Modifier la tâche", + "copyTask": "Copier la tâche", + "copied": "Copié !", + "deleteTask": "Supprimer la tâche", + "statusHeading": "Statut", + "editTaskAria": "Modifier la tâche" + }, + "editDialog": { + "title": "Modifier la tâche", + "description": "Modifiez les détails, sous-tâches, priorités, etc.", + "taskTitleLabel": "Titre de la tâche *", + "taskTitlePlaceholder": "Que faut-il faire ?", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Plus de détails sur cette tâche…", + "statusLabel": "Statut", + "priorityLabel": "Priorité", + "dueDateLabel": "Échéance", + "pickDate": "Choisir une date", + "clearDate": "Effacer la date", + "projectLabel": "Projet", + "selectProjectPlaceholder": "Choisir un projet", + "noProject": "Aucun projet", + "timeEstimateLabel": "Estimation (minutes)", + "timeEstimatePlaceholder": "Durée prévue ?", + "tagsLabel": "Étiquettes", + "customTagNamePlaceholder": "Nom d’étiquette", + "subtasksLabel": "Sous-tâches", + "addSubtaskPlaceholder": "Ajouter une sous-tâche…", + "cancel": "Annuler", + "saving": "Enregistrement…", + "saveChanges": "Enregistrer" + }, + "export": { + "title": "Exporter les tâches", + "description": "Exportez vos tâches en JSON, CSV ou Excel.", + "projectLabel": "Projet", + "statusLabel": "Statut", + "tagLabel": "Étiquette", + "allProjects": "Tous les projets", + "allStatuses": "Tous les statuts", + "allTags": "Toutes les étiquettes", + "exportAsJsonTitle": "Exporter en JSON", + "exportAsJsonDescription": "Données des tâches avec sous-tâches, étiquettes et métadonnées.", + "exportJson": "Exporter JSON", + "exportAsCsvExcelTitle": "Exporter en CSV / Excel", + "exportAsCsvExcelDescription": "Export pour tableurs.", + "exportCsv": "Exporter CSV", + "exportExcel": "Exporter Excel", + "close": "Fermer", + "toastNoTasks": "Aucune tâche à exporter avec ces filtres", + "toastExportedJson": "{count, plural, one {# tâche exportée} other {# tâches exportées}} en JSON !", + "toastExportedCsv": "{count, plural, one {# tâche exportée} other {# tâches exportées}} en CSV !", + "toastExportedExcel": "{count, plural, one {# tâche exportée} other {# tâches exportées}} en Excel !", + "toastFailedJson": "Échec de l’export JSON", + "toastFailedCsv": "Échec de l’export CSV", + "toastFailedExcel": "Échec de l’export Excel", + "unknownProject": "Projet inconnu" + }, + "ack": { + "taskAddedTitle": "Tâche ajoutée", + "taskAddedFailedTitle": "Impossible d’ajouter la tâche", + "taskUpdatedTitle": "Tâche mise à jour", + "taskUpdatedFailedTitle": "Impossible de mettre à jour la tâche", + "taskStatusUpdateFailedTitle": "Impossible de mettre à jour le statut", + "taskMovedTitle": "Tâche déplacée vers {status}", + "tryAgain": "Veuillez réessayer.", + "taskDeletedTitle": "Tâche « {text} » supprimée", + "undo": "Annuler", + "taskRestoredTitle": "Tâche restaurée", + "taskRestoredSuccessTitle": "Tâche restaurée", + "taskRestoreFailedTitle": "Impossible de restaurer la tâche. Réessayez.", + "taskDeleteFailedTitle": "Impossible de supprimer la tâche. Réessayez.", + "tasksImportedTitle": "{count, plural, one {# tâche importée} other {# tâches importées}} !", + "tasksImportFailedTitle": "Échec de l’import des tâches. Réessayez.", + "unknownProject": "Projet inconnu", + "projectCreatedTitle": "Projet créé", + "projectCreateFailedTitle": "Impossible de créer le projet", + "projectUpdatedTitle": "Projet mis à jour", + "projectUpdateFailedTitle": "Impossible de mettre à jour le projet", + "projectDeletedTitle": "Projet supprimé", + "projectDeleteFailedTitle": "Impossible de supprimer le projet" + } + }, + "Notes": { + "page": { + "loading": "Chargement…" + }, + "layout": { + "mobileNavTitle": "Navigation des notes", + "openMenuAria": "Ouvrir le menu des notes" + }, + "sidebar": { + "title": "Notes", + "createNoteAria": "Créer une note", + "searchPlaceholder": "Rechercher des notes…", + "loading": "Chargement…", + "noNotesFound": "Aucune note trouvée.", + "noNotesYet": "Pas encore de notes. Cliquez sur + pour en créer une.", + "inParent": "dans {parentTitle}", + "untitled": "Sans titre", + "addSubPage": "Ajouter une sous-page", + "delete": "Supprimer", + "deleteConfirmTitle": "Supprimer la note ?", + "deleteConfirmDescription": "Supprimer « {title} » ? Cette action est irréversible.", + "cancel": "Annuler", + "confirmDelete": "Supprimer", + "noteOptionsAria": "Options de la note" + }, + "editor": { + "emptyState": "Sélectionnez une note ou créez-en une pour commencer.", + "titlePlaceholder": "Sans titre", + "saving": "Enregistrement…", + "ready": "Prêt", + "saved": "Enregistré {time}", + "stats": "{words} mots · {read} min de lecture", + "template": "Modèle", + "applyTemplate": "Appliquer le modèle", + "exportMdLabel": ".md", + "exportMd": "Exporter en Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Exporter en HTML", + "focusMode": "Mode concentration", + "exitFocusMode": "Quitter le mode concentration", + "save": "Enregistrer", + "addTag": "Ajouter une étiquette…", + "removeTag": "Supprimer l'étiquette {tag}", + "loading": "Chargement…", + "searchHint": "pour rechercher", + "focusHint": "mode concentration", + "notePath": "Chemin de la note" + }, + "context": { + "authRequiredError": "Utilisateur non authentifié", + "defaultTitle": "Sans titre", + "cloudSyncImageError": "Activez la synchronisation cloud pour stocker les images des notes. Les notes texte restent locales." + } + }, + "PasswordManager": { + "page": { + "loading": "Chargement…", + "title": "Gestionnaire de mots de passe", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Impossible de déchiffrer les mots de passe", + "toastLoadFailed": "Impossible de charger les mots de passe", + "toastUnlockedAuto": "Coffre déverrouillé automatiquement", + "toastAutoUnlockFailed": "Déverrouillage auto échoué. Déverrouillez manuellement.", + "toastConnectFailed": "Connexion au serveur impossible", + "toastAutoUnlockWarning": "Coffre déverrouillé ; le déverrouillage auto peut échouer au rechargement", + "errorIncorrectPassword": "Mot de passe principal incorrect", + "errorUnlockFailed": "Impossible de déverrouiller le coffre", + "errorPasswordsMismatch": "Les mots de passe ne correspondent pas", + "errorPasswordTooShort": "Le mot de passe doit contenir au moins 8 caractères", + "errorSetupFailed": "Impossible de configurer le coffre", + "errorConnectFailed": "Connexion au serveur impossible.", + "mobileTitleCreate": "Créer votre coffre", + "mobileTitleWelcome": "Bon retour", + "mobileSubtitleCreate": "Définissez un mot de passe principal pour protéger vos identifiants", + "mobileSubtitleUnlock": "Saisissez votre mot de passe principal pour déverrouiller", + "placeholderMasterPassword": "Mot de passe principal", + "placeholderConfirmPassword": "Confirmer le mot de passe", + "labelMasterPassword": "Mot de passe principal", + "labelConfirmPassword": "Confirmer le mot de passe", + "placeholderEnterMaster": "Mot de passe principal", + "placeholderConfirmMaster": "Confirmer le mot de passe principal", + "buttonCreateVault": "Créer le coffre", + "buttonUnlock": "Déverrouiller", + "buttonUnlockVault": "Déverrouiller le coffre", + "footerCreate": "Votre mot de passe n’est jamais stocké et ne peut pas être récupéré", + "footerUnlock": "Chiffrement de bout en bout", + "desktopTitleSetup": "Configurer le coffre", + "desktopTitleUnlock": "Déverrouiller le coffre", + "desktopDescriptionSetup": "Créez un mot de passe principal pour sécuriser vos identifiants. Il n’est jamais stocké et ne peut pas être récupéré.", + "desktopDescriptionUnlock": "Saisissez votre mot de passe principal pour accéder à vos identifiants." + }, + "list": { + "loading": "Chargement de vos mots de passe…", + "emptyTitle": "Coffre vide", + "emptyHintMobile": "Appuyez sur + pour ajouter votre premier mot de passe", + "emptyHintDesktop": "Aucun mot de passe enregistré. Cliquez sur « Ajouter un mot de passe » ci-dessus.", + "addPassword": "Ajouter un mot de passe", + "searchPlaceholderMobile": "Rechercher dans le coffre…", + "searchPlaceholderDesktop": "Rechercher…", + "importExport": "Importer / exporter", + "lockVault": "Verrouiller le coffre", + "allPasswordsTitle": "Tous les mots de passe", + "passwordCount": "{count, plural, one {# mot de passe} other {# mots de passe}}", + "vaultHealthDrawerTitle": "Santé du coffre", + "noResults": "Aucun mot de passe pour « {query} »", + "tableService": "Service", + "tableUsername": "Nom d’utilisateur", + "tablePassword": "Mot de passe", + "tableUrl": "URL", + "tableActions": "Actions", + "gridViewAria": "Vue grille", + "listViewAria": "Vue liste", + "passwordStrengthTitle": "Robustesse du mot de passe", + "deleteDialogTitle": "Supprimer le mot de passe", + "deleteDialogDescription": "Supprimer ce mot de passe ? Cette action est irréversible.", + "cancel": "Annuler", + "delete": "Supprimer", + "edit": "Modifier" + }, + "toasts": { + "copiedPassword": "Mot de passe copié", + "copiedUsername": "Nom d’utilisateur copié", + "passwordDeleted": "Mot de passe supprimé", + "deleteFailed": "Impossible de supprimer le mot de passe", + "vaultLocked": "Coffre verrouillé", + "importedCount": "{count, plural, one {# mot de passe importé} other {# mots de passe importés}}" + }, + "form": { + "addTitle": "Nouveau mot de passe", + "editTitle": "Modifier le mot de passe", + "serviceName": "Nom du service", + "usernameEmail": "Nom d’utilisateur / e-mail", + "password": "Mot de passe", + "urlOptional": "URL du site (facultatif)", + "notesOptional": "Notes (facultatif)", + "tags": "Étiquettes", + "placeholderService": "ex. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Saisir ou générer un mot de passe", + "placeholderUrl": "https://…", + "placeholderNotes": "Détails supplémentaires…", + "placeholderTags": "Étiquettes (Entrée ou virgule)", + "strength": "Robustesse", + "strengthWeak": "Faible", + "strengthMedium": "Moyenne", + "strengthStrong": "Forte", + "advancedGenerator": "Générateur avancé", + "cancel": "Annuler", + "saving": "Enregistrement…", + "savePassword": "Enregistrer", + "updating": "Mise à jour…", + "updatePassword": "Mettre à jour", + "addPasswordSrOnly": "Ajouter un mot de passe", + "triggerAdd": "Ajouter un mot de passe", + "toastSaved": "Mot de passe enregistré", + "toastSaveFailed": "Impossible d’enregistrer", + "toastUpdated": "Mot de passe mis à jour", + "toastUpdateFailed": "Impossible de mettre à jour" + }, + "security": { + "vaultHealth": "Santé du coffre", + "statusLabel": "Statut", + "totalPasswords": "Total des mots de passe", + "activeEntries": "Entrées actives dans votre coffre", + "protected": "Protégé", + "weakPasswords": "Mots de passe faibles", + "needingImprovement": "À renforcer", + "allGood": "Tout va bien", + "reusedPasswords": "Mots de passe réutilisés", + "usedMultipleTimes": "Utilisés plusieurs fois", + "unique": "Uniques", + "total": "Total", + "weak": "Faibles", + "reused": "Réutilisés", + "totalCountLabel": "{count, plural, one {# mot de passe} other {# mots de passe}}", + "weakCountLabel": "{count} faibles", + "allSecure": "Tout est sécurisé", + "scoreExcellent": "Excellent", + "scoreGood": "Bon", + "scoreFair": "Moyen", + "scoreCritical": "Critique" + }, + "importExport": { + "title": "Importer / exporter le coffre", + "description": "Gérez vos données. Export JSON ou import depuis un fichier JSON.", + "tabExport": "Exporter", + "tabImport": "Importer", + "warningTitle": "Attention", + "warningBody": "Les fichiers exportés contiennent vos mots de passe en clair. Protégez le fichier et supprimez-le après usage.", + "exportBlurb": "Téléchargez tout le coffre en JSON (services, identifiants, mots de passe, notes).", + "downloadJson": "Télécharger l’export JSON", + "pasteLabel": "Coller les données JSON", + "pastePlaceholder": "[{\"service\": \"Exemple\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Collez ici le contenu d’un export JSON précédent.", + "importing": "Import…", + "importPasswords": "Importer", + "trigger": "Importer / exporter", + "toastExported": "Coffre exporté", + "toastExportFailed": "Échec de l’export", + "toastImportFailed": "Import impossible : JSON invalide" + }, + "card": { + "copyUsername": "Copier le nom d’utilisateur", + "openUrl": "Ouvrir l’URL", + "strength": "Robustesse" + }, + "swipeable": { + "copy": "Copier", + "delete": "Supprimer", + "copyPassword": "Copier le mot de passe", + "copyUsername": "Copier le nom d’utilisateur", + "edit": "Modifier", + "deleteAction": "Supprimer" + }, + "floatingMenu": { + "lockVault": "Verrouiller le coffre", + "importExport": "Importer / exporter", + "addPassword": "Ajouter un mot de passe" + }, + "generator": { + "tabPassword": "Mot de passe", + "tabPassphrase": "Phrase secrète", + "strength": "Robustesse", + "lengthLabel": "Longueur : {length} caractères", + "lengthBadge": "{length} car.", + "characterSets": "Jeux de caractères", + "uppercase": "Majuscules", + "lowercase": "Minuscules", + "numbers": "Chiffres", + "symbols": "Symboles", + "wordCountLabel": "Nombre de mots : {count}", + "wordCountBadge": "{count, plural, one {# mot} other {# mots}}", + "separator": "Séparateur", + "capitalization": "Casse", + "includeNumber": "Inclure un chiffre" + } + }, + "EnvironmentManager": { + "subtitle": "Variables d'environnement chiffrées par projet et environnement.", + "page": { + "loading": "Chargement…", + "title": "Gestionnaire d'environnement", + "loadFailed": "Impossible de charger les jeux d'environnement" + }, + "list": { + "title": "Environnements", + "loading": "Chargement de vos jeux…", + "emptyTitle": "Aucun jeu d'environnement pour l'instant", + "emptyHint": "Créez un jeu pour chaque projet et environnement (ex. clés API de production). Tout est chiffré dans votre navigateur avant l'envoi.", + "searchPlaceholder": "Rechercher projet, environnement, étiquettes, notes…", + "tagFilterPlaceholder": "Filtrer par étiquette", + "variableCount": "{count, plural, one {# variable} other {# variables}}", + "updatedAgo": "Mis à jour {time}", + "copyDotEnv": "Copier .env", + "edit": "Modifier", + "delete": "Supprimer", + "cancel": "Annuler", + "confirmDelete": "Supprimer", + "deleteDialogTitle": "Supprimer le jeu d'environnement ?", + "deleteDialogDescription": "Cela supprime le blob chiffré de votre compte. Action irréversible.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env copié dans le presse-papiers", + "deleted": "Jeu d'environnement supprimé", + "deleteFailed": "Échec de la suppression" + }, + "form": { + "addTitle": "Nouveau jeu d'environnement", + "editTitle": "Modifier le jeu d'environnement", + "triggerAdd": "Ajouter un jeu d'environnement", + "addSrOnly": "Ajouter un jeu d'environnement", + "project": "Projet", + "environment": "Environnement", + "placeholderProject": "ex. billing-api", + "placeholderEnvironment": "ex. production", + "pasteEnvLabel": "Coller un .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Fusionner les lignes collées", + "variables": "Variables", + "addRow": "Ajouter une ligne", + "keyPlaceholder": "CLÉ", + "valuePlaceholder": "valeur", + "notesOptional": "Notes (facultatif)", + "placeholderNotes": "Politique de rotation, propriétaire, liens…", + "tags": "Étiquettes", + "placeholderTags": "Ajouter des étiquettes (Entrée ou virgule)", + "cancel": "Annuler", + "save": "Enregistrer", + "saving": "Enregistrement…", + "update": "Mettre à jour", + "updating": "Mise à jour…", + "toastSaved": "Jeu d'environnement enregistré", + "toastSaveFailed": "Échec de l'enregistrement", + "toastUpdated": "Jeu d'environnement mis à jour", + "toastUpdateFailed": "Échec de la mise à jour", + "toastPasteEmpty": "Aucune ligne CLÉ=valeur trouvée", + "toastPasteApplied": "{count} variables fusionnées depuis le collage", + "toastProjectEnvRequired": "Le projet et l'environnement sont obligatoires", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Chargement…" + }, + "manager": { + "foldersHeading": "Dossiers", + "tagsHeading": "Étiquettes", + "tagsMore": "+{count} de plus", + "tagsShowLess": "Show less", + "searchPlaceholder": "Rechercher des signets…", + "import": "Importer", + "export": "Exporter", + "exportAsHtml": "Exporter en HTML", + "exportAsJson": "Exporter en JSON", + "addBookmark": "Ajouter un signet", + "matchingQuery": "correspondant à « {query} »", + "bookmarkCount": "{count, plural, one {# signet} other {# signets}}", + "allBookmarks": "Tous les signets", + "uncategorized": "Sans catégorie", + "unknownFolder": "Inconnu" + }, + "card": { + "urlCopied": "URL copiée", + "bookmarkDeleted": "Signet supprimé", + "edit": "Modifier", + "delete": "Supprimer", + "copyUrl": "Copier l’URL", + "deleteConfirmTitle": "Supprimer le signet ?", + "deleteConfirmDescription": "Suppression définitive de « {title} ».", + "cancel": "Annuler" + }, + "grid": { + "emptyTitle": "Aucun signet", + "emptyDescription": "Ajoutez un signet ou importez depuis votre navigateur." + }, + "folderTree": { + "allBookmarks": "Tous les signets", + "addSubfolder": "Ajouter un sous-dossier", + "rename": "Renommer", + "deleteFolderTitle": "Supprimer le dossier « {name} » ?", + "deleteFolderDescription": "Le dossier et ses sous-dossiers seront supprimés. Les signets iront dans Sans catégorie." + }, + "import": { + "title": "Importer des signets", + "description": "Importez depuis un fichier exporté par votre navigateur.", + "parseError": "Fichier de signets invalide.", + "successToast": "{bookmarks} signets et {folders} dossiers importés", + "errorToast": "Échec de l’import", + "chromeInstruction": "Menu → Signets → Gestionnaire de signets → ⋮ → Exporter les signets", + "firefoxInstruction": "Menu → Signets → Gérer les signets → Importer et sauvegarder → Exporter", + "safariInstruction": "Fichier → Exporter les signets…", + "edgeInstruction": "Menu → Favoris → ⋯ → Exporter les favoris", + "dropZoneTitle": "Déposez votre fichier ici", + "dropZoneHint": "ou cliquez pour parcourir (HTML ou JSON)", + "selectedFileHint": "Cliquez pour choisir un autre fichier", + "errorTitle": "Erreur", + "previewTitle": "Aperçu de l’import", + "previewBookmarks": "{count, plural, one {# signet} other {# signets}}", + "previewFolders": "{count, plural, one {# dossier} other {# dossiers}}", + "importModeLabel": "Mode d’import", + "modeMerge": "Fusionner avec les signets existants", + "modeReplace": "Remplacer tous les signets", + "cancel": "Annuler", + "importButton": "Importer", + "importButtonWithCount": "Importer {count, plural, one {# signet} other {# signets}}" + }, + "addBookmark": { + "titleAdd": "Ajouter un signet", + "titleEdit": "Modifier le signet", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "URL invalide", + "titleLabel": "Titre", + "titlePlaceholder": "Titre du signet", + "titleRequired": "Saisissez un titre", + "descriptionLabel": "Description (facultatif)", + "descriptionPlaceholder": "Note sur ce signet", + "folderLabel": "Dossier", + "folderPlaceholder": "Choisir un dossier", + "noFolder": "Aucun dossier", + "tagsLabel": "Étiquettes", + "tagsPlaceholder": "Étiquettes (Entrée)", + "toastUpdated": "Signet mis à jour", + "toastAdded": "Signet ajouté", + "cancel": "Annuler", + "submitAdd": "Ajouter", + "submitUpdate": "Mettre à jour" + }, + "addFolder": { + "title": "Créer un dossier", + "nameLabel": "Nom du dossier", + "namePlaceholder": "Mon dossier", + "nameRequired": "Saisissez un nom", + "parentLabel": "Dossier parent", + "parentPlaceholder": "Choisir le parent", + "rootParent": "Racine (aucun parent)", + "toastCreated": "Dossier créé", + "cancel": "Annuler", + "submit": "Créer le dossier" + }, + "editFolder": { + "title": "Renommer le dossier", + "nameLabel": "Nom du dossier", + "namePlaceholder": "Nom du dossier", + "nameRequired": "Saisissez un nom", + "toastRenamed": "Dossier renommé", + "cancel": "Annuler", + "save": "Enregistrer" + } + }, + "EmailValidator": { + "header": { + "title": "Validateur d’e-mail", + "description": "Vérifiez la syntaxe, le domaine et la délivrabilité des adresses e-mail." + }, + "tabs": { + "single": "Vérification unitaire", + "bulk": "Import groupé" + }, + "single": { + "cardTitle": "Vérification d’une adresse", + "cardDescription": "Saisissez une adresse pour vérifier délivrabilité et réputation.", + "placeholder": "Adresse e-mail…", + "clearAria": "Effacer", + "validate": "Valider", + "validating": "Validation…", + "formatError": "Adresse e-mail invalide" + }, + "toasts": { + "errorTitle": "Erreur", + "enterEmail": "Saisissez une adresse e-mail", + "invalidFormatTitle": "Format invalide", + "invalidFormatDescription": "Saisissez une adresse e-mail valide", + "validateFailedDescription": "Erreur lors de la validation", + "noEmailsTitle": "Aucun e-mail", + "noEmailsDescription": "Votre fichier Excel doit contenir une colonne « email ».", + "fileTooLargeTitle": "Fichier trop volumineux", + "fileTooLargeDescription": "{total} adresses trouvées. Seules les 4000 premières seront validées.", + "successTitle": "Succès", + "successValidated": "{count} adresses validées.", + "batchFailedTitle": "Échec du lot", + "batchFailedDescription": "Erreur lors de la validation groupée.", + "noExportTitle": "Rien à exporter", + "noExportValidOnly": "Aucune adresse valide à exporter.", + "noExportAll": "Aucun résultat à exporter.", + "exportSuccessTitle": "Export réussi", + "exportSuccessExcelAll": "{count} adresses exportées vers Excel.", + "exportSuccessExcelValid": "{count} adresses valides exportées vers Excel.", + "exportSuccessCsvAll": "{count} adresses exportées vers CSV.", + "exportSuccessCsvValid": "{count} adresses valides exportées vers CSV." + }, + "disposableAlert": { + "title": "E-mail jetable détecté", + "description": "Cette adresse provient d’un fournisseur jetable. Elle peut être temporaire ou risquée." + }, + "score": { + "reliabilityTitle": "Score de fiabilité", + "scoreLabel": "Score" + }, + "details": { + "sectionTitle": "Détails de validation" + }, + "validation": { + "syntaxLabel": "Syntaxe valide", + "syntaxTooltip": "Vérifie le format (ex. user@domaine.com)", + "domainLabel": "Domaine existant", + "domainTooltip": "Vérifie que le domaine existe", + "mxLabel": "Enregistrements MX", + "mxTooltip": "Vérifie la présence d’enregistrements MX pour recevoir les e-mails", + "mailboxLabel": "Boîte existante", + "mailboxTooltip": "Vérifie que la boîte existe sur le serveur", + "disposableLabel": "E-mail jetable", + "disposableTooltip": "Indique un service d’e-mail temporaire (risqué)", + "roleBasedLabel": "E-mail de rôle", + "roleBasedTooltip": "Identifie une adresse générique (support@, info@) plutôt qu’une adresse personnelle" + }, + "copy": { + "emailCopied": "E-mail copié !", + "copyTooltip": "Copier l’e-mail" + }, + "bulk": { + "dropHere": "Déposez votre fichier ici", + "dragHere": "Glissez-déposez votre fichier ici", + "uploadHint": "Fichier .xlsx, .xls ou .csv avec une colonne « email », jusqu’à 4000 adresses.", + "filePrefix": "Fichier :", + "selectFile": "Choisir un fichier", + "downloadTemplate": "Télécharger le modèle" + }, + "bulkLoading": { + "title": "Validation des e-mails…", + "batchProgress": "Lot {current} sur {total}…" + }, + "stats": { + "totalProcessed": "Total traité", + "valid": "Valides", + "risky": "À risque", + "invalid": "Invalides" + }, + "results": { + "detailedTitle": "Résultats détaillés", + "searchPlaceholder": "Rechercher…", + "newVerification": "Nouvelle vérification", + "exportAll": "Tout exporter", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Exporter les valides ({count})" + }, + "showingResults": "{shown} résultat(s) sur {total}", + "table": { + "email": "E-mail", + "status": "Statut", + "score": "Score", + "checks": "Vérifications" + }, + "noSearchResults": "Aucun résultat pour « {query} »", + "checksTooltip": { + "syntax": "Syntaxe", + "domain": "Domaine", + "mx": "MX", + "mailbox": "Boîte" + }, + "exportColumns": { + "email": "E-mail", + "status": "Statut", + "score": "Score", + "syntax": "Syntaxe", + "domain": "Domaine", + "mx": "MX", + "mailbox": "Boîte", + "disposable": "Jetable", + "roleBased": "Rôle", + "valid": "Valide", + "invalid": "Invalide", + "yes": "Oui", + "no": "Non" + }, + "sheets": { + "validEmails": "E-mails valides", + "results": "Résultats", + "template": "Modèle" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "Explorateur NoSQL", + "heroSubtitle": "Connectez-vous à vos bases pour gérer les collections, exécuter des requêtes et visualiser les données.", + "featureMultiTitle": "Multi-connexion", + "featureMultiDesc": "Gérez plusieurs connexions simultanément.", + "featureMongoTitle": "Prise en charge MongoDB", + "featureMongoDesc": "Support MongoDB natif avec filtrage avancé.", + "featureQueryTitle": "Requête intelligente", + "featureQueryDesc": "Constructeur de requêtes avec coloration syntaxique.", + "connectCta": "Se connecter à MongoDB", + "footerNote": "Prend en charge {db} pour l’instant. D’autres bases suivront.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Choisir une collection", + "selectCollectionDesc": "Sélectionnez une collection dans la barre latérale pour afficher les documents.", + "dialogAddConnection": "Ajouter une connexion", + "toastConnectionAdded": "Connexion ajoutée", + "toastFetchFailed": "Impossible de charger les documents pour {collection}", + "toastDocDeleted": "Document supprimé", + "deleteDocTitle": "Confirmer la suppression ?", + "deleteDocDescription": "Action irréversible : le document sera supprimé de la collection.", + "closeAllTitle": "Fermer tous les onglets ?", + "closeAllDescription": "Les filtres de requête non enregistrés seront perdus.", + "closeAllConfirm": "Tout fermer", + "cancel": "Annuler", + "delete": "Supprimer" + }, + "connection": { + "titleEdit": "Modifier la connexion", + "titleConnect": "Connexion à MongoDB", + "subtitleEdit": "Mettez à jour les détails ci-dessous.", + "subtitleConnect": "Saisissez la chaîne de connexion MongoDB pour explorer vos données.", + "labelName": "Nom", + "placeholderName": "ex. Prod", + "labelConnectionString": "Chaîne de connexion", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format : mongodb://utilisateur:motdepasse@hôte:port/base", + "testConnection": "Tester la connexion", + "connecting": "Connexion…", + "updateConnect": "Mettre à jour et connecter", + "connect": "Connecter", + "cancelEdit": "Annuler", + "savedConnections": "Connexions enregistrées", + "emptySaved": "Aucune connexion enregistrée.", + "lastUsed": "Dernière utilisation {time}", + "toastTestOk": "Connexion réussie !", + "toastTestFail": "Échec : {message}", + "toastUpdated": "Connexion mise à jour", + "confirmDelete": "Supprimer cette connexion enregistrée ?", + "toastDeletedConn": "Connexion supprimée", + "toastDeleteFail": "Impossible de supprimer la connexion", + "copyStringTitle": "Copier la chaîne de connexion", + "toastStringCopied": "Chaîne copiée !", + "editTitle": "Modifier", + "deleteTitle": "Supprimer" + }, + "sidebar": { + "explorer": "Explorateur", + "add": "Ajouter", + "tooltipAdd": "Nouvelle connexion", + "tooltipRefresh": "Actualiser les connexions", + "searchPlaceholder": "Rechercher…", + "loadingConnections": "Chargement des connexions…", + "noConnectionsFound": "Aucune connexion", + "toastLoadFail": "Impossible de charger les connexions", + "toastConnectFail": "Connexion impossible à {name}", + "toastCollectionsFail": "Impossible de charger les collections pour {name}", + "toastRenamed": "Connexion renommée", + "toastRenameFail": "Impossible de renommer", + "confirmDeleteConnection": "Supprimer la connexion « {name} » ?", + "toastDeleted": "Connexion supprimée", + "toastDeleteConnFail": "Impossible de supprimer la connexion", + "confirmDropDb": "Supprimer la base « {name} » ? Action irréversible.", + "toastDbDropped": "Base {name} supprimée", + "confirmDropCollection": "Supprimer la collection « {name} » ? Action irréversible.", + "toastCollectionDropped": "Collection {name} supprimée", + "toastCollectionRenamed": "Collection renommée en {name}", + "toastDatabaseRenamed": "Base renommée en {name}", + "connecting": "Connexion…", + "connectionFailed": "Échec de la connexion", + "noDatabases": "Aucune base", + "loading": "Chargement…", + "noCollections": "Aucune collection", + "renameCollectionTitle": "Renommer la collection", + "renameDatabaseTitle": "Renommer la base", + "placeholderNewCollection": "Nouveau nom de collection", + "placeholderNewDatabase": "Nouveau nom de base", + "rename": "Renommer", + "refresh": "Actualiser", + "copyDbName": "Copier le nom de la base", + "toastDbNameCopied": "Nom de base copié", + "dropDatabase": "Supprimer la base", + "dropCollection": "Supprimer la collection", + "menuRename": "Renommer", + "menuCopyConnectionString": "Copier la chaîne de connexion", + "menuDeleteConnection": "Supprimer la connexion", + "toastSidebarStringCopied": "Chaîne copiée !", + "cancel": "Annuler", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} docs", + "table": "Tableau", + "json": "JSON", + "tree": "Arbre", + "tooltipTable": "Vue tableau", + "tooltipJson": "Vue JSON", + "tooltipTree": "Vue arborescente", + "itemsPerPage": "Éléments par page", + "perPage": "{n} / page", + "prevPage": "Page précédente", + "nextPage": "Page suivante", + "expandAll": "Tout développer", + "collapseAll": "Tout réduire", + "refreshData": "Actualiser", + "insert": "Insérer", + "export": "Exporter", + "actions": "Actions", + "invalidJson": "JSON invalide", + "copiedClipboard": "Copié dans le presse-papiers", + "docUpdated": "Document mis à jour", + "docInserted": "Document inséré", + "invalidJsonShort": "JSON invalide", + "docCopied": "Document copié", + "openFullEditor": "Ouvrir dans l’éditeur complet", + "cellArray": "Tableau({length})", + "cellObject": "Objet", + "cellArrayBracket": "Tableau[{length}]", + "emptyFilterTitle": "Aucun document ne correspond", + "emptyFilterHint": "Modifiez ou effacez le filtre.", + "clearFilter": "Effacer le filtre", + "emptyCollectionTitle": "Collection vide", + "emptyCollectionHint": "Insérez un document pour commencer.", + "insertDocument": "Insérer un document", + "formatJson": "Formater le JSON", + "compactJson": "Compacter le JSON", + "copyClipboard": "Copier", + "copyJson": "Copier le JSON", + "duplicate": "Dupliquer", + "editDoc": "Modifier le document", + "deleteDoc": "Supprimer le document", + "documentLabel": "Document {n}", + "editDialogTitle": "Modifier le document", + "insertDialogTitle": "Insérer un document", + "viewValueTitle": "Voir la valeur", + "saveChanges": "Enregistrer", + "close": "Fermer", + "cancel": "Annuler", + "insertButton": "Insérer", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Filtre actif", + "filterActiveTooltip": "Filtre actif", + "closeAll": "Tout fermer" + }, + "query": { + "migrateHistory": "Historique des requêtes migré vers le cloud", + "saveHistoryFail": "Impossible d’enregistrer l’historique", + "deleteQueryFail": "Impossible de supprimer la requête", + "invalidJsonQuery": "Requête JSON invalide", + "placeholder": "ex. { \"status\": \"active\" }", + "clearQuery": "Effacer la requête", + "openAdvanced": "Éditeur avancé", + "visualBuilder": "Constructeur visuel", + "filterRules": "Règles de filtre", + "addRule": "Ajouter une règle", + "noFilters": "Aucun filtre. Ajoutez une règle.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "Champ", + "opPlaceholder": "Op", + "typePlaceholder": "Type", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Not In", + "opExists": "Exists", + "typeAuto": "Auto", + "typeString": "Chaîne", + "typeNumber": "Nombre", + "typeBoolean": "Booléen", + "typeDate": "Date", + "typeObjectId": "ObjectId", + "true": "Vrai", + "false": "Faux", + "valuePlaceholder": "Valeur", + "valueListPlaceholder": "valeur1, valeur2", + "clearFilters": "Effacer les filtres", + "applyFilters": "Appliquer", + "queryHistory": "Historique", + "noSavedQueries": "Aucune requête enregistrée", + "advancedEditorTitle": "Éditeur de requête avancé", + "templatesPlaceholder": "Modèles", + "templateEmptyAgg": "Agrégation vide", + "templateMatch": "$match (filtre)", + "templateLookup": "$lookup (jointure)", + "templateGroup": "$group (compte/somme)", + "format": "Formater", + "formattedJson": "JSON formaté", + "runQuery": "Exécuter" + }, + "export": { + "selectFieldsError": "Sélectionnez au moins un champ à exporter", + "success": "Export réussi : {filename}", + "fail": "Échec de l’export", + "title": "Exporter les données", + "formatLabel": "Format", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Champs", + "selectAll": "Tout sélectionner", + "fieldsSelected": "{count, plural, one {# champ sélectionné} other {# champs sélectionnés}}", + "cancel": "Annuler", + "export": "Exporter", + "sheetName": "Données" + }, + "jsonTree": { + "typeLabel": "Type : {type}", + "itemSummary": "{count, plural, one {# élément} other {# éléments}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Nouvelle requête", + "importedRequest": "Requête importée", + "untitledRequest": "Requête sans titre" + }, + "toasts": { + "curlImported": "cURL importé", + "curlParseFailed": "Impossible d’analyser la commande cURL", + "invalidJsonBody": "Corps JSON invalide", + "requestFailed": "Échec de la requête : {message}", + "curlPasted": "cURL collé et analysé", + "responseCopied": "Réponse copiée", + "codeCopied": "Code copié", + "copyFailed": "Impossible de copier dans le presse-papiers" + }, + "layout": { + "collections": "Collections", + "closeSidebar": "Fermer la barre latérale", + "openSidebar": "Ouvrir la barre latérale", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Méthode", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Envoi…", + "send": "Envoyer", + "invalidJsonBodyHelp": "Envoi impossible : le corps JSON est invalide" + }, + "requestTabs": { + "params": "Paramètres", + "headers": "En-têtes", + "body": "Corps", + "auth": "Auth", + "bodyType": "Type de corps", + "selectType": "Choisir le type", + "bodyNone": "Aucun", + "bodyJson": "JSON", + "bodyText": "Texte", + "noBodyHint": "Aucun corps pour cette requête", + "switchToJson": "Passer au corps JSON", + "authType": "Type d’auth", + "noAuth": "Sans auth", + "bearerToken": "Jeton Bearer", + "basicAuth": "Auth basique", + "apiKey": "Clé API", + "labelToken": "Jeton", + "placeholderBearerToken": "Jeton Bearer", + "labelUsername": "Nom d’utilisateur", + "placeholderUsername": "Nom d’utilisateur", + "labelPassword": "Mot de passe", + "placeholderPassword": "Mot de passe", + "labelKey": "Clé", + "placeholderApiKeyName": "Nom de la clé (ex. X-Api-Key)", + "labelValue": "Valeur", + "placeholderValue": "Valeur", + "addTo": "Ajouter à", + "header": "En-tête", + "queryParams": "Paramètres de requête", + "noAuthHint": "Cette requête n’utilise pas d’authentification." + }, + "responsePanel": { + "emptyTitle": "Prêt à explorer ?", + "emptyHint": "Saisissez une URL et cliquez sur Envoyer pour voir la réponse.", + "responsePreviewAlt": "Aperçu de la réponse", + "previewUnavailable": "Aperçu indisponible pour ce type de contenu", + "status": "Statut", + "time": "Durée", + "size": "Taille", + "copyBody": "Copier le corps", + "downloadResponse": "Télécharger la réponse", + "bodyTab": "Corps", + "previewTab": "Aperçu", + "headersTab": "En-têtes", + "binaryRawView": "Les données binaires ne peuvent pas s’afficher en brut.", + "errorStatusLabel": "Erreur", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Nouvel onglet" + }, + "collectionsSidebar": { + "title": "Ressources API", + "newCollection": "Nouvelle collection", + "tabCollections": "Collections", + "tabHistory": "Historique", + "noCollectionsTitle": "Aucune collection", + "noCollectionsHint": "Créez une collection pour organiser vos requêtes.", + "createCollection": "Créer une collection", + "newFolder": "Nouveau dossier", + "rename": "Renommer", + "delete": "Supprimer", + "emptyCollection": "Collection vide", + "recentRequests": "Requêtes récentes", + "clearAll": "Tout effacer", + "noHistoryTitle": "Pas encore d’historique", + "noHistoryHint": "Envoyez une requête pour la voir ici.", + "dialogNewFolderTitle": "Nouveau dossier", + "dialogNewFolderDescription": "Créez un dossier pour organiser vos requêtes.", + "labelFolderName": "Nom du dossier", + "placeholderFolderName": "Mon dossier", + "createFolder": "Créer le dossier", + "dialogNewCollectionTitle": "Nouvelle collection", + "dialogNewCollectionDescription": "Créer une collection de premier niveau.", + "labelCollectionName": "Nom de la collection", + "placeholderCollectionName": "Ma collection", + "dialogRenameCollectionTitle": "Renommer la collection", + "dialogRenameCollectionDescription": "Nouveau nom pour la collection.", + "placeholderRenameCollection": "Nom de la collection", + "renameAction": "Renommer", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Nouveau dossier", + "delete": "Supprimer", + "emptyFolder": "Dossier vide" + }, + "environmentManager": { + "noEnvironment": "Aucun environnement", + "manageDialogTitle": "Gérer les environnements", + "placeholderNewEnv": "Nouvel environnement", + "variablesHeading": "Variables {name}", + "addVariable": "Ajouter une variable", + "columnVariable": "Variable", + "columnValue": "Valeur", + "placeholderKey": "Clé", + "placeholderValue": "Valeur", + "selectEnvToEdit": "Sélectionnez un environnement à modifier" + }, + "codeGenerator": { + "triggerTitle": "Générer le code", + "dialogTitle": "Générer le code", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Copier", + "errorGenerating": "Erreur lors de la génération" + }, + "keyValueEditor": { + "columnKey": "Clé", + "columnValue": "Valeur", + "placeholderKey": "Clé", + "placeholderValue": "Valeur", + "addItem": "Ajouter" + }, + "importCurl": { + "trigger": "Importer cURL", + "dialogTitle": "Importer cURL", + "dialogDescription": "Collez votre commande cURL pour importer une nouvelle requête.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Importer" + }, + "saveRequest": { + "trigger": "Enregistrer", + "dialogTitle": "Enregistrer la requête", + "dialogDescription": "Enregistrer cette requête dans un dossier de collection.", + "labelName": "Nom de la requête", + "placeholderName": "Ma requête", + "labelFolder": "Dossier", + "placeholderFolder": "Choisir un dossier", + "save": "Enregistrer" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Mode édition", + "viewOnlyMode": "Lecture seule", + "insertComponent": "Insérer un composant", + "addUnorderedList": "Liste à puces", + "addOrderedList": "Liste numérotée", + "addTable": "Insérer un tableau" + }, + "addBlock": "Ajouter un bloc", + "media": { + "popoverTitle": "Ajouter un média", + "singleImage": "Image unique", + "singleImageDesc": "Téléverser une image", + "multipleImages": "Plusieurs images", + "multipleImagesDesc": "Grille d’images", + "video": "Vidéo", + "videoDesc": "Téléverser une vidéo", + "uploadingAlt": "Téléversement…", + "uploadFailedAlt": "Échec du téléversement" + }, + "export": { + "fabAria": "Exporter le code", + "title": "Exporter le code", + "description": "Copier la sortie HTML ou JSON du contenu", + "preview": "Aperçu", + "html": "HTML", + "json": "JSON", + "previewOptions": "Options d’aperçu", + "enhanceSpaces": "Améliorer les espaces", + "livePreview": "Aperçu HTML en direct", + "htmlTailwind": "HTML avec classes Tailwind", + "editorJson": "État de l’éditeur (JSON)", + "copyHtml": "Copier HTML", + "copyJson": "Copier JSON", + "copied": "Copié !" + }, + "template": { + "fabTitle": "Changer de modèle", + "title": "Choisir un modèle", + "description": "Modèles prédéfinis pour démarrer rapidement", + "catAll": "Tous les modèles", + "catProductivity": "Productivité", + "catCreative": "Créatif", + "catBusiness": "Professionnel", + "catPersonal": "Personnel", + "noTemplates": "Aucun modèle dans cette catégorie", + "confirmTitle": "Remplacer le contenu ?", + "confirmDescription": "Le contenu actuel sera remplacé par ce modèle.", + "cannotUndo": "Action irréversible.", + "cancel": "Annuler", + "replaceContent": "Remplacer" + }, + "command": { + "searchPlaceholder": "Rechercher une commande…", + "noResults": "Aucune commande.", + "turnInto": "Transformer en", + "h1": { + "label": "Titre 1", + "description": "Grand titre de section", + "keywords": "h1,titre,heading" + }, + "h2": { + "label": "Titre 2", + "description": "Titre de section moyen", + "keywords": "h2,sous-titre" + }, + "h3": { + "label": "Titre 3", + "description": "Petit titre", + "keywords": "h3" + }, + "h4": { + "label": "Titre 4", + "description": "Très petit titre", + "keywords": "h4" + }, + "h5": { + "label": "Titre 5", + "description": "Titre encore plus petit", + "keywords": "h5" + }, + "h6": { + "label": "Titre 6", + "description": "Plus petit titre", + "keywords": "h6" + }, + "p": { + "label": "Paragraphe", + "description": "Texte courant", + "keywords": "p,paragraphe,texte" + }, + "code": { + "label": "Bloc de code", + "description": "Extrait de code", + "keywords": "code,snippet" + }, + "blockquote": { + "label": "Citation", + "description": "Bloc de citation", + "keywords": "citation,quote" + }, + "li": { + "label": "Liste à puces", + "description": "Élément de liste", + "keywords": "liste,puce" + }, + "ol": { + "label": "Liste numérotée", + "description": "Élément numéroté", + "keywords": "liste,numéro" + }, + "img": { + "label": "Image", + "description": "Insérer une image", + "keywords": "image,photo" + }, + "video": { + "label": "Vidéo", + "description": "Insérer une vidéo", + "keywords": "vidéo,mp4" + }, + "table": { + "label": "Tableau", + "description": "Créer un tableau", + "keywords": "tableau,grille" + } + }, + "toasts": { + "tableImported": "Tableau importé", + "tableImportedDesc": "Table Markdown importée", + "mediaPasted": "Média collé", + "pasteMixed": "{imageCount} image(s) et {videoCount} vidéo(s) collées.", + "pasteImages": "{count} image(s) collée(s).", + "pasteVideos": "{count} vidéo(s) collée(s).", + "pasteFailed": "Échec du collage", + "unexpectedError": "Erreur inattendue", + "uploadFailed": "Échec du téléversement" + }, + "blockBackground": { + "menuItem": "Couleur de fond du bloc", + "dialogTitle": "Couleur de fond", + "dialogDescription": "Choisir une couleur de fond pour ce bloc", + "tabPreset": "Couleurs prédéfinies", + "tabCustom": "Couleur personnalisée", + "sectionHeading": "Couleurs de fond", + "apply": "Appliquer", + "colors": { + "none": "Aucune", + "red": "Rouge", + "orange": "Orange", + "yellow": "Jaune", + "green": "Vert", + "blue": "Bleu", + "indigo": "Indigo", + "purple": "Violet", + "pink": "Rose", + "teal": "Sarcelle", + "cyan": "Cyan", + "gray": "Gris" + } + }, + "tableDialog": { + "title": "Créer un tableau", + "description": "Nouveau tableau ou import Markdown", + "markdownCheckbox": "J’ai un tableau Markdown", + "pasteMarkdownLabel": "Coller le tableau Markdown", + "markdownPlaceholder": "| En-tête 1 | En-tête 2 | En-tête 3 |\n|----------|----------|----------|\n| Cellule 1 | Cellule 2 | Cellule 3 |\n| Cellule 4 | Cellule 5 | Cellule 6 |", + "markdownHint": "Collez le tableau ci-dessus (ligne d’en-tête et séparateur requises).", + "rows": "Lignes", + "columns": "Colonnes", + "maxDimensions": "Maximum : 20 lignes × 10 colonnes", + "cancel": "Annuler", + "importTable": "Importer", + "createTable": "Créer" + }, + "tableParse": { + "errors": { + "minRows": "Le tableau doit avoir une ligne d’en-tête et une ligne séparatrice", + "pipeFormat": "Chaque ligne doit commencer et finir par |", + "headerMinCol": "L’en-tête doit avoir au moins une colonne", + "separatorRow": "La deuxième ligne doit être un séparateur (ex. |---|---|)", + "rowColumnMismatch": "Ligne {row} : {got} colonnes, {expected} attendues", + "parseFailed": "Impossible d’analyser le tableau Markdown" + } + }, + "link": { + "editLink": "Modifier le lien", + "addLink": "Ajouter un lien", + "selectedText": "Texte sélectionné : « {text} »", + "linkUrl": "URL du lien", + "urlPlaceholder": "https://example.com", + "update": "Mettre à jour", + "addLinkButton": "Ajouter le lien", + "toastApplied": "Lien appliqué", + "toastLinkedTo": "Lié à : {url}", + "toastRemoved": "Lien supprimé", + "toastRemovedDesc": "Le lien a été retiré de la sélection" + }, + "customClass": { + "devMode": "Mode dev", + "toggleDevModeAria": "Basculer le mode dev", + "searchPlaceholderDev": "Rechercher des classes… (ex. text, bg, flex)", + "searchPlaceholderUser": "Rechercher des styles…", + "appliesTitle": "Applique : {className}", + "noMatches": "Aucune classe pour « {query} »", + "toastClassReplaced": "Classe remplacée", + "toastClassReplacedDesc": "« {replaced} » remplacé par « {className} »", + "toastCustomClassApplied": "Classe personnalisée appliquée", + "toastAppliedClass": "Classe : {className}" + }, + "insertComponents": { + "title": "Insérer un composant", + "description": "Choisissez un composant à insérer", + "noComponents": "Aucun composant disponible", + "categories": { + "media": "Média", + "layout": "Mise en page", + "interactive": "Interactif", + "decoration": "Décoration" + }, + "freeImage": { + "name": "Image libre", + "description": "Image repositionnable et redimensionnable librement" + } + } + }, + "JsonFormatter": { + "title": "Formateur JSON", + "description": "Formatez, validez et modifiez du JSON en vue texte et arborescente.", + "documentNameText": "Document texte {n}", + "documentNameTree": "Document arborescent {n}", + "newDocument": "Nouveau", + "copy": "Copier", + "save": "Enregistrer", + "load": "Charger", + "saving": "Enregistrement...", + "leftDocNameLabel": "Nom du document gauche", + "rightDocNameLabel": "Nom du document droit", + "toastCopiedText": "Panneau texte copié", + "toastCopiedTree": "Panneau arborescence copié", + "toastCopyFailed": "Échec de la copie du JSON", + "toastLoginRequired": "Connectez-vous pour enregistrer des documents", + "toastSaved": "Document enregistré", + "toastSaveFailed": "Échec de l'enregistrement du document", + "format": "Formater", + "compact": "Compacter", + "sortKeys": "Trier les clés", + "repair": "Réparer", + "query": "Requête", + "toastInvalidJson": "JSON invalide", + "toastAlreadyValid": "Le JSON est déjà valide", + "toastRepaired": "JSON réparé", + "toastRepairFailed": "Impossible de réparer le JSON" + }, + "JwtDecoder": { + "title": "Décodeur JWT", + "subtitle": "Inspectez l’en-tête et la charge utile. L’expiration utilise la revendication exp. Rien n’est envoyé à un serveur ; la signature n’est pas vérifiée.", + "tokenLabel": "Jeton", + "clear": "Effacer", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Copier", + "expiryLabel": "Expiration (exp)", + "signaturePresent": "Signature présente (non vérifiée)", + "noExp": "Aucune revendication exp dans la charge utile.", + "status": { + "expired": "Expiré", + "valid": "Valide" + }, + "fields": { + "unix": "Unix", + "local": "Local", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Émis à (iat)", + "notBefore": "Pas avant (nbf)" + }, + "panels": { + "header": "En-tête", + "payload": "Charge utile" + }, + "errors": { + "empty": "Collez un JWT à décoder.", + "segments": "Un JWT nécessite au moins un en-tête et une charge utile (deux segments).", + "base64": "Impossible de décoder en base64url l’en-tête ou la charge utile." + }, + "relative": { + "future": "dans {n} {unit}", + "past": "il y a {n} {unit}" + }, + "units": { + "second": "{count, plural, one {seconde} other {secondes}}", + "minute": "{count, plural, one {minute} other {minutes}}", + "hour": "{count, plural, one {heure} other {heures}}", + "day": "{count, plural, one {jour} other {jours}}" + }, + "tabs": { + "decode": "Décoder", + "sign": "Générateur / signataire" + }, + "signer": { + "algLabel": "Algorithme (alg)", + "tabs": { + "payload": "Charge utile", + "header": "En-tête" + }, + "payloadLabel": "JSON de la charge utile", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON de l’en-tête", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Secret (HS*)", + "secretPlaceholder": "Collez votre secret HMAC…", + "privateKeyLabel": "Clé privée PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "L’algorithme \"none\" crée un jeton non signé. C’est dangereux et doit servir uniquement aux tests.", + "sign": "Signer", + "verify": "Vérifier la signature", + "outputLabel": "Jeton (JWS compact)", + "outputPlaceholder": "Cliquez sur « Signer »…", + "copyToken": "Copier le jeton", + "copied": "Copié", + "verifyKeyLabel": "Clé de vérification (facultative)", + "verifyKeyPlaceholder": "Pour RS* : collez la clé publique (SPKI) PEM. Pour HS* : laissez vide pour réutiliser le secret.", + "verifyKeyHint": "Si vide, la vérification utilise la clé de signature (utile pour HS*).", + "verifyStatus": { + "valid": "Signature valide", + "invalid": "Signature invalide" + }, + "securityNote": "Tout s’exécute localement dans votre navigateur ; les clés et jetons ne sont pas téléversés." + }, + "signErrors": { + "invalidJson": "L’en-tête et la charge utile doivent être un JSON valide.", + "unsupportedAlg": "Algorithme non pris en charge.", + "missingKey": "Une clé de signature est requise pour cet algorithme.", + "cryptoFailed": "La signature a échoué. Vérifiez le format de la clé et réessayez." + }, + "verifyErrors": { + "empty": "Saisissez un JWT à vérifier.", + "segments": "Un JWT doit avoir exactement 3 segments pour vérifier une signature.", + "unsupportedAlg": "Algorithme non pris en charge ou alg manquant dans l’en-tête.", + "missingKey": "Une clé de vérification est requise pour cet algorithme.", + "base64": "Impossible de décoder l’en-tête ou la signature du JWT.", + "cryptoFailed": "La vérification a échoué. Vérifiez le format de la clé et réessayez." + } + }, + "CertificatePemDecoder": { + "title": "Décodeur de certificat / PEM", + "subtitle": "Collez un certificat X.509 (-----BEGIN CERTIFICATE-----) ou une CSR (-----BEGIN CERTIFICATE REQUEST-----). Sujet, émetteur, dates et SAN sont analysés localement ; rien n’est envoyé.", + "pemLabel": "Saisie PEM", + "clear": "Effacer", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Analyse…", + "copyHex": "Copier l’empreinte (hex sans deux-points)", + "errors": { + "empty": "Collez un certificat PEM ou une CSR à décoder.", + "noBlock": "Aucun bloc PEM de certificat ou de CSR trouvé.", + "parseFailed": "Impossible d’analyser ces données PEM." + }, + "block": { + "title": "Bloc PEM {index}", + "certificate": "Certificat X.509", + "csr": "Demande de signature de certificat" + }, + "fields": { + "subject": "Sujet", + "issuer": "Émetteur", + "notBefore": "Non valide avant", + "notAfter": "Non valide après", + "serial": "Numéro de série (hex)", + "sha256": "Empreinte SHA-256", + "sans": "Noms alternatifs du sujet", + "sansNone": "Aucune extension SAN.", + "signatureAlgorithm": "Algorithme de signature", + "csrIssuerNote": "Les CSR n’incluent pas d’émetteur (présent sur le certificat émis).", + "csrValidityNote": "Les CSR n’incluent pas de dates de validité (présentes sur le certificat émis)." + }, + "sanType": { + "dns": "DNS", + "dn": "Nom distinctif", + "email": "E-mail", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID enregistré", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Formateur SQL", + "subtitle": "Mettre en forme du SQL pour MySQL, PostgreSQL ou SQLite. Tout s’exécute dans votre navigateur ; rien n’est téléversé.", + "dialectLabel": "Dialecte", + "keywordsLabel": "Mots-clés", + "indentLabel": "Indentation", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "MAJUSCULES", + "lower": "minuscules", + "preserve": "Préserver" + }, + "spaces": "{n} espaces", + "format": "Formater", + "clear": "Effacer", + "counter": "{current} / {max} caractères", + "inputPanel": "Entrée", + "formattedPanel": "Formaté", + "copy": "Copier", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Cliquez sur « Formater »…", + "errors": { + "tooLong": "La saisie est limitée à {max} caractères.", + "couldNotFormat": "Impossible de formater ce SQL." + } + }, + "YamlFormatter": { + "title": "Formateur YAML", + "subtitle": "Formatez, validez et convertissez du YAML dans votre navigateur. Rien n'est téléversé.", + "outputLabel": "Sortie", + "outputModes": { + "yaml": "YAML (formaté)", + "json": "JSON" + }, + "indentLabel": "Indentation", + "spaces": "{n} espaces", + "format": "Formater", + "clear": "Effacer", + "copy": "Copier", + "counter": "{current} / {max} caractères", + "inputPanel": "Entrée", + "outputPanel": "Sortie", + "errors": { + "tooLong": "L'entrée est limitée à {max} caractères.", + "couldNotFormat": "Impossible d'analyser ce YAML." + } + }, + "GraphqlFormatter": { + "title": "Formateur GraphQL", + "subtitle": "Formatez les requêtes, mutations et abonnements GraphQL dans le navigateur. Rien n’est téléversé.", + "tabs": { + "format": "Formater", + "build": "Éditeur de requête" + }, + "outputModeLabel": "Sortie", + "outputModes": { + "pretty": "Pretty-print", + "minify": "Minifier" + }, + "format": "Formater", + "clear": "Effacer", + "counter": "{current} / {max} caractères", + "inputPanel": "Entrée", + "outputPanel": "Sortie", + "copy": "Copier", + "builderHint": "Composez un squelette puis appliquez-le dans l’éditeur. Définitions de variables en syntaxe GraphQL (ex. $id : ID ! ) — parenthèses facultatives.", + "builder": { + "operation": "Opération", + "operationName": "Nom de l’opération", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Définitions de variables", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Sélection", + "apply": "Appliquer à l’entrée", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "La saisie est limitée à {max} caractères.", + "couldNotFormat": "Impossible d’analyser ou d’imprimer ce GraphQL.", + "emptySelection": "Ajoutez une sélection avant d’appliquer." + } + }, + "RegexTester": { + "title": "Testeur de regex", + "subtitle": "Regex de style JavaScript en direct sur votre texte. Les correspondances sont surlignées ; rien ne quitte votre navigateur.", + "patternLabel": "Motif", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i insensible à la casse", + "m": "m multiligne", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(vide)", + "noFlags": "—", + "matches": "{count, plural, one {correspondance} other {correspondances}}", + "testTextLabel": "Texte de test", + "counter": "{current} / {max}", + "clear": "Effacer", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Le texte est limité à {max} caractères.", + "invalid": "Expression régulière invalide.", + "runaway": "Arrêt après {max} correspondances (motif possiblement incontrôlé)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Convertisseur d’horodatage", + "subtitle": "Secondes Unix (≤10 chiffres) ou millisecondes (entiers plus longs), ISO-8601 ou chaînes que votre navigateur peut analyser. Tout s’exécute localement.", + "inputLabel": "Entrée", + "placeholder": "1744200000 ou 2025-04-09T12:00:00Z", + "now": "Maintenant", + "copy": "Copier", + "hint": "Collez une valeur ou cliquez sur Maintenant.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relatif", + "human": "Lisible" + }, + "fields": { + "seconds": "Secondes", + "milliseconds": "Millisecondes", + "utc": "UTC", + "local": "Local", + "distance": "Distance" + }, + "errors": { + "empty": "Saisissez un timestamp Unix, une chaîne ISO ou un texte de date.", + "invalidNumber": "Timestamp numérique invalide.", + "outOfRange": "Hors plage pour Date JavaScript.", + "couldNotParse": "Impossible d’analyser en Unix, ISO-8601 ou date interprétable par le navigateur." + } + }, + "HttpStatusCodes": { + "title": "Codes d’état HTTP", + "subtitle": "Recherchez et parcourez les codes d’état des réponses HTTP (1xx5xx) avec des descriptions courtes et des références RFC. Tout s’exécute localement.", + "header": { + "title": "Codes d’état HTTP", + "description": "Référence consultable des codes de réponse HTTP avec descriptions et références RFC." + }, + "searchPlaceholder": "Rechercher : 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtrer la classe…", + "classFilterAll": "Toutes les classes", + "resultsCount": "{count} sur {total}", + "table": { + "code": "Code", + "phrase": "Libellé", + "description": "Description", + "copy": "Copier" + }, + "noResults": "Aucun résultat pour « {query} ».", + "specPrefix": "Spéc. :", + "copied": "Code copié" + }, + "CronBuilder": { + "title": "Constructeur d’expression Cron", + "subtitle": "Cron standard à 5 champs : minute heure jour mois jour_semaine. Le parseur utilise seconds=0 en interne. Le format optionnel à 6 champs (sec min heure dom mois dow) fonctionne dans l’éditeur.", + "tabs": { + "builder": "Constructeur visuel", + "expression": "Expression" + }, + "presetsLabel": "Préréglages", + "fieldsLabel": "Champs", + "rawExpressionLabel": "Expression brute", + "expressionHint": "Quittez le champ (perte de focus) pour analyser et synchroniser le constructeur. {count} {count, plural, one {jeton} other {jetons}}.", + "currentExpressionLabel": "Expression actuelle", + "copy": "Copier", + "quickPick": "Sélection rapide", + "emptyExpression": "—", + "plainLanguageLabel": "En langage naturel", + "nextRunsLabel": "Prochaines exécutions (heure locale)", + "fields": { + "minute": "Minute", + "hour": "Heure", + "dayOfMonth": "Jour du mois", + "month": "Mois", + "dayOfWeek": "Jour de la semaine" + }, + "presets": { + "everyMinute": "Chaque minute", + "every5Minutes": "Toutes les 5 minutes", + "every15Minutes": "Toutes les 15 minutes", + "everyHour": "Chaque heure (à :00)", + "every6Hours": "Toutes les 6 heures", + "dailyMidnight": "Tous les jours à minuit", + "daily0900": "Tous les jours à 09:00", + "weekdays0900": "En semaine à 09:00", + "weeklySunday": "Hebdomadaire (dimanche 00:00)", + "monthlyFirst": "Mensuel (1er 00:00)", + "yearlyJan1": "Annuel (1 jan 00:00)" + }, + "picks": { + "minute": { + "every": "chaque minute" + }, + "hour": { + "every": "chaque heure" + }, + "dom": { + "any": "n’importe quel jour", + "last": "dernier jour" + }, + "month": { + "every": "chaque mois" + }, + "dow": { + "any": "n’importe quel jour", + "weekdays": "semaine", + "weekend": "week-end" + } + }, + "months": { + "jan": "Jan", + "feb": "Fév", + "mar": "Mar", + "apr": "Avr", + "may": "Mai", + "jun": "Juin", + "jul": "Juil", + "aug": "Aoû", + "sep": "Sep", + "oct": "Oct", + "nov": "Nov", + "dec": "Déc" + }, + "weekdays": { + "sun": "Dim", + "mon": "Lun", + "tue": "Mar", + "wed": "Mer", + "thu": "Jeu", + "fri": "Ven", + "sat": "Sam" + }, + "errors": { + "fieldCount": "Attendu : 5 champs (minute heure jour mois jour_semaine) ou 6 avec les secondes.", + "invalid": "Expression cron invalide." + } + }, + "Base64": { + "title": "Encodeur / décodeur Base64", + "subtitle": "Encoder du texte en Base64 ou décoder des chaînes Base64", + "upload": "Téléverser", + "clear": "Effacer", + "modes": { + "encode": "Encoder", + "decode": "Décoder" + }, + "swapTitle": "Permuter entrée et sortie", + "panels": { + "plainText": "Texte brut", + "base64Input": "Entrée Base64", + "base64Output": "Sortie Base64", + "decodedText": "Texte décodé" + }, + "placeholders": { + "encode": "Saisissez du texte à encoder en Base64…", + "decode": "Collez une chaîne Base64 à décoder…" + }, + "dropHere": "Déposez un fichier ici", + "charCount": "{count} car.", + "download": { + "title": "Télécharger la sortie", + "encodedFilename": "encode.b64", + "decodedFilename": "decode.txt" + }, + "copyTitle": "Copier dans le presse‑papiers", + "outputPlaceholder": "La sortie apparaîtra ici…", + "errors": { + "invalidBase64": "Chaîne Base64 invalide. Vérifiez votre saisie.", + "encodeFailed": "Échec de l’encodage." + } + }, + "UrlEncode": { + "title": "Encodeur / décodeur d’URL", + "subtitle": "Encoder/décoder en pourcentage via encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Téléverser", + "clear": "Effacer", + "modes": { + "encode": "Encoder", + "decode": "Décoder" + }, + "swapTitle": "Permuter entrée et sortie", + "panels": { + "plainText": "Texte brut", + "encodedInput": "Entrée encodée", + "encodedOutput": "Sortie encodée", + "decodedText": "Texte décodé" + }, + "placeholders": { + "encode": "Saisissez du texte à encoder (ex. valeurs de query string)…", + "decode": "Collez du texte encodé en pourcentage à décoder…" + }, + "dropHere": "Déposez un fichier ici", + "charCount": "{count} car.", + "download": { + "title": "Télécharger la sortie", + "encodedFilename": "url-encode.txt", + "decodedFilename": "url-decode.txt" + }, + "copyTitle": "Copier dans le presse‑papiers", + "outputPlaceholder": "La sortie apparaîtra ici…", + "errors": { + "invalidEncoding": "Encodage en pourcentage invalide. Vérifiez les paires % incomplètes ou les séquences d’échappement.", + "encodeFailed": "Impossible d’encoder (Unicode invalide dans l’entrée)." + } + }, + "UrlParser": { + "title": "Analyseur d’URL", + "subtitle": "Décompose une URL en protocole, hôte, chemin, paramètres de requête et fragment (#).", + "upload": "Téléverser", + "clear": "Effacer", + "panels": { + "input": "Entrée URL", + "output": "Résultat" + }, + "placeholders": { + "input": "Collez une URL (ex. https://example.com/path?foo=bar#section) ou un hôte/chemin (ex. example.com/path)…" + }, + "dropHere": "Déposez un fichier ici", + "charCount": "{count} caractères", + "outputPlaceholder": "Les composants analysés apparaîtront ici…", + "fields": { + "protocol": "Protocole", + "host": "Hôte", + "path": "Chemin", + "hash": "Fragment", + "queryParams": "Paramètres de requête" + }, + "paramCount": "{count} paramètres", + "noParams": "Aucun paramètre de requête.", + "paramKey": "Clé", + "paramValue": "Valeur", + "copyTitle": "Copier dans le presse‑papiers", + "copyParamTitle": "Copier comme key=value", + "download": { + "title": "Télécharger le JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL invalide. Incluez un nom d’hôte (example.com) et vérifiez les caractères." + } + }, + "UuidGenerator": { + "title": "Générateur UUID / ULID", + "subtitle": "Générez des UUID (RFC 9562) ou des ULID dans le navigateur. Export en lot jusqu’à {max} par exécution.", + "format": "Format", + "kindHints": { + "ulid": "Identifiant triable, sûr pour URL, 26 caractères", + "uuid4": "Aléatoire (RFC 9562)", + "uuid7": "Ordonné dans le temps, ms Unix + aléatoire", + "uuid1": "Basé MAC/temps (RFC 9562)", + "uuid6": "Temps grégorien réordonné", + "uuid3": "Hachage MD5 du nom + espace de noms", + "uuid5": "Hachage SHA‑1 du nom + espace de noms", + "nil": "UUID nul (tout à zéro, RFC 9562)" + }, + "countLabel": "Quantité (1–{max})", + "nameLabel": "Nom (chaîne d’entrée)", + "namePlaceholder": "ex. mon.app/ressource", + "bulkHint": "En lot, on ajoute #0, #1, … pour rendre chaque ID unique (v3/v5 sont déterministes).", + "namespace": "Espace de noms", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID personnalisé" + }, + "customNamespaceLabel": "UUID d’espace de noms", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Générer", + "output": "Sortie", + "lines": "{count} lignes", + "download": { + "title": "Télécharger .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Tout copier", + "uppercaseToggle": "Sortie en majuscules", + "hyphensToggle": "Afficher les tirets", + "copyJsonTitle": "Copier en tant que tableau JSON", + "outputPlaceholder": "Cliquez sur « Générer »…", + "errors": { + "invalidCustomNamespace": "L’espace de noms personnalisé doit être un UUID valide.", + "missingName": "Le nom est requis pour UUID v3 et v5.", + "unknown": "Impossible de générer les identifiants." + } + }, + "SecretApiKeyGenerator": { + "title": "Générateur de secret / clé API", + "subtitle": "Générez des chaînes aléatoires cryptographiques avec longueur et alphabet configurables. Export groupé jusqu’à {max} par exécution. Complète le générateur UUID / ULID.", + "alphabetLabel": "Alphabet", + "alphabetHint": "Les doublons sont supprimés. Chaque symbole est tiré uniformément avec crypto.getRandomValues et échantillonnage par rejet (sans biais modulo).", + "alphabetCount": "{count} symboles uniques", + "presetsLabel": "Préréglages", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alphanumérique", + "hex": "Hexadécimal (minuscules)", + "asciiPrintable": "ASCII imprimable" + }, + "lengthLabel": "Longueur par secret (1–{maxLen})", + "countLabel": "Nombre (1–{maxBulk})", + "generate": "Générer", + "output": "Sortie", + "lines": "{count} lignes", + "download": { + "title": "Télécharger .txt", + "filename": "secrets.txt" + }, + "copyTitle": "Tout copier", + "outputPlaceholder": "Cliquez sur « Générer »…", + "entropy": "{bits} bits d'entropie", + "strength": { + "weak": "Faible", + "fair": "Correct", + "strong": "Fort" + }, + "excludeAmbiguous": "Exclure les caractères ambigus", + "excludeAmbiguousHint": "Supprime {chars} — caractères facilement mal lus ou hostiles au shell.", + "prefixLabel": "Préfixe de la clé (facultatif)", + "errors": { + "emptyAlphabet": "Ajoutez au moins un caractère à l’alphabet.", + "alphabetTooLong": "Après suppression des doublons, l’alphabet peut comporter au plus {maxAlpha} caractères.", + "lengthOutOfRange": "La longueur doit être comprise entre 1 et {maxLen}.", + "countOutOfRange": "Le nombre doit être compris entre 1 et {maxBulk}.", + "unknown": "Impossible de générer les secrets." + } + }, + "LoremIpsum": { + "title": "Générateur Lorem Ipsum", + "subtitle": "Générez du texte latin fictif pour vos mises en page et maquettes.", + "paragraphs": "Paragraphes", + "sentences": "Phrases", + "words": "Mots", + "count": "Nombre", + "generate": "Générer", + "copy": "Copier", + "startWith": "Commencer par 'Lorem ipsum...'", + "outputPlaceholder": "Le résultat apparaîtra ici...", + "outputType": "Type de sortie", + "bulletList": "Liste à puces", + "htmlMarkup": "Balisage HTML", + "htmlMarkupHint": "Encapsuler la sortie dans des paragraphes, ou utiliser une liste pour le mode liste", + "output": "Résultat", + "download": "Télécharger" + }, + "ColorPicker": { + "title": "Sélecteur et convertisseur de couleurs", + "subtitle": "Convertissez HEX, RGB et HSL ; créez des palettes harmonieuses. Tout le traitement se fait dans votre navigateur.", + "nativePicker": "Sélecteur natif", + "chooseColor": "Choisir une couleur", + "eyeDropper": "Pipette", + "invalidHex": "Couleur HEX invalide.", + "cssValues": "Valeurs CSS", + "palettes": "Palettes", + "copied": "Copié", + "clickToCopy": "Cliquer pour copier", + "copyAllHex": "Copier tous les HEX", + "copy": "Copier", + "copyColor": "Copier {color}", + "paletteShades": "Nuances", + "paletteComplementary": "Complémentaire", + "paletteTriadic": "Triadique", + "paletteAnalogous": "Analogue", + "paletteSplit": "Compl. divisé", + "paletteSquare": "Carré" + }, + "ContrastChecker": { + "title": "Vérificateur de contraste", + "subtitle": "Calcule le ratio de contraste WCAG 2.1 entre premier plan et arrière-plan. Indique AA et AAA pour le texte courant et grand. Tout s’exécute dans votre navigateur.", + "foreground": "Texte / premier plan", + "background": "Arrière-plan", + "hexPlaceholder": "#RRGGBB", + "swap": "Échanger les couleurs", + "chooseForeground": "Choisir la couleur du premier plan", + "chooseBackground": "Choisir la couleur d'arrière-plan", + "eyeDropper": "Pipette", + "ratio": "Ratio de contraste", + "copyRatio": "Copier le ratio", + "copied": "Copié", + "previewHeading": "Aperçu", + "previewBody": "Portez ce vieux whisky au juge blond qui fume. Voyez le brick géant que j’examine près du wharf.", + "previewCaption": "Exemple de texte courant à taille par défaut.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — texte courant (4,5:1)", + "levelAALarge": "AA — grand texte (3:1)", + "levelAAANormal": "AAA — texte courant (7:1)", + "levelAAALarge": "AAA — grand texte (4,5:1)", + "pass": "Réussi", + "fail": "Échoué", + "invalidHex": "Couleur HEX invalide.", + "largeTextHint": "Grand texte : 18 pt+ en normal, ou 14 pt+ en gras (définitions WCAG)." + }, + "DiffChecker": { + "title": "Vérificateur de différences", + "subtitle": "Comparaison ligne par ligne de deux textes côte à côte. Tout reste dans votre navigateur.", + "rowsCount": "{count} lignes", + "truncatedWarning": "Affichage des premières {max} lignes.", + "limitExceeded": "Chaque côté est limité à {max} caractères.", + "clearBoth": "Effacer les deux", + "original": "Original", + "modified": "Modifié", + "originalPlaceholder": "Collez le texte original…", + "modifiedPlaceholder": "Collez le texte modifié…", + "comparisonLabel": "Comparaison côte à côte", + "emptyState": "Saisissez du texte dans les deux panneaux pour comparer." + }, + "JsonSchemaGenerator": { + "title": "Générateur de schéma JSON", + "subtitle": "Collez du JSON valide pour générer un schéma JSON ou des modèles typés. Tout s'exécute dans votre navigateur.", + "languageLabel": "Langue de sortie", + "inputLabel": "Entrée JSON", + "inputPlaceholder": "Collez le JSON ici…", + "outputLabel": "Résultat généré", + "outputPlaceholder": "Le schéma ou les types générés s'afficheront ici…", + "parseError": "JSON invalide", + "clear": "Effacer", + "copy": "Copier", + "copied": "Copié dans le presse-papiers", + "copyFailed": "Impossible de copier", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Schéma JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Chargement de l’éditeur…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Image vers Base64", + "subtitle": "Déposez une image, obtenez un URI de données ou une chaîne Base64 brute. Tout reste dans votre navigateur.", + "upload": "Télécharger une image", + "clear": "Clair", + "modes": { + "dataUri": "URI des données", + "rawString": "Base brute64" + }, + "panels": { + "imageInput": "Entrée d'image", + "output": "Sortie Base64" + }, + "dropHere": "Déposez l'image ici", + "charCount": "{count} caractères", + "placeholders": { + "uploadImage": "Faites glisser et déposez une image ou cliquez sur Télécharger pour commencer." + }, + "fileTooLarge": "Le fichier est trop volumineux. Le traitement peut geler votre navigateur.", + "copyTitle": "Copier dans le presse-papier", + "download": { + "title": "Télécharger le texte", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Compresseur d’images", + "subtitle": "Réduisez la sortie JPEG, PNG ou WebP localement avec un contrôle de qualité — rien n’est envoyé au serveur.", + "upload": "Importer une image", + "clear": "Effacer", + "dropHere": "Déposez l’image ici", + "placeholders": { + "uploadImage": "Glissez-déposez une image, ou cliquez pour choisir un fichier." + }, + "fileTooLarge": "Fichier trop volumineux (max. {maxMb} Mo).", + "unsupportedFormat": "Type de fichier non pris en charge. Utilisez JPEG, PNG, WebP, GIF, AVIF ou BMP.", + "compressFailed": "Impossible de compresser cette image. Essayez un autre fichier ou format.", + "panels": { + "original": "Original", + "compressed": "Aperçu compressé" + }, + "outputFormat": "Format de sortie", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Qualité", + "qualityPercent": "{value} %", + "qualityPngHint": "Le PNG est sans perte dans le navigateur ; le réglage de qualité s’applique uniquement au JPEG et au WebP.", + "sizes": { + "original": "Original : {size}", + "compressed": "Compressé : {size}", + "saved": "{percent} % plus léger" + }, + "largerWarning": "La sortie est plus grande que l’entrée — essayez un autre format ou une autre qualité.", + "download": "Télécharger", + "downloadFilename": "image-compressee", + "processing": "Compression…", + "emptyCompressed": "L’aperçu compressé s’affiche ici." + }, + "CssGradientBuilder": { + "title": "Générateur de dégradé CSS", + "subtitle": "Générateur de dégradé CSS visuel avec contrôle de l'angle, arrêts de couleur et copie CSS en un clic.", + "preview": "Aperçu du dégradé", + "settings": "Paramètres", + "type": "Taper", + "linear": "Linéaire", + "radial": "Radial", + "angle": "Angle", + "stops": "Arrêts de couleur", + "color": "Couleur", + "position": "Position (%)", + "addStop": "Ajouter un arrêt", + "deleteStop": "Supprimer l'arrêt", + "output": "Sortie CSS", + "copy": "Copier CSS", + "copied": "Copié!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Générateur .gitignore", + "subtitle": "Générez instantanément des fichiers .gitignore fusionnés pour une pile ou une combinaison technologique spécifique.", + "searchPlaceholder": "Piles de recherche (par exemple Node, Python, macOS)...", + "noResults": "Aucune pile trouvée.", + "selectedStacks": "Piles sélectionnées", + "output": "Aperçu de la sortie", + "copy": "Copier dans le Presse-papiers", + "copied": "Copié!", + "download": "Télécharger .gitignore", + "downloading": "Téléchargement...", + "errorFetching": "Échec de la récupération de la liste des meilleurs résultats.", + "errorGenerating": "Échec de la génération de .gitignore à partir des piles sélectionnées.", + "generating": "Générateur...", + "emptyState": "Sélectionnez une ou plusieurs piles technologiques pour générer votre .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Importez un fichier CSV ou Excel, modifiez le JSON, puis téléchargez en CSV ou XLSX. Données tabulaires uniquement ; les dates Excel deviennent des horodatages ISO dans le JSON.", + "dropTitle": "Déposez un fichier CSV ou Excel", + "dropHint": "La première feuille est utilisée pour .xlsx / .xls. Le CSV doit comporter une ligne d’en-têtes.", + "chooseFile": "Choisir un fichier", + "jsonLabel": "JSON (tableau d’objets)", + "jsonPlaceholder": "Collez un tableau JSON d’objets ou importez CSV / Excel…", + "loadSample": "Charger un exemple", + "actions": { + "format": "Formater le JSON", + "clear": "Effacer", + "copy": "Copier", + "copied": "Copié", + "downloadCsv": "Télécharger CSV", + "downloadXlsx": "Télécharger Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Données", + "toastLoadedCsv": "CSV chargé dans l’éditeur", + "toastLoadedExcel": "Fichier Excel chargé dans l’éditeur", + "toastFormatted": "JSON formaté", + "toastExportedCsv": "CSV téléchargé", + "toastExportedXlsx": "Fichier Excel téléchargé", + "toastCopied": "Copié dans le presse-papiers", + "footnote": "Tout s’exécute dans votre navigateur. Le JSON doit être un tableau non vide d’objets. Les valeurs imbriquées sont sérialisées en JSON dans les cellules CSV.", + "errors": { + "unsupportedFile": "Utilisez un fichier .csv, .xlsx ou .xls.", + "parseFailed": "Impossible d’analyser ce fichier.", + "invalidJson": "JSON invalide", + "exportFailed": "Échec de l’export", + "copyFailed": "Impossible de copier" + } + }, + "SnippetManager": { + "title": "Gestionnaire d'extraits de code", + "subtitle": "Enregistrez des extraits dans votre navigateur ou connectez-vous pour les synchroniser avec votre compte. Passez de la consultation à l’édition, copiez en un clic, choisissez un langage ou la détection automatique, et formatez JSON, SQL ou le code pris en charge par Monaco.", + "snippets": "Extraits", + "newSnippet": "Nouvel extrait", + "newSnippetTitle": "Nouvel extrait", + "defaultSnippetTitle": "Bienvenue", + "defaultSnippetCode": "// Bienvenue dans Extraits de code\n// Essayez Affichage / Édition, Copier, Formater et langage auto\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Rechercher des extraits…", + "searchAria": "Rechercher des extraits", + "emptySearch": "Aucun extrait ne correspond à votre recherche.", + "snippetTitle": "Titre", + "snippetTitlePlaceholder": "Nom de l'extrait", + "language": "Langage", + "detectedHint": "Coloration syntaxique : {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Lecture", + "modeEdit": "Édition", + "copy": "Copier", + "copied": "Copié", + "format": "Formater", + "cancel": "Annuler", + "deleteTitle": "Supprimer l'extrait ?", + "deleteDescription": "« {title} » sera supprimé de ce navigateur.", + "deleteConfirm": "Supprimer", + "editorAria": "Éditeur de code de l'extrait", + "editorLoading": "Chargement de l'éditeur…", + "toastCopied": "Copié dans le presse-papiers", + "toastCopyFailed": "Impossible de copier", + "toastNothingToCopy": "Rien à copier", + "toastFormatted": "Formaté", + "toastFormatFailed": "Impossible de formater", + "toastFormatJsonInvalid": "JSON invalide — corrigez la syntaxe d'abord", + "toastFormatUnsupported": "Pas de formateur pour ce langage — essayez JSON ou SQL, ou passez en mode édition", + "toastDeleted": "Extrait supprimé", + "toastLoadFailed": "Impossible de charger les extraits depuis le serveur.", + "toastSyncFailed": "Impossible de synchroniser avec le serveur.", + "languages": { + "auto": "Détection automatique", + "plaintext": "Texte brut", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Client SQL", + "dialogTitle": "Connexions SQL", + "mobileSheetTitle": "Explorateur SQL", + "newTabLabel": "Nouvel onglet", + "vaultLockedTitle": "Coffre verrouillé", + "vaultLockedDesc": "Déverrouillez votre coffre pour accéder aux connexions.", + "heroTitle": "Client SQL", + "heroDesc": "Connectez-vous à PostgreSQL, MySQL ou MariaDB. Les identifiants sont chiffrés dans votre navigateur.", + "heroAddConnection": "Ajouter une connexion", + "selectTableTitle": "Sélectionnez une table ou ouvrez un onglet", + "selectTableDesc": "Cliquez sur une table dans la barre latérale ou utilisez ⌘↩.", + "toastLoadFail": "Impossible de charger les connexions." + }, + "connection": { + "titleNew": "Nouvelle connexion SQL", + "titleEdit": "Modifier la connexion", + "labelDbType": "Type de base de données", + "labelName": "Nom de la connexion", + "placeholderName": "Ma base de prod", + "labelHostPort": "Hôte & Port", + "labelDatabase": "Base de données", + "placeholderDatabase": "mabase", + "labelUsername": "Nom d'utilisateur", + "labelPassword": "Mot de passe", + "labelSsl": "Activer SSL / TLS", + "sslDesc": "Chiffrer la connexion au serveur", + "securityNote": "Les identifiants sont chiffrés AES-256 dans votre navigateur avant stockage", + "btnTest": "Tester la connexion", + "btnSave": "Enregistrer & Connecter", + "btnUpdate": "Mettre à jour", + "btnCancelEdit": "Annuler la modification", + "savedConnections": "Connexions enregistrées", + "emptySaved": "Les connexions apparaîtront ici", + "confirmDelete": "Supprimer cette connexion ?", + "toastFillFields": "Remplissez l'hôte et la base avant de tester.", + "toastConnectOk": "Connecté ! {version}", + "toastConnectFail": "Connexion échouée : {message}", + "toastUnlockVault": "Déverrouillez votre coffre d'abord.", + "toastFieldsRequired": "L'hôte et la base de données sont requis.", + "toastUpdated": "Connexion mise à jour.", + "toastSaved": "Connexion enregistrée.", + "toastSaveFail": "Échec de l'enregistrement : {message}", + "toastDeleted": "Connexion supprimée.", + "toastDeleteFail": "Impossible de supprimer la connexion." + }, + "sidebar": { + "title": "Explorateur SQL", + "tooltipAddConnection": "Ajouter une connexion", + "tooltipRefreshSchema": "Actualiser le schéma", + "noConnections": "Aucune connexion enregistrée", + "toastSchemaFail": "Impossible de charger le schéma : {message}" + }, + "editor": { + "btnRun": "Exécuter", + "executing": "Exécution en cours…", + "emptyTitle": "Exécutez une requête pour voir les résultats", + "emptyHint": "Appuyez sur ⌘↩ ou cliquez sur Exécuter", + "toastNoConnection": "Aucune connexion active." + }, + "results": { + "filterPlaceholder": "Filtrer les résultats…", + "rowCount": "{count} {count, plural, one {ligne} other {lignes}}", + "rowCountFiltered": "{filtered} / {total} lignes", + "exportCsv": "CSV", + "noRows": "Aucune ligne retournée" + } + }, + "QrCodeGenerator": { + "title": "Générateur de codes QR", + "subtitle": "Transformez tout texte ou URL en code QR PNG. Tout est rendu localement dans le navigateur.", + "contentLabel": "Contenu", + "contentPlaceholder": "https://exemple.fr, texte brut, chaîne Wi‑Fi, vCard…", + "styleLabel": "Style", + "dotTypeLabel": "Forme des points", + "dotTypeSquare": "Carré", + "dotTypeDots": "Points", + "dotTypeRounded": "Arrondi", + "dotTypeExtraRounded": "Extra arrondi", + "dotTypeClassy": "Élégant", + "dotTypeClassyRounded": "Élégant arrondi", + "cornerSquareLabel": "Carrés de coin", + "cornerSquareDot": "Point", + "cornerSquareSquare": "Carré", + "cornerSquareExtraRounded": "Extra arrondi", + "cornerDotLabel": "Points de coin", + "cornerDotDot": "Point", + "cornerDotSquare": "Carré", + "eccLabel": "Correction d’erreurs", + "eccL": "Faible (~7 %)", + "eccM": "Moyenne (~15 %)", + "eccQ": "Quartile (~25 %)", + "eccH": "Élevée (~30 %)", + "marginLabel": "Marge (modules)", + "widthLabel": "Largeur de l’image", + "widthEffective": "Rendu à {px} px", + "darkColor": "Modules foncés", + "lightColor": "Modules clairs", + "resetColors": "Réinitialiser les couleurs", + "logoLabel": "Logo central", + "logoMarginLabel": "Marge", + "uploadLogo": "Téléverser une image", + "removeLogo": "Supprimer le logo", + "downloadPng": "Télécharger PNG", + "downloadSvg": "Télécharger SVG", + "copyImage": "Copier l’image", + "copied": "Copié", + "clear": "Effacer", + "previewAlt": "Aperçu du code QR", + "emptyHint": "Saisissez ou collez du contenu pour prévisualiser le code QR.", + "filename": "code-qr.png", + "errors": { + "generationFailed": "Impossible de générer ce code QR. Essayez un texte plus court ou moins de correction.", + "copyUnsupported": "Votre navigateur ne permet pas de copier des images dans le presse-papiers.", + "copyFailed": "Impossible de copier dans le presse-papiers." + } + }, + "IpSubnetCalculator": { + "title": "Calculateur IP / sous-réseau", + "subtitle": "Détails CIDR IPv4 et IPv6 dans le navigateur—sans requêtes externes.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Réseau", + "placeholderIpv4": "10.0.0.0/24 ou 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 ou 2001:db8::1/64", + "hintIpv4": "Utilisez la notation CIDR (ex. /24) ou une IPv4 avec masque décimal.", + "hintIpv6": "Indiquez la longueur de préfixe (ex. /64). Sans cela, /128 est supposé.", + "copyValue": "Copier la valeur", + "rows": { + "cidr": "CIDR", + "network": "Adresse réseau", + "netmask": "Masque de sous-réseau", + "wildcard": "Masque wildcard", + "broadcast": "Broadcast", + "firstHost": "Premier hôte", + "lastHost": "Dernier hôte", + "totalAddresses": "Adresses totales", + "usableHosts": "Hôtes utilisables", + "prefixLength": "Longueur du préfixe (bits)", + "networkIpv6": "Adresse réseau", + "lastIpv6": "Dernière adresse de la plage", + "totalAddressesIpv6": "Adresses dans le sous-réseau" + }, + "usableNote": { + "pointToPoint": "Les liaisons /31 point-à-point utilisent les deux adresses comme hôtes (RFC 3021).", + "singleHost": "/32 correspond à un seul hôte." + }, + "ipv6RangeHint": { + "normal": "Plage mathématique complète affichée ; les règles du routeur peuvent varier.", + "single": "/128 est une adresse unique.", + "full": "/0 couvre tout l’IPv6." + }, + "errors": { + "invalidIpv4": "Réseau IPv4 invalide. Essayez CIDR ou adresse + masque.", + "invalidIpv6": "Réseau IPv6 invalide. Utilisez adresse/préfixe, ex. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Générateur de hash", + "subtitle": "Empreintes MD5, SHA-1 et SHA-2 pour texte ou fichiers ; bcrypt pour les mots de passe (saisie texte). Tout est calculé localement dans le navigateur.", + "algorithmLabel": "Algorithme", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Texte", + "modeFile": "Fichier", + "inputLabelText": "Entrée", + "placeholderText": "Saisissez ou collez du texte…", + "chooseFile": "Choisir un fichier", + "clearFile": "Effacer", + "fileHint": "Lecture en mémoire uniquement ; max. {maxMb} Mo par fichier. Aucun envoi.", + "outputLabel": "Empreinte (hex)", + "copyHash": "Copier l’empreinte", + "copied": "Copié", + "byteCount": "{count, plural, one {# octet} other {# octets}}", + "charCount": "{count, plural, one {# caractère} other {# caractères}}", + "emptyHint": "Saisissez du texte ou choisissez un fichier.", + "securityNote": "MD5 et SHA-1 ne sont pas recommandés pour les nouveaux usages de sécurité ; préférez SHA-256 ou SHA-512.", + "errors": { + "fileTooLarge": "Fichier supérieur à {maxMb} Mo.", + "fileReadFailed": "Impossible de lire le fichier." + }, + "bcryptRoundsLabel": "Coût (tours)", + "bcryptRoundsHint": "Des valeurs plus élevées sont plus lentes et plus résistantes aux attaques par force brute. Souvent 10 par défaut.", + "fileTabDisabledTitle": "bcrypt ne hache que des mots de passe saisis en texte, pas des fichiers arbitraires.", + "placeholderBcrypt": "Saisissez ou collez un mot de passe à hacher…", + "outputLabelBcrypt": "Hachage bcrypt", + "copyBcrypt": "Copier le hachage", + "emptyHintBcrypt": "Entrez un mot de passe pour calculer un hachage bcrypt.", + "securityNoteBcrypt": "Chaque sortie bcrypt inclut un sel aléatoire ; le même mot de passe produit une chaîne différente à chaque fois. C’est attendu. Utilisez un coût élevé en production.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Bac à sable chiffrement", + "subtitle": "AES-GCM dans le navigateur : chiffrer en paquet JSON avec une phrase secrète ou une clé brute, puis déchiffrer localement. Pédagogique et parfois pratique — rien n’est envoyé.", + "tabEncrypt": "Chiffrer", + "tabDecrypt": "Déchiffrer", + "keyModeLabel": "Matériel de clé", + "modePassphrase": "Phrase secrète (PBKDF2 + AES-GCM)", + "modeRawKey": "Clé AES brute (hex ou Base64)", + "aesBitsLabel": "Longueur de clé AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Encodage de la clé brute", + "encodingAuto": "Auto (hex si cela ressemble à du hex, sinon Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Clé brute", + "rawKeyPlaceholder": "Clé de 32 octets en 64 caractères hex, ou Base64 pour AES-256 (la longueur dépend de la taille AES ci-dessus).", + "rawKeyPlaceholderDecrypt": "Uniquement si le paquet indique keyDerivation « none ».", + "passphraseLabel": "Phrase secrète", + "passphrasePlaceholder": "Phrase secrète utilisée uniquement dans le navigateur pour dériver la clé.", + "passphrasePlaceholderDecrypt": "Uniquement si le paquet utilise PBKDF2-SHA256.", + "pbkdf2IterLabel": "Itérations PBKDF2", + "pbkdf2Hint": "Limité entre 10 000 et 2 000 000. La valeur par défaut suit les usages courants des coffres-forts.", + "plaintextLabel": "Texte en clair", + "plaintextPlaceholder": "Saisissez ou collez le message à chiffrer…", + "bundleInputLabel": "Paquet chiffré (JSON)", + "bundlePlaceholder": "Collez le JSON issu de Chiffrer (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Paquet chiffré", + "plaintextOutputLabel": "Texte en clair", + "encryptButton": "Chiffrer", + "decryptButton": "Déchiffrer", + "copyBundle": "Copier le JSON", + "copyPlaintext": "Copier le texte en clair", + "copied": "Copié", + "emptyBundleHint": "Le JSON chiffré apparaît ici après chiffrement.", + "emptyDecryptHint": "Le texte déchiffré apparaît ici.", + "decryptKeyHint": "Utilisez la clé brute si keyDerivation vaut « none » ; la phrase secrète si c’est PBKDF2-SHA256. Le paquet contient le sel, les itérations, l’IV et la taille AES.", + "securityNote": "Les clés et le texte en clair ne quittent pas votre navigateur. Pour des secrets de production, préférez des outils dédiés.", + "errors": { + "emptyKey": "Entrez une clé brute correspondant à la taille AES.", + "emptyPassphrase": "Entrez une phrase secrète.", + "badKeyMaterial": "Impossible de lire la clé en hex ou Base64.", + "wrongKeyLength": "La longueur de clé ne correspond pas à la taille AES choisie (16 / 24 / 32 octets).", + "invalidJson": "JSON non valide.", + "invalidBundle": "Ce JSON n’est pas un paquet de chiffrement pris en charge.", + "decryptFailed": "Échec du déchiffrement — mauvais secret, paquet corrompu ou paramètres incorrects.", + "encryptFailed": "Échec du chiffrement. Vérifiez Web Crypto et les entrées." + } + }, + "HmacGenerator": { + "title": "Générateur HMAC", + "subtitle": "Pour signer des webhooks et tester des intégrations d’API. HMAC-SHA1/256/384/512 en hexadécimal ou Base64—calculé localement dans le navigateur.", + "secretLabel": "Clé secrète", + "messageLabel": "Message / charge utile", + "placeholderSecret": "Collez votre secret de signature…", + "placeholderMessage": "Collez le corps brut ou la chaîne à authentifier (souvent les octets exacts du webhook).", + "algorithmLabel": "Algorithme HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Format de sortie", + "formatHex": "Hexadécimal (minuscules)", + "formatBase64": "Base64", + "outputLabel": "Signature", + "copyOutput": "Copier la signature", + "copied": "Copié", + "byteCountHint": "{count, plural, one {# octet} other {# octets}} (UTF-8)", + "emptyHint": "Saisissez une clé secrète pour calculer le HMAC.", + "securityNote": "Ne partagez jamais votre secret de signature. Cette page n’envoie pas de données ; tout s’exécute dans le navigateur.", + "errors": { + "cryptoFailed": "Impossible de calculer le HMAC. Vérifiez le support de Web Crypto et réessayez.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Générateur de code TOTP / 2FA", + "subtitle": "Collez un secret Base32 (application d’authentification) et obtenez le code à six chiffres RFC 6238 actuel (SHA-1, 30 s). Tout s’exécute localement dans le navigateur—utile pour tester la connexion ou le MFA.", + "secretLabel": "Secret (Base32)", + "placeholderSecret": "Collez la clé secrète (lettres A–Z et chiffres 2–7, comme lors de la configuration de l’authentificateur)…", + "codeLabel": "Code actuel", + "copyCode": "Copier le code", + "copied": "Copié", + "nextIn": "Actualisation dans {seconds} s", + "standardHint": "Pas de temps 30 s, 6 chiffres, HMAC-SHA-1 (compatible Google Authenticator).", + "digitsLabel": "Chiffres", + "periodLabel": "Période", + "algorithmLabel": "Algorithme", + "periodSeconds": "{seconds} s", + "emptyHint": "Collez un secret pour générer un code à six chiffres.", + "invalidHint": "Corrigez le secret pour afficher le code.", + "securityNote": "Traitez cela comme un mot de passe. Le secret ne quitte pas votre navigateur ; rien n’est envoyé au serveur.", + "importLabel": "Importer depuis un URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importer", + "qrSectionTitle": "Code QR de provisionnement", + "qrHint": "Scannez avec une application d'authentification (Google Authenticator, 1Password, Aegis…) pour ajouter ce secret. Le QR code encode le secret — traitez-le comme un mot de passe.", + "accountLabel": "Compte", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Émetteur (facultatif)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Saisissez un secret valide pour générer un code QR.", + "errors": { + "invalidSecret": "Ce n’est pas un secret Base32 valide.", + "cryptoFailed": "Impossible de calculer le TOTP. Vérifiez la disponibilité de Web Crypto et réessayez.", + "invalidUri": "Ce n'est pas un URI otpauth:// TOTP valide.", + "unsupportedUri": "Cet URI utilise des valeurs que l'outil ne prend pas en charge (les chiffres doivent être 6 ou 8 ; la période 15, 30 ou 60 s)." + } + }, + "MarkdownPreview": { + "title": "Aperçu Markdown & HTML", + "subtitle": "Rendu Markdown en direct avec export HTML et conversion HTML → Markdown. Tout s'exécute dans votre navigateur.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Entrée Markdown", + "htmlInputLabel": "Entrée HTML", + "previewLabel": "Aperçu", + "htmlOutputLabel": "Source HTML", + "mdOutputLabel": "Sortie Markdown", + "inputPlaceholder": "# Bonjour\\n\\nTapez votre **markdown** ici…", + "htmlInputPlaceholder": "Collez votre HTML ici…", + "copyHtml": "Copier le HTML", + "copyMarkdown": "Copier le Markdown", + "exportHtml": "Exporter le HTML", + "copied": "Copié", + "charCount": "{count, plural, one {# caractère} other {# caractères}}", + "emptyHint": "Commencez à taper du Markdown pour voir l'aperçu et la sortie HTML.", + "emptyHintHtmlToMd": "Collez du HTML pour le convertir en Markdown.", + "securityNote": "Tout s'exécute localement dans votre navigateur. Rien n'est téléchargé." + }, + "NumberBaseConverter": { + "title": "Convertisseur de base numérique", + "subtitle": "Convertissez des entiers entre les bases 2 à 36. Préfixes optionnels 0x / 0b / 0o. Tout est calculé localement dans le navigateur.", + "inputBaseLabel": "Depuis la base", + "outputBaseLabel": "Vers la base", + "inputLabel": "Nombre d’entrée", + "placeholderInput": "Saisissez un entier dans la base d’entrée sélectionnée…", + "outputLabel": "Valeur convertie", + "copyOutput": "Copier le résultat", + "copied": "Copié", + "swap": "Échanger les bases", + "byteCountHint": "{count, plural, one {# octet} other {# octets}} (longueur UTF-8)", + "emptyHint": "Saisissez un nombre à convertir.", + "note": "Les chiffres au-delà de 9 utilisent les lettres A–Z (insensible à la casse). Espaces et tirets bas ignorés.", + "errors": { + "invalidNumber": "Cette valeur n’est pas valide pour la base d’entrée sélectionnée." + } + }, + "FormatConverter": { + "title": "Convertisseur de formats", + "subtitle": "Convertissez entre JSON, YAML, TOML et XML dans n'importe quelle direction. Les 12 combinaisons. Tout s'exécute dans votre navigateur.", + "fromLabel": "De", + "toLabel": "Vers", + "swap": "Inverser", + "swapTitle": "Inverser les formats et utiliser la sortie comme entrée", + "copy": "Copier", + "copied": "Copié", + "charCount": "{count, plural, one {# caractère} other {# caractères}}", + "emptyHint": "Collez ou tapez votre entrée à gauche pour voir la sortie convertie.", + "localNote": "Tout s'exécute localement dans votre navigateur. Rien n'est téléchargé." + }, + "MimeTypeLookup": { + "title": "Recherche de type MIME", + "subtitle": "Recherchez le type MIME (type de média) d'une extension ou d'un nom de fichier complet. Tout s'exécute localement dans votre navigateur.", + "inputLabel": "Extension ou nom de fichier", + "placeholder": "ex. .png, package.json, photo.jpeg", + "hint": "Astuce : collez une extension (avec ou sans point) ou un nom de fichier complet.", + "clear": "Effacer", + "exampleFilename": "Utiliser un nom de fichier exemple", + "exampleExtension": "Utiliser une extension exemple", + "resultLabel": "Résultat", + "normalizedLabel": "Entrée normalisée :", + "mimeTypeLabel": "Type MIME", + "contentTypeLabel": "En-tête Content-Type", + "charsetLabel": "Jeu de caractères", + "copyMimeType": "Copier le type MIME", + "copy": "Copier", + "copied": "Copié", + "emptyHint": "Saisissez une extension ou un nom de fichier pour rechercher son type MIME.", + "notFound": "Aucun type MIME trouvé pour cette entrée.", + "localNote": "Tout s'exécute localement dans votre navigateur. Rien n'est téléchargé." + }, + "UserAgentParser": { + "title": "Analyseur User-Agent", + "subtitle": "Collez une chaîne User-Agent (UA) pour obtenir le détail navigateur, OS, moteur, appareil et CPU. Tout s'exécute localement dans votre navigateur.", + "inputLabel": "Chaîne User-Agent", + "placeholder": "Collez votre User-Agent ici…", + "hint": "Astuce : vous pouvez copier un UA depuis les en-têtes de requête, des logs ou les devtools du navigateur.", + "clear": "Effacer", + "exampleDesktop": "Utiliser un exemple desktop", + "exampleMobile": "Utiliser un exemple mobile", + "resultLabel": "Détails", + "browserLabel": "Navigateur", + "osLabel": "Système d’exploitation", + "engineLabel": "Moteur", + "deviceTypeLabel": "Type d’appareil", + "deviceVendorLabel": "Fabricant", + "deviceModelLabel": "Modèle", + "cpuArchLabel": "Architecture CPU", + "copyJson": "Copier JSON", + "copy": "Copier", + "copied": "Copié", + "emptyHint": "Collez une chaîne User-Agent pour voir les détails.", + "localNote": "Tout s'exécute localement dans votre navigateur. Rien n'est téléchargé." + }, + "MockDataGenerator": { + "title": "Générateur de données fictives", + "subtitle": "Définissez des colonnes, choisissez des types et exportez des jeux de données pour les seeds, Postman ou les tests. Jusqu’à {max} lignes, tout dans le navigateur — rien n’est envoyé.", + "mockarooLink": "Flux de travail façon Mockaroo", + "rowsLabel": "Lignes", + "rowsHint": "Au plus {max} lignes par génération pour garder l’onglet réactif.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nom de table SQL", + "presetsLabel": "Modèles", + "presetPlaceholder": "Charger un modèle…", + "presetDefault": "Départ (utilisateur)", + "presetUsers": "Utilisateurs & rôles", + "presetProducts": "SKU e-commerce", + "presetApiLog": "Journal des requêtes HTTP", + "generate": "Générer", + "warningDuplicateNames": "Noms de colonnes en double : les colonnes suivantes écrasent les précédentes dans les objets JSON. Renommez-les.", + "schemaHeading": "Schéma", + "addField": "Ajouter une colonne", + "removeField": "Supprimer la colonne", + "duplicateField": "Dupliquer la colonne", + "colName": "Nom", + "colType": "Type", + "colOptions": "Options", + "colBlank": "Vide %", + "optMin": "min", + "optMax": "max", + "optDecimals": "déc", + "optDateFrom": "date début", + "optDateTo": "date fin", + "optValues": "a, b, c", + "optStep": "pas", + "optRowBase": "base", + "output": "Sortie", + "outputPlaceholder": "Cliquez sur Générer pour créer les données…", + "copy": "Copier", + "download": "Télécharger", + "copied": "Copié", + "groups": { + "personal": "Personnel", + "location": "Localisation", + "internetApi": "Internet & API", + "numbersIds": "Nombres & ID", + "dateTime": "Date & heure", + "textDesign": "Texte & design", + "custom": "Personnalisé" + }, + "fieldTypes": { + "first_name": "Prénom", + "last_name": "Nom", + "full_name": "Nom complet", + "email": "Email", + "username": "Nom d’utilisateur", + "password": "Mot de passe", + "phone": "Téléphone", + "company": "Entreprise", + "job_title": "Intitulé du poste", + "avatar_url": "URL d’avatar", + "city": "Ville", + "country": "Pays", + "country_code": "Code pays (ISO)", + "street_address": "Adresse", + "zip_code": "Code postal", + "latitude": "Latitude", + "longitude": "Longitude", + "url": "URL", + "domain": "Domaine", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Entier", + "float": "Décimal", + "boolean": "Booléen", + "uuid": "UUID", + "credit_card": "Carte bancaire (test)", + "semver": "SemVer", + "row_index": "Indice de ligne", + "sequence": "Séquence", + "date": "Date", + "datetime": "Date et heure", + "unix_timestamp": "Horodatage Unix", + "timezone": "Fuseau horaire", + "word": "Mot", + "sentence": "Phrase", + "paragraph": "Paragraphe", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Code devise", + "language_code": "Étiquette de langue", + "custom_list": "Liste personnalisée" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Optimiseur SVG", + "subtitle": "Collez du SVG, minifiez avec SVGO dans le navigateur et comparez la taille UTF-8 avant/après.", + "inputLabel": "Entrée", + "outputLabel": "Optimisé", + "placeholder": "Collez un extrait ou un fichier complet…", + "outputPlaceholder": "Le SVG optimisé apparaît ici…", + "chars": "{count} caractères • {bytes} octets (UTF-8)", + "preview": "Aperçu", + "localNote": "Tout est traité localement dans votre navigateur. Rien n’est envoyé.", + "stats": { + "title": "Taille (UTF-8)", + "before": "Avant", + "after": "Après", + "savedBytes": "{count} octets économisés ({percent}%)", + "noChange": "Aucune réduction — déjà minimal ou sortie vide." + }, + "actions": { + "clear": "Effacer", + "sample": "Exemple", + "copy": "Copier", + "copied": "Copié", + "download": "Télécharger" + }, + "errors": { + "optimizeFailed": "Impossible d’optimiser ce SVG." + } + }, + "UnitConverter": { + "title": "Convertisseur d'unités", + "subtitle": "Convertissez entre 323 unités sur 43 catégories", + "category": "Catégorie", + "from": "De", + "to": "Vers", + "value": "Valeur", + "result": "Résultat", + "swapUnits": "Inverser les unités", + "placeholder": "Saisir une valeur", + "copyResult": "Copier le résultat", + "searchCategory": "Rechercher une catégorie...", + "selectCategory": "Sélectionner une catégorie", + "noResults": "Aucune catégorie trouvée." + }, + "DockerComposeGenerator": { + "title": "Générateur Docker Compose", + "subtitle": "Sélectionnez bases de données, caches, files d’attente, proxys inverses et observabilité — copiez un docker-compose.yml prêt pour le développement local.", + "searchPlaceholder": "Rechercher (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Services", + "selectedCount": "{count, plural, one {# service} other {# services}}", + "outputTitle": "docker-compose.yml", + "copy": "Copier", + "copied": "Copié !", + "download": "Télécharger", + "emptyState": "Sélectionnez un ou plusieurs services pour générer le YAML Compose.", + "hintPorts": "Les ports hôte par défaut limitent les conflits ; modifiez-les si Docker signale un port déjà utilisé. Remplacez les mots de passe de démo avant tout environnement partagé.", + "categories": { + "databases": "Bases de données et stockages", + "cache": "Cache et mémoire", + "queues": "Files d’attente et messagerie", + "search": "Recherche et journaux", + "storage": "Stockage objet", + "proxy": "Proxys inverses et entrée", + "observability": "Métriques et traçage", + "mail": "E-mail (dev local)", + "admin": "Interfaces d’admin et BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Effacer" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (compatible Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (compatible Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (compatible S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (capture SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (mode dev)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Faites glisser les carreaux et fusionnez-les pour atteindre 2048", + "score": "Score", + "best": "Meilleur", + "newGame": "Nouveau", + "howToPlay": "Utilisez les touches fléchées (ou faites glisser sur le mobile) pour déplacer les tuiles. Les tuiles avec le même numéro fusionnent. Atteignez 2048 pour gagner !", + "youWin": "Vous avez atteint 2048 !", + "keepGoing": "Continue", + "gameOver": "Fin de partie ", + "tryAgain": "Réessayez", + "gameOverMessage": "Plus de mouvements à gauche.", + "undo": "Annuler" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "Toutes les étapes sont terminées.", + "time": "Heure", + "best": "Meilleur", + "difficulty": { + "easy": "Facile", + "medium": "Moyen", + "hard": "Dure" + }, + "stage": "Étape", + "solved": "Résolu! ", + "score": "Score", + "nextStage": "Étape suivante", + "replay": "Relire", + "erase": "Effacer", + "howToPlay": "Cliquez sur une cellule, puis appuyez sur un numéro ou appuyez sur 1–9. Le retour arrière ou la suppression efface la cellule. Les chiffres en gras sont des indices. Remplissez la grille de sorte que chaque ligne, colonne et case 3×3 contienne 1 à 9 sans répétition.", + "pencil": "Crayon", + "undo": "Annuler", + "hint": "Indice", + "hintsUsed": "indices utilisés", + "mistakes": "Erreurs", + "failed": "Échec!", + "mistakeMode": "Mode 3 erreurs" + }, + "Snake": { + "title": "Snake", + "subtitle": "Mange, grandis, évite les murs et toi-même", + "score": "Score", + "best": "Meilleur", + "startPrompt": "Appuie sur une touche fléchée", + "startButton": "Démarrer", + "gameOver": "Partie terminée", + "playAgain": "Rejouer", + "howToPlay": "Utilise les flèches ou WASD pour te déplacer. Mange pour grandir. La vitesse augmente tous les 50 points.", + "diffEasy": "Facile", + "diffNormal": "Normal", + "diffHard": "Difficile", + "diffInfinite": "Infini", + "orTap": "ou tapez ci-dessous", + "howToPlayInfinite": "Les murs se rejoignent — pas de mort par les murs. Mange pour grandir.", + "shieldActive": "Bouclier actif", + "speedBoost": "Boost de vitesse", + "powerSpeed": "Vitesse x2", + "powerShield": "Survive à une mort", + "powerShrink": "Raccourcit la queue" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Connexions Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Connectez-vous à des instances Redis. Parcourez les clés, modifiez les valeurs et exécutez des commandes. Les identifiants sont chiffrés dans votre navigateur.", + "heroAddConnection": "Ajouter une connexion", + "vaultLockedTitle": "Coffre verrouillé", + "vaultLockedDesc": "Déverrouillez votre coffre pour accéder aux connexions enregistrées." + } + }, + "Minesweeper": { + "title": "Démineur", + "subtitle": "Trouvez toutes les cases sûres sans déclencher de mine", + "mines": "Mines", + "newGame": "Nouvelle partie", + "win": "Vous avez gagné !", + "loss": "Perdu !", + "playAgain": "Rejouer", + "tryAgain": "Réessayer", + "startPrompt": "Cliquez sur une case pour commencer", + "difficulty": { + "beginner": "Débutant", + "intermediate": "Intermédiaire", + "expert": "Expert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Arrangez les blocs qui tombent pour compléter des lignes", + "next": "Suivant", + "score": "Score", + "best": "Meilleur", + "lines": "Lignes", + "level": "Niveau", + "pause": "Pause", + "resume": "Reprendre", + "start": "Démarrer", + "restart": "Recommencer", + "paused": "En pause", + "gameOver": "Partie terminée" + }, + "SshKeyGenerator": { + "title": "Générateur de clés SSH / RSA", + "subtitle": "Générez des paires de clés Ed25519 ou RSA dans votre navigateur. Téléchargez les clés privée et publique aux formats OpenSSH et PEM. Rien n'est téléversé.", + "keyTypeLabel": "Type de clé", + "recommended": "recommandé", + "ed25519Desc": "Rapide, compact et moderne. Pris en charge par GitHub, GitLab et tous les principaux serveurs SSH.", + "rsa2048Desc": "Largement compatible. Sûr pour la plupart des usages.", + "rsa4096Desc": "Compatibilité et robustesse maximales pour les systèmes anciens.", + "commentLabel": "Commentaire (optionnel)", + "commentHint": "Ajouté à la ligne de clé publique OpenSSH.", + "generateBtn": "Générer la paire de clés", + "generating": "Génération…", + "generatingLong": "Génération (peut prendre un moment)…", + "copy": "Copier", + "download": "Télécharger", + "privateKeyLabel": "Clé privée (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Clé publique (format OpenSSH)", + "publicKeySpkiLabel": "Clé publique (SPKI PEM)", + "browserNote": "Toutes les clés sont générées dans votre navigateur. Rien n'est envoyé à aucun serveur.", + "errors": { + "generationFailed": "Échec de la génération de la clé" + } + }, + "DnsLookup": { + "title": "Recherche DNS", + "subtitle": "Interrogez les enregistrements DNS d'un domaine — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Saisissez un domaine (p. ex. github.com)", + "lookupBtn": "Rechercher", + "looking": "Recherche en cours…", + "refreshBtn": "Actualiser", + "recordTypesLabel": "Types d'enregistrement", + "allBtn": "Tous", + "tryLabel": "Essayer :", + "errors": { + "lookupFailed": "Échec de la recherche DNS" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Générateur et vérificateur Bcrypt", + "subtitle": "Hachez des mots de passe avec bcrypt et vérifiez des hachages — tout reste dans votre navigateur", + "tabs": { + "hash": "Hacher", + "verify": "Vérifier" + }, + "showPassword": "Afficher le mot de passe", + "hidePassword": "Masquer le mot de passe", + "copy": "Copier", + "copied": "Copié", + "hash": { + "passwordLabel": "Mot de passe", + "passwordPlaceholder": "Saisissez un mot de passe à hacher…", + "costLabel": "Facteur de coût", + "costHint": "Un coût plus élevé double le travail par étape. 10-12 est un choix courant en production.", + "slowWarning": "Les facteurs de coût de 13 et plus peuvent prendre plusieurs secondes à calculer.", + "generate": "Générer le hachage", + "generating": "Hachage…", + "resultLabel": "Hachage bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomie du hachage", + "algorithm": "algorithme", + "cost": "coût", + "salt": "sel (22 caractères)", + "hash": "hachage (31 caractères)" + } + }, + "verify": { + "passwordLabel": "Mot de passe", + "passwordPlaceholder": "Saisissez le mot de passe à vérifier…", + "hashLabel": "Hachage bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Saisissez un mot de passe et un hachage bcrypt pour les comparer.", + "invalidHash": "Ceci ne ressemble pas à un hachage bcrypt valide.", + "checking": "Comparaison…", + "match": "Correspondance — le mot de passe produit ce hachage.", + "noMatch": "Aucune correspondance — le mot de passe ne produit pas ce hachage." + } + }, + "ChmodCalculator": { + "title": "Calculateur Chmod", + "subtitle": "Convertissez les permissions de fichiers Unix entre cases à cocher, octal et notation symbolique", + "grid": { + "title": "Permissions", + "read": "Lecture", + "write": "Écriture", + "execute": "Exécution", + "owner": "Propriétaire", + "group": "Groupe", + "others": "Autres" + }, + "special": { + "title": "Bits spéciaux", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Octal", + "placeholder": "755 ou 4755", + "invalid": "Saisissez 3 ou 4 chiffres octaux (0-7)." + }, + "symbolic": { + "label": "Symbolique", + "placeholder": "rwxr-xr-x", + "invalid": "Saisissez 9 caractères comme rwxr-xr-x (s/S, t/T autorisés)." + }, + "output": { + "title": "Commande", + "copy": "Copier", + "copied": "Copié" + }, + "explain": { + "line": "{cls} peut {perms}.", + "none": "{cls} n'a aucune permission.", + "setuid": "Setuid : le fichier s'exécute avec les privilèges de son propriétaire.", + "setgid": "Setgid : le fichier s'exécute avec les privilèges de son groupe (les nouveaux fichiers d'un répertoire héritent de son groupe).", + "sticky": "Sticky : seul le propriétaire d'un fichier peut le supprimer ou le renommer dans ce répertoire." + }, + "presets": { + "title": "Préréglages courants", + "octal": "Octal", + "permissions": "Permissions", + "description": "Usage typique", + "p644": "Fichiers ordinaires : le propriétaire écrit, tout le monde lit.", + "p755": "Exécutables et répertoires : tout le monde peut lire et entrer.", + "p600": "Fichiers privés : seul le propriétaire lit et écrit.", + "p700": "Exécutables et répertoires privés : propriétaire uniquement.", + "p664": "Fichiers modifiables par le groupe.", + "p775": "Répertoires et exécutables modifiables par le groupe.", + "p400": "Lecture seule pour le propriétaire (ex. clés privées SSH)." + } + }, + "CodeScreenshot": { + "title": "Capture de code", + "subtitle": "Transformez votre code en images élégantes et partageables", + "languageLabel": "Langage", + "autoDetect": "Détection auto", + "backgroundLabel": "Arrière-plan", + "paddingLabel": "Marge intérieure", + "filenameLabel": "Nom de fichier", + "filenamePlaceholder": "example.ts", + "windowLabel": "Points de fenêtre", + "themeLabel": "Thème de fenêtre", + "themeDark": "Sombre", + "themeLight": "Clair", + "scaleLabel": "Échelle d'export", + "copyImage": "Copier l'image", + "copied": "Copié", + "exportPng": "Exporter en PNG", + "inputLabel": "Code", + "detected": "Détecté : {language}", + "clear": "Effacer", + "placeholder": "Collez votre code ici…", + "previewLabel": "Aperçu", + "emptyPreview": "L'aperçu de votre code apparaîtra ici", + "exportFailed": "Échec de l'export de l'image", + "copyUnsupported": "La copie d'images n'est pas prise en charge dans ce navigateur", + "copyFailed": "Échec de la copie de l'image", + "backgrounds": { + "ocean": "Océan", + "sunset": "Coucher de soleil", + "candy": "Bonbon", + "forest": "Forêt", + "midnight": "Minuit", + "gold": "Or", + "aurora": "Aurore", + "slate": "Ardoise", + "transparent": "Transparent" + } + }, + "CssGenerators": { + "title": "Générateurs CSS", + "subtitle": "Box-shadow, border-radius et clamp() fluide — copiez le CSS.", + "preview": "Aperçu", + "output": "CSS", + "copy": "Copier", + "copied": "Copié", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() fluide" + }, + "boxShadow": { + "presets": "Préréglages", + "layer": "Calque", + "addLayer": "Ajouter un calque", + "removeLayer": "Supprimer le calque", + "x": "X", + "y": "Y", + "blur": "Flou", + "spread": "Étendue", + "color": "Couleur", + "alpha": "Alpha", + "inset": "Intérieur", + "presetNames": { + "subtleCard": "Carte subtile", + "elevated": "Surélevé", + "hardOffset": "Décalage net", + "neonGlow": "Lueur néon", + "inner": "Intérieur", + "layeredSmooth": "Superposé doux" + } + }, + "borderRadius": { + "modeLabel": "Mode", + "modes": { + "simple": "Par coin", + "blob": "Blob organique" + }, + "unit": "Unité", + "linkCorners": "Lier les coins", + "randomize": "Aléatoire", + "horizontal": "Rayons horizontaux", + "vertical": "Rayons verticaux", + "corners": { + "tl": "Haut gauche", + "tr": "Haut droite", + "br": "Bas droite", + "bl": "Bas gauche" + } + }, + "clamp": { + "minFont": "Police min (px)", + "maxFont": "Police max (px)", + "minViewport": "Viewport min (px)", + "maxViewport": "Viewport max (px)", + "rootFont": "Police racine (px)", + "viewportWidth": "Viewport simulé", + "computedSize": "Taille calculée", + "slope": "Pente", + "intercept": "Ordonnée à l'origine", + "previewText": "Portez ce vieux whisky au juge blond", + "invalid": "Saisissez des bornes de viewport valides et différentes." + } + }, + "ExifViewer": { + "title": "Visionneuse et nettoyeur EXIF", + "subtitle": "Inspectez les métadonnées des photos et supprimez-les dans votre navigateur", + "dropTitle": "Déposez une image ici", + "dropHint": "ou cliquez pour parcourir — votre image ne quitte jamais votre appareil", + "browse": "Choisir une image", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Lecture des métadonnées…", + "parseFailed": "Échec de la lecture de cette image", + "clear": "Supprimer l'image", + "gpsWarning": "Contient une position GPS", + "openMap": "Voir sur OpenStreetMap", + "noMetadata": "Aucune métadonnée intégrée n'a été trouvée dans cette image.", + "facts": { + "name": "Nom", + "type": "Type", + "dimensions": "Dimensions", + "size": "Taille" + }, + "groups": { + "camera": "Appareil photo", + "exposure": "Exposition", + "timestamps": "Horodatages", + "gps": "Position GPS", + "software": "Logiciel et autres" + }, + "gpsCard": { + "latitude": "Latitude", + "longitude": "Longitude", + "altitude": "Altitude" + }, + "raw": { + "title": "Toutes les métadonnées", + "copyJson": "Copier le JSON", + "copied": "Copié" + }, + "remove": { + "title": "Supprimer les métadonnées", + "jpegDescription": "Supprimez sans perte les données EXIF, XMP, IPTC et les commentaires sans recompresser l'image.", + "reencodeDescription": "Réencodez l'image pour supprimer les métadonnées intégrées. Cela recompresse les pixels.", + "keepIcc": "Conserver le profil colorimétrique (ICC)", + "stripButton": "Supprimer les métadonnées", + "reencodeButton": "Réencoder et nettoyer", + "unsupported": "La suppression des métadonnées n'est pas prise en charge pour ce type de fichier.", + "failed": "Échec de la suppression des métadonnées", + "before": "Avant", + "after": "Après", + "download": "Télécharger l'image nettoyée" + } + }, + "FaviconGenerator": { + "title": "Générateur de favicon", + "subtitle": "Créez des favicons et icônes d'application à partir d'une seule image", + "dropTitle": "Déposez une image ici", + "dropHint": "PNG, JPEG ou SVG — cliquez pour parcourir", + "browse": "Choisir une image", + "clear": "Supprimer l'image", + "backgroundLabel": "Arrière-plan", + "transparent": "Transparent", + "solidColor": "Uni", + "cornerLabel": "Forme des coins", + "cornerNone": "Carré", + "cornerRounded": "Arrondi", + "cornerCircle": "Cercle", + "maskPaddingLabel": "Marge maskable", + "maskable": "maskable", + "previewLabel": "Aperçu", + "copy": "Copier", + "headSnippet": "Extrait pour le head HTML", + "downloadIco": "Télécharger favicon.ico", + "downloadZip": "Tout télécharger (.zip)" + }, + "JsonDiff": { + "title": "Diff JSON", + "subtitle": "Comparaison sémantique tenant compte de la structure de deux documents JSON — ordre des clés et formatage ignorés", + "options": { + "ignoreArrayOrder": "Ignorer l'ordre des tableaux" + }, + "loadBothSamples": "Charger des exemples", + "sample": "Exemple", + "format": "Formater", + "clear": "Effacer", + "formatError": "Impossible de formater : l'entrée n'est pas un JSON valide", + "placeholder": "Collez ou saisissez du JSON ici…", + "panels": { + "left": "Original (gauche)", + "right": "Modifié (droite)", + "results": "Différences" + }, + "summary": { + "added": "{count} ajouté(s)", + "removed": "{count} supprimé(s)", + "changed": "{count} modifié(s)" + }, + "types": { + "added": "Ajouté", + "removed": "Supprimé", + "changed": "Modifié" + }, + "copyChanges": "Copier en JSON", + "copied": "Copié", + "empty": "Collez du JSON dans les deux panneaux pour voir leurs différences.", + "identical": "Les deux documents sont sémantiquement identiques." + }, + "JsonpathPlayground": { + "title": "Playground JSONPath", + "subtitle": "Interrogez des documents JSON avec JSONPath ou JMESPath et voyez les résultats en direct", + "engineLabel": "Moteur", + "queryLabel": "Requête", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Aide-mémoire", + "cheats": { + "jpAllTitles": "Tous les titres de livres", + "jpAllAuthors": "Chaque auteur, n'importe où dans le document", + "jpFirstBook": "Premier livre", + "jpLastBook": "Dernier livre", + "jpSlice": "Les deux premiers livres (slice)", + "jpFilterPrice": "Livres à moins de 40", + "jpFilterInStock": "Titres des livres en stock", + "jpRecursive": "Toutes les valeurs, récursivement", + "jmAllTitles": "Tous les titres de livres", + "jmFirstAuthor": "Auteur du premier livre", + "jmLastBook": "Dernier livre", + "jmSlice": "Les deux premiers livres (slice)", + "jmFilterPrice": "Titres des livres à moins de 40", + "jmCount": "Compter les livres en stock", + "jmProjection": "Reformer chaque livre en un nouvel objet", + "jmSort": "Titres triés par prix" + }, + "panels": { + "input": "Entrée JSON", + "results": "Résultats" + }, + "sample": "Exemple", + "format": "Formater", + "clear": "Effacer", + "formatError": "Impossible de formater : l'entrée n'est pas un JSON valide", + "placeholder": "Collez ou saisissez du JSON ici…", + "outputPlaceholder": "Les correspondances apparaîtront ici…", + "matchCount": "{count, plural, one {# correspondance} other {# correspondances}}", + "copy": "Copier", + "copied": "Copié" + }, + "KeycodeInspector": { + "title": "Inspecteur de keycode", + "subtitle": "Appuyez sur n'importe quelle touche pour inspecter son événement clavier JavaScript", + "capture": { + "prompt": "Appuyez sur une touche…", + "ready": "À l'écoute — appuyez sur une touche de votre clavier", + "clickToFocus": "Cliquez ici pour donner le focus, puis appuyez sur une touche" + }, + "hero": { + "spaceKey": "(Espace)", + "keyLabel": "event.key", + "repeat": "répétition" + }, + "copyJson": "Copier le JSON", + "copiedJson": "JSON de l'événement copié dans le presse-papiers", + "deprecated": "obsolète", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standard", + "left": "Gauche", + "right": "Droite", + "numpad": "Pavé numérique", + "unknown": "Inconnu" + }, + "history": { + "title": "Historique", + "clear": "Effacer l'historique", + "empty": "Les appuis sur les touches apparaîtront ici." + } + }, + "MarkdownTableGenerator": { + "title": "Générateur de tableaux Markdown", + "subtitle": "Modifiez une grille, collez depuis un tableur et copiez un tableau Markdown ou HTML joliment aligné", + "addRow": "Ajouter une ligne", + "addColumn": "Ajouter une colonne", + "importLabel": "Importer", + "importPlaceholder": "Collez du TSV (depuis Excel / Google Sheets) ou du CSV ici…", + "importButton": "Importer dans la grille", + "pasteClipboard": "Coller depuis le presse-papiers", + "importEmpty": "Rien à importer — collez d'abord quelques lignes", + "clipboardError": "Impossible de lire le presse-papiers", + "clear": "Effacer", + "panels": { + "editor": "Éditeur de tableau", + "output": "Résultat" + }, + "align": { + "left": "Aligner à gauche", + "center": "Centrer", + "right": "Aligner à droite" + }, + "headerPlaceholder": "En-tête", + "cellPlaceholder": "Cellule", + "removeRow": "Supprimer la ligne", + "removeColumn": "Supprimer la colonne", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Copier", + "copied": "Copié" + }, + "TimezoneConverter": { + "title": "Convertisseur de fuseaux horaires", + "subtitle": "Convertissez un moment à travers le monde pour planifier vos réunions.", + "dateTimeLabel": "Date et heure", + "now": "Maintenant", + "sourceZoneLabel": "Fuseau source", + "selectZone": "Sélectionner un fuseau", + "searchZone": "Rechercher des fuseaux...", + "noZone": "Aucun fuseau trouvé.", + "scrubLabel": "Décaler l'heure", + "reset": "Réinitialiser", + "targetsLabel": "Fuseaux cibles", + "addZone": "Ajouter un fuseau", + "remove": "Supprimer", + "localBadge": "Local", + "invalidDate": "Saisissez une date et une heure valides.", + "bands": { + "work": "Heures ouvrées (09:00–18:00)", + "edge": "Heures limites (tôt / soir)", + "night": "Nuit" + } + }, + "TokenCounter": { + "title": "Compteur de tokens LLM", + "subtitle": "Comptez les tokens, visualisez les segments et estimez le coût de l'API.", + "inputLabel": "Texte", + "sample": "Exemple", + "clear": "Effacer", + "placeholder": "Collez du texte, du code ou du JSON pour compter les tokens...", + "encoding": "Encodage", + "loading": "Chargement du tokeniseur...", + "encodings": { + "o200k": "o200k_base (GPT-4o / série o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Caractères", + "words": "Mots", + "ratio": "Tokens/mot", + "ratioSub": "approx." + }, + "claudeRow": "Claude (approximatif)", + "estimate": "estimation", + "viz": { + "title": "Visualisation des tokens", + "toggle": "Afficher", + "hint": "Activez pour colorer chaque token comme un segment.", + "empty": "Saisissez du texte pour visualiser les tokens.", + "truncated": "Affichage des {limit} premiers tokens sur {total}." + }, + "cost": { + "title": "Calculateur de coût", + "disclaimer": "valeurs par défaut modifiables — vérifiez les tarifs actuels", + "model": "Modèle", + "expectedOutput": "Tokens de sortie attendus", + "customInput": "Entrée $/MTok", + "customOutput": "Sortie $/MTok", + "inputCost": "Coût d'entrée", + "outputCost": "Coût de sortie", + "total": "Total", + "per1000": "Pour 1000 appels" + } + }, + "WebhookTester": { + "title": "Testeur de webhook", + "subtitle": "Créez une URL temporaire et inspectez les requêtes webhook entrantes en direct.", + "intro": { + "title": "Créez une URL de webhook pour commencer à capturer les requêtes", + "hint": "Vous obtiendrez une URL non devinable qui enregistre chaque requête entrante. Elle expire au bout de 24 heures." + }, + "createBtn": "Créer une URL de webhook", + "creating": "Création…", + "urlLabel": "Votre URL de webhook", + "expiresIn": "Expire dans {countdown}", + "copyUrlBtn": "Copier l'URL", + "copied": "Copié", + "clearBtn": "Effacer", + "newBinBtn": "Nouvelle URL", + "requestsTitle": "Requêtes", + "selectPrompt": "Sélectionnez une requête pour en inspecter les détails.", + "expiredNotice": "Cette URL de webhook a expiré. Créez-en une nouvelle pour continuer les tests.", + "base64Badge": "base64", + "truncatedBadge": "tronqué", + "empty": { + "title": "En attente de requêtes…", + "hint": "Envoyez une requête à votre URL et elle apparaîtra ici instantanément. Essayez ceci :" + }, + "detail": { + "receivedAt": "Reçu", + "source": "IP source", + "contentType": "Content-Type", + "query": "Paramètres de requête", + "headers": "En-têtes", + "body": "Corps", + "copyBody": "Copier le corps", + "emptyBody": "Aucun corps" + }, + "errors": { + "createFailed": "Échec de la création de l'URL de webhook. Veuillez réessayer.", + "clearFailed": "Échec de l'effacement des requêtes. Veuillez réessayer." + } + }, + "WebsocketTester": { + "title": "Testeur WebSocket", + "subtitle": "Connectez-vous à un point de terminaison WebSocket et envoyez, recevez et inspectez des messages en direct", + "urlLabel": "URL du point de terminaison", + "protocolsLabel": "Sous-protocoles", + "protocolsPlaceholder": "facultatif, séparés par des virgules", + "connect": "Connecter", + "disconnect": "Déconnecter", + "status": { + "disconnected": "Déconnecté", + "connecting": "Connexion", + "connected": "Connecté", + "error": "Erreur" + }, + "messages": { + "connecting": "Connexion à {url}…", + "connected": "Connexion établie", + "connectedWithProtocol": "Connexion établie (protocole : {protocol})", + "invalidUrl": "URL invalide — doit commencer par ws:// ou wss://", + "connectFailed": "Échec de l'ouverture de la connexion", + "socketError": "Une erreur de connexion s'est produite", + "notConnected": "Non connecté — connectez-vous d'abord", + "binaryMessage": "Message binaire reçu" + }, + "log": { + "title": "Journal des messages", + "empty": "Aucun message pour l'instant. Connectez-vous et commencez à envoyer.", + "copyAll": "Tout copier", + "download": "Télécharger en .txt", + "clear": "Effacer le journal", + "prettyPrint": "Formater le JSON", + "showRaw": "Afficher le texte brut" + }, + "composer": { + "placeholder": "Saisissez un message à envoyer…", + "send": "Envoyer", + "formatJson": "Formater en JSON", + "invalidJson": "Le message n'est pas un JSON valide", + "hint": "Cmd/Ctrl+Entrée pour envoyer" + }, + "ping": { + "label": "Auto-ping", + "seconds": "s", + "intervalTitle": "Intervalle de ping en secondes", + "payloadPlaceholder": "charge utile du ping" + } + }, + "WhoisLookup": { + "title": "Recherche Whois", + "subtitle": "Consultez les détails d'enregistrement de n'importe quel domaine ou adresse IP via RDAP", + "placeholder": "example.com ou 8.8.8.8", + "lookupBtn": "Rechercher", + "looking": "Recherche…", + "refreshBtn": "Actualiser", + "tryLabel": "Essayez :", + "copy": "Copier", + "copied": "Copié", + "notAvailable": "Non disponible", + "summary": { + "registrar": "Bureau d'enregistrement", + "network": "Réseau" + }, + "dates": { + "created": "Créé", + "updated": "Mis à jour", + "expires": "Expire" + }, + "sections": { + "status": "Statut", + "nameservers": "Serveurs de noms", + "contacts": "Contacts", + "rawRdap": "RDAP brut" + }, + "contacts": { + "roles": "Rôles", + "name": "Nom", + "organization": "Organisation" + }, + "errors": { + "invalidTarget": "Saisissez un nom de domaine ou une adresse IP valide", + "lookupFailed": "Échec de la recherche (statut {status})" + } + } +} diff --git a/apps/desktop-ui/messages/id.json b/apps/desktop-ui/messages/id.json new file mode 100644 index 00000000..27ba4a1d --- /dev/null +++ b/apps/desktop-ui/messages/id.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Dasbor ", + "settings": "Pengaturan", + "apiClient": "Klien api", + "httpStatusCodes": "Kode status HTTP", + "passwordManager": "Pengelola Kata Sandi", + "environmentManager": "Pengelola lingkungan", + "bookmarks": "Penanda", + "jsonFormatter": "Pemformat JSON", + "emailValidator": "Validator Email", + "nosqlExplorer": "Penjelajah Basis Data", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Arena enkripsi", + "jwtDecoder": "Dekoder JWT", + "certificatePemDecoder": "Dekoder sertifikat / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Pemformat GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Parser URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Rahasia / kunci API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Pemeriksa kontras", + "jsonSchemaGenerator": "Generator Skema JSON", + "imageToBase64": "Gambar ke Base64", + "imageCompressor": "Kompresor gambar", + "cssGradientBuilder": "Pembuat Gradien CSS", + "gitignoreGenerator": ".gitignore Generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Cuplikan kode", + "qrCodeGenerator": "Generator kode QR", + "dnsLookup": "Pencarian DNS", + "ipSubnetCalculator": "Kalkulator IP / subnet", + "hashGenerator": "Generator hash", + "hmacGenerator": "Generator HMAC", + "totpGenerator": "Kode TOTP / 2FA", + "markdownPreview": "Pratinjau Markdown", + "numberBaseConverter": "Konverter basis", + "formatConverter": "Konverter Format", + "mockDataGenerator": "Generator data mock", + "svgOptimizer": "Pengoptimal SVG", + "unitConverter": "Konverter Satuan", + "dockerComposeGenerator": "Generator Docker Compose", + "breakRoom": "Ruang Istirahat", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Pemformat YAML", + "sshKeyGenerator": "Generator Kunci SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Kalkulator Chmod", + "codeScreenshot": "Tangkapan Kode", + "cssGenerators": "Generator CSS", + "exifViewer": "Penampil EXIF", + "faviconGenerator": "Generator Favicon", + "jsonDiff": "Diff JSON", + "jsonpathPlayground": "Playground JSONPath", + "keycodeInspector": "Inspektur Keycode", + "markdownTableGenerator": "Tabel Markdown", + "timezoneConverter": "Konverter Zona Waktu", + "tokenCounter": "Penghitung Token", + "webhookTester": "Penguji Webhook", + "websocketTester": "Penguji WebSocket", + "whoisLookup": "Pencarian Whois" + }, + "Help": { + "title": "Bantuan & dokumentasi", + "subtitle": "Cara MyDevTools bekerja, apa yang dilakukan tiap aplikasi, dan bagaimana keamanan serta enkripsi melindungi data Anda.", + "tabs": { + "overview": "Ringkasan", + "apps": "Aplikasi", + "security": "Keamanan", + "tips": "Tips" + }, + "overview": { + "whatIs": { + "title": "Apa itu MyDevTools?", + "description": "Workspace developer dengan login: dasbor dan kumpulan tools untuk kebutuhan sehari-hari.", + "p1": "Setelah login, aplikasi membuat sesi dengan MyDevTools API agar tools dapat memuat dan menyimpan data Anda. Sidebar menampilkan semua aplikasi; Anda dapat menyembunyikan tools yang tidak digunakan dari", + "settingsLink": "Pengaturan", + "p1After": ".", + "p2": "Beberapa fitur menggunakan kata sandi utama (atau kunci vault global) agar rahasia seperti kata sandi dan connection string dienkripsi di perangkat Anda sebelum mencapai server." + }, + "quickLinks": { + "title": "Tautan cepat", + "dashboard": "Dasbor", + "settings": "Pengaturan" + } + }, + "apps": { + "intro": "Buka tool apa pun dari sidebar atau dasbor. Berikut panduan singkat untuk tiap aplikasi.", + "open": "Buka", + "howItWorks": "Cara kerja", + "generic": "Gunakan tool ini dari sidebar untuk workflow khususnya." + }, + "security": { + "session": { + "title": "Login dan sesi API", + "description": "Identitas dan cara browser berkomunikasi dengan backend.", + "items": { + "authentication": "Autentikasi menggunakan Firebase untuk login. Setelah login, aplikasi menukar Firebase ID token Anda menjadi cookie API.", + "accessTokens": "Access token adalah JWT yang ditandatangani server (HS256) untuk membuktikan pengguna yang memanggil API.", + "refreshTokens": "Refresh token disimpan di cookie HttpOnly; server menyimpan hash SHA-256 dari token, bukan token mentahnya." + } + }, + "encryption": { + "title": "Enkripsi yang perlu diketahui", + "description": "Kripto sisi klien untuk vault dan data koneksi sensitif.", + "p1": "Untuk pengelola kata sandi, pengelola lingkungan, dan koneksi tersimpan di NoSQL Explorer, aplikasi web menggunakan Web Crypto API browser:", + "items": { + "kdf": "PBKDF2 dengan SHA-256, 100.000 iterasi, dan salt acak menurunkan kunci 256-bit dari kata sandi utama Anda.", + "aes": "AES-256-GCM mengenkripsi data; IV 12-byte acak digunakan untuk setiap enkripsi.", + "scope": "Kunci turunan tetap di browser (tidak dapat diekstrak). Server hanya menerima ciphertext dan IV, bukan kata sandi utama atau rahasia dalam plaintext." + }, + "warning": "Jika Anda lupa kata sandi utama, data terenkripsi tidak dapat dipulihkan. Simpan dengan aman." + }, + "logout": { + "title": "Logout dan data lokal", + "body": "Logout akan menghapus rahasia di memori, status vault, dan (jika berlaku) material kunci utama dari IndexedDB, serta mengakhiri sesi API. Data yang hanya tersimpan di penyimpanan lokal perangkat dapat tetap ada sampai Anda menghapusnya di browser." + } + }, + "tips": { + "title": "Menggunakan MyDevTools dengan aman", + "items": { + "masterPassword": "Gunakan kata sandi utama yang kuat dan unik untuk fitur vault.", + "apiClient": "Perlakukan API Client seperti HTTP client lainnya: hindari menempelkan rahasia produksi ke lingkungan bersama jika Anda tidak tahu siapa yang bisa melihatnya.", + "nosql": "Penjelajah Basis Data terhubung ke database yang Anda konfigurasi; gunakan akun dengan hak minimum dan aturan jaringan bila memungkinkan.", + "json": "Pemformat JSON berjalan lokal di browser—tetap hindari menempelkan data sangat sensitif di mesin bersama.", + "settings": "Sesuaikan tools yang terlihat di Pengaturan agar sidebar tetap fokus." + } + } + }, + "Settings": { + "title": "Pengaturan", + "language": "Bahasa", + "spanish": "Spanyol", + "english": "Inggris", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalan", + "chineseSimplified": "Mandarin (Disederhanakan)", + "czech": "Čeština", + "greek": "ελληνικά", + "german": "German", + "danish": "Bahasa Denmark", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukraina" + }, + "SettingsPage": { + "title": "Pengaturan", + "subtitle": "Kelola pengaturan dan preferensi akun Anda.", + "userProfile": { + "title": "Profil Mitra", + "description": "Informasi pribadi dan detail akun Anda.", + "avatarAlt": "Pengguna", + "anonymousUser": "Pengguna Anonim", + "noEmailProvided": "Tidak ada email yang diberikan", + "notLoggedIn": "Belum masuk" + }, + "tools": { + "title": "Alat & Fitur", + "description": "Pilih alat mana yang muncul di bilah sisi navigasi Anda." + }, + "appearance": { + "title": "Penampilan", + "description": "Sesuaikan tampilan aplikasi di perangkat Anda.", + "themePreference": "Preferensi Tema", + "themes": { + "light": "Cahaya", + "dark": "Gelap", + "system": "Sistem" + } + }, + "language": { + "title": "Bahasa", + "description": "Atur bahasa yang digunakan di seluruh platform.", + "selectLanguage": "Pilih Bahasa", + "selectPlaceholder": "Pilih bahasa", + "helpText": "Mengubah bahasa Anda akan memperbarui menu dan label di seluruh platform.", + "languages": { + "en": "Bahasa Inggris", + "fr": "Français", + "es": "Español (Spanyol)", + "ar": "عربى (Arab)", + "ca": "Català", + "zh": "简体 中文", + "cs": "Čeština", + "el": "Ελληνικά (Yunani)", + "de": "Deutsch (Jerman)", + "da": "Dansk (Denmark)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italia (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Selamat pagi", + "afternoon": "Good afternoon. ", + "evening": "Selamat sore." + }, + "commaName": ", {name}", + "welcomeBack": "Selamat datang kembali. ", + "welcomeBackNamed": "Selamat datang kembali, {name}", + "tagline": "Apa yang ingin Anda bangun hari ini?", + "brandName": "MyDevTools", + "stats": { + "tools": "Alat yang digunakan", + "favorites": "Favorit", + "pinned": "Disematkan", + "recent": "Baru Digunakan" + }, + "sections": { + "recentlyUsed": "Baru Digunakan", + "favorites": "Favorit", + "whatsNew": "Yang baru", + "pinned": "Disematkan" + }, + "groups": { + "apps": "Aplikasi" + }, + "toolCard": { + "defaultDescription": "Jelajahi alat ini untuk fungsionalitas yang lebih baik.", + "launchTool": "Luncurkan Alat" + }, + "tools": { + "toDo": { + "title": "Tugas", + "description": "Kelola daftar tugas Anda secara efisien." + }, + "notes": { + "title": "Ket", + "description": "Tangkap ide dan atur pemikiran Anda." + }, + "passwordManager": { + "title": "Pengelola Kata Sandi", + "description": "Simpan dan kelola kata sandi Anda dengan aman." + }, + "environmentManager": { + "title": "Pengelola lingkungan", + "description": "Simpan variabel bergaya .env per proyek dan lingkungan dengan tag." + }, + "bookmarks": { + "title": "Penanda", + "description": "Kelola dan atur bookmark Anda." + }, + "apiClient": { + "title": "Klien api", + "description": "Uji dan debug permintaan HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Jelajahi basis data MongoDB." + }, + "emailValidator": { + "title": "Validator Email", + "description": "Verifikasi dan validasi alamat email." + }, + "encryptionPlayground": { + "title": "Arena enkripsi", + "description": "AES-GCM dengan kunci mentah atau frasa sandi; paket JSON, Web Crypto hanya di peramban." + }, + "jwtDecoder": { + "title": "Dekoder JWT", + "description": "Decode header, payload, dan kedaluwarsa JWT." + }, + "certificatePemDecoder": { + "title": "Dekoder sertifikat / PEM", + "description": "Dekode sertifikat X.509 dan CSR: subjek, penerbit, masa berlaku, dan SAN." + }, + "sshKeyGenerator": { + "title": "Generator Kunci SSH / RSA", + "description": "Buat pasangan kunci Ed25519 atau RSA di browser. Unduh dalam format OpenSSH dan PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Pemformat GraphQL", + "description": "Format GraphQL dengan penyorotan sintaks dan pembangun kueri sederhana." + }, + "yamlFormatter": { + "title": "Pemformat YAML", + "description": "Format, validasi, dan konversi YAML ke JSON di browser." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Parser URL", + "description": "Uraikan URL menjadi protokol, host, path, parameter kueri, dan hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Rahasia / kunci API", + "description": "String acak kriptografis dengan alfabet, panjang, dan ekspor massal yang dapat dikonfigurasi." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Pemeriksa kontras", + "description": "Rasio kontras WCAG 2.1 teks dan latar (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Generator Skema JSON", + "description": "Turunkan skema JSON dan model bertipe dari contoh JSON." + }, + "imageToBase64": { + "title": "Gambar ke Base64", + "description": "Konversikan gambar ke URI Data atau string Base64 mentah." + }, + "imageCompressor": { + "title": "Kompresor gambar", + "description": "Kompres JPEG, PNG, dan WebP secara lokal dengan penggeser kualitas." + }, + "cssGradientBuilder": { + "title": "Pembuat Gradien CSS", + "description": "Pembuat gradien CSS visual dengan kontrol sudut dan penghentian warna." + }, + "gitignoreGenerator": { + "title": ".gitignore Generator", + "description": "Hasilkan file .gitignore gabungan secara instan dari 500+ tumpukan teknologi." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Ubah file CSV atau Excel menjadi JSON dan ekspor kembali ke CSV atau XLSX." + }, + "snippetManager": { + "title": "Cuplikan kode", + "description": "Simpan cuplikan dengan penyorotan sintaks, deteksi bahasa otomatis, dan pemformatan." + }, + "qrCodeGenerator": { + "title": "Generator kode QR", + "description": "Buat kode QR PNG dari teks atau URL di peramban." + }, + "ipSubnetCalculator": { + "title": "Kalkulator IP / subnet", + "description": "IPv4 dan IPv6 CIDR: netmask, rentang, dan jumlah alamat." + }, + "dnsLookup": { + "title": "Pencarian DNS", + "description": "Kueri rekaman DNS untuk domain apa pun — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Generator hash", + "description": "MD5, SHA-1, dan SHA-2 untuk teks atau file." + }, + "hmacGenerator": { + "title": "Generator HMAC", + "description": "Menandatangani webhook dan menguji integrasi API dengan HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Generator kode TOTP / 2FA", + "description": "Tempel rahasia Base32 dari aplikasi autentikator; kode enam digit diperbarui setiap 30 dtk—berguna untuk menguji login dan MFA." + }, + "markdownPreview": { + "title": "Pratinjau Markdown", + "description": "Renderer Markdown langsung dengan ekspor HTML dan konversi HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Konverter basis angka", + "description": "Konversi bilangan bulat antar basis 2–36." + }, + "formatConverter": { + "title": "Konverter Format", + "description": "Konversi antara JSON, YAML, TOML, dan XML ke segala arah. Semua 12 kombinasi didukung." + }, + "mockDataGenerator": { + "title": "Generator data mock", + "description": "Tentukan kolom, pilih tipe data, dan ekspor set data untuk benih, Postman, atau pengujian. Hingga 5000 baris, semua di peramban — tidak ada unggahan." + }, + "httpStatusCodes": { + "title": "Kode Status HTTP", + "description": "Cari kode status HTTP dengan deskripsi (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Pengoptimal SVG", + "description": "Tempel SVG, buang noise dengan SVGO, dan lihat penghematan ukuran UTF-8." + }, + "unitConverter": { + "title": "Konverter Satuan", + "description": "Konversi antara 323 satuan di 43 kategori ilmiah dan teknik." + }, + "dockerComposeGenerator": { + "title": "Generator Docker Compose", + "description": "Pilih basis data, cache, antrean, proxy, dan image observabilitas; ekspor docker-compose.yml untuk stack lokal." + }, + "game2048": { + "title": "2048", + "description": "Geser dan gabungkan ubin untuk mencapai 2048. Permainan puzzle angka klasik." + }, + "sudoku": { + "title": "Sudoku", + "description": "Teka - teki angka klasik. 3 tingkat kesulitan, masing - masing 150 tahap." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Jelajahi kunci, periksa nilai, dan jalankan perintah di instans Redis mana pun." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Generator & Pemeriksa Bcrypt", + "description": "Hash kata sandi dengan bcrypt dan verifikasi hash yang ada." + }, + "chmodCalculator": { + "title": "Kalkulator Chmod", + "description": "Konversi izin Unix antara oktal, simbolik, dan kotak centang." + }, + "codeScreenshot": { + "title": "Tangkapan Kode", + "description": "Ubah kode sumber menjadi gambar indah yang mudah dibagikan." + }, + "cssGenerators": { + "title": "Generator CSS", + "description": "Generator box-shadow, border-radius, dan clamp() fluid." + }, + "exifViewer": { + "title": "Penampil & Penghapus EXIF", + "description": "Periksa dan hapus metadata EXIF foto secara lokal." + }, + "faviconGenerator": { + "title": "Generator Favicon", + "description": "Buat favicon, ikon aplikasi, dan cuplikan manifest." + }, + "jsonDiff": { + "title": "Diff JSON", + "description": "Perbandingan dua dokumen JSON secara semantik dan sadar struktur." + }, + "jsonpathPlayground": { + "title": "Playground JSONPath", + "description": "Kueri JSON dengan JSONPath atau JMESPath dan lihat hasilnya secara langsung." + }, + "keycodeInspector": { + "title": "Inspektur Keycode", + "description": "Tekan tombol untuk melihat kode event JavaScript dan tombol pengubahnya." + }, + "markdownTableGenerator": { + "title": "Generator Tabel Markdown", + "description": "Buat tabel Markdown dan HTML di editor grid mirip spreadsheet." + }, + "timezoneConverter": { + "title": "Konverter Zona Waktu", + "description": "Konversi satu waktu ke banyak zona dengan petunjuk pergeseran hari dan jam kerja." + }, + "tokenCounter": { + "title": "Penghitung Token LLM", + "description": "Hitung token LLM, visualisasikan potongan, dan perkirakan biaya API." + }, + "webhookTester": { + "title": "Penguji Webhook", + "description": "Tangkap dan periksa permintaan webhook masuk secara real time." + }, + "websocketTester": { + "title": "Penguji WebSocket", + "description": "Sambungkan ke endpoint WebSocket dan bertukar pesan secara real time." + }, + "whoisLookup": { + "title": "Pencarian Whois", + "description": "Cari detail pendaftaran domain dan IP melalui RDAP." + } + }, + "tabs": { + "apps": "Aplikasi", + "analytics": "Analitik", + "ui": "Antarmuka" + }, + "analytics": { + "title": "Data Anda sekilas", + "subtitle": "Jumlah disinkronkan dengan akun Anda di server.", + "loadError": "Tidak dapat memuat analitik.", + "retry": "Coba lagi", + "loginHint": "Masuk untuk melihat analitik penggunaan dan preferensi antarmuka.", + "passwordEntries": "Entri kata sandi", + "bookmarks": "Penanda", + "bookmarkFolders": "Folder penanda", + "tasksTotal": "Tugas", + "tasksCompleted": "Selesai", + "tasksOngoing": "Berjalan", + "tasksNotStarted": "Belum dimulai", + "projects": "Proyek", + "nosqlConnections": "Koneksi basis data", + "notes": "Catatan", + "apiClientCollections": "Koleksi API", + "apiClientEnvironments": "Lingkungan API", + "apiClientHistory": "Entri riwayat API", + "jsonFormatterDocuments": "Dokumen pemformat JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Cuplikan kode", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Preferensi antarmuka", + "subtitle": "Tema, bahasa, dan alat yang terlihat di bilah sisi — dari pengaturan akun.", + "loadError": "Tidak dapat memuat preferensi.", + "loginHint": "Masuk untuk melihat preferensi antarmuka yang disimpan.", + "theme": "Tema", + "accent": "Warna aksen", + "locale": "Bahasa", + "enabledTools": "Alat terlihat", + "favoriteTools": "Alat favorit", + "toolsWithUsage": "Alat dengan pelacakan penggunaan" + }, + "signIn": "Masuk", + "noResults": "Tidak ada alat yang cocok dengan pencarian Anda", + "noResultsHint": "Coba kata kunci lain atau hapus filter.", + "filterAll": "Semua", + "loginCta": { + "description": "Masuk untuk melacak alat yang baru saja Anda gunakan." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Memuat...", + "myTasksTitle": "Tugas Saya", + "statsLine": "{total} tugas • {percent}% selesai", + "statsLineDesktop": "{total} total • {percent}% selesai" + }, + "filters": { + "all": "Semua", + "total": "Total", + "allProjects": "Semua Projek", + "export": "Ekspor", + "kanban": "Kanban", + "list": "Dunia", + "searchPlaceholder": "Cari tugas...", + "searchAria": "Cari tugas", + "clearSearchAria": "Bersihkan pencarian", + "foundTasks": "Ditemukan {count, plural, one {# tugas} other {# tugas}}", + "filterByAllAria": "Saring bedasarkan", + "filterByStatusAria": "Filter menurut {status}" + }, + "status": { + "not-started": { + "label": "Belum Dimulai", + "description": "Tugas yang belum dimulai" + }, + "ongoing": { + "label": "Sedang berlangsung", + "description": "Tugas sedang berlangsung" + }, + "completed": { + "label": "Selesai", + "description": "Tugas yang telah selesai" + } + }, + "priorities": { + "high": "Tinggi", + "medium": "Menengah", + "low": "Rendah" + }, + "form": { + "addNewTaskLabel": "Tambahkan tugas baru", + "multilinePlaceholder": "Tambahkan detail tugas... (Shift+Enter untuk baris baru)", + "singlelinePlaceholder": "Apa yang perlu dilakukan? Tekan Enter untuk menambahkan", + "taskInputAria": "Input tugas", + "projectPlaceholder": "Proyek ", + "selectProject": "Pilih Proyek", + "errorSelectProject": "Pilih proyek atau buat proyek baru untuk menambahkan tugas.", + "hintMultiline": "Shift+Enter untuk baris baru, Enter untuk menambahkan", + "hintSingleline": "Masuk untuk menambahkan", + "shortcutsAria": "Pintasan papan tik", + "shortcutsLabel": "Pintasan", + "shortcutAddTask": "Tambahkan Tugas", + "shortcutNewLine": "Baris baru", + "shortcutClear": "Bening", + "addButton": "Tambah" + }, + "list": { + "ariaLabel": "Daftar tugas", + "loading": "Memuat tugas Anda...", + "emptyTitle": "Tidak ada tugas yang ditemukan", + "emptyPrimary": "Mulai dengan menambahkan tugas pertama Anda di atas! Tekan Enter untuk menambahkan tugas dengan cepat.", + "emptySecondary": "Coba sesuaikan filter atau kueri pencarian Anda." + }, + "mobileDrawer": { + "addTaskSrOnly": "Tambahkan Tugas", + "addNewTaskTitle": "Tambahkan tugas baru", + "addNewTaskDescription": "Buat tugas baru untuk melacak progres Anda.", + "cancel": "Batalkan" + }, + "kanban": { + "ariaLabel": "Kanban Board", + "loading": "Memuat tugas Anda...", + "dropHere": "Lepaskan tugas di sini", + "noTasksYet": "Belum ada tugas", + "emptyColumnAria": "Daftar kolom kosong", + "columnAria": "Kolom {title} dengan {count} tugas", + "countInColumnAria": "{count} tugas di {title}" + }, + "taskItem": { + "confirmDelete": "Anda yakin ingin menghapus ini?", + "statusPlaceholder": "Status", + "changeStatusAria": "Ubah status tugas", + "taskOptionsAria": "Opsi tugas", + "editTask": "Edit tugas", + "copyTask": "Salin Tugas", + "copied": "Disalin!", + "deleteTask": "Hapus tugas", + "statusHeading": "Status", + "editTaskAria": "Edit tugas" + }, + "editDialog": { + "title": "Edit tugas", + "description": "Perbarui detail tugas, tambahkan subtugas, tetapkan prioritas, dan banyak lagi.", + "taskTitleLabel": "Kelompok", + "taskTitlePlaceholder": "Apa yang perlu dilakukan?", + "descriptionLabel": "Deskripsi", + "descriptionPlaceholder": "Tambahkan detail lebih lanjut tentang tugas ini...", + "statusLabel": "Status", + "priorityLabel": "Prioritas", + "dueDateLabel": "tanggal jatuh tempo BG", + "pickDate": "Pilih tanggal", + "clearDate": "tanggal yang jelas", + "projectLabel": "Proyek ", + "selectProjectPlaceholder": "Pilih sebuah berkas pencetak cantik", + "noProject": "Tidak Ada Proyek", + "timeEstimateLabel": "Perkiraan Waktu (menit)", + "timeEstimatePlaceholder": "Berapa lama waktu yang dibutuhkan?", + "tagsLabel": "Tag", + "customTagNamePlaceholder": "Nama tag khusus", + "subtasksLabel": "Subtugas", + "addSubtaskPlaceholder": "Tambahkan subtugas...", + "cancel": "Batalkan", + "saving": "Penghematan...", + "saveChanges": "Simpan Perubahan" + }, + "export": { + "title": "Ekspor Tugas", + "description": "Ekspor tugas Anda ke format JSON, CSV, atau Excel.", + "projectLabel": "Proyek ", + "statusLabel": "Status", + "tagLabel": "Tag", + "allProjects": "Semua Projek", + "allStatuses": "Semua Status", + "allTags": "Semua Tags", + "exportAsJsonTitle": "Ekspor sebagai JSON", + "exportAsJsonDescription": "Ekspor data tugas termasuk subtugas, tag, dan metadata.", + "exportJson": "Ekspor ke JSON", + "exportAsCsvExcelTitle": "Ekspor sebagai CSV / Excel", + "exportAsCsvExcelDescription": "Ekspor tugas untuk aplikasi spreadsheet.", + "exportCsv": "Ekspor CSV", + "exportExcel": "Ekspor excel", + "close": "Tutup", + "toastNoTasks": "Tidak ada tugas untuk diekspor dengan filter saat ini", + "toastExportedJson": "Mengekspor {count} tugas ke JSON!", + "toastExportedCsv": "Mengekspor {count} tugas ke CSV!", + "toastExportedExcel": "Mengekspor {count} tugas ke Excel!", + "toastFailedJson": "Gagal mengekspor ke JSON", + "toastFailedCsv": "Gagal mengekspor CSV", + "toastFailedExcel": "Gagal mengekspor ke Excel", + "unknownProject": "Proyek Tidak Dikenal" + }, + "ack": { + "taskAddedTitle": "Tugas berhasil ditambahkan", + "taskAddedFailedTitle": "Gagal menambahkan tugas", + "taskUpdatedTitle": "Iklan berhasil di perbarui.", + "taskUpdatedFailedTitle": "Gagal memperbarui", + "taskStatusUpdateFailedTitle": "Gagal memperbarui status tugas", + "taskMovedTitle": "Tugas dipindahkan ke {status}", + "tryAgain": "Silakan coba lagi. ", + "taskDeletedTitle": "Tugas \"{text}\" dihapus", + "undo": "Membatalkan", + "taskRestoredTitle": "Tugas dipulihkan", + "taskRestoredSuccessTitle": "Berhasil dipulihkan", + "taskRestoreFailedTitle": "Gagal memulihkan tugas. Silakan coba lagi.", + "taskDeleteFailedTitle": "Gagal menghapus tugas. Silakan coba lagi.", + "tasksImportedTitle": "Berhasil mengimpor {count, plural, one {# tugas} other {# tugas}}!", + "tasksImportFailedTitle": "Gagal mengimpor tugas. Silakan coba lagi.", + "unknownProject": "Proyek Tidak Dikenal", + "projectCreatedTitle": "Proyek berhasil dibuat", + "projectCreateFailedTitle": "Gagal membuat proyek", + "projectUpdatedTitle": "Proyek berhasil diperbarui", + "projectUpdateFailedTitle": "Gagal memperbarui proyek", + "projectDeletedTitle": "Proyek berhasil dihapus", + "projectDeleteFailedTitle": "Gagal menghapus proyek" + } + }, + "Notes": { + "page": { + "loading": "Memuat..." + }, + "layout": { + "mobileNavTitle": "Catatan Navigasi", + "openMenuAria": "Buka menu catatan" + }, + "sidebar": { + "title": "Ket", + "createNoteAria": "Buat catatan", + "searchPlaceholder": "Cari catatan...", + "loading": "Memuat...", + "noNotesFound": "Catatan tidak ditemukan.", + "noNotesYet": "Belum ada catatan. Klik + untuk membuatnya.", + "inParent": "di {parentTitle}", + "untitled": "Tanpa judul", + "addSubPage": "Tambahkan sub - halaman", + "delete": "Hapus ", + "deleteConfirmTitle": "Hapus catatan", + "deleteConfirmDescription": "Anda yakin ingin menghapus \"{title}\"? Tindakan ini tidak dapat dibatalkan.", + "cancel": "Batalkan", + "confirmDelete": "Hapus ", + "noteOptionsAria": "Opsi catatan" + }, + "editor": { + "emptyState": "Pilih catatan atau buat catatan baru untuk memulai.", + "titlePlaceholder": "Tanpa judul", + "saving": "Menyimpan…", + "ready": "Siap", + "saved": "Tersimpan {time}", + "stats": "{words} kata · {read} mnt baca", + "template": "Templat", + "applyTemplate": "Terapkan templat", + "exportMdLabel": ".md", + "exportMd": "Ekspor sebagai Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Ekspor sebagai HTML", + "focusMode": "Mode fokus", + "exitFocusMode": "Keluar dari mode fokus", + "save": "Simpan", + "addTag": "Tambah tag…", + "removeTag": "Hapus tag {tag}", + "loading": "Memuat…", + "searchHint": "untuk mencari", + "focusHint": "mode fokus", + "notePath": "Jalur catatan" + }, + "context": { + "authRequiredError": "Pengguna tidak diautentikasi", + "defaultTitle": "Tanpa judul", + "cloudSyncImageError": "Aktifkan Sinkronisasi Cloud untuk menyimpan gambar catatan. Catatan teks tetap lokal." + } + }, + "PasswordManager": { + "page": { + "loading": "Memuat...", + "title": "Pengelola Kata Sandi", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Gagal mendekripsi kata sandi", + "toastLoadFailed": "Gagal memuat kata sandi", + "toastUnlockedAuto": "Vault dibuka secara otomatis", + "toastAutoUnlockFailed": "Gagal membuka kunci otomatis. Buka kunci secara manual.", + "toastConnectFailed": "“Gagal tersambung ke SERVER\"", + "toastAutoUnlockWarning": "Kubah tidak terkunci, tetapi kunci otomatis mungkin tidak berfungsi saat memuat ulang", + "errorIncorrectPassword": "Kata sandi master salah", + "errorUnlockFailed": "Gagal membuka kunci brankas", + "errorPasswordsMismatch": "Kata sandi tidak cocok", + "errorPasswordTooShort": "Kata sandi minimal terdiri dari 8 karakter", + "errorSetupFailed": "Gagal menyiapkan brankas", + "errorConnectFailed": "“Gagal tersambung ke SERVER\"", + "mobileTitleCreate": "Buat brankas Anda", + "mobileTitleWelcome": "Selamat datang kembali. ", + "mobileSubtitleCreate": "Atur kata sandi utama untuk melindungi kredensial Anda", + "mobileSubtitleUnlock": "Masukkan kata sandi utama Anda untuk membuka kunci", + "placeholderMasterPassword": "Kata sandi induk", + "placeholderConfirmPassword": "Konfirmasi password", + "labelMasterPassword": "Kata sandi induk", + "labelConfirmPassword": "Konfirmasi password", + "placeholderEnterMaster": "Masukkan kata sandi utama", + "placeholderConfirmMaster": "Konfirmasi kata sandi utama", + "buttonCreateVault": "Buat brankas", + "buttonUnlock": "Buka kunci", + "buttonUnlockVault": "Buka kunci brankas", + "footerCreate": "Kata sandi Anda tidak pernah disimpan dan tidak dapat dipulihkan", + "footerUnlock": "Diamankan dengan enkripsi end - to - end", + "desktopTitleSetup": "Siapkan brankas kata sandi", + "desktopTitleUnlock": "Buka kunci brankas", + "desktopDescriptionSetup": "Buat kata sandi utama untuk mengamankan kredensial Anda. Kata sandi ini tidak pernah disimpan dan tidak dapat dipulihkan.", + "desktopDescriptionUnlock": "Masukkan kata sandi utama Anda untuk mengakses kredensial aman Anda." + }, + "list": { + "loading": "Memuat kata sandi Anda...", + "emptyTitle": "Vault kosong", + "emptyHintMobile": "Ketuk tombol + untuk menambahkan kata sandi pertama Anda", + "emptyHintDesktop": "Anda belum menyimpan kata sandi apa pun. Klik tombol \"Tambahkan kata sandi\" di atas untuk memulai.", + "addPassword": "Tambah Kata Sandi", + "searchPlaceholderMobile": "Cari brankas Anda...", + "searchPlaceholderDesktop": "Cari brankas...", + "importExport": "Impor/Ekspor", + "lockVault": "Kunci brankas", + "allPasswordsTitle": "Semua kata sandi", + "passwordCount": "{count, plural, one {# kata sandi} other {# kata sandi}}", + "vaultHealthDrawerTitle": "Kesehatan lemari besi", + "noResults": "Tidak ditemukan kata sandi yang cocok dengan \"{query}\"", + "tableService": "Layanan", + "tableUsername": "Nama pengguna", + "tablePassword": "Kata sandi", + "tableUrl": "URL ", + "tableActions": "Tindakan", + "gridViewAria": "Grid view", + "listViewAria": "List tampilan", + "passwordStrengthTitle": "BagusPassword strength", + "deleteDialogTitle": "Hapus Kata Sandi", + "deleteDialogDescription": "Apakah kamu yakin ingin menghapus Iklan ini? Tindakan ini tidak bisa dibatalkan.", + "cancel": "Batalkan", + "delete": "Hapus ", + "edit": "Edit" + }, + "toasts": { + "copiedPassword": "Kata sandi disalin ke papan klip", + "copiedUsername": "Nama pengguna disalin ke papan klip", + "passwordDeleted": "Kata sandi dihapus", + "deleteFailed": "Gagal menghapus kata sandi", + "vaultLocked": "Kubah terkunci", + "importedCount": "Berhasil mengimpor {count, plural, one {# kata sandi} other {# kata sandi}}" + }, + "form": { + "addTitle": "Kata sandi baru", + "editTitle": "Edit Kata Sandi", + "serviceName": "Nama Layanan", + "usernameEmail": "Nama Pengguna / Email", + "password": "Kata sandi", + "urlOptional": "URL Situs Web (opsional)", + "notesOptional": "Catatn (opsional):", + "tags": "Tag", + "placeholderService": "misalnya Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Masukkan atau buat kata sandi", + "placeholderUrl": "https://...", + "placeholderNotes": "Perincian tambahan", + "placeholderTags": "Tambahkan tag (tekan Enter atau koma)", + "strength": "Kekuatan", + "strengthWeak": "Lemah", + "strengthMedium": "Menengah", + "strengthStrong": "Kuat", + "advancedGenerator": "Generator tingkat lanjut", + "cancel": "Batalkan", + "saving": "Penghematan...", + "savePassword": "Simpan kata sandi", + "updating": "Memperbarui...", + "updatePassword": "Perbarui Kata Sandi", + "addPasswordSrOnly": "Tambah Kata Sandi", + "triggerAdd": "Tambah Kata Sandi", + "toastSaved": "Kata sandi berhasil disimpan", + "toastSaveFailed": "Gagal menyimpan kata sandi", + "toastUpdated": "Kata sandi berhasil diperbarui", + "toastUpdateFailed": "Gagal memperbarui kata sandi" + }, + "security": { + "vaultHealth": "Kesehatan lemari besi", + "statusLabel": "Status", + "totalPasswords": "Total kata sandi", + "activeEntries": "Entri aktif di brankas Anda", + "protected": "Terlindungi", + "weakPasswords": "Kata sandi lemah", + "needingImprovement": "Kata sandi yang perlu ditingkatkan", + "allGood": "Saya nggak masalah. ", + "reusedPasswords": "Kata sandi yang digunakan kembali", + "usedMultipleTimes": "Kata sandi digunakan beberapa kali", + "unique": "Unik", + "total": "Total", + "weak": "Lemah", + "reused": "Digunakan kembali", + "totalCountLabel": "{count, plural, one {# kata sandi} other {# kata sandi}}", + "weakCountLabel": "{count} lemah", + "allSecure": "Semua aman", + "scoreExcellent": "Sempurna", + "scoreGood": "Baik", + "scoreFair": "Lumayan", + "scoreCritical": "Kritikal" + }, + "importExport": { + "title": "Impor / ekspor brankas", + "description": "Kelola data kata sandi Anda. Anda dapat mengekspor brankas Anda ke JSON atau mengimpor dari file JSON.", + "tabExport": "Ekspor", + "tabImport": "Impor", + "warningTitle": "Perhatian", + "warningBody": "File yang diekspor berisi kata sandi Anda dalam teks biasa. Simpan file ini dengan aman dan hapus setelah digunakan.", + "exportBlurb": "Unduh seluruh brankas Anda sebagai file JSON. Ini termasuk semua layanan, nama pengguna, kata sandi, dan catatan.", + "downloadJson": "Unduh ekspor JSON", + "pasteLabel": "Tempel data JSON", + "pastePlaceholder": "[{\"service\": \"Contoh\", \"username\": \"pengguna\", \"password\": \"123\", ...}]", + "pasteHint": "Tempel konten file JSON yang sebelumnya diekspor di sini.", + "importing": "Mengimpor", + "importPasswords": "Impor Kata Sandi", + "trigger": "Impor/Ekspor", + "toastExported": "Vault berhasil diekspor", + "toastExportFailed": "Gagal mengekspor brankas", + "toastImportFailed": "Gagal mengimpor: format JSON tidak valid" + }, + "card": { + "copyUsername": "Salin nama pengguna", + "openUrl": "Buka URL", + "strength": "Kekuatan" + }, + "swipeable": { + "copy": "Tembusan", + "delete": "Hapus ", + "copyPassword": "Salin kata sandi", + "copyUsername": "Salin nama pengguna", + "edit": "Edit", + "deleteAction": "Hapus " + }, + "floatingMenu": { + "lockVault": "Kunci brankas", + "importExport": "Impor/Ekspor", + "addPassword": "Tambah Kata Sandi" + }, + "generator": { + "tabPassword": "Kata sandi", + "tabPassphrase": "Passphrase", + "strength": "Kekuatan", + "lengthLabel": "Panjang: {length} karakter", + "lengthBadge": "{length} karakter", + "characterSets": "Kumpulan karakter", + "uppercase": "Huruf besar", + "lowercase": "Huruf kecil", + "numbers": "Angka", + "symbols": "Simbol", + "wordCountLabel": "Jumlah kata: {count}", + "wordCountBadge": "{count, plural, one {# kata} other {# kata}}", + "separator": "Magnetik", + "capitalization": "Kapitalisasi", + "includeNumber": "Sertakan nomor" + } + }, + "EnvironmentManager": { + "subtitle": "Variabel lingkungan terenkripsi per proyek dan lingkungan.", + "page": { + "loading": "Memuat…", + "title": "Pengelola lingkungan", + "loadFailed": "Gagal memuat set lingkungan" + }, + "list": { + "title": "Lingkungan", + "loading": "Memuat set Anda…", + "emptyTitle": "Belum ada set lingkungan", + "emptyHint": "Buat set untuk setiap proyek dan lingkungan (mis. kunci API produksi). Semua dienkripsi di browser sebelum diunggah.", + "searchPlaceholder": "Cari proyek, lingkungan, tag, catatan…", + "tagFilterPlaceholder": "Saring menurut tag", + "variableCount": "{count, plural, other {# variabel}}", + "updatedAgo": "Diperbarui {time}", + "copyDotEnv": "Salin .env", + "edit": "Edit", + "delete": "Hapus", + "cancel": "Batal", + "confirmDelete": "Hapus", + "deleteDialogTitle": "Hapus set lingkungan?", + "deleteDialogDescription": "Ini menghapus data terenkripsi dari akun Anda. Tidak dapat dibatalkan.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env disalin ke papan klip", + "deleted": "Set lingkungan dihapus", + "deleteFailed": "Gagal menghapus" + }, + "form": { + "addTitle": "Set lingkungan baru", + "editTitle": "Edit set lingkungan", + "triggerAdd": "Tambah set lingkungan", + "addSrOnly": "Tambah set lingkungan", + "project": "Proyek", + "environment": "Lingkungan", + "placeholderProject": "mis. billing-api", + "placeholderEnvironment": "mis. production", + "pasteEnvLabel": "Tempel .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Gabungkan baris yang ditempel", + "variables": "Variabel", + "addRow": "Tambah baris", + "keyPlaceholder": "KUNCI", + "valuePlaceholder": "nilai", + "notesOptional": "Catatan (opsional)", + "placeholderNotes": "Kebijakan rotasi, pemilik, tautan…", + "tags": "Tag", + "placeholderTags": "Tambah tag (Enter atau koma)", + "cancel": "Batal", + "save": "Simpan", + "saving": "Menyimpan…", + "update": "Perbarui", + "updating": "Memperbarui…", + "toastSaved": "Set lingkungan disimpan", + "toastSaveFailed": "Gagal menyimpan", + "toastUpdated": "Set lingkungan diperbarui", + "toastUpdateFailed": "Gagal memperbarui", + "toastPasteEmpty": "Tidak ada baris KUNCI=nilai", + "toastPasteApplied": "{count} variabel digabung dari tempelan", + "toastProjectEnvRequired": "Proyek dan lingkungan wajib diisi", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Memuat..." + }, + "manager": { + "foldersHeading": "Folder", + "tagsHeading": "Tag", + "tagsMore": "+{count} lagi", + "tagsShowLess": "Show less", + "searchPlaceholder": "Cari bookmark Anda...", + "import": "Impor", + "export": "Ekspor", + "exportAsHtml": "Ekspor sebagai HTML", + "exportAsJson": "Ekspor sebagai JSON", + "addBookmark": "Tambah Markah", + "matchingQuery": "mencocokkan \"{query}\"", + "bookmarkCount": "{count, plural, one {# markah} other {# markah}}", + "allBookmarks": "Semua Bookmark", + "uncategorized": "Tidak Dikategorikan", + "unknownFolder": "Tidak diketahui" + }, + "card": { + "urlCopied": "Disalin ke papan klip", + "bookmarkDeleted": "Bookmark dihapus", + "edit": "Edit", + "delete": "Hapus ", + "copyUrl": "Salin URL", + "deleteConfirmTitle": "Hapus markah", + "deleteConfirmDescription": "Ini akan menghapus \"{title}\" secara permanen.", + "cancel": "Batalkan" + }, + "grid": { + "emptyTitle": "Belum ada markah?", + "emptyDescription": "Tambahkan bookmark pertama Anda atau impor dari browser Anda untuk memulai." + }, + "folderTree": { + "allBookmarks": "Semua Bookmark", + "addSubfolder": "Tambahkan Subfolder", + "rename": "Ubah nama", + "deleteFolderTitle": "Hapus folder \"{name}\"?", + "deleteFolderDescription": "Ini akan menghapus folder dan semua subfoldernya. Bookmark di folder ini akan dipindahkan ke Uncategorized." + }, + "import": { + "title": "Impor Markah", + "description": "Impor bookmark dari file yang diekspor browser Anda.", + "parseError": "Gagal mengurai file bookmark. Pastikan itu adalah ekspor bookmark yang valid.", + "successToast": "Mengimpor {bookmarks} markah dan {folders} folder", + "errorToast": "Gagal mengimpor bookmark", + "chromeInstruction": "Menu → Bookmarks → Bookmark Manager → ‡ → Ekspor Bookmarks", + "firefoxInstruction": "Menu → Bookmark → Mengelola → Impor Bookmark dan → Ekspor Cadangan", + "safariInstruction": "File → Ekspor Bookmark...", + "edgeInstruction": "→ Favorit Menu → ‡ Favorit → Ekspor", + "dropZoneTitle": "Letakkan file bookmark Anda di sini", + "dropZoneHint": "atau klik untuk menelusuri (HTML atau JSON)", + "selectedFileHint": "Klik untuk memilih file lain", + "errorTitle": "ERROR", + "previewTitle": "Impor Pratinjau", + "previewBookmarks": "{count, plural, one {# markah} other {# markah}}", + "previewFolders": "{count, plural, one {# folder} other {# folder}}", + "importModeLabel": "Mode Impor", + "modeMerge": "Gabungkan dengan bookmark yang ada", + "modeReplace": "Ganti semua bookmark yang ada", + "cancel": "Batalkan", + "importButton": "Impor", + "importButtonWithCount": "Impor {count, plural, one {# markah} other {# markah}}" + }, + "addBookmark": { + "titleAdd": "Tambah Markah", + "titleEdit": "Sunting Markah", + "urlLabel": "URL ", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Masukkan URL yang valid", + "titleLabel": "Jabatan", + "titlePlaceholder": "Judul Marka", + "titleRequired": "Silakan masukkan judul.", + "descriptionLabel": "Deskripsi (optional)", + "descriptionPlaceholder": "Tambahkan catatan tentang bookmark ini", + "folderLabel": "Folder", + "folderPlaceholder": "Pilih folder", + "noFolder": "Tidak ada folder", + "tagsLabel": "Tag", + "tagsPlaceholder": "Tambahkan tag (tekan Enter)", + "toastUpdated": "Bookmark diperbarui", + "toastAdded": "Bookmark ditambahkan", + "cancel": "Batalkan", + "submitAdd": "Tambah Markah", + "submitUpdate": "Perbarui Bookmark" + }, + "addFolder": { + "title": "Buat folder", + "nameLabel": "Nama Folder", + "namePlaceholder": "Folder Saya", + "nameRequired": "Masukkan nama folder", + "parentLabel": "Folder Induk", + "parentPlaceholder": "Pilih folder induk", + "rootParent": "Akar (Tanpa induk)", + "toastCreated": "Folder dibuat", + "cancel": "Batalkan", + "submit": "Buat folder" + }, + "editFolder": { + "title": "Ganti nama folder", + "nameLabel": "Nama Folder", + "namePlaceholder": "Nama folder", + "nameRequired": "Masukkan nama folder", + "toastRenamed": "Folder diganti namanya", + "cancel": "Batalkan", + "save": "Simpan" + } + }, + "EmailValidator": { + "header": { + "title": "Validator Email", + "description": "Verifikasi dan validasi alamat email untuk sintaks, domain, dan kemampuan pengiriman." + }, + "tabs": { + "single": "Verifikasi Tunggal", + "bulk": "Unggahan Massal" + }, + "single": { + "cardTitle": "Verifikasi Email Tunggal", + "cardDescription": "Masukkan alamat email untuk memeriksa kemampuan pengiriman dan reputasinya.", + "placeholder": "Masukkan alamat Email", + "clearAria": "Hapus email", + "validate": "Validasi", + "validating": "Validating", + "formatError": "Isi alamat email yang valid" + }, + "toasts": { + "errorTitle": "ERROR", + "enterEmail": "Silakan masukkan alamat email", + "invalidFormatTitle": "Format email tidak benar", + "invalidFormatDescription": "Isi alamat email yang valid", + "validateFailedDescription": "Terjadi kesalahan saat memvalidasi email", + "noEmailsTitle": "Tidak Ditemukan Email", + "noEmailsDescription": "Pastikan Excel Anda memiliki kolom 'email '.", + "fileTooLargeTitle": "Berkas terlalu besar", + "fileTooLargeDescription": "Ditemukan {total} email. Hanya 4000 pertama yang akan divalidasi.", + "successTitle": "Berhasil", + "successValidated": "{count} email berhasil divalidasi.", + "batchFailedTitle": "Validasi Batch Gagal", + "batchFailedDescription": "Terjadi kesalahan saat memproses validasi massal.", + "noExportTitle": "Tidak Ada Data untuk Diekspor", + "noExportValidOnly": "Tidak ditemukan email yang valid untuk diekspor.", + "noExportAll": "Tidak ada hasil untuk diekspor.", + "exportSuccessTitle": "Ekspor Berhasil", + "exportSuccessExcelAll": "Mengekspor {count} email ke Excel.", + "exportSuccessExcelValid": "Mengekspor {count} email valid ke Excel.", + "exportSuccessCsvAll": "Mengekspor {count} email ke CSV.", + "exportSuccessCsvValid": "Mengekspor {count} email valid ke CSV." + }, + "disposableAlert": { + "title": "Email Sekali Pakai Terdeteksi", + "description": "Alamat email ini milik penyedia email sekali pakai. Mungkin bersifat sementara atau tidak aman." + }, + "score": { + "reliabilityTitle": "Skor Reliabilitas", + "scoreLabel": "Skor" + }, + "details": { + "sectionTitle": "Detail Validasi" + }, + "validation": { + "syntaxLabel": "Sintaksis Valid", + "syntaxTooltip": "Memeriksa apakah alamat email mengikuti format yang benar (misalnya, user@domain.com)", + "domainLabel": "Domain Ada", + "domainTooltip": "Memverifikasi bahwa nama domain di alamat email benar - benar ada", + "mxLabel": "MX Records Ditemukan", + "mxTooltip": "Memeriksa apakah domain memiliki catatan pertukaran surat (MX) yang dikonfigurasi untuk menerima email", + "mailboxLabel": "Kotak Surat Ada", + "mailboxTooltip": "Memverifikasi bahwa kotak surat/akun tertentu ada di server email", + "disposableLabel": "Email Sekali Pakai", + "disposableTooltip": "Menunjukkan apakah email ini milik layanan email sementara/sekali pakai (mungkin berisiko)", + "roleBasedLabel": "Email Berbasis Peran", + "roleBasedTooltip": "Mengidentifikasi apakah ini adalah email berbasis peran (misalnya, dukungan@, info@) daripada alamat pribadi" + }, + "copy": { + "emailCopied": "Email disalin!", + "copyTooltip": "Salin email" + }, + "bulk": { + "dropHere": "Drop File Foto Anda disini", + "dragHere": "Tarik dan lepas file Anda di sini", + "uploadHint": "Unggah file .xlsx, .xls, atau .csv yang berisi kolom 'email' untuk memvalidasi hingga 4000 email.", + "filePrefix": "Berkas:", + "selectFile": "Pilih FIle", + "downloadTemplate": "Unduh Template" + }, + "bulkLoading": { + "title": "Memvalidasi Email...", + "batchProgress": "Memproses batch {current} dari {total}..." + }, + "stats": { + "totalProcessed": "Total Diproses", + "valid": "Berlaku", + "risky": "Risky ", + "invalid": "Tidak valid" + }, + "results": { + "detailedTitle": "Hasil Terperinci", + "searchPlaceholder": "Cari email...", + "newVerification": "Verifikasi Baru", + "exportAll": "Ekspor Semua", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Ekspor valid ({count})" + }, + "showingResults": "Menampilkan {shown} dari {total} hasil", + "table": { + "email": "Email", + "status": "Status", + "score": "Skor", + "checks": "Pengecekan" + }, + "noSearchResults": "Tidak ada hasil yang cocok dengan \"{query}\"", + "checksTooltip": { + "syntax": "Syntax", + "domain": "Domain", + "mx": "MX", + "mailbox": "Kotak-surat" + }, + "exportColumns": { + "email": "Email", + "status": "Status", + "score": "Skor", + "syntax": "Syntax", + "domain": "Domain", + "mx": "MX", + "mailbox": "Kotak-surat", + "disposable": "Sekali pakai", + "roleBased": "Berbasis Peran", + "valid": "Berlaku", + "invalid": "Tidak valid", + "yes": "Ya", + "no": "No" + }, + "sheets": { + "validEmails": "Email yang Valid", + "results": "Hasil", + "template": "Templat" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Explorer", + "heroSubtitle": "Sambungkan ke database Anda untuk mengelola koleksi, menjalankan kueri, dan memvisualisasikan data Anda.", + "featureMultiTitle": "Multi - Koneksi", + "featureMultiDesc": "Kelola beberapa koneksi database secara bersamaan.", + "featureMongoTitle": "Dukungan MongoDB", + "featureMongoDesc": "Dukungan asli untuk MongoDB dengan penyaringan lanjutan.", + "featureQueryTitle": "Smart Query", + "featureQueryDesc": "Pembuat kueri yang kuat dengan penyorotan sintaks.", + "connectCta": "Hubungkan ke MongoDB", + "footerNote": "Saat ini mendukung {db}. Database lainnya segera hadir.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Pilih Koleksi", + "selectCollectionDesc": "Pilih koleksi dari bilah sisi untuk melihat dokumen dan mulai menjelajahi data Anda.", + "dialogAddConnection": "Tambah koneksi", + "toastConnectionAdded": "Koneksi ditambahkan", + "toastFetchFailed": "Gagal mengambil dokumen untuk {collection}", + "toastDocDeleted": "Dokumen dihapus", + "deleteDocTitle": "Apakah Anda benar yakin?", + "deleteDocDescription": "Tindakan ini tidak dapat dibatalkan. Ini akan menghapus dokumen secara permanen dari koleksi Anda.", + "closeAllTitle": "Tutup semua tab", + "closeAllDescription": "Anda yakin ingin menutup semua tab? Setiap perubahan yang belum disimpan dalam filter kueri akan hilang.", + "closeAllConfirm": "Tutup Semua", + "cancel": "Batalkan", + "delete": "Hapus " + }, + "connection": { + "titleEdit": "Edit Koneksi", + "titleConnect": "Hubungkan ke MongoDB", + "subtitleEdit": "Perbarui detail koneksi Anda di bawah ini.", + "subtitleConnect": "Masukkan string koneksi MongoDB Anda untuk mulai menjelajahi data Anda.", + "labelName": "Name", + "placeholderName": "misalnya Production DB", + "labelConnectionString": "String koneksi", + "placeholderConnectionString": "mongodb :// localhost:27017", + "hintFormat": "Format: mongodb :// username:password@host:port/database", + "testConnection": "Tes Koneksi", + "connecting": "Menghubungkan...", + "updateConnect": "Perbarui & Hubungkan", + "connect": "Hubungkan", + "cancelEdit": "Batalkan Edit", + "savedConnections": "Koneksi Tersimpan", + "emptySaved": "Belum ada koneksi yang tersimpan.", + "lastUsed": "Terakhir digunakan {time}", + "toastTestOk": "Koneksi berhasil!", + "toastTestFail": "Koneksi gagal: {message}", + "toastUpdated": "Koneksi diperbarui", + "confirmDelete": "Anda yakin ingin menghapus koneksi yang disimpan ini?", + "toastDeletedConn": "Koneksi dihapus", + "toastDeleteFail": "Gagal menghapus koneksi", + "copyStringTitle": "String koneksi", + "toastStringCopied": "String koneksi disalin!", + "editTitle": "Edit", + "deleteTitle": "Hapus " + }, + "sidebar": { + "explorer": "Penjelajah", + "add": "Tambah", + "tooltipAdd": "T_ambah Koneksi Baru", + "tooltipRefresh": "Segarkan koneksi", + "searchPlaceholder": "Cari...", + "loadingConnections": "Memuat sambungan...", + "noConnectionsFound": "Koneksi tidak ditemukan", + "toastLoadFail": "Gagal memuat koneksi", + "toastConnectFail": "Gagal terhubung ke {name}", + "toastCollectionsFail": "Gagal mengambil koleksi untuk {name}", + "toastRenamed": "Koneksi diganti namanya", + "toastRenameFail": "Gagal mengganti nama koneksi", + "confirmDeleteConnection": "Anda yakin ingin menghapus koneksi \"{name}\"?", + "toastDeleted": "Koneksi dihapus", + "toastDeleteConnFail": "Gagal menghapus koneksi", + "confirmDropDb": "Anda yakin ingin menghapus basis data \"{name}\"? Tindakan ini tidak dapat dibatalkan.", + "toastDbDropped": "Basis data {name} dihapus", + "confirmDropCollection": "Anda yakin ingin menghapus koleksi \"{name}\"? Tindakan ini tidak dapat dibatalkan.", + "toastCollectionDropped": "Koleksi {name} dihapus", + "toastCollectionRenamed": "Koleksi diganti namanya menjadi {name}", + "toastDatabaseRenamed": "Basis data diubah namanya menjadi {name}", + "connecting": "Menghubungkan...", + "connectionFailed": "Connection failed. ", + "noDatabases": "Tidak ada basis data", + "loading": "Memuat...", + "noCollections": "Tidak ada koleksi", + "renameCollectionTitle": "Ganti Nama Koleksi", + "renameDatabaseTitle": "Ganti Nama Basis Data", + "placeholderNewCollection": "Nama koleksi baru", + "placeholderNewDatabase": "Nama database baru", + "rename": "Ubah nama", + "refresh": "Segarkan", + "copyDbName": "Salin Nama DB", + "toastDbNameCopied": "Nama basis data disalin ke clipboard", + "dropDatabase": "Jatuhkan Basis Data", + "dropCollection": "Jatuhkan Koleksi", + "menuRename": "Ubah nama", + "menuCopyConnectionString": "String koneksi", + "menuDeleteConnection": "Hapus Koneksi", + "toastSidebarStringCopied": "String koneksi disalin!", + "cancel": "Batalkan", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} dokumen", + "table": "Daftar", + "json": "JSON", + "tree": "Pohon", + "tooltipTable": "Tampilan Tabel", + "tooltipJson": "Tampilan JSON", + "tooltipTree": "Tampilan Pohon", + "itemsPerPage": "Item Per Halaman", + "perPage": "{n} / halaman", + "prevPage": "Halaman Sebelumnya", + "nextPage": "Halaman Berikutnya", + "expandAll": "Tampikan Semua", + "collapseAll": "Perkecil Semua", + "refreshData": "Segarkan Data", + "insert": "Masukkan", + "export": "Ekspor", + "actions": "Tindakan", + "invalidJson": "Konten JSON tidak valid", + "copiedClipboard": "Disalin ke papan klip", + "docUpdated": "Dokumen berhasil diperbarui", + "docInserted": "Dokumen berhasil dimasukkan", + "invalidJsonShort": "JSON tidak valid.", + "docCopied": "Dokumen disalin ke clipboard", + "openFullEditor": "Buka dalam T_erminal", + "cellArray": "Array({length})", + "cellObject": "Objek", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Tidak ada dokumen yang cocok dengan kueri Anda", + "emptyFilterHint": "Coba sesuaikan filter Anda atau kosongkan kueri.", + "clearFilter": "Hapus Filter", + "emptyCollectionTitle": "Koleksi ini kosong", + "emptyCollectionHint": "Masukkan dokumen untuk memulai.", + "insertDocument": "Sisipkan Dokumen", + "formatJson": "Format JSON", + "compactJson": "Compact JSON", + "copyClipboard": "Salin ke Papan Klip", + "copyJson": "Salin JSON", + "duplicate": "Duplikat", + "editDoc": "Edit Dokumen", + "deleteDoc": "Hapus Dokumen", + "documentLabel": "Dokumen {n}", + "editDialogTitle": "Edit Dokumen", + "insertDialogTitle": "Sisipkan Dokumen", + "viewValueTitle": "Lihat Nilai", + "saveChanges": "Simpan Perubahan", + "close": "Tutup", + "cancel": "Batalkan", + "insertButton": "Masukkan", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Filter aktif diterapkan", + "filterActiveTooltip": "Filter aktif", + "closeAll": "Tutup Semua" + }, + "query": { + "migrateHistory": "Riwayat kueri yang dimigrasikan ke cloud", + "saveHistoryFail": "Gagal menyimpan riwayat kueri", + "deleteQueryFail": "Gagal menghapus kueri", + "invalidJsonQuery": "Kueri JSON tidak valid", + "placeholder": "mis. {\"status\": \"active\"}", + "clearQuery": "Hapus kueri", + "openAdvanced": "Buka Penyunting Lanjutan", + "visualBuilder": "Pembuat Kueri Visual", + "filterRules": "Aturan Filter", + "addRule": "Tambahkan Aturan", + "noFilters": "Tidak ada filter yang diterapkan. Tambahkan aturan untuk memfilter dokumen.", + "where": "TEMPAT ACARA", + "and": "DAN", + "fieldPlaceholder": "Bidang ", + "opPlaceholder": "Op.", + "typePlaceholder": "Tipe", + "opRegex": "Regex", + "opIn": "Di ", + "opNin": "BUKAN DI", + "opExists": "Ada", + "typeAuto": "Auto", + "typeString": "Tali", + "typeNumber": "Nomor", + "typeBoolean": "Boolean", + "typeDate": "Tanggal", + "typeObjectId": "ObjectId", + "true": "Benar", + "false": "Salah", + "valuePlaceholder": "Value", + "valueListPlaceholder": "value1, value2", + "clearFilters": "Hapus Filter", + "applyFilters": "Gunakan Filter ", + "queryHistory": "Riwayat Kueri", + "noSavedQueries": "Belum ada kueri tersimpan", + "advancedEditorTitle": "Editor Kueri Lanjutan", + "templatesPlaceholder": "Templat", + "templateEmptyAgg": "Agregasi Kosong", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Gabung)", + "templateGroup": "$grup (Hitungan/Jumlah)", + "format": "Format", + "formattedJson": "JSON Berformat", + "runQuery": "Jalankan Kueri" + }, + "export": { + "selectFieldsError": "Pilih setidaknya satu bidang untuk diekspor", + "success": "Berhasil diekspor ke {filename}", + "fail": "Gagal mengekspor", + "title": "Ekspor data...", + "formatLabel": "Format Ekspor", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Pilih Bidang", + "selectAll": "Pilih Semua", + "fieldsSelected": "{count, plural, one {# bidang dipilih} other {# bidang dipilih}}", + "cancel": "Batalkan", + "export": "Ekspor", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Tipe: {type}", + "itemSummary": "{count, plural, one {# item} other {# item}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Permintaan baru", + "importedRequest": "Permintaan yang Diimpor", + "untitledRequest": "Permintaan Tanpa Judul" + }, + "toasts": { + "curlImported": "Berhasil diimpor.", + "curlParseFailed": "Gagal mengurai perintah cURL", + "invalidJsonBody": "Isi JSON tidak valid", + "requestFailed": "Permintaan gagal: {message}", + "curlPasted": "cURL berhasil ditempelkan dan diuraikan", + "responseCopied": "Tanggapan disalin ke clipboard", + "codeCopied": "Disalin ke papan klip", + "copyFailed": "Gagal menyalin ke papan klip" + }, + "layout": { + "collections": "Penagihan", + "closeSidebar": "Tutup bilah sisi", + "openSidebar": "Buka bilah sisi", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Metode ", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Mengirim...", + "send": "Kirim", + "invalidJsonBodyHelp": "Tidak dapat mengirim: isi JSON tidak valid" + }, + "requestTabs": { + "params": "parameter", + "headers": "Header", + "body": "Body", + "auth": "Auth", + "bodyType": "tipe bodi", + "selectType": "Pilih jenis", + "bodyNone": "Tidak ada", + "bodyJson": "JSON", + "bodyText": "Teks", + "noBodyHint": "Tidak ada isi untuk permintaan ini", + "switchToJson": "Beralih ke badan JSON", + "authType": "Jenis autentikasi", + "noAuth": "Tidak ada auth", + "bearerToken": "Token pembawa", + "basicAuth": "Autentikasi dasar", + "apiKey": "Kunci API", + "labelToken": "Token", + "placeholderBearerToken": "Token pembawa", + "labelUsername": "Nama pengguna", + "placeholderUsername": "Nama pengguna", + "labelPassword": "Kata sandi", + "placeholderPassword": "Kata sandi", + "labelKey": "Tombol", + "placeholderApiKeyName": "Nama kunci (misalnya X - Api - Key)", + "labelValue": "Value", + "placeholderValue": "Value", + "addTo": "Tambahkan ke", + "header": "Header", + "queryParams": "Parameter kueri", + "noAuthHint": "Permintaan ini tidak menggunakan autentikasi." + }, + "responsePanel": { + "emptyTitle": "Siap Untuk Mengeksplorasi Web3?", + "emptyHint": "Masukkan URL dan klik Kirim untuk melihat data respons di sini.", + "responsePreviewAlt": "Pratinjau respons", + "previewUnavailable": "Pratinjau tidak tersedia untuk jenis konten ini", + "status": "Status", + "time": "Waktu", + "size": "Ukuran", + "copyBody": "Salin isi", + "downloadResponse": "Download response", + "bodyTab": "Body", + "previewTab": "Preview", + "headersTab": "Headers", + "binaryRawView": "Binary data cannot be displayed in raw view.", + "errorStatusLabel": "Error", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "New tab" + }, + "collectionsSidebar": { + "title": "API resources", + "newCollection": "New collection", + "tabCollections": "Collections", + "tabHistory": "History", + "noCollectionsTitle": "No collections", + "noCollectionsHint": "Create a collection to organize your API requests.", + "createCollection": "Create collection", + "newFolder": "New folder", + "rename": "Rename", + "delete": "Delete", + "emptyCollection": "Empty collection", + "recentRequests": "Recent requests", + "clearAll": "Clear all", + "noHistoryTitle": "No history yet", + "noHistoryHint": "Send a request to see it here.", + "dialogNewFolderTitle": "New folder", + "dialogNewFolderDescription": "Create a new folder to organize your requests.", + "labelFolderName": "Folder name", + "placeholderFolderName": "My folder", + "createFolder": "Create folder", + "dialogNewCollectionTitle": "New collection", + "dialogNewCollectionDescription": "Create a new top-level collection.", + "labelCollectionName": "Collection name", + "placeholderCollectionName": "My collection", + "dialogRenameCollectionTitle": "Rename collection", + "dialogRenameCollectionDescription": "Enter a new name for the collection.", + "placeholderRenameCollection": "Collection name", + "renameAction": "Rename", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "New folder", + "delete": "Delete", + "emptyFolder": "Empty folder" + }, + "environmentManager": { + "noEnvironment": "No environment", + "manageDialogTitle": "Manage environments", + "placeholderNewEnv": "New environment", + "variablesHeading": "{name} variables", + "addVariable": "Add variable", + "columnVariable": "Variable", + "columnValue": "Value", + "placeholderKey": "Key", + "placeholderValue": "Value", + "selectEnvToEdit": "Select an environment to edit" + }, + "codeGenerator": { + "triggerTitle": "Generate code", + "dialogTitle": "Generate code", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Copy", + "errorGenerating": "Error generating code" + }, + "keyValueEditor": { + "columnKey": "Key", + "columnValue": "Value", + "placeholderKey": "Key", + "placeholderValue": "Value", + "addItem": "Add item" + }, + "importCurl": { + "trigger": "Import cURL", + "dialogTitle": "Import cURL", + "dialogDescription": "Paste your cURL command below to import it as a new request.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Import" + }, + "saveRequest": { + "trigger": "Save", + "dialogTitle": "Save request", + "dialogDescription": "Save this request to a collection folder.", + "labelName": "Request name", + "placeholderName": "My request", + "labelFolder": "Folder", + "placeholderFolder": "Select a folder", + "save": "Save" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Edit Mode", + "viewOnlyMode": "View Only Mode", + "insertComponent": "Insert component", + "addUnorderedList": "Add unordered list", + "addOrderedList": "Add ordered list", + "addTable": "Add table" + }, + "addBlock": "Add block", + "media": { + "popoverTitle": "Add media", + "singleImage": "Single Image", + "singleImageDesc": "Upload one image", + "multipleImages": "Multiple Images", + "multipleImagesDesc": "Upload image grid", + "video": "Video", + "videoDesc": "Upload video file", + "uploadingAlt": "Uploading...", + "uploadFailedAlt": "Upload failed" + }, + "export": { + "fabAria": "Export code", + "title": "Export Code", + "description": "Copy the HTML or JSON output of your editor content", + "preview": "Preview", + "html": "HTML", + "json": "JSON", + "previewOptions": "Preview Options", + "enhanceSpaces": "Enhance Spaces", + "livePreview": "Live preview of rendered HTML", + "htmlTailwind": "HTML with Tailwind CSS classes", + "editorJson": "Editor state as JSON", + "copyHtml": "Copy HTML", + "copyJson": "Copy JSON", + "copied": "Copied!" + }, + "template": { + "fabTitle": "Switch Template", + "title": "Choose a Template", + "description": "Select a template to get started quickly with pre-built layouts and content", + "catAll": "All Templates", + "catProductivity": "Productivity", + "catCreative": "Creative", + "catBusiness": "Business", + "catPersonal": "Personal", + "noTemplates": "No templates found in this category", + "confirmTitle": "Replace Existing Content?", + "confirmDescription": "You have existing content in the editor. Applying this template will replace all current content.", + "cannotUndo": "This action cannot be undone.", + "cancel": "Cancel", + "replaceContent": "Replace Content" + }, + "command": { + "searchPlaceholder": "Search commands...", + "noResults": "No commands found.", + "turnInto": "Turn into", + "h1": { + "label": "Heading 1", + "description": "Big section heading", + "keywords": "h1,heading,title,large" + }, + "h2": { + "label": "Heading 2", + "description": "Medium section heading", + "keywords": "h2,heading,subtitle" + }, + "h3": { + "label": "Heading 3", + "description": "Small section heading", + "keywords": "h3,heading,subheading" + }, + "h4": { + "label": "Heading 4", + "description": "Tiny section heading", + "keywords": "h4,heading" + }, + "h5": { + "label": "Heading 5", + "description": "Smaller heading", + "keywords": "h5,heading" + }, + "h6": { + "label": "Heading 6", + "description": "Smallest heading", + "keywords": "h6,heading" + }, + "p": { + "label": "Paragraph", + "description": "Regular text paragraph", + "keywords": "p,paragraph,text,normal" + }, + "code": { + "label": "Code Block", + "description": "Code snippet", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "Quote", + "description": "Block quote", + "keywords": "quote,blockquote,citation" + }, + "li": { + "label": "Bulleted List", + "description": "Simple list item with bullet", + "keywords": "list,bullet,unordered,ul,li" + }, + "ol": { + "label": "Numbered List", + "description": "Numbered list item", + "keywords": "list,numbered,ordered,ol,li" + }, + "img": { + "label": "Image", + "description": "Upload or embed an image", + "keywords": "image,img,picture,photo,upload" + }, + "video": { + "label": "Video", + "description": "Upload or embed a video", + "keywords": "video,vid,movie,mp4,upload" + }, + "table": { + "label": "Table", + "description": "Create a table", + "keywords": "table,grid,rows,columns,cells" + } + }, + "toasts": { + "tableImported": "Table Imported", + "tableImportedDesc": "Markdown table has been imported successfully", + "mediaPasted": "Media pasted", + "pasteMixed": "{imageCount} image(s) and {videoCount} video(s) pasted successfully.", + "pasteImages": "{count} image(s) pasted successfully.", + "pasteVideos": "{count} video(s) pasted successfully.", + "pasteFailed": "Paste failed", + "unexpectedError": "An unexpected error occurred", + "uploadFailed": "Upload failed" + }, + "blockBackground": { + "menuItem": "Change Background Color", + "dialogTitle": "Block Background Color", + "dialogDescription": "Choose a background color for this block", + "tabPreset": "Preset Colors", + "tabCustom": "Custom Color", + "sectionHeading": "Background Colors", + "apply": "Apply", + "colors": { + "none": "None", + "red": "Red", + "orange": "Orange", + "yellow": "Yellow", + "green": "Green", + "blue": "Blue", + "indigo": "Indigo", + "purple": "Purple", + "pink": "Pink", + "teal": "Teal", + "cyan": "Cyan", + "gray": "Gray" + } + }, + "tableDialog": { + "title": "Create Table", + "description": "Create a new table or import from markdown", + "markdownCheckbox": "I have a markdown table", + "pasteMarkdownLabel": "Paste Markdown Table", + "markdownPlaceholder": "| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "Paste your markdown table above. Must include header and separator rows.", + "rows": "Rows", + "columns": "Columns", + "maxDimensions": "Maximum: 20 rows × 10 columns", + "cancel": "Cancel", + "importTable": "Import Table", + "createTable": "Create Table" + }, + "tableParse": { + "errors": { + "minRows": "Table must have at least a header row and separator row", + "pipeFormat": "Table rows must start and end with |", + "headerMinCol": "Header row must have at least one column", + "separatorRow": "Second row must be a separator (e.g., |---|---|)", + "rowColumnMismatch": "Row {row} has {got} columns, expected {expected}", + "parseFailed": "Failed to parse markdown table" + } + }, + "link": { + "editLink": "Edit Link", + "addLink": "Add Link", + "selectedText": "Selected text: \"{text}\"", + "linkUrl": "Link URL", + "urlPlaceholder": "https://example.com", + "update": "Update", + "addLinkButton": "Add Link", + "toastApplied": "Link Applied", + "toastLinkedTo": "Linked to: {url}", + "toastRemoved": "Link Removed", + "toastRemovedDesc": "Link has been removed from selection" + }, + "customClass": { + "devMode": "Dev Mode", + "toggleDevModeAria": "Toggle dev mode", + "searchPlaceholderDev": "Search classes... (e.g., 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Search styles... (e.g., 'red', 'bold', 'shadow')", + "appliesTitle": "Applies: {className}", + "noMatches": "No classes found matching \"{query}\"", + "toastClassReplaced": "Class Replaced", + "toastClassReplacedDesc": "Replaced \"{replaced}\" with \"{className}\"", + "toastCustomClassApplied": "Custom Class Applied", + "toastAppliedClass": "Applied class: {className}" + }, + "insertComponents": { + "title": "Insert Component", + "description": "Choose a component to insert into your document", + "noComponents": "No components available", + "categories": { + "media": "Media", + "layout": "Layout", + "interactive": "Interactive", + "decoration": "Decoration" + }, + "freeImage": { + "name": "Free Movement Image", + "description": "Add an image that can be positioned anywhere and resized freely" + } + } + }, + "JsonFormatter": { + "title": "Pemformat JSON", + "description": "Format, validasi, dan edit JSON dalam tampilan teks dan pohon.", + "documentNameText": "Dokumen teks {n}", + "documentNameTree": "Dokumen pohon {n}", + "newDocument": "Baru", + "copy": "Salin", + "save": "Simpan", + "load": "Muat", + "saving": "Menyimpan...", + "leftDocNameLabel": "Nama dokumen kiri", + "rightDocNameLabel": "Nama dokumen kanan", + "toastCopiedText": "Panel teks disalin", + "toastCopiedTree": "Panel pohon disalin", + "toastCopyFailed": "Gagal menyalin JSON", + "toastLoginRequired": "Silakan masuk untuk menyimpan dokumen", + "toastSaved": "Dokumen disimpan", + "toastSaveFailed": "Gagal menyimpan dokumen", + "format": "Format", + "compact": "Padatkan", + "sortKeys": "Urutkan kunci", + "repair": "Perbaiki", + "query": "Kueri", + "toastInvalidJson": "JSON tidak valid", + "toastAlreadyValid": "JSON sudah valid", + "toastRepaired": "JSON diperbaiki", + "toastRepairFailed": "Tidak dapat memperbaiki JSON" + }, + "JwtDecoder": { + "title": "Dekoder JWT", + "subtitle": "Periksa header dan payload. Kedaluwarsa menggunakan klaim exp. Tidak ada yang dikirim ke server; tanda tangan tidak diverifikasi.", + "tokenLabel": "Token", + "clear": "Bersihkan", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Salin", + "expiryLabel": "Kedaluwarsa (exp)", + "signaturePresent": "Tanda tangan ada (tidak diverifikasi)", + "noExp": "Tidak ada klaim exp di payload.", + "status": { + "expired": "Kedaluwarsa", + "valid": "Valid" + }, + "fields": { + "unix": "Unix", + "local": "Lokal", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Diterbitkan (iat)", + "notBefore": "Tidak sebelum (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Tempel JWT untuk didekode.", + "segments": "JWT butuh setidaknya header dan payload (dua segmen).", + "base64": "Tidak dapat mendekode base64url header atau payload." + }, + "relative": { + "future": "dalam {n} {unit}", + "past": "{n} {unit} yang lalu" + }, + "units": { + "second": "{count, plural, one {detik} other {detik}}", + "minute": "{count, plural, one {menit} other {menit}}", + "hour": "{count, plural, one {jam} other {jam}}", + "day": "{count, plural, one {hari} other {hari}}" + }, + "tabs": { + "decode": "Dekode", + "sign": "Generator / penandatangan" + }, + "signer": { + "algLabel": "Algoritma (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "JSON payload", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON header", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Rahasia (HS*)", + "secretPlaceholder": "Tempel rahasia HMAC Anda…", + "privateKeyLabel": "Kunci privat PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Algoritma \"none\" membuat token tanpa tanda tangan. Ini tidak aman dan hanya untuk pengujian.", + "sign": "Tanda tangani", + "verify": "Verifikasi tanda tangan", + "outputLabel": "Token (JWS ringkas)", + "outputPlaceholder": "Klik \"Tanda tangani\"…", + "copyToken": "Salin token", + "copied": "Tersalin", + "verifyKeyLabel": "Kunci verifikasi (opsional)", + "verifyKeyPlaceholder": "Untuk RS*: tempel kunci publik (SPKI) PEM. Untuk HS*: kosongkan untuk memakai rahasia yang sama.", + "verifyKeyHint": "Jika kosong, verifikasi menggunakan kunci penandatanganan (berguna untuk HS*).", + "verifyStatus": { + "valid": "Tanda tangan valid", + "invalid": "Tanda tangan tidak valid" + }, + "securityNote": "Semua berjalan lokal di browser; kunci dan token tidak diunggah." + }, + "signErrors": { + "invalidJson": "Header dan payload harus JSON yang valid.", + "unsupportedAlg": "Algoritma tidak didukung.", + "missingKey": "Kunci penandatanganan diperlukan untuk algoritma ini.", + "cryptoFailed": "Penandatanganan gagal. Periksa format kunci dan coba lagi." + }, + "verifyErrors": { + "empty": "Masukkan JWT untuk diverifikasi.", + "segments": "JWT harus memiliki tepat 3 segmen untuk memverifikasi tanda tangan.", + "unsupportedAlg": "Algoritma tidak didukung atau alg tidak ada di header.", + "missingKey": "Kunci verifikasi diperlukan untuk algoritma ini.", + "base64": "Tidak dapat mendekode header atau tanda tangan JWT.", + "cryptoFailed": "Verifikasi gagal. Periksa format kunci dan coba lagi." + } + }, + "CertificatePemDecoder": { + "title": "Dekoder sertifikat / PEM", + "subtitle": "Tempel sertifikat X.509 (-----BEGIN CERTIFICATE-----) atau CSR (-----BEGIN CERTIFICATE REQUEST-----). Subjek, penerbit, tanggal, dan SAN diproses secara lokal; tidak ada yang diunggah.", + "pemLabel": "Masukan PEM", + "clear": "Hapus", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Memproses…", + "copyHex": "Salin sidik jari (hex tanpa titik dua)", + "errors": { + "empty": "Tempel sertifikat PEM atau CSR untuk mendekode.", + "noBlock": "Tidak ada blok PEM sertifikat atau CSR.", + "parseFailed": "Tidak dapat memproses data PEM ini." + }, + "block": { + "title": "Blok PEM {index}", + "certificate": "Sertifikat X.509", + "csr": "Permintaan penandatanganan sertifikat" + }, + "fields": { + "subject": "Subjek", + "issuer": "Penerbit", + "notBefore": "Tidak berlaku sebelum", + "notAfter": "Tidak berlaku setelah", + "serial": "Nomor seri (hex)", + "sha256": "Sidik jari SHA-256", + "sans": "Nama alternatif subjek", + "sansNone": "Tidak ada ekstensi SAN.", + "signatureAlgorithm": "Algoritma tanda tangan", + "csrIssuerNote": "CSR tidak berisi penerbit (ada pada sertifikat yang diterbitkan).", + "csrValidityNote": "CSR tidak berisi tanggal berlaku (ada pada sertifikat yang diterbitkan)." + }, + "sanType": { + "dns": "DNS", + "dn": "Nama direktori", + "email": "Email", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID terdaftar", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Pemformat SQL", + "subtitle": "Rapikan SQL untuk MySQL, PostgreSQL, atau SQLite. Berjalan di browser; tidak ada yang diunggah.", + "dialectLabel": "Dialek", + "keywordsLabel": "Kata kunci", + "indentLabel": "Indentasi", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "HURUF BESAR", + "lower": "huruf kecil", + "preserve": "Pertahankan" + }, + "spaces": "{n} spasi", + "format": "Format", + "clear": "Bersihkan", + "counter": "{current} / {max} karakter", + "inputPanel": "Input", + "formattedPanel": "Terformat", + "copy": "Salin", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klik \"Format\"…", + "errors": { + "tooLong": "Input dibatasi hingga {max} karakter.", + "couldNotFormat": "Tidak dapat memformat SQL ini." + } + }, + "YamlFormatter": { + "title": "Pemformat YAML", + "subtitle": "Format, validasi, dan konversi YAML di browser Anda. Tidak ada yang diunggah.", + "outputLabel": "Keluaran", + "outputModes": { + "yaml": "YAML (diformat)", + "json": "JSON" + }, + "indentLabel": "Indentasi", + "spaces": "{n} spasi", + "format": "Format", + "clear": "Hapus", + "copy": "Salin", + "counter": "{current} / {max} karakter", + "inputPanel": "Masukan", + "outputPanel": "Keluaran", + "errors": { + "tooLong": "Masukan dibatasi hingga {max} karakter.", + "couldNotFormat": "Tidak dapat mengurai YAML ini." + } + }, + "GraphqlFormatter": { + "title": "Pemformat GraphQL", + "subtitle": "Format kueri, mutasi, dan langganan GraphQL di peramban. Tidak ada yang diunggah.", + "tabs": { + "format": "Format", + "build": "Pembangun kueri" + }, + "outputModeLabel": "Keluaran", + "outputModes": { + "pretty": "Cetak rapi", + "minify": "Perkecil" + }, + "format": "Format", + "clear": "Hapus", + "counter": "{current} / {max} karakter", + "inputPanel": "Masukan", + "outputPanel": "Keluaran", + "copy": "Salin", + "builderHint": "Buat kerangka dokumen lalu terapkan ke editor. Definisi variabel memakai sintaks GraphQL (mis. $id: ID!) — tanda kurung bersifat opsional.", + "builder": { + "operation": "Operasi", + "operationName": "Nama operasi", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definisi variabel", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Himpunan seleksi", + "apply": "Terapkan ke masukan", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Masukan dibatasi {max} karakter.", + "couldNotFormat": "Tidak dapat menguraikan atau mencetak GraphQL ini.", + "emptySelection": "Tambahkan himpunan seleksi sebelum menerapkan." + } + }, + "RegexTester": { + "title": "Penguji regex", + "subtitle": "Regex gaya JavaScript secara langsung pada teks Anda. Kecocokan disorot; tidak ada yang keluar dari browser.", + "patternLabel": "Pola", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i abaikan huruf besar/kecil", + "m": "m multiline", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(kosong)", + "noFlags": "—", + "matches": "{count, plural, one {kecocokan} other {kecocokan}}", + "testTextLabel": "Teks uji", + "counter": "{current} / {max}", + "clear": "Bersihkan", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Teks dibatasi hingga {max} karakter.", + "invalid": "Ekspresi reguler tidak valid.", + "runaway": "Dihentikan setelah {max} kecocokan (kemungkinan pola runaway)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Konverter timestamp", + "subtitle": "Detik Unix (≤10 digit) atau milidetik (bilangan lebih panjang), ISO-8601, atau string yang bisa diparse browser. Semua berjalan lokal.", + "inputLabel": "Input", + "placeholder": "1744200000 atau 2025-04-09T12:00:00Z", + "now": "Sekarang", + "copy": "Salin", + "hint": "Tempel nilai atau klik Sekarang.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relatif", + "human": "Mudah dibaca" + }, + "fields": { + "seconds": "Detik", + "milliseconds": "Milidetik", + "utc": "UTC", + "local": "Lokal", + "distance": "Jarak" + }, + "errors": { + "empty": "Masukkan timestamp Unix, string ISO, atau teks tanggal.", + "invalidNumber": "Timestamp numerik tidak valid.", + "outOfRange": "Di luar rentang untuk JavaScript Date.", + "couldNotParse": "Tidak dapat diparse sebagai Unix, ISO-8601, atau tanggal browser." + } + }, + "HttpStatusCodes": { + "title": "Kode status HTTP", + "subtitle": "Cari dan jelajahi kode status respons HTTP (1xx5xx) dengan deskripsi singkat dan referensi RFC. Berjalan secara lokal.", + "header": { + "title": "Kode status HTTP", + "description": "Referensi yang dapat dicari untuk kode respons HTTP beserta deskripsi dan referensi RFC." + }, + "searchPlaceholder": "Cari: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filter kelas…", + "classFilterAll": "Semua kelas", + "resultsCount": "{count} dari {total}", + "table": { + "code": "Code", + "phrase": "Frasa", + "description": "Deskripsi", + "copy": "Salin" + }, + "noResults": "Tidak ada hasil untuk “{query}”.", + "specPrefix": "Spes.:", + "copied": "Kode disalin" + }, + "CronBuilder": { + "title": "Pembuat ekspresi Cron", + "subtitle": "Cron standar 5 bidang: minute hour day month weekday. Parser menggunakan seconds=0 secara internal. Format opsional 6 bidang (sec min hour dom mon dow) berfungsi di editor ekspresi.", + "tabs": { + "builder": "Pembuat visual", + "expression": "Ekspresi" + }, + "presetsLabel": "Preset", + "fieldsLabel": "Bidang", + "rawExpressionLabel": "Ekspresi mentah", + "expressionHint": "Keluar dari field untuk mem-parse dan menyinkronkan pembuat. {count} {count, plural, one {token} other {token}}.", + "currentExpressionLabel": "Ekspresi saat ini", + "copy": "Salin", + "quickPick": "Pilih cepat", + "emptyExpression": "—", + "plainLanguageLabel": "Bahasa natural", + "nextRunsLabel": "Jadwal berikutnya (waktu lokal)", + "fields": { + "minute": "Menit", + "hour": "Jam", + "dayOfMonth": "Hari dalam bulan", + "month": "Bulan", + "dayOfWeek": "Hari dalam minggu" + }, + "presets": { + "everyMinute": "Setiap menit", + "every5Minutes": "Setiap 5 menit", + "every15Minutes": "Setiap 15 menit", + "everyHour": "Setiap jam (pada :00)", + "every6Hours": "Setiap 6 jam", + "dailyMidnight": "Harian tengah malam", + "daily0900": "Harian 09:00", + "weekdays0900": "Hari kerja 09:00", + "weeklySunday": "Mingguan (Minggu 00:00)", + "monthlyFirst": "Bulanan (tgl 1 00:00)", + "yearlyJan1": "Tahunan (1 Jan 00:00)" + }, + "picks": { + "minute": { + "every": "setiap menit" + }, + "hour": { + "every": "setiap jam" + }, + "dom": { + "any": "hari apa saja", + "last": "hari terakhir" + }, + "month": { + "every": "setiap bulan" + }, + "dow": { + "any": "hari apa saja", + "weekdays": "hari kerja", + "weekend": "akhir pekan" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mar", + "apr": "Apr", + "may": "Mei", + "jun": "Jun", + "jul": "Jul", + "aug": "Agu", + "sep": "Sep", + "oct": "Okt", + "nov": "Nov", + "dec": "Des" + }, + "weekdays": { + "sun": "Min", + "mon": "Sen", + "tue": "Sel", + "wed": "Rab", + "thu": "Kam", + "fri": "Jum", + "sat": "Sab" + }, + "errors": { + "fieldCount": "Diharapkan 5 bidang (minute hour day month weekday) atau 6 termasuk detik.", + "invalid": "Ekspresi cron tidak valid." + } + }, + "Base64": { + "title": "Encoder / Decoder Base64", + "subtitle": "Encode teks ke Base64 atau decode string Base64", + "upload": "Unggah", + "clear": "Bersihkan", + "modes": { + "encode": "Encode", + "decode": "Decode" + }, + "swapTitle": "Tukar input dan output", + "panels": { + "plainText": "Teks biasa", + "base64Input": "Input Base64", + "base64Output": "Output Base64", + "decodedText": "Teks ter-decode" + }, + "placeholders": { + "encode": "Masukkan teks untuk di-encode ke Base64…", + "decode": "Tempel string Base64 untuk di-decode…" + }, + "dropHere": "Jatuhkan file di sini", + "charCount": "{count} karakter", + "download": { + "title": "Unduh output", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Salin ke clipboard", + "outputPlaceholder": "Output akan muncul di sini…", + "errors": { + "invalidBase64": "String Base64 tidak valid. Periksa input.", + "encodeFailed": "Gagal meng-encode input." + } + }, + "UrlEncode": { + "title": "Encoder / Decoder URL", + "subtitle": "Percent-encode/decode dengan encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Unggah", + "clear": "Bersihkan", + "modes": { + "encode": "Encode", + "decode": "Decode" + }, + "swapTitle": "Tukar input dan output", + "panels": { + "plainText": "Teks biasa", + "encodedInput": "Input ter-encode", + "encodedOutput": "Output ter-encode", + "decodedText": "Teks ter-decode" + }, + "placeholders": { + "encode": "Masukkan teks untuk percent-encode…", + "decode": "Tempel teks percent-encoded untuk decode…" + }, + "dropHere": "Jatuhkan file di sini", + "charCount": "{count} karakter", + "download": { + "title": "Unduh output", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Salin ke clipboard", + "outputPlaceholder": "Output akan muncul di sini…", + "errors": { + "invalidEncoding": "Percent-encoding tidak valid. Periksa pasangan % yang tidak lengkap atau escape yang salah.", + "encodeFailed": "Tidak dapat meng-encode (Unicode tidak valid pada input)." + } + }, + "UrlParser": { + "title": "Parser URL", + "subtitle": "Uraikan URL menjadi protokol, host, path, parameter kueri, dan hash (fragmen).", + "upload": "Unggah", + "clear": "Bersihkan", + "panels": { + "input": "Input URL", + "output": "Hasil" + }, + "placeholders": { + "input": "Tempel URL (mis. https://example.com/path?foo=bar#section) atau host/path (mis. example.com/path)…" + }, + "dropHere": "Jatuhkan file di sini", + "charCount": "{count} karakter", + "outputPlaceholder": "Komponen yang diurai akan muncul di sini…", + "fields": { + "protocol": "Protokol", + "host": "Host", + "path": "Path", + "hash": "Hash", + "queryParams": "Parameter kueri" + }, + "paramCount": "{count} parameter", + "noParams": "Tidak ada parameter kueri.", + "paramKey": "Kunci", + "paramValue": "Nilai", + "copyTitle": "Salin ke clipboard", + "copyParamTitle": "Salin sebagai key=value", + "download": { + "title": "Unduh JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL tidak valid. Sertakan hostname (example.com) dan periksa karakternya." + } + }, + "UuidGenerator": { + "title": "Generator UUID / ULID", + "subtitle": "Buat UUID (RFC 9562) atau ULID di browser. Ekspor massal hingga {max} per sekali jalan.", + "format": "Format", + "kindHints": { + "ulid": "ID 26 karakter yang dapat diurutkan dan aman untuk URL", + "uuid4": "Acak (RFC 9562)", + "uuid7": "Berurutan waktu, ms Unix + acak", + "uuid1": "Berbasis MAC/waktu (RFC 9562)", + "uuid6": "Waktu Gregorian yang diurut ulang", + "uuid3": "Hash MD5 dari nama + namespace", + "uuid5": "Hash SHA‑1 dari nama + namespace", + "nil": "UUID nol (semua nol, RFC 9562)" + }, + "countLabel": "Jumlah (1–{max})", + "nameLabel": "Nama (string input)", + "namePlaceholder": "mis. my.app/resource", + "bulkHint": "Mode massal menambahkan #0, #1, … agar setiap ID berbeda (v3/v5 deterministik).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID kustom" + }, + "customNamespaceLabel": "UUID namespace", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Buat", + "output": "Output", + "lines": "{count} baris", + "download": { + "title": "Unduh .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Salin semua", + "uppercaseToggle": "Keluaran huruf besar", + "hyphensToggle": "Tampilkan tanda hubung", + "copyJsonTitle": "Salin sebagai array JSON", + "outputPlaceholder": "Klik \"Buat\"…", + "errors": { + "invalidCustomNamespace": "Namespace kustom harus berupa UUID yang valid.", + "missingName": "Nama diperlukan untuk UUID v3 dan v5.", + "unknown": "Tidak dapat membuat ID." + } + }, + "SecretApiKeyGenerator": { + "title": "Generator rahasia / kunci API", + "subtitle": "Hasilkan string acak kriptografis dengan panjang dan alfabet yang dapat dikonfigurasi. Ekspor massal hingga {max} per eksekusi. Melengkapi generator UUID / ULID.", + "alphabetLabel": "Alfabet", + "alphabetHint": "Karakter duplikat dihapus. Setiap simbol dipilih seragam dengan crypto.getRandomValues dan rejection sampling (tanpa bias modulo).", + "alphabetCount": "{count} simbol unik", + "presetsLabel": "Prasetel", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumerik", + "hex": "Heks (huruf kecil)", + "asciiPrintable": "ASCII yang dapat dicetak" + }, + "lengthLabel": "Panjang per rahasia (1–{maxLen})", + "countLabel": "Jumlah (1–{maxBulk})", + "generate": "Hasilkan", + "output": "Keluaran", + "lines": "{count} baris", + "download": { + "title": "Unduh .txt", + "filename": "rahasia.txt" + }, + "copyTitle": "Salin semua", + "outputPlaceholder": "Klik \"Hasilkan\"…", + "entropy": "{bits} bit entropi", + "strength": { + "weak": "Lemah", + "fair": "Cukup", + "strong": "Kuat" + }, + "excludeAmbiguous": "Kecualikan karakter ambigu", + "excludeAmbiguousHint": "Menghapus {chars} — karakter yang mudah salah baca atau tidak ramah bagi shell.", + "prefixLabel": "Awalan kunci (opsional)", + "errors": { + "emptyAlphabet": "Tambahkan setidaknya satu karakter ke alfabet.", + "alphabetTooLong": "Setelah duplikat dihapus, alfabet paling banyak {maxAlpha} karakter.", + "lengthOutOfRange": "Panjang harus antara 1 dan {maxLen}.", + "countOutOfRange": "Jumlah harus antara 1 dan {maxBulk}.", + "unknown": "Tidak dapat menghasilkan rahasia." + } + }, + "LoremIpsum": { + "title": "Generator Lorem Ipsum", + "subtitle": "Hasilkan teks Latin pengganti untuk tata letak dan mockup Anda.", + "paragraphs": "Paragraf", + "sentences": "Kalimat", + "words": "Kata", + "count": "Jumlah", + "generate": "Hasilkan", + "copy": "Salin", + "startWith": "Mulai dengan 'Lorem ipsum...'", + "outputPlaceholder": "Output akan muncul di sini...", + "outputType": "Jenis output", + "bulletList": "Daftar poin", + "htmlMarkup": "Markup HTML", + "htmlMarkupHint": "Bungkus output dalam paragraf, atau gunakan daftar untuk mode poin", + "output": "Output", + "download": "Unduh" + }, + "ColorPicker": { + "title": "Pemilih dan konverter warna", + "subtitle": "Konversi HEX, RGB, dan HSL; buat palet harmonis. Semua pemrosesan dilakukan di browser Anda.", + "nativePicker": "Pemilih bawaan", + "chooseColor": "Pilih warna", + "eyeDropper": "Penetes mata", + "invalidHex": "Warna HEX tidak valid.", + "cssValues": "Nilai CSS", + "palettes": "Palet", + "copied": "Disalin", + "clickToCopy": "Klik untuk menyalin", + "copyAllHex": "Salin semua HEX", + "copy": "Salin", + "copyColor": "Salin {color}", + "paletteShades": "Nuansa", + "paletteComplementary": "Komplementer", + "paletteTriadic": "Triadik", + "paletteAnalogous": "Analog", + "paletteSplit": "Komp. terpisah", + "paletteSquare": "Persegi" + }, + "ContrastChecker": { + "title": "Pemeriksa kontras", + "subtitle": "Menghitung rasio kontras WCAG 2.1 antara latar depan dan latar belakang. Memeriksa AA dan AAA untuk teks normal dan besar. Semua di browser Anda.", + "foreground": "Teks / latar depan", + "background": "Latar belakang", + "hexPlaceholder": "#RRGGBB", + "swap": "Tukar warna", + "chooseForeground": "Pilih warna latar depan", + "chooseBackground": "Pilih warna latar belakang", + "eyeDropper": "Pipet", + "ratio": "Rasio kontras", + "copyRatio": "Salin rasio", + "copied": "Disalin", + "previewHeading": "Pratinjau", + "previewBody": "Selamat datang. Uji kontras teks ini agar mudah dibaca di layar. Pencinta alam menjaga hutan, sungai, dan satwa demi generasi mendatang.", + "previewCaption": "Contoh teks ukuran bawaan.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — teks normal (4,5:1)", + "levelAALarge": "AA — teks besar (3:1)", + "levelAAANormal": "AAA — teks normal (7:1)", + "levelAAALarge": "AAA — teks besar (4,5:1)", + "pass": "Lulus", + "fail": "Tidak lulus", + "invalidHex": "Warna HEX tidak valid.", + "largeTextHint": "Teks besar: 18 pt+ reguler atau 14 pt+ tebal (definisi WCAG)." + }, + "DiffChecker": { + "title": "Pemeriksa Perbedaan", + "subtitle": "Perbandingan baris per baris dua teks secara berdampingan. Semuanya tetap di browser Anda.", + "rowsCount": "{count} baris", + "truncatedWarning": "Menampilkan {max} baris pertama.", + "limitExceeded": "Setiap sisi dibatasi hingga {max} karakter.", + "clearBoth": "Hapus keduanya", + "original": "Asli", + "modified": "Dimodifikasi", + "originalPlaceholder": "Tempel teks asli…", + "modifiedPlaceholder": "Tempel teks yang dimodifikasi…", + "comparisonLabel": "Perbandingan berdampingan", + "emptyState": "Masukkan teks di kedua panel untuk membandingkan." + }, + "JsonSchemaGenerator": { + "title": "Generator Skema JSON", + "subtitle": "Tempel JSON yang valid untuk menghasilkan skema JSON atau model bertipe. Semua berjalan di peramban Anda.", + "languageLabel": "Bahasa keluaran", + "inputLabel": "Masukan JSON", + "inputPlaceholder": "Tempel JSON di sini…", + "outputLabel": "Keluaran yang dihasilkan", + "outputPlaceholder": "Skema atau tipe yang dihasilkan akan muncul di sini…", + "parseError": "JSON tidak valid", + "clear": "Bersihkan", + "copy": "Salin", + "copied": "Disalin ke papan klip", + "copyFailed": "Tidak dapat menyalin", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Skema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Memuat editor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Gambar ke Base64", + "subtitle": "Jatuhkan gambar, dapatkan URI data atau string Base64 mentah. Semuanya tetap ada di browser Anda.", + "upload": "Unggah Gambar", + "clear": "Jernih", + "modes": { + "dataUri": "URI Data", + "rawString": "Basis Mentah64" + }, + "panels": { + "imageInput": "Masukan Gambar", + "output": "Keluaran Base64" + }, + "dropHere": "Jatuhkan gambar di sini", + "charCount": "{count} karakter", + "placeholders": { + "uploadImage": "Seret dan lepas gambar atau klik unggah untuk memulai." + }, + "fileTooLarge": "File terlalu besar. Pemrosesan mungkin membekukan browser Anda.", + "copyTitle": "Salin ke papan klip", + "download": { + "title": "Unduh teks", + "filename": "gambar-base64.txt" + } + }, + "ImageCompressor": { + "title": "Kompresor gambar", + "subtitle": "Perkecil keluaran JPEG, PNG, atau WebP secara lokal dengan kontrol kualitas — tidak ada unggahan.", + "upload": "Unggah gambar", + "clear": "Hapus", + "dropHere": "Letakkan gambar di sini", + "placeholders": { + "uploadImage": "Seret dan lepas gambar, atau klik untuk memilih file." + }, + "fileTooLarge": "File terlalu besar (maks {maxMb} MB).", + "unsupportedFormat": "Jenis file tidak didukung. Gunakan JPEG, PNG, WebP, GIF, AVIF, atau BMP.", + "compressFailed": "Tidak dapat mengompres gambar ini. Coba file atau format lain.", + "panels": { + "original": "Asli", + "compressed": "Pratinjau terkompresi" + }, + "outputFormat": "Format keluaran", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kualitas", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG tanpa rugi di perangkat; penggeser kualitas hanya untuk JPEG dan WebP.", + "sizes": { + "original": "Asli: {size}", + "compressed": "Terkompresi: {size}", + "saved": "{percent}% lebih kecil" + }, + "largerWarning": "Keluaran lebih besar dari masukan — coba format atau kualitas lain.", + "download": "Unduh", + "downloadFilename": "gambar-terkompresi", + "processing": "Mengompres…", + "emptyCompressed": "Pratinjau terkompresi muncul di sini." + }, + "CssGradientBuilder": { + "title": "Pembuat Gradien CSS", + "subtitle": "Pembuat gradien CSS visual dengan kontrol sudut, penghentian warna, dan salinan CSS sekali klik.", + "preview": "Pratinjau Gradien", + "settings": "Pengaturan", + "type": "Jenis", + "linear": "Linier", + "radial": "Radial", + "angle": "Sudut", + "stops": "Warna Berhenti", + "color": "Warna", + "position": "Posisi (%)", + "addStop": "Tambahkan Berhenti", + "deleteStop": "Hapus Berhenti", + "output": "Keluaran CSS", + "copy": "Salin CSS", + "copied": "Disalin!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Generator", + "subtitle": "Hasilkan file .gitignore gabungan secara instan untuk tumpukan atau kombinasi teknologi tertentu.", + "searchPlaceholder": "Tumpukan pencarian (misalnya Node, Python, macOS)...", + "noResults": "Tidak ada tumpukan yang ditemukan.", + "selectedStacks": "Tumpukan yang Dipilih", + "output": "Pratinjau Keluaran", + "copy": "Salin ke Papan Klip", + "copied": "Disalin!", + "download": "Unduh .gitignore", + "downloading": "Mengunduh...", + "errorFetching": "Gagal mengambil daftar teratas.", + "errorGenerating": "Gagal menghasilkan .gitignore dari tumpukan yang dipilih.", + "generating": "Menghasilkan...", + "emptyState": "Pilih satu atau lebih tumpukan teknologi untuk menghasilkan .gitignore Anda." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Unggah CSV atau Excel, edit JSON, lalu unduh CSV atau XLSX. Hanya data tabular; tanggal Excel menjadi stempel waktu ISO di JSON.", + "dropTitle": "Letakkan file CSV atau Excel", + "dropHint": "Untuk .xlsx / .xls digunakan lembar pertama. CSV harus punya baris header.", + "chooseFile": "Pilih file", + "jsonLabel": "JSON (array objek)", + "jsonPlaceholder": "Tempel array JSON objek atau impor CSV / Excel…", + "loadSample": "Muat contoh", + "actions": { + "format": "Format JSON", + "clear": "Hapus", + "copy": "Salin", + "copied": "Disalin", + "downloadCsv": "Unduh CSV", + "downloadXlsx": "Unduh Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Data", + "toastLoadedCsv": "CSV dimuat ke editor", + "toastLoadedExcel": "Excel dimuat ke editor", + "toastFormatted": "JSON diformat", + "toastExportedCsv": "CSV diunduh", + "toastExportedXlsx": "Excel diunduh", + "toastCopied": "Disalin ke papan klip", + "footnote": "Semua berjalan di browser. JSON harus berupa array objek yang tidak kosong. Nilai bersarang diserialisasi sebagai JSON di sel CSV.", + "errors": { + "unsupportedFile": "Gunakan file .csv, .xlsx, atau .xls.", + "parseFailed": "Tidak dapat mengurai file ini.", + "invalidJson": "JSON tidak valid", + "exportFailed": "Ekspor gagal", + "copyFailed": "Tidak dapat menyalin" + } + }, + "SnippetManager": { + "title": "Pengelola cuplikan kode", + "subtitle": "Simpan cuplikan di browser atau masuk untuk menyinkronkannya dengan akun Anda. Beralih antara lihat dan sunting, salin sekali klik, pilih bahasa atau deteksi otomatis, dan format JSON, SQL, atau kode yang didukung Monaco.", + "snippets": "Cuplikan", + "newSnippet": "Cuplikan baru", + "newSnippetTitle": "Cuplikan baru", + "defaultSnippetTitle": "Selamat datang", + "defaultSnippetCode": "// Selamat datang di cuplikan kode\n// Coba lihat vs sunting, salin, format, dan bahasa otomatis\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Cari cuplikan…", + "searchAria": "Cari cuplikan", + "emptySearch": "Tidak ada cuplikan yang cocok.", + "snippetTitle": "Judul", + "snippetTitlePlaceholder": "Nama cuplikan", + "language": "Bahasa", + "detectedHint": "Sorotan sebagai {lang}", + "badgeAuto": "Otomatis · {lang}", + "modeView": "Lihat", + "modeEdit": "Sunting", + "copy": "Salin", + "copied": "Disalin", + "format": "Format", + "cancel": "Batal", + "deleteTitle": "Hapus cuplikan?", + "deleteDescription": "“{title}” akan dihapus dari browser ini.", + "deleteConfirm": "Hapus", + "editorAria": "Editor kode cuplikan", + "editorLoading": "Memuat editor…", + "toastCopied": "Disalin ke papan klip", + "toastCopyFailed": "Tidak dapat menyalin", + "toastNothingToCopy": "Tidak ada yang disalin", + "toastFormatted": "Sudah diformat", + "toastFormatFailed": "Tidak dapat memformat", + "toastFormatJsonInvalid": "JSON tidak valid — perbaiki sintaks dulu", + "toastFormatUnsupported": "Tidak ada pemformat untuk bahasa ini — coba JSON atau SQL atau mode sunting", + "toastDeleted": "Cuplikan dihapus", + "toastLoadFailed": "Tidak dapat memuat cuplikan dari server.", + "toastSyncFailed": "Tidak dapat menyinkronkan dengan server.", + "languages": { + "auto": "Deteksi otomatis", + "plaintext": "Teks biasa", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Klien SQL", + "dialogTitle": "Koneksi SQL", + "mobileSheetTitle": "Penjelajah SQL", + "newTabLabel": "Tab kueri baru", + "vaultLockedTitle": "Brankas terkunci", + "vaultLockedDesc": "Buka kunci brankas untuk mengakses koneksi tersimpan.", + "heroTitle": "Klien SQL", + "heroDesc": "Hubungkan ke PostgreSQL, MySQL, atau MariaDB. Kredensial dienkripsi di browser Anda.", + "heroAddConnection": "Tambah koneksi", + "selectTableTitle": "Pilih tabel atau buka tab kueri", + "selectTableDesc": "Klik tabel di bilah samping atau gunakan ⌘↩.", + "toastLoadFail": "Gagal memuat koneksi." + }, + "connection": { + "titleNew": "Koneksi SQL baru", + "titleEdit": "Edit koneksi", + "labelDbType": "Jenis basis data", + "labelName": "Nama koneksi", + "placeholderName": "DB produksi saya", + "labelHostPort": "Host & Port", + "labelDatabase": "Basis data", + "placeholderDatabase": "mydb", + "labelUsername": "Nama pengguna", + "labelPassword": "Kata sandi", + "labelSsl": "Aktifkan SSL / TLS", + "sslDesc": "Enkripsi koneksi ke server", + "securityNote": "Kredensial dienkripsi AES-256 di browser sebelum disimpan", + "btnTest": "Uji koneksi", + "btnSave": "Simpan & Hubungkan", + "btnUpdate": "Perbarui", + "btnCancelEdit": "Batalkan pengeditan", + "savedConnections": "Koneksi tersimpan", + "emptySaved": "Koneksi tersimpan akan muncul di sini", + "confirmDelete": "Hapus koneksi ini?", + "toastFillFields": "Isi host dan basis data sebelum menguji.", + "toastConnectOk": "Terhubung! {version}", + "toastConnectFail": "Koneksi gagal: {message}", + "toastUnlockVault": "Buka kunci brankas terlebih dahulu.", + "toastFieldsRequired": "Host dan basis data wajib diisi.", + "toastUpdated": "Koneksi diperbarui.", + "toastSaved": "Koneksi disimpan.", + "toastSaveFail": "Gagal menyimpan: {message}", + "toastDeleted": "Koneksi dihapus.", + "toastDeleteFail": "Gagal menghapus koneksi." + }, + "sidebar": { + "title": "Penjelajah SQL", + "tooltipAddConnection": "Tambah koneksi", + "tooltipRefreshSchema": "Segarkan skema", + "noConnections": "Belum ada koneksi tersimpan", + "toastSchemaFail": "Gagal memuat skema: {message}" + }, + "editor": { + "btnRun": "Jalankan", + "executing": "Menjalankan kueri…", + "emptyTitle": "Jalankan kueri untuk melihat hasil", + "emptyHint": "Tekan ⌘↩ atau klik Jalankan", + "toastNoConnection": "Tidak ada koneksi aktif." + }, + "results": { + "filterPlaceholder": "Filter hasil…", + "rowCount": "{count} baris", + "rowCountFiltered": "{filtered} / {total} baris", + "exportCsv": "CSV", + "noRows": "Tidak ada baris yang dikembalikan" + } + }, + "QrCodeGenerator": { + "title": "Generator kode QR", + "subtitle": "Ubah teks atau URL apa pun menjadi kode QR PNG. Semua diproses secara lokal di peramban.", + "contentLabel": "Konten", + "contentPlaceholder": "https://example.com, teks biasa, string Wi‑Fi, vCard…", + "styleLabel": "Gaya", + "dotTypeLabel": "Bentuk titik", + "dotTypeSquare": "Kotak", + "dotTypeDots": "Titik", + "dotTypeRounded": "Membulat", + "dotTypeExtraRounded": "Sangat membulat", + "dotTypeClassy": "Elegan", + "dotTypeClassyRounded": "Elegan membulat", + "cornerSquareLabel": "Kotak sudut", + "cornerSquareDot": "Titik", + "cornerSquareSquare": "Kotak", + "cornerSquareExtraRounded": "Sangat membulat", + "cornerDotLabel": "Titik sudut", + "cornerDotDot": "Titik", + "cornerDotSquare": "Kotak", + "eccLabel": "Koreksi kesalahan", + "eccL": "Rendah (~7%)", + "eccM": "Sedang (~15%)", + "eccQ": "Kuartil (~25%)", + "eccH": "Tinggi (~30%)", + "marginLabel": "Margin (modul)", + "widthLabel": "Lebar gambar", + "widthEffective": "Dirender pada {px} px", + "darkColor": "Modul gelap", + "lightColor": "Modul terang", + "resetColors": "Atur ulang warna", + "logoLabel": "Logo tengah", + "logoMarginLabel": "Margin", + "uploadLogo": "Unggah gambar", + "removeLogo": "Hapus logo", + "downloadPng": "Unduh PNG", + "downloadSvg": "Unduh SVG", + "copyImage": "Salin gambar", + "copied": "Disalin", + "clear": "Hapus", + "previewAlt": "Pratinjau kode QR", + "emptyHint": "Ketik atau tempel konten untuk melihat pratinjau.", + "filename": "kode-qr.png", + "errors": { + "generationFailed": "Tidak dapat membuat kode QR. Coba teks lebih pendek atau koreksi lebih rendah.", + "copyUnsupported": "Peramban tidak mendukung menyalin gambar ke papan klip.", + "copyFailed": "Tidak dapat menyalin ke papan klip." + } + }, + "IpSubnetCalculator": { + "title": "Kalkulator IP / subnet", + "subtitle": "Detail CIDR IPv4 dan IPv6 di peramban—tanpa permintaan jaringan.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Jaringan", + "placeholderIpv4": "10.0.0.0/24 atau 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 atau 2001:db8::1/64", + "hintIpv4": "Gunakan CIDR (mis. /24) atau IPv4 + netmask bertitik.", + "hintIpv6": "Sertakan panjang prefiks (mis. /64). Jika tidak, /128.", + "copyValue": "Salin nilai", + "rows": { + "cidr": "CIDR", + "network": "Alamat jaringan", + "netmask": "Subnet mask", + "wildcard": "Wildcard mask", + "broadcast": "Broadcast", + "firstHost": "Host pertama", + "lastHost": "Host terakhir", + "totalAddresses": "Total alamat", + "usableHosts": "Host yang dapat dipakai", + "prefixLength": "Panjang prefiks (bit)", + "networkIpv6": "Alamat jaringan", + "lastIpv6": "Alamat terakhir dalam rentang", + "totalAddressesIpv6": "Alamat dalam subnet" + }, + "usableNote": { + "pointToPoint": "Tautan /31 titik-ke-titik memakai kedua alamat sebagai host (RFC 3021).", + "singleHost": "/32 adalah satu host." + }, + "ipv6RangeHint": { + "normal": "Menampilkan rentang matematis penuh; aturan router dapat berbeda.", + "single": "/128 adalah satu alamat.", + "full": "/0 mencakup seluruh IPv6." + }, + "errors": { + "invalidIpv4": "Jaringan IPv4 tidak valid. Coba CIDR atau alamat + mask.", + "invalidIpv6": "Jaringan IPv6 tidak valid. Gunakan alamat/prefiks, mis. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Generator hash", + "subtitle": "Ringkasan MD5, SHA-1, dan SHA-2 untuk teks atau file; bcrypt untuk kata sandi (input teks). Semua dihitung lokal di peramban.", + "algorithmLabel": "Algoritme", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Teks", + "modeFile": "Berkas", + "inputLabelText": "Masukan", + "placeholderText": "Ketik atau tempel teks…", + "chooseFile": "Pilih berkas", + "clearFile": "Hapus", + "fileHint": "Berkas hanya dibaca di memori; maks. {maxMb} MB. Tidak diunggah.", + "outputLabel": "Ringkasan (hex)", + "copyHash": "Salin ringkasan", + "copied": "Disalin", + "byteCount": "{count, plural, other {# byte}}", + "charCount": "{count, plural, other {# karakter}}", + "emptyHint": "Masukkan teks atau pilih berkas.", + "securityNote": "MD5 dan SHA-1 tidak disarankan untuk keamanan baru; gunakan SHA-256 atau SHA-512.", + "errors": { + "fileTooLarge": "Berkas lebih besar dari {maxMb} MB.", + "fileReadFailed": "Tidak dapat membaca berkas." + }, + "bcryptRoundsLabel": "Biaya (putaran)", + "bcryptRoundsHint": "Nilai lebih tinggi lebih lambat dan lebih tahan brute force. Biasanya default 10.", + "fileTabDisabledTitle": "bcrypt hanya meng-hash kata sandi dari teks, bukan file sembarangan.", + "placeholderBcrypt": "Ketik atau tempel kata sandi untuk di-hash…", + "outputLabelBcrypt": "Hash bcrypt", + "copyBcrypt": "Salin hash", + "emptyHintBcrypt": "Masukkan kata sandi untuk menghitung hash bcrypt.", + "securityNoteBcrypt": "Setiap keluaran bcrypt menyertakan garam acak; kata sandi yang sama menghasilkan string berbeda setiap kali. Ini wajar. Gunakan biaya tinggi di produksi.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Arena enkripsi", + "subtitle": "AES-GCM di peramban: enkripsi ke paket JSON dengan frasa sandi atau kunci mentah, lalu dekripsi secara lokal. Edukatif dan kadang praktis — tidak ada unggahan.", + "tabEncrypt": "Enkripsi", + "tabDecrypt": "Dekripsi", + "keyModeLabel": "Material kunci", + "modePassphrase": "Frasa sandi (PBKDF2 + AES-GCM)", + "modeRawKey": "Kunci AES mentah (hex atau Base64)", + "aesBitsLabel": "Panjang kunci AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Pengodean kunci mentah", + "encodingAuto": "Otomatis (hex jika tampak seperti hex, jika tidak Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Kunci mentah", + "rawKeyPlaceholder": "Kunci 32 byte sebagai 64 karakter hex, atau Base64 untuk AES-256 (panjang tergantung ukuran AES di atas).", + "rawKeyPlaceholderDecrypt": "Hanya diperlukan jika paket memiliki keyDerivation \"none\".", + "passphraseLabel": "Frasa sandi", + "passphrasePlaceholder": "Frasa sandi hanya digunakan di peramban untuk menurunkan kunci.", + "passphrasePlaceholderDecrypt": "Hanya diperlukan jika paket memakai PBKDF2-SHA256.", + "pbkdf2IterLabel": "Iterasi PBKDF2", + "pbkdf2Hint": "Antara 10.000 dan 2.000.000. Default mengikuti pengaturan brankas umum.", + "plaintextLabel": "Teks biasa", + "plaintextPlaceholder": "Ketik atau tempel pesan untuk dienkripsi…", + "bundleInputLabel": "Paket terenkripsi (JSON)", + "bundlePlaceholder": "Tempel JSON dari Enkripsi (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Paket terenkripsi", + "plaintextOutputLabel": "Teks biasa", + "encryptButton": "Enkripsi", + "decryptButton": "Dekripsi", + "copyBundle": "Salin JSON", + "copyPlaintext": "Salin teks biasa", + "copied": "Disalin", + "emptyBundleHint": "JSON terenkripsi muncul di sini setelah enkripsi.", + "emptyDecryptHint": "Teks terdekripsi muncul di sini.", + "decryptKeyHint": "Gunakan kunci mentah jika keyDerivation \"none\"; frasa sandi untuk PBKDF2-SHA256. Paket menyimpan garam, iterasi, IV, dan panjang AES.", + "securityNote": "Kunci dan teks biasa tidak meninggalkan peramban. Untuk rahasia produksi gunakan alat khusus.", + "errors": { + "emptyKey": "Masukkan kunci mentah yang sesuai ukuran AES.", + "emptyPassphrase": "Masukkan frasa sandi.", + "badKeyMaterial": "Tidak dapat membaca kunci sebagai hex atau Base64.", + "wrongKeyLength": "Panjang kunci tidak cocok dengan AES (16 / 24 / 32 byte).", + "invalidJson": "Bukan JSON yang valid.", + "invalidBundle": "JSON ini bukan paket enkripsi yang didukung.", + "decryptFailed": "Dekripsi gagal — rahasia salah, paket rusak, atau parameter tidak cocok.", + "encryptFailed": "Enkripsi gagal. Periksa Web Crypto dan masukan." + } + }, + "HmacGenerator": { + "title": "Generator HMAC", + "subtitle": "Untuk menandatangani webhook dan menguji integrasi API. HMAC-SHA1/256/384/512 dalam hex atau Base64—dihitung secara lokal di peramban.", + "secretLabel": "Kunci rahasia", + "messageLabel": "Pesan / muatan", + "placeholderSecret": "Tempel rahasia penandatanganan…", + "placeholderMessage": "Tempel isi mentah atau string untuk diautentikasi (sering byte webhook yang persis).", + "algorithmLabel": "Algoritma HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Format keluaran", + "formatHex": "Hex (huruf kecil)", + "formatBase64": "Base64", + "outputLabel": "Tanda tangan", + "copyOutput": "Salin tanda tangan", + "copied": "Disalin", + "byteCountHint": "{count, plural, other {# byte}} (UTF-8)", + "emptyHint": "Masukkan kunci rahasia untuk menghitung HMAC.", + "securityNote": "Jangan pernah membagikan rahasia penandatanganan. Halaman ini tidak mengunggah data—semua berjalan di peramban.", + "errors": { + "cryptoFailed": "Tidak dapat menghitung HMAC. Periksa dukungan Web Crypto dan coba lagi.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Generator kode TOTP / 2FA", + "subtitle": "Tempel rahasia Base32 dan dapatkan kode enam digit RFC 6238 saat ini (SHA-1, 30 dtk). Berjalan lokal di browser—berguna untuk menguji login atau MFA.", + "secretLabel": "Rahasia (Base32)", + "placeholderSecret": "Tempel kunci rahasia (huruf A–Z dan angka 2–7, seperti saat menyiapkan autentikator)…", + "codeLabel": "Kode saat ini", + "copyCode": "Salin kode", + "copied": "Disalin", + "nextIn": "Diperbarui dalam {seconds} dtk", + "standardHint": "Langkah waktu 30 dtk, 6 digit, HMAC-SHA-1 (kompatibel Google Authenticator).", + "digitsLabel": "Digit", + "periodLabel": "Periode", + "algorithmLabel": "Algoritma", + "periodSeconds": "{seconds} dtk", + "emptyHint": "Tempel rahasia untuk membuat kode enam digit.", + "invalidHint": "Perbaiki rahasia untuk melihat kode.", + "securityNote": "Perlakukan seperti sandi. Rahasia tidak meninggalkan browser; tidak ada unggahan.", + "importLabel": "Impor dari URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Impor", + "qrSectionTitle": "Kode QR provisioning", + "qrHint": "Pindai dengan aplikasi autentikator (Google Authenticator, 1Password, Aegis…) untuk menambahkan rahasia ini. Kode QR ini menyandikan rahasia — perlakukan seperti kata sandi.", + "accountLabel": "Akun", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Penerbit (opsional)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Masukkan rahasia yang valid untuk membuat kode QR.", + "errors": { + "invalidSecret": "Bukan rahasia Base32 yang valid.", + "cryptoFailed": "Tidak dapat menghitung TOTP. Periksa Web Crypto dan coba lagi.", + "invalidUri": "Ini bukan URI otpauth:// TOTP yang valid.", + "unsupportedUri": "URI ini menggunakan nilai yang tidak didukung alat ini (digit harus 6 atau 8; periode 15, 30, atau 60 dtk)." + } + }, + "MarkdownPreview": { + "title": "Pratinjau Markdown & HTML", + "subtitle": "Renderer Markdown langsung dengan ekspor HTML dan konversi HTML → Markdown. Semua berjalan di browser Anda.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Input Markdown", + "htmlInputLabel": "Input HTML", + "previewLabel": "Pratinjau", + "htmlOutputLabel": "Sumber HTML", + "mdOutputLabel": "Output Markdown", + "inputPlaceholder": "# Halo\\n\\nKetik **markdown** Anda di sini…", + "htmlInputPlaceholder": "Tempel HTML Anda di sini…", + "copyHtml": "Salin HTML", + "copyMarkdown": "Salin Markdown", + "exportHtml": "Ekspor HTML", + "copied": "Disalin", + "charCount": "{count, plural, other {# karakter}}", + "emptyHint": "Mulai mengetik Markdown untuk melihat pratinjau langsung dan output HTML.", + "emptyHintHtmlToMd": "Tempel HTML untuk mengonversinya ke Markdown.", + "securityNote": "Semua berjalan secara lokal di browser Anda. Tidak ada yang diunggah." + }, + "NumberBaseConverter": { + "title": "Konverter basis angka", + "subtitle": "Konversi bilangan bulat antar basis 2–36. Awalan opsional 0x / 0b / 0o. Semua dihitung lokal di peramban.", + "inputBaseLabel": "Dari basis", + "outputBaseLabel": "Ke basis", + "inputLabel": "Angka masukan", + "placeholderInput": "Masukkan bilangan bulat pada basis masukan yang dipilih…", + "outputLabel": "Nilai hasil konversi", + "copyOutput": "Salin hasil", + "copied": "Disalin", + "swap": "Tukar basis", + "byteCountHint": "{count, plural, other {# byte}} (panjang UTF-8)", + "emptyHint": "Masukkan angka untuk dikonversi.", + "note": "Digit di atas 9 memakai huruf A–Z (tidak peka huruf besar/kecil). Spasi dan garis bawah diabaikan.", + "errors": { + "invalidNumber": "Nilai tidak valid untuk basis masukan yang dipilih." + } + }, + "FormatConverter": { + "title": "Konverter Format", + "subtitle": "Konversi antara JSON, YAML, TOML, dan XML dalam arah apapun. Semua 12 kombinasi. Semua berjalan di browser Anda.", + "fromLabel": "Dari", + "toLabel": "Ke", + "swap": "Tukar", + "swapTitle": "Tukar format dan gunakan keluaran sebagai masukan", + "copy": "Salin", + "copied": "Disalin", + "charCount": "{count, plural, other {# karakter}}", + "emptyHint": "Tempel atau ketik masukan Anda di sebelah kiri untuk melihat keluaran yang dikonversi.", + "localNote": "Semua berjalan secara lokal di browser Anda. Tidak ada yang diunggah." + }, + "MimeTypeLookup": { + "title": "Pencarian tipe MIME", + "subtitle": "Cari tipe MIME (media type) untuk ekstensi file atau nama file lengkap. Semua berjalan secara lokal di browser Anda.", + "inputLabel": "Ekstensi atau nama file", + "placeholder": "mis. .png, package.json, photo.jpeg", + "hint": "Tip: tempel ekstensi (dengan atau tanpa titik) atau nama file lengkap.", + "clear": "Bersihkan", + "exampleFilename": "Gunakan contoh nama file", + "exampleExtension": "Gunakan contoh ekstensi", + "resultLabel": "Hasil", + "normalizedLabel": "Masukan ternormalisasi:", + "mimeTypeLabel": "Tipe MIME", + "contentTypeLabel": "Header Content-Type", + "charsetLabel": "Charset", + "copyMimeType": "Salin tipe MIME", + "copy": "Salin", + "copied": "Disalin", + "emptyHint": "Masukkan ekstensi atau nama file untuk mencari tipe MIME.", + "notFound": "Tidak ditemukan tipe MIME untuk masukan tersebut.", + "localNote": "Semua berjalan secara lokal di browser Anda. Tidak ada yang diunggah." + }, + "UserAgentParser": { + "title": "Parser User-Agent", + "subtitle": "Tempel string User-Agent (UA) dan dapatkan rincian browser, OS, engine, perangkat, dan CPU. Semua berjalan secara lokal di browser Anda.", + "inputLabel": "String User-Agent", + "placeholder": "Tempel User-Agent Anda di sini…", + "hint": "Tip: Anda dapat menyalin UA dari header permintaan, log, atau devtools browser.", + "clear": "Bersihkan", + "exampleDesktop": "Gunakan contoh desktop", + "exampleMobile": "Gunakan contoh mobile", + "resultLabel": "Rincian", + "browserLabel": "Browser", + "osLabel": "Sistem operasi", + "engineLabel": "Engine", + "deviceTypeLabel": "Jenis perangkat", + "deviceVendorLabel": "Vendor perangkat", + "deviceModelLabel": "Model perangkat", + "cpuArchLabel": "Arsitektur CPU", + "copyJson": "Salin JSON", + "copy": "Salin", + "copied": "Disalin", + "emptyHint": "Tempel string User-Agent untuk melihat rinciannya.", + "localNote": "Semua berjalan secara lokal di browser Anda. Tidak ada yang diunggah." + }, + "MockDataGenerator": { + "title": "Generator data mock", + "subtitle": "Tentukan kolom, pilih tipe data, dan ekspor set data untuk benih, Postman, atau pengujian. Hingga {max} baris, semua di peramban — tidak ada unggahan.", + "mockarooLink": "Alur kerja ala Mockaroo", + "rowsLabel": "Baris", + "rowsHint": "Maksimum {max} baris per pembuatan agar tab tetap responsif.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nama tabel SQL", + "presetsLabel": "Prasetel", + "presetPlaceholder": "Muat prasetel…", + "presetDefault": "Awal (pengguna)", + "presetUsers": "Pengguna & peran", + "presetProducts": "SKU e-commerce", + "presetApiLog": "Log permintaan HTTP", + "generate": "Buat", + "warningDuplicateNames": "Nama kolom duplikat: kolom belakang menimpa kolom depan dalam objek JSON. Ubah nama.", + "schemaHeading": "Skema", + "addField": "Tambah kolom", + "removeField": "Hapus kolom", + "duplicateField": "Duplikasi kolom", + "colName": "Nama", + "colType": "Tipe", + "colOptions": "Opsi", + "colBlank": "Kosong %", + "optMin": "min", + "optMax": "maks", + "optDecimals": "des", + "optDateFrom": "dari tanggal", + "optDateTo": "sampai tanggal", + "optValues": "a, b, c", + "optStep": "langkah", + "optRowBase": "dasar", + "output": "Keluaran", + "outputPlaceholder": "Klik Buat untuk menghasilkan data…", + "copy": "Salin", + "download": "Unduh", + "copied": "Disalin", + "groups": { + "personal": "Pribadi", + "location": "Lokasi", + "internetApi": "Internet & API", + "numbersIds": "Angka & ID", + "dateTime": "Tanggal & waktu", + "textDesign": "Teks & desain", + "custom": "Kustom" + }, + "fieldTypes": { + "first_name": "Nama depan", + "last_name": "Nama belakang", + "full_name": "Nama lengkap", + "email": "Email", + "username": "Nama pengguna", + "password": "Kata sandi", + "phone": "Telepon", + "company": "Perusahaan", + "job_title": "Jabatan", + "avatar_url": "URL avatar", + "city": "Kota", + "country": "Negara", + "country_code": "Kode negara (ISO)", + "street_address": "Alamat", + "zip_code": "Kode pos", + "latitude": "Lintang", + "longitude": "Bujur", + "url": "URL", + "domain": "Domain", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Bilangan bulat", + "float": "Desimal", + "boolean": "Boolean", + "uuid": "UUID", + "credit_card": "Kartu (uji)", + "semver": "SemVer", + "row_index": "Indeks baris", + "sequence": "Urutan", + "date": "Tanggal", + "datetime": "Tanggal & waktu", + "unix_timestamp": "Stempel waktu Unix", + "timezone": "Zona waktu", + "word": "Kata", + "sentence": "Kalimat", + "paragraph": "Paragraf", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Kode mata uang", + "language_code": "Tag bahasa", + "custom_list": "Daftar kustom" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Pengoptimal SVG", + "subtitle": "Tempel SVG, minify dengan SVGO di peramban, dan bandingkan ukuran UTF-8 sebelum/sesudah.", + "inputLabel": "Masukan", + "outputLabel": "Dioptimalkan", + "placeholder": "Tempel cuplikan atau seluruh berkas…", + "outputPlaceholder": "SVG teroptimalkan muncul di sini…", + "chars": "{count} karakter • {bytes} byte (UTF-8)", + "preview": "Pratinjau", + "localNote": "Semua diproses lokal di peramban. Tidak ada yang diunggah.", + "stats": { + "title": "Ukuran (UTF-8)", + "before": "Sebelum", + "after": "Sesudah", + "savedBytes": "Hemat {count} byte ({percent}%)", + "noChange": "Tidak berkurang — sudah minimal atau keluaran kosong." + }, + "actions": { + "clear": "Hapus", + "sample": "Muat contoh", + "copy": "Salin", + "copied": "Disalin", + "download": "Unduh" + }, + "errors": { + "optimizeFailed": "Tidak dapat mengoptimalkan SVG ini." + } + }, + "UnitConverter": { + "title": "Konverter Satuan", + "subtitle": "Konversi antara 323 satuan di 43 kategori", + "category": "Kategori", + "from": "Dari", + "to": "Ke", + "value": "Nilai", + "result": "Hasil", + "swapUnits": "Tukar satuan", + "placeholder": "Masukkan nilai", + "copyResult": "Salin hasil", + "searchCategory": "Cari kategori...", + "selectCategory": "Pilih kategori", + "noResults": "Tidak ada kategori ditemukan." + }, + "DockerComposeGenerator": { + "title": "Generator Docker Compose", + "subtitle": "Pilih basis data populer, cache, antrean pesan, reverse proxy, dan observabilitas — salin docker-compose.yml siap pakai untuk pengembangan lokal.", + "searchPlaceholder": "Cari (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Layanan", + "selectedCount": "{count, plural, other {# layanan}}", + "outputTitle": "docker-compose.yml", + "copy": "Salin", + "copied": "Disalin!", + "download": "Unduh", + "emptyState": "Pilih satu atau lebih layanan untuk menghasilkan YAML Compose.", + "hintPorts": "Port host bawaan mengurangi bentrok; ubah jika Docker melaporkan port terpakai. Ganti kata sandi demo sebelum lingkungan bersama.", + "categories": { + "databases": "Basis data & penyimpanan", + "cache": "Cache & dalam memori", + "queues": "Antrean & perpesanan", + "search": "Pencarian & log", + "storage": "Penyimpanan objek", + "proxy": "Reverse proxy & ingress", + "observability": "Metrik & pelacakan", + "mail": "Email (pengembangan lokal)", + "admin": "Antarmuka admin & BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Bersihkan" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (kompatibel Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (kompatibel Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (kompatibel S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (tangkap SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (server dev)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Geser ubin dan gabungkan untuk mencapai 2048", + "score": "Skor", + "best": "Terbaik", + "newGame": "Permainan Baru", + "howToPlay": "Gunakan tombol panah (atau geser di ponsel) untuk memindahkan ubin. Ubin dengan jumlah yang sama bergabung. Raih 2048 untuk menang!", + "youWin": "Anda mencapai 2048!", + "keepGoing": "Teruskan", + "gameOver": "Permainan Berakhir", + "tryAgain": "Coba Lagi", + "gameOverMessage": "Tidak ada lagi gerakan yang tersisa.", + "undo": "Urungkan" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "tahapan selesai", + "time": "Waktu", + "best": "Terbaik", + "difficulty": { + "easy": "Mudah", + "medium": "Sedang", + "hard": "Sulit" + }, + "stage": "Babak", + "solved": "Solved", + "score": "Skor", + "nextStage": "Tahap berikutnya", + "replay": "Putar Ulang", + "erase": "Hapus", + "howToPlay": "Klik sel, lalu ketuk angka atau tekan 1 -9. Backspace atau Delete membersihkan sel. Angka tebal adalah petunjuk. Isi grid sehingga setiap baris, kolom, dan kotak 3×3 berisi 1 -9 tanpa pengulangan.", + "pencil": "Pensil", + "undo": "Batalkan", + "hint": "Petunjuk", + "hintsUsed": "petunjuk digunakan", + "mistakes": "Kesalahan", + "failed": "Gagal!", + "mistakeMode": "Mode 3 kesalahan" + }, + "Snake": { + "title": "Ular", + "subtitle": "Makan makanan, tumbuh, hindari dinding dan dirimu", + "score": "Skor", + "best": "Terbaik", + "startPrompt": "Tekan tombol panah mana saja", + "startButton": "Mulai Permainan", + "gameOver": "Permainan Selesai", + "playAgain": "Main Lagi", + "howToPlay": "Gunakan tombol panah atau WASD. Makan untuk tumbuh. Kecepatan meningkat setiap 50 poin.", + "diffEasy": "Mudah", + "diffNormal": "Normal", + "diffHard": "Sulit", + "diffInfinite": "Tak Terbatas", + "orTap": "atau ketuk di bawah", + "howToPlayInfinite": "Dinding melingkar — tidak mati karena dinding. Makan untuk tumbuh.", + "shieldActive": "Perisai aktif", + "speedBoost": "Boost kecepatan", + "powerSpeed": "Kecepatan x2", + "powerShield": "Bertahan dari kematian", + "powerShrink": "Mempersingkat ekor" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Koneksi Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Hubungkan ke instans Redis. Jelajahi kunci, edit nilai, dan jalankan perintah. Kredensial dienkripsi di browser Anda.", + "heroAddConnection": "Tambah Koneksi", + "vaultLockedTitle": "Vault Terkunci", + "vaultLockedDesc": "Buka kunci vault untuk mengakses koneksi yang tersimpan." + } + }, + "Minesweeper": { + "title": "Penyapu Ranjau", + "subtitle": "Temukan semua sel aman tanpa meledakkan ranjau", + "mines": "Ranjau", + "newGame": "Game baru", + "win": "Anda menang!", + "loss": "Kalah!", + "playAgain": "Main lagi", + "tryAgain": "Coba lagi", + "startPrompt": "Klik sel mana saja untuk memulai", + "difficulty": { + "beginner": "Pemula", + "intermediate": "Menengah", + "expert": "Ahli" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Susun balok yang jatuh untuk melengkapi baris", + "next": "Berikutnya", + "score": "Skor", + "best": "Terbaik", + "lines": "Baris", + "level": "Level", + "pause": "Jeda", + "resume": "Lanjut", + "start": "Mulai", + "restart": "Mulai ulang", + "paused": "Dijeda", + "gameOver": "Permainan berakhir" + }, + "SshKeyGenerator": { + "title": "Generator Kunci SSH / RSA", + "subtitle": "Buat pasangan kunci Ed25519 atau RSA di browser Anda. Unduh kunci privat dan publik dalam format OpenSSH dan PEM. Tidak ada yang diunggah.", + "keyTypeLabel": "Jenis Kunci", + "recommended": "disarankan", + "ed25519Desc": "Cepat, kecil, modern. Didukung oleh GitHub, GitLab, dan semua server SSH utama.", + "rsa2048Desc": "Kompatibilitas luas. Aman untuk sebagian besar penggunaan.", + "rsa4096Desc": "Kompatibilitas dan kekuatan maksimum untuk sistem lama.", + "commentLabel": "Komentar (opsional)", + "commentHint": "Ditambahkan ke baris kunci publik OpenSSH.", + "generateBtn": "Buat Pasangan Kunci", + "generating": "Membuat…", + "generatingLong": "Membuat (mungkin perlu beberapa saat)…", + "copy": "Salin", + "download": "Unduh", + "privateKeyLabel": "Kunci Privat (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Kunci Publik (format OpenSSH)", + "publicKeySpkiLabel": "Kunci Publik (SPKI PEM)", + "browserNote": "Semua kunci dibuat di browser Anda. Tidak ada yang dikirim ke server mana pun.", + "errors": { + "generationFailed": "Pembuatan kunci gagal" + } + }, + "DnsLookup": { + "title": "Pencarian DNS", + "subtitle": "Kueri rekaman DNS untuk domain apa pun — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Masukkan domain (mis. github.com)", + "lookupBtn": "Cari", + "looking": "Mencari…", + "refreshBtn": "Segarkan", + "recordTypesLabel": "Jenis rekaman", + "allBtn": "Semua", + "tryLabel": "Coba:", + "errors": { + "lookupFailed": "Pencarian DNS gagal" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Generator & Pemeriksa Bcrypt", + "subtitle": "Hash kata sandi dengan bcrypt dan verifikasi hash — semuanya tetap di browser Anda", + "tabs": { + "hash": "Hash", + "verify": "Verifikasi" + }, + "showPassword": "Tampilkan kata sandi", + "hidePassword": "Sembunyikan kata sandi", + "copy": "Salin", + "copied": "Disalin", + "hash": { + "passwordLabel": "Kata sandi", + "passwordPlaceholder": "Masukkan kata sandi untuk di-hash…", + "costLabel": "Faktor biaya", + "costHint": "Biaya yang lebih tinggi menggandakan pekerjaan per langkah. 10-12 adalah pilihan umum untuk produksi.", + "slowWarning": "Faktor biaya 13 ke atas bisa memakan waktu beberapa detik untuk dihitung.", + "generate": "Buat hash", + "generating": "Meng-hash…", + "resultLabel": "Hash Bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomi hash", + "algorithm": "algoritma", + "cost": "biaya", + "salt": "salt (22 karakter)", + "hash": "hash (31 karakter)" + } + }, + "verify": { + "passwordLabel": "Kata sandi", + "passwordPlaceholder": "Masukkan kata sandi yang akan diperiksa…", + "hashLabel": "Hash Bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Masukkan kata sandi dan hash bcrypt untuk membandingkannya.", + "invalidHash": "Ini sepertinya bukan hash bcrypt yang valid.", + "checking": "Membandingkan…", + "match": "Cocok — kata sandi ini menghasilkan hash ini.", + "noMatch": "Tidak cocok — kata sandi ini tidak menghasilkan hash ini." + } + }, + "ChmodCalculator": { + "title": "Kalkulator Chmod", + "subtitle": "Konversi izin file Unix antara kotak centang, oktal, dan notasi simbolik", + "grid": { + "title": "Izin", + "read": "Baca", + "write": "Tulis", + "execute": "Eksekusi", + "owner": "Pemilik", + "group": "Grup", + "others": "Lainnya" + }, + "special": { + "title": "Bit khusus", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Oktal", + "placeholder": "755 atau 4755", + "invalid": "Masukkan 3 atau 4 digit oktal (0-7)." + }, + "symbolic": { + "label": "Simbolik", + "placeholder": "rwxr-xr-x", + "invalid": "Masukkan 9 karakter seperti rwxr-xr-x (s/S, t/T diperbolehkan)." + }, + "output": { + "title": "Perintah", + "copy": "Salin", + "copied": "Disalin" + }, + "explain": { + "line": "{cls} dapat {perms}.", + "none": "{cls} tidak memiliki izin.", + "setuid": "Setuid: file berjalan dengan hak istimewa pemiliknya.", + "setgid": "Setgid: file berjalan dengan hak istimewa grupnya (file baru dalam direktori mewarisi grupnya).", + "sticky": "Sticky: hanya pemilik file yang dapat menghapus atau mengganti namanya di dalam direktori ini." + }, + "presets": { + "title": "Preset umum", + "octal": "Oktal", + "permissions": "Izin", + "description": "Penggunaan umum", + "p644": "File biasa: pemilik menulis, semua orang membaca.", + "p755": "File eksekusi dan direktori: semua orang dapat membaca dan masuk.", + "p600": "File privat: hanya pemilik yang membaca dan menulis.", + "p700": "File eksekusi dan direktori privat: hanya pemilik.", + "p664": "File yang dapat ditulis grup.", + "p775": "Direktori dan file eksekusi yang dapat ditulis grup.", + "p400": "Baca saja untuk pemilik (mis. kunci privat SSH)." + } + }, + "CodeScreenshot": { + "title": "Tangkapan Kode", + "subtitle": "Ubah kode menjadi gambar indah yang mudah dibagikan", + "languageLabel": "Bahasa", + "autoDetect": "Deteksi otomatis", + "backgroundLabel": "Latar belakang", + "paddingLabel": "Padding", + "filenameLabel": "Nama file", + "filenamePlaceholder": "example.ts", + "windowLabel": "Titik jendela", + "themeLabel": "Tema jendela", + "themeDark": "Gelap", + "themeLight": "Terang", + "scaleLabel": "Skala ekspor", + "copyImage": "Salin gambar", + "copied": "Disalin", + "exportPng": "Ekspor PNG", + "inputLabel": "Kode", + "detected": "Terdeteksi: {language}", + "clear": "Bersihkan", + "placeholder": "Tempel kode Anda di sini…", + "previewLabel": "Pratinjau", + "emptyPreview": "Pratinjau kode Anda akan muncul di sini", + "exportFailed": "Gagal mengekspor gambar", + "copyUnsupported": "Menyalin gambar tidak didukung di browser ini", + "copyFailed": "Gagal menyalin gambar", + "backgrounds": { + "ocean": "Samudra", + "sunset": "Senja", + "candy": "Permen", + "forest": "Hutan", + "midnight": "Tengah malam", + "gold": "Emas", + "aurora": "Aurora", + "slate": "Batu tulis", + "transparent": "Transparan" + } + }, + "CssGenerators": { + "title": "Generator CSS", + "subtitle": "Box-shadow, border-radius, dan clamp() fluid — salin CSS-nya.", + "preview": "Pratinjau", + "output": "CSS", + "copy": "Salin", + "copied": "Disalin", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() fluid" + }, + "boxShadow": { + "presets": "Preset", + "layer": "Lapisan", + "addLayer": "Tambah lapisan", + "removeLayer": "Hapus lapisan", + "x": "X", + "y": "Y", + "blur": "Blur", + "spread": "Sebaran", + "color": "Warna", + "alpha": "Alfa", + "inset": "Inset", + "presetNames": { + "subtleCard": "Kartu halus", + "elevated": "Terangkat", + "hardOffset": "Offset tegas", + "neonGlow": "Cahaya neon", + "inner": "Bayangan dalam", + "layeredSmooth": "Berlapis halus" + } + }, + "borderRadius": { + "modeLabel": "Mode", + "modes": { + "simple": "Per sudut", + "blob": "Blob organik" + }, + "unit": "Satuan", + "linkCorners": "Tautkan sudut", + "randomize": "Acak", + "horizontal": "Radius horizontal", + "vertical": "Radius vertikal", + "corners": { + "tl": "Kiri atas", + "tr": "Kanan atas", + "br": "Kanan bawah", + "bl": "Kiri bawah" + } + }, + "clamp": { + "minFont": "Font min (px)", + "maxFont": "Font maks (px)", + "minViewport": "Viewport min (px)", + "maxViewport": "Viewport maks (px)", + "rootFont": "Font root (px)", + "viewportWidth": "Viewport simulasi", + "computedSize": "Ukuran terhitung", + "slope": "Kemiringan", + "intercept": "Intersep", + "previewText": "The quick brown fox", + "invalid": "Masukkan batas viewport yang valid dan berbeda." + } + }, + "ExifViewer": { + "title": "Penampil & Penghapus EXIF", + "subtitle": "Periksa metadata foto dan hapus di browser Anda", + "dropTitle": "Jatuhkan gambar di sini", + "dropHint": "atau klik untuk menjelajah — gambar Anda tidak pernah meninggalkan perangkat", + "browse": "Pilih gambar", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Membaca metadata…", + "parseFailed": "Gagal membaca gambar ini", + "clear": "Hapus gambar", + "gpsWarning": "Berisi lokasi GPS", + "openMap": "Lihat di OpenStreetMap", + "noMetadata": "Tidak ada metadata tertanam yang ditemukan pada gambar ini.", + "facts": { + "name": "Nama", + "type": "Tipe", + "dimensions": "Dimensi", + "size": "Ukuran" + }, + "groups": { + "camera": "Kamera", + "exposure": "Pencahayaan", + "timestamps": "Stempel waktu", + "gps": "Lokasi GPS", + "software": "Perangkat lunak & lainnya" + }, + "gpsCard": { + "latitude": "Lintang", + "longitude": "Bujur", + "altitude": "Ketinggian" + }, + "raw": { + "title": "Semua metadata", + "copyJson": "Salin JSON", + "copied": "Disalin" + }, + "remove": { + "title": "Hapus metadata", + "jpegDescription": "Hapus EXIF, XMP, IPTC, dan komentar tanpa kehilangan kualitas dan tanpa mengompresi ulang gambar.", + "reencodeDescription": "Encode ulang gambar untuk menghapus metadata tertanam. Ini mengompresi ulang piksel.", + "keepIcc": "Pertahankan profil warna (ICC)", + "stripButton": "Hapus metadata", + "reencodeButton": "Encode ulang & hapus", + "unsupported": "Penghapusan metadata tidak didukung untuk tipe file ini.", + "failed": "Gagal menghapus metadata", + "before": "Sebelum", + "after": "Sesudah", + "download": "Unduh gambar yang sudah dibersihkan" + } + }, + "FaviconGenerator": { + "title": "Generator Favicon", + "subtitle": "Buat favicon dan ikon aplikasi dari satu gambar", + "dropTitle": "Jatuhkan gambar di sini", + "dropHint": "PNG, JPEG atau SVG — klik untuk menjelajah", + "browse": "Pilih gambar", + "clear": "Hapus gambar", + "backgroundLabel": "Latar belakang", + "transparent": "Transparan", + "solidColor": "Solid", + "cornerLabel": "Bentuk sudut", + "cornerNone": "Persegi", + "cornerRounded": "Membulat", + "cornerCircle": "Lingkaran", + "maskPaddingLabel": "Padding maskable", + "maskable": "maskable", + "previewLabel": "Pratinjau", + "copy": "Salin", + "headSnippet": "Cuplikan HTML head", + "downloadIco": "Unduh favicon.ico", + "downloadZip": "Unduh semua (.zip)" + }, + "JsonDiff": { + "title": "Diff JSON", + "subtitle": "Perbandingan semantik dan sadar struktur atas dua dokumen JSON — urutan kunci dan format diabaikan", + "options": { + "ignoreArrayOrder": "Abaikan urutan array" + }, + "loadBothSamples": "Muat contoh", + "sample": "Contoh", + "format": "Format", + "clear": "Bersihkan", + "formatError": "Tidak dapat memformat: input bukan JSON yang valid", + "placeholder": "Tempel atau ketik JSON di sini…", + "panels": { + "left": "Asli (kiri)", + "right": "Dimodifikasi (kanan)", + "results": "Perbedaan" + }, + "summary": { + "added": "{count} ditambahkan", + "removed": "{count} dihapus", + "changed": "{count} diubah" + }, + "types": { + "added": "Ditambahkan", + "removed": "Dihapus", + "changed": "Diubah" + }, + "copyChanges": "Salin sebagai JSON", + "copied": "Disalin", + "empty": "Tempel JSON ke kedua panel untuk melihat perbedaannya.", + "identical": "Kedua dokumen identik secara semantik." + }, + "JsonpathPlayground": { + "title": "Playground JSONPath", + "subtitle": "Kueri dokumen JSON dengan JSONPath atau JMESPath dan lihat hasilnya secara langsung", + "engineLabel": "Engine", + "queryLabel": "Kueri", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Contekan", + "cheats": { + "jpAllTitles": "Semua judul buku", + "jpAllAuthors": "Setiap penulis, di mana pun dalam dokumen", + "jpFirstBook": "Buku pertama", + "jpLastBook": "Buku terakhir", + "jpSlice": "Dua buku pertama (slice)", + "jpFilterPrice": "Buku lebih murah dari 40", + "jpFilterInStock": "Judul buku yang tersedia", + "jpRecursive": "Semua nilai, secara rekursif", + "jmAllTitles": "Semua judul buku", + "jmFirstAuthor": "Penulis buku pertama", + "jmLastBook": "Buku terakhir", + "jmSlice": "Dua buku pertama (slice)", + "jmFilterPrice": "Judul buku yang lebih murah dari 40", + "jmCount": "Hitung buku yang tersedia", + "jmProjection": "Bentuk ulang setiap buku menjadi objek baru", + "jmSort": "Judul diurutkan berdasarkan harga" + }, + "panels": { + "input": "Input JSON", + "results": "Hasil" + }, + "sample": "Contoh", + "format": "Format", + "clear": "Bersihkan", + "formatError": "Tidak dapat memformat: input bukan JSON yang valid", + "placeholder": "Tempel atau ketik JSON di sini…", + "outputPlaceholder": "Kecocokan akan muncul di sini…", + "matchCount": "{count, plural, one {# kecocokan} other {# kecocokan}}", + "copy": "Salin", + "copied": "Disalin" + }, + "KeycodeInspector": { + "title": "Inspektur Keycode", + "subtitle": "Tekan tombol apa saja untuk memeriksa event keyboard JavaScript-nya", + "capture": { + "prompt": "Tekan tombol apa saja…", + "ready": "Mendengarkan — tekan tombol apa saja di keyboard Anda", + "clickToFocus": "Klik di sini untuk fokus, lalu tekan tombol apa saja" + }, + "hero": { + "spaceKey": "(Spasi)", + "keyLabel": "event.key", + "repeat": "ulang" + }, + "copyJson": "Salin JSON", + "copiedJson": "JSON event disalin ke clipboard", + "deprecated": "usang", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standar", + "left": "Kiri", + "right": "Kanan", + "numpad": "Numpad", + "unknown": "Tidak diketahui" + }, + "history": { + "title": "Riwayat", + "clear": "Bersihkan riwayat", + "empty": "Penekanan tombol akan muncul di sini." + } + }, + "MarkdownTableGenerator": { + "title": "Generator Tabel Markdown", + "subtitle": "Edit grid, tempel dari spreadsheet, dan salin tabel Markdown atau HTML yang tersejajar rapi", + "addRow": "Tambah baris", + "addColumn": "Tambah kolom", + "importLabel": "Impor", + "importPlaceholder": "Tempel TSV (dari Excel / Google Sheets) atau CSV di sini…", + "importButton": "Impor ke grid", + "pasteClipboard": "Tempel dari clipboard", + "importEmpty": "Tidak ada yang diimpor — tempel beberapa baris dulu", + "clipboardError": "Tidak dapat membaca dari clipboard", + "clear": "Bersihkan", + "panels": { + "editor": "Editor tabel", + "output": "Output" + }, + "align": { + "left": "Rata kiri", + "center": "Rata tengah", + "right": "Rata kanan" + }, + "headerPlaceholder": "Header", + "cellPlaceholder": "Sel", + "removeRow": "Hapus baris", + "removeColumn": "Hapus kolom", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Salin", + "copied": "Disalin" + }, + "TimezoneConverter": { + "title": "Konverter Zona Waktu", + "subtitle": "Konversi satu momen ke seluruh dunia untuk merencanakan rapat.", + "dateTimeLabel": "Tanggal & waktu", + "now": "Sekarang", + "sourceZoneLabel": "Zona waktu sumber", + "selectZone": "Pilih zona", + "searchZone": "Cari zona waktu...", + "noZone": "Zona waktu tidak ditemukan.", + "scrubLabel": "Geser waktu", + "reset": "Atur ulang", + "targetsLabel": "Zona tujuan", + "addZone": "Tambah zona", + "remove": "Hapus", + "localBadge": "Lokal", + "invalidDate": "Masukkan tanggal dan waktu yang valid.", + "bands": { + "work": "Jam kerja (09:00–18:00)", + "edge": "Jam pinggir (pagi / malam)", + "night": "Malam" + } + }, + "TokenCounter": { + "title": "Penghitung Token LLM", + "subtitle": "Hitung token, visualisasikan potongan, dan perkirakan biaya API.", + "inputLabel": "Teks", + "sample": "Contoh", + "clear": "Bersihkan", + "placeholder": "Tempel teks, kode, atau JSON untuk menghitung token...", + "encoding": "Encoding", + "loading": "Memuat tokenizer...", + "encodings": { + "o200k": "o200k_base (GPT-4o / seri o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Token", + "characters": "Karakter", + "words": "Kata", + "ratio": "Token/kata", + "ratioSub": "kira-kira" + }, + "claudeRow": "Claude (perkiraan)", + "estimate": "perkiraan", + "viz": { + "title": "Visualisasi token", + "toggle": "Tampilkan", + "hint": "Alihkan untuk mewarnai setiap token sebagai satu potongan.", + "empty": "Masukkan teks untuk memvisualisasikan token.", + "truncated": "Menampilkan {limit} token pertama dari {total}." + }, + "cost": { + "title": "Kalkulator biaya", + "disclaimer": "nilai bawaan dapat diedit — verifikasi harga terkini", + "model": "Model", + "expectedOutput": "Token output yang diharapkan", + "customInput": "Input $/MTok", + "customOutput": "Output $/MTok", + "inputCost": "Biaya input", + "outputCost": "Biaya output", + "total": "Total", + "per1000": "Per 1000 panggilan" + } + }, + "WebhookTester": { + "title": "Penguji Webhook", + "subtitle": "Buat URL sementara dan periksa permintaan webhook masuk secara langsung.", + "intro": { + "title": "Buat URL webhook untuk mulai menangkap permintaan", + "hint": "Anda akan mendapat URL yang tidak dapat ditebak yang merekam setiap permintaan masuk. URL kedaluwarsa setelah 24 jam." + }, + "createBtn": "Buat URL webhook", + "creating": "Membuat…", + "urlLabel": "URL webhook Anda", + "expiresIn": "Kedaluwarsa dalam {countdown}", + "copyUrlBtn": "Salin URL", + "copied": "Disalin", + "clearBtn": "Bersihkan", + "newBinBtn": "URL baru", + "requestsTitle": "Permintaan", + "selectPrompt": "Pilih permintaan untuk memeriksa detailnya.", + "expiredNotice": "URL webhook ini telah kedaluwarsa. Buat yang baru untuk terus menguji.", + "base64Badge": "base64", + "truncatedBadge": "dipotong", + "empty": { + "title": "Menunggu permintaan…", + "hint": "Kirim permintaan ke URL Anda dan itu akan muncul di sini seketika. Coba ini:" + }, + "detail": { + "receivedAt": "Diterima", + "source": "IP sumber", + "contentType": "Content-Type", + "query": "Parameter kueri", + "headers": "Header", + "body": "Body", + "copyBody": "Salin body", + "emptyBody": "Tidak ada body" + }, + "errors": { + "createFailed": "Gagal membuat URL webhook. Silakan coba lagi.", + "clearFailed": "Gagal membersihkan permintaan. Silakan coba lagi." + } + }, + "WebsocketTester": { + "title": "Penguji WebSocket", + "subtitle": "Sambungkan ke endpoint WebSocket lalu kirim, terima, dan periksa pesan secara langsung", + "urlLabel": "URL endpoint", + "protocolsLabel": "Subprotokol", + "protocolsPlaceholder": "opsional, dipisahkan koma", + "connect": "Hubungkan", + "disconnect": "Putuskan", + "status": { + "disconnected": "Terputus", + "connecting": "Menghubungkan", + "connected": "Terhubung", + "error": "Kesalahan" + }, + "messages": { + "connecting": "Menghubungkan ke {url}…", + "connected": "Koneksi berhasil dibuat", + "connectedWithProtocol": "Koneksi berhasil dibuat (protokol: {protocol})", + "invalidUrl": "URL tidak valid — harus dimulai dengan ws:// atau wss://", + "connectFailed": "Gagal membuka koneksi", + "socketError": "Terjadi kesalahan koneksi", + "notConnected": "Tidak terhubung — hubungkan dulu", + "binaryMessage": "Menerima pesan biner" + }, + "log": { + "title": "Log Pesan", + "empty": "Belum ada pesan. Hubungkan dan mulai mengirim.", + "copyAll": "Salin semua", + "download": "Unduh sebagai .txt", + "clear": "Bersihkan log", + "prettyPrint": "Cetak rapi JSON", + "showRaw": "Tampilkan teks mentah" + }, + "composer": { + "placeholder": "Ketik pesan untuk dikirim…", + "send": "Kirim", + "formatJson": "Format sebagai JSON", + "invalidJson": "Pesan bukan JSON yang valid", + "hint": "Cmd/Ctrl+Enter untuk mengirim" + }, + "ping": { + "label": "Ping otomatis", + "seconds": "dtk", + "intervalTitle": "Interval ping dalam detik", + "payloadPlaceholder": "payload ping" + } + }, + "WhoisLookup": { + "title": "Pencarian Whois", + "subtitle": "Cari detail pendaftaran untuk domain atau alamat IP apa pun melalui RDAP", + "placeholder": "example.com atau 8.8.8.8", + "lookupBtn": "Cari", + "looking": "Mencari…", + "refreshBtn": "Segarkan", + "tryLabel": "Coba:", + "copy": "Salin", + "copied": "Disalin", + "notAvailable": "Tidak tersedia", + "summary": { + "registrar": "Registrar", + "network": "Jaringan" + }, + "dates": { + "created": "Dibuat", + "updated": "Diperbarui", + "expires": "Kedaluwarsa" + }, + "sections": { + "status": "Status", + "nameservers": "Nameserver", + "contacts": "Kontak", + "rawRdap": "RDAP mentah" + }, + "contacts": { + "roles": "Peran", + "name": "Nama", + "organization": "Organisasi" + }, + "errors": { + "invalidTarget": "Masukkan nama domain atau alamat IP yang valid", + "lookupFailed": "Pencarian gagal (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/it.json b/apps/desktop-ui/messages/it.json new file mode 100644 index 00000000..dc923dfc --- /dev/null +++ b/apps/desktop-ui/messages/it.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Pannello di controllo", + "settings": "Impostazioni", + "apiClient": "Cliente API", + "httpStatusCodes": "Codici di stato HTTP", + "passwordManager": "Gestore delle password", + "environmentManager": "Gestore ambienti", + "bookmarks": "Segnalibri", + "jsonFormatter": "Formattatore JSON", + "emailValidator": "Validatore di posta elettronica", + "nosqlExplorer": "Esplora database", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Sandbox crittografia", + "jwtDecoder": "Decodificatore JWT", + "certificatePemDecoder": "Decodificatore certificati / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Formattatore GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Parser URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Segreto / chiave API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Controllo contrasto", + "jsonSchemaGenerator": "Generatore di schema JSON", + "imageToBase64": "Immagine su Base64", + "imageCompressor": "Compressore immagini", + "cssGradientBuilder": "Generatore di gradienti CSS", + "gitignoreGenerator": "Generatore .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Snippet di codice", + "qrCodeGenerator": "Generatore di codici QR", + "dnsLookup": "Ricerca DNS", + "ipSubnetCalculator": "Calcolatrice IP / subnet", + "hashGenerator": "Generatore di hash", + "hmacGenerator": "Generatore HMAC", + "totpGenerator": "Codice TOTP / 2FA", + "markdownPreview": "Anteprima Markdown", + "numberBaseConverter": "Convertitore di base", + "formatConverter": "Convertitore di formati", + "mockDataGenerator": "Generatore di dati mock", + "svgOptimizer": "Ottimizzatore SVG", + "unitConverter": "Convertitore di unità", + "dockerComposeGenerator": "Generatore Docker Compose", + "breakRoom": "Spazio per le pause di lavoro", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Formattatore YAML", + "sshKeyGenerator": "Generatore di chiavi SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Calcolatore Chmod", + "codeScreenshot": "Screenshot di codice", + "cssGenerators": "Generatori CSS", + "exifViewer": "Visualizzatore EXIF", + "faviconGenerator": "Generatore di favicon", + "jsonDiff": "Diff JSON", + "jsonpathPlayground": "Playground JSONPath", + "keycodeInspector": "Ispettore di keycode", + "markdownTableGenerator": "Tabella Markdown", + "timezoneConverter": "Convertitore di fusi orari", + "tokenCounter": "Contatore di token", + "webhookTester": "Tester di webhook", + "websocketTester": "Tester WebSocket", + "whoisLookup": "Ricerca Whois" + }, + "Help": { + "title": "Aiuto e documentazione", + "subtitle": "Come funziona MyDevTools, cosa fa ogni app e come sicurezza e crittografia proteggono i tuoi dati.", + "tabs": { + "overview": "Panoramica", + "apps": "App", + "security": "Sicurezza", + "tips": "Suggerimenti" + }, + "overview": { + "whatIs": { + "title": "Che cos’è MyDevTools?", + "description": "Uno spazio di lavoro per sviluppatori con accesso: una dashboard e una raccolta di strumenti quotidiani.", + "p1": "Dopo l’accesso, l’app stabilisce una sessione con l’API di MyDevTools per consentire agli strumenti di caricare e salvare i tuoi dati. La barra laterale elenca tutte le app; puoi nascondere gli strumenti che non usi da", + "settingsLink": "Impostazioni", + "p1After": ".", + "p2": "Alcune funzionalità usano una password principale (o chiave globale del vault) così che segreti come password e stringhe di connessione vengano crittografati sul tuo dispositivo prima di arrivare al server." + }, + "quickLinks": { + "title": "Link rapidi", + "dashboard": "Pannello di controllo", + "settings": "Impostazioni" + } + }, + "apps": { + "intro": "Apri qualsiasi strumento dalla barra laterale o dalla dashboard. Qui sotto trovi una guida breve per ogni app.", + "open": "Apri", + "howItWorks": "Come funziona", + "generic": "Usa questo strumento dalla barra laterale per il suo flusso di lavoro dedicato." + }, + "security": { + "session": { + "title": "Accesso e sessione API", + "description": "Identità e come il browser comunica con il backend.", + "items": { + "authentication": "Autenticazione tramite Firebase. Dopo il login, l’app scambia l’ID token di Firebase con cookie dell’API.", + "accessTokens": "Token di accesso sono JWT firmati sul server (HS256) e indicano quale utente sta chiamando l’API.", + "refreshTokens": "Token di refresh sono salvati in cookie HttpOnly; il server conserva un hash SHA-256 del token, non il token in chiaro." + } + }, + "encryption": { + "title": "Crittografia da conoscere", + "description": "Crittografia lato client per vault e dati di connessione sensibili.", + "p1": "Per il gestore delle password, il gestore degli ambienti e le connessioni salvate in NoSQL Explorer, l'app web usa la Web Crypto API del browser:", + "items": { + "kdf": "PBKDF2 con SHA-256, 100.000 iterazioni e un salt casuale deriva una chiave a 256 bit dalla password principale.", + "aes": "AES-256-GCM cifra i dati; viene usato un IV di 12 byte casuale per ogni cifratura.", + "scope": "La chiave derivata resta nel browser (non esportabile). Il server riceve solo ciphertext e IV, non la password principale né segreti in chiaro." + }, + "warning": "Se dimentichi la password principale, i dati crittografati non possono essere recuperati. Conservala con cura." + }, + "logout": { + "title": "Logout e dati locali", + "body": "Il logout elimina i segreti in memoria, lo stato del vault e, quando applicabile, il materiale della chiave principale da IndexedDB, e termina la sessione API. I dati mantenuti solo nello storage locale del dispositivo possono rimanere finché non li cancelli dal browser." + } + }, + "tips": { + "title": "Usare MyDevTools in sicurezza", + "items": { + "masterPassword": "Usa una password principale forte e unica per le funzionalità del vault.", + "apiClient": "Tratta il Cliente API come qualsiasi client HTTP: evita di incollare segreti di produzione in ambienti condivisi se non sai chi può vederli.", + "nosql": "Esplora database si collega a database configurati da te; usa utenti a privilegi minimi e regole di rete quando possibile.", + "json": "JSON Formatter è locale nel browser—evita comunque di incollare dati molto sensibili su macchine condivise.", + "settings": "Personalizza gli strumenti visibili in Impostazioni per mantenere la barra laterale più pulita." + } + } + }, + "Settings": { + "title": "Impostazioni", + "language": "Lingua", + "spanish": "spagnolo", + "english": "Inglese", + "french": "Francese", + "arabic": "عربى", + "catalan": "catalano", + "chineseSimplified": "Cinese (semplificato)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "tedesco", + "danish": "danese", + "afrikaans": "afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ucraino" + }, + "SettingsPage": { + "title": "Impostazioni", + "subtitle": "Gestisci le impostazioni e le preferenze del tuo account.", + "userProfile": { + "title": "Profilo utente", + "description": "Le tue informazioni personali e i dettagli dell'account.", + "avatarAlt": "Utente", + "anonymousUser": "Utente anonimo", + "noEmailProvided": "Nessuna e-mail fornita", + "notLoggedIn": "Non effettuato l'accesso." + }, + "tools": { + "title": "Strumenti e funzionalità", + "description": "Scegli quali strumenti visualizzare nella barra laterale di navigazione." + }, + "appearance": { + "title": "Aspetto", + "description": "Personalizza l'aspetto dell'applicazione sul tuo dispositivo.", + "themePreference": "Preferenza tema", + "themes": { + "light": "Leggero", + "dark": "Buio", + "system": "Sistema" + } + }, + "language": { + "title": "Lingua", + "description": "Imposta la lingua utilizzata sulla piattaforma.", + "selectLanguage": "Seleziona lingua", + "selectPlaceholder": "Seleziona una lingua", + "helpText": "La modifica della lingua aggiornerà i menu e le etichette su tutta la piattaforma.", + "languages": { + "en": "Inglese", + "fr": "Francese", + "es": "Español (spagnolo)", + "ar": "عربى (arabo)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (greco)", + "de": "Deutsch (tedesco)", + "da": "Dansk (danese)", + "af": "afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italiano", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Buongiorno", + "afternoon": "Buon pomeriggio", + "evening": "Buonasera" + }, + "commaName": ", {name}", + "welcomeBack": "Bentornato", + "welcomeBackNamed": "Bentornato, {name}", + "tagline": "Cosa ti piacerebbe costruire oggi?", + "brandName": "MyDevTools", + "stats": { + "tools": "Utensili", + "favorites": "Preferiti", + "pinned": "In evidenza", + "recent": "Usato di recente" + }, + "sections": { + "recentlyUsed": "Usato di recente", + "favorites": "Preferiti", + "whatsNew": "Novità", + "pinned": "In evidenza" + }, + "groups": { + "apps": "App" + }, + "toolCard": { + "defaultDescription": "Esplora questo strumento per una migliore funzionalità.", + "launchTool": "Strumento di avvio" + }, + "tools": { + "toDo": { + "title": "Compiti", + "description": "Gestisci le tue liste di cose da fare in modo efficiente." + }, + "notes": { + "title": "Note", + "description": "Capture ideas and organize your thoughts." + }, + "passwordManager": { + "title": "Gestore delle password", + "description": "Archivia e gestisci in modo sicuro le tue password." + }, + "environmentManager": { + "title": "Gestore ambienti", + "description": "Salva variabili in stile .env per progetto e ambiente con tag." + }, + "bookmarks": { + "title": "Segnalibri", + "description": "Gestisci e organizza i tuoi segnalibri." + }, + "apiClient": { + "title": "Cliente API", + "description": "Testare ed eseguire il debug delle richieste HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Esplora i database MongoDB." + }, + "emailValidator": { + "title": "Validatore di posta elettronica", + "description": "Verificare e convalidare gli indirizzi e-mail." + }, + "encryptionPlayground": { + "title": "Sandbox crittografia", + "description": "AES-GCM con chiave grezza o passphrase; pacchetti JSON, Web Crypto solo nel browser." + }, + "jwtDecoder": { + "title": "Decodificatore JWT", + "description": "Decodifica intestazione, payload e scadenza dei JWT." + }, + "certificatePemDecoder": { + "title": "Decodificatore certificati / PEM", + "description": "Decodifica certificati X.509 e CSR: soggetto, emittente, validità e SAN." + }, + "sshKeyGenerator": { + "title": "Generatore di chiavi SSH / RSA", + "description": "Genera coppie di chiavi Ed25519 o RSA. Scarica le chiavi nei formati OpenSSH e PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Formattatore GraphQL", + "description": "Formatta GraphQL con evidenziazione della sintassi e un editor di query semplice." + }, + "yamlFormatter": { + "title": "Formattatore YAML", + "description": "Formatta, valida e converti YAML in JSON nel browser." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Parser URL", + "description": "Scompone un URL in protocollo, host, percorso, parametri di query e hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Segreto / chiave API", + "description": "Stringhe casuali crittografiche con alfabeto, lunghezza ed esportazione massiva configurabili." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Controllo contrasto", + "description": "Rapporto di contrasto WCAG 2.1 tra testo e sfondo (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Generatore di schema JSON", + "description": "Ricava uno schema JSON e modelli tipizzati da un campione JSON." + }, + "imageToBase64": { + "title": "Immagine su Base64", + "description": "Converti immagini in URI dati o stringhe Base64 non elaborate." + }, + "imageCompressor": { + "title": "Compressore immagini", + "description": "Comprimi JPEG, PNG e WebP in locale con uno slider di qualità." + }, + "cssGradientBuilder": { + "title": "Generatore di gradienti CSS", + "description": "Generatore di gradienti visivi CSS con controllo dell'angolo e interruzioni del colore." + }, + "gitignoreGenerator": { + "title": "Generatore .gitignore", + "description": "Genera istantaneamente file .gitignore uniti da oltre 500 stack tecnologici." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Converti file CSV o Excel in JSON ed esporta in CSV o XLSX." + }, + "snippetManager": { + "title": "Snippet di codice", + "description": "Salva snippet con evidenziazione sintattica, rilevamento automatico della lingua e formattazione." + }, + "qrCodeGenerator": { + "title": "Generatore di codici QR", + "description": "Crea codici QR PNG da testo o URL nel browser." + }, + "ipSubnetCalculator": { + "title": "Calcolatrice IP / subnet", + "description": "IPv4 e IPv6 CIDR: maschera, intervalli e conteggio indirizzi." + }, + "dnsLookup": { + "title": "Ricerca DNS", + "description": "Interroga i record DNS per qualsiasi dominio — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Generatore di hash", + "description": "MD5, SHA-1 e SHA-2 per testo o file." + }, + "hmacGenerator": { + "title": "Generatore HMAC", + "description": "Firma webhook e testa integrazioni API con HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Generatore di codice TOTP / 2FA", + "description": "Incolla un segreto Base32 dell’autenticatore; il codice a sei cifre si aggiorna ogni 30 s per testare accesso e MFA." + }, + "markdownPreview": { + "title": "Anteprima Markdown", + "description": "Renderer Markdown in tempo reale con esportazione HTML e conversione HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Convertitore di base numerica", + "description": "Converti interi tra basi da 2 a 36." + }, + "formatConverter": { + "title": "Convertitore di formati", + "description": "Converti tra JSON, YAML, TOML e XML in qualsiasi direzione. Tutte le 12 combinazioni sono supportate." + }, + "mockDataGenerator": { + "title": "Generatore di dati mock", + "description": "Definisci colonne, scegli i tipi ed esporta dataset per seed, Postman o test. Fino a 5000 righe, tutto nel browser: nulla viene caricato." + }, + "httpStatusCodes": { + "title": "Codici di stato HTTP", + "description": "Cerca codici di stato HTTP con descrizioni (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Ottimizzatore SVG", + "description": "Incolla SVG, rimuovi il superfluo con SVGO e vedi il risparmio di byte UTF-8." + }, + "unitConverter": { + "title": "Convertitore di unità", + "description": "Converti tra 323 unità in 43 categorie scientifiche e ingegneristiche." + }, + "dockerComposeGenerator": { + "title": "Generatore Docker Compose", + "description": "Scegli database, cache, code, proxy e immagini di osservabilità; esporta un docker-compose.yml per stack locali." + }, + "game2048": { + "title": "2048", + "description": "Fai scorrere e unisci le tessere per raggiungere il 2048. Classico gioco di puzzle numerico." + }, + "sudoku": { + "title": "Sudoku", + "description": "Classico puzzle numerico. 3 livelli di difficoltà, 150 livelli ciascuno." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Sfoglia le chiavi, ispeziona i valori ed esegui comandi su qualsiasi istanza Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Generatore e verificatore Bcrypt", + "description": "Genera hash di password con bcrypt e verifica hash esistenti." + }, + "chmodCalculator": { + "title": "Calcolatore Chmod", + "description": "Converti i permessi Unix tra ottale, simbolico e caselle di controllo." + }, + "codeScreenshot": { + "title": "Screenshot di codice", + "description": "Trasforma il codice sorgente in immagini eleganti e condivisibili." + }, + "cssGenerators": { + "title": "Generatori CSS", + "description": "Generatori di box-shadow, border-radius e clamp() fluido." + }, + "exifViewer": { + "title": "Visualizzatore e rimozione EXIF", + "description": "Ispeziona e rimuovi i metadati EXIF delle foto in locale." + }, + "faviconGenerator": { + "title": "Generatore di favicon", + "description": "Genera favicon, icone dell'app e snippet di manifest." + }, + "jsonDiff": { + "title": "Diff JSON", + "description": "Confronto semantico e consapevole della struttura tra due documenti JSON." + }, + "jsonpathPlayground": { + "title": "Playground JSONPath", + "description": "Interroga JSON con JSONPath o JMESPath e vedi i risultati in tempo reale." + }, + "keycodeInspector": { + "title": "Ispettore di keycode", + "description": "Premi un tasto per vedere i suoi codici evento JavaScript e i modificatori." + }, + "markdownTableGenerator": { + "title": "Generatore di tabelle Markdown", + "description": "Crea tabelle Markdown e HTML in un editor a griglia simile a un foglio di calcolo." + }, + "timezoneConverter": { + "title": "Convertitore di fusi orari", + "description": "Converti un orario tra molti fusi con indicazioni di cambio giorno e orario lavorativo." + }, + "tokenCounter": { + "title": "Contatore di token LLM", + "description": "Conta i token LLM, visualizza i chunk e stima il costo dell'API." + }, + "webhookTester": { + "title": "Tester di webhook", + "description": "Cattura e ispeziona le richieste webhook in arrivo in tempo reale." + }, + "websocketTester": { + "title": "Tester WebSocket", + "description": "Connettiti a endpoint WebSocket e scambia messaggi in tempo reale." + }, + "whoisLookup": { + "title": "Ricerca Whois", + "description": "Cerca i dettagli di registrazione di domini e IP tramite RDAP." + } + }, + "tabs": { + "apps": "App", + "analytics": "Analisi", + "ui": "Interfaccia" + }, + "analytics": { + "title": "I tuoi dati a colpo d’occhio", + "subtitle": "Conteggi sincronizzati con il tuo account sul server.", + "loadError": "Impossibile caricare le statistiche.", + "retry": "Riprova", + "loginHint": "Accedi per vedere analisi d’uso e preferenze di interfaccia.", + "passwordEntries": "Voci password", + "bookmarks": "Segnalibri", + "bookmarkFolders": "Cartelle segnalibri", + "tasksTotal": "Attività", + "tasksCompleted": "Completate", + "tasksOngoing": "In corso", + "tasksNotStarted": "Non iniziate", + "projects": "Progetti", + "nosqlConnections": "Connessioni DB", + "notes": "Note", + "apiClientCollections": "Raccolte API", + "apiClientEnvironments": "Ambienti API", + "apiClientHistory": "Voci cronologia API", + "jsonFormatterDocuments": "Documenti formattatore JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Snippet di codice", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Preferenze interfaccia", + "subtitle": "Tema, lingua e strumenti visibili nella barra laterale — dalle impostazioni account.", + "loadError": "Impossibile caricare le preferenze.", + "loginHint": "Accedi per vedere le preferenze di interfaccia salvate.", + "theme": "Tema", + "accent": "Colore accento", + "locale": "Lingua", + "enabledTools": "Strumenti visibili", + "favoriteTools": "Strumenti preferiti", + "toolsWithUsage": "Strumenti con utilizzo tracciato" + }, + "signIn": "Accedi", + "noResults": "Nessuno strumento corrisponde alla tua ricerca", + "noResultsHint": "Prova una parola chiave diversa o cancella il filtro.", + "filterAll": "Tutti", + "loginCta": { + "description": "Accedi per tenere traccia degli strumenti usati di recente." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Caricamento...", + "myTasksTitle": "I miei compiti", + "statsLine": "Attività {total} • {percent}% completate", + "statsLineDesktop": "{total} totale • {percent}% completato" + }, + "filters": { + "all": "Tutto", + "total": "Totale", + "allProjects": "Tutti i progetti", + "export": "Esportare", + "kanban": "Kanban", + "list": "Lista", + "searchPlaceholder": "Cerca attività...", + "searchAria": "Compiti di ricerca", + "clearSearchAria": "Cancella ricerca", + "foundTasks": "Trovato {count} {count, plural, one {task} other {tasks}}", + "filterByAllAria": "Filtra per Tutti", + "filterByStatusAria": "Filtra per {status}" + }, + "status": { + "not-started": { + "label": "Non iniziato", + "description": "Attività che non sono state ancora avviate" + }, + "ongoing": { + "label": "In corso", + "description": "Attività attualmente in corso" + }, + "completed": { + "label": "Completato", + "description": "Compiti finiti" + } + }, + "priorities": { + "high": "Alto", + "medium": "Medio", + "low": "Basso" + }, + "form": { + "addNewTaskLabel": "Aggiungi nuova attività", + "multilinePlaceholder": "Aggiungi dettagli attività... (Maiusc+Invio per una nuova riga)", + "singlelinePlaceholder": "Cosa bisogna fare? Premi Invio per aggiungere", + "taskInputAria": "Ingresso attività", + "projectPlaceholder": "Progetto", + "selectProject": "Seleziona Progetto", + "errorSelectProject": "Seleziona un progetto o creane uno nuovo per aggiungere un'attività.", + "hintMultiline": "Maiusc+Invio per una nuova riga, Invio per aggiungere", + "hintSingleline": "Inserisci per aggiungere", + "shortcutsAria": "Scorciatoie da tastiera", + "shortcutsLabel": "Scorciatoie", + "shortcutAddTask": "Aggiungi attività", + "shortcutNewLine": "Nuova linea", + "shortcutClear": "Chiaro", + "addButton": "Aggiungere" + }, + "list": { + "ariaLabel": "Elenco attività", + "loading": "Caricamento delle tue attività...", + "emptyTitle": "Nessuna attività trovata", + "emptyPrimary": "Inizia aggiungendo la tua prima attività qui sopra! Premi Invio per aggiungere rapidamente attività.", + "emptySecondary": "Prova a modificare i filtri o la query di ricerca." + }, + "mobileDrawer": { + "addTaskSrOnly": "Aggiungi attività", + "addNewTaskTitle": "Aggiungi nuova attività", + "addNewTaskDescription": "Crea una nuova attività per monitorare i tuoi progressi.", + "cancel": "Cancellare" + }, + "kanban": { + "ariaLabel": "Tabellone Kanban", + "loading": "Caricamento delle tue attività...", + "dropHere": "Rilascia l'attività qui", + "noTasksYet": "Nessuna attività ancora", + "emptyColumnAria": "Colonna vuota", + "columnAria": "Colonna {title} con attività {count}", + "countInColumnAria": "{count} attività in {title}" + }, + "taskItem": { + "confirmDelete": "Sei sicuro di voler eliminare questa attività?", + "statusPlaceholder": "Stato", + "changeStatusAria": "Modifica lo stato dell'attività", + "taskOptionsAria": "Opzioni attività", + "editTask": "Modifica attività", + "copyTask": "Copia attività", + "copied": "Copiato!", + "deleteTask": "Elimina attività", + "statusHeading": "Stato", + "editTaskAria": "Modifica attività" + }, + "editDialog": { + "title": "Modifica attività", + "description": "Aggiorna i dettagli delle attività, aggiungi attività secondarie, imposta le priorità e altro ancora.", + "taskTitleLabel": "Titolo dell'attività *", + "taskTitlePlaceholder": "Cosa bisogna fare?", + "descriptionLabel": "Descrizione", + "descriptionPlaceholder": "Aggiungi ulteriori dettagli su questa attività...", + "statusLabel": "Stato", + "priorityLabel": "Priorità", + "dueDateLabel": "Scadenza", + "pickDate": "Scegli una data", + "clearDate": "Cancella data", + "projectLabel": "Progetto", + "selectProjectPlaceholder": "Seleziona un progetto", + "noProject": "Nessun progetto", + "timeEstimateLabel": "Tempo stimato (minuti)", + "timeEstimatePlaceholder": "Quanto tempo ci vorrà?", + "tagsLabel": "Tag", + "customTagNamePlaceholder": "Nome dell'etichetta personalizzata", + "subtasksLabel": "Attività secondarie", + "addSubtaskPlaceholder": "Aggiungi un'attività secondaria...", + "cancel": "Cancellare", + "saving": "Risparmio...", + "saveChanges": "Salva modifiche" + }, + "export": { + "title": "Esporta attività", + "description": "Esporta le tue attività nei formati JSON, CSV o Excel.", + "projectLabel": "Progetto", + "statusLabel": "Stato", + "tagLabel": "Etichetta", + "allProjects": "Tutti i progetti", + "allStatuses": "Tutti gli stati", + "allTags": "Tutti i tag", + "exportAsJsonTitle": "Esporta come JSON", + "exportAsJsonDescription": "Esporta i dati delle attività, incluse attività secondarie, tag e metadati.", + "exportJson": "Esporta JSON", + "exportAsCsvExcelTitle": "Esporta come CSV/Excel", + "exportAsCsvExcelDescription": "Esporta attività per applicazioni di fogli di calcolo.", + "exportCsv": "Esporta CSV", + "exportExcel": "Esporta Excel", + "close": "Vicino", + "toastNoTasks": "Nessuna attività da esportare con i filtri attuali", + "toastExportedJson": "Attività {count} esportate in JSON!", + "toastExportedCsv": "Attività {count} esportate in CSV!", + "toastExportedExcel": "Attività {count} esportate in Excel!", + "toastFailedJson": "Impossibile esportare in JSON", + "toastFailedCsv": "Impossibile esportare in CSV", + "toastFailedExcel": "Impossibile esportare in Excel", + "unknownProject": "Progetto sconosciuto" + }, + "ack": { + "taskAddedTitle": "Attività aggiunta correttamente", + "taskAddedFailedTitle": "Impossibile aggiungere attività", + "taskUpdatedTitle": "Attività aggiornata correttamente", + "taskUpdatedFailedTitle": "Impossibile aggiornare l'attività", + "taskStatusUpdateFailedTitle": "Impossibile aggiornare lo stato dell'attività", + "taskMovedTitle": "Attività spostata in {status}", + "tryAgain": "Per favore riprova.", + "taskDeletedTitle": "Attività \"{text}\" eliminata", + "undo": "Disfare", + "taskRestoredTitle": "Attività ripristinata", + "taskRestoredSuccessTitle": "Attività ripristinata correttamente", + "taskRestoreFailedTitle": "Impossibile ripristinare l'attività. Per favore riprova.", + "taskDeleteFailedTitle": "Impossibile eliminare l'attività. Per favore riprova.", + "tasksImportedTitle": "Importazione {count} {count, plural, one {task} other {tasks}} importata con successo!", + "tasksImportFailedTitle": "Impossibile importare le attività. Per favore riprova.", + "unknownProject": "Progetto sconosciuto", + "projectCreatedTitle": "Progetto creato con successo", + "projectCreateFailedTitle": "Impossibile creare il progetto", + "projectUpdatedTitle": "Progetto aggiornato con successo", + "projectUpdateFailedTitle": "Impossibile aggiornare il progetto", + "projectDeletedTitle": "Progetto eliminato correttamente", + "projectDeleteFailedTitle": "Impossibile eliminare il progetto" + } + }, + "Notes": { + "page": { + "loading": "Caricamento..." + }, + "layout": { + "mobileNavTitle": "Navigazione delle note", + "openMenuAria": "Apri il menu delle note" + }, + "sidebar": { + "title": "Note", + "createNoteAria": "Crea nota", + "searchPlaceholder": "Cerca note...", + "loading": "Caricamento...", + "noNotesFound": "Nessuna nota trovata.", + "noNotesYet": "Nessuna nota ancora. Fare clic su + per crearne uno.", + "inParent": "tra {parentTitle}", + "untitled": "Senza titolo", + "addSubPage": "Aggiungi sottopagina", + "delete": "Eliminare", + "deleteConfirmTitle": "Eliminare la nota?", + "deleteConfirmDescription": "Sei sicuro di voler eliminare \"{title}\"? Questa azione non può essere annullata.", + "cancel": "Cancellare", + "confirmDelete": "Eliminare", + "noteOptionsAria": "Opzioni della nota" + }, + "editor": { + "emptyState": "Seleziona una nota o creane una nuova per iniziare.", + "titlePlaceholder": "Senza titolo", + "saving": "Salvataggio…", + "ready": "Pronto", + "saved": "Salvato {time}", + "stats": "{words} parole · {read} min di lettura", + "template": "Modello", + "applyTemplate": "Applica modello", + "exportMdLabel": ".md", + "exportMd": "Esporta come Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Esporta come HTML", + "focusMode": "Modalità concentrazione", + "exitFocusMode": "Esci dalla modalità concentrazione", + "save": "Salva", + "addTag": "Aggiungi tag…", + "removeTag": "Rimuovi tag {tag}", + "loading": "Caricamento…", + "searchHint": "per cercare", + "focusHint": "modalità concentrazione", + "notePath": "Percorso della nota" + }, + "context": { + "authRequiredError": "Utente non autenticato", + "defaultTitle": "Senza titolo", + "cloudSyncImageError": "Attiva la sincronizzazione cloud per salvare le immagini delle note. Le note di testo restano locali." + } + }, + "PasswordManager": { + "page": { + "loading": "Caricamento...", + "title": "Gestore delle password", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Impossibile decrittografare le password", + "toastLoadFailed": "Impossibile caricare le password", + "toastUnlockedAuto": "Vault sbloccato automaticamente", + "toastAutoUnlockFailed": "Sblocco automatico non riuscito. Si prega di sbloccare manualmente.", + "toastConnectFailed": "Impossibile connettersi al server", + "toastAutoUnlockWarning": "Vault sbloccato, ma lo sblocco automatico potrebbe non funzionare dopo la ricarica", + "errorIncorrectPassword": "Password principale errata", + "errorUnlockFailed": "Impossibile sbloccare il vault", + "errorPasswordsMismatch": "Le password non corrispondono", + "errorPasswordTooShort": "La password deve contenere almeno 8 caratteri", + "errorSetupFailed": "Impossibile impostare il vault", + "errorConnectFailed": "Impossibile connettersi al server.", + "mobileTitleCreate": "Crea la tua cassaforte", + "mobileTitleWelcome": "Bentornato", + "mobileSubtitleCreate": "Imposta una password principale per proteggere le tue credenziali", + "mobileSubtitleUnlock": "Inserisci la tua password principale per sbloccare", + "placeholderMasterPassword": "Parola d'ordine principale", + "placeholderConfirmPassword": "Conferma password", + "labelMasterPassword": "Parola d'ordine principale", + "labelConfirmPassword": "Conferma password", + "placeholderEnterMaster": "Inserisci la password principale", + "placeholderConfirmMaster": "Conferma la password principale", + "buttonCreateVault": "Crea cassaforte", + "buttonUnlock": "Sbloccare", + "buttonUnlockVault": "Sblocca il caveau", + "footerCreate": "La tua password non viene mai memorizzata e non può essere recuperata", + "footerUnlock": "Protetto con crittografia end-to-end", + "desktopTitleSetup": "Configura l'archivio password", + "desktopTitleUnlock": "Sblocca il caveau", + "desktopDescriptionSetup": "Crea una password principale per proteggere le tue credenziali. Questa password non viene mai memorizzata e non può essere recuperata.", + "desktopDescriptionUnlock": "Inserisci la tua password principale per accedere alle tue credenziali protette." + }, + "list": { + "loading": "Caricamento delle tue password...", + "emptyTitle": "Il caveau è vuoto", + "emptyHintMobile": "Tocca il pulsante + per aggiungere la tua prima password", + "emptyHintDesktop": "Non hai ancora memorizzato alcuna password. Fai clic sul pulsante \"Aggiungi password\" qui sopra per iniziare.", + "addPassword": "Aggiungi password", + "searchPlaceholderMobile": "Cerca nel tuo caveau...", + "searchPlaceholderDesktop": "Cerca nel caveau...", + "importExport": "Importa/esporta", + "lockVault": "Bloccare il caveau", + "allPasswordsTitle": "Tutte le password", + "passwordCount": "{contare, plurale, uno {# password} altro {# passwords}}", + "vaultHealthDrawerTitle": "Salute del caveau", + "noResults": "Nessuna password trovata corrispondente a \"{query}\"", + "tableService": "Servizio", + "tableUsername": "Nome utente", + "tablePassword": "Password", + "tableUrl": "URL", + "tableActions": "Azioni", + "gridViewAria": "Visualizzazione griglia", + "listViewAria": "Visualizzazione elenco", + "passwordStrengthTitle": "Forza della password", + "deleteDialogTitle": "Elimina la password", + "deleteDialogDescription": "Sei sicuro di voler eliminare questa password? Questa azione non può essere annullata.", + "cancel": "Cancellare", + "delete": "Eliminare", + "edit": "Modificare" + }, + "toasts": { + "copiedPassword": "Password copiata negli appunti", + "copiedUsername": "Nome utente copiato negli appunti", + "passwordDeleted": "Password cancellata", + "deleteFailed": "Impossibile eliminare la password", + "vaultLocked": "Deposito chiuso a chiave", + "importedCount": "Importazione riuscita di {count, plural, one {# password} other {# passwords}}" + }, + "form": { + "addTitle": "Aggiungi nuova password", + "editTitle": "Modifica password", + "serviceName": "Nome del servizio", + "usernameEmail": "Nome utente/e-mail", + "password": "Password", + "urlOptional": "URL del sito web (facoltativo)", + "notesOptional": "Note (facoltativo)", + "tags": "Tag", + "placeholderService": "per esempio. Netflix", + "placeholderUsername": "utente@esempio.com", + "placeholderPassword": "Inserisci o genera una password", + "placeholderUrl": "https://...", + "placeholderNotes": "Ulteriori dettagli...", + "placeholderTags": "Aggiungi tag (premi Invio o virgola)", + "strength": "Forza", + "strengthWeak": "Debole", + "strengthMedium": "Medio", + "strengthStrong": "Forte", + "advancedGenerator": "Generatore avanzato", + "cancel": "Cancellare", + "saving": "Risparmio...", + "savePassword": "Salva password", + "updating": "Aggiornamento...", + "updatePassword": "Aggiorna password", + "addPasswordSrOnly": "Aggiungi password", + "triggerAdd": "Aggiungi password", + "toastSaved": "Password salvata con successo", + "toastSaveFailed": "Impossibile salvare la password", + "toastUpdated": "Password aggiornata con successo", + "toastUpdateFailed": "Impossibile aggiornare la password" + }, + "security": { + "vaultHealth": "Salute del caveau", + "statusLabel": "Stato", + "totalPasswords": "Password totali", + "activeEntries": "Voci attive nel tuo caveau", + "protected": "Protetto", + "weakPasswords": "Password deboli", + "needingImprovement": "Password da migliorare", + "allGood": "Tutto bene", + "reusedPasswords": "Password riutilizzate", + "usedMultipleTimes": "Password utilizzate più volte", + "unique": "Unico", + "total": "Totale", + "weak": "Debole", + "reused": "Riutilizzato", + "totalCountLabel": "{contare, plurale, uno {# password} altro {# passwords}}", + "weakCountLabel": "{count} debole", + "allSecure": "Tutto sicuro", + "scoreExcellent": "Eccellente", + "scoreGood": "Bene", + "scoreFair": "Giusto", + "scoreCritical": "Critico" + }, + "importExport": { + "title": "Importa/esporta caveau", + "description": "Gestisci i dati della tua password. Puoi esportare il tuo vault in JSON o importarlo da un file JSON.", + "tabExport": "Esportare", + "tabImport": "Importare", + "warningTitle": "Avvertimento", + "warningBody": "I file esportati contengono le tue password in testo semplice. Conserva questo file al sicuro ed eliminalo dopo l'uso.", + "exportBlurb": "Scarica l'intero vault come file JSON. Ciò include tutti i servizi, nomi utente, password e note.", + "downloadJson": "Scarica l'esportazione JSON", + "pasteLabel": "Incolla i dati JSON", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Incolla qui il contenuto di un file JSON precedentemente esportato.", + "importing": "Importazione...", + "importPasswords": "Importa password", + "trigger": "Importa/esporta", + "toastExported": "Vault esportato correttamente", + "toastExportFailed": "Impossibile esportare il vault", + "toastImportFailed": "Impossibile importare: formato JSON non valido" + }, + "card": { + "copyUsername": "Copia nome utente", + "openUrl": "Apri l'URL", + "strength": "Forza" + }, + "swipeable": { + "copy": "Copia", + "delete": "Eliminare", + "copyPassword": "Copia la password", + "copyUsername": "Copia nome utente", + "edit": "Modificare", + "deleteAction": "Eliminare" + }, + "floatingMenu": { + "lockVault": "Bloccare il caveau", + "importExport": "Importa/esporta", + "addPassword": "Aggiungi password" + }, + "generator": { + "tabPassword": "Password", + "tabPassphrase": "Passphrase", + "strength": "Forza", + "lengthLabel": "Lunghezza: {length} caratteri", + "lengthBadge": "{length} caratteri", + "characterSets": "Set di caratteri", + "uppercase": "Maiuscolo", + "lowercase": "Minuscolo", + "numbers": "Numeri", + "symbols": "Simboli", + "wordCountLabel": "Conteggio parole: {count}", + "wordCountBadge": "{contare, plurale, uno {# word} altro {# words}}", + "separator": "Separatore", + "capitalization": "Capitalizzazione", + "includeNumber": "Includi numero" + } + }, + "EnvironmentManager": { + "subtitle": "Variabili d'ambiente crittografate per progetto e ambiente.", + "page": { + "loading": "Caricamento…", + "title": "Gestore ambienti", + "loadFailed": "Impossibile caricare i set di ambiente" + }, + "list": { + "title": "Ambienti", + "loading": "Caricamento dei set…", + "emptyTitle": "Nessun set di ambiente ancora", + "emptyHint": "Crea un set per ogni progetto e ambiente (es. chiavi API di produzione). Tutto viene crittografato nel browser prima del caricamento.", + "searchPlaceholder": "Cerca progetto, ambiente, tag, note…", + "tagFilterPlaceholder": "Filtra per tag", + "variableCount": "{count, plural, one {# variabile} other {# variabili}}", + "updatedAgo": "Aggiornato {time}", + "copyDotEnv": "Copia .env", + "edit": "Modifica", + "delete": "Elimina", + "cancel": "Annulla", + "confirmDelete": "Elimina", + "deleteDialogTitle": "Eliminare il set di ambiente?", + "deleteDialogDescription": "Rimuove il blob crittografato dal tuo account. Operazione irreversibile.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env copiato negli appunti", + "deleted": "Set di ambiente eliminato", + "deleteFailed": "Eliminazione non riuscita" + }, + "form": { + "addTitle": "Nuovo set di ambiente", + "editTitle": "Modifica set di ambiente", + "triggerAdd": "Aggiungi set di ambiente", + "addSrOnly": "Aggiungi set di ambiente", + "project": "Progetto", + "environment": "Ambiente", + "placeholderProject": "es. billing-api", + "placeholderEnvironment": "es. production", + "pasteEnvLabel": "Incolla .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Unisci righe incollate", + "variables": "Variabili", + "addRow": "Aggiungi riga", + "keyPlaceholder": "CHIAVE", + "valuePlaceholder": "valore", + "notesOptional": "Note (opzionale)", + "placeholderNotes": "Policy di rotazione, owner, link…", + "tags": "Tag", + "placeholderTags": "Aggiungi tag (Invio o virgola)", + "cancel": "Annulla", + "save": "Salva", + "saving": "Salvataggio…", + "update": "Aggiorna", + "updating": "Aggiornamento…", + "toastSaved": "Set di ambiente salvato", + "toastSaveFailed": "Salvataggio non riuscito", + "toastUpdated": "Set di ambiente aggiornato", + "toastUpdateFailed": "Aggiornamento non riuscito", + "toastPasteEmpty": "Nessuna riga CHIAVE=valore trovata", + "toastPasteApplied": "Unite {count} variabili dall'incolla", + "toastProjectEnvRequired": "Progetto e ambiente sono obbligatori", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Caricamento..." + }, + "manager": { + "foldersHeading": "Cartelle", + "tagsHeading": "Tag", + "tagsMore": "+{count} altro", + "tagsShowLess": "Show less", + "searchPlaceholder": "Cerca nei tuoi segnalibri...", + "import": "Importare", + "export": "Esportare", + "exportAsHtml": "Esporta come HTML", + "exportAsJson": "Esporta come JSON", + "addBookmark": "Aggiungi segnalibro", + "matchingQuery": "corrispondente a \"{query}\"", + "bookmarkCount": "{contare, plurale, uno {# bookmark} altro {# bookmarks}}", + "allBookmarks": "Tutti i segnalibri", + "uncategorized": "Senza categoria", + "unknownFolder": "Sconosciuto" + }, + "card": { + "urlCopied": "URL copiato negli appunti", + "bookmarkDeleted": "Segnalibro eliminato", + "edit": "Modificare", + "delete": "Eliminare", + "copyUrl": "Copia l'URL", + "deleteConfirmTitle": "Eliminare il segnalibro?", + "deleteConfirmDescription": "Questa operazione eliminerà definitivamente \"{title}\".", + "cancel": "Cancellare" + }, + "grid": { + "emptyTitle": "Nessun segnalibro ancora", + "emptyDescription": "Aggiungi il tuo primo segnalibro o importalo dal tuo browser per iniziare." + }, + "folderTree": { + "allBookmarks": "Tutti i segnalibri", + "addSubfolder": "Aggiungi sottocartella", + "rename": "Rinominare", + "deleteFolderTitle": "Eliminare la cartella \"{name}\"?", + "deleteFolderDescription": "Ciò eliminerà la cartella e tutte le sue sottocartelle. I segnalibri in queste cartelle verranno spostati in Senza categoria." + }, + "import": { + "title": "Importa segnalibri", + "description": "Importa i segnalibri dal file esportato del tuo browser.", + "parseError": "Impossibile analizzare il file dei segnalibri. Assicurati che si tratti di un'esportazione di segnalibri valida.", + "successToast": "Preferiti {bookmarks} e cartelle {folders} importati", + "errorToast": "Impossibile importare i segnalibri", + "chromeInstruction": "Menu → Segnalibri → Gestione segnalibri → ⋮ → Esporta segnalibri", + "firefoxInstruction": "Menu → Segnalibri → Gestisci segnalibri → Importa e backup → Esporta", + "safariInstruction": "File → Esporta segnalibri...", + "edgeInstruction": "Menu → Preferiti → ⋯ → Esporta preferiti", + "dropZoneTitle": "Trascina qui il file dei tuoi segnalibri", + "dropZoneHint": "oppure fai clic per navigare (HTML o JSON)", + "selectedFileHint": "Fare clic per selezionare un file diverso", + "errorTitle": "Errore", + "previewTitle": "Importa anteprima", + "previewBookmarks": "{contare, plurale, uno {# bookmark} altro {# bookmarks}}", + "previewFolders": "{contare, plurale, uno {# folder} altro {# folders}}", + "importModeLabel": "Modalità di importazione", + "modeMerge": "Unisci con i segnalibri esistenti", + "modeReplace": "Sostituisci tutti i segnalibri esistenti", + "cancel": "Cancellare", + "importButton": "Importare", + "importButtonWithCount": "Importa {count, plural, one {# bookmark} other {# bookmarks}}" + }, + "addBookmark": { + "titleAdd": "Aggiungi segnalibro", + "titleEdit": "Modifica segnalibro", + "urlLabel": "URL", + "urlPlaceholder": "https://esempio.com", + "urlInvalid": "Inserisci un URL valido", + "titleLabel": "Titolo", + "titlePlaceholder": "Titolo del segnalibro", + "titleRequired": "Inserisci un titolo", + "descriptionLabel": "Descrizione (facoltativa)", + "descriptionPlaceholder": "Aggiungi una nota su questo segnalibro", + "folderLabel": "Cartella", + "folderPlaceholder": "Seleziona la cartella", + "noFolder": "Nessuna cartella", + "tagsLabel": "Tag", + "tagsPlaceholder": "Aggiungi tag (premi Invio)", + "toastUpdated": "Segnalibro aggiornato", + "toastAdded": "Segnalibro aggiunto", + "cancel": "Cancellare", + "submitAdd": "Aggiungi segnalibro", + "submitUpdate": "Aggiorna segnalibro" + }, + "addFolder": { + "title": "Crea cartella", + "nameLabel": "Nome della cartella", + "namePlaceholder": "La mia cartella", + "nameRequired": "Inserisci il nome di una cartella", + "parentLabel": "Cartella principale", + "parentPlaceholder": "Seleziona la cartella principale", + "rootParent": "Radice (nessun genitore)", + "toastCreated": "Cartella creata", + "cancel": "Cancellare", + "submit": "Crea cartella" + }, + "editFolder": { + "title": "Rinomina cartella", + "nameLabel": "Nome della cartella", + "namePlaceholder": "Nome della cartella", + "nameRequired": "Inserisci il nome di una cartella", + "toastRenamed": "Cartella rinominata", + "cancel": "Cancellare", + "save": "Salva" + } + }, + "EmailValidator": { + "header": { + "title": "Validatore di posta elettronica", + "description": "Verifica e convalida gli indirizzi e-mail per sintassi, dominio e consegnabilità." + }, + "tabs": { + "single": "Verifica unica", + "bulk": "Caricamento collettivo" + }, + "single": { + "cardTitle": "Verifica e-mail singola", + "cardDescription": "Inserisci un indirizzo email per verificarne la consegnabilità e la reputazione.", + "placeholder": "Inserisci l'indirizzo e-mail...", + "clearAria": "Cancella e-mail", + "validate": "Convalidare", + "validating": "Convalida...", + "formatError": "Si prega di inserire un indirizzo email valido" + }, + "toasts": { + "errorTitle": "Errore", + "enterEmail": "Inserisci un indirizzo email", + "invalidFormatTitle": "Formato e-mail non valido", + "invalidFormatDescription": "Si prega di inserire un indirizzo email valido", + "validateFailedDescription": "Si è verificato un errore durante la convalida dell'e-mail", + "noEmailsTitle": "Nessuna email trovata", + "noEmailsDescription": "Assicurati che il tuo Excel abbia una colonna \"e-mail\".", + "fileTooLargeTitle": "File troppo grande", + "fileTooLargeDescription": "Trovate {total} email. Verranno convalidati solo i primi 4000.", + "successTitle": "Successo", + "successValidated": "Email {count} convalidate correttamente.", + "batchFailedTitle": "Convalida batch non riuscita", + "batchFailedDescription": "Si è verificato un errore durante l'elaborazione della convalida in blocco.", + "noExportTitle": "Nessun dato da esportare", + "noExportValidOnly": "Nessuna email valida trovata da esportare.", + "noExportAll": "Nessun risultato da esportare.", + "exportSuccessTitle": "Esportazione riuscita", + "exportSuccessExcelAll": "E-mail {count} esportate in Excel.", + "exportSuccessExcelValid": "E-mail valide {count} esportate in Excel.", + "exportSuccessCsvAll": "Email {count} esportate in CSV.", + "exportSuccessCsvValid": "Esportate {count} email valide in CSV." + }, + "disposableAlert": { + "title": "Rilevata e-mail usa e getta", + "description": "Questo indirizzo email appartiene a un provider di posta elettronica usa e getta. Potrebbe essere temporaneo o pericoloso." + }, + "score": { + "reliabilityTitle": "Punteggio di affidabilità", + "scoreLabel": "Punto" + }, + "details": { + "sectionTitle": "Dettagli di convalida" + }, + "validation": { + "syntaxLabel": "Sintassi valida", + "syntaxTooltip": "Controlla se l'indirizzo email segue il formato corretto (ad esempio, utente@dominio.com)", + "domainLabel": "Il dominio esiste", + "domainTooltip": "Verifica che il nome di dominio nell'indirizzo email esista effettivamente", + "mxLabel": "Record MX trovati", + "mxTooltip": "Controlla se il dominio dispone di record Mail Exchange (MX) configurati per ricevere e-mail", + "mailboxLabel": "La casella di posta esiste", + "mailboxTooltip": "Verifica che la casella di posta/account specifica esista sul server di posta", + "disposableLabel": "E-mail usa e getta", + "disposableTooltip": "Indica se questa email appartiene a un servizio di posta elettronica temporaneo/usa e getta (potrebbe essere rischioso)", + "roleBasedLabel": "E-mail basata sui ruoli", + "roleBasedTooltip": "Identifica se si tratta di un'e-mail basata sul ruolo (ad esempio, supporto@, info@) anziché di un indirizzo personale" + }, + "copy": { + "emailCopied": "E-mail copiata!", + "copyTooltip": "Copia e-mail" + }, + "bulk": { + "dropHere": "Rilascia il tuo file qui", + "dragHere": "Trascina e rilascia il tuo file qui", + "uploadHint": "Carica un file .xlsx, .xls o .csv contenente una colonna \"email\" per convalidare fino a 4000 email.", + "filePrefix": "File:", + "selectFile": "Seleziona File", + "downloadTemplate": "Scarica modello" + }, + "bulkLoading": { + "title": "Convalida e-mail...", + "batchProgress": "Lotto di elaborazione {current} di {total}..." + }, + "stats": { + "totalProcessed": "Totale elaborato", + "valid": "Valido", + "risky": "Rischioso", + "invalid": "Non valido" + }, + "results": { + "detailedTitle": "Risultati dettagliati", + "searchPlaceholder": "Cerca email...", + "newVerification": "Nuova verifica", + "exportAll": "Esporta tutto", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Esportazione valida ({count})" + }, + "showingResults": "Visualizzazione di {shown} di {total} risultati", + "table": { + "email": "E-mail", + "status": "Stato", + "score": "Punto", + "checks": "Controlli" + }, + "noSearchResults": "Nessun risultato trovato corrispondente a \"{query}\"", + "checksTooltip": { + "syntax": "Sintassi", + "domain": "Dominio", + "mx": "MX", + "mailbox": "Cassetta postale" + }, + "exportColumns": { + "email": "E-mail", + "status": "Stato", + "score": "Punto", + "syntax": "Sintassi", + "domain": "Dominio", + "mx": "MX", + "mailbox": "Cassetta postale", + "disposable": "Monouso", + "roleBased": "Basato sui ruoli", + "valid": "Valido", + "invalid": "Non valido", + "yes": "SÌ", + "no": "NO" + }, + "sheets": { + "validEmails": "Email valide", + "results": "Risultati", + "template": "Modello" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "Esploratore NoSQL", + "heroSubtitle": "Connettiti ai tuoi database per gestire raccolte, eseguire query e visualizzare i tuoi dati.", + "featureMultiTitle": "Connessione multipla", + "featureMultiDesc": "Gestisci più connessioni al database contemporaneamente.", + "featureMongoTitle": "Supporto MongoDB", + "featureMongoDesc": "Supporto nativo per MongoDB con filtraggio avanzato.", + "featureQueryTitle": "Domanda intelligente", + "featureQueryDesc": "Potente generatore di query con evidenziazione della sintassi.", + "connectCta": "Connettiti a MongoDB", + "footerNote": "Attualmente supporta {db}. Altri database in arrivo.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Seleziona una raccolta", + "selectCollectionDesc": "Scegli una raccolta dalla barra laterale per visualizzare i documenti e iniziare a esplorare i tuoi dati.", + "dialogAddConnection": "Aggiungi connessione", + "toastConnectionAdded": "Connessione aggiunta", + "toastFetchFailed": "Impossibile recuperare i documenti per {collection}", + "toastDocDeleted": "Documento eliminato", + "deleteDocTitle": "Ne sei assolutamente sicuro?", + "deleteDocDescription": "Questa azione non può essere annullata. Ciò eliminerà definitivamente il documento dalla tua raccolta.", + "closeAllTitle": "Chiudere tutte le schede?", + "closeAllDescription": "Sei sicuro di voler chiudere tutte le schede? Eventuali modifiche non salvate nei filtri di query andranno perse.", + "closeAllConfirm": "Chiudi tutto", + "cancel": "Cancellare", + "delete": "Eliminare" + }, + "connection": { + "titleEdit": "Modifica connessione", + "titleConnect": "Connettiti a MongoDB", + "subtitleEdit": "Aggiorna i dettagli della tua connessione di seguito.", + "subtitleConnect": "Inserisci la stringa di connessione MongoDB per iniziare a esplorare i tuoi dati.", + "labelName": "Nome", + "placeholderName": "per esempio. DB di produzione", + "labelConnectionString": "Stringa di connessione", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Formato: mongodb://nomeutente:password@host:porta/database", + "testConnection": "Prova connessione", + "connecting": "Collegamento...", + "updateConnect": "Aggiorna e connetti", + "connect": "Collegare", + "cancelEdit": "Annulla modifica", + "savedConnections": "Connessioni salvate", + "emptySaved": "Nessuna connessione ancora salvata.", + "lastUsed": "Ultimo utilizzo {time}", + "toastTestOk": "Connessione riuscita!", + "toastTestFail": "Connessione non riuscita: {message}", + "toastUpdated": "Connessione aggiornata", + "confirmDelete": "Sei sicuro di voler eliminare questa connessione salvata?", + "toastDeletedConn": "Connessione eliminata", + "toastDeleteFail": "Impossibile eliminare la connessione", + "copyStringTitle": "Copia stringa di connessione", + "toastStringCopied": "Stringa di connessione copiata!", + "editTitle": "Modificare", + "deleteTitle": "Eliminare" + }, + "sidebar": { + "explorer": "Esploratore", + "add": "Aggiungere", + "tooltipAdd": "Aggiungi nuova connessione", + "tooltipRefresh": "Aggiorna le connessioni", + "searchPlaceholder": "Ricerca...", + "loadingConnections": "Caricamento connessioni...", + "noConnectionsFound": "Nessuna connessione trovata", + "toastLoadFail": "Impossibile caricare le connessioni", + "toastConnectFail": "Impossibile connettersi a {name}", + "toastCollectionsFail": "Impossibile recuperare le raccolte per {name}", + "toastRenamed": "Connessione rinominata", + "toastRenameFail": "Impossibile rinominare la connessione", + "confirmDeleteConnection": "Sei sicuro di voler eliminare la connessione \"{name}\"?", + "toastDeleted": "Connessione eliminata", + "toastDeleteConnFail": "Impossibile eliminare la connessione", + "confirmDropDb": "Eliminare il database \"{name}\"? Questa azione non può essere annullata.", + "toastDbDropped": "Database {name} eliminato", + "confirmDropCollection": "Vuoi eliminare la raccolta \"{name}\"? Questa azione non può essere annullata.", + "toastCollectionDropped": "Raccolta {name} abbandonata", + "toastCollectionRenamed": "Raccolta rinominata {name}", + "toastDatabaseRenamed": "Database rinominato in {name}", + "connecting": "Collegamento...", + "connectionFailed": "Connessione non riuscita", + "noDatabases": "Nessun database", + "loading": "Caricamento...", + "noCollections": "Nessuna raccolta", + "renameCollectionTitle": "Rinomina raccolta", + "renameDatabaseTitle": "Rinominare la banca dati", + "placeholderNewCollection": "Nuovo nome della collezione", + "placeholderNewDatabase": "Nuovo nome del database", + "rename": "Rinominare", + "refresh": "Aggiorna", + "copyDbName": "Copia nome database", + "toastDbNameCopied": "Nome del database copiato negli appunti", + "dropDatabase": "Elimina database", + "dropCollection": "Raccolta gocce", + "menuRename": "Rinominare", + "menuCopyConnectionString": "Copia stringa di connessione", + "menuDeleteConnection": "Elimina connessione", + "toastSidebarStringCopied": "Stringa di connessione copiata!", + "cancel": "Cancellare", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} documenti", + "table": "Tavolo", + "json": "JSON", + "tree": "Albero", + "tooltipTable": "Visualizzazione tabella", + "tooltipJson": "Vista JSON", + "tooltipTree": "Visualizzazione ad albero", + "itemsPerPage": "Articoli per pagina", + "perPage": "{n}/pagina", + "prevPage": "Pagina precedente", + "nextPage": "Pagina successiva", + "expandAll": "Espandi tutto", + "collapseAll": "Comprimi tutto", + "refreshData": "Aggiorna dati", + "insert": "Inserire", + "export": "Esportare", + "actions": "Azioni", + "invalidJson": "Contenuto JSON non valido", + "copiedClipboard": "Copiato negli appunti", + "docUpdated": "Documento aggiornato con successo", + "docInserted": "Documento inserito correttamente", + "invalidJsonShort": "JSON non valido", + "docCopied": "Documento copiato negli appunti", + "openFullEditor": "Apri nell'editor completo", + "cellArray": "Matrice({length})", + "cellObject": "Oggetto", + "cellArrayBracket": "Matrice[{length}]", + "emptyFilterTitle": "Nessun documento corrisponde alla tua query", + "emptyFilterHint": "Prova a modificare il filtro o a cancellare la query.", + "clearFilter": "Cancella filtro", + "emptyCollectionTitle": "Questa raccolta è vuota", + "emptyCollectionHint": "Inserisci un documento per iniziare.", + "insertDocument": "Inserisci documento", + "formatJson": "Formato JSON", + "compactJson": "JSON compatto", + "copyClipboard": "Copia negli appunti", + "copyJson": "Copia JSON", + "duplicate": "Duplicato", + "editDoc": "Modifica documento", + "deleteDoc": "Elimina documento", + "documentLabel": "Documento {n}", + "editDialogTitle": "Modifica documento", + "insertDialogTitle": "Inserisci documento", + "viewValueTitle": "Visualizza valore", + "saveChanges": "Salva modifiche", + "close": "Vicino", + "cancel": "Cancellare", + "insertButton": "Inserire", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Filtro attivo applicato", + "filterActiveTooltip": "Filtro attivo", + "closeAll": "Chiudi tutto" + }, + "query": { + "migrateHistory": "Cronologia delle query migrata nel cloud", + "saveHistoryFail": "Impossibile salvare la cronologia delle query", + "deleteQueryFail": "Impossibile eliminare la query", + "invalidJsonQuery": "Query JSON non valida", + "placeholder": "per esempio. { \"status\": \"active\" }", + "clearQuery": "Cancella domanda", + "openAdvanced": "Apri Editor avanzato", + "visualBuilder": "Generatore di query visive", + "filterRules": "Regole di filtro", + "addRule": "Aggiungi regola", + "noFilters": "Nessun filtro applicato. Aggiungi una regola per filtrare i documenti.", + "where": "DOVE", + "and": "E", + "fieldPlaceholder": "Campo", + "opPlaceholder": "Op", + "typePlaceholder": "Tipo", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Non dentro", + "opExists": "Esiste", + "typeAuto": "Auto", + "typeString": "Corda", + "typeNumber": "Numero", + "typeBoolean": "Booleano", + "typeDate": "Data", + "typeObjectId": "OggettoId", + "true": "VERO", + "false": "Falso", + "valuePlaceholder": "Valore", + "valueListPlaceholder": "valore1, valore2", + "clearFilters": "Cancella filtri", + "applyFilters": "Applica filtri", + "queryHistory": "Cronologia delle query", + "noSavedQueries": "Nessuna query ancora salvata", + "advancedEditorTitle": "Editor di query avanzato", + "templatesPlaceholder": "Modelli", + "templateEmptyAgg": "Aggregazione vuota", + "templateMatch": "$corrispondenza (filtro)", + "templateLookup": "$ ricerca (Iscriviti)", + "templateGroup": "$gruppo (Conteggio/Somma)", + "format": "Formato", + "formattedJson": "JSON formattato", + "runQuery": "Esegui interrogazione" + }, + "export": { + "selectFieldsError": "Seleziona almeno un campo da esportare", + "success": "Esportazione riuscita in {filename}", + "fail": "Impossibile esportare i dati", + "title": "Esporta dati", + "formatLabel": "Formato di esportazione", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Seleziona Campi", + "selectAll": "Seleziona tutto", + "fieldsSelected": "{contare, plurale, uno {# field selected} altro {# fields selected}}", + "cancel": "Cancellare", + "export": "Esportare", + "sheetName": "Dati" + }, + "jsonTree": { + "typeLabel": "Tipo: {type}", + "itemSummary": "{contare, plurale, uno {# item} altro {# items}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Nuova richiesta", + "importedRequest": "Richiesta importata", + "untitledRequest": "Richiesta senza titolo" + }, + "toasts": { + "curlImported": "cURL importato correttamente", + "curlParseFailed": "Impossibile analizzare il comando cURL", + "invalidJsonBody": "Corpo JSON non valido", + "requestFailed": "Richiesta non riuscita: {message}", + "curlPasted": "cURL incollato e analizzato correttamente", + "responseCopied": "Risposta copiata negli appunti", + "codeCopied": "Codice copiato negli appunti", + "copyFailed": "Copia negli appunti non riuscita" + }, + "layout": { + "collections": "Collezioni", + "closeSidebar": "Chiudi la barra laterale", + "openSidebar": "Apri la barra laterale", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Metodo", + "urlPlaceholder": "https://api.esempio.com/v1/...", + "sending": "Invio...", + "send": "Inviare", + "invalidJsonBodyHelp": "Impossibile inviare: il corpo JSON non è valido" + }, + "requestTabs": { + "params": "Param", + "headers": "Intestazioni", + "body": "Corpo", + "auth": "Aut", + "bodyType": "Tipo di corporatura", + "selectType": "Seleziona il tipo", + "bodyNone": "Nessuno", + "bodyJson": "JSON", + "bodyText": "Testo", + "noBodyHint": "Nessun contenuto del corpo per questa richiesta", + "switchToJson": "Passa al corpo JSON", + "authType": "Tipo di autenticazione", + "noAuth": "Nessuna autenticazione", + "bearerToken": "Gettone al portatore", + "basicAuth": "Aut. di base", + "apiKey": "Chiave API", + "labelToken": "Gettone", + "placeholderBearerToken": "Gettone al portatore", + "labelUsername": "Nome utente", + "placeholderUsername": "Nome utente", + "labelPassword": "Password", + "placeholderPassword": "Password", + "labelKey": "Chiave", + "placeholderApiKeyName": "Nome della chiave (ad esempio X-Api-Key)", + "labelValue": "Valore", + "placeholderValue": "Valore", + "addTo": "aggiungere a", + "header": "Intestazione", + "queryParams": "Parametri query", + "noAuthHint": "Questa richiesta non utilizza l'autenticazione." + }, + "responsePanel": { + "emptyTitle": "Pronto per esplorare?", + "emptyHint": "Inserisci un URL e fai clic su Invia per visualizzare i dati della risposta qui.", + "responsePreviewAlt": "Anteprima della risposta", + "previewUnavailable": "Anteprima non disponibile per questo tipo di contenuto", + "status": "Stato", + "time": "Tempo", + "size": "Misurare", + "copyBody": "Copia corpo", + "downloadResponse": "Scarica la risposta", + "bodyTab": "Corpo", + "previewTab": "Anteprima", + "headersTab": "Intestazioni", + "binaryRawView": "I dati binari non possono essere visualizzati nella vista grezza.", + "errorStatusLabel": "Errore", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Nuova scheda" + }, + "collectionsSidebar": { + "title": "Risorse API", + "newCollection": "Nuova collezione", + "tabCollections": "Collezioni", + "tabHistory": "Storia", + "noCollectionsTitle": "Nessuna raccolta", + "noCollectionsHint": "Crea una raccolta per organizzare le tue richieste API.", + "createCollection": "Crea raccolta", + "newFolder": "Nuova cartella", + "rename": "Rinominare", + "delete": "Eliminare", + "emptyCollection": "Raccolta vuota", + "recentRequests": "Richieste recenti", + "clearAll": "Cancella tutto", + "noHistoryTitle": "Nessuna storia ancora", + "noHistoryHint": "Invia una richiesta per vederlo qui.", + "dialogNewFolderTitle": "Nuova cartella", + "dialogNewFolderDescription": "Crea una nuova cartella per organizzare le tue richieste.", + "labelFolderName": "Nome della cartella", + "placeholderFolderName": "La mia cartella", + "createFolder": "Crea cartella", + "dialogNewCollectionTitle": "Nuova collezione", + "dialogNewCollectionDescription": "Crea una nuova raccolta di primo livello.", + "labelCollectionName": "Nome della raccolta", + "placeholderCollectionName": "La mia collezione", + "dialogRenameCollectionTitle": "Rinominare la raccolta", + "dialogRenameCollectionDescription": "Inserisci un nuovo nome per la raccolta.", + "placeholderRenameCollection": "Nome della raccolta", + "renameAction": "Rinominare", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Nuova cartella", + "delete": "Eliminare", + "emptyFolder": "Cartella vuota" + }, + "environmentManager": { + "noEnvironment": "Nessun ambiente", + "manageDialogTitle": "Gestire gli ambienti", + "placeholderNewEnv": "Nuovo ambiente", + "variablesHeading": "{name} variabili", + "addVariable": "Aggiungi variabile", + "columnVariable": "Variabile", + "columnValue": "Valore", + "placeholderKey": "Chiave", + "placeholderValue": "Valore", + "selectEnvToEdit": "Seleziona un ambiente da modificare" + }, + "codeGenerator": { + "triggerTitle": "Genera codice", + "dialogTitle": "Genera codice", + "languageCurl": "arricciare", + "languageJavaScript": "JavaScript (recupera)", + "languagePython": "Python (richieste)", + "languageGo": "Andare", + "copy": "Copia", + "errorGenerating": "Errore durante la generazione del codice" + }, + "keyValueEditor": { + "columnKey": "Chiave", + "columnValue": "Valore", + "placeholderKey": "Chiave", + "placeholderValue": "Valore", + "addItem": "Aggiungi elemento" + }, + "importCurl": { + "trigger": "Importa arricciatura", + "dialogTitle": "Importa arricciatura", + "dialogDescription": "Incolla il comando cURL di seguito per importarlo come nuova richiesta.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Importare" + }, + "saveRequest": { + "trigger": "Salva", + "dialogTitle": "Salva richiesta", + "dialogDescription": "Salva questa richiesta in una cartella di raccolta.", + "labelName": "Richiedi nome", + "placeholderName": "La mia richiesta", + "labelFolder": "Cartella", + "placeholderFolder": "Seleziona una cartella", + "save": "Salva" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Modalità Modifica", + "viewOnlyMode": "Modalità di sola visualizzazione", + "insertComponent": "Inserisci componente", + "addUnorderedList": "Aggiungi elenco non ordinato", + "addOrderedList": "Aggiungi elenco ordinato", + "addTable": "Aggiungi tabella" + }, + "addBlock": "Aggiungi blocco", + "media": { + "popoverTitle": "Aggiungi contenuti multimediali", + "singleImage": "Immagine singola", + "singleImageDesc": "Carica un'immagine", + "multipleImages": "Immagini multiple", + "multipleImagesDesc": "Carica la griglia delle immagini", + "video": "Video", + "videoDesc": "Carica il file video", + "uploadingAlt": "Caricamento...", + "uploadFailedAlt": "Caricamento non riuscito" + }, + "export": { + "fabAria": "Codice di esportazione", + "title": "Codice di esportazione", + "description": "Copia l'output HTML o JSON del contenuto dell'editor", + "preview": "Anteprima", + "html": "HTML", + "json": "JSON", + "previewOptions": "Opzioni di anteprima", + "enhanceSpaces": "Valorizzare gli spazi", + "livePreview": "Anteprima dal vivo dell'HTML renderizzato", + "htmlTailwind": "HTML con classi CSS Tailwind", + "editorJson": "Stato dell'editor come JSON", + "copyHtml": "Copia HTML", + "copyJson": "Copia JSON", + "copied": "Copiato!" + }, + "template": { + "fabTitle": "Cambia modello", + "title": "Scegli un modello", + "description": "Seleziona un modello per iniziare rapidamente con layout e contenuti predefiniti", + "catAll": "Tutti i modelli", + "catProductivity": "Produttività", + "catCreative": "Creativo", + "catBusiness": "Attività commerciale", + "catPersonal": "Personale", + "noTemplates": "Nessun modello trovato in questa categoria", + "confirmTitle": "Sostituire il contenuto esistente?", + "confirmDescription": "Hai contenuti esistenti nell'editor. L'applicazione di questo modello sostituirà tutti i contenuti attuali.", + "cannotUndo": "Questa azione non può essere annullata.", + "cancel": "Cancellare", + "replaceContent": "Sostituisci contenuto" + }, + "command": { + "searchPlaceholder": "Comandi di ricerca...", + "noResults": "Nessun comando trovato.", + "turnInto": "Trasformarsi in", + "h1": { + "label": "Rubrica 1", + "description": "Titolo di sezione grande", + "keywords": "h1,intestazione,titolo,grande" + }, + "h2": { + "label": "Rubrica 2", + "description": "Titolo della sezione media", + "keywords": "h2,intestazione,sottotitolo" + }, + "h3": { + "label": "Rubrica 3", + "description": "Titolo di piccola sezione", + "keywords": "h3,intestazione,sottointestazione" + }, + "h4": { + "label": "Rubrica 4", + "description": "Titolo di sezione minuscolo", + "keywords": "h4,intestazione" + }, + "h5": { + "label": "Rubrica 5", + "description": "Intestazione più piccola", + "keywords": "h5, direzione" + }, + "h6": { + "label": "Rubrica 6", + "description": "Intestazione più piccola", + "keywords": "h6, direzione" + }, + "p": { + "label": "Paragrafo", + "description": "Paragrafo di testo regolare", + "keywords": "p,paragrafo,testo,normale" + }, + "code": { + "label": "Blocco codice", + "description": "Frammento di codice", + "keywords": "codice, blocco codice, snippet, pre" + }, + "blockquote": { + "label": "Citazione", + "description": "Citazione in blocco", + "keywords": "citazione, citazione, citazione" + }, + "li": { + "label": "Elenco puntato", + "description": "Elemento di elenco semplice con punto elenco", + "keywords": "elenco,punto,non ordinato,ul,li" + }, + "ol": { + "label": "Elenco numerato", + "description": "Voce dell'elenco numerato", + "keywords": "elenco,numerato,ordinato,ol,li" + }, + "img": { + "label": "Immagine", + "description": "Carica o incorpora un'immagine", + "keywords": "immagine,img,immagine,foto,carica" + }, + "video": { + "label": "Video", + "description": "Carica o incorpora un video", + "keywords": "video, video, film, mp4, caricamento" + }, + "table": { + "label": "Tavolo", + "description": "Crea una tabella", + "keywords": "tabella, griglia, righe, colonne, celle" + } + }, + "toasts": { + "tableImported": "Tabella importata", + "tableImportedDesc": "La tabella Markdown è stata importata correttamente", + "mediaPasted": "Supporti incollati", + "pasteMixed": "{imageCount} immagini e {videoCount} video incollati correttamente.", + "pasteImages": "{count} immagine/i incollata/e correttamente.", + "pasteVideos": "{count} video incollati correttamente.", + "pasteFailed": "Incolla non riuscito", + "unexpectedError": "Si è verificato un errore imprevisto", + "uploadFailed": "Caricamento non riuscito" + }, + "blockBackground": { + "menuItem": "Cambia colore di sfondo", + "dialogTitle": "Blocca il colore di sfondo", + "dialogDescription": "Scegli un colore di sfondo per questo blocco", + "tabPreset": "Colori preimpostati", + "tabCustom": "Colore personalizzato", + "sectionHeading": "Colori di sfondo", + "apply": "Fare domanda a", + "colors": { + "none": "Nessuno", + "red": "Rosso", + "orange": "Arancia", + "yellow": "Giallo", + "green": "Verde", + "blue": "Blu", + "indigo": "Indaco", + "purple": "Viola", + "pink": "Rosa", + "teal": "Verde acqua", + "cyan": "Ciano", + "gray": "Grigio" + } + }, + "tableDialog": { + "title": "Crea tabella", + "description": "Crea una nuova tabella o importa dal markdown", + "markdownCheckbox": "Ho una tabella di ribasso", + "pasteMarkdownLabel": "Incolla la tabella di markdown", + "markdownPlaceholder": "| Intestazione 1 | Intestazione 2 | Intestazione 3 |\n|----------|----------|----------|\n| Cella 1 | Cella 2 | Cella 3 |\n| Cella 4 | Cella 5 | Cella 6 |", + "markdownHint": "Incolla la tabella dei ribassi sopra. Deve includere righe di intestazione e separatori.", + "rows": "Righe", + "columns": "Colonne", + "maxDimensions": "Massimo: 20 righe × 10 colonne", + "cancel": "Cancellare", + "importTable": "Tabella di importazione", + "createTable": "Crea tabella" + }, + "tableParse": { + "errors": { + "minRows": "La tabella deve avere almeno una riga di intestazione e una riga di separatore", + "pipeFormat": "Le righe della tabella devono iniziare e terminare con |", + "headerMinCol": "La riga di intestazione deve avere almeno una colonna", + "separatorRow": "La seconda riga deve essere un separatore (ad esempio, |---|---|)", + "rowColumnMismatch": "La riga {row} ha {got} colonne, previste {expected}", + "parseFailed": "Impossibile analizzare la tabella di markdown" + } + }, + "link": { + "editLink": "Modifica collegamento", + "addLink": "Aggiungi collegamento", + "selectedText": "Testo selezionato: \"{text}\"", + "linkUrl": "URL di collegamento", + "urlPlaceholder": "https://esempio.com", + "update": "Aggiornamento", + "addLinkButton": "Aggiungi collegamento", + "toastApplied": "Collegamento applicato", + "toastLinkedTo": "Collegato a: {url}", + "toastRemoved": "Collegamento rimosso", + "toastRemovedDesc": "Il collegamento è stato rimosso dalla selezione" + }, + "customClass": { + "devMode": "Modalità sviluppatore", + "toggleDevModeAria": "Attiva/disattiva la modalità sviluppatore", + "searchPlaceholderDev": "Cerca classi... (ad esempio, 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Stili di ricerca... (ad esempio, \"rosso\", \"grassetto\", \"ombra\")", + "appliesTitle": "Si applica: {className}", + "noMatches": "Nessuna classe trovata corrispondente a \"{query}\"", + "toastClassReplaced": "Classe sostituita", + "toastClassReplacedDesc": "Sostituito \"{replaced}\" con \"{className}\"", + "toastCustomClassApplied": "Classe personalizzata applicata", + "toastAppliedClass": "Classe applicata: {className}" + }, + "insertComponents": { + "title": "Inserisci componente", + "description": "Scegli un componente da inserire nel documento", + "noComponents": "Nessun componente disponibile", + "categories": { + "media": "Media", + "layout": "Disposizione", + "interactive": "Interattivo", + "decoration": "Decorazione" + }, + "freeImage": { + "name": "Immagine di libero movimento", + "description": "Aggiungi un'immagine che può essere posizionata ovunque e ridimensionata liberamente" + } + } + }, + "JsonFormatter": { + "title": "Formattatore JSON", + "description": "Formatta, valida e modifica JSON nelle viste testo e albero.", + "documentNameText": "Documento di testo {n}", + "documentNameTree": "Documento ad albero {n}", + "newDocument": "Nuovo", + "copy": "Copia", + "save": "Salva", + "load": "Carica", + "saving": "Salvataggio...", + "leftDocNameLabel": "Nome documento sinistro", + "rightDocNameLabel": "Nome documento destro", + "toastCopiedText": "Pannello testo copiato", + "toastCopiedTree": "Pannello albero copiato", + "toastCopyFailed": "Impossibile copiare il JSON", + "toastLoginRequired": "Accedi per salvare i documenti", + "toastSaved": "Documento salvato", + "toastSaveFailed": "Impossibile salvare il documento", + "format": "Formatta", + "compact": "Compatta", + "sortKeys": "Ordina chiavi", + "repair": "Ripara", + "query": "Query", + "toastInvalidJson": "JSON non valido", + "toastAlreadyValid": "Il JSON è già valido", + "toastRepaired": "JSON riparato", + "toastRepairFailed": "Impossibile riparare il JSON" + }, + "JwtDecoder": { + "title": "Decodificatore JWT", + "subtitle": "Ispeziona header e payload. La scadenza usa la claim exp. Nulla viene inviato a un server; la firma non è verificata.", + "tokenLabel": "Token", + "clear": "Pulisci", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Copia", + "expiryLabel": "Scadenza (exp)", + "signaturePresent": "Firma presente (non verificata)", + "noExp": "Nessuna claim exp nel payload.", + "status": { + "expired": "Scaduto", + "valid": "Valido" + }, + "fields": { + "unix": "Unix", + "local": "Locale", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Emesso a (iat)", + "notBefore": "Non prima di (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Incolla un JWT da decodificare.", + "segments": "Un JWT richiede almeno header e payload (due segmenti).", + "base64": "Impossibile decodificare header o payload in base64url." + }, + "relative": { + "future": "tra {n} {unit}", + "past": "{n} {unit} fa" + }, + "units": { + "second": "{count, plural, one {secondo} other {secondi}}", + "minute": "{count, plural, one {minuto} other {minuti}}", + "hour": "{count, plural, one {ora} other {ore}}", + "day": "{count, plural, one {giorno} other {giorni}}" + }, + "tabs": { + "decode": "Decodifica", + "sign": "Generatore / firmatario" + }, + "signer": { + "algLabel": "Algoritmo (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "JSON del payload", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON dell’header", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Segreto (HS*)", + "secretPlaceholder": "Incolla il tuo segreto HMAC…", + "privateKeyLabel": "Chiave privata PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "L’algoritmo \"none\" crea un token non firmato. È insicuro e va usato solo per test.", + "sign": "Firma", + "verify": "Verifica firma", + "outputLabel": "Token (JWS compatto)", + "outputPlaceholder": "Clicca \"Firma\"…", + "copyToken": "Copia token", + "copied": "Copiato", + "verifyKeyLabel": "Chiave di verifica (opzionale)", + "verifyKeyPlaceholder": "Per RS*: incolla la chiave pubblica (SPKI) PEM. Per HS*: lascia vuoto per riutilizzare il segreto.", + "verifyKeyHint": "Se vuoto, la verifica usa la chiave di firma (utile per HS*).", + "verifyStatus": { + "valid": "Firma valida", + "invalid": "Firma non valida" + }, + "securityNote": "Tutto avviene localmente nel browser; chiavi e token non vengono caricati." + }, + "signErrors": { + "invalidJson": "Header e payload devono essere JSON valido.", + "unsupportedAlg": "Algoritmo non supportato.", + "missingKey": "È richiesta una chiave di firma per questo algoritmo.", + "cryptoFailed": "Firma non riuscita. Controlla il formato della chiave e riprova." + }, + "verifyErrors": { + "empty": "Inserisci un JWT da verificare.", + "segments": "Un JWT deve avere esattamente 3 segmenti per verificare una firma.", + "unsupportedAlg": "Algoritmo non supportato o alg mancante nell’header.", + "missingKey": "È richiesta una chiave di verifica per questo algoritmo.", + "base64": "Impossibile decodificare l’header o la firma del JWT.", + "cryptoFailed": "Verifica non riuscita. Controlla il formato della chiave e riprova." + } + }, + "CertificatePemDecoder": { + "title": "Decodificatore certificati / PEM", + "subtitle": "Incolla un certificato X.509 (-----BEGIN CERTIFICATE-----) o una CSR (-----BEGIN CERTIFICATE REQUEST-----). Soggetto, emittente, date e SAN sono analizzati in locale; nulla viene caricato.", + "pemLabel": "Input PEM", + "clear": "Cancella", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Analisi in corso…", + "copyHex": "Copia impronta (hex senza due punti)", + "errors": { + "empty": "Incolla un certificato PEM o una CSR da decodificare.", + "noBlock": "Nessun blocco PEM di certificato o CSR trovato.", + "parseFailed": "Impossibile analizzare questi dati PEM." + }, + "block": { + "title": "Blocco PEM {index}", + "certificate": "Certificato X.509", + "csr": "Richiesta di firma certificato" + }, + "fields": { + "subject": "Soggetto", + "issuer": "Emittente", + "notBefore": "Non valido prima del", + "notAfter": "Non valido dopo il", + "serial": "Numero di serie (hex)", + "sha256": "Impronta SHA-256", + "sans": "Nomi alternativi del soggetto", + "sansNone": "Nessuna estensione SAN.", + "signatureAlgorithm": "Algoritmo di firma", + "csrIssuerNote": "Le CSR non includono l’emittente (compare sul certificato emesso).", + "csrValidityNote": "Le CSR non includono le date di validità (compaiono sul certificato emesso)." + }, + "sanType": { + "dns": "DNS", + "dn": "Nome distintivo", + "email": "Email", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID registrato", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Formattatore SQL", + "subtitle": "Formatta SQL per MySQL, PostgreSQL o SQLite. Funziona nel browser; non viene caricato nulla.", + "dialectLabel": "Dialetto", + "keywordsLabel": "Parole chiave", + "indentLabel": "Indentazione", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "MAIUSCOLO", + "lower": "minuscolo", + "preserve": "Mantieni" + }, + "spaces": "{n} spazi", + "format": "Formatta", + "clear": "Pulisci", + "counter": "{current} / {max} caratteri", + "inputPanel": "Input", + "formattedPanel": "Formattato", + "copy": "Copia", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Clicca su \"Formatta\"…", + "errors": { + "tooLong": "L’input è limitato a {max} caratteri.", + "couldNotFormat": "Impossibile formattare questo SQL." + } + }, + "YamlFormatter": { + "title": "Formattatore YAML", + "subtitle": "Formatta, valida e converti YAML nel browser. Nulla viene caricato.", + "outputLabel": "Output", + "outputModes": { + "yaml": "YAML (formattato)", + "json": "JSON" + }, + "indentLabel": "Rientro", + "spaces": "{n} spazi", + "format": "Formatta", + "clear": "Cancella", + "copy": "Copia", + "counter": "{current} / {max} caratteri", + "inputPanel": "Input", + "outputPanel": "Output", + "errors": { + "tooLong": "L'input è limitato a {max} caratteri.", + "couldNotFormat": "Impossibile analizzare questo YAML." + } + }, + "GraphqlFormatter": { + "title": "Formattatore GraphQL", + "subtitle": "Formatta query, mutazioni e subscription GraphQL nel browser. Nulla viene caricato.", + "tabs": { + "format": "Formatta", + "build": "Editor di query" + }, + "outputModeLabel": "Output", + "outputModes": { + "pretty": "Stampa leggibile", + "minify": "Minifica" + }, + "format": "Formatta", + "clear": "Svuota", + "counter": "{current} / {max} caratteri", + "inputPanel": "Input", + "outputPanel": "Output", + "copy": "Copia", + "builderHint": "Componi lo scheletro del documento e applicalo all’editor. Le definizioni di variabili usano la sintassi GraphQL (es. $id: ID!) — le parentesi sono opzionali.", + "builder": { + "operation": "Operazione", + "operationName": "Nome operazione", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definizioni di variabili", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Selezione", + "apply": "Applica all’input", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "L’input è limitato a {max} caratteri.", + "couldNotFormat": "Impossibile analizzare o stampare questo GraphQL.", + "emptySelection": "Aggiungi una selezione prima di applicare." + } + }, + "RegexTester": { + "title": "Tester regex", + "subtitle": "Regex stile JavaScript in tempo reale sul tuo testo. Le corrispondenze sono evidenziate; nulla lascia il browser.", + "patternLabel": "Pattern", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g globale", + "i": "i ignora maiuscole/minuscole", + "m": "m multilinea", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(vuoto)", + "noFlags": "—", + "matches": "{count, plural, one {corrispondenza} other {corrispondenze}}", + "testTextLabel": "Testo di prova", + "counter": "{current} / {max}", + "clear": "Pulisci", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Il testo è limitato a {max} caratteri.", + "invalid": "Espressione regolare non valida.", + "runaway": "Interrotto dopo {max} corrispondenze (pattern potenzialmente incontrollato)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Convertitore timestamp", + "subtitle": "Secondi Unix (≤10 cifre) o millisecondi (interi più lunghi), ISO-8601 o stringhe che il browser può interpretare. Tutto avviene localmente.", + "inputLabel": "Input", + "placeholder": "1744200000 o 2025-04-09T12:00:00Z", + "now": "Adesso", + "copy": "Copia", + "hint": "Incolla un valore o fai clic su Adesso.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativo", + "human": "Leggibile" + }, + "fields": { + "seconds": "Secondi", + "milliseconds": "Millisecondi", + "utc": "UTC", + "local": "Locale", + "distance": "Distanza" + }, + "errors": { + "empty": "Inserisci un timestamp Unix, una stringa ISO o un testo data.", + "invalidNumber": "Timestamp numerico non valido.", + "outOfRange": "Fuori intervallo per Date di JavaScript.", + "couldNotParse": "Impossibile interpretare come Unix, ISO-8601 o data del browser." + } + }, + "HttpStatusCodes": { + "title": "Codici di stato HTTP", + "subtitle": "Cerca e sfoglia i codici di stato delle risposte HTTP (1xx5xx) con brevi descrizioni e riferimenti RFC. Funziona localmente.", + "header": { + "title": "Codici di stato HTTP", + "description": "Riferimento ricercabile dei codici di risposta HTTP con descrizioni e riferimenti RFC." + }, + "searchPlaceholder": "Cerca: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtra classe…", + "classFilterAll": "Tutte le classi", + "resultsCount": "{count} di {total}", + "table": { + "code": "Code", + "phrase": "Frase", + "description": "Description", + "copy": "Copia" + }, + "noResults": "Nessun risultato per “{query}”.", + "specPrefix": "Spec:", + "copied": "Codice copiato" + }, + "CronBuilder": { + "title": "Costruttore di espressioni Cron", + "subtitle": "Cron standard a 5 campi: minute hour day month weekday. Il parser usa seconds=0 internamente. Il formato opzionale a 6 campi (sec min hour dom mon dow) funziona nell’editor.", + "tabs": { + "builder": "Costruttore visuale", + "expression": "Espressione" + }, + "presetsLabel": "Preset", + "fieldsLabel": "Campi", + "rawExpressionLabel": "Espressione grezza", + "expressionHint": "Sposta il focus per analizzare e sincronizzare il builder. {count} {count, plural, one {token} other {token}}.", + "currentExpressionLabel": "Espressione corrente", + "copy": "Copia", + "quickPick": "Scelta rapida", + "emptyExpression": "—", + "plainLanguageLabel": "Linguaggio naturale", + "nextRunsLabel": "Prossime esecuzioni (ora locale)", + "fields": { + "minute": "Minuto", + "hour": "Ora", + "dayOfMonth": "Giorno del mese", + "month": "Mese", + "dayOfWeek": "Giorno della settimana" + }, + "presets": { + "everyMinute": "Ogni minuto", + "every5Minutes": "Ogni 5 minuti", + "every15Minutes": "Ogni 15 minuti", + "everyHour": "Ogni ora (a :00)", + "every6Hours": "Ogni 6 ore", + "dailyMidnight": "Ogni giorno a mezzanotte", + "daily0900": "Ogni giorno alle 09:00", + "weekdays0900": "Feriali alle 09:00", + "weeklySunday": "Settimanale (domenica 00:00)", + "monthlyFirst": "Mensile (1° 00:00)", + "yearlyJan1": "Annuale (1 gen 00:00)" + }, + "picks": { + "minute": { + "every": "ogni minuto" + }, + "hour": { + "every": "ogni ora" + }, + "dom": { + "any": "qualsiasi giorno", + "last": "ultimo giorno" + }, + "month": { + "every": "ogni mese" + }, + "dow": { + "any": "qualsiasi giorno", + "weekdays": "feriali", + "weekend": "weekend" + } + }, + "months": { + "jan": "Gen", + "feb": "Feb", + "mar": "Mar", + "apr": "Apr", + "may": "Mag", + "jun": "Giu", + "jul": "Lug", + "aug": "Ago", + "sep": "Set", + "oct": "Ott", + "nov": "Nov", + "dec": "Dic" + }, + "weekdays": { + "sun": "Dom", + "mon": "Lun", + "tue": "Mar", + "wed": "Mer", + "thu": "Gio", + "fri": "Ven", + "sat": "Sab" + }, + "errors": { + "fieldCount": "Previsti 5 campi (minute hour day month weekday) o 6 includendo i secondi.", + "invalid": "Espressione cron non valida." + } + }, + "Base64": { + "title": "Codificatore / decodificatore Base64", + "subtitle": "Codifica testo in Base64 o decodifica stringhe Base64", + "upload": "Carica", + "clear": "Pulisci", + "modes": { + "encode": "Codifica", + "decode": "Decodifica" + }, + "swapTitle": "Scambia input e output", + "panels": { + "plainText": "Testo semplice", + "base64Input": "Input Base64", + "base64Output": "Output Base64", + "decodedText": "Testo decodificato" + }, + "placeholders": { + "encode": "Inserisci testo da codificare in Base64…", + "decode": "Incolla stringa Base64 da decodificare…" + }, + "dropHere": "Rilascia qui il file", + "charCount": "{count} caratteri", + "download": { + "title": "Scarica output", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Copia negli appunti", + "outputPlaceholder": "L’output apparirà qui…", + "errors": { + "invalidBase64": "Stringa Base64 non valida. Controlla l’input.", + "encodeFailed": "Impossibile codificare l’input." + } + }, + "UrlEncode": { + "title": "Codificatore / decodificatore URL", + "subtitle": "Codifica/decodifica percentuale con encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Carica", + "clear": "Pulisci", + "modes": { + "encode": "Codifica", + "decode": "Decodifica" + }, + "swapTitle": "Scambia input e output", + "panels": { + "plainText": "Testo semplice", + "encodedInput": "Input codificato", + "encodedOutput": "Output codificato", + "decodedText": "Testo decodificato" + }, + "placeholders": { + "encode": "Inserisci testo da codificare in percentuale (es. query string)…", + "decode": "Incolla testo percent-encoded da decodificare…" + }, + "dropHere": "Rilascia qui il file", + "charCount": "{count} caratteri", + "download": { + "title": "Scarica output", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Copia negli appunti", + "outputPlaceholder": "L’output apparirà qui…", + "errors": { + "invalidEncoding": "Percent-encoding non valido. Verifica coppie % incomplete o sequenze di escape.", + "encodeFailed": "Impossibile codificare (Unicode non valido nell’input)." + } + }, + "UrlParser": { + "title": "Parser URL", + "subtitle": "Scompone un URL in protocollo, host, percorso, parametri di query e hash (frammento).", + "upload": "Carica", + "clear": "Pulisci", + "panels": { + "input": "Input URL", + "output": "Output" + }, + "placeholders": { + "input": "Incolla un URL (es. https://example.com/path?foo=bar#section) o un host/percorso (es. example.com/path)…" + }, + "dropHere": "Rilascia qui il file", + "charCount": "{count} caratteri", + "outputPlaceholder": "I componenti analizzati appariranno qui…", + "fields": { + "protocol": "Protocollo", + "host": "Host", + "path": "Percorso", + "hash": "Hash", + "queryParams": "Parametri di query" + }, + "paramCount": "{count} parametri", + "noParams": "Nessun parametro di query.", + "paramKey": "Chiave", + "paramValue": "Valore", + "copyTitle": "Copia negli appunti", + "copyParamTitle": "Copia come key=value", + "download": { + "title": "Scarica JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL non valido. Includi un hostname (example.com) e controlla i caratteri." + } + }, + "UuidGenerator": { + "title": "Generatore UUID / ULID", + "subtitle": "Genera UUID (RFC 9562) o ULID nel browser. Esportazione in blocco fino a {max} per esecuzione.", + "format": "Formato", + "kindHints": { + "ulid": "Identificatore ordinabile, sicuro per URL, 26 caratteri", + "uuid4": "Casuale (RFC 9562)", + "uuid7": "Ordinato nel tempo, ms Unix + casuale", + "uuid1": "Basato su MAC/tempo (RFC 9562)", + "uuid6": "Tempo gregoriano riordinato", + "uuid3": "Hash MD5 di nome + namespace", + "uuid5": "Hash SHA‑1 di nome + namespace", + "nil": "UUID nullo (tutti zeri, RFC 9562)" + }, + "countLabel": "Quantità (1–{max})", + "nameLabel": "Nome (stringa di input)", + "namePlaceholder": "es. my.app/resource", + "bulkHint": "Nei batch viene aggiunto #0, #1, … così ogni ID è distinto (v3/v5 sono deterministici).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID personalizzato" + }, + "customNamespaceLabel": "UUID namespace", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Genera", + "output": "Output", + "lines": "{count} righe", + "download": { + "title": "Scarica .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Copia tutto", + "uppercaseToggle": "Output in maiuscolo", + "hyphensToggle": "Mostra trattini", + "copyJsonTitle": "Copia come array JSON", + "outputPlaceholder": "Clicca su \"Genera\"…", + "errors": { + "invalidCustomNamespace": "Il namespace personalizzato deve essere un UUID valido.", + "missingName": "Il nome è obbligatorio per UUID v3 e v5.", + "unknown": "Impossibile generare gli ID." + } + }, + "SecretApiKeyGenerator": { + "title": "Generatore di segreto / chiave API", + "subtitle": "Genera stringhe casuali crittografiche con lunghezza e alfabeto configurabili. Esportazione massiva fino a {max} per esecuzione. Si affianca al generatore UUID / ULID.", + "alphabetLabel": "Alfabeto", + "alphabetHint": "I caratteri duplicati vengono rimossi. Ogni simbolo è estratto in modo uniforme con crypto.getRandomValues e campionamento per rigetto (senza bias di modulo).", + "alphabetCount": "{count} simboli unici", + "presetsLabel": "Preimpostazioni", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumerico", + "hex": "Esadecimale (minuscolo)", + "asciiPrintable": "ASCII stampabile" + }, + "lengthLabel": "Lunghezza per segreto (1–{maxLen})", + "countLabel": "Quantità (1–{maxBulk})", + "generate": "Genera", + "output": "Output", + "lines": "{count} righe", + "download": { + "title": "Scarica .txt", + "filename": "segreti.txt" + }, + "copyTitle": "Copia tutto", + "outputPlaceholder": "Fai clic su \"Genera\"…", + "entropy": "{bits} bit di entropia", + "strength": { + "weak": "Debole", + "fair": "Discreta", + "strong": "Forte" + }, + "excludeAmbiguous": "Escludi caratteri ambigui", + "excludeAmbiguousHint": "Rimuove {chars} — caratteri facilmente confondibili o ostili alla shell.", + "prefixLabel": "Prefisso della chiave (opzionale)", + "errors": { + "emptyAlphabet": "Aggiungi almeno un carattere all’alfabeto.", + "alphabetTooLong": "Dopo la rimozione dei duplicati, l’alfabeto può contenere al massimo {maxAlpha} caratteri.", + "lengthOutOfRange": "La lunghezza deve essere tra 1 e {maxLen}.", + "countOutOfRange": "La quantità deve essere tra 1 e {maxBulk}.", + "unknown": "Impossibile generare i segreti." + } + }, + "LoremIpsum": { + "title": "Generatore Lorem Ipsum", + "subtitle": "Genera testo latino segnaposto per i tuoi layout e mockup.", + "paragraphs": "Paragrafi", + "sentences": "Frasi", + "words": "Parole", + "count": "Numero", + "generate": "Genera", + "copy": "Copia", + "startWith": "Inizia con 'Lorem ipsum...'", + "outputPlaceholder": "L'output apparirà qui...", + "outputType": "Tipo di output", + "bulletList": "Elenco puntato", + "htmlMarkup": "Markup HTML", + "htmlMarkupHint": "Avvolgi l'output in paragrafi, o usa un elenco per la modalità elenco puntato", + "output": "Output", + "download": "Scarica" + }, + "ColorPicker": { + "title": "Selettore e convertitore di colori", + "subtitle": "Converti HEX, RGB e HSL; crea tavolozze armoniche. Tutto il processamento avviene nel tuo browser.", + "nativePicker": "Selettore nativo", + "chooseColor": "Scegli un colore", + "eyeDropper": "Contagocce", + "invalidHex": "Colore HEX non valido.", + "cssValues": "Valori CSS", + "palettes": "Tavolozze", + "copied": "Copiato", + "clickToCopy": "Clicca per copiare", + "copyAllHex": "Copia tutti gli HEX", + "copy": "Copia", + "copyColor": "Copia {color}", + "paletteShades": "Sfumature", + "paletteComplementary": "Complementare", + "paletteTriadic": "Triadico", + "paletteAnalogous": "Analogo", + "paletteSplit": "Compl. diviso", + "paletteSquare": "Quadrato" + }, + "ContrastChecker": { + "title": "Controllo contrasto", + "subtitle": "Calcola il rapporto di contrasto WCAG 2.1 tra primo piano e sfondo. Verifica AA e AAA per testo normale e grande. Tutto nel browser.", + "foreground": "Testo / primo piano", + "background": "Sfondo", + "hexPlaceholder": "#RRGGBB", + "swap": "Scambia colori", + "chooseForeground": "Scegli colore primo piano", + "chooseBackground": "Scegli colore di sfondo", + "eyeDropper": "Contagocce", + "ratio": "Rapporto di contrasto", + "copyRatio": "Copia rapporto", + "copied": "Copiato", + "previewHeading": "Anteprima", + "previewBody": "Ma la volpe, col suo balzo, ha raggiunto il quieto Fido. Quel fez serbato dagli struzzi còi bufali osserva tal quiz.", + "previewCaption": "Testo di esempio a dimensione predefinita.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — testo normale (4,5:1)", + "levelAALarge": "AA — testo grande (3:1)", + "levelAAANormal": "AAA — testo normale (7:1)", + "levelAAALarge": "AAA — testo grande (4,5:1)", + "pass": "Superato", + "fail": "Non superato", + "invalidHex": "Colore HEX non valido.", + "largeTextHint": "Testo grande: 18 pt+ regolare o 14 pt+ grassetto (definizioni WCAG)." + }, + "DiffChecker": { + "title": "Controllo differenze", + "subtitle": "Confronto riga per riga di due testi fianco a fianco. Tutto rimane nel tuo browser.", + "rowsCount": "{count} righe", + "truncatedWarning": "Mostrando le prime {max} righe.", + "limitExceeded": "Ogni lato è limitato a {max} caratteri.", + "clearBoth": "Cancella entrambi", + "original": "Originale", + "modified": "Modificato", + "originalPlaceholder": "Incolla il testo originale…", + "modifiedPlaceholder": "Incolla il testo modificato…", + "comparisonLabel": "Confronto fianco a fianco", + "emptyState": "Inserisci testo in entrambi i pannelli per confrontare." + }, + "JsonSchemaGenerator": { + "title": "Generatore di schema JSON", + "subtitle": "Incolla JSON valido per generare uno schema JSON o modelli tipizzati. Tutto viene eseguito nel browser.", + "languageLabel": "Linguaggio di output", + "inputLabel": "Input JSON", + "inputPlaceholder": "Incolla il JSON qui…", + "outputLabel": "Output generato", + "outputPlaceholder": "Lo schema o i tipi generati appariranno qui…", + "parseError": "JSON non valido", + "clear": "Cancella", + "copy": "Copia", + "copied": "Copiato negli appunti", + "copyFailed": "Impossibile copiare", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Schema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Caricamento editor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Immagine su Base64", + "subtitle": "Rilascia un'immagine, ottieni un URI di dati o una stringa Base64 non elaborata. Tutto rimane nel tuo browser.", + "upload": "Carica immagine", + "clear": "Chiaro", + "modes": { + "dataUri": "URI dei dati", + "rawString": "Base grezza64" + }, + "panels": { + "imageInput": "Ingresso immagine", + "output": "Uscita Base64" + }, + "dropHere": "Rilascia l'immagine qui", + "charCount": "{count} caratteri", + "placeholders": { + "uploadImage": "Trascina e rilascia un'immagine o fai clic su Carica per iniziare." + }, + "fileTooLarge": "Il file è troppo grande. L'elaborazione potrebbe bloccare il browser.", + "copyTitle": "Copia negli appunti", + "download": { + "title": "Scarica testo", + "filename": "immagine-base64.txt" + } + }, + "ImageCompressor": { + "title": "Compressore immagini", + "subtitle": "Riduci l’output JPEG, PNG o WebP in locale con controllo qualità — nulla viene caricato.", + "upload": "Carica immagine", + "clear": "Cancella", + "dropHere": "Trascina qui l’immagine", + "placeholders": { + "uploadImage": "Trascina un’immagine o fai clic per scegliere un file." + }, + "fileTooLarge": "File troppo grande (max {maxMb} MB).", + "unsupportedFormat": "Tipo di file non supportato. Usa JPEG, PNG, WebP, GIF, AVIF o BMP.", + "compressFailed": "Impossibile comprimere l’immagine. Prova un altro file o formato.", + "panels": { + "original": "Originale", + "compressed": "Anteprima compressa" + }, + "outputFormat": "Formato di output", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Qualità", + "qualityPercent": "{value}%", + "qualityPngHint": "Il PNG è senza perdita nel browser; lo slider riguarda solo JPEG e WebP.", + "sizes": { + "original": "Originale: {size}", + "compressed": "Compresso: {size}", + "saved": "{percent}% più piccolo" + }, + "largerWarning": "L’output è più grande dell’input — prova un altro formato o qualità.", + "download": "Scarica", + "downloadFilename": "immagine-compressa", + "processing": "Compressione…", + "emptyCompressed": "L’anteprima compressa appare qui." + }, + "CssGradientBuilder": { + "title": "Generatore di gradienti CSS", + "subtitle": "Generatore di gradienti CSS visivi con controllo dell'angolo, interruzioni di colore e copia CSS con un clic.", + "preview": "Anteprima gradiente", + "settings": "Impostazioni", + "type": "Tipo", + "linear": "Lineare", + "radial": "Radiale", + "angle": "Angolo", + "stops": "Il colore si ferma", + "color": "Colore", + "position": "Posizione (%)", + "addStop": "Aggiungi fermata", + "deleteStop": "Elimina Interrompi", + "output": "Uscita CSS", + "copy": "Copia CSS", + "copied": "Copiato!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Generatore .gitignore", + "subtitle": "Genera istantaneamente file .gitignore uniti per uno stack tecnologico o una combinazione specifica.", + "searchPlaceholder": "Stack di ricerca (ad esempio Node, Python, macOS)...", + "noResults": "Nessuno stack trovato.", + "selectedStacks": "Stack selezionati", + "output": "Anteprima dell'output", + "copy": "Copia negli appunti", + "copied": "Copiato!", + "download": "Scarica .gitignore", + "downloading": "Download in corso...", + "errorFetching": "Impossibile recuperare l'elenco dei migliori risultati.", + "errorGenerating": "Impossibile generare .gitignore dagli stack selezionati.", + "generating": "Generazione...", + "emptyState": "Seleziona uno o più stack tecnologici per generare il tuo .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Carica CSV o Excel, modifica il JSON, poi scarica CSV o XLSX. Solo dati tabulari; le date Excel diventano timestamp ISO nel JSON.", + "dropTitle": "Trascina un CSV o Excel", + "dropHint": "Per .xlsx / .xls si usa il primo foglio. Il CSV deve avere una riga di intestazione.", + "chooseFile": "Scegli file", + "jsonLabel": "JSON (array di oggetti)", + "jsonPlaceholder": "Incolla un array JSON di oggetti o importa CSV / Excel…", + "loadSample": "Carica esempio", + "actions": { + "format": "Formatta JSON", + "clear": "Cancella", + "copy": "Copia", + "copied": "Copiato", + "downloadCsv": "Scarica CSV", + "downloadXlsx": "Scarica Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Dati", + "toastLoadedCsv": "CSV caricato nell’editor", + "toastLoadedExcel": "File Excel caricato nell’editor", + "toastFormatted": "JSON formattato", + "toastExportedCsv": "CSV scaricato", + "toastExportedXlsx": "Excel scaricato", + "toastCopied": "Copiato negli appunti", + "footnote": "Tutto nel browser. Il JSON deve essere un array non vuoto di oggetti. I valori annidati sono serializzati in JSON nelle celle CSV.", + "errors": { + "unsupportedFile": "Usa un file .csv, .xlsx o .xls.", + "parseFailed": "Impossibile analizzare il file.", + "invalidJson": "JSON non valido", + "exportFailed": "Esportazione non riuscita", + "copyFailed": "Impossibile copiare" + } + }, + "SnippetManager": { + "title": "Gestore snippet di codice", + "subtitle": "Conserva gli snippet nel browser o accedi per sincronizzarli con il tuo account. Passa da visualizzazione a modifica, copia con un clic, scegli la lingua o il rilevamento automatico e formatta JSON, SQL o codice supportato da Monaco.", + "snippets": "Snippet", + "newSnippet": "Nuovo snippet", + "newSnippetTitle": "Nuovo snippet", + "defaultSnippetTitle": "Benvenuto", + "defaultSnippetCode": "// Benvenuto negli snippet di codice\n// Prova Visualizza vs Modifica, Copia, Formatta e lingua automatica\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Cerca snippet…", + "searchAria": "Cerca snippet", + "emptySearch": "Nessuno snippet corrisponde alla ricerca.", + "snippetTitle": "Titolo", + "snippetTitlePlaceholder": "Nome snippet", + "language": "Linguaggio", + "detectedHint": "Evidenziazione come {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Visualizza", + "modeEdit": "Modifica", + "copy": "Copia", + "copied": "Copiato", + "format": "Formatta", + "cancel": "Annulla", + "deleteTitle": "Eliminare lo snippet?", + "deleteDescription": "«{title}» verrà rimosso da questo browser.", + "deleteConfirm": "Elimina", + "editorAria": "Editor codice snippet", + "editorLoading": "Caricamento editor…", + "toastCopied": "Copiato negli appunti", + "toastCopyFailed": "Impossibile copiare", + "toastNothingToCopy": "Niente da copiare", + "toastFormatted": "Formattato", + "toastFormatFailed": "Impossibile formattare", + "toastFormatJsonInvalid": "JSON non valido — correggi prima la sintassi", + "toastFormatUnsupported": "Nessun formattatore per questo linguaggio — prova JSON o SQL o la modalità modifica", + "toastDeleted": "Snippet eliminato", + "toastLoadFailed": "Impossibile caricare gli snippet dal server.", + "toastSyncFailed": "Impossibile sincronizzare con il server.", + "languages": { + "auto": "Rilevamento automatico", + "plaintext": "Testo normale", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Client SQL", + "dialogTitle": "Connessioni SQL", + "mobileSheetTitle": "Esplora SQL", + "newTabLabel": "Nuova scheda query", + "vaultLockedTitle": "Archivio bloccato", + "vaultLockedDesc": "Sblocca l'archivio per accedere alle connessioni salvate.", + "heroTitle": "Client SQL", + "heroDesc": "Connettiti a PostgreSQL, MySQL o MariaDB. Le credenziali sono cifrate nel browser.", + "heroAddConnection": "Aggiungi connessione", + "selectTableTitle": "Seleziona una tabella o apri una scheda query", + "selectTableDesc": "Clicca su una tabella nella barra laterale o usa ⌘↩.", + "toastLoadFail": "Caricamento connessioni fallito." + }, + "connection": { + "titleNew": "Nuova connessione SQL", + "titleEdit": "Modifica connessione", + "labelDbType": "Tipo di database", + "labelName": "Nome connessione", + "placeholderName": "Il mio DB di produzione", + "labelHostPort": "Host e Porta", + "labelDatabase": "Database", + "placeholderDatabase": "mydb", + "labelUsername": "Nome utente", + "labelPassword": "Password", + "labelSsl": "Abilita SSL / TLS", + "sslDesc": "Cifra la connessione al server", + "securityNote": "Le credenziali sono cifrate AES-256 nel browser prima dell'archiviazione", + "btnTest": "Testa connessione", + "btnSave": "Salva e connetti", + "btnUpdate": "Aggiorna", + "btnCancelEdit": "Annulla modifica", + "savedConnections": "Connessioni salvate", + "emptySaved": "Le connessioni salvate appariranno qui", + "confirmDelete": "Eliminare questa connessione?", + "toastFillFields": "Compila host e database prima di testare.", + "toastConnectOk": "Connesso! {version}", + "toastConnectFail": "Connessione fallita: {message}", + "toastUnlockVault": "Sblocca prima l'archivio.", + "toastFieldsRequired": "Host e database sono obbligatori.", + "toastUpdated": "Connessione aggiornata.", + "toastSaved": "Connessione salvata.", + "toastSaveFail": "Salvataggio fallito: {message}", + "toastDeleted": "Connessione eliminata.", + "toastDeleteFail": "Impossibile eliminare la connessione." + }, + "sidebar": { + "title": "Esplora SQL", + "tooltipAddConnection": "Aggiungi connessione", + "tooltipRefreshSchema": "Aggiorna schema", + "noConnections": "Nessuna connessione salvata", + "toastSchemaFail": "Caricamento schema fallito: {message}" + }, + "editor": { + "btnRun": "Esegui", + "executing": "Esecuzione query…", + "emptyTitle": "Esegui una query per vedere i risultati", + "emptyHint": "Premi ⌘↩ o clicca Esegui", + "toastNoConnection": "Nessuna connessione attiva." + }, + "results": { + "filterPlaceholder": "Filtra risultati…", + "rowCount": "{count} {count, plural, one {riga} other {righe}}", + "rowCountFiltered": "{filtered} / {total} righe", + "exportCsv": "CSV", + "noRows": "Nessuna riga restituita" + } + }, + "QrCodeGenerator": { + "title": "Generatore di codici QR", + "subtitle": "Trasforma qualsiasi testo o URL in un codice QR PNG. Tutto viene renderizzato localmente nel browser.", + "contentLabel": "Contenuto", + "contentPlaceholder": "https://esempio.it, testo semplice, stringa Wi‑Fi, vCard…", + "styleLabel": "Stile", + "dotTypeLabel": "Forma dei punti", + "dotTypeSquare": "Quadrato", + "dotTypeDots": "Punti", + "dotTypeRounded": "Arrotondato", + "dotTypeExtraRounded": "Extra arrotondato", + "dotTypeClassy": "Elegante", + "dotTypeClassyRounded": "Elegante arrotondato", + "cornerSquareLabel": "Quadrati d'angolo", + "cornerSquareDot": "Punto", + "cornerSquareSquare": "Quadrato", + "cornerSquareExtraRounded": "Extra arrotondato", + "cornerDotLabel": "Punti d'angolo", + "cornerDotDot": "Punto", + "cornerDotSquare": "Quadrato", + "eccLabel": "Correzione errori", + "eccL": "Bassa (~7%)", + "eccM": "Media (~15%)", + "eccQ": "Quartile (~25%)", + "eccH": "Alta (~30%)", + "marginLabel": "Margine (moduli)", + "widthLabel": "Larghezza immagine", + "widthEffective": "Render a {px} px", + "darkColor": "Moduli scuri", + "lightColor": "Moduli chiari", + "resetColors": "Ripristina colori", + "logoLabel": "Logo centrale", + "logoMarginLabel": "Margine", + "uploadLogo": "Carica immagine", + "removeLogo": "Rimuovi logo", + "downloadPng": "Scarica PNG", + "downloadSvg": "Scarica SVG", + "copyImage": "Copia immagine", + "copied": "Copiato", + "clear": "Cancella", + "previewAlt": "Anteprima codice QR", + "emptyHint": "Digita o incolla contenuto per vedere l’anteprima.", + "filename": "codice-qr.png", + "errors": { + "generationFailed": "Impossibile generare il codice QR. Prova un testo più corto o meno correzione.", + "copyUnsupported": "Il browser non supporta la copia delle immagini negli appunti.", + "copyFailed": "Impossibile copiare negli appunti." + } + }, + "IpSubnetCalculator": { + "title": "Calcolatrice IP / subnet", + "subtitle": "Dettagli CIDR IPv4 e IPv6 nel browser—nessuna richiesta esterna.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Rete", + "placeholderIpv4": "10.0.0.0/24 o 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 o 2001:db8::1/64", + "hintIpv4": "Usa CIDR (es. /24) o IPv4 con maschera decimale.", + "hintIpv6": "Includi il prefisso (es. /64). Se omesso, /128.", + "copyValue": "Copia valore", + "rows": { + "cidr": "CIDR", + "network": "Indirizzo di rete", + "netmask": "Maschera di sottorete", + "wildcard": "Maschera wildcard", + "broadcast": "Broadcast", + "firstHost": "Primo host", + "lastHost": "Ultimo host", + "totalAddresses": "Indirizzi totali", + "usableHosts": "Host utilizzabili", + "prefixLength": "Lunghezza prefisso (bit)", + "networkIpv6": "Indirizzo di rete", + "lastIpv6": "Ultimo indirizzo nell’intervallo", + "totalAddressesIpv6": "Indirizzi nella sottorete" + }, + "usableNote": { + "pointToPoint": "I collegamenti /31 punto-punto usano entrambi gli indirizzi come host (RFC 3021).", + "singleHost": "/32 è un singolo host." + }, + "ipv6RangeHint": { + "normal": "Intervallo matematico completo; le regole del router possono variare.", + "single": "/128 è un singolo indirizzo.", + "full": "/0 copre tutto IPv6." + }, + "errors": { + "invalidIpv4": "Rete IPv4 non valida. Prova CIDR o indirizzo + maschera.", + "invalidIpv6": "Rete IPv6 non valida. Usa indirizzo/prefisso, es. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Generatore di hash", + "subtitle": "Digest MD5, SHA-1 e SHA-2 per testo o file; bcrypt per le password (input testuale). Tutto viene calcolato localmente nel browser.", + "algorithmLabel": "Algoritmo", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Testo", + "modeFile": "File", + "inputLabelText": "Input", + "placeholderText": "Digita o incolla testo…", + "chooseFile": "Scegli file", + "clearFile": "Cancella", + "fileHint": "I file sono letti solo in memoria; max {maxMb} MB. Nessun caricamento.", + "outputLabel": "Digest (hex)", + "copyHash": "Copia digest", + "copied": "Copiato", + "byteCount": "{count, plural, one {# byte} other {# byte}}", + "charCount": "{count, plural, one {# carattere} other {# caratteri}}", + "emptyHint": "Inserisci testo o scegli un file.", + "securityNote": "MD5 e SHA-1 non sono consigliati per nuovi usi di sicurezza; preferire SHA-256 o SHA-512.", + "errors": { + "fileTooLarge": "File più grande di {maxMb} MB.", + "fileReadFailed": "Impossibile leggere il file." + }, + "bcryptRoundsLabel": "Costo (giri)", + "bcryptRoundsHint": "Valori più alti sono più lenti e più resistenti alla forza bruta. Spesso il default è 10.", + "fileTabDisabledTitle": "bcrypt elabora solo password da testo, non file arbitrari.", + "placeholderBcrypt": "Digita o incolla una password da sottoporre a hash…", + "outputLabelBcrypt": "Hash bcrypt", + "copyBcrypt": "Copia hash", + "emptyHintBcrypt": "Inserisci una password per calcolare l’hash bcrypt.", + "securityNoteBcrypt": "Ogni output bcrypt include un sale casuale; la stessa password produce una stringa diversa ogni volta. È normale. Usa costi elevati in produzione.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Sandbox crittografia", + "subtitle": "AES-GCM nel browser: cifra in un bundle JSON con passphrase o chiave grezza, poi decifra in locale. Didattico e talvolta utile — nulla viene caricato.", + "tabEncrypt": "Cifra", + "tabDecrypt": "Decifra", + "keyModeLabel": "Materiale della chiave", + "modePassphrase": "Passphrase (PBKDF2 + AES-GCM)", + "modeRawKey": "Chiave AES grezza (hex o Base64)", + "aesBitsLabel": "Lunghezza chiave AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Codifica chiave grezza", + "encodingAuto": "Auto (hex se sembra hex, altrimenti Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Chiave grezza", + "rawKeyPlaceholder": "Chiave da 32 byte come 64 caratteri hex, o Base64 per AES-256 (la lunghezza dipende dalla dimensione AES sopra).", + "rawKeyPlaceholderDecrypt": "Serve solo se nel bundle c’è keyDerivation \"none\".", + "passphraseLabel": "Passphrase", + "passphrasePlaceholder": "Passphrase usata solo nel browser per derivare la chiave.", + "passphrasePlaceholderDecrypt": "Serve solo se il bundle usa PBKDF2-SHA256.", + "pbkdf2IterLabel": "Iterazioni PBKDF2", + "pbkdf2Hint": "Tra 10.000 e 2.000.000. Il default segue impostazioni comuni dei vault.", + "plaintextLabel": "Testo in chiaro", + "plaintextPlaceholder": "Digita o incolla il messaggio da cifrare…", + "bundleInputLabel": "Bundle cifrato (JSON)", + "bundlePlaceholder": "Incolla il JSON da Cifra (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Bundle cifrato", + "plaintextOutputLabel": "Testo in chiaro", + "encryptButton": "Cifra", + "decryptButton": "Decifra", + "copyBundle": "Copia JSON", + "copyPlaintext": "Copia testo in chiaro", + "copied": "Copiato", + "emptyBundleHint": "Il JSON cifrato apparirà qui dopo la cifratura.", + "emptyDecryptHint": "Il testo decifrato apparirà qui.", + "decryptKeyHint": "Usa la chiave grezza se keyDerivation è \"none\"; la passphrase se è PBKDF2-SHA256. Il bundle contiene salt, iterazioni, IV e lunghezza AES.", + "securityNote": "Chiavi e testo in chiaro non lasciano il browser. Per segreti di produzione usa strumenti dedicati.", + "errors": { + "emptyKey": "Inserisci una chiave grezza coerente con la dimensione AES.", + "emptyPassphrase": "Inserisci una passphrase.", + "badKeyMaterial": "Impossibile leggere la chiave come hex o Base64.", + "wrongKeyLength": "La lunghezza della chiave non corrisponde alla dimensione AES (16 / 24 / 32 byte).", + "invalidJson": "JSON non valido.", + "invalidBundle": "Questo JSON non è un bundle di cifratura supportato.", + "decryptFailed": "Decifra fallita — segreto errato, bundle corrotto o parametri non coincidenti.", + "encryptFailed": "Cifratura fallita. Controlla Web Crypto e gli input." + } + }, + "HmacGenerator": { + "title": "Generatore HMAC", + "subtitle": "Per firmare webhook e testare integrazioni API. HMAC-SHA1/256/384/512 in hex o Base64—calcolato localmente nel browser.", + "secretLabel": "Chiave segreta", + "messageLabel": "Messaggio / payload", + "placeholderSecret": "Incolla il secret di firma…", + "placeholderMessage": "Incolla il corpo grezzo o la stringa da autenticare (spesso gli stessi byte esatti del webhook).", + "algorithmLabel": "Algoritmo HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Formato di output", + "formatHex": "Hex (minuscolo)", + "formatBase64": "Base64", + "outputLabel": "Firma", + "copyOutput": "Copia firma", + "copied": "Copiato", + "byteCountHint": "{count, plural, one {# byte} other {# byte}} (UTF-8)", + "emptyHint": "Inserisci una chiave segreta per calcolare l’HMAC.", + "securityNote": "Non condividere mai il secret di firma. Questa pagina non carica dati; tutto viene eseguito nel browser.", + "errors": { + "cryptoFailed": "Impossibile calcolare l’HMAC. Verifica il supporto a Web Crypto e riprova.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Generatore di codice TOTP / 2FA", + "subtitle": "Incolla il segreto Base32 dell’autenticatore e ottieni il codice a sei cifre RFC 6238 corrente (SHA-1, 30 s). Eseguito localmente nel browser—utile per testare login o MFA.", + "secretLabel": "Segreto (Base32)", + "placeholderSecret": "Incolla la chiave segreta (lettere A–Z e cifre 2–7, come mostrato nella configurazione dell’autenticatore)…", + "codeLabel": "Codice attuale", + "copyCode": "Copia codice", + "copied": "Copiato", + "nextIn": "Si aggiorna tra {seconds} s", + "standardHint": "Passo temporale 30 s, 6 cifre, HMAC-SHA-1 (compatibile con Google Authenticator).", + "digitsLabel": "Cifre", + "periodLabel": "Periodo", + "algorithmLabel": "Algoritmo", + "periodSeconds": "{seconds} s", + "emptyHint": "Incolla un segreto per generare un codice a sei cifre.", + "invalidHint": "Correggi il segreto per vedere il codice.", + "securityNote": "Trattalo come una password. Il segreto non lascia il browser; nulla viene caricato.", + "importLabel": "Importa da un URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importa", + "qrSectionTitle": "Codice QR di provisioning", + "qrHint": "Scansiona con un'app di autenticazione (Google Authenticator, 1Password, Aegis…) per aggiungere questo segreto. Il codice QR codifica il segreto — trattalo come una password.", + "accountLabel": "Account", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Emittente (opzionale)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Inserisci un segreto valido per generare un codice QR.", + "errors": { + "invalidSecret": "Non è un segreto Base32 valido.", + "cryptoFailed": "Impossibile calcolare il TOTP. Verifica Web Crypto e riprova.", + "invalidUri": "Questo non è un URI otpauth:// TOTP valido.", + "unsupportedUri": "Questo URI utilizza valori non supportati dallo strumento (le cifre devono essere 6 o 8; il periodo 15, 30 o 60 s)." + } + }, + "MarkdownPreview": { + "title": "Anteprima Markdown & HTML", + "subtitle": "Renderer Markdown in tempo reale con esportazione HTML e conversione HTML → Markdown. Tutto gira nel tuo browser.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Input Markdown", + "htmlInputLabel": "Input HTML", + "previewLabel": "Anteprima", + "htmlOutputLabel": "Sorgente HTML", + "mdOutputLabel": "Output Markdown", + "inputPlaceholder": "# Ciao\\n\\nDigita il tuo **markdown** qui…", + "htmlInputPlaceholder": "Incolla il tuo HTML qui…", + "copyHtml": "Copia HTML", + "copyMarkdown": "Copia Markdown", + "exportHtml": "Esporta HTML", + "copied": "Copiato", + "charCount": "{count, plural, one {# carattere} other {# caratteri}}", + "emptyHint": "Inizia a digitare Markdown per vedere l'anteprima live e l'output HTML.", + "emptyHintHtmlToMd": "Incolla HTML per convertirlo in Markdown.", + "securityNote": "Tutto viene eseguito localmente nel tuo browser. Nulla viene caricato." + }, + "NumberBaseConverter": { + "title": "Convertitore di base numerica", + "subtitle": "Converti interi tra basi 2–36. Prefissi opzionali 0x / 0b / 0o. Tutto viene calcolato localmente nel browser.", + "inputBaseLabel": "Da base", + "outputBaseLabel": "A base", + "inputLabel": "Numero in ingresso", + "placeholderInput": "Inserisci un intero nella base di ingresso selezionata…", + "outputLabel": "Valore convertito", + "copyOutput": "Copia risultato", + "copied": "Copiato", + "swap": "Scambia basi", + "byteCountHint": "{count, plural, one {# byte} other {# byte}} (lunghezza UTF-8)", + "emptyHint": "Inserisci un numero da convertire.", + "note": "Le cifre oltre il 9 usano le lettere A–Z (maiuscole/minuscole indifferenti). Spazi e underscore ignorati.", + "errors": { + "invalidNumber": "Valore non valido per la base di ingresso selezionata." + } + }, + "FormatConverter": { + "title": "Convertitore di formati", + "subtitle": "Converti tra JSON, YAML, TOML e XML in qualsiasi direzione. Tutte e 12 le combinazioni. Tutto funziona nel tuo browser.", + "fromLabel": "Da", + "toLabel": "A", + "swap": "Scambia", + "swapTitle": "Scambia i formati e usa l'output come input", + "copy": "Copia", + "copied": "Copiato", + "charCount": "{count, plural, one {# carattere} other {# caratteri}}", + "emptyHint": "Incolla o digita il tuo input a sinistra per vedere il risultato convertito.", + "localNote": "Tutto funziona localmente nel tuo browser. Nulla viene caricato." + }, + "MimeTypeLookup": { + "title": "Ricerca tipo MIME", + "subtitle": "Cerca il tipo MIME (media type) per un'estensione o un nome file completo. Tutto funziona localmente nel tuo browser.", + "inputLabel": "Estensione o nome file", + "placeholder": "es. .png, package.json, photo.jpeg", + "hint": "Suggerimento: incolla un'estensione (con o senza punto) oppure un nome file completo.", + "clear": "Cancella", + "exampleFilename": "Usa un nome file di esempio", + "exampleExtension": "Usa un'estensione di esempio", + "resultLabel": "Risultato", + "normalizedLabel": "Input normalizzato:", + "mimeTypeLabel": "Tipo MIME", + "contentTypeLabel": "Header Content-Type", + "charsetLabel": "Set di caratteri", + "copyMimeType": "Copia tipo MIME", + "copy": "Copia", + "copied": "Copiato", + "emptyHint": "Inserisci un'estensione o un nome file per cercare il tipo MIME.", + "notFound": "Nessun tipo MIME trovato per questo input.", + "localNote": "Tutto funziona localmente nel tuo browser. Nulla viene caricato." + }, + "UserAgentParser": { + "title": "Parser User-Agent", + "subtitle": "Incolla una stringa User-Agent (UA) e ottieni un riepilogo di browser, OS, engine, dispositivo e CPU. Tutto funziona localmente nel tuo browser.", + "inputLabel": "Stringa User-Agent", + "placeholder": "Incolla qui il tuo User-Agent…", + "hint": "Suggerimento: puoi copiare un UA dagli header della richiesta, dai log o dai devtools del browser.", + "clear": "Cancella", + "exampleDesktop": "Usa esempio desktop", + "exampleMobile": "Usa esempio mobile", + "resultLabel": "Riepilogo", + "browserLabel": "Browser", + "osLabel": "Sistema operativo", + "engineLabel": "Engine", + "deviceTypeLabel": "Tipo di dispositivo", + "deviceVendorLabel": "Produttore", + "deviceModelLabel": "Modello", + "cpuArchLabel": "Architettura CPU", + "copyJson": "Copia JSON", + "copy": "Copia", + "copied": "Copiato", + "emptyHint": "Incolla una stringa User-Agent per vedere il riepilogo.", + "localNote": "Tutto funziona localmente nel tuo browser. Nulla viene caricato." + }, + "MockDataGenerator": { + "title": "Generatore di dati mock", + "subtitle": "Definisci colonne, scegli i tipi ed esporta dataset per seed, Postman o test. Fino a {max} righe, tutto nel browser: nulla viene caricato.", + "mockarooLink": "Flusso in stile Mockaroo", + "rowsLabel": "Righe", + "rowsHint": "Massimo {max} righe per generazione per mantenere la scheda reattiva.", + "formatLabel": "Formato", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nome tabella SQL", + "presetsLabel": "Modelli", + "presetPlaceholder": "Carica modello…", + "presetDefault": "Avvio (utente)", + "presetUsers": "Utenti e ruoli", + "presetProducts": "SKU e-commerce", + "presetApiLog": "Log richieste HTTP", + "generate": "Genera", + "warningDuplicateNames": "Nomi colonna duplicati: le colonne successive sovrascrivono le precedenti negli oggetti JSON. Rinomina.", + "schemaHeading": "Schema", + "addField": "Aggiungi colonna", + "removeField": "Rimuovi colonna", + "duplicateField": "Duplica colonna", + "colName": "Nome", + "colType": "Tipo", + "colOptions": "Opzioni", + "colBlank": "Vuoto %", + "optMin": "min", + "optMax": "max", + "optDecimals": "dec", + "optDateFrom": "da data", + "optDateTo": "a data", + "optValues": "a, b, c", + "optStep": "passo", + "optRowBase": "base", + "output": "Output", + "outputPlaceholder": "Fai clic su Genera per creare i dati…", + "copy": "Copia", + "download": "Scarica", + "copied": "Copiato", + "groups": { + "personal": "Personale", + "location": "Posizione", + "internetApi": "Internet e API", + "numbersIds": "Numeri e ID", + "dateTime": "Data e ora", + "textDesign": "Testo e design", + "custom": "Personalizzato" + }, + "fieldTypes": { + "first_name": "Nome", + "last_name": "Cognome", + "full_name": "Nome completo", + "email": "Email", + "username": "Nome utente", + "password": "Password", + "phone": "Telefono", + "company": "Azienda", + "job_title": "Ruolo", + "avatar_url": "URL avatar", + "city": "Città", + "country": "Paese", + "country_code": "Codice paese (ISO)", + "street_address": "Indirizzo", + "zip_code": "CAP", + "latitude": "Latitudine", + "longitude": "Longitudine", + "url": "URL", + "domain": "Dominio", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Intero", + "float": "Decimale", + "boolean": "Booleano", + "uuid": "UUID", + "credit_card": "Carta (test)", + "semver": "SemVer", + "row_index": "Indice riga", + "sequence": "Sequenza", + "date": "Data", + "datetime": "Data e ora", + "unix_timestamp": "Timestamp Unix", + "timezone": "Fuso orario", + "word": "Parola", + "sentence": "Frase", + "paragraph": "Paragrafo", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Codice valuta", + "language_code": "Tag lingua", + "custom_list": "Elenco personalizzato" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Ottimizzatore SVG", + "subtitle": "Incolla SVG, minifica con SVGO nel browser e confronta la dimensione UTF-8 prima e dopo.", + "inputLabel": "Input", + "outputLabel": "Ottimizzato", + "placeholder": "Incolla un frammento o il file intero…", + "outputPlaceholder": "L’SVG ottimizzato compare qui…", + "chars": "{count} caratteri • {bytes} byte (UTF-8)", + "preview": "Anteprima", + "localNote": "Elaborazione locale nel browser. Nessun caricamento.", + "stats": { + "title": "Dimensione (UTF-8)", + "before": "Prima", + "after": "Dopo", + "savedBytes": "{count} byte risparmiati ({percent}%)", + "noChange": "Nessuna riduzione — già minimo o output vuoto." + }, + "actions": { + "clear": "Cancella", + "sample": "Carica esempio", + "copy": "Copia", + "copied": "Copiato", + "download": "Scarica" + }, + "errors": { + "optimizeFailed": "Impossibile ottimizzare questo SVG." + } + }, + "UnitConverter": { + "title": "Convertitore di unità", + "subtitle": "Converti tra 323 unità in 43 categorie", + "category": "Categoria", + "from": "Da", + "to": "A", + "value": "Valore", + "result": "Risultato", + "swapUnits": "Scambia unità", + "placeholder": "Inserisci un valore", + "copyResult": "Copia risultato", + "searchCategory": "Cerca categoria...", + "selectCategory": "Seleziona una categoria", + "noResults": "Nessuna categoria trovata." + }, + "DockerComposeGenerator": { + "title": "Generatore Docker Compose", + "subtitle": "Seleziona database, cache, code di messaggi, reverse proxy e osservabilità — copia un docker-compose.yml pronto per lo sviluppo locale.", + "searchPlaceholder": "Cerca (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Servizi", + "selectedCount": "{count, plural, one {# servizio} other {# servizi}}", + "outputTitle": "docker-compose.yml", + "copy": "Copia", + "copied": "Copiato!", + "download": "Scarica", + "emptyState": "Seleziona uno o più servizi per generare lo YAML Compose.", + "hintPorts": "Le porte host predefinite riduono i conflitti; modifiale se Docker segnala un bind in uso. Sostituisci le password demo prima di ambienti condivisi.", + "categories": { + "databases": "Database e archivi", + "cache": "Cache e in-memory", + "queues": "Code e messaggistica", + "search": "Ricerca e log", + "storage": "Object storage", + "proxy": "Reverse proxy e ingress", + "observability": "Metriche e tracing", + "mail": "E-mail (sviluppo locale)", + "admin": "Interfacce admin e BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Cancella" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (compatibile Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (compatibile Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (compatibile S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (cattura SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (server di sviluppo)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Fai scorrere le tessere e uniscile per raggiungere il 2048", + "score": "Punteggio", + "best": "Migliore", + "newGame": "Nuova partita", + "howToPlay": "Usa i tasti freccia (o scorri sul cellulare) per spostare le tessere. Le tessere con lo stesso numero si uniscono. Raggiungi il 2048 per vincere!", + "youWin": "Hai raggiunto il 2048!", + "keepGoing": "Continua pure", + "gameOver": "Fine partita", + "tryAgain": "Riprova", + "gameOverMessage": "NON CI SONO PIÙ MOSSE", + "undo": "Annulla" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "fasi completate", + "time": "Ora", + "best": "Migliore", + "difficulty": { + "easy": "Facile", + "medium": "Medio", + "hard": "Difficile" + }, + "stage": "Scena", + "solved": "Risolto", + "score": "Punteggio", + "nextStage": "Fase successiva", + "replay": "Ripeti (Replay)", + "erase": "Cancella", + "howToPlay": "Fare clic su una cella, quindi toccare un numero o premere 1–9. Backspace o Delete cancella la cella. I numeri in grassetto sono indizi. Riempi la griglia in modo che ogni riga, colonna e casella 3×3 contenga 1–9 senza ripetizioni.", + "pencil": "Matita", + "undo": "Annulla", + "hint": "Suggerimento", + "hintsUsed": "suggerimenti usati", + "mistakes": "Errori", + "failed": "Fallito!", + "mistakeMode": "Modalità 3 errori" + }, + "Snake": { + "title": "Snake", + "subtitle": "Mangia, cresci, evita muri e te stesso", + "score": "Punteggio", + "best": "Migliore", + "startPrompt": "Premi un tasto freccia", + "startButton": "Inizia gioco", + "gameOver": "Game Over", + "playAgain": "Gioca ancora", + "howToPlay": "Usa i tasti freccia o WASD. Mangia per crescere. La velocità aumenta ogni 50 punti.", + "diffEasy": "Facile", + "diffNormal": "Normale", + "diffHard": "Difficile", + "diffInfinite": "Infinito", + "orTap": "o tocca sotto", + "howToPlayInfinite": "I muri si avvolgono — nessuna morte per i muri. Mangia per crescere.", + "shieldActive": "Scudo attivo", + "speedBoost": "Boost velocità", + "powerSpeed": "Velocità x2", + "powerShield": "Sopravvivi a una morte", + "powerShrink": "Accorcia la coda" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Connessioni Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Connettiti alle istanze Redis. Sfoglia le chiavi, modifica i valori ed esegui comandi. Le credenziali sono crittografate nel browser.", + "heroAddConnection": "Aggiungi connessione", + "vaultLockedTitle": "Vault bloccato", + "vaultLockedDesc": "Sblocca il vault per accedere alle connessioni salvate." + } + }, + "Minesweeper": { + "title": "Campo minato", + "subtitle": "Trova tutte le celle sicure senza far esplodere una mina", + "mines": "Mine", + "newGame": "Nuova partita", + "win": "Hai vinto!", + "loss": "Hai perso!", + "playAgain": "Gioca ancora", + "tryAgain": "Riprova", + "startPrompt": "Clicca su una cella per iniziare", + "difficulty": { + "beginner": "Principiante", + "intermediate": "Intermedio", + "expert": "Esperto" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Organizza i blocchi in caduta per completare le righe", + "next": "Prossimo", + "score": "Punteggio", + "best": "Migliore", + "lines": "Righe", + "level": "Livello", + "pause": "Pausa", + "resume": "Riprendi", + "start": "Avvia", + "restart": "Ricomincia", + "paused": "In pausa", + "gameOver": "Fine partita" + }, + "SshKeyGenerator": { + "title": "Generatore di chiavi SSH / RSA", + "subtitle": "Genera coppie di chiavi Ed25519 o RSA nel tuo browser. Scarica le chiavi privata e pubblica nei formati OpenSSH e PEM. Nulla viene caricato.", + "keyTypeLabel": "Tipo di chiave", + "recommended": "consigliato", + "ed25519Desc": "Veloce, compatto e moderno. Supportato da GitHub, GitLab e tutti i principali server SSH.", + "rsa2048Desc": "Ampiamente compatibile. Sicuro per la maggior parte degli usi.", + "rsa4096Desc": "Massima compatibilità e robustezza per i sistemi legacy.", + "commentLabel": "Commento (opzionale)", + "commentHint": "Aggiunto alla riga della chiave pubblica OpenSSH.", + "generateBtn": "Genera coppia di chiavi", + "generating": "Generazione…", + "generatingLong": "Generazione (potrebbe richiedere un momento)…", + "copy": "Copia", + "download": "Scarica", + "privateKeyLabel": "Chiave privata (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Chiave pubblica (formato OpenSSH)", + "publicKeySpkiLabel": "Chiave pubblica (SPKI PEM)", + "browserNote": "Tutte le chiavi vengono generate nel tuo browser. Nulla viene inviato ad alcun server.", + "errors": { + "generationFailed": "Generazione della chiave fallita" + } + }, + "DnsLookup": { + "title": "Ricerca DNS", + "subtitle": "Interroga i record DNS per qualsiasi dominio — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Inserisci un dominio (es. github.com)", + "lookupBtn": "Cerca", + "looking": "Ricerca in corso…", + "refreshBtn": "Aggiorna", + "recordTypesLabel": "Tipi di record", + "allBtn": "Tutti", + "tryLabel": "Prova:", + "errors": { + "lookupFailed": "Ricerca DNS non riuscita" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Generatore e verificatore Bcrypt", + "subtitle": "Genera hash di password con bcrypt e verifica gli hash — tutto resta nel tuo browser", + "tabs": { + "hash": "Hash", + "verify": "Verifica" + }, + "showPassword": "Mostra password", + "hidePassword": "Nascondi password", + "copy": "Copia", + "copied": "Copiato", + "hash": { + "passwordLabel": "Password", + "passwordPlaceholder": "Inserisci una password da hashare…", + "costLabel": "Fattore di costo", + "costHint": "Un costo più alto raddoppia il lavoro per passaggio. 10-12 è una scelta comune in produzione.", + "slowWarning": "I fattori di costo pari o superiori a 13 possono richiedere diversi secondi per il calcolo.", + "generate": "Genera hash", + "generating": "Hashing…", + "resultLabel": "Hash bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomia dell'hash", + "algorithm": "algoritmo", + "cost": "costo", + "salt": "salt (22 caratteri)", + "hash": "hash (31 caratteri)" + } + }, + "verify": { + "passwordLabel": "Password", + "passwordPlaceholder": "Inserisci la password da verificare…", + "hashLabel": "Hash bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Inserisci una password e un hash bcrypt per confrontarli.", + "invalidHash": "Questo non sembra un hash bcrypt valido.", + "checking": "Confronto in corso…", + "match": "Corrispondenza — la password produce questo hash.", + "noMatch": "Nessuna corrispondenza — la password non produce questo hash." + } + }, + "ChmodCalculator": { + "title": "Calcolatore Chmod", + "subtitle": "Converti i permessi dei file Unix tra caselle di controllo, ottale e notazione simbolica", + "grid": { + "title": "Permessi", + "read": "Lettura", + "write": "Scrittura", + "execute": "Esecuzione", + "owner": "Proprietario", + "group": "Gruppo", + "others": "Altri" + }, + "special": { + "title": "Bit speciali", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Ottale", + "placeholder": "755 o 4755", + "invalid": "Inserisci 3 o 4 cifre ottali (0-7)." + }, + "symbolic": { + "label": "Simbolico", + "placeholder": "rwxr-xr-x", + "invalid": "Inserisci 9 caratteri come rwxr-xr-x (sono consentiti s/S, t/T)." + }, + "output": { + "title": "Comando", + "copy": "Copia", + "copied": "Copiato" + }, + "explain": { + "line": "{cls} può {perms}.", + "none": "{cls} non ha permessi.", + "setuid": "Setuid: il file viene eseguito con i privilegi del proprietario.", + "setgid": "Setgid: il file viene eseguito con i privilegi del suo gruppo (i nuovi file in una directory ne ereditano il gruppo).", + "sticky": "Sticky: solo il proprietario di un file può eliminarlo o rinominarlo all'interno di questa directory." + }, + "presets": { + "title": "Preimpostazioni comuni", + "octal": "Ottale", + "permissions": "Permessi", + "description": "Uso tipico", + "p644": "File normali: il proprietario scrive, tutti leggono.", + "p755": "Eseguibili e directory: tutti possono leggere ed entrare.", + "p600": "File privati: solo il proprietario legge e scrive.", + "p700": "Eseguibili e directory privati: solo il proprietario.", + "p664": "File scrivibili dal gruppo.", + "p775": "Directory ed eseguibili scrivibili dal gruppo.", + "p400": "Sola lettura per il proprietario (es. chiavi private SSH)." + } + }, + "CodeScreenshot": { + "title": "Screenshot di codice", + "subtitle": "Trasforma il codice in immagini eleganti e condivisibili", + "languageLabel": "Linguaggio", + "autoDetect": "Rilevamento automatico", + "backgroundLabel": "Sfondo", + "paddingLabel": "Spaziatura", + "filenameLabel": "Nome del file", + "filenamePlaceholder": "example.ts", + "windowLabel": "Puntini della finestra", + "themeLabel": "Tema della finestra", + "themeDark": "Scuro", + "themeLight": "Chiaro", + "scaleLabel": "Scala di esportazione", + "copyImage": "Copia immagine", + "copied": "Copiato", + "exportPng": "Esporta PNG", + "inputLabel": "Codice", + "detected": "Rilevato: {language}", + "clear": "Pulisci", + "placeholder": "Incolla qui il tuo codice…", + "previewLabel": "Anteprima", + "emptyPreview": "L'anteprima del tuo codice apparirà qui", + "exportFailed": "Esportazione dell'immagine non riuscita", + "copyUnsupported": "La copia delle immagini non è supportata in questo browser", + "copyFailed": "Copia dell'immagine non riuscita", + "backgrounds": { + "ocean": "Oceano", + "sunset": "Tramonto", + "candy": "Caramella", + "forest": "Foresta", + "midnight": "Mezzanotte", + "gold": "Oro", + "aurora": "Aurora", + "slate": "Ardesia", + "transparent": "Trasparente" + } + }, + "CssGenerators": { + "title": "Generatori CSS", + "subtitle": "Box-shadow, border-radius e clamp() fluido — copia il CSS.", + "preview": "Anteprima", + "output": "CSS", + "copy": "Copia", + "copied": "Copiato", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() fluido" + }, + "boxShadow": { + "presets": "Preimpostazioni", + "layer": "Livello", + "addLayer": "Aggiungi livello", + "removeLayer": "Rimuovi livello", + "x": "X", + "y": "Y", + "blur": "Sfocatura", + "spread": "Estensione", + "color": "Colore", + "alpha": "Alfa", + "inset": "Interno", + "presetNames": { + "subtleCard": "Scheda delicata", + "elevated": "Rialzato", + "hardOffset": "Offset netto", + "neonGlow": "Bagliore neon", + "inner": "Interno", + "layeredSmooth": "Stratificato morbido" + } + }, + "borderRadius": { + "modeLabel": "Modalità", + "modes": { + "simple": "Per angolo", + "blob": "Blob organico" + }, + "unit": "Unità", + "linkCorners": "Collega gli angoli", + "randomize": "Casuale", + "horizontal": "Raggi orizzontali", + "vertical": "Raggi verticali", + "corners": { + "tl": "In alto a sinistra", + "tr": "In alto a destra", + "br": "In basso a destra", + "bl": "In basso a sinistra" + } + }, + "clamp": { + "minFont": "Font min (px)", + "maxFont": "Font max (px)", + "minViewport": "Viewport min (px)", + "maxViewport": "Viewport max (px)", + "rootFont": "Font radice (px)", + "viewportWidth": "Viewport simulato", + "computedSize": "Dimensione calcolata", + "slope": "Pendenza", + "intercept": "Intercetta", + "previewText": "Ma la volpe col suo balzo", + "invalid": "Inserisci limiti di viewport validi e diversi tra loro." + } + }, + "ExifViewer": { + "title": "Visualizzatore e rimozione EXIF", + "subtitle": "Ispeziona i metadati delle foto e rimuovili nel tuo browser", + "dropTitle": "Trascina qui un'immagine", + "dropHint": "oppure clicca per sfogliare — la tua immagine non lascia mai il tuo dispositivo", + "browse": "Scegli immagine", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Lettura dei metadati…", + "parseFailed": "Impossibile leggere questa immagine", + "clear": "Rimuovi immagine", + "gpsWarning": "Contiene posizione GPS", + "openMap": "Visualizza su OpenStreetMap", + "noMetadata": "Nessun metadato incorporato trovato in questa immagine.", + "facts": { + "name": "Nome", + "type": "Tipo", + "dimensions": "Dimensioni", + "size": "Peso" + }, + "groups": { + "camera": "Fotocamera", + "exposure": "Esposizione", + "timestamps": "Timestamp", + "gps": "Posizione GPS", + "software": "Software e altro" + }, + "gpsCard": { + "latitude": "Latitudine", + "longitude": "Longitudine", + "altitude": "Altitudine" + }, + "raw": { + "title": "Tutti i metadati", + "copyJson": "Copia JSON", + "copied": "Copiato" + }, + "remove": { + "title": "Rimuovi metadati", + "jpegDescription": "Rimuovi senza perdita EXIF, XMP, IPTC e commenti senza ricomprimere l'immagine.", + "reencodeDescription": "Ricodifica l'immagine per rimuovere i metadati incorporati. Questo ricomprime i pixel.", + "keepIcc": "Mantieni il profilo colore (ICC)", + "stripButton": "Rimuovi metadati", + "reencodeButton": "Ricodifica e rimuovi", + "unsupported": "La rimozione dei metadati non è supportata per questo tipo di file.", + "failed": "Impossibile rimuovere i metadati", + "before": "Prima", + "after": "Dopo", + "download": "Scarica immagine ripulita" + } + }, + "FaviconGenerator": { + "title": "Generatore di favicon", + "subtitle": "Crea favicon e icone dell'app da una singola immagine", + "dropTitle": "Trascina qui un'immagine", + "dropHint": "PNG, JPEG o SVG — clicca per sfogliare", + "browse": "Scegli immagine", + "clear": "Rimuovi immagine", + "backgroundLabel": "Sfondo", + "transparent": "Trasparente", + "solidColor": "Tinta unita", + "cornerLabel": "Forma degli angoli", + "cornerNone": "Quadrato", + "cornerRounded": "Arrotondato", + "cornerCircle": "Cerchio", + "maskPaddingLabel": "Spaziatura maskable", + "maskable": "maskable", + "previewLabel": "Anteprima", + "copy": "Copia", + "headSnippet": "Snippet per l'head HTML", + "downloadIco": "Scarica favicon.ico", + "downloadZip": "Scarica tutto (.zip)" + }, + "JsonDiff": { + "title": "Diff JSON", + "subtitle": "Confronto semantico e consapevole della struttura tra due documenti JSON — ordine delle chiavi e formattazione ignorati", + "options": { + "ignoreArrayOrder": "Ignora l'ordine degli array" + }, + "loadBothSamples": "Carica esempi", + "sample": "Carica esempio", + "format": "Formatta", + "clear": "Pulisci", + "formatError": "Impossibile formattare: l'input non è un JSON valido", + "placeholder": "Incolla o digita qui il JSON…", + "panels": { + "left": "Originale (sinistra)", + "right": "Modificato (destra)", + "results": "Differenze" + }, + "summary": { + "added": "{count} aggiunti", + "removed": "{count} rimossi", + "changed": "{count} modificati" + }, + "types": { + "added": "Aggiunto", + "removed": "Rimosso", + "changed": "Modificato" + }, + "copyChanges": "Copia come JSON", + "copied": "Copiato", + "empty": "Incolla il JSON in entrambi i pannelli per vederne le differenze.", + "identical": "I due documenti sono semanticamente identici." + }, + "JsonpathPlayground": { + "title": "Playground JSONPath", + "subtitle": "Interroga documenti JSON con JSONPath o JMESPath e vedi i risultati in tempo reale", + "engineLabel": "Motore", + "queryLabel": "Query", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Prontuario", + "cheats": { + "jpAllTitles": "Tutti i titoli dei libri", + "jpAllAuthors": "Ogni autore, ovunque nel documento", + "jpFirstBook": "Primo libro", + "jpLastBook": "Ultimo libro", + "jpSlice": "I primi due libri (slice)", + "jpFilterPrice": "Libri sotto i 40", + "jpFilterInStock": "Titoli dei libri disponibili", + "jpRecursive": "Tutti i valori, ricorsivamente", + "jmAllTitles": "Tutti i titoli dei libri", + "jmFirstAuthor": "Autore del primo libro", + "jmLastBook": "Ultimo libro", + "jmSlice": "I primi due libri (slice)", + "jmFilterPrice": "Titoli dei libri sotto i 40", + "jmCount": "Conta i libri disponibili", + "jmProjection": "Rimodella ogni libro in un nuovo oggetto", + "jmSort": "Titoli ordinati per prezzo" + }, + "panels": { + "input": "Input JSON", + "results": "Risultati" + }, + "sample": "Carica esempio", + "format": "Formatta", + "clear": "Pulisci", + "formatError": "Impossibile formattare: l'input non è un JSON valido", + "placeholder": "Incolla o digita qui il JSON…", + "outputPlaceholder": "Le corrispondenze appariranno qui…", + "matchCount": "{count, plural, one {# corrispondenza} other {# corrispondenze}}", + "copy": "Copia", + "copied": "Copiato" + }, + "KeycodeInspector": { + "title": "Ispettore di keycode", + "subtitle": "Premi un tasto qualsiasi per ispezionare il suo evento da tastiera JavaScript", + "capture": { + "prompt": "Premi un tasto qualsiasi…", + "ready": "In ascolto — premi un tasto qualsiasi sulla tastiera", + "clickToFocus": "Clicca qui per mettere a fuoco, poi premi un tasto qualsiasi" + }, + "hero": { + "spaceKey": "(Spazio)", + "keyLabel": "event.key", + "repeat": "ripetizione" + }, + "copyJson": "Copia JSON", + "copiedJson": "JSON dell'evento copiato negli appunti", + "deprecated": "deprecato", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standard", + "left": "Sinistra", + "right": "Destra", + "numpad": "Tastierino numerico", + "unknown": "Sconosciuto" + }, + "history": { + "title": "Cronologia", + "clear": "Cancella cronologia", + "empty": "Le pressioni dei tasti appariranno qui." + } + }, + "MarkdownTableGenerator": { + "title": "Generatore di tabelle Markdown", + "subtitle": "Modifica una griglia, incolla da un foglio di calcolo e copia una tabella Markdown o HTML ben allineata", + "addRow": "Aggiungi riga", + "addColumn": "Aggiungi colonna", + "importLabel": "Importa", + "importPlaceholder": "Incolla qui TSV (da Excel / Google Sheets) o CSV…", + "importButton": "Importa nella griglia", + "pasteClipboard": "Incolla dagli appunti", + "importEmpty": "Niente da importare — incolla prima alcune righe", + "clipboardError": "Impossibile leggere dagli appunti", + "clear": "Pulisci", + "panels": { + "editor": "Editor di tabelle", + "output": "Output" + }, + "align": { + "left": "Allinea a sinistra", + "center": "Centra", + "right": "Allinea a destra" + }, + "headerPlaceholder": "Intestazione", + "cellPlaceholder": "Cella", + "removeRow": "Rimuovi riga", + "removeColumn": "Rimuovi colonna", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Copia", + "copied": "Copiato" + }, + "TimezoneConverter": { + "title": "Convertitore di fusi orari", + "subtitle": "Converti un singolo momento in tutto il mondo per pianificare le riunioni.", + "dateTimeLabel": "Data e ora", + "now": "Adesso", + "sourceZoneLabel": "Fuso orario di origine", + "selectZone": "Seleziona fuso", + "searchZone": "Cerca fusi orari...", + "noZone": "Nessun fuso orario trovato.", + "scrubLabel": "Sposta l'ora", + "reset": "Reimposta", + "targetsLabel": "Fusi di destinazione", + "addZone": "Aggiungi fuso", + "remove": "Rimuovi", + "localBadge": "Locale", + "invalidDate": "Inserisci una data e un'ora valide.", + "bands": { + "work": "Orario lavorativo (09:00–18:00)", + "edge": "Orari limite (presto / sera)", + "night": "Notte" + } + }, + "TokenCounter": { + "title": "Contatore di token LLM", + "subtitle": "Conta i token, visualizza i chunk e stima il costo dell'API.", + "inputLabel": "Testo", + "sample": "Carica esempio", + "clear": "Pulisci", + "placeholder": "Incolla testo, codice o JSON per contare i token...", + "encoding": "Codifica", + "loading": "Caricamento del tokenizer...", + "encodings": { + "o200k": "o200k_base (GPT-4o / serie o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Token", + "characters": "Caratteri", + "words": "Parole", + "ratio": "Token/parola", + "ratioSub": "circa" + }, + "claudeRow": "Claude (approssimativo)", + "estimate": "stima", + "viz": { + "title": "Visualizzazione dei token", + "toggle": "Mostra", + "hint": "Attiva per colorare ogni token come un chunk.", + "empty": "Inserisci del testo per visualizzare i token.", + "truncated": "Mostra i primi {limit} di {total} token." + }, + "cost": { + "title": "Calcolatore di costi", + "disclaimer": "valori predefiniti modificabili — verifica i prezzi attuali", + "model": "Modello", + "expectedOutput": "Token di output previsti", + "customInput": "Input $/MTok", + "customOutput": "Output $/MTok", + "inputCost": "Costo di input", + "outputCost": "Costo di output", + "total": "Totale", + "per1000": "Ogni 1000 chiamate" + } + }, + "WebhookTester": { + "title": "Tester di webhook", + "subtitle": "Crea un URL temporaneo e ispeziona le richieste webhook in arrivo dal vivo.", + "intro": { + "title": "Crea un URL webhook per iniziare a catturare le richieste", + "hint": "Otterrai un URL non indovinabile che registra ogni richiesta in arrivo. Scade dopo 24 ore." + }, + "createBtn": "Crea URL webhook", + "creating": "Creazione…", + "urlLabel": "Il tuo URL webhook", + "expiresIn": "Scade tra {countdown}", + "copyUrlBtn": "Copia URL", + "copied": "Copiato", + "clearBtn": "Pulisci", + "newBinBtn": "Nuovo URL", + "requestsTitle": "Richieste", + "selectPrompt": "Seleziona una richiesta per ispezionarne i dettagli.", + "expiredNotice": "Questo URL webhook è scaduto. Creane uno nuovo per continuare a testare.", + "base64Badge": "base64", + "truncatedBadge": "troncato", + "empty": { + "title": "In attesa di richieste…", + "hint": "Invia una richiesta al tuo URL e apparirà qui all'istante. Prova così:" + }, + "detail": { + "receivedAt": "Ricevuto", + "source": "IP di origine", + "contentType": "Content-Type", + "query": "Parametri query", + "headers": "Intestazioni", + "body": "Corpo", + "copyBody": "Copia corpo", + "emptyBody": "Nessun corpo" + }, + "errors": { + "createFailed": "Impossibile creare l'URL webhook. Riprova.", + "clearFailed": "Impossibile cancellare le richieste. Riprova." + } + }, + "WebsocketTester": { + "title": "Tester WebSocket", + "subtitle": "Connettiti a un endpoint WebSocket e invia, ricevi e ispeziona messaggi dal vivo", + "urlLabel": "URL dell'endpoint", + "protocolsLabel": "Sottoprotocolli", + "protocolsPlaceholder": "opzionale, separati da virgola", + "connect": "Connetti", + "disconnect": "Disconnetti", + "status": { + "disconnected": "Disconnesso", + "connecting": "Connessione", + "connected": "Connesso", + "error": "Errore" + }, + "messages": { + "connecting": "Connessione a {url}…", + "connected": "Connessione stabilita", + "connectedWithProtocol": "Connessione stabilita (protocollo: {protocol})", + "invalidUrl": "URL non valido — deve iniziare con ws:// o wss://", + "connectFailed": "Impossibile aprire la connessione", + "socketError": "Si è verificato un errore di connessione", + "notConnected": "Non connesso — connettiti prima", + "binaryMessage": "Ricevuto un messaggio binario" + }, + "log": { + "title": "Registro dei messaggi", + "empty": "Ancora nessun messaggio. Connettiti e inizia a inviare.", + "copyAll": "Copia tutto", + "download": "Scarica come .txt", + "clear": "Cancella registro", + "prettyPrint": "Formatta JSON", + "showRaw": "Mostra testo grezzo" + }, + "composer": { + "placeholder": "Digita un messaggio da inviare…", + "send": "Invia", + "formatJson": "Formatta come JSON", + "invalidJson": "Il messaggio non è un JSON valido", + "hint": "Cmd/Ctrl+Invio per inviare" + }, + "ping": { + "label": "Auto-ping", + "seconds": "sec", + "intervalTitle": "Intervallo di ping in secondi", + "payloadPlaceholder": "payload del ping" + } + }, + "WhoisLookup": { + "title": "Ricerca Whois", + "subtitle": "Cerca i dettagli di registrazione di qualsiasi dominio o indirizzo IP tramite RDAP", + "placeholder": "example.com o 8.8.8.8", + "lookupBtn": "Cerca", + "looking": "Ricerca…", + "refreshBtn": "Aggiorna", + "tryLabel": "Prova:", + "copy": "Copia", + "copied": "Copiato", + "notAvailable": "Non disponibile", + "summary": { + "registrar": "Registrar", + "network": "Rete" + }, + "dates": { + "created": "Creato", + "updated": "Aggiornato", + "expires": "Scade" + }, + "sections": { + "status": "Stato", + "nameservers": "Nameserver", + "contacts": "Contatti", + "rawRdap": "RDAP grezzo" + }, + "contacts": { + "roles": "Ruoli", + "name": "Nome", + "organization": "Organizzazione" + }, + "errors": { + "invalidTarget": "Inserisci un nome di dominio o un indirizzo IP valido", + "lookupFailed": "Ricerca non riuscita (stato {status})" + } + } +} diff --git a/apps/desktop-ui/messages/ja.json b/apps/desktop-ui/messages/ja.json new file mode 100644 index 00000000..ed767a47 --- /dev/null +++ b/apps/desktop-ui/messages/ja.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "ダッシュボード", + "settings": "設定", + "apiClient": "APIクライアント", + "httpStatusCodes": "HTTPステータスコード", + "passwordManager": "パスワードマネージャー", + "environmentManager": "環境変数マネージャー", + "bookmarks": "ブックマーク", + "jsonFormatter": "JSON フォーマッター", + "emailValidator": "メールバリデーター", + "nosqlExplorer": "データベースエクスプローラー", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "暗号化プレイグラウンド", + "jwtDecoder": "JWTデコーダー", + "certificatePemDecoder": "証明書 / PEM デコーダ", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL フォーマッター", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL パーサー", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "シークレット / APIキー", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "コントラストチェッカー", + "jsonSchemaGenerator": "JSONスキーマジェネレーター", + "imageToBase64": "画像をBase64に変換", + "imageCompressor": "画像圧縮", + "cssGradientBuilder": "CSS グラデーション ビルダー", + "gitignoreGenerator": ".gitignore ジェネレーター", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "コードスニペット", + "qrCodeGenerator": "QRコード生成", + "dnsLookup": "DNS ルックアップ", + "ipSubnetCalculator": "IP/サブネット計算", + "hashGenerator": "ハッシュ生成", + "hmacGenerator": "HMACジェネレーター", + "totpGenerator": "TOTP / 2FA コード", + "markdownPreview": "Markdownプレビュー", + "numberBaseConverter": "基数変換", + "formatConverter": "フォーマット変換", + "mockDataGenerator": "モックデータジェネレーター", + "svgOptimizer": "SVG 最適化", + "unitConverter": "単位変換器", + "dockerComposeGenerator": "Docker Compose ジェネレーター", + "breakRoom": "休憩室", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAMLフォーマッター", + "sshKeyGenerator": "SSH / RSAキージェネレーター", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod 計算機", + "codeScreenshot": "コードスクリーンショット", + "cssGenerators": "CSS ジェネレーター", + "exifViewer": "EXIF ビューアー", + "faviconGenerator": "Favicon ジェネレーター", + "jsonDiff": "JSON 差分", + "jsonpathPlayground": "JSONPath プレイグラウンド", + "keycodeInspector": "キーコードインスペクター", + "markdownTableGenerator": "Markdown テーブル", + "timezoneConverter": "タイムゾーン変換", + "tokenCounter": "トークンカウンター", + "webhookTester": "Webhook テスター", + "websocketTester": "WebSocket テスター", + "whoisLookup": "Whois 検索" + }, + "Help": { + "title": "ヘルプとドキュメント", + "subtitle": "MyDevTools の全体像、各アプリの使い方、そしてセキュリティと暗号化がデータをどのように保護するかを説明します。", + "tabs": { + "overview": "概要", + "apps": "アプリ", + "security": "セキュリティ", + "tips": "ヒント" + }, + "overview": { + "whatIs": { + "title": "MyDevTools とは?", + "description": "ログインして使う開発者向けワークスペース。ダッシュボードと日常ツール群を提供します。", + "p1": "ログイン後、アプリは MyDevTools API とセッションを確立し、各ツールがデータを読み書きできるようにします。サイドバーにはすべてのアプリが表示され、使わないツールは", + "settingsLink": "設定", + "p1After": "で非表示にできます。", + "p2": "一部の機能では マスターパスワード(またはグローバルなボールトキー)を使用し、パスワードや接続文字列などの機密情報をサーバーに送る前に端末内で暗号化します。" + }, + "quickLinks": { + "title": "クイックリンク", + "dashboard": "ダッシュボード", + "settings": "設定" + } + }, + "apps": { + "intro": "サイドバーまたはダッシュボードから任意のツールを開けます。以下は各アプリの簡単なガイドです。", + "open": "開く", + "howItWorks": "仕組み", + "generic": "このツールはサイドバーから開いて専用のワークフローで使用してください。" + }, + "security": { + "session": { + "title": "ログインと API セッション", + "description": "認証とブラウザがバックエンドと通信する仕組み。", + "items": { + "authentication": "認証は Firebase を使用します。ログイン後、Firebase の ID トークンを API の Cookie に交換します。", + "accessTokens": "アクセストークンはサーバーで署名された JWT(HS256)で、API を呼び出すユーザーを証明します。", + "refreshTokens": "リフレッシュトークンは HttpOnly Cookie に保存され、サーバー側にはトークンそのものではなく SHA-256 ハッシュが保存されます。" + } + }, + "encryption": { + "title": "知っておくべき暗号化", + "description": "ボールトや機密の接続データのためのクライアント側暗号化。", + "p1": "パスワードマネージャー環境変数マネージャーNoSQL Explorer の保存接続では、ブラウザの Web Crypto API を使用します:", + "items": { + "kdf": "PBKDF2SHA-256100,000 回、ランダム salt)でマスターパスワードから 256-bit キーを導出します。", + "aes": "AES-256-GCM で暗号化し、暗号化ごとにランダムな 12 バイト IV を使用します。", + "scope": "導出キーはブラウザ内に保持され(取り出し不可)、サーバーに送られるのは 暗号文と IV のみです。マスターパスワードや平文の秘密は送信されません。" + }, + "warning": "マスターパスワードを忘れると暗号化されたデータは復元できません。安全に保管してください。" + }, + "logout": { + "title": "ログアウトとローカルデータ", + "body": "ログアウト時にメモリ上の機密情報、ボールト状態、(該当する場合)IndexedDB 上のマスターキー素材を消去し、API セッションを終了します。端末のローカルストレージのみに残るデータは、ブラウザで削除するまで残る場合があります。" + } + }, + "tips": { + "title": "安全に使うためのヒント", + "items": { + "masterPassword": "ボールト機能には強力で一意なマスターパスワードを使用してください。", + "apiClient": "API クライアントは通常の HTTP クライアントと同様に扱い、共有環境では本番のシークレットを貼り付けないようにしてください。", + "nosql": "データベースエクスプローラーはあなたが設定した DB に接続します。可能なら最小権限ユーザーとネットワーク制限を使ってください。", + "json": "JSON フォーマッターはブラウザ内で処理されますが、共有端末では機密情報の貼り付けを避けてください。", + "settings": "設定で表示ツールをカスタマイズしてサイドバーを整理できます。" + } + } + }, + "Settings": { + "title": "設定", + "language": "言語", + "spanish": "スペイン語", + "english": "English", + "french": "Français", + "arabic": "عربى", + "catalan": "カタルーニャ語", + "chineseSimplified": "中国語(簡体字)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "ドイツ語", + "danish": "デンマーク語", + "afrikaans": "アフリカーンス語", + "persian": "فارسی", + "indonesian": "インドネシア語", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "ウクライナ語" + }, + "SettingsPage": { + "title": "設定", + "subtitle": "アカウント設定と環境設定を管理します。", + "userProfile": { + "title": "ユーザープロフィール", + "description": "個人情報とアカウントの詳細です。", + "avatarAlt": "ユーザー", + "anonymousUser": "匿名ユーザー", + "noEmailProvided": "メールアドレス未設定", + "notLoggedIn": "ログインしていません。" + }, + "tools": { + "title": "ツールと機能", + "description": "ナビゲーションサイドバーに表示するツールを選択します。" + }, + "appearance": { + "title": "外観", + "description": "デバイスでのアプリケーションの表示をカスタマイズします。", + "themePreference": "テーマ設定", + "themes": { + "light": "ライト", + "dark": "ダーク", + "system": "システム" + } + }, + "language": { + "title": "言語", + "description": "プラットフォーム全体で使用する言語を設定します。", + "selectLanguage": "言語を選択", + "selectPlaceholder": "言語を選択してください", + "helpText": "言語を変更すると、プラットフォーム全体のメニューやラベルが更新されます。", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español(スペイン語)", + "ar": "عربى(アラビア語)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά(ギリシャ語)", + "de": "Deutsch(ドイツ語)", + "da": "Dansk(デンマーク語)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "イタリア語 (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "おはようございます", + "afternoon": "こんにちは", + "evening": "こんばんは" + }, + "commaName": "、{name}", + "welcomeBack": "おかえりなさい", + "welcomeBackNamed": "おかえりなさい、{name}", + "tagline": "今日は何を作りますか?", + "brandName": "MyDevTools", + "stats": { + "tools": "ツール", + "favorites": "お気に入り", + "pinned": "ピン留め", + "recent": "最近使用したツール" + }, + "sections": { + "recentlyUsed": "最近使用したツール", + "favorites": "お気に入り", + "whatsNew": "新着", + "pinned": "ピン留め" + }, + "groups": { + "apps": "アプリ" + }, + "toolCard": { + "defaultDescription": "このツールを探索してより良い機能を活用しましょう。", + "launchTool": "ツールを起動" + }, + "tools": { + "toDo": { + "title": "タスク", + "description": "ToDoリストを効率的に管理します。" + }, + "notes": { + "title": "ノート", + "description": "アイデアをキャプチャし、考えを整理します。" + }, + "passwordManager": { + "title": "パスワードマネージャー", + "description": "パスワードを安全に保存・管理します。" + }, + "environmentManager": { + "title": "環境変数マネージャー", + "description": "プロジェクトと環境ごとに .env 形式の変数をタグ付きで保存します。" + }, + "bookmarks": { + "title": "ブックマーク", + "description": "ブックマークを管理・整理します。" + }, + "apiClient": { + "title": "APIクライアント", + "description": "HTTPリクエストのテストとデバッグを行います。" + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "MongoDBデータベースを探索します。" + }, + "emailValidator": { + "title": "メールバリデーター", + "description": "メールアドレスの検証と確認を行います。" + }, + "encryptionPlayground": { + "title": "暗号化プレイグラウンド", + "description": "生キーまたはパスフレーズで AES-GCM。JSON バンドル、Web Crypto はブラウザ内のみ。" + }, + "jwtDecoder": { + "title": "JWTデコーダー", + "description": "JWT のヘッダー、ペイロード、有効期限をデコードします。" + }, + "certificatePemDecoder": { + "title": "証明書 / PEM デコーダ", + "description": "X.509 証明書と CSR をデコード: サブジェクト、発行者、有効期間、SAN。" + }, + "sshKeyGenerator": { + "title": "SSH / RSA キー生成器", + "description": "ブラウザで Ed25519 または RSA キーペアを生成。OpenSSH および PEM 形式でダウンロード。" + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL フォーマッター", + "description": "GraphQL を整形し、シンタックスハイライトとシンプルなクエリビルダーを提供します。" + }, + "yamlFormatter": { + "title": "YAMLフォーマッター", + "description": "ブラウザでYAMLを整形・検証・JSON変換します。" + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL パーサー", + "description": "URL をプロトコル、ホスト、パス、クエリパラメータ、ハッシュに分解します。" + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "シークレット / APIキー", + "description": "アルファベット・長さ・一括出力を設定できる暗号論的乱数文字列。" + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "コントラストチェッカー", + "description": "テキストと背景の WCAG 2.1 コントラスト比(AA / AAA)。" + }, + "jsonSchemaGenerator": { + "title": "JSONスキーマジェネレーター", + "description": "サンプルJSONからJSONスキーマと型付きモデルを推論します。" + }, + "imageToBase64": { + "title": "画像をBase64に変換", + "description": "画像をデータ URI または生の Base64 文字列に変換します。" + }, + "imageCompressor": { + "title": "画像圧縮", + "description": "JPEG・PNG・WebPをブラウザ内で品質スライダー付きで圧縮。" + }, + "cssGradientBuilder": { + "title": "CSS グラデーション ビルダー", + "description": "角度制御とカラーストップを備えたビジュアル CSS グラデーション ビルダー。" + }, + "gitignoreGenerator": { + "title": ".gitignore ジェネレーター", + "description": "500 を超える技術スタックからマージされた .gitignore ファイルを即座に生成します。" + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "CSV や Excel を JSON に変換し、CSV や XLSX に再エクスポートします。" + }, + "snippetManager": { + "title": "コードスニペット", + "description": "シンタックスハイライト、自動言語検出、整形でスニペットを保存します。" + }, + "qrCodeGenerator": { + "title": "QRコード生成", + "description": "テキストやURLからブラウザ内でPNGのQRコードを作成。" + }, + "ipSubnetCalculator": { + "title": "IP/サブネット計算", + "description": "IPv4/IPv6 CIDR:ネットマスク、範囲、件数。" + }, + "dnsLookup": { + "title": "DNS ルックアップ", + "description": "任意のドメインの DNS レコードを照会 — A、AAAA、MX、TXT、NS、CNAME。" + }, + "hashGenerator": { + "title": "ハッシュ生成", + "description": "テキストやファイルの MD5、SHA-1、SHA-2。" + }, + "hmacGenerator": { + "title": "HMACジェネレーター", + "description": "Webhookの署名とAPI連携のテストに HMAC-SHA1/256/384/512。" + }, + "totpGenerator": { + "title": "TOTP / 2FA コードジェネレーター", + "description": "認証アプリの Base32 秘密鍵を貼り付け;6桁コードが30秒ごとに更新されます。ログインやMFAのテストに便利です。" + }, + "markdownPreview": { + "title": "Markdownプレビュー", + "description": "HTMLエクスポートとHTML → Markdown変換を備えたライブMarkdownレンダラー。" + }, + "numberBaseConverter": { + "title": "基数変換ツール", + "description": "2~36進法の整数を相互変換。" + }, + "formatConverter": { + "title": "フォーマット変換", + "description": "JSON、YAML、TOML、XML を双方向に変換します。12 通りの組み合わせに対応します。" + }, + "mockDataGenerator": { + "title": "モックデータジェネレーター", + "description": "列を定義し型を選び、シードや Postman、テスト用のデータをエクスポートします。最大 5000 行、すべてブラウザ内で処理されアップロードはありません。" + }, + "httpStatusCodes": { + "title": "HTTPステータスコード", + "description": "説明付きのHTTPステータスコードを検索します (1xx–5xx)。" + }, + "svgOptimizer": { + "title": "SVG 最適化", + "description": "SVG を貼り付け、SVGO で不要部分を削除し UTF-8 サイズ削減を確認。" + }, + "unitConverter": { + "title": "単位変換器", + "description": "43の科学・工学カテゴリにわたる323単位間の変換。" + }, + "dockerComposeGenerator": { + "title": "Docker Compose ジェネレーター", + "description": "データベース、キャッシュ、キュー、プロキシ、可観測性イメージを選び、ローカル用の docker-compose.yml を出力します。" + }, + "game2048": { + "title": "2048年", + "description": "タイルをスライドさせてマージし、2048に到達します。クラシックなナンバーパズルゲーム。" + }, + "sudoku": { + "title": "数独", + "description": "クラシックな数字パズル。3つの難易度、それぞれ150ステージ。" + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Redisインスタンスのキーを閲覧し、値を検査し、コマンドを実行します。" + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt 生成・検証", + "description": "bcrypt でパスワードをハッシュ化し、既存のハッシュを検証します。" + }, + "chmodCalculator": { + "title": "Chmod 計算機", + "description": "Unix のパーミッションを 8 進数、シンボリック表記、チェックボックス間で変換します。" + }, + "codeScreenshot": { + "title": "コードスクリーンショット", + "description": "ソースコードを美しく共有しやすい画像に変換します。" + }, + "cssGenerators": { + "title": "CSS ジェネレーター", + "description": "Box-shadow、border-radius、流動的な clamp() のジェネレーター。" + }, + "exifViewer": { + "title": "EXIF ビューアー・除去", + "description": "写真の EXIF メタデータをローカルで確認・除去します。" + }, + "faviconGenerator": { + "title": "Favicon ジェネレーター", + "description": "favicon、アプリアイコン、マニフェストのスニペットを生成します。" + }, + "jsonDiff": { + "title": "JSON 差分", + "description": "2 つの JSON ドキュメントを意味的・構造的に比較します。" + }, + "jsonpathPlayground": { + "title": "JSONPath プレイグラウンド", + "description": "JSONPath または JMESPath で JSON をクエリし、結果をリアルタイムで確認します。" + }, + "keycodeInspector": { + "title": "キーコードインスペクター", + "description": "キーを押して JavaScript のイベントコードと修飾キーを確認します。" + }, + "markdownTableGenerator": { + "title": "Markdown テーブルジェネレーター", + "description": "スプレッドシートのようなグリッドエディターで Markdown と HTML のテーブルを作成します。" + }, + "timezoneConverter": { + "title": "タイムゾーン変換", + "description": "1 つの時刻を複数のタイムゾーンに変換し、日付のずれや勤務時間を表示します。" + }, + "tokenCounter": { + "title": "LLM トークンカウンター", + "description": "LLM のトークンを数え、チャンクを可視化し、API コストを見積もります。" + }, + "webhookTester": { + "title": "Webhook テスター", + "description": "受信した webhook リクエストをリアルタイムでキャプチャして確認します。" + }, + "websocketTester": { + "title": "WebSocket テスター", + "description": "WebSocket エンドポイントに接続し、メッセージをリアルタイムでやり取りします。" + }, + "whoisLookup": { + "title": "Whois 検索", + "description": "RDAP を使ってドメインと IP の登録情報を調べます。" + } + }, + "tabs": { + "apps": "アプリ", + "analytics": "分析", + "ui": "インターフェース" + }, + "analytics": { + "title": "データの概要", + "subtitle": "サーバー上のアカウントと同期された件数です。", + "loadError": "分析を読み込めませんでした。", + "retry": "再試行", + "loginHint": "ログインすると利用状況の分析とインターフェース設定を表示できます。", + "passwordEntries": "パスワード項目", + "bookmarks": "ブックマーク", + "bookmarkFolders": "ブックマークフォルダ", + "tasksTotal": "タスク", + "tasksCompleted": "完了", + "tasksOngoing": "進行中", + "tasksNotStarted": "未着手", + "projects": "プロジェクト", + "nosqlConnections": "DB接続", + "notes": "ノート", + "apiClientCollections": "APIコレクション", + "apiClientEnvironments": "API環境", + "apiClientHistory": "API履歴エントリ", + "jsonFormatterDocuments": "JSONフォーマッターのドキュメント", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "コードスニペット", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "インターフェース設定", + "subtitle": "テーマ・言語・サイドバーに表示するツール — アカウント設定から。", + "loadError": "設定を読み込めませんでした。", + "loginHint": "ログインすると保存したインターフェース設定を表示できます。", + "theme": "テーマ", + "accent": "アクセントカラー", + "locale": "言語", + "enabledTools": "表示中のツール", + "favoriteTools": "お気に入りのツール", + "toolsWithUsage": "利用状況が記録されたツール" + }, + "signIn": "ログイン", + "noResults": "検索に一致するツールがありません", + "noResultsHint": "別のキーワードを試すか、フィルターをクリアしてください。", + "filterAll": "すべて", + "loginCta": { + "description": "ログインして最近使用したツールを追跡しましょう。" + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "読み込み中...", + "myTasksTitle": "マイタスク", + "statsLine": "{total}件のタスク • {percent}%完了", + "statsLineDesktop": "合計{total}件 • {percent}%完了" + }, + "filters": { + "all": "すべて", + "total": "合計", + "allProjects": "すべてのプロジェクト", + "export": "エクスポート", + "kanban": "カンバン", + "list": "リスト", + "searchPlaceholder": "タスクを検索...", + "searchAria": "タスクを検索", + "clearSearchAria": "検索をクリア", + "foundTasks": "{count}件のタスクが見つかりました", + "filterByAllAria": "すべてでフィルター", + "filterByStatusAria": "{status}でフィルター" + }, + "status": { + "not-started": { + "label": "未着手", + "description": "まだ開始されていないタスク" + }, + "ongoing": { + "label": "進行中", + "description": "現在進行中のタスク" + }, + "completed": { + "label": "完了", + "description": "完了したタスク" + } + }, + "priorities": { + "high": "高", + "medium": "中", + "low": "低" + }, + "form": { + "addNewTaskLabel": "新しいタスクを追加", + "multilinePlaceholder": "タスクの詳細を追加...(Shift+Enterで改行)", + "singlelinePlaceholder": "何をすべきですか?Enterキーで追加", + "taskInputAria": "タスク入力", + "projectPlaceholder": "プロジェクト", + "selectProject": "プロジェクトを選択", + "errorSelectProject": "タスクを追加するにはプロジェクトを選択するか、新しいプロジェクトを作成してください。", + "hintMultiline": "Shift+Enterで改行、Enterで追加", + "hintSingleline": "Enterで追加", + "shortcutsAria": "キーボードショートカット", + "shortcutsLabel": "ショートカット", + "shortcutAddTask": "タスクを追加", + "shortcutNewLine": "改行", + "shortcutClear": "クリア", + "addButton": "追加" + }, + "list": { + "ariaLabel": "タスクリスト", + "loading": "タスクを読み込んでいます...", + "emptyTitle": "タスクが見つかりません", + "emptyPrimary": "上のフィールドから最初のタスクを追加しましょう!Enterキーで素早くタスクを追加できます。", + "emptySecondary": "フィルターや検索クエリを調整してみてください。" + }, + "mobileDrawer": { + "addTaskSrOnly": "タスクを追加", + "addNewTaskTitle": "新しいタスクを追加", + "addNewTaskDescription": "進捗を管理するための新しいタスクを作成します。", + "cancel": "キャンセル" + }, + "kanban": { + "ariaLabel": "カンバンボード", + "loading": "タスクを読み込んでいます...", + "dropHere": "ここにタスクをドロップ", + "noTasksYet": "タスクはまだありません", + "emptyColumnAria": "空の列", + "columnAria": "{title}列({count}件のタスク)", + "countInColumnAria": "{title}に{count}件のタスク" + }, + "taskItem": { + "confirmDelete": "このタスクを削除してもよろしいですか?", + "statusPlaceholder": "ステータス", + "changeStatusAria": "タスクのステータスを変更", + "taskOptionsAria": "タスクオプション", + "editTask": "タスクを編集", + "copyTask": "タスクをコピー", + "copied": "コピーしました!", + "deleteTask": "タスクを削除", + "statusHeading": "ステータス", + "editTaskAria": "タスクを編集" + }, + "editDialog": { + "title": "タスクを編集", + "description": "タスクの詳細を更新し、サブタスクを追加し、優先度などを設定します。", + "taskTitleLabel": "タスク名 *", + "taskTitlePlaceholder": "何をすべきですか?", + "descriptionLabel": "説明", + "descriptionPlaceholder": "このタスクの詳細を追加...", + "statusLabel": "ステータス", + "priorityLabel": "優先度", + "dueDateLabel": "期限", + "pickDate": "日付を選択", + "clearDate": "日付をクリア", + "projectLabel": "プロジェクト", + "selectProjectPlaceholder": "プロジェクトを選択", + "noProject": "プロジェクトなし", + "timeEstimateLabel": "見積もり時間(分)", + "timeEstimatePlaceholder": "どのくらいかかりますか?", + "tagsLabel": "タグ", + "customTagNamePlaceholder": "カスタムタグ名", + "subtasksLabel": "サブタスク", + "addSubtaskPlaceholder": "サブタスクを追加...", + "cancel": "キャンセル", + "saving": "保存中...", + "saveChanges": "変更を保存" + }, + "export": { + "title": "タスクをエクスポート", + "description": "タスクをJSON、CSV、またはExcel形式でエクスポートします。", + "projectLabel": "プロジェクト", + "statusLabel": "ステータス", + "tagLabel": "タグ", + "allProjects": "すべてのプロジェクト", + "allStatuses": "すべてのステータス", + "allTags": "すべてのタグ", + "exportAsJsonTitle": "JSONとしてエクスポート", + "exportAsJsonDescription": "サブタスク、タグ、メタデータを含むタスクデータをエクスポートします。", + "exportJson": "JSONエクスポート", + "exportAsCsvExcelTitle": "CSV / Excelとしてエクスポート", + "exportAsCsvExcelDescription": "スプレッドシートアプリケーション用にタスクをエクスポートします。", + "exportCsv": "CSVエクスポート", + "exportExcel": "Excelエクスポート", + "close": "閉じる", + "toastNoTasks": "現在のフィルターでエクスポートするタスクがありません", + "toastExportedJson": "{count}件のタスクをJSONにエクスポートしました!", + "toastExportedCsv": "{count}件のタスクをCSVにエクスポートしました!", + "toastExportedExcel": "{count}件のタスクをExcelにエクスポートしました!", + "toastFailedJson": "JSONへのエクスポートに失敗しました", + "toastFailedCsv": "CSVへのエクスポートに失敗しました", + "toastFailedExcel": "Excelへのエクスポートに失敗しました", + "unknownProject": "不明なプロジェクト" + }, + "ack": { + "taskAddedTitle": "タスクが正常に追加されました", + "taskAddedFailedTitle": "タスクの追加に失敗しました", + "taskUpdatedTitle": "タスクが正常に更新されました", + "taskUpdatedFailedTitle": "タスクの更新に失敗しました", + "taskStatusUpdateFailedTitle": "タスクのステータス更新に失敗しました", + "taskMovedTitle": "タスクを{status}に移動しました", + "tryAgain": "もう一度お試しください。", + "taskDeletedTitle": "タスク「{text}」を削除しました", + "undo": "元に戻す", + "taskRestoredTitle": "タスクを復元しました", + "taskRestoredSuccessTitle": "タスクが正常に復元されました", + "taskRestoreFailedTitle": "タスクの復元に失敗しました。もう一度お試しください。", + "taskDeleteFailedTitle": "タスクの削除に失敗しました。もう一度お試しください。", + "tasksImportedTitle": "{count}件のタスクを正常にインポートしました!", + "tasksImportFailedTitle": "タスクのインポートに失敗しました。もう一度お試しください。", + "unknownProject": "不明なプロジェクト", + "projectCreatedTitle": "プロジェクトが正常に作成されました", + "projectCreateFailedTitle": "プロジェクトの作成に失敗しました", + "projectUpdatedTitle": "プロジェクトが正常に更新されました", + "projectUpdateFailedTitle": "プロジェクトの更新に失敗しました", + "projectDeletedTitle": "プロジェクトが正常に削除されました", + "projectDeleteFailedTitle": "プロジェクトの削除に失敗しました" + } + }, + "Notes": { + "page": { + "loading": "読み込み中..." + }, + "layout": { + "mobileNavTitle": "ノートナビゲーション", + "openMenuAria": "ノートメニューを開く" + }, + "sidebar": { + "title": "ノート", + "createNoteAria": "ノートを作成", + "searchPlaceholder": "ノートを検索...", + "loading": "読み込み中...", + "noNotesFound": "ノートが見つかりません。", + "noNotesYet": "ノートはまだありません。+をクリックして作成してください。", + "inParent": "{parentTitle}内", + "untitled": "無題", + "addSubPage": "サブページを追加", + "delete": "削除", + "deleteConfirmTitle": "ノートを削除しますか?", + "deleteConfirmDescription": "「{title}」を削除してもよろしいですか?この操作は取り消せません。", + "cancel": "キャンセル", + "confirmDelete": "削除", + "noteOptionsAria": "ノートオプション" + }, + "editor": { + "emptyState": "ノートを選択するか、新しいノートを作成してください。", + "titlePlaceholder": "無題", + "saving": "保存中…", + "ready": "準備完了", + "saved": "{time} に保存", + "stats": "{words} 語 · 読了 {read} 分", + "template": "テンプレート", + "applyTemplate": "テンプレートを適用", + "exportMdLabel": ".md", + "exportMd": "Markdown としてエクスポート", + "exportHtmlLabel": ".html", + "exportHtml": "HTML としてエクスポート", + "focusMode": "集中モード", + "exitFocusMode": "集中モードを終了", + "save": "保存", + "addTag": "タグを追加…", + "removeTag": "タグ {tag} を削除", + "loading": "読み込み中…", + "searchHint": "で検索", + "focusHint": "集中モード", + "notePath": "ノートのパス" + }, + "context": { + "authRequiredError": "ユーザーが認証されていません", + "defaultTitle": "無題", + "cloudSyncImageError": "ノート画像を保存するにはクラウド同期をオンにしてください。テキストノートはローカルに保存されます。" + } + }, + "PasswordManager": { + "page": { + "loading": "読み込み中...", + "title": "パスワードマネージャー", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "パスワードの復号に失敗しました", + "toastLoadFailed": "パスワードの読み込みに失敗しました", + "toastUnlockedAuto": "ボールトが自動的にロック解除されました", + "toastAutoUnlockFailed": "自動ロック解除に失敗しました。手動でロック解除してください。", + "toastConnectFailed": "サーバーへの接続に失敗しました", + "toastAutoUnlockWarning": "ボールトのロックが解除されましたが、リロード時に自動ロック解除が機能しない場合があります", + "errorIncorrectPassword": "マスターパスワードが正しくありません", + "errorUnlockFailed": "ボールトのロック解除に失敗しました", + "errorPasswordsMismatch": "パスワードが一致しません", + "errorPasswordTooShort": "パスワードは8文字以上である必要があります", + "errorSetupFailed": "ボールトのセットアップに失敗しました", + "errorConnectFailed": "サーバーへの接続に失敗しました。", + "mobileTitleCreate": "ボールトを作成", + "mobileTitleWelcome": "おかえりなさい", + "mobileSubtitleCreate": "資格情報を保護するマスターパスワードを設定してください", + "mobileSubtitleUnlock": "マスターパスワードを入力してロック解除", + "placeholderMasterPassword": "マスターパスワード", + "placeholderConfirmPassword": "パスワードの確認", + "labelMasterPassword": "マスターパスワード", + "labelConfirmPassword": "パスワードの確認", + "placeholderEnterMaster": "マスターパスワードを入力", + "placeholderConfirmMaster": "マスターパスワードの確認", + "buttonCreateVault": "ボールトを作成", + "buttonUnlock": "ロック解除", + "buttonUnlockVault": "ボールトのロック解除", + "footerCreate": "パスワードは保存されず、復元もできません", + "footerUnlock": "エンドツーエンド暗号化で保護されています", + "desktopTitleSetup": "パスワードボールトのセットアップ", + "desktopTitleUnlock": "ボールトのロック解除", + "desktopDescriptionSetup": "資格情報を保護するマスターパスワードを作成してください。このパスワードは保存されず、復元もできません。", + "desktopDescriptionUnlock": "保護された資格情報にアクセスするにはマスターパスワードを入力してください。" + }, + "list": { + "loading": "パスワードを読み込んでいます...", + "emptyTitle": "ボールトは空です", + "emptyHintMobile": "+ボタンをタップして最初のパスワードを追加してください", + "emptyHintDesktop": "パスワードはまだ保存されていません。上の「パスワードを追加」ボタンをクリックして始めましょう。", + "addPassword": "パスワードを追加", + "searchPlaceholderMobile": "ボールトを検索...", + "searchPlaceholderDesktop": "ボールトを検索...", + "importExport": "インポート / エクスポート", + "lockVault": "ボールトをロック", + "allPasswordsTitle": "すべてのパスワード", + "passwordCount": "{count, plural, one {#件のパスワード} other {#件のパスワード}}", + "vaultHealthDrawerTitle": "ボールトの健全性", + "noResults": "「{query}」に一致するパスワードが見つかりません", + "tableService": "サービス", + "tableUsername": "ユーザー名", + "tablePassword": "パスワード", + "tableUrl": "URL", + "tableActions": "操作", + "gridViewAria": "グリッド表示", + "listViewAria": "リスト表示", + "passwordStrengthTitle": "パスワード強度", + "deleteDialogTitle": "パスワードを削除", + "deleteDialogDescription": "このパスワードを削除してもよろしいですか?この操作は取り消せません。", + "cancel": "キャンセル", + "delete": "削除", + "edit": "編集" + }, + "toasts": { + "copiedPassword": "パスワードをクリップボードにコピーしました", + "copiedUsername": "ユーザー名をクリップボードにコピーしました", + "passwordDeleted": "パスワードを削除しました", + "deleteFailed": "パスワードの削除に失敗しました", + "vaultLocked": "ボールトをロックしました", + "importedCount": "{count, plural, one {#件のパスワード} other {#件のパスワード}}を正常にインポートしました" + }, + "form": { + "addTitle": "新しいパスワードを追加", + "editTitle": "パスワードを編集", + "serviceName": "サービス名", + "usernameEmail": "ユーザー名 / メールアドレス", + "password": "パスワード", + "urlOptional": "ウェブサイトURL(任意)", + "notesOptional": "メモ(任意)", + "tags": "タグ", + "placeholderService": "例:Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "パスワードを入力または生成", + "placeholderUrl": "https://...", + "placeholderNotes": "追加の詳細...", + "placeholderTags": "タグを追加(EnterまたはCommaで確定)", + "strength": "強度", + "strengthWeak": "弱い", + "strengthMedium": "普通", + "strengthStrong": "強い", + "advancedGenerator": "高度なジェネレーター", + "cancel": "キャンセル", + "saving": "保存中...", + "savePassword": "パスワードを保存", + "updating": "更新中...", + "updatePassword": "パスワードを更新", + "addPasswordSrOnly": "パスワードを追加", + "triggerAdd": "パスワードを追加", + "toastSaved": "パスワードが正常に保存されました", + "toastSaveFailed": "パスワードの保存に失敗しました", + "toastUpdated": "パスワードが正常に更新されました", + "toastUpdateFailed": "パスワードの更新に失敗しました" + }, + "security": { + "vaultHealth": "ボールトの健全性", + "statusLabel": "ステータス", + "totalPasswords": "パスワード総数", + "activeEntries": "ボールト内のアクティブなエントリー", + "protected": "保護済み", + "weakPasswords": "弱いパスワード", + "needingImprovement": "改善が必要なパスワード", + "allGood": "問題なし", + "reusedPasswords": "再利用されたパスワード", + "usedMultipleTimes": "複数回使用されたパスワード", + "unique": "ユニーク", + "total": "合計", + "weak": "弱い", + "reused": "再利用", + "totalCountLabel": "{count, plural, one {#件のパスワード} other {#件のパスワード}}", + "weakCountLabel": "{count}件が弱い", + "allSecure": "すべて安全", + "scoreExcellent": "優秀", + "scoreGood": "良好", + "scoreFair": "普通", + "scoreCritical": "危険" + }, + "importExport": { + "title": "ボールトのインポート / エクスポート", + "description": "パスワードデータを管理します。ボールトをJSONにエクスポートしたり、JSONファイルからインポートできます。", + "tabExport": "エクスポート", + "tabImport": "インポート", + "warningTitle": "警告", + "warningBody": "エクスポートされたファイルにはパスワードが平文で含まれています。このファイルを安全に保管し、使用後は削除してください。", + "exportBlurb": "ボールト全体をJSONファイルとしてダウンロードします。すべてのサービス、ユーザー名、パスワード、メモが含まれます。", + "downloadJson": "JSONエクスポートをダウンロード", + "pasteLabel": "JSONデータを貼り付け", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "以前にエクスポートしたJSONファイルの内容をここに貼り付けてください。", + "importing": "インポート中...", + "importPasswords": "パスワードをインポート", + "trigger": "インポート / エクスポート", + "toastExported": "ボールトが正常にエクスポートされました", + "toastExportFailed": "ボールトのエクスポートに失敗しました", + "toastImportFailed": "インポートに失敗しました:無効なJSON形式です" + }, + "card": { + "copyUsername": "ユーザー名をコピー", + "openUrl": "URLを開く", + "strength": "強度" + }, + "swipeable": { + "copy": "コピー", + "delete": "削除", + "copyPassword": "パスワードをコピー", + "copyUsername": "ユーザー名をコピー", + "edit": "編集", + "deleteAction": "削除" + }, + "floatingMenu": { + "lockVault": "ボールトをロック", + "importExport": "インポート / エクスポート", + "addPassword": "パスワードを追加" + }, + "generator": { + "tabPassword": "パスワード", + "tabPassphrase": "パスフレーズ", + "strength": "強度", + "lengthLabel": "長さ:{length}文字", + "lengthBadge": "{length}文字", + "characterSets": "文字セット", + "uppercase": "大文字", + "lowercase": "小文字", + "numbers": "数字", + "symbols": "記号", + "wordCountLabel": "単語数:{count}", + "wordCountBadge": "{count, plural, one {#語} other {#語}}", + "separator": "区切り文字", + "capitalization": "大文字化", + "includeNumber": "数字を含める" + } + }, + "EnvironmentManager": { + "subtitle": "プロジェクトと環境ごとに暗号化された環境変数。", + "page": { + "loading": "読み込み中…", + "title": "環境変数マネージャー", + "loadFailed": "環境セットの読み込みに失敗しました" + }, + "list": { + "title": "環境", + "loading": "セットを読み込み中…", + "emptyTitle": "環境セットはまだありません", + "emptyHint": "プロジェクトと環境ごとにセットを作成します(例: 本番 API キー)。アップロード前にブラウザで暗号化されます。", + "searchPlaceholder": "プロジェクト・環境・タグ・メモを検索…", + "tagFilterPlaceholder": "タグで絞り込み", + "variableCount": "{count, plural, other {# 件の変数}}", + "updatedAgo": "更新 {time}", + "copyDotEnv": ".env をコピー", + "edit": "編集", + "delete": "削除", + "cancel": "キャンセル", + "confirmDelete": "削除", + "deleteDialogTitle": "環境セットを削除しますか?", + "deleteDialogDescription": "アカウントから暗号化データが削除されます。元に戻せません。", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env をクリップボードにコピーしました", + "deleted": "環境セットを削除しました", + "deleteFailed": "削除に失敗しました" + }, + "form": { + "addTitle": "新しい環境セット", + "editTitle": "環境セットを編集", + "triggerAdd": "環境セットを追加", + "addSrOnly": "環境セットを追加", + "project": "プロジェクト", + "environment": "環境", + "placeholderProject": "例: billing-api", + "placeholderEnvironment": "例: production", + "pasteEnvLabel": ".env を貼り付け", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "貼り付けた行をマージ", + "variables": "変数", + "addRow": "行を追加", + "keyPlaceholder": "キー", + "valuePlaceholder": "値", + "notesOptional": "メモ(任意)", + "placeholderNotes": "ローテーション方針、担当者、リンク…", + "tags": "タグ", + "placeholderTags": "タグを追加(Enter またはカンマ)", + "cancel": "キャンセル", + "save": "保存", + "saving": "保存中…", + "update": "更新", + "updating": "更新中…", + "toastSaved": "環境セットを保存しました", + "toastSaveFailed": "保存に失敗しました", + "toastUpdated": "環境セットを更新しました", + "toastUpdateFailed": "更新に失敗しました", + "toastPasteEmpty": "KEY=値 の行が見つかりません", + "toastPasteApplied": "貼り付けから {count} 件の変数をマージしました", + "toastProjectEnvRequired": "プロジェクトと環境は必須です", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "読み込み中..." + }, + "manager": { + "foldersHeading": "フォルダー", + "tagsHeading": "タグ", + "tagsMore": "+{count}件", + "tagsShowLess": "Show less", + "searchPlaceholder": "ブックマークを検索...", + "import": "インポート", + "export": "エクスポート", + "exportAsHtml": "HTMLとしてエクスポート", + "exportAsJson": "JSONとしてエクスポート", + "addBookmark": "ブックマークを追加", + "matchingQuery": "「{query}」に一致", + "bookmarkCount": "{count, plural, one {#件のブックマーク} other {#件のブックマーク}}", + "allBookmarks": "すべてのブックマーク", + "uncategorized": "未分類", + "unknownFolder": "不明" + }, + "card": { + "urlCopied": "URLをクリップボードにコピーしました", + "bookmarkDeleted": "ブックマークを削除しました", + "edit": "編集", + "delete": "削除", + "copyUrl": "URLをコピー", + "deleteConfirmTitle": "ブックマークを削除しますか?", + "deleteConfirmDescription": "「{title}」を完全に削除します。", + "cancel": "キャンセル" + }, + "grid": { + "emptyTitle": "ブックマークはまだありません", + "emptyDescription": "最初のブックマークを追加するか、ブラウザからインポートして始めましょう。" + }, + "folderTree": { + "allBookmarks": "すべてのブックマーク", + "addSubfolder": "サブフォルダーを追加", + "rename": "名前を変更", + "deleteFolderTitle": "フォルダー「{name}」を削除しますか?", + "deleteFolderDescription": "このフォルダーとすべてのサブフォルダーが削除されます。これらのフォルダー内のブックマークは未分類に移動されます。" + }, + "import": { + "title": "ブックマークをインポート", + "description": "ブラウザからエクスポートしたファイルからブックマークをインポートします。", + "parseError": "ブックマークファイルの解析に失敗しました。有効なブックマークエクスポートであることを確認してください。", + "successToast": "{bookmarks}件のブックマークと{folders}件のフォルダーをインポートしました", + "errorToast": "ブックマークのインポートに失敗しました", + "chromeInstruction": "メニュー → ブックマーク → ブックマークマネージャー → ⋮ → ブックマークをエクスポート", + "firefoxInstruction": "メニュー → ブックマーク → ブックマークを管理 → インポートとバックアップ → エクスポート", + "safariInstruction": "ファイル → ブックマークをエクスポート...", + "edgeInstruction": "メニュー → お気に入り → ⋯ → お気に入りをエクスポート", + "dropZoneTitle": "ブックマークファイルをここにドロップ", + "dropZoneHint": "またはクリックして選択(HTMLまたはJSON)", + "selectedFileHint": "クリックして別のファイルを選択", + "errorTitle": "エラー", + "previewTitle": "インポートプレビュー", + "previewBookmarks": "{count, plural, one {#件のブックマーク} other {#件のブックマーク}}", + "previewFolders": "{count, plural, one {#件のフォルダー} other {#件のフォルダー}}", + "importModeLabel": "インポートモード", + "modeMerge": "既存のブックマークとマージ", + "modeReplace": "既存のブックマークをすべて置換", + "cancel": "キャンセル", + "importButton": "インポート", + "importButtonWithCount": "{count, plural, one {#件のブックマーク} other {#件のブックマーク}}をインポート" + }, + "addBookmark": { + "titleAdd": "ブックマークを追加", + "titleEdit": "ブックマークを編集", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "有効なURLを入力してください", + "titleLabel": "タイトル", + "titlePlaceholder": "ブックマークのタイトル", + "titleRequired": "タイトルを入力してください", + "descriptionLabel": "説明(任意)", + "descriptionPlaceholder": "このブックマークについてメモを追加", + "folderLabel": "フォルダー", + "folderPlaceholder": "フォルダーを選択", + "noFolder": "フォルダーなし", + "tagsLabel": "タグ", + "tagsPlaceholder": "タグを追加(Enterで確定)", + "toastUpdated": "ブックマークを更新しました", + "toastAdded": "ブックマークを追加しました", + "cancel": "キャンセル", + "submitAdd": "ブックマークを追加", + "submitUpdate": "ブックマークを更新" + }, + "addFolder": { + "title": "フォルダーを作成", + "nameLabel": "フォルダー名", + "namePlaceholder": "マイフォルダー", + "nameRequired": "フォルダー名を入力してください", + "parentLabel": "親フォルダー", + "parentPlaceholder": "親フォルダーを選択", + "rootParent": "ルート(親なし)", + "toastCreated": "フォルダーを作成しました", + "cancel": "キャンセル", + "submit": "フォルダーを作成" + }, + "editFolder": { + "title": "フォルダー名を変更", + "nameLabel": "フォルダー名", + "namePlaceholder": "フォルダー名", + "nameRequired": "フォルダー名を入力してください", + "toastRenamed": "フォルダー名を変更しました", + "cancel": "キャンセル", + "save": "保存" + } + }, + "EmailValidator": { + "header": { + "title": "メールバリデーター", + "description": "メールアドレスの構文、ドメイン、配信可能性を検証・確認します。" + }, + "tabs": { + "single": "単一検証", + "bulk": "一括アップロード" + }, + "single": { + "cardTitle": "単一メール検証", + "cardDescription": "メールアドレスを入力して配信可能性と評判を確認します。", + "placeholder": "メールアドレスを入力...", + "clearAria": "メールをクリア", + "validate": "検証", + "validating": "検証中...", + "formatError": "有効なメールアドレスを入力してください" + }, + "toasts": { + "errorTitle": "エラー", + "enterEmail": "メールアドレスを入力してください", + "invalidFormatTitle": "無効なメール形式", + "invalidFormatDescription": "有効なメールアドレスを入力してください", + "validateFailedDescription": "メールの検証中にエラーが発生しました", + "noEmailsTitle": "メールが見つかりません", + "noEmailsDescription": "Excelに「email」列があることを確認してください。", + "fileTooLargeTitle": "ファイルが大きすぎます", + "fileTooLargeDescription": "{total}件のメールが見つかりました。最初の4000件のみ検証されます。", + "successTitle": "成功", + "successValidated": "{count}件のメールを正常に検証しました。", + "batchFailedTitle": "一括検証に失敗しました", + "batchFailedDescription": "一括検証の処理中にエラーが発生しました。", + "noExportTitle": "エクスポートするデータがありません", + "noExportValidOnly": "有効なメールが見つかりませんでした。", + "noExportAll": "エクスポートする結果がありません。", + "exportSuccessTitle": "エクスポート成功", + "exportSuccessExcelAll": "{count}件のメールをExcelにエクスポートしました。", + "exportSuccessExcelValid": "{count}件の有効なメールをExcelにエクスポートしました。", + "exportSuccessCsvAll": "{count}件のメールをCSVにエクスポートしました。", + "exportSuccessCsvValid": "{count}件の有効なメールをCSVにエクスポートしました。" + }, + "disposableAlert": { + "title": "使い捨てメール検出", + "description": "このメールアドレスは使い捨てメールプロバイダーに属しています。一時的または安全でない可能性があります。" + }, + "score": { + "reliabilityTitle": "信頼性スコア", + "scoreLabel": "スコア" + }, + "details": { + "sectionTitle": "検証の詳細" + }, + "validation": { + "syntaxLabel": "構文が有効", + "syntaxTooltip": "メールアドレスが正しい形式に従っているか確認します(例:user@domain.com)", + "domainLabel": "ドメインが存在", + "domainTooltip": "メールアドレスのドメイン名が実際に存在するか確認します", + "mxLabel": "MXレコードあり", + "mxTooltip": "ドメインにメール受信用のMXレコードが設定されているか確認します", + "mailboxLabel": "メールボックスが存在", + "mailboxTooltip": "メールサーバー上に特定のメールボックス/アカウントが存在するか確認します", + "disposableLabel": "使い捨てメール", + "disposableTooltip": "このメールが一時的/使い捨てメールサービスに属しているかを示します(リスクがある場合があります)", + "roleBasedLabel": "ロールベースメール", + "roleBasedTooltip": "個人アドレスではなく、ロールベースのメール(例:support@、info@)かどうかを識別します" + }, + "copy": { + "emailCopied": "メールをコピーしました!", + "copyTooltip": "メールをコピー" + }, + "bulk": { + "dropHere": "ファイルをここにドロップ", + "dragHere": "ファイルをここにドラッグ&ドロップ", + "uploadHint": "「email」列を含む.xlsx、.xls、または.csvファイルをアップロードして、最大4000件のメールを検証します。", + "filePrefix": "ファイル:", + "selectFile": "ファイルを選択", + "downloadTemplate": "テンプレートをダウンロード" + }, + "bulkLoading": { + "title": "メールを検証中...", + "batchProgress": "バッチ{current}/{total}を処理中..." + }, + "stats": { + "totalProcessed": "処理済み合計", + "valid": "有効", + "risky": "リスクあり", + "invalid": "無効" + }, + "results": { + "detailedTitle": "詳細結果", + "searchPlaceholder": "メールを検索...", + "newVerification": "新しい検証", + "exportAll": "すべてエクスポート", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "有効なメールをエクスポート({count}件)" + }, + "showingResults": "{total}件中{shown}件を表示", + "table": { + "email": "メール", + "status": "ステータス", + "score": "スコア", + "checks": "チェック" + }, + "noSearchResults": "「{query}」に一致する結果が見つかりません", + "checksTooltip": { + "syntax": "構文", + "domain": "ドメイン", + "mx": "MX", + "mailbox": "メールボックス" + }, + "exportColumns": { + "email": "メール", + "status": "ステータス", + "score": "スコア", + "syntax": "構文", + "domain": "ドメイン", + "mx": "MX", + "mailbox": "メールボックス", + "disposable": "使い捨て", + "roleBased": "ロールベース", + "valid": "有効", + "invalid": "無効", + "yes": "はい", + "no": "いいえ" + }, + "sheets": { + "validEmails": "有効なメール", + "results": "結果", + "template": "テンプレート" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQLエクスプローラー", + "heroSubtitle": "データベースに接続して、コレクションの管理、クエリの実行、データの視覚化を行います。", + "featureMultiTitle": "マルチ接続", + "featureMultiDesc": "複数のデータベース接続を同時に管理します。", + "featureMongoTitle": "MongoDBサポート", + "featureMongoDesc": "高度なフィルタリングを備えたMongoDBのネイティブサポート。", + "featureQueryTitle": "スマートクエリ", + "featureQueryDesc": "シンタックスハイライト付きの強力なクエリビルダー。", + "connectCta": "MongoDBに接続", + "footerNote": "現在{db}をサポートしています。その他のデータベースは近日対応予定です。", + "footerDb": "MongoDB", + "selectCollectionTitle": "コレクションを選択", + "selectCollectionDesc": "サイドバーからコレクションを選択して、ドキュメントを表示しデータの探索を開始します。", + "dialogAddConnection": "接続を追加", + "toastConnectionAdded": "接続が追加されました", + "toastFetchFailed": "{collection}のドキュメント取得に失敗しました", + "toastDocDeleted": "ドキュメントを削除しました", + "deleteDocTitle": "本当に削除しますか?", + "deleteDocDescription": "この操作は取り消せません。ドキュメントはコレクションから完全に削除されます。", + "closeAllTitle": "すべてのタブを閉じますか?", + "closeAllDescription": "すべてのタブを閉じてもよろしいですか?クエリフィルターの未保存の変更は失われます。", + "closeAllConfirm": "すべて閉じる", + "cancel": "キャンセル", + "delete": "削除" + }, + "connection": { + "titleEdit": "接続を編集", + "titleConnect": "MongoDBに接続", + "subtitleEdit": "以下の接続情報を更新してください。", + "subtitleConnect": "MongoDB接続文字列を入力して、データの探索を開始します。", + "labelName": "名前", + "placeholderName": "例:本番DB", + "labelConnectionString": "接続文字列", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "形式:mongodb://username:password@host:port/database", + "testConnection": "接続テスト", + "connecting": "接続中...", + "updateConnect": "更新して接続", + "connect": "接続", + "cancelEdit": "編集をキャンセル", + "savedConnections": "保存済み接続", + "emptySaved": "保存済みの接続はまだありません。", + "lastUsed": "最終使用:{time}", + "toastTestOk": "接続に成功しました!", + "toastTestFail": "接続に失敗しました:{message}", + "toastUpdated": "接続を更新しました", + "confirmDelete": "この保存済み接続を削除してもよろしいですか?", + "toastDeletedConn": "接続を削除しました", + "toastDeleteFail": "接続の削除に失敗しました", + "copyStringTitle": "接続文字列をコピー", + "toastStringCopied": "接続文字列をコピーしました!", + "editTitle": "編集", + "deleteTitle": "削除" + }, + "sidebar": { + "explorer": "エクスプローラー", + "add": "追加", + "tooltipAdd": "新しい接続を追加", + "tooltipRefresh": "接続を更新", + "searchPlaceholder": "検索...", + "loadingConnections": "接続を読み込んでいます...", + "noConnectionsFound": "接続が見つかりません", + "toastLoadFail": "接続の読み込みに失敗しました", + "toastConnectFail": "{name}への接続に失敗しました", + "toastCollectionsFail": "{name}のコレクション取得に失敗しました", + "toastRenamed": "接続名を変更しました", + "toastRenameFail": "接続名の変更に失敗しました", + "confirmDeleteConnection": "接続「{name}」を削除してもよろしいですか?", + "toastDeleted": "接続を削除しました", + "toastDeleteConnFail": "接続の削除に失敗しました", + "confirmDropDb": "データベース「{name}」を削除してもよろしいですか?この操作は取り消せません。", + "toastDbDropped": "データベース{name}を削除しました", + "confirmDropCollection": "コレクション「{name}」を削除してもよろしいですか?この操作は取り消せません。", + "toastCollectionDropped": "コレクション{name}を削除しました", + "toastCollectionRenamed": "コレクション名を{name}に変更しました", + "toastDatabaseRenamed": "データベース名を{name}に変更しました", + "connecting": "接続中...", + "connectionFailed": "接続に失敗しました", + "noDatabases": "データベースなし", + "loading": "読み込み中...", + "noCollections": "コレクションなし", + "renameCollectionTitle": "コレクション名を変更", + "renameDatabaseTitle": "データベース名を変更", + "placeholderNewCollection": "新しいコレクション名", + "placeholderNewDatabase": "新しいデータベース名", + "rename": "名前を変更", + "refresh": "更新", + "copyDbName": "DB名をコピー", + "toastDbNameCopied": "データベース名をクリップボードにコピーしました", + "dropDatabase": "データベースを削除", + "dropCollection": "コレクションを削除", + "menuRename": "名前を変更", + "menuCopyConnectionString": "接続文字列をコピー", + "menuDeleteConnection": "接続を削除", + "toastSidebarStringCopied": "接続文字列をコピーしました!", + "cancel": "キャンセル", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n}件のドキュメント", + "table": "テーブル", + "json": "JSON", + "tree": "ツリー", + "tooltipTable": "テーブル表示", + "tooltipJson": "JSON表示", + "tooltipTree": "ツリー表示", + "itemsPerPage": "ページあたりの件数", + "perPage": "{n}件/ページ", + "prevPage": "前のページ", + "nextPage": "次のページ", + "expandAll": "すべて展開", + "collapseAll": "すべて折りたたむ", + "refreshData": "データを更新", + "insert": "挿入", + "export": "エクスポート", + "actions": "操作", + "invalidJson": "無効なJSONコンテンツ", + "copiedClipboard": "クリップボードにコピーしました", + "docUpdated": "ドキュメントが正常に更新されました", + "docInserted": "ドキュメントが正常に挿入されました", + "invalidJsonShort": "無効なJSON", + "docCopied": "ドキュメントをクリップボードにコピーしました", + "openFullEditor": "フルエディターで開く", + "cellArray": "配列({length})", + "cellObject": "オブジェクト", + "cellArrayBracket": "配列[{length}]", + "emptyFilterTitle": "クエリに一致するドキュメントがありません", + "emptyFilterHint": "フィルターを調整するか、クエリをクリアしてみてください。", + "clearFilter": "フィルターをクリア", + "emptyCollectionTitle": "このコレクションは空です", + "emptyCollectionHint": "ドキュメントを挿入して始めましょう。", + "insertDocument": "ドキュメントを挿入", + "formatJson": "JSONを整形", + "compactJson": "JSONを圧縮", + "copyClipboard": "クリップボードにコピー", + "copyJson": "JSONをコピー", + "duplicate": "複製", + "editDoc": "ドキュメントを編集", + "deleteDoc": "ドキュメントを削除", + "documentLabel": "ドキュメント{n}", + "editDialogTitle": "ドキュメントを編集", + "insertDialogTitle": "ドキュメントを挿入", + "viewValueTitle": "値を表示", + "saveChanges": "変更を保存", + "close": "閉じる", + "cancel": "キャンセル", + "insertButton": "挿入", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "アクティブなフィルターが適用されています", + "filterActiveTooltip": "フィルターがアクティブ", + "closeAll": "すべて閉じる" + }, + "query": { + "migrateHistory": "クエリ履歴をクラウドに移行しました", + "saveHistoryFail": "クエリ履歴の保存に失敗しました", + "deleteQueryFail": "クエリの削除に失敗しました", + "invalidJsonQuery": "無効なJSONクエリ", + "placeholder": "例:{ \"status\": \"active\" }", + "clearQuery": "クエリをクリア", + "openAdvanced": "高度なエディターを開く", + "visualBuilder": "ビジュアルクエリビルダー", + "filterRules": "フィルタールール", + "addRule": "ルールを追加", + "noFilters": "フィルターは適用されていません。ルールを追加してドキュメントをフィルタリングします。", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "フィールド", + "opPlaceholder": "演算子", + "typePlaceholder": "型", + "opRegex": "正規表現", + "opIn": "含む", + "opNin": "含まない", + "opExists": "存在する", + "typeAuto": "自動", + "typeString": "文字列", + "typeNumber": "数値", + "typeBoolean": "ブール", + "typeDate": "日付", + "typeObjectId": "ObjectId", + "true": "真", + "false": "偽", + "valuePlaceholder": "値", + "valueListPlaceholder": "値1, 値2", + "clearFilters": "フィルターをクリア", + "applyFilters": "フィルターを適用", + "queryHistory": "クエリ履歴", + "noSavedQueries": "保存されたクエリはまだありません", + "advancedEditorTitle": "高度なクエリエディター", + "templatesPlaceholder": "テンプレート", + "templateEmptyAgg": "空の集約", + "templateMatch": "$match(フィルター)", + "templateLookup": "$lookup(結合)", + "templateGroup": "$group(カウント/合計)", + "format": "整形", + "formattedJson": "整形済みJSON", + "runQuery": "クエリを実行" + }, + "export": { + "selectFieldsError": "エクスポートするフィールドを少なくとも1つ選択してください", + "success": "{filename}にエクスポートしました", + "fail": "データのエクスポートに失敗しました", + "title": "データをエクスポート", + "formatLabel": "エクスポート形式", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "フィールドを選択", + "selectAll": "すべて選択", + "fieldsSelected": "{count, plural, one {#フィールド選択済み} other {#フィールド選択済み}}", + "cancel": "キャンセル", + "export": "エクスポート", + "sheetName": "データ" + }, + "jsonTree": { + "typeLabel": "型:{type}", + "itemSummary": "{count, plural, one {#項目} other {#項目}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "新しいリクエスト", + "importedRequest": "インポートされたリクエスト", + "untitledRequest": "無題のリクエスト" + }, + "toasts": { + "curlImported": "cURLが正常にインポートされました", + "curlParseFailed": "cURLコマンドの解析に失敗しました", + "invalidJsonBody": "無効なJSONボディ", + "requestFailed": "リクエストに失敗しました:{message}", + "curlPasted": "cURLが貼り付けられ、正常に解析されました", + "responseCopied": "レスポンスをクリップボードにコピーしました", + "codeCopied": "コードをクリップボードにコピーしました", + "copyFailed": "クリップボードへのコピーに失敗しました" + }, + "layout": { + "collections": "コレクション", + "closeSidebar": "サイドバーを閉じる", + "openSidebar": "サイドバーを開く", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "メソッド", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "送信中...", + "send": "送信", + "invalidJsonBodyHelp": "送信できません: JSONボディが無効です" + }, + "requestTabs": { + "params": "パラメータ", + "headers": "ヘッダー", + "body": "ボディ", + "auth": "認証", + "bodyType": "ボディタイプ", + "selectType": "タイプを選択", + "bodyNone": "なし", + "bodyJson": "JSON", + "bodyText": "テキスト", + "noBodyHint": "このリクエストにはボディがありません", + "switchToJson": "JSONボディに切り替え", + "authType": "認証タイプ", + "noAuth": "認証なし", + "bearerToken": "Bearerトークン", + "basicAuth": "Basic認証", + "apiKey": "APIキー", + "labelToken": "トークン", + "placeholderBearerToken": "Bearerトークン", + "labelUsername": "ユーザー名", + "placeholderUsername": "ユーザー名", + "labelPassword": "パスワード", + "placeholderPassword": "パスワード", + "labelKey": "キー", + "placeholderApiKeyName": "キー名(例:X-Api-Key)", + "labelValue": "値", + "placeholderValue": "値", + "addTo": "追加先", + "header": "ヘッダー", + "queryParams": "クエリパラメータ", + "noAuthHint": "このリクエストは認証を使用しません。" + }, + "responsePanel": { + "emptyTitle": "探索する準備はできましたか?", + "emptyHint": "URLを入力して「送信」をクリックすると、ここにレスポンスデータが表示されます。", + "responsePreviewAlt": "レスポンスプレビュー", + "previewUnavailable": "このコンテンツタイプのプレビューは利用できません", + "status": "ステータス", + "time": "時間", + "size": "サイズ", + "copyBody": "ボディをコピー", + "downloadResponse": "レスポンスをダウンロード", + "bodyTab": "ボディ", + "previewTab": "プレビュー", + "headersTab": "ヘッダー", + "binaryRawView": "バイナリデータはraw表示できません。", + "errorStatusLabel": "エラー", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "新しいタブ" + }, + "collectionsSidebar": { + "title": "APIリソース", + "newCollection": "新しいコレクション", + "tabCollections": "コレクション", + "tabHistory": "履歴", + "noCollectionsTitle": "コレクションなし", + "noCollectionsHint": "APIリクエストを整理するコレクションを作成します。", + "createCollection": "コレクションを作成", + "newFolder": "新しいフォルダー", + "rename": "名前を変更", + "delete": "削除", + "emptyCollection": "空のコレクション", + "recentRequests": "最近のリクエスト", + "clearAll": "すべてクリア", + "noHistoryTitle": "履歴はまだありません", + "noHistoryHint": "リクエストを送信すると、ここに表示されます。", + "dialogNewFolderTitle": "新しいフォルダー", + "dialogNewFolderDescription": "リクエストを整理するための新しいフォルダーを作成します。", + "labelFolderName": "フォルダー名", + "placeholderFolderName": "マイフォルダー", + "createFolder": "フォルダーを作成", + "dialogNewCollectionTitle": "新しいコレクション", + "dialogNewCollectionDescription": "新しいトップレベルコレクションを作成します。", + "labelCollectionName": "コレクション名", + "placeholderCollectionName": "マイコレクション", + "dialogRenameCollectionTitle": "コレクション名を変更", + "dialogRenameCollectionDescription": "コレクションの新しい名前を入力してください。", + "placeholderRenameCollection": "コレクション名", + "renameAction": "名前を変更", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "新しいフォルダー", + "delete": "削除", + "emptyFolder": "空のフォルダー" + }, + "environmentManager": { + "noEnvironment": "環境なし", + "manageDialogTitle": "環境を管理", + "placeholderNewEnv": "新しい環境", + "variablesHeading": "{name}の変数", + "addVariable": "変数を追加", + "columnVariable": "変数", + "columnValue": "値", + "placeholderKey": "キー", + "placeholderValue": "値", + "selectEnvToEdit": "編集する環境を選択してください" + }, + "codeGenerator": { + "triggerTitle": "コードを生成", + "dialogTitle": "コードを生成", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "コピー", + "errorGenerating": "コード生成エラー" + }, + "keyValueEditor": { + "columnKey": "キー", + "columnValue": "値", + "placeholderKey": "キー", + "placeholderValue": "値", + "addItem": "項目を追加" + }, + "importCurl": { + "trigger": "cURLをインポート", + "dialogTitle": "cURLをインポート", + "dialogDescription": "cURLコマンドを貼り付けて、新しいリクエストとしてインポートします。", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "インポート" + }, + "saveRequest": { + "trigger": "保存", + "dialogTitle": "リクエストを保存", + "dialogDescription": "このリクエストをコレクションフォルダーに保存します。", + "labelName": "リクエスト名", + "placeholderName": "マイリクエスト", + "labelFolder": "フォルダー", + "placeholderFolder": "フォルダーを選択", + "save": "保存" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "編集モード", + "viewOnlyMode": "閲覧モード", + "insertComponent": "コンポーネントを挿入", + "addUnorderedList": "箇条書きリストを追加", + "addOrderedList": "番号付きリストを追加", + "addTable": "テーブルを追加" + }, + "addBlock": "ブロックを追加", + "media": { + "popoverTitle": "メディアを追加", + "singleImage": "単一画像", + "singleImageDesc": "画像を1枚アップロード", + "multipleImages": "複数画像", + "multipleImagesDesc": "画像グリッドをアップロード", + "video": "動画", + "videoDesc": "動画ファイルをアップロード", + "uploadingAlt": "アップロード中...", + "uploadFailedAlt": "アップロードに失敗しました" + }, + "export": { + "fabAria": "コードをエクスポート", + "title": "コードをエクスポート", + "description": "エディターコンテンツのHTMLまたはJSON出力をコピー", + "preview": "プレビュー", + "html": "HTML", + "json": "JSON", + "previewOptions": "プレビューオプション", + "enhanceSpaces": "スペースを強化", + "livePreview": "レンダリングされたHTMLのライブプレビュー", + "htmlTailwind": "Tailwind CSSクラス付きHTML", + "editorJson": "エディター状態のJSON", + "copyHtml": "HTMLをコピー", + "copyJson": "JSONをコピー", + "copied": "コピーしました!" + }, + "template": { + "fabTitle": "テンプレートを切り替え", + "title": "テンプレートを選択", + "description": "あらかじめ用意されたレイアウトとコンテンツで素早く始めましょう", + "catAll": "すべてのテンプレート", + "catProductivity": "生産性", + "catCreative": "クリエイティブ", + "catBusiness": "ビジネス", + "catPersonal": "パーソナル", + "noTemplates": "このカテゴリにテンプレートが見つかりません", + "confirmTitle": "既存のコンテンツを置換しますか?", + "confirmDescription": "エディターに既存のコンテンツがあります。このテンプレートを適用すると、現在のコンテンツがすべて置換されます。", + "cannotUndo": "この操作は取り消せません。", + "cancel": "キャンセル", + "replaceContent": "コンテンツを置換" + }, + "command": { + "searchPlaceholder": "コマンドを検索...", + "noResults": "コマンドが見つかりません。", + "turnInto": "変換", + "h1": { + "label": "見出し1", + "description": "大きなセクション見出し", + "keywords": "h1,heading,title,large,見出し" + }, + "h2": { + "label": "見出し2", + "description": "中サイズのセクション見出し", + "keywords": "h2,heading,subtitle,見出し" + }, + "h3": { + "label": "見出し3", + "description": "小さなセクション見出し", + "keywords": "h3,heading,subheading,見出し" + }, + "h4": { + "label": "見出し4", + "description": "最小のセクション見出し", + "keywords": "h4,heading,見出し" + }, + "h5": { + "label": "見出し5", + "description": "より小さな見出し", + "keywords": "h5,heading,見出し" + }, + "h6": { + "label": "見出し6", + "description": "最も小さな見出し", + "keywords": "h6,heading,見出し" + }, + "p": { + "label": "段落", + "description": "通常のテキスト段落", + "keywords": "p,paragraph,text,normal,段落" + }, + "code": { + "label": "コードブロック", + "description": "コードスニペット", + "keywords": "code,codeblock,snippet,pre,コード" + }, + "blockquote": { + "label": "引用", + "description": "ブロック引用", + "keywords": "quote,blockquote,citation,引用" + }, + "li": { + "label": "箇条書きリスト", + "description": "箇条書きの項目", + "keywords": "list,bullet,unordered,ul,li,リスト" + }, + "ol": { + "label": "番号付きリスト", + "description": "番号付きの項目", + "keywords": "list,numbered,ordered,ol,li,リスト" + }, + "img": { + "label": "画像", + "description": "画像のアップロードまたは埋め込み", + "keywords": "image,img,picture,photo,upload,画像" + }, + "video": { + "label": "動画", + "description": "動画のアップロードまたは埋め込み", + "keywords": "video,vid,movie,mp4,upload,動画" + }, + "table": { + "label": "テーブル", + "description": "テーブルを作成", + "keywords": "table,grid,rows,columns,cells,テーブル" + } + }, + "toasts": { + "tableImported": "テーブルをインポートしました", + "tableImportedDesc": "Markdownテーブルが正常にインポートされました", + "mediaPasted": "メディアを貼り付けました", + "pasteMixed": "画像{imageCount}枚と動画{videoCount}本を正常に貼り付けました。", + "pasteImages": "画像{count}枚を正常に貼り付けました。", + "pasteVideos": "動画{count}本を正常に貼り付けました。", + "pasteFailed": "貼り付けに失敗しました", + "unexpectedError": "予期しないエラーが発生しました", + "uploadFailed": "アップロードに失敗しました" + }, + "blockBackground": { + "menuItem": "背景色を変更", + "dialogTitle": "ブロックの背景色", + "dialogDescription": "このブロックの背景色を選択してください", + "tabPreset": "プリセットカラー", + "tabCustom": "カスタムカラー", + "sectionHeading": "背景色", + "apply": "適用", + "colors": { + "none": "なし", + "red": "赤", + "orange": "オレンジ", + "yellow": "黄", + "green": "緑", + "blue": "青", + "indigo": "インディゴ", + "purple": "紫", + "pink": "ピンク", + "teal": "ティール", + "cyan": "シアン", + "gray": "グレー" + } + }, + "tableDialog": { + "title": "テーブルを作成", + "description": "新しいテーブルを作成するか、Markdownからインポート", + "markdownCheckbox": "Markdownテーブルがあります", + "pasteMarkdownLabel": "Markdownテーブルを貼り付け", + "markdownPlaceholder": "| ヘッダー1 | ヘッダー2 | ヘッダー3 |\n|----------|----------|----------|\n| セル1 | セル2 | セル3 |\n| セル4 | セル5 | セル6 |", + "markdownHint": "Markdownテーブルを上に貼り付けてください。ヘッダー行とセパレーター行を含む必要があります。", + "rows": "行", + "columns": "列", + "maxDimensions": "最大:20行 × 10列", + "cancel": "キャンセル", + "importTable": "テーブルをインポート", + "createTable": "テーブルを作成" + }, + "tableParse": { + "errors": { + "minRows": "テーブルにはヘッダー行とセパレーター行が最低限必要です", + "pipeFormat": "テーブルの行は | で始まり | で終わる必要があります", + "headerMinCol": "ヘッダー行には少なくとも1列が必要です", + "separatorRow": "2行目はセパレーターである必要があります(例:|---|---|)", + "rowColumnMismatch": "行{row}は{got}列ですが、{expected}列が期待されます", + "parseFailed": "Markdownテーブルの解析に失敗しました" + } + }, + "link": { + "editLink": "リンクを編集", + "addLink": "リンクを追加", + "selectedText": "選択テキスト:「{text}」", + "linkUrl": "リンクURL", + "urlPlaceholder": "https://example.com", + "update": "更新", + "addLinkButton": "リンクを追加", + "toastApplied": "リンクを適用しました", + "toastLinkedTo": "リンク先:{url}", + "toastRemoved": "リンクを削除しました", + "toastRemovedDesc": "選択範囲からリンクが削除されました" + }, + "customClass": { + "devMode": "開発モード", + "toggleDevModeAria": "開発モードを切り替え", + "searchPlaceholderDev": "クラスを検索...(例:'text', 'bg', 'flex')", + "searchPlaceholderUser": "スタイルを検索...(例:'red', 'bold', 'shadow')", + "appliesTitle": "適用:{className}", + "noMatches": "「{query}」に一致するクラスが見つかりません", + "toastClassReplaced": "クラスを置換しました", + "toastClassReplacedDesc": "「{replaced}」を「{className}」に置換しました", + "toastCustomClassApplied": "カスタムクラスを適用しました", + "toastAppliedClass": "適用したクラス:{className}" + }, + "insertComponents": { + "title": "コンポーネントを挿入", + "description": "ドキュメントに挿入するコンポーネントを選択してください", + "noComponents": "利用可能なコンポーネントはありません", + "categories": { + "media": "メディア", + "layout": "レイアウト", + "interactive": "インタラクティブ", + "decoration": "デコレーション" + }, + "freeImage": { + "name": "自由配置画像", + "description": "自由に配置やサイズ変更ができる画像を追加" + } + } + }, + "JsonFormatter": { + "title": "JSON フォーマッター", + "description": "テキスト表示とツリー表示で JSON を整形・検証・編集します。", + "documentNameText": "テキストドキュメント {n}", + "documentNameTree": "ツリードキュメント {n}", + "newDocument": "新規", + "copy": "コピー", + "save": "保存", + "load": "読み込み", + "saving": "保存中...", + "leftDocNameLabel": "左ドキュメント名", + "rightDocNameLabel": "右ドキュメント名", + "toastCopiedText": "テキストパネルをコピーしました", + "toastCopiedTree": "ツリーパネルをコピーしました", + "toastCopyFailed": "JSONのコピーに失敗しました", + "toastLoginRequired": "ドキュメントを保存するにはログインしてください", + "toastSaved": "ドキュメントを保存しました", + "toastSaveFailed": "ドキュメントの保存に失敗しました", + "format": "整形", + "compact": "圧縮", + "sortKeys": "キーをソート", + "repair": "修復", + "query": "クエリ", + "toastInvalidJson": "無効な JSON", + "toastAlreadyValid": "JSON はすでに有効です", + "toastRepaired": "JSON を修復しました", + "toastRepairFailed": "JSON を修復できませんでした" + }, + "JwtDecoder": { + "title": "JWTデコーダー", + "subtitle": "ヘッダーとペイロードを確認します。有効期限は exp クレームを使用します。サーバーへ送信されず、署名は検証されません。", + "tokenLabel": "トークン", + "clear": "クリア", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "コピー", + "expiryLabel": "有効期限 (exp)", + "signaturePresent": "署名あり(未検証)", + "noExp": "ペイロードに exp クレームがありません。", + "status": { + "expired": "期限切れ", + "valid": "有効" + }, + "fields": { + "unix": "Unix", + "local": "ローカル", + "utc": "UTC" + }, + "claims": { + "issuedAt": "発行時刻 (iat)", + "notBefore": "有効開始 (nbf)" + }, + "panels": { + "header": "ヘッダー", + "payload": "ペイロード" + }, + "errors": { + "empty": "デコードする JWT を貼り付けてください。", + "segments": "JWT には少なくともヘッダーとペイロード(2 セグメント)が必要です。", + "base64": "ヘッダーまたはペイロードを base64url デコードできませんでした。" + }, + "relative": { + "future": "{n}{unit}後", + "past": "{n}{unit}前" + }, + "units": { + "second": "{count, plural, one {秒} other {秒}}", + "minute": "{count, plural, one {分} other {分}}", + "hour": "{count, plural, one {時間} other {時間}}", + "day": "{count, plural, one {日} other {日}}" + }, + "tabs": { + "decode": "デコード", + "sign": "生成 / 署名" + }, + "signer": { + "algLabel": "アルゴリズム (alg)", + "tabs": { + "payload": "ペイロード", + "header": "ヘッダー" + }, + "payloadLabel": "ペイロード JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "ヘッダー JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "シークレット (HS*)", + "secretPlaceholder": "HMAC シークレットを貼り付け…", + "privateKeyLabel": "秘密鍵 PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "\"none\" は署名なしトークンを作成します。安全ではないためテスト用途のみにしてください。", + "sign": "署名", + "verify": "署名を検証", + "outputLabel": "トークン(コンパクト JWS)", + "outputPlaceholder": "「署名」をクリック…", + "copyToken": "トークンをコピー", + "copied": "コピーしました", + "verifyKeyLabel": "検証キー(任意)", + "verifyKeyPlaceholder": "RS*: 公開鍵(SPKI)PEM を貼り付け。HS*: 空欄でシークレットを再利用。", + "verifyKeyHint": "空欄の場合、検証は署名キーを使用します(HS* に便利)。", + "verifyStatus": { + "valid": "署名は有効です", + "invalid": "署名は無効です" + }, + "securityNote": "すべてブラウザ内でローカル実行され、キーやトークンはアップロードされません。" + }, + "signErrors": { + "invalidJson": "ヘッダーとペイロードは有効な JSON である必要があります。", + "unsupportedAlg": "未対応のアルゴリズムです。", + "missingKey": "このアルゴリズムには署名キーが必要です。", + "cryptoFailed": "署名に失敗しました。キー形式を確認して再試行してください。" + }, + "verifyErrors": { + "empty": "検証する JWT を入力してください。", + "segments": "署名検証には JWT が 3 セグメントである必要があります。", + "unsupportedAlg": "ヘッダー内の alg が未対応、または欠落しています。", + "missingKey": "このアルゴリズムには検証キーが必要です。", + "base64": "JWT のヘッダーまたは署名をデコードできませんでした。", + "cryptoFailed": "検証に失敗しました。キー形式を確認して再試行してください。" + } + }, + "CertificatePemDecoder": { + "title": "証明書 / PEM デコーダ", + "subtitle": "X.509 証明書 (-----BEGIN CERTIFICATE-----) または CSR (-----BEGIN CERTIFICATE REQUEST-----) を貼り付けます。サブジェクト、発行者、日付、SAN はローカルで解析され、アップロードは行われません。", + "pemLabel": "PEM 入力", + "clear": "クリア", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "解析中…", + "copyHex": "フィンガープリントをコピー(コロンなしの16進)", + "errors": { + "empty": "デコードする PEM 証明書または CSR を貼り付けてください。", + "noBlock": "証明書または CSR の PEM ブロックが見つかりません。", + "parseFailed": "この PEM データを解析できませんでした。" + }, + "block": { + "title": "PEM ブロック {index}", + "certificate": "X.509 証明書", + "csr": "証明書署名要求" + }, + "fields": { + "subject": "サブジェクト", + "issuer": "発行者", + "notBefore": "有効開始前は無効", + "notAfter": "有効終了後は無効", + "serial": "シリアル番号(16進)", + "sha256": "SHA-256 フィンガープリント", + "sans": "サブジェクト代替名", + "sansNone": "SAN 拡張はありません。", + "signatureAlgorithm": "署名アルゴリズム", + "csrIssuerNote": "CSR に発行者は含まれません(発行された証明書に記載されます)。", + "csrValidityNote": "CSR に有効期間は含まれません(発行された証明書に記載されます)。" + }, + "sanType": { + "dns": "DNS", + "dn": "ディレクトリ名", + "email": "メール", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "登録 ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQLフォーマッター", + "subtitle": "MySQL / PostgreSQL / SQLite のSQLを整形します。ブラウザ内で実行され、アップロードされません。", + "dialectLabel": "方言", + "keywordsLabel": "キーワード", + "indentLabel": "インデント", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "大文字", + "lower": "小文字", + "preserve": "保持" + }, + "spaces": "{n} スペース", + "format": "整形", + "clear": "クリア", + "counter": "{current} / {max} 文字", + "inputPanel": "入力", + "formattedPanel": "整形結果", + "copy": "コピー", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "「整形」をクリック…", + "errors": { + "tooLong": "入力は {max} 文字までです。", + "couldNotFormat": "このSQLを整形できませんでした。" + } + }, + "YamlFormatter": { + "title": "YAMLフォーマッター", + "subtitle": "YAMLをブラウザ内で整形・検証・変換します。アップロードされません。", + "outputLabel": "出力", + "outputModes": { + "yaml": "YAML(整形済み)", + "json": "JSON" + }, + "indentLabel": "インデント", + "spaces": "{n} スペース", + "format": "整形", + "clear": "クリア", + "copy": "コピー", + "counter": "{current} / {max} 文字", + "inputPanel": "入力", + "outputPanel": "出力", + "errors": { + "tooLong": "入力は {max} 文字以内にしてください。", + "couldNotFormat": "このYAMLを解析できませんでした。" + } + }, + "GraphqlFormatter": { + "title": "GraphQL フォーマッター", + "subtitle": "ブラウザで GraphQL のクエリ・ミューテーション・サブスクリプションを整形します。アップロードは行われません。", + "tabs": { + "format": "整形", + "build": "クエリビルダー" + }, + "outputModeLabel": "出力", + "outputModes": { + "pretty": "整形表示", + "minify": "圧縮" + }, + "format": "整形", + "clear": "クリア", + "counter": "{current} / {max} 文字", + "inputPanel": "入力", + "outputPanel": "出力", + "copy": "コピー", + "builderHint": "ひな形を組み立てて入力エディターに反映します。変数定義は GraphQL の構文(例: $id: ID!)で、括弧は任意です。", + "builder": { + "operation": "オペレーション", + "operationName": "オペレーション名", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "変数定義", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "セレクションセット", + "apply": "入力に反映", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "入力は {max} 文字までです。", + "couldNotFormat": "この GraphQL を解析または出力できませんでした。", + "emptySelection": "適用する前にセレクションセットを入力してください。" + } + }, + "RegexTester": { + "title": "正規表現テスター", + "subtitle": "JavaScript 形式の正規表現をテキストに対してライブ実行します。一致箇所はハイライトされ、ブラウザ外へ送信されません。", + "patternLabel": "パターン", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g グローバル", + "i": "i 大文字小文字を無視", + "m": "m 複数行", + "s": "s dotAll", + "u": "u Unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(空)", + "noFlags": "—", + "matches": "{count, plural, one {一致} other {一致}}", + "testTextLabel": "テストテキスト", + "counter": "{current} / {max}", + "clear": "クリア", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "テキストは {max} 文字までです。", + "invalid": "正規表現が無効です。", + "runaway": "{max} 件で停止しました(暴走している可能性があります)。" + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "タイムスタンプ変換", + "subtitle": "Unix秒(10桁以下)またはミリ秒(それ以上の整数)、ISO-8601、またはブラウザが解釈できる文字列に対応します。すべてローカルで動作します。", + "inputLabel": "入力", + "placeholder": "1744200000 または 2025-04-09T12:00:00Z", + "now": "今", + "copy": "コピー", + "hint": "値を貼り付けるか をクリックしてください。", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "相対", + "human": "表示" + }, + "fields": { + "seconds": "秒", + "milliseconds": "ミリ秒", + "utc": "UTC", + "local": "ローカル", + "distance": "差" + }, + "errors": { + "empty": "Unixタイムスタンプ、ISO文字列、または日付テキストを入力してください。", + "invalidNumber": "数値のタイムスタンプが無効です。", + "outOfRange": "JavaScript Date の範囲外です。", + "couldNotParse": "Unix/ISO-8601/ブラウザ日付として解析できませんでした。" + } + }, + "HttpStatusCodes": { + "title": "HTTPステータスコード", + "subtitle": "HTTPレスポンスのステータスコード(1xx5xx)を検索・参照できます。短い説明とRFC参照付き。ローカルで動作します。", + "header": { + "title": "HTTPステータスコード", + "description": "HTTPレスポンスコードの検索可能なリファレンス。説明とRFC参照付き。" + }, + "searchPlaceholder": "検索: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "クラスで絞り込み…", + "classFilterAll": "すべてのクラス", + "resultsCount": "{total}件中{count}件", + "table": { + "code": "Code", + "phrase": "フレーズ", + "description": "説明", + "copy": "コピー" + }, + "noResults": "「{query}」に一致する項目がありません。", + "specPrefix": "仕様:", + "copied": "コピーしました" + }, + "CronBuilder": { + "title": "Cron 式ビルダー", + "subtitle": "標準の5フィールドcron: minute hour day month weekday。パーサーは内部で seconds=0 を使用します。式エディターでは任意の6フィールド(sec min hour dom mon dow)も利用できます。", + "tabs": { + "builder": "ビジュアルビルダー", + "expression": "式" + }, + "presetsLabel": "プリセット", + "fieldsLabel": "フィールド", + "rawExpressionLabel": "生の式", + "expressionHint": "フォーカスを外すと解析してビルダーに同期します。{count} {count, plural, one {トークン} other {トークン}}。", + "currentExpressionLabel": "現在の式", + "copy": "コピー", + "quickPick": "クイック選択", + "emptyExpression": "—", + "plainLanguageLabel": "説明", + "nextRunsLabel": "次の実行(ローカル)", + "fields": { + "minute": "分", + "hour": "時", + "dayOfMonth": "日", + "month": "月", + "dayOfWeek": "曜日" + }, + "presets": { + "everyMinute": "毎分", + "every5Minutes": "5分ごと", + "every15Minutes": "15分ごと", + "everyHour": "毎時(:00)", + "every6Hours": "6時間ごと", + "dailyMidnight": "毎日0:00", + "daily0900": "毎日09:00", + "weekdays0900": "平日09:00", + "weeklySunday": "毎週(日 00:00)", + "monthlyFirst": "毎月(1日 00:00)", + "yearlyJan1": "毎年(1/1 00:00)" + }, + "picks": { + "minute": { + "every": "毎分" + }, + "hour": { + "every": "毎時" + }, + "dom": { + "any": "任意の日", + "last": "月末" + }, + "month": { + "every": "毎月" + }, + "dow": { + "any": "任意の曜日", + "weekdays": "平日", + "weekend": "週末" + } + }, + "months": { + "jan": "1月", + "feb": "2月", + "mar": "3月", + "apr": "4月", + "may": "5月", + "jun": "6月", + "jul": "7月", + "aug": "8月", + "sep": "9月", + "oct": "10月", + "nov": "11月", + "dec": "12月" + }, + "weekdays": { + "sun": "日", + "mon": "月", + "tue": "火", + "wed": "水", + "thu": "木", + "fri": "金", + "sat": "土" + }, + "errors": { + "fieldCount": "5フィールド(minute hour day month weekday)または秒を含む6フィールドが必要です。", + "invalid": "cron式が無効です。" + } + }, + "Base64": { + "title": "Base64 エンコード / デコード", + "subtitle": "テキストを Base64 にエンコード、または Base64 文字列をデコードします", + "upload": "アップロード", + "clear": "クリア", + "modes": { + "encode": "エンコード", + "decode": "デコード" + }, + "swapTitle": "入力と出力を入れ替え", + "panels": { + "plainText": "プレーンテキスト", + "base64Input": "Base64 入力", + "base64Output": "Base64 出力", + "decodedText": "デコード結果" + }, + "placeholders": { + "encode": "Base64 にエンコードするテキストを入力…", + "decode": "デコードする Base64 を貼り付け…" + }, + "dropHere": "ここにファイルをドロップ", + "charCount": "{count} 文字", + "download": { + "title": "出力をダウンロード", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "クリップボードにコピー", + "outputPlaceholder": "ここに出力が表示されます…", + "errors": { + "invalidBase64": "無効な Base64 文字列です。入力を確認してください。", + "encodeFailed": "エンコードに失敗しました。" + } + }, + "UrlEncode": { + "title": "URL エンコード / デコード", + "subtitle": "encodeURIComponent / decodeURIComponent(UTF‑8)でパーセントエンコード/デコードします", + "upload": "アップロード", + "clear": "クリア", + "modes": { + "encode": "エンコード", + "decode": "デコード" + }, + "swapTitle": "入力と出力を入れ替え", + "panels": { + "plainText": "プレーンテキスト", + "encodedInput": "エンコード入力", + "encodedOutput": "エンコード出力", + "decodedText": "デコード結果" + }, + "placeholders": { + "encode": "パーセントエンコードするテキストを入力…", + "decode": "デコードする percent-encoded テキストを貼り付け…" + }, + "dropHere": "ここにファイルをドロップ", + "charCount": "{count} 文字", + "download": { + "title": "出力をダウンロード", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "クリップボードにコピー", + "outputPlaceholder": "ここに出力が表示されます…", + "errors": { + "invalidEncoding": "パーセントエンコードが無効です。不完全な % ペアや不正なエスケープを確認してください。", + "encodeFailed": "エンコードできません(入力に無効な Unicode)。" + } + }, + "UrlParser": { + "title": "URL パーサー", + "subtitle": "URL をプロトコル、ホスト、パス、クエリパラメータ、ハッシュ(フラグメント)に分解します。", + "upload": "アップロード", + "clear": "クリア", + "panels": { + "input": "URL 入力", + "output": "解析結果" + }, + "placeholders": { + "input": "URL を貼り付け(例: https://example.com/path?foo=bar#section)またはホスト/パス(例: example.com/path)…" + }, + "dropHere": "ここにファイルをドロップ", + "charCount": "{count} 文字", + "outputPlaceholder": "解析されたコンポーネントがここに表示されます…", + "fields": { + "protocol": "プロトコル", + "host": "ホスト", + "path": "パス", + "hash": "ハッシュ", + "queryParams": "クエリパラメータ" + }, + "paramCount": "{count} 件", + "noParams": "クエリパラメータはありません。", + "paramKey": "キー", + "paramValue": "値", + "copyTitle": "クリップボードにコピー", + "copyParamTitle": "key=value としてコピー", + "download": { + "title": "JSON をダウンロード", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL が無効です。ホスト名(example.com)を含め、文字を確認してください。" + } + }, + "UuidGenerator": { + "title": "UUID / ULID ジェネレーター", + "subtitle": "ブラウザで RFC 9562 UUID または ULID を生成します。1回あたり最大 {max} 件まで一括エクスポートできます。", + "format": "形式", + "kindHints": { + "ulid": "並べ替え可能で URL セーフな 26 文字 ID", + "uuid4": "ランダム(RFC 9562)", + "uuid7": "時系列順(Unix ms + ランダム)", + "uuid1": "MAC/時刻ベース(RFC 9562)", + "uuid6": "並べ替えられたグレゴリオ暦時刻ベース", + "uuid3": "名前 + 名前空間の MD5 ハッシュ", + "uuid5": "名前 + 名前空間の SHA‑1 ハッシュ", + "nil": "ニル UUID(すべてゼロ、RFC 9562)" + }, + "countLabel": "件数(1~{max})", + "nameLabel": "名前(入力文字列)", + "namePlaceholder": "例: my.app/resource", + "bulkHint": "一括実行では #0#1… を付与し、各 ID を一意にします(v3/v5 は決定的)。", + "namespace": "名前空間", + "namespaceOptions": { + "dns": "DNS(RFC 4122)", + "url": "URL(RFC 4122)", + "custom": "カスタム UUID" + }, + "customNamespaceLabel": "名前空間 UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "生成", + "output": "出力", + "lines": "{count} 行", + "download": { + "title": ".txt をダウンロード", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "すべてコピー", + "uppercaseToggle": "大文字で出力", + "hyphensToggle": "ハイフンを表示", + "copyJsonTitle": "JSON 配列としてコピー", + "outputPlaceholder": "「生成」をクリック…", + "errors": { + "invalidCustomNamespace": "カスタム名前空間は有効な UUID である必要があります。", + "missingName": "UUID v3 と v5 には名前が必要です。", + "unknown": "ID を生成できませんでした。" + } + }, + "SecretApiKeyGenerator": { + "title": "シークレット / APIキー ジェネレーター", + "subtitle": "長さとアルファベットを指定して暗号論的乱数文字列を生成します。1回あたり最大 {max} 件まで一括出力。UUID / ULID ジェネレーターと併用できます。", + "alphabetLabel": "アルファベット", + "alphabetHint": "重複文字は除去されます。各文字は crypto.getRandomValues と棄却サンプリングで一様に選ばれます(モジュロバイアスなし)。", + "alphabetCount": "ユニーク文字数 {count}", + "presetsLabel": "プリセット", + "preset": { + "base64url": "Base64url", + "alphanumeric": "英数字", + "hex": "16進(小文字)", + "asciiPrintable": "ASCII印字可能" + }, + "lengthLabel": "1件あたりの長さ(1–{maxLen})", + "countLabel": "件数(1–{maxBulk})", + "generate": "生成", + "output": "出力", + "lines": "{count} 行", + "download": { + "title": ".txt をダウンロード", + "filename": "secrets.txt" + }, + "copyTitle": "すべてコピー", + "outputPlaceholder": "「生成」をクリック…", + "entropy": "エントロピー {bits} ビット", + "strength": { + "weak": "弱い", + "fair": "普通", + "strong": "強い" + }, + "excludeAmbiguous": "紛らわしい文字を除外", + "excludeAmbiguousHint": "{chars} を削除します — 誤読しやすい、またはシェルに不向きな文字です。", + "prefixLabel": "キーの接頭辞(任意)", + "errors": { + "emptyAlphabet": "アルファベットに1文字以上追加してください。", + "alphabetTooLong": "重複除去後のアルファベットは最大 {maxAlpha} 文字までです。", + "lengthOutOfRange": "長さは 1〜{maxLen} の範囲にしてください。", + "countOutOfRange": "件数は 1〜{maxBulk} の範囲にしてください。", + "unknown": "シークレットを生成できませんでした。" + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum ジェネレーター", + "subtitle": "レイアウトやモックアップ用のラテン語プレースホルダーテキストを生成します。", + "paragraphs": "段落", + "sentences": "文", + "words": "単語", + "count": "数", + "generate": "生成", + "copy": "コピー", + "startWith": "'Lorem ipsum...' で始める", + "outputPlaceholder": "出力はここに表示されます...", + "outputType": "出力タイプ", + "bulletList": "箇条書きリスト", + "htmlMarkup": "HTMLマークアップ", + "htmlMarkupHint": "出力を段落で囲むか、箇条書きモードではリストを使用", + "output": "出力", + "download": "ダウンロード" + }, + "ColorPicker": { + "title": "カラーピッカーとコンバーター", + "subtitle": "HEX、RGB、HSLを変換し、ハーモニックパレットを作成します。すべての処理はブラウザ内で行われます。", + "nativePicker": "ネイティブピッカー", + "chooseColor": "色を選択", + "eyeDropper": "スポイト", + "invalidHex": "無効なHEXカラーです。", + "cssValues": "CSS値", + "palettes": "パレット", + "copied": "コピー済み", + "clickToCopy": "クリックしてコピー", + "copyAllHex": "すべてのHEXをコピー", + "copy": "コピー", + "copyColor": "{color} をコピー", + "paletteShades": "シェード", + "paletteComplementary": "補色", + "paletteTriadic": "トライアド", + "paletteAnalogous": "アナログ", + "paletteSplit": "分裂補色", + "paletteSquare": "スクエア" + }, + "ContrastChecker": { + "title": "コントラストチェッカー", + "subtitle": "前景色と背景色の WCAG 2.1 コントラスト比を計算します。通常テキスト・大きいテキストの AA / AAA を表示。すべてブラウザ内で処理されます。", + "foreground": "テキスト / 前景", + "background": "背景", + "hexPlaceholder": "#RRGGBB", + "swap": "色を入れ替え", + "chooseForeground": "前景色を選ぶ", + "chooseBackground": "背景色を選ぶ", + "eyeDropper": "スポイト", + "ratio": "コントラスト比", + "copyRatio": "比率をコピー", + "copied": "コピーしました", + "previewHeading": "プレビュー", + "previewBody": "いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせすん。", + "previewCaption": "標準サイズのサンプルテキスト。", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — 通常テキスト(4.5:1)", + "levelAALarge": "AA — 大きいテキスト(3:1)", + "levelAAANormal": "AAA — 通常テキスト(7:1)", + "levelAAALarge": "AAA — 大きいテキスト(4.5:1)", + "pass": "適合", + "fail": "不適合", + "invalidHex": "無効な HEX 色です。", + "largeTextHint": "大きいテキスト:18pt 以上の通常、または 14pt 以上の太字(WCAG の定義)。" + }, + "DiffChecker": { + "title": "差分チェッカー", + "subtitle": "2つのテキストを並べて行単位で比較します。すべてブラウザ内で処理されます。", + "rowsCount": "{count} 行", + "truncatedWarning": "最初の {max} 行を表示中。", + "limitExceeded": "各側は {max} 文字に制限されています。", + "clearBoth": "両方をクリア", + "original": "オリジナル", + "modified": "変更後", + "originalPlaceholder": "元のテキストを貼り付け…", + "modifiedPlaceholder": "変更後のテキストを貼り付け…", + "comparisonLabel": "並べて比較", + "emptyState": "両方のパネルにテキストを入力して比較してください。" + }, + "JsonSchemaGenerator": { + "title": "JSONスキーマジェネレーター", + "subtitle": "有効なJSONを貼り付けてJSONスキーマや型付きモデルを生成します。すべてブラウザ内で処理されます。", + "languageLabel": "出力言語", + "inputLabel": "JSON入力", + "inputPlaceholder": "JSONをここに貼り付け…", + "outputLabel": "生成された出力", + "outputPlaceholder": "生成されたスキーマや型がここに表示されます…", + "parseError": "無効なJSON", + "clear": "クリア", + "copy": "コピー", + "copied": "クリップボードにコピーしました", + "copyFailed": "コピーできませんでした", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSONスキーマ(Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "エディタを読み込み中…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "画像をBase64に変換", + "subtitle": "画像をドロップし、データ URI または生の Base64 文字列を取得します。すべてがブラウザ内に残ります。", + "upload": "画像をアップロードする", + "clear": "クリア", + "modes": { + "dataUri": "データURI", + "rawString": "生のBase64" + }, + "panels": { + "imageInput": "画像入力", + "output": "Base64出力" + }, + "dropHere": "ここに画像をドロップ", + "charCount": "{count} 文字", + "placeholders": { + "uploadImage": "画像をドラッグ アンド ドロップするか、アップロードをクリックして開始します。" + }, + "fileTooLarge": "ファイルが大きすぎます。処理中にブラウザがフリーズする可能性があります。", + "copyTitle": "クリップボードにコピー", + "download": { + "title": "テキストをダウンロードする", + "filename": "画像-base64.txt" + } + }, + "ImageCompressor": { + "title": "画像圧縮", + "subtitle": "JPEG / PNG / WebP を品質調整でローカル圧縮。アップロードはありません。", + "upload": "画像をアップロード", + "clear": "クリア", + "dropHere": "ここに画像をドロップ", + "placeholders": { + "uploadImage": "ドラッグ&ドロップするか、クリックしてファイルを選択。" + }, + "fileTooLarge": "ファイルが大きすぎます(最大 {maxMb} MB)。", + "unsupportedFormat": "未対応の形式です。JPEG、PNG、WebP、GIF、AVIF、BMP を使用してください。", + "compressFailed": "圧縮できませんでした。別のファイルや形式を試してください。", + "panels": { + "original": "元画像", + "compressed": "圧縮プレビュー" + }, + "outputFormat": "出力形式", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "品質", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG はブラウザ上で可逆圧縮です。品質スライダーは JPEG と WebP のみ有効です。", + "sizes": { + "original": "元: {size}", + "compressed": "圧縮後: {size}", + "saved": "{percent}% 削減" + }, + "largerWarning": "出力が元より大きいです。形式や品質を変えてください。", + "download": "ダウンロード", + "downloadFilename": "compressed-image", + "processing": "圧縮中…", + "emptyCompressed": "圧縮プレビューはここに表示されます。" + }, + "CssGradientBuilder": { + "title": "CSS グラデーション ビルダー", + "subtitle": "角度コントロール、カラーストップ、ワンクリック CSS コピーを備えたビジュアル CSS グラデーション ビルダー。", + "preview": "グラデーションのプレビュー", + "settings": "設定", + "type": "タイプ", + "linear": "リニア", + "radial": "ラジアル", + "angle": "角度", + "stops": "カラーストップ", + "color": "色", + "position": "位置 (%)", + "addStop": "ストップの追加", + "deleteStop": "ストップの削除", + "output": "CSS出力", + "copy": "CSSをコピーする", + "copied": "コピーしました!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore ジェネレーター", + "subtitle": "特定の技術スタックまたは組み合わせに対して、マージされた .gitignore ファイルを即座に生成します。", + "searchPlaceholder": "検索スタック (例: Node、Python、macOS)...", + "noResults": "スタックが見つかりませんでした。", + "selectedStacks": "選択されたスタック", + "output": "出力のプレビュー", + "copy": "クリップボードにコピー", + "copied": "コピーしました!", + "download": ".gitignore をダウンロード", + "downloading": "ダウンロード中...", + "errorFetching": "上位リストの取得に失敗しました。", + "errorGenerating": "選択したスタックから .gitignore を生成できませんでした。", + "generating": "生成中...", + "emptyState": ".gitignore を生成するには、1 つ以上の技術スタックを選択します。" + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV または Excel をアップロードし、JSON を編集してから CSV または XLSX をダウンロード。表形式データのみ。Excel の日付セルは JSON では ISO 形式になります。", + "dropTitle": "CSV または Excel ファイルをドロップ", + "dropHint": ".xlsx / .xls は先頭のシートを使用します。CSV はヘッダー行が必要です。", + "chooseFile": "ファイルを選択", + "jsonLabel": "JSON(オブジェクトの配列)", + "jsonPlaceholder": "オブジェクトの JSON 配列を貼り付けるか、CSV / Excel を取り込み…", + "loadSample": "サンプルを読み込む", + "actions": { + "format": "JSON を整形", + "clear": "クリア", + "copy": "コピー", + "copied": "コピーしました", + "downloadCsv": "CSV をダウンロード", + "downloadXlsx": "Excel をダウンロード" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "データ", + "toastLoadedCsv": "CSV をエディターに読み込みました", + "toastLoadedExcel": "Excel をエディターに読み込みました", + "toastFormatted": "JSON を整形しました", + "toastExportedCsv": "CSV をダウンロードしました", + "toastExportedXlsx": "Excel をダウンロードしました", + "toastCopied": "クリップボードにコピーしました", + "footnote": "すべてブラウザ内で処理されます。JSON は空でないオブジェクトの配列である必要があります。ネストした値は CSV セル内で JSON 文字列化されます。", + "errors": { + "unsupportedFile": ".csv、.xlsx、または .xls ファイルを使用してください。", + "parseFailed": "このファイルを解析できませんでした。", + "invalidJson": "無効な JSON", + "exportFailed": "エクスポートに失敗しました", + "copyFailed": "コピーできませんでした" + } + }, + "SnippetManager": { + "title": "コードスニペットマネージャー", + "subtitle": "ブラウザにスニペットを保存するか、サインインしてアカウントと同期します。表示と編集の切り替え、ワンクリックコピー、言語の選択または自動検出、JSON・SQL・Monaco 対応コードの整形ができます。", + "snippets": "スニペット", + "newSnippet": "新しいスニペット", + "newSnippetTitle": "新しいスニペット", + "defaultSnippetTitle": "ようこそ", + "defaultSnippetCode": "// コードスニペットへようこそ\n// 表示と編集、コピー、整形、自動言語をお試しください\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "スニペットを検索…", + "searchAria": "スニペットを検索", + "emptySearch": "一致するスニペットがありません。", + "snippetTitle": "タイトル", + "snippetTitlePlaceholder": "スニペット名", + "language": "言語", + "detectedHint": "ハイライト: {lang}", + "badgeAuto": "自動 · {lang}", + "modeView": "表示", + "modeEdit": "編集", + "copy": "コピー", + "copied": "コピーしました", + "format": "整形", + "cancel": "キャンセル", + "deleteTitle": "スニペットを削除しますか?", + "deleteDescription": "「{title}」はこのブラウザから削除されます。", + "deleteConfirm": "削除", + "editorAria": "スニペットコードエディター", + "editorLoading": "エディターを読み込み中…", + "toastCopied": "クリップボードにコピーしました", + "toastCopyFailed": "コピーできませんでした", + "toastNothingToCopy": "コピーする内容がありません", + "toastFormatted": "整形しました", + "toastFormatFailed": "整形できませんでした", + "toastFormatJsonInvalid": "無効な JSON — 先に構文を修正してください", + "toastFormatUnsupported": "この言語用の整形はありません — JSON か SQL、または編集モードをお試しください", + "toastDeleted": "スニペットを削除しました", + "toastLoadFailed": "サーバーからスニペットを読み込めませんでした。", + "toastSyncFailed": "サーバーと同期できませんでした。", + "languages": { + "auto": "自動検出", + "plaintext": "プレーンテキスト", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL クライアント", + "dialogTitle": "SQL 接続", + "mobileSheetTitle": "SQL エクスプローラー", + "newTabLabel": "新しいクエリタブ", + "vaultLockedTitle": "ボルトがロックされています", + "vaultLockedDesc": "ボルトをアンロックして保存済み接続にアクセスします。", + "heroTitle": "SQL クライアント", + "heroDesc": "PostgreSQL、MySQL、MariaDB に接続します。認証情報はブラウザで暗号化されます。", + "heroAddConnection": "接続を追加", + "selectTableTitle": "テーブルを選択するかクエリタブを開く", + "selectTableDesc": "サイドバーのテーブルをクリックするか ⌘↩ を使用します。", + "toastLoadFail": "接続の読み込みに失敗しました。" + }, + "connection": { + "titleNew": "新しい SQL 接続", + "titleEdit": "接続を編集", + "labelDbType": "データベースの種類", + "labelName": "接続名", + "placeholderName": "本番データベース", + "labelHostPort": "ホストとポート", + "labelDatabase": "データベース", + "placeholderDatabase": "mydb", + "labelUsername": "ユーザー名", + "labelPassword": "パスワード", + "labelSsl": "SSL / TLS を有効にする", + "sslDesc": "サーバーへの接続を暗号化する", + "securityNote": "認証情報はストレージ前にブラウザで AES-256 暗号化されます", + "btnTest": "接続テスト", + "btnSave": "保存して接続", + "btnUpdate": "更新", + "btnCancelEdit": "編集をキャンセル", + "savedConnections": "保存済み接続", + "emptySaved": "保存済み接続はここに表示されます", + "confirmDelete": "この接続を削除しますか?", + "toastFillFields": "テスト前にホストとデータベースを入力してください。", + "toastConnectOk": "接続しました!{version}", + "toastConnectFail": "接続失敗:{message}", + "toastUnlockVault": "まずボルトをアンロックしてください。", + "toastFieldsRequired": "ホストとデータベースは必須です。", + "toastUpdated": "接続を更新しました。", + "toastSaved": "接続を保存しました。", + "toastSaveFail": "保存失敗:{message}", + "toastDeleted": "接続を削除しました。", + "toastDeleteFail": "接続を削除できませんでした。" + }, + "sidebar": { + "title": "SQL エクスプローラー", + "tooltipAddConnection": "接続を追加", + "tooltipRefreshSchema": "スキーマを更新", + "noConnections": "保存済み接続はありません", + "toastSchemaFail": "スキーマの読み込みに失敗:{message}" + }, + "editor": { + "btnRun": "実行", + "executing": "クエリを実行中…", + "emptyTitle": "クエリを実行して結果を表示", + "emptyHint": "⌘↩ を押すか実行をクリック", + "toastNoConnection": "アクティブな接続がありません。" + }, + "results": { + "filterPlaceholder": "結果をフィルタ…", + "rowCount": "{count} 行", + "rowCountFiltered": "{filtered} / {total} 行", + "exportCsv": "CSV", + "noRows": "行が返されませんでした" + } + }, + "QrCodeGenerator": { + "title": "QRコード生成", + "subtitle": "テキストやURLをPNGのQRコードに変換します。描画はすべてブラウザ内で行われます。", + "contentLabel": "内容", + "contentPlaceholder": "https://example.com、プレーンテキスト、Wi‑Fi文字列、vCard…", + "styleLabel": "スタイル", + "dotTypeLabel": "ドットの形", + "dotTypeSquare": "四角", + "dotTypeDots": "ドット", + "dotTypeRounded": "角丸", + "dotTypeExtraRounded": "超角丸", + "dotTypeClassy": "クラシー", + "dotTypeClassyRounded": "クラシー角丸", + "cornerSquareLabel": "コーナー四角", + "cornerSquareDot": "ドット", + "cornerSquareSquare": "四角", + "cornerSquareExtraRounded": "超角丸", + "cornerDotLabel": "コーナードット", + "cornerDotDot": "ドット", + "cornerDotSquare": "四角", + "eccLabel": "誤り訂正", + "eccL": "低 (~7%)", + "eccM": "中 (~15%)", + "eccQ": "四分位 (~25%)", + "eccH": "高 (~30%)", + "marginLabel": "余白(モジュール)", + "widthLabel": "画像の幅", + "widthEffective": "{px} px で描画", + "darkColor": "暗いモジュール", + "lightColor": "明るいモジュール", + "resetColors": "色をリセット", + "logoLabel": "中央ロゴ", + "logoMarginLabel": "余白", + "uploadLogo": "画像をアップロード", + "removeLogo": "ロゴを削除", + "downloadPng": "PNGをダウンロード", + "downloadSvg": "SVGをダウンロード", + "copyImage": "画像をコピー", + "copied": "コピーしました", + "clear": "クリア", + "previewAlt": "QRコードのプレビュー", + "emptyHint": "内容を入力または貼り付けてプレビューを表示します。", + "filename": "qr-code.png", + "errors": { + "generationFailed": "QRコードを生成できませんでした。文字を短くするか誤り訂正を下げてください。", + "copyUnsupported": "お使いのブラウザは画像のクリップボードコピーに対応していません。", + "copyFailed": "クリップボードにコピーできませんでした。" + } + }, + "IpSubnetCalculator": { + "title": "IP/サブネット計算", + "subtitle": "ブラウザ内で IPv4/IPv6 の CIDR を計算—外部通信なし。", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "ネットワーク", + "placeholderIpv4": "10.0.0.0/24 または 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 または 2001:db8::1/64", + "hintIpv4": "CIDR(例 /24)または IPv4+ドット付きサブネットマスク。", + "hintIpv6": "プレフィックス長(例 /64)。省略時は /128。", + "copyValue": "値をコピー", + "rows": { + "cidr": "CIDR", + "network": "ネットワークアドレス", + "netmask": "サブネットマスク", + "wildcard": "ワイルドカードマスク", + "broadcast": "ブロードキャスト", + "firstHost": "先頭ホスト", + "lastHost": "末尾ホスト", + "totalAddresses": "総アドレス数", + "usableHosts": "usable ホスト数", + "prefixLength": "プレフィックス長(ビット)", + "networkIpv6": "ネットワークアドレス", + "lastIpv6": "範囲の最終アドレス", + "totalAddressesIpv6": "サブネット内アドレス数" + }, + "usableNote": { + "pointToPoint": "/31 のポイントツーポイントは両方をホストとして使用(RFC 3021)。", + "singleHost": "/32 は単一ホスト。" + }, + "ipv6RangeHint": { + "normal": "数学上の全範囲を表示。ルーター運用ルールは異なる場合があります。", + "single": "/128 は単一アドレス。", + "full": "/0 は IPv6 全体。" + }, + "errors": { + "invalidIpv4": "IPv4 が無効です。CIDR または アドレス+マスクを試してください。", + "invalidIpv6": "IPv6 が無効です。例: 2001:db8::/48 の形式で入力。" + } + }, + "HashGenerator": { + "title": "ハッシュ生成", + "subtitle": "テキストまたはファイル向けの MD5・SHA-1・SHA-2 ダイジェスト、パスワード向けの bcrypt(テキスト入力)。すべてブラウザ内でローカル計算されます。", + "algorithmLabel": "アルゴリズム", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "テキスト", + "modeFile": "ファイル", + "inputLabelText": "入力", + "placeholderText": "テキストを入力または貼り付け…", + "chooseFile": "ファイルを選択", + "clearFile": "クリア", + "fileHint": "ファイルはメモリ上のみ読み込み、最大 {maxMb} MB。アップロードしません。", + "outputLabel": "ダイジェスト(hex)", + "copyHash": "ダイジェストをコピー", + "copied": "コピーしました", + "byteCount": "{count, plural, other {# バイト}}", + "charCount": "{count, plural, other {# 文字}}", + "emptyHint": "テキストを入力するかファイルを選んでください。", + "securityNote": "新規のセキュリティ用途には MD5 と SHA-1 は非推奨です。SHA-256 または SHA-512 を推奨。", + "errors": { + "fileTooLarge": "ファイルが {maxMb} MB を超えています。", + "fileReadFailed": "ファイルを読めませんでした。" + }, + "bcryptRoundsLabel": "コスト(ラウンド)", + "bcryptRoundsHint": "値が大きいほど遅くなりブルートフォースに強くなります。一般的な既定は 10 です。", + "fileTabDisabledTitle": "bcrypt はテキストのパスワードのみをハッシュし、任意のファイルは扱いません。", + "placeholderBcrypt": "ハッシュするパスワードを入力または貼り付け…", + "outputLabelBcrypt": "bcrypt ハッシュ", + "copyBcrypt": "ハッシュをコピー", + "emptyHintBcrypt": "bcrypt ハッシュを計算するにはパスワードを入力してください。", + "securityNoteBcrypt": "bcrypt の出力には毎回ランダムなソルトが含まれます。同じパスワードでも毎回異なる文字列になります。本番では十分なコストを設定してください。", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "暗号化プレイグラウンド", + "subtitle": "ブラウザ上の AES-GCM:パスフレーズまたは生キーで JSON バンドルに暗号化し、ローカルで復号。学習向けで時には実用的 — アップロードはありません。", + "tabEncrypt": "暗号化", + "tabDecrypt": "復号", + "keyModeLabel": "鍵素材", + "modePassphrase": "パスフレーズ(PBKDF2 + AES-GCM)", + "modeRawKey": "生の AES 鍵(hex または Base64)", + "aesBitsLabel": "AES 鍵長", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "生キーのエンコード", + "encodingAuto": "自動(hex に見えれば hex、それ以外は Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "生キー", + "rawKeyPlaceholder": "32 バイト鍵は 64 文字の hex、または AES-256 なら Base64(長さは上の AES サイズに依存)。", + "rawKeyPlaceholderDecrypt": "バンドルに keyDerivation \"none\" がある場合のみ必要です。", + "passphraseLabel": "パスフレーズ", + "passphrasePlaceholder": "パスフレーズは鍵導出のためブラウザ内でのみ使われます。", + "passphrasePlaceholderDecrypt": "バンドルが PBKDF2-SHA256 の場合のみ必要です。", + "pbkdf2IterLabel": "PBKDF2 反復回数", + "pbkdf2Hint": "10,000〜2,000,000 に制限。既定は一般的な保管庫設定に合わせています。", + "plaintextLabel": "平文", + "plaintextPlaceholder": "暗号化するメッセージを入力または貼り付け…", + "bundleInputLabel": "暗号化バンドル(JSON)", + "bundlePlaceholder": "「暗号化」で得た JSON を貼り付け(v, alg, ivB64, cipherB64, …)。", + "bundleOutputLabel": "暗号化バンドル", + "plaintextOutputLabel": "平文", + "encryptButton": "暗号化", + "decryptButton": "復号", + "copyBundle": "JSON をコピー", + "copyPlaintext": "平文をコピー", + "copied": "コピーしました", + "emptyBundleHint": "暗号化後、暗号化された JSON がここに表示されます。", + "emptyDecryptHint": "復号されたテキストがここに表示されます。", + "decryptKeyHint": "keyDerivation が \"none\" なら生キー、PBKDF2-SHA256 ならパスフレーズを使います。バンドルにソルト、反復、IV、AES 長が含まれます。", + "securityNote": "鍵と平文はブラウザの外に出ません。本番の秘密には専用ツールを使ってください。", + "errors": { + "emptyKey": "AES サイズに合う生キーを入力してください。", + "emptyPassphrase": "パスフレーズを入力してください。", + "badKeyMaterial": "鍵を hex または Base64 として読み取れませんでした。", + "wrongKeyLength": "鍵長が選択した AES(16 / 24 / 32 バイト)と一致しません。", + "invalidJson": "有効な JSON ではありません。", + "invalidBundle": "この JSON はサポートされている暗号バンドルではありません。", + "decryptFailed": "復号に失敗しました — 秘密が違う、破損、またはパラメータ不一致。", + "encryptFailed": "暗号化に失敗しました。Web Crypto と入力を確認してください。" + } + }, + "HmacGenerator": { + "title": "HMACジェネレーター", + "subtitle": "Webhookの署名とAPI連携のテスト用。HMAC-SHA1/256/384/512 を hex または Base64 で出力—すべてブラウザ内で計算。", + "secretLabel": "秘密鍵", + "messageLabel": "メッセージ / ペイロード", + "placeholderSecret": "署名用シークレットを貼り付け…", + "placeholderMessage": "検証する生の本文または文字列を貼り付け(多くの場合Webhook本文のバイト列そのもの)。", + "algorithmLabel": "HMACアルゴリズム", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "出力形式", + "formatHex": "Hex(小文字)", + "formatBase64": "Base64", + "outputLabel": "署名", + "copyOutput": "署名をコピー", + "copied": "コピーしました", + "byteCountHint": "{count, plural, other {# バイト}} (UTF-8)", + "emptyHint": "HMACを計算するには秘密鍵を入力してください。", + "securityNote": "署名用シークレットは共有しないでください。データはアップロードされず、すべてブラウザ内で処理されます。", + "errors": { + "cryptoFailed": "HMACを計算できませんでした。Web Crypto の対応を確認して再試行してください。", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA コードジェネレーター", + "subtitle": "認証アプリの Base32 秘密鍵を貼り付けて、現在の6桁 RFC 6238 コード(SHA-1、30秒)を表示。ブラウザ内のみで処理—ログインやMFAのテストに便利です。", + "secretLabel": "秘密鍵(Base32)", + "placeholderSecret": "秘密鍵を貼り付け(A–Z と 2–7 の数字、認証アプリ設定時と同じ形式)…", + "codeLabel": "現在のコード", + "copyCode": "コードをコピー", + "copied": "コピーしました", + "nextIn": "{seconds} 秒後に更新", + "standardHint": "30秒タイムステップ、6桁、HMAC-SHA-1(Google Authenticator 互換)。", + "digitsLabel": "桁数", + "periodLabel": "周期", + "algorithmLabel": "アルゴリズム", + "periodSeconds": "{seconds}秒", + "emptyHint": "秘密鍵を貼り付けて6桁コードを生成します。", + "invalidHint": "コードを表示するには秘密鍵を修正してください。", + "securityNote": "パスワード同様に扱ってください。秘密鍵はブラウザ外に出ません。アップロードはありません。", + "importLabel": "otpauth:// URIからインポート", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "インポート", + "qrSectionTitle": "プロビジョニングQRコード", + "qrHint": "認証アプリ(Google Authenticator、1Password、Aegisなど)でスキャンしてこのシークレットを追加します。QRコードにはシークレットが符号化されています — パスワードと同様に扱ってください。", + "accountLabel": "アカウント", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "発行者(任意)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "QRコードを生成するには有効なシークレットを入力してください。", + "errors": { + "invalidSecret": "有効な Base32 秘密鍵ではありません。", + "cryptoFailed": "TOTP を計算できませんでした。Web Crypto を確認して再試行してください。", + "invalidUri": "有効な otpauth:// TOTP URI ではありません。", + "unsupportedUri": "この URI はツールが対応していない値を使用しています(桁数は 6 または 8、周期は 15、30、60 秒のいずれかである必要があります)。" + } + }, + "MarkdownPreview": { + "title": "Markdownプレビュー & HTML", + "subtitle": "HTMLエクスポートとHTML → Markdown変換を備えたライブMarkdownレンダラー。すべてブラウザで動作します。", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown入力", + "htmlInputLabel": "HTML入力", + "previewLabel": "プレビュー", + "htmlOutputLabel": "HTMLソース", + "mdOutputLabel": "Markdown出力", + "inputPlaceholder": "# こんにちは\\n\\nここに **markdown** を入力してください…", + "htmlInputPlaceholder": "HTMLをここに貼り付けてください…", + "copyHtml": "HTMLをコピー", + "copyMarkdown": "Markdownをコピー", + "exportHtml": "HTMLをエクスポート", + "copied": "コピーしました", + "charCount": "{count, plural, other {# 文字}}", + "emptyHint": "Markdownの入力を開始すると、ライブプレビューとHTML出力が表示されます。", + "emptyHintHtmlToMd": "HTMLを貼り付けてMarkdownに変換します。", + "securityNote": "すべてブラウザでローカルに実行されます。何もアップロードされません。" + }, + "NumberBaseConverter": { + "title": "基数変換", + "subtitle": "2~36進法の整数を変換。0x / 0b / 0o プレフィックスに対応。すべてブラウザ内で計算されます。", + "inputBaseLabel": "変換元の基数", + "outputBaseLabel": "変換先の基数", + "inputLabel": "入力値", + "placeholderInput": "選択した入力基数で整数を入力…", + "outputLabel": "変換結果", + "copyOutput": "結果をコピー", + "copied": "コピーしました", + "swap": "基数を入れ替え", + "byteCountHint": "{count, plural, other {# バイト}} (UTF-8 の入力長)", + "emptyHint": "変換する数値を入力してください。", + "note": "10を超える桁は A–Z(大文字小文字を区別しません)。スペースとアンダースコアは無視されます。", + "errors": { + "invalidNumber": "選択した入力基数では無効な値です。" + } + }, + "FormatConverter": { + "title": "フォーマット変換", + "subtitle": "JSON、YAML、TOML、XML間を任意の方向に変換。12通りすべての組み合わせに対応。すべてブラウザ上で動作します。", + "fromLabel": "変換元", + "toLabel": "変換先", + "swap": "入れ替え", + "swapTitle": "フォーマットを入れ替えて出力を入力として使用", + "copy": "コピー", + "copied": "コピーしました", + "charCount": "{count, plural, other {# 文字}}", + "emptyHint": "左側に入力を貼り付けるか入力すると、変換された出力が表示されます。", + "localNote": "すべてブラウザ上でローカルに動作します。何もアップロードされません。" + }, + "MimeTypeLookup": { + "title": "MIME タイプ検索", + "subtitle": "拡張子またはファイル名から MIME タイプ(メディアタイプ)を調べます。すべてローカルでブラウザ内にて実行されます。", + "inputLabel": "拡張子またはファイル名", + "placeholder": "例: .png、package.json、photo.jpeg", + "hint": "ヒント: 拡張子(ドットあり/なし)または完全なファイル名を貼り付けてください。", + "clear": "クリア", + "exampleFilename": "例のファイル名を使う", + "exampleExtension": "例の拡張子を使う", + "resultLabel": "結果", + "normalizedLabel": "正規化した入力:", + "mimeTypeLabel": "MIME タイプ", + "contentTypeLabel": "Content-Type ヘッダー", + "charsetLabel": "文字セット", + "copyMimeType": "MIME タイプをコピー", + "copy": "コピー", + "copied": "コピーしました", + "emptyHint": "拡張子またはファイル名を入力して MIME タイプを検索します。", + "notFound": "この入力に対応する MIME タイプが見つかりません。", + "localNote": "すべてブラウザ上でローカルに動作します。何もアップロードされません。" + }, + "UserAgentParser": { + "title": "User-Agent 解析", + "subtitle": "User-Agent(UA)文字列を貼り付けて、ブラウザ・OS・エンジン・デバイス・CPU の内訳を確認します。すべてブラウザ内でローカルに動作します。", + "inputLabel": "User-Agent 文字列", + "placeholder": "ここに User-Agent を貼り付けてください…", + "hint": "ヒント: UA はリクエストヘッダー、ログ、ブラウザの DevTools からコピーできます。", + "clear": "クリア", + "exampleDesktop": "デスクトップ例を使う", + "exampleMobile": "モバイル例を使う", + "resultLabel": "内訳", + "browserLabel": "ブラウザ", + "osLabel": "オペレーティングシステム", + "engineLabel": "エンジン", + "deviceTypeLabel": "デバイスタイプ", + "deviceVendorLabel": "メーカー", + "deviceModelLabel": "モデル", + "cpuArchLabel": "CPU アーキテクチャ", + "copyJson": "JSON をコピー", + "copy": "コピー", + "copied": "コピーしました", + "emptyHint": "User-Agent 文字列を貼り付けると内訳が表示されます。", + "localNote": "すべてブラウザ内でローカルに動作します。何もアップロードされません。" + }, + "MockDataGenerator": { + "title": "モックデータジェネレーター", + "subtitle": "列を定義し型を選び、シードや Postman、テスト用のデータをエクスポートします。最大 {max} 行、すべてブラウザ内で処理されアップロードはありません。", + "mockarooLink": "Mockaroo 風のワークフロー", + "rowsLabel": "行数", + "rowsHint": "タブを軽く保つため、1 回あたり最大 {max} 行です。", + "formatLabel": "形式", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "SQL INSERT", + "xml": "XML" + }, + "tableNameLabel": "SQL テーブル名", + "presetsLabel": "プリセット", + "presetPlaceholder": "プリセットを読み込み…", + "presetDefault": "スタート(ユーザー)", + "presetUsers": "ユーザーとロール", + "presetProducts": "EC の SKU", + "presetApiLog": "HTTP リクエストログ", + "generate": "生成", + "warningDuplicateNames": "列名が重複しています。JSON オブジェクトでは後の列が前を上書きします。名前を変更してください。", + "schemaHeading": "スキーマ", + "addField": "列を追加", + "removeField": "列を削除", + "duplicateField": "列を複製", + "colName": "名前", + "colType": "型", + "colOptions": "オプション", + "colBlank": "空欄 %", + "optMin": "最小", + "optMax": "最大", + "optDecimals": "小数", + "optDateFrom": "開始日", + "optDateTo": "終了日", + "optValues": "a, b, c", + "optStep": "刻み", + "optRowBase": "基準", + "output": "出力", + "outputPlaceholder": "「生成」をクリックしてデータを作成…", + "copy": "コピー", + "download": "ダウンロード", + "copied": "コピーしました", + "groups": { + "personal": "個人", + "location": "所在地", + "internetApi": "インターネットとAPI", + "numbersIds": "数値とID", + "dateTime": "日付と時刻", + "textDesign": "テキストとデザイン", + "custom": "カスタム" + }, + "fieldTypes": { + "first_name": "名", + "last_name": "姓", + "full_name": "氏名", + "email": "Email", + "username": "ユーザー名", + "password": "パスワード", + "phone": "電話", + "company": "会社", + "job_title": "役職", + "avatar_url": "アバターURL", + "city": "市区町村", + "country": "国", + "country_code": "国コード(ISO)", + "street_address": "住所", + "zip_code": "郵便番号", + "latitude": "緯度", + "longitude": "経度", + "url": "URL", + "domain": "ドメイン", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "整数", + "float": "浮動小数", + "boolean": "真偽値", + "uuid": "UUID", + "credit_card": "テスト用カード", + "semver": "SemVer", + "row_index": "行番号", + "sequence": "連番", + "date": "日付", + "datetime": "日時", + "unix_timestamp": "Unixタイムスタンプ", + "timezone": "タイムゾーン", + "word": "単語", + "sentence": "文", + "paragraph": "段落", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "通貨コード", + "language_code": "言語タグ", + "custom_list": "カスタム一覧" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG 最適化", + "subtitle": "SVG を貼り付け、ブラウザ上で SVGO により圧縮し、UTF-8 サイズを前後比較します。", + "inputLabel": "入力", + "outputLabel": "最適化後", + "placeholder": " の断片またはファイル全体を貼り付け…", + "outputPlaceholder": "最適化された SVG がここに表示されます…", + "chars": "{count} 文字 • {bytes} バイト(UTF-8)", + "preview": "プレビュー", + "localNote": "処理はすべてブラウザ内で行われます。アップロードはありません。", + "stats": { + "title": "サイズ(UTF-8)", + "before": "前", + "after": "後", + "savedBytes": "{count} バイト削減({percent}%)", + "noChange": "変化なし — すでに最小か、出力が空です。" + }, + "actions": { + "clear": "クリア", + "sample": "サンプルを読み込む", + "copy": "コピー", + "copied": "コピーしました", + "download": "ダウンロード" + }, + "errors": { + "optimizeFailed": "この SVG を最適化できませんでした。" + } + }, + "UnitConverter": { + "title": "単位変換器", + "subtitle": "43カテゴリにわたる323単位間の変換", + "category": "カテゴリ", + "from": "変換前", + "to": "変換後", + "value": "値", + "result": "結果", + "swapUnits": "単位を入れ替え", + "placeholder": "値を入力", + "copyResult": "結果をコピー", + "searchCategory": "カテゴリを検索...", + "selectCategory": "カテゴリを選択", + "noResults": "カテゴリが見つかりません。" + }, + "DockerComposeGenerator": { + "title": "Docker Compose ジェネレーター", + "subtitle": "代表的なデータベース、キャッシュ、メッセージキュー、リバースプロキシ、可観測性を選んで、ローカル開発向けの docker-compose.yml をコピーできます。", + "searchPlaceholder": "検索(postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "サービス", + "selectedCount": "{count, plural, other {# 件のサービス}}", + "outputTitle": "docker-compose.yml", + "copy": "コピー", + "copied": "コピーしました", + "download": "ダウンロード", + "emptyState": "Compose YAML を生成するサービスを 1 つ以上選んでください。", + "hintPorts": "デフォルトのホストポートは衝突を避ける配置です。バインドエラー時は変更してください。共有環境ではデモ用パスワードを必ず置き換えてください。", + "categories": { + "databases": "データベースとストア", + "cache": "キャッシュ・インメモリ", + "queues": "キューとメッセージング", + "search": "検索とログ", + "storage": "オブジェクトストレージ", + "proxy": "リバースプロキシと Ingress", + "observability": "メトリクスとトレース", + "mail": "メール(ローカル開発)", + "admin": "管理画面と BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "クリア" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey(Redis 互換)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda(Kafka 互換)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO(S3 互換)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit(SMTP キャプチャ)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault(開発サーバー)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg(Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "タイルをスライドさせてマージし、2048に到達します", + "score": "スコア", + "best": "おすすめ", + "newGame": "新しいゲーム", + "howToPlay": "タイルを移動するには、矢印キー(またはモバイルでスワイプ)を使用します。同じ番号のタイルがマージされます。2048年に到達して勝利しましょう!", + "youWin": "2048に到達しました!", + "keepGoing": "立ち止まるな", + "gameOver": "ゲームオーバー", + "tryAgain": "再試行してください。", + "gameOverMessage": "これ以上のムーブはありません。", + "undo": "元に戻す" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "個のステージが完了しました", + "time": "時刻", + "best": "おすすめ", + "difficulty": { + "easy": "イージー", + "medium": "中", + "hard": "ハード" + }, + "stage": "段階", + "solved": "解決済", + "score": "スコア", + "nextStage": "ステージ", + "replay": "リプレイ", + "erase": "删除", + "howToPlay": "セルをクリックし、数字をタップするか、1 -9を押します。BackspaceまたはDeleteを選択すると、セルがクリアされます。太字の数字は手がかりです。すべての行、列、および3 × 3ボックスに1 -9が繰り返されないように、グリッドを塗りつぶします。", + "pencil": "メモ", + "undo": "元に戻す", + "hint": "ヒント", + "hintsUsed": "ヒント使用", + "mistakes": "ミス", + "failed": "失敗!", + "mistakeMode": "3ミスモード" + }, + "Snake": { + "title": "スネーク", + "subtitle": "食べ物を食べ、成長し、壁と自分自身を避けよう", + "score": "スコア", + "best": "ベスト", + "startPrompt": "矢印キーを押してください", + "startButton": "ゲーム開始", + "gameOver": "ゲームオーバー", + "playAgain": "もう一度プレイ", + "howToPlay": "矢印キーまたはWASDで移動。食べ物を食べて成長しよう。50点ごとに速度が上がる。", + "diffEasy": "簡単", + "diffNormal": "普通", + "diffHard": "難しい", + "diffInfinite": "無限", + "orTap": "または下をタップ", + "howToPlayInfinite": "壁がループする — 壁での死なし。食べ物を食べて成長しよう。", + "shieldActive": "シールド有効", + "speedBoost": "スピードブースト", + "powerSpeed": "速度 x2", + "powerShield": "死を一回回避", + "powerShrink": "尾を短縮" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis接続", + "heroTitle": "Redis Commander", + "heroDesc": "Redisインスタンスに接続します。キーを閲覧し、値を編集し、コマンドを実行します。認証情報はブラウザで暗号化されます。", + "heroAddConnection": "接続を追加", + "vaultLockedTitle": "ボルトがロックされています", + "vaultLockedDesc": "保存された接続にアクセスするにはボルトをアンロックしてください。" + } + }, + "Minesweeper": { + "title": "マインスイーパー", + "subtitle": "地雷を爆発させずにすべての安全なマスを見つけよう", + "mines": "地雷", + "newGame": "新しいゲーム", + "win": "勝利!", + "loss": "ゲームオーバー!", + "playAgain": "もう一度プレイ", + "tryAgain": "再挑戦", + "startPrompt": "任意のマスをクリックして開始", + "difficulty": { + "beginner": "初級", + "intermediate": "中級", + "expert": "上級" + } + }, + "Tetris": { + "title": "テトリス", + "subtitle": "落ちてくるブロックを並べて行を完成させよう", + "next": "次", + "score": "スコア", + "best": "ベスト", + "lines": "ライン", + "level": "レベル", + "pause": "一時停止", + "resume": "再開", + "start": "スタート", + "restart": "リスタート", + "paused": "一時停止中", + "gameOver": "ゲームオーバー" + }, + "SshKeyGenerator": { + "title": "SSH / RSA 鍵ジェネレーター", + "subtitle": "ブラウザでEd25519またはRSA鍵ペアを生成します。OpenSSHとPEM形式で秘密鍵と公開鍵をダウンロードできます。何もアップロードされません。", + "keyTypeLabel": "鍵の種類", + "recommended": "推奨", + "ed25519Desc": "高速、コンパクト、モダン。GitHub、GitLab、および主要なSSHサーバーすべてで対応。", + "rsa2048Desc": "広く互換性があります。ほとんどの用途に安全です。", + "rsa4096Desc": "レガシーシステム向けに最大の互換性と強度。", + "commentLabel": "コメント(任意)", + "commentHint": "OpenSSH公開鍵の行に追加されます。", + "generateBtn": "鍵ペアを生成", + "generating": "生成中…", + "generatingLong": "生成中(しばらくかかる場合があります)…", + "copy": "コピー", + "download": "ダウンロード", + "privateKeyLabel": "秘密鍵(PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "公開鍵(OpenSSH形式)", + "publicKeySpkiLabel": "公開鍵(SPKI PEM)", + "browserNote": "すべての鍵はブラウザで生成されます。サーバーには何も送信されません。", + "errors": { + "generationFailed": "鍵の生成に失敗しました" + } + }, + "DnsLookup": { + "title": "DNS ルックアップ", + "subtitle": "任意のドメインの DNS レコードを照会 — A、AAAA、MX、TXT、NS、CNAME。", + "placeholder": "ドメインを入力(例: github.com)", + "lookupBtn": "検索", + "looking": "検索中…", + "refreshBtn": "更新", + "recordTypesLabel": "レコード種別", + "allBtn": "すべて", + "tryLabel": "試す:", + "errors": { + "lookupFailed": "DNS ルックアップに失敗しました" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt 生成・検証", + "subtitle": "bcrypt でパスワードをハッシュ化し、ハッシュを検証 — すべてブラウザ内で完結します", + "tabs": { + "hash": "ハッシュ", + "verify": "検証" + }, + "showPassword": "パスワードを表示", + "hidePassword": "パスワードを非表示", + "copy": "コピー", + "copied": "コピーしました", + "hash": { + "passwordLabel": "パスワード", + "passwordPlaceholder": "ハッシュ化するパスワードを入力…", + "costLabel": "コストファクター", + "costHint": "コストが高いほど、各ステップの処理量が倍増します。本番環境では 10〜12 が一般的です。", + "slowWarning": "コストファクターが 13 以上になると、計算に数秒かかることがあります。", + "generate": "ハッシュを生成", + "generating": "ハッシュ化中…", + "resultLabel": "Bcrypt ハッシュ", + "timeTaken": "{ms} ミリ秒", + "anatomy": { + "title": "ハッシュの構造", + "algorithm": "アルゴリズム", + "cost": "コスト", + "salt": "ソルト(22 文字)", + "hash": "ハッシュ(31 文字)" + } + }, + "verify": { + "passwordLabel": "パスワード", + "passwordPlaceholder": "確認するパスワードを入力…", + "hashLabel": "Bcrypt ハッシュ", + "hashPlaceholder": "$2b$10$…", + "idle": "パスワードと bcrypt ハッシュを入力して比較します。", + "invalidHash": "これは有効な bcrypt ハッシュではないようです。", + "checking": "比較中…", + "match": "一致 — このパスワードはこのハッシュを生成します。", + "noMatch": "不一致 — このパスワードはこのハッシュを生成しません。" + } + }, + "ChmodCalculator": { + "title": "Chmod 計算機", + "subtitle": "Unix ファイルのパーミッションをチェックボックス、8 進数、シンボリック表記の間で変換します", + "grid": { + "title": "パーミッション", + "read": "読み取り", + "write": "書き込み", + "execute": "実行", + "owner": "所有者", + "group": "グループ", + "others": "その他" + }, + "special": { + "title": "特殊ビット", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "8 進数", + "placeholder": "755 または 4755", + "invalid": "3 桁または 4 桁の 8 進数(0-7)を入力してください。" + }, + "symbolic": { + "label": "シンボリック", + "placeholder": "rwxr-xr-x", + "invalid": "rwxr-xr-x のように 9 文字を入力してください(s/S、t/T も可)。" + }, + "output": { + "title": "コマンド", + "copy": "コピー", + "copied": "コピーしました" + }, + "explain": { + "line": "{cls}は{perms}ことができます。", + "none": "{cls}にはパーミッションがありません。", + "setuid": "Setuid: ファイルは所有者の権限で実行されます。", + "setgid": "Setgid: ファイルはグループの権限で実行されます(ディレクトリ内の新規ファイルはそのグループを継承します)。", + "sticky": "Sticky: このディレクトリ内では、ファイルの所有者のみが削除や名前変更を行えます。" + }, + "presets": { + "title": "よく使うプリセット", + "octal": "8 進数", + "permissions": "パーミッション", + "description": "一般的な用途", + "p644": "通常のファイル: 所有者が書き込み、全員が読み取り。", + "p755": "実行ファイルとディレクトリ: 全員が読み取りと移動が可能。", + "p600": "プライベートファイル: 所有者のみが読み書き。", + "p700": "プライベートな実行ファイルとディレクトリ: 所有者のみ。", + "p664": "グループが書き込み可能なファイル。", + "p775": "グループが書き込み可能なディレクトリと実行ファイル。", + "p400": "所有者のみ読み取り可(例: SSH 秘密鍵)。" + } + }, + "CodeScreenshot": { + "title": "コードスクリーンショット", + "subtitle": "コードを美しく共有しやすい画像に変換", + "languageLabel": "言語", + "autoDetect": "自動検出", + "backgroundLabel": "背景", + "paddingLabel": "パディング", + "filenameLabel": "ファイル名", + "filenamePlaceholder": "example.ts", + "windowLabel": "ウィンドウのドット", + "themeLabel": "ウィンドウのテーマ", + "themeDark": "ダーク", + "themeLight": "ライト", + "scaleLabel": "書き出し倍率", + "copyImage": "画像をコピー", + "copied": "コピーしました", + "exportPng": "PNG を書き出し", + "inputLabel": "コード", + "detected": "検出: {language}", + "clear": "クリア", + "placeholder": "ここにコードを貼り付け…", + "previewLabel": "プレビュー", + "emptyPreview": "コードのプレビューがここに表示されます", + "exportFailed": "画像の書き出しに失敗しました", + "copyUnsupported": "このブラウザでは画像のコピーがサポートされていません", + "copyFailed": "画像のコピーに失敗しました", + "backgrounds": { + "ocean": "オーシャン", + "sunset": "サンセット", + "candy": "キャンディ", + "forest": "フォレスト", + "midnight": "ミッドナイト", + "gold": "ゴールド", + "aurora": "オーロラ", + "slate": "スレート", + "transparent": "透明" + } + }, + "CssGenerators": { + "title": "CSS ジェネレーター", + "subtitle": "Box-shadow、border-radius、流動的な clamp() — CSS をコピー。", + "preview": "プレビュー", + "output": "CSS", + "copy": "コピー", + "copied": "コピーしました", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "流動的な clamp()" + }, + "boxShadow": { + "presets": "プリセット", + "layer": "レイヤー", + "addLayer": "レイヤーを追加", + "removeLayer": "レイヤーを削除", + "x": "X", + "y": "Y", + "blur": "ぼかし", + "spread": "広がり", + "color": "色", + "alpha": "不透明度", + "inset": "内側", + "presetNames": { + "subtleCard": "控えめなカード", + "elevated": "浮き上がり", + "hardOffset": "ハードオフセット", + "neonGlow": "ネオングロー", + "inner": "内側", + "layeredSmooth": "重ねたスムーズ" + } + }, + "borderRadius": { + "modeLabel": "モード", + "modes": { + "simple": "角ごと", + "blob": "オーガニックブロブ" + }, + "unit": "単位", + "linkCorners": "角を連動", + "randomize": "ランダム", + "horizontal": "水平方向の半径", + "vertical": "垂直方向の半径", + "corners": { + "tl": "左上", + "tr": "右上", + "br": "右下", + "bl": "左下" + } + }, + "clamp": { + "minFont": "最小フォント (px)", + "maxFont": "最大フォント (px)", + "minViewport": "最小ビューポート (px)", + "maxViewport": "最大ビューポート (px)", + "rootFont": "ルートフォント (px)", + "viewportWidth": "シミュレートするビューポート", + "computedSize": "計算されたサイズ", + "slope": "傾き", + "intercept": "切片", + "previewText": "The quick brown fox", + "invalid": "有効で異なるビューポートの範囲を入力してください。" + } + }, + "ExifViewer": { + "title": "EXIF ビューアー・除去", + "subtitle": "写真のメタデータを確認し、ブラウザ内で除去します", + "dropTitle": "ここに画像をドロップ", + "dropHint": "またはクリックして選択 — 画像がデバイスから送信されることはありません", + "browse": "画像を選択", + "formats": "JPEG、PNG、WebP、HEIC、TIFF", + "parsing": "メタデータを読み取り中…", + "parseFailed": "この画像の読み取りに失敗しました", + "clear": "画像を削除", + "gpsWarning": "GPS 位置情報を含みます", + "openMap": "OpenStreetMap で表示", + "noMetadata": "この画像に埋め込まれたメタデータは見つかりませんでした。", + "facts": { + "name": "名前", + "type": "種類", + "dimensions": "寸法", + "size": "サイズ" + }, + "groups": { + "camera": "カメラ", + "exposure": "露出", + "timestamps": "タイムスタンプ", + "gps": "GPS 位置情報", + "software": "ソフトウェア・その他" + }, + "gpsCard": { + "latitude": "緯度", + "longitude": "経度", + "altitude": "高度" + }, + "raw": { + "title": "すべてのメタデータ", + "copyJson": "JSON をコピー", + "copied": "コピーしました" + }, + "remove": { + "title": "メタデータを除去", + "jpegDescription": "画像を再圧縮せずに、EXIF、XMP、IPTC、コメントを可逆的に除去します。", + "reencodeDescription": "画像を再エンコードして埋め込まれたメタデータを除去します。ピクセルが再圧縮されます。", + "keepIcc": "カラープロファイル (ICC) を保持", + "stripButton": "メタデータを除去", + "reencodeButton": "再エンコードして除去", + "unsupported": "このファイル形式ではメタデータの除去はサポートされていません。", + "failed": "メタデータの除去に失敗しました", + "before": "変更前", + "after": "変更後", + "download": "クリーンな画像をダウンロード" + } + }, + "FaviconGenerator": { + "title": "Favicon ジェネレーター", + "subtitle": "1 枚の画像から favicon とアプリアイコンを作成", + "dropTitle": "ここに画像をドロップ", + "dropHint": "PNG、JPEG、SVG — クリックして選択", + "browse": "画像を選択", + "clear": "画像を削除", + "backgroundLabel": "背景", + "transparent": "透明", + "solidColor": "単色", + "cornerLabel": "角の形状", + "cornerNone": "四角", + "cornerRounded": "角丸", + "cornerCircle": "円形", + "maskPaddingLabel": "マスカブルの余白", + "maskable": "maskable", + "previewLabel": "プレビュー", + "copy": "コピー", + "headSnippet": "HTML head スニペット", + "downloadIco": "favicon.ico をダウンロード", + "downloadZip": "すべてダウンロード (.zip)" + }, + "JsonDiff": { + "title": "JSON 差分", + "subtitle": "2 つの JSON ドキュメントを意味的・構造的に比較 — キーの順序や書式は無視されます", + "options": { + "ignoreArrayOrder": "配列の順序を無視" + }, + "loadBothSamples": "サンプルを読み込む", + "sample": "サンプル", + "format": "整形", + "clear": "クリア", + "formatError": "整形できません: 入力が有効な JSON ではありません", + "placeholder": "ここに JSON を貼り付けまたは入力…", + "panels": { + "left": "元 (左)", + "right": "変更後 (右)", + "results": "差分" + }, + "summary": { + "added": "{count} 件追加", + "removed": "{count} 件削除", + "changed": "{count} 件変更" + }, + "types": { + "added": "追加", + "removed": "削除", + "changed": "変更" + }, + "copyChanges": "JSON としてコピー", + "copied": "コピーしました", + "empty": "両方のパネルに JSON を貼り付けて差分を確認します。", + "identical": "2 つのドキュメントは意味的に同一です。" + }, + "JsonpathPlayground": { + "title": "JSONPath プレイグラウンド", + "subtitle": "JSONPath または JMESPath で JSON ドキュメントをクエリし、結果をリアルタイムで確認します", + "engineLabel": "エンジン", + "queryLabel": "クエリ", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "チートシート", + "cheats": { + "jpAllTitles": "すべての書籍タイトル", + "jpAllAuthors": "ドキュメント内のすべての著者", + "jpFirstBook": "最初の書籍", + "jpLastBook": "最後の書籍", + "jpSlice": "最初の 2 冊 (スライス)", + "jpFilterPrice": "40 未満の書籍", + "jpFilterInStock": "在庫のある書籍のタイトル", + "jpRecursive": "すべての値を再帰的に取得", + "jmAllTitles": "すべての書籍タイトル", + "jmFirstAuthor": "最初の書籍の著者", + "jmLastBook": "最後の書籍", + "jmSlice": "最初の 2 冊 (スライス)", + "jmFilterPrice": "40 未満の書籍のタイトル", + "jmCount": "在庫のある書籍を集計", + "jmProjection": "各書籍を新しいオブジェクトに再構成", + "jmSort": "価格順に並べたタイトル" + }, + "panels": { + "input": "JSON 入力", + "results": "結果" + }, + "sample": "サンプル", + "format": "整形", + "clear": "クリア", + "formatError": "整形できません: 入力が有効な JSON ではありません", + "placeholder": "ここに JSON を貼り付けまたは入力…", + "outputPlaceholder": "一致した結果がここに表示されます…", + "matchCount": "{count, plural, one {# 件の一致} other {# 件の一致}}", + "copy": "コピー", + "copied": "コピーしました" + }, + "KeycodeInspector": { + "title": "キーコードインスペクター", + "subtitle": "任意のキーを押して JavaScript のキーボードイベントを確認します", + "capture": { + "prompt": "任意のキーを押してください…", + "ready": "待機中 — キーボードの任意のキーを押してください", + "clickToFocus": "ここをクリックしてフォーカスし、任意のキーを押してください" + }, + "hero": { + "spaceKey": "(スペース)", + "keyLabel": "event.key", + "repeat": "リピート" + }, + "copyJson": "JSON をコピー", + "copiedJson": "イベント JSON をクリップボードにコピーしました", + "deprecated": "非推奨", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "標準", + "left": "左", + "right": "右", + "numpad": "テンキー", + "unknown": "不明" + }, + "history": { + "title": "履歴", + "clear": "履歴をクリア", + "empty": "キー入力がここに表示されます。" + } + }, + "MarkdownTableGenerator": { + "title": "Markdown テーブルジェネレーター", + "subtitle": "グリッドを編集し、スプレッドシートから貼り付け、きれいに整列した Markdown または HTML テーブルをコピーします", + "addRow": "行を追加", + "addColumn": "列を追加", + "importLabel": "インポート", + "importPlaceholder": "TSV (Excel / Google スプレッドシート) または CSV をここに貼り付け…", + "importButton": "グリッドにインポート", + "pasteClipboard": "クリップボードから貼り付け", + "importEmpty": "インポートするものがありません — まず行を貼り付けてください", + "clipboardError": "クリップボードから読み取れませんでした", + "clear": "クリア", + "panels": { + "editor": "テーブルエディター", + "output": "出力" + }, + "align": { + "left": "左揃え", + "center": "中央揃え", + "right": "右揃え" + }, + "headerPlaceholder": "ヘッダー", + "cellPlaceholder": "セル", + "removeRow": "行を削除", + "removeColumn": "列を削除", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "コピー", + "copied": "コピーしました" + }, + "TimezoneConverter": { + "title": "タイムゾーン変換", + "subtitle": "会議の計画のために、ある瞬間を世界中の時刻に変換します。", + "dateTimeLabel": "日付と時刻", + "now": "現在", + "sourceZoneLabel": "変換元のタイムゾーン", + "selectZone": "タイムゾーンを選択", + "searchZone": "タイムゾーンを検索...", + "noZone": "タイムゾーンが見つかりません。", + "scrubLabel": "時刻をずらす", + "reset": "リセット", + "targetsLabel": "変換先のタイムゾーン", + "addZone": "タイムゾーンを追加", + "remove": "削除", + "localBadge": "ローカル", + "invalidDate": "有効な日付と時刻を入力してください。", + "bands": { + "work": "勤務時間 (09:00–18:00)", + "edge": "境界の時間帯 (早朝 / 夜)", + "night": "夜間" + } + }, + "TokenCounter": { + "title": "LLM トークンカウンター", + "subtitle": "トークンを数え、チャンクを可視化し、API コストを見積もります。", + "inputLabel": "テキスト", + "sample": "サンプル", + "clear": "クリア", + "placeholder": "トークンを数えるテキスト、コード、JSON を貼り付け...", + "encoding": "エンコーディング", + "loading": "トークナイザーを読み込み中...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o シリーズ)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "トークン", + "characters": "文字", + "words": "単語", + "ratio": "トークン/単語", + "ratioSub": "概算" + }, + "claudeRow": "Claude (概算)", + "estimate": "見積もり", + "viz": { + "title": "トークンの可視化", + "toggle": "表示", + "hint": "切り替えて各トークンをチャンクとして色付けします。", + "empty": "テキストを入力してトークンを可視化します。", + "truncated": "{total} 個中、最初の {limit} 個のトークンを表示しています。" + }, + "cost": { + "title": "コスト計算機", + "disclaimer": "編集可能なデフォルト値 — 最新の料金を確認してください", + "model": "モデル", + "expectedOutput": "想定される出力トークン", + "customInput": "入力 $/MTok", + "customOutput": "出力 $/MTok", + "inputCost": "入力コスト", + "outputCost": "出力コスト", + "total": "合計", + "per1000": "1000 回の呼び出しあたり" + } + }, + "WebhookTester": { + "title": "Webhook テスター", + "subtitle": "一時的な URL を作成し、受信した webhook リクエストをライブで確認します。", + "intro": { + "title": "webhook URL を作成してリクエストのキャプチャを開始", + "hint": "推測できない URL が発行され、受信したすべてのリクエストを記録します。24 時間後に期限切れになります。" + }, + "createBtn": "webhook URL を作成", + "creating": "作成中…", + "urlLabel": "あなたの webhook URL", + "expiresIn": "{countdown} 後に期限切れ", + "copyUrlBtn": "URL をコピー", + "copied": "コピーしました", + "clearBtn": "クリア", + "newBinBtn": "新しい URL", + "requestsTitle": "リクエスト", + "selectPrompt": "リクエストを選択して詳細を確認します。", + "expiredNotice": "この webhook URL は期限切れです。テストを続けるには新しく作成してください。", + "base64Badge": "base64", + "truncatedBadge": "切り詰め", + "empty": { + "title": "リクエストを待機中…", + "hint": "URL にリクエストを送信すると、すぐにここに表示されます。試してみてください:" + }, + "detail": { + "receivedAt": "受信日時", + "source": "送信元 IP", + "contentType": "Content-Type", + "query": "クエリパラメーター", + "headers": "ヘッダー", + "body": "ボディ", + "copyBody": "ボディをコピー", + "emptyBody": "ボディなし" + }, + "errors": { + "createFailed": "webhook URL の作成に失敗しました。もう一度お試しください。", + "clearFailed": "リクエストのクリアに失敗しました。もう一度お試しください。" + } + }, + "WebsocketTester": { + "title": "WebSocket テスター", + "subtitle": "WebSocket エンドポイントに接続し、メッセージの送受信と確認をライブで行います", + "urlLabel": "エンドポイント URL", + "protocolsLabel": "サブプロトコル", + "protocolsPlaceholder": "任意、カンマ区切り", + "connect": "接続", + "disconnect": "切断", + "status": { + "disconnected": "切断済み", + "connecting": "接続中", + "connected": "接続済み", + "error": "エラー" + }, + "messages": { + "connecting": "{url} に接続中…", + "connected": "接続が確立されました", + "connectedWithProtocol": "接続が確立されました (プロトコル: {protocol})", + "invalidUrl": "無効な URL — ws:// または wss:// で始まる必要があります", + "connectFailed": "接続を開けませんでした", + "socketError": "接続エラーが発生しました", + "notConnected": "未接続 — まず接続してください", + "binaryMessage": "バイナリメッセージを受信しました" + }, + "log": { + "title": "メッセージログ", + "empty": "メッセージはまだありません。接続して送信を開始してください。", + "copyAll": "すべてコピー", + "download": ".txt でダウンロード", + "clear": "ログをクリア", + "prettyPrint": "JSON を整形", + "showRaw": "生テキストを表示" + }, + "composer": { + "placeholder": "送信するメッセージを入力…", + "send": "送信", + "formatJson": "JSON として整形", + "invalidJson": "メッセージが有効な JSON ではありません", + "hint": "Cmd/Ctrl+Enter で送信" + }, + "ping": { + "label": "自動 ping", + "seconds": "秒", + "intervalTitle": "Ping の間隔 (秒)", + "payloadPlaceholder": "ping ペイロード" + } + }, + "WhoisLookup": { + "title": "Whois 検索", + "subtitle": "RDAP を使って任意のドメインや IP アドレスの登録情報を調べます", + "placeholder": "example.com または 8.8.8.8", + "lookupBtn": "検索", + "looking": "検索中…", + "refreshBtn": "更新", + "tryLabel": "お試し:", + "copy": "コピー", + "copied": "コピーしました", + "notAvailable": "利用できません", + "summary": { + "registrar": "レジストラ", + "network": "ネットワーク" + }, + "dates": { + "created": "作成日", + "updated": "更新日", + "expires": "有効期限" + }, + "sections": { + "status": "ステータス", + "nameservers": "ネームサーバー", + "contacts": "連絡先", + "rawRdap": "生の RDAP" + }, + "contacts": { + "roles": "役割", + "name": "名前", + "organization": "組織" + }, + "errors": { + "invalidTarget": "有効なドメイン名または IP アドレスを入力してください", + "lookupFailed": "検索に失敗しました (ステータス {status})" + } + } +} diff --git a/apps/desktop-ui/messages/ko.json b/apps/desktop-ui/messages/ko.json new file mode 100644 index 00000000..8eef3fb7 --- /dev/null +++ b/apps/desktop-ui/messages/ko.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "대시보드", + "settings": "설정", + "apiClient": "API 클라이언트", + "httpStatusCodes": "HTTP 상태 코드", + "passwordManager": "비밀번호 관리자", + "environmentManager": "환경 변수 관리자", + "bookmarks": "북마크", + "jsonFormatter": "JSON 포맷터", + "emailValidator": "이메일 검증기", + "nosqlExplorer": "데이터베이스 탐색기", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "암호화 연습장", + "jwtDecoder": "JWT 디코더", + "certificatePemDecoder": "인증서 / PEM 디코더", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL 포매터", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL 파서", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "비밀 / API 키", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "대비 검사기", + "jsonSchemaGenerator": "JSON 스키마 생성기", + "imageToBase64": "이미지를 Base64로", + "imageCompressor": "이미지 압축", + "cssGradientBuilder": "CSS 그라디언트 빌더", + "gitignoreGenerator": ".gitignore 생성기", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "코드 스니펫", + "qrCodeGenerator": "QR 코드 생성기", + "dnsLookup": "DNS 조회", + "ipSubnetCalculator": "IP / 서브넷 계산기", + "hashGenerator": "해시 생성기", + "hmacGenerator": "HMAC 생성기", + "totpGenerator": "TOTP / 2FA 코드", + "markdownPreview": "마크다운 미리보기", + "numberBaseConverter": "진법 변환기", + "formatConverter": "포맷 변환기", + "mockDataGenerator": "모의 데이터 생성기", + "svgOptimizer": "SVG 최적화", + "unitConverter": "단위 변환기", + "dockerComposeGenerator": "Docker Compose 생성기", + "breakRoom": "휴게실", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAML 포매터", + "sshKeyGenerator": "SSH / RSA 키 생성기", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod 계산기", + "codeScreenshot": "코드 스크린샷", + "cssGenerators": "CSS 생성기", + "exifViewer": "EXIF 뷰어", + "faviconGenerator": "Favicon 생성기", + "jsonDiff": "JSON 비교", + "jsonpathPlayground": "JSONPath 플레이그라운드", + "keycodeInspector": "키코드 검사기", + "markdownTableGenerator": "Markdown 표", + "timezoneConverter": "시간대 변환기", + "tokenCounter": "토큰 카운터", + "webhookTester": "Webhook 테스터", + "websocketTester": "WebSocket 테스터", + "whoisLookup": "Whois 조회" + }, + "Help": { + "title": "도움말 및 문서", + "subtitle": "MyDevTools의 구성, 각 앱의 기능, 그리고 보안과 암호화가 데이터를 어떻게 보호하는지 안내합니다.", + "tabs": { + "overview": "개요", + "apps": "앱", + "security": "보안", + "tips": "팁" + }, + "overview": { + "whatIs": { + "title": "MyDevTools란?", + "description": "로그인 기반 개발자 워크스페이스: 대시보드와 일상적인 도구 모음.", + "p1": "로그인하면 앱이 MyDevTools API와 세션을 설정해 도구가 데이터를 불러오고 저장할 수 있습니다. 사이드바에는 모든 앱이 표시되며, 사용하지 않는 도구는", + "settingsLink": "설정", + "p1After": "에서 숨길 수 있습니다.", + "p2": "일부 기능은 마스터 비밀번호(또는 전역 볼트 키)를 사용하여 비밀번호나 DB 연결 문자열 같은 민감 정보가 서버로 전송되기 전에 기기에서 암호화되도록 합니다." + }, + "quickLinks": { + "title": "빠른 링크", + "dashboard": "대시보드", + "settings": "설정" + } + }, + "apps": { + "intro": "사이드바 또는 대시보드에서 어떤 도구든 열 수 있습니다. 아래는 각 앱에 대한 간단한 안내입니다.", + "open": "열기", + "howItWorks": "작동 방식", + "generic": "이 도구는 사이드바에서 열어 전용 워크플로로 사용하세요." + }, + "security": { + "session": { + "title": "로그인 및 API 세션", + "description": "신원 확인과 브라우저가 백엔드와 통신하는 방식.", + "items": { + "authentication": "인증은 Firebase를 사용합니다. 로그인 후 앱이 Firebase ID 토큰을 API 쿠키로 교환합니다.", + "accessTokens": "액세스 토큰은 서버에서 서명된 JWT(HS256)로, API를 호출하는 사용자를 증명합니다.", + "refreshTokens": "리프레시 토큰은 HttpOnly 쿠키에 저장되며, 서버는 토큰 원문이 아닌 SHA-256 해시를 저장합니다." + } + }, + "encryption": { + "title": "알아두면 좋은 암호화", + "description": "볼트 및 민감한 연결 데이터에 대한 클라이언트 측 암호화.", + "p1": "비밀번호 관리자, 환경 변수 관리자, NoSQL Explorer에 저장된 연결을 위해 웹앱은 브라우저의 Web Crypto API를 사용합니다:", + "items": { + "kdf": "PBKDF2(SHA-256, 100,000회 반복, 랜덤 salt)로 마스터 비밀번호에서 256비트 키를 파생합니다.", + "aes": "AES-256-GCM으로 데이터를 암호화하며, 매 암호화마다 랜덤 12바이트 IV를 사용합니다.", + "scope": "파생 키는 브라우저에 남아(추출 불가) 서버에는 암호문과 IV만 전달됩니다. 마스터 비밀번호나 평문 비밀은 전송되지 않습니다." + }, + "warning": "마스터 비밀번호를 잊으면 암호화된 데이터를 복구할 수 없습니다. 안전하게 보관하세요." + }, + "logout": { + "title": "로그아웃 및 로컬 데이터", + "body": "로그아웃 시 메모리의 민감 정보, 볼트 상태, (해당되는 경우) IndexedDB의 마스터 키 자료를 삭제하고 API 세션을 종료합니다. 기기의 로컬 저장소에만 있는 데이터는 브라우저에서 지우기 전까지 남아 있을 수 있습니다." + } + }, + "tips": { + "title": "안전하게 사용하기", + "items": { + "masterPassword": "볼트 기능에는 강력하고 고유한 마스터 비밀번호를 사용하세요.", + "apiClient": "API 클라이언트는 일반 HTTP 클라이언트처럼 사용하세요. 공유 환경에서는 누가 볼 수 있는지 모르면 운영 비밀키를 붙여넣지 마세요.", + "nosql": "데이터베이스 탐색기는 사용자가 설정한 DB에 연결합니다. 가능하면 최소 권한 계정과 네트워크 규칙을 사용하세요.", + "json": "JSON 포맷터는 브라우저에서 로컬로 동작하지만, 공유 기기에서는 민감한 데이터를 붙여넣지 마세요.", + "settings": "설정에서 표시할 도구를 선택해 사이드바를 더 깔끔하게 유지하세요." + } + } + }, + "Settings": { + "title": "설정", + "language": "언어", + "spanish": "Spanish", + "english": "English", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalan", + "chineseSimplified": "Chinese (Simplified)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "German", + "danish": "Danish", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "우크라이나어" + }, + "SettingsPage": { + "title": "설정", + "subtitle": "계정 설정 및 환경설정을 관리하세요.", + "userProfile": { + "title": "사용자 프로필", + "description": "개인 정보 및 계정 세부 사항입니다.", + "avatarAlt": "사용자", + "anonymousUser": "익명 사용자", + "noEmailProvided": "이메일이 제공되지 않았습니다", + "notLoggedIn": "로그인되지 않았습니다." + }, + "tools": { + "title": "도구 및 기능", + "description": "내비게이션 사이드바에 표시할 도구를 선택하세요." + }, + "appearance": { + "title": "외관", + "description": "기기에서 애플리케이션의 모양을 사용자 지정하세요.", + "themePreference": "테마 설정", + "themes": { + "light": "라이트", + "dark": "다크", + "system": "시스템" + } + }, + "language": { + "title": "언어", + "description": "플랫폼 전체에서 사용할 언어를 설정하세요.", + "selectLanguage": "언어 선택", + "selectPlaceholder": "언어를 선택하세요", + "helpText": "언어를 변경하면 플랫폼 전체의 메뉴와 레이블이 업데이트됩니다.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spanish)", + "ar": "عربى (Arabic)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Greek)", + "de": "Deutsch (German)", + "da": "Dansk (Danish)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "이탈리아어 (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "좋은 아침이에요", + "afternoon": "좋은 오후에요", + "evening": "좋은 저녁이에요" + }, + "commaName": ", {name}", + "welcomeBack": "다시 오신 것을 환영합니다", + "welcomeBackNamed": "다시 오신 것을 환영합니다, {name}", + "tagline": "오늘은 무엇을 만들어 볼까요?", + "brandName": "MyDevTools", + "stats": { + "tools": "도구", + "favorites": "즐겨찾기", + "pinned": "고정", + "recent": "최근 사용" + }, + "sections": { + "recentlyUsed": "최근 사용", + "favorites": "즐겨찾기", + "whatsNew": "새 소식", + "pinned": "고정" + }, + "groups": { + "apps": "앱" + }, + "toolCard": { + "defaultDescription": "더 나은 기능을 위해 이 도구를 탐색하세요.", + "launchTool": "도구 실행" + }, + "tools": { + "toDo": { + "title": "할 일", + "description": "할 일 목록을 효율적으로 관리하세요." + }, + "notes": { + "title": "메모", + "description": "아이디어를 기록하고 생각을 정리하세요." + }, + "passwordManager": { + "title": "비밀번호 관리자", + "description": "비밀번호를 안전하게 저장하고 관리하세요." + }, + "environmentManager": { + "title": "환경 변수 관리자", + "description": "프로젝트와 환경별로 .env 형식 변수를 태그와 함께 저장합니다." + }, + "bookmarks": { + "title": "북마크", + "description": "북마크를 관리하고 정리하세요." + }, + "apiClient": { + "title": "API 클라이언트", + "description": "HTTP 요청을 테스트하고 디버그하세요." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "MongoDB 데이터베이스를 탐색하세요." + }, + "emailValidator": { + "title": "이메일 검증기", + "description": "이메일 주소를 검증하세요." + }, + "encryptionPlayground": { + "title": "암호화 연습장", + "description": "원시 키 또는 암호로 AES-GCM, JSON 번들, Web Crypto는 브라우저에서만." + }, + "jwtDecoder": { + "title": "JWT 디코더", + "description": "JWT 헤더, 페이로드, 만료 시간을 디코딩합니다." + }, + "certificatePemDecoder": { + "title": "인증서 / PEM 디코더", + "description": "X.509 인증서와 CSR 디코딩: 주체, 발급자, 유효 기간, SAN." + }, + "sshKeyGenerator": { + "title": "SSH / RSA 키 생성기", + "description": "브라우저에서 Ed25519 또는 RSA 키 쌍을 생성합니다. OpenSSH 및 PEM 형식으로 다운로드." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL 포매터", + "description": "구문 강조와 간단한 쿼리 빌더로 GraphQL을 포맷합니다." + }, + "yamlFormatter": { + "title": "YAML 포매터", + "description": "브라우저에서 YAML을 형식화, 유효성 검사, JSON 변환합니다." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL 파서", + "description": "URL을 프로토콜, 호스트, 경로, 쿼리 파라미터, 해시로 분해합니다." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "비밀 / API 키", + "description": "알파벳·길이·대량보내기를 설정할 수 있는 암호학적 난수 문자열." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "대비 검사기", + "description": "텍스트와 배경의 WCAG 2.1 대비 비율(AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "JSON 스키마 생성기", + "description": "샘플 JSON에서 JSON 스키마와 타입이 지정된 모델을 추론합니다." + }, + "imageToBase64": { + "title": "이미지를 Base64로", + "description": "이미지를 데이터 URI 또는 ​​원시 Base64 문자열로 변환합니다." + }, + "imageCompressor": { + "title": "이미지 압축", + "description": "JPEG, PNG, WebP를 브라우저에서 품질 슬라이더로 압축합니다." + }, + "cssGradientBuilder": { + "title": "CSS 그라디언트 빌더", + "description": "각도 제어 및 색상 정지 기능을 갖춘 시각적 CSS 그라데이션 빌더입니다." + }, + "gitignoreGenerator": { + "title": ".gitignore 생성기", + "description": "500개 이상의 기술 스택에서 병합된 .gitignore 파일을 즉시 생성합니다." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "CSV 또는 Excel 파일을 JSON으로 변환하고 CSV 또는 XLSX로 다시보냅니다." + }, + "snippetManager": { + "title": "코드 스니펫", + "description": "구문 강조, 자동 언어 감지 및 서식으로 스니펫을 저장하세요." + }, + "qrCodeGenerator": { + "title": "QR 코드 생성기", + "description": "브라우저에서 텍스트나 URL로 PNG QR 코드를 만듭니다." + }, + "ipSubnetCalculator": { + "title": "IP / 서브넷 계산기", + "description": "IPv4·IPv6 CIDR: 넷마스크, 범위, 주소 수." + }, + "dnsLookup": { + "title": "DNS 조회", + "description": "도메인의 DNS 레코드를 조회합니다 — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "해시 생성기", + "description": "텍스트나 파일용 MD5, SHA-1, SHA-2." + }, + "hmacGenerator": { + "title": "HMAC 생성기", + "description": "HMAC-SHA1/256/384/512로 웹훅 서명 및 API 연동 테스트." + }, + "totpGenerator": { + "title": "TOTP / 2FA 코드 생성기", + "description": "인증 앱의 Base32 비밀키를 붙여넣으면 6자리 코드가 30초마다 갱신됩니다. 로그인·MFA 테스트에 유용합니다." + }, + "markdownPreview": { + "title": "마크다운 미리보기", + "description": "HTML 내보내기 및 HTML → Markdown 변환이 포함된 실시간 Markdown 렌더러." + }, + "numberBaseConverter": { + "title": "진법 변환기", + "description": "2~36진법 정수를 서로 변환합니다." + }, + "formatConverter": { + "title": "포맷 변환기", + "description": "JSON, YAML, TOML, XML 간 모든 방향 변환. 12가지 조합을 모두 지원합니다." + }, + "mockDataGenerator": { + "title": "모의 데이터 생성기", + "description": "열을 정의하고 유형을 선택한 뒤 시드, Postman 또는 테스트용 데이터를 보냅니다. 최대 5000행, 모두 브라우저에서 처리되며 업로드되지 않습니다." + }, + "httpStatusCodes": { + "title": "HTTP 상태 코드", + "description": "설명이 포함된 HTTP 상태 코드를 검색합니다 (1xx–5xx)." + }, + "svgOptimizer": { + "title": "SVG 최적화", + "description": "SVG를 붙여넣고 SVGO로 불필요한 부분을 제거한 뒤 UTF-8 크기 절감을 확인하세요." + }, + "unitConverter": { + "title": "단위 변환기", + "description": "43개의 과학 및 공학 카테고리에서 323개의 단위 간 변환." + }, + "dockerComposeGenerator": { + "title": "Docker Compose 생성기", + "description": "데이터베이스, 캐시, 큐, 프록시, 관측 가능성 이미지를 선택하고 로컬 스택용 docker-compose.yml을 보냅니다." + }, + "game2048": { + "title": "2048", + "description": "2048년까지 타일을 밀고 합칩니다. 클래식 숫자 퍼즐 게임." + }, + "sudoku": { + "title": "스도쿠", + "description": "클래식 숫자 퍼즐. 3개의 난이도 레벨, 각 스테이지 150개." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Redis 인스턴스에서 키를 탐색하고 값을 검사하며 명령을 실행합니다." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt 생성 및 검증", + "description": "bcrypt로 비밀번호를 해시하고 기존 해시를 검증합니다." + }, + "chmodCalculator": { + "title": "Chmod 계산기", + "description": "Unix 권한을 8진수, 기호 표기, 체크박스 간에 변환합니다." + }, + "codeScreenshot": { + "title": "코드 스크린샷", + "description": "소스 코드를 아름답고 공유하기 좋은 이미지로 변환합니다." + }, + "cssGenerators": { + "title": "CSS 생성기", + "description": "Box-shadow, border-radius, 유동적 clamp() 생성기입니다." + }, + "exifViewer": { + "title": "EXIF 뷰어 및 제거", + "description": "사진의 EXIF 메타데이터를 로컬에서 확인하고 제거합니다." + }, + "faviconGenerator": { + "title": "Favicon 생성기", + "description": "favicon, 앱 아이콘, 매니페스트 스니펫을 생성합니다." + }, + "jsonDiff": { + "title": "JSON 비교", + "description": "두 JSON 문서를 의미론적, 구조 인식 방식으로 비교합니다." + }, + "jsonpathPlayground": { + "title": "JSONPath 플레이그라운드", + "description": "JSONPath 또는 JMESPath로 JSON을 쿼리하고 결과를 실시간으로 확인합니다." + }, + "keycodeInspector": { + "title": "키코드 검사기", + "description": "키를 눌러 JavaScript 이벤트 코드와 수정 키를 확인합니다." + }, + "markdownTableGenerator": { + "title": "Markdown 표 생성기", + "description": "스프레드시트 같은 그리드 편집기에서 Markdown 및 HTML 표를 만듭니다." + }, + "timezoneConverter": { + "title": "시간대 변환기", + "description": "하나의 시각을 여러 시간대로 변환하고 날짜 변동과 근무 시간을 표시합니다." + }, + "tokenCounter": { + "title": "LLM 토큰 카운터", + "description": "LLM 토큰을 세고 청크를 시각화하며 API 비용을 추정합니다." + }, + "webhookTester": { + "title": "Webhook 테스터", + "description": "수신되는 webhook 요청을 실시간으로 캡처하고 검사합니다." + }, + "websocketTester": { + "title": "WebSocket 테스터", + "description": "WebSocket 엔드포인트에 연결하여 메시지를 실시간으로 주고받습니다." + }, + "whoisLookup": { + "title": "Whois 조회", + "description": "RDAP를 통해 도메인과 IP 등록 정보를 조회합니다." + } + }, + "tabs": { + "apps": "앱", + "analytics": "분석", + "ui": "인터페이스" + }, + "analytics": { + "title": "데이터 한눈에 보기", + "subtitle": "서버의 계정과 동기화된 개수입니다.", + "loadError": "분석을 불러올 수 없습니다.", + "retry": "다시 시도", + "loginHint": "로그인하면 사용 분석과 인터페이스 설정을 볼 수 있습니다.", + "passwordEntries": "비밀번호 항목", + "bookmarks": "북마크", + "bookmarkFolders": "북마크 폴더", + "tasksTotal": "작업", + "tasksCompleted": "완료", + "tasksOngoing": "진행 중", + "tasksNotStarted": "시작 전", + "projects": "프로젝트", + "nosqlConnections": "DB 연결", + "notes": "노트", + "apiClientCollections": "API 컬렉션", + "apiClientEnvironments": "API 환경", + "apiClientHistory": "API 기록 항목", + "jsonFormatterDocuments": "JSON 포맷터 문서", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "코드 스니펫", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "인터페이스 설정", + "subtitle": "테마, 언어, 사이드바에 표시할 도구 — 계정 설정에서.", + "loadError": "설정을 불러올 수 없습니다.", + "loginHint": "로그인하면 저장된 인터페이스 설정을 볼 수 있습니다.", + "theme": "테마", + "accent": "강조 색", + "locale": "언어", + "enabledTools": "표시 중인 도구", + "favoriteTools": "즐겨찾는 도구", + "toolsWithUsage": "사용량이 추적된 도구" + }, + "signIn": "로그인", + "noResults": "검색과 일치하는 도구가 없습니다", + "noResultsHint": "다른 키워드를 시도하거나 필터를 지우세요.", + "filterAll": "전체", + "loginCta": { + "description": "로그인하여 최근 사용한 도구를 추적하세요." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "로딩 중...", + "myTasksTitle": "내 할 일", + "statsLine": "{total}개 할 일 • {percent}% 완료", + "statsLineDesktop": "총 {total}개 • {percent}% 완료" + }, + "filters": { + "all": "전체", + "total": "합계", + "allProjects": "모든 프로젝트", + "export": "내보내기", + "kanban": "칸반", + "list": "목록", + "searchPlaceholder": "할 일 검색...", + "searchAria": "할 일 검색", + "clearSearchAria": "검색 지우기", + "foundTasks": "{count}개의 할 일을 찾았습니다", + "filterByAllAria": "전체로 필터", + "filterByStatusAria": "{status}로 필터" + }, + "status": { + "not-started": { + "label": "시작 안 함", + "description": "아직 시작하지 않은 할 일" + }, + "ongoing": { + "label": "진행 중", + "description": "현재 진행 중인 할 일" + }, + "completed": { + "label": "완료", + "description": "완료된 할 일" + } + }, + "priorities": { + "high": "높음", + "medium": "보통", + "low": "낮음" + }, + "form": { + "addNewTaskLabel": "새 할 일 추가", + "multilinePlaceholder": "할 일 세부 사항 입력... (Shift+Enter로 줄 바꿈)", + "singlelinePlaceholder": "무엇을 해야 하나요? Enter를 눌러 추가", + "taskInputAria": "할 일 입력", + "projectPlaceholder": "프로젝트", + "selectProject": "프로젝트 선택", + "errorSelectProject": "할 일을 추가하려면 프로젝트를 선택하거나 새로 만드세요.", + "hintMultiline": "Shift+Enter로 줄 바꿈, Enter로 추가", + "hintSingleline": "Enter로 추가", + "shortcutsAria": "키보드 단축키", + "shortcutsLabel": "단축키", + "shortcutAddTask": "할 일 추가", + "shortcutNewLine": "줄 바꿈", + "shortcutClear": "지우기", + "addButton": "추가" + }, + "list": { + "ariaLabel": "할 일 목록", + "loading": "할 일을 불러오는 중...", + "emptyTitle": "할 일이 없습니다", + "emptyPrimary": "위에서 첫 번째 할 일을 추가하여 시작하세요! Enter를 눌러 빠르게 추가할 수 있습니다.", + "emptySecondary": "필터나 검색 조건을 조정해 보세요." + }, + "mobileDrawer": { + "addTaskSrOnly": "할 일 추가", + "addNewTaskTitle": "새 할 일 추가", + "addNewTaskDescription": "진행 상황을 추적할 새 할 일을 만드세요.", + "cancel": "취소" + }, + "kanban": { + "ariaLabel": "칸반 보드", + "loading": "할 일을 불러오는 중...", + "dropHere": "여기에 드롭하세요", + "noTasksYet": "아직 할 일이 없습니다", + "emptyColumnAria": "빈 열", + "columnAria": "{title} 열 ({count}개 할 일)", + "countInColumnAria": "{title}에 {count}개 할 일" + }, + "taskItem": { + "confirmDelete": "이 할 일을 삭제하시겠습니까?", + "statusPlaceholder": "상태", + "changeStatusAria": "할 일 상태 변경", + "taskOptionsAria": "할 일 옵션", + "editTask": "할 일 수정", + "copyTask": "할 일 복사", + "copied": "복사됨!", + "deleteTask": "할 일 삭제", + "statusHeading": "상태", + "editTaskAria": "할 일 수정" + }, + "editDialog": { + "title": "할 일 수정", + "description": "할 일 세부 사항을 업데이트하고, 하위 작업을 추가하고, 우선순위를 설정하세요.", + "taskTitleLabel": "할 일 제목 *", + "taskTitlePlaceholder": "무엇을 해야 하나요?", + "descriptionLabel": "설명", + "descriptionPlaceholder": "이 할 일에 대한 자세한 내용을 추가하세요...", + "statusLabel": "상태", + "priorityLabel": "우선순위", + "dueDateLabel": "마감일", + "pickDate": "날짜 선택", + "clearDate": "날짜 지우기", + "projectLabel": "프로젝트", + "selectProjectPlaceholder": "프로젝트를 선택하세요", + "noProject": "프로젝트 없음", + "timeEstimateLabel": "예상 소요 시간 (분)", + "timeEstimatePlaceholder": "얼마나 걸릴까요?", + "tagsLabel": "태그", + "customTagNamePlaceholder": "사용자 지정 태그 이름", + "subtasksLabel": "하위 작업", + "addSubtaskPlaceholder": "하위 작업 추가...", + "cancel": "취소", + "saving": "저장 중...", + "saveChanges": "변경 사항 저장" + }, + "export": { + "title": "할 일 내보내기", + "description": "할 일을 JSON, CSV 또는 Excel 형식으로 내보내세요.", + "projectLabel": "프로젝트", + "statusLabel": "상태", + "tagLabel": "태그", + "allProjects": "모든 프로젝트", + "allStatuses": "모든 상태", + "allTags": "모든 태그", + "exportAsJsonTitle": "JSON으로 내보내기", + "exportAsJsonDescription": "하위 작업, 태그 및 메타데이터를 포함한 할 일 데이터를 내보내세요.", + "exportJson": "JSON 내보내기", + "exportAsCsvExcelTitle": "CSV / Excel로 내보내기", + "exportAsCsvExcelDescription": "스프레드시트 애플리케이션용으로 할 일을 내보내세요.", + "exportCsv": "CSV 내보내기", + "exportExcel": "Excel 내보내기", + "close": "닫기", + "toastNoTasks": "현재 필터로 내보낼 할 일이 없습니다", + "toastExportedJson": "{count}개의 할 일을 JSON으로 내보냈습니다!", + "toastExportedCsv": "{count}개의 할 일을 CSV로 내보냈습니다!", + "toastExportedExcel": "{count}개의 할 일을 Excel로 내보냈습니다!", + "toastFailedJson": "JSON 내보내기에 실패했습니다", + "toastFailedCsv": "CSV 내보내기에 실패했습니다", + "toastFailedExcel": "Excel 내보내기에 실패했습니다", + "unknownProject": "알 수 없는 프로젝트" + }, + "ack": { + "taskAddedTitle": "할 일이 추가되었습니다", + "taskAddedFailedTitle": "할 일 추가에 실패했습니다", + "taskUpdatedTitle": "할 일이 업데이트되었습니다", + "taskUpdatedFailedTitle": "할 일 업데이트에 실패했습니다", + "taskStatusUpdateFailedTitle": "할 일 상태 업데이트에 실패했습니다", + "taskMovedTitle": "할 일이 {status}(으)로 이동되었습니다", + "tryAgain": "다시 시도해 주세요.", + "taskDeletedTitle": "할 일 \"{text}\"이(가) 삭제되었습니다", + "undo": "실행 취소", + "taskRestoredTitle": "할 일이 복원되었습니다", + "taskRestoredSuccessTitle": "할 일이 복원되었습니다", + "taskRestoreFailedTitle": "할 일 복원에 실패했습니다. 다시 시도해 주세요.", + "taskDeleteFailedTitle": "할 일 삭제에 실패했습니다. 다시 시도해 주세요.", + "tasksImportedTitle": "{count}개의 할 일을 가져왔습니다!", + "tasksImportFailedTitle": "할 일 가져오기에 실패했습니다. 다시 시도해 주세요.", + "unknownProject": "알 수 없는 프로젝트", + "projectCreatedTitle": "프로젝트가 생성되었습니다", + "projectCreateFailedTitle": "프로젝트 생성에 실패했습니다", + "projectUpdatedTitle": "프로젝트가 업데이트되었습니다", + "projectUpdateFailedTitle": "프로젝트 업데이트에 실패했습니다", + "projectDeletedTitle": "프로젝트가 삭제되었습니다", + "projectDeleteFailedTitle": "프로젝트 삭제에 실패했습니다" + } + }, + "Notes": { + "page": { + "loading": "로딩 중..." + }, + "layout": { + "mobileNavTitle": "메모 내비게이션", + "openMenuAria": "메모 메뉴 열기" + }, + "sidebar": { + "title": "메모", + "createNoteAria": "메모 작성", + "searchPlaceholder": "메모 검색...", + "loading": "로딩 중...", + "noNotesFound": "메모를 찾을 수 없습니다.", + "noNotesYet": "아직 메모가 없습니다. +를 눌러 만드세요.", + "inParent": "{parentTitle} 안에", + "untitled": "제목 없음", + "addSubPage": "하위 페이지 추가", + "delete": "삭제", + "deleteConfirmTitle": "메모를 삭제하시겠습니까?", + "deleteConfirmDescription": "\"{title}\"을(를) 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "cancel": "취소", + "confirmDelete": "삭제", + "noteOptionsAria": "메모 옵션" + }, + "editor": { + "emptyState": "메모를 선택하거나 새로 만들어 시작하세요.", + "titlePlaceholder": "제목 없음", + "saving": "저장 중…", + "ready": "준비됨", + "saved": "{time}에 저장됨", + "stats": "{words}단어 · 읽기 {read}분", + "template": "템플릿", + "applyTemplate": "템플릿 적용", + "exportMdLabel": ".md", + "exportMd": "Markdown으로 내보내기", + "exportHtmlLabel": ".html", + "exportHtml": "HTML로 내보내기", + "focusMode": "집중 모드", + "exitFocusMode": "집중 모드 종료", + "save": "저장", + "addTag": "태그 추가…", + "removeTag": "태그 {tag} 제거", + "loading": "불러오는 중…", + "searchHint": "검색하려면", + "focusHint": "집중 모드", + "notePath": "노트 경로" + }, + "context": { + "authRequiredError": "사용자가 인증되지 않았습니다", + "defaultTitle": "제목 없음", + "cloudSyncImageError": "노트 이미지를 저장하려면 클라우드 동기화를 켜세요. 텍스트 노트는 로컬에 유지됩니다." + } + }, + "PasswordManager": { + "page": { + "loading": "로딩 중...", + "title": "비밀번호 관리자", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "비밀번호 복호화에 실패했습니다", + "toastLoadFailed": "비밀번호 불러오기에 실패했습니다", + "toastUnlockedAuto": "볼트가 자동으로 잠금 해제되었습니다", + "toastAutoUnlockFailed": "자동 잠금 해제에 실패했습니다. 수동으로 잠금을 해제해 주세요.", + "toastConnectFailed": "서버 연결에 실패했습니다", + "toastAutoUnlockWarning": "볼트가 잠금 해제되었지만, 새로고침 시 자동 잠금 해제가 작동하지 않을 수 있습니다", + "errorIncorrectPassword": "마스터 비밀번호가 올바르지 않습니다", + "errorUnlockFailed": "볼트 잠금 해제에 실패했습니다", + "errorPasswordsMismatch": "비밀번호가 일치하지 않습니다", + "errorPasswordTooShort": "비밀번호는 최소 8자 이상이어야 합니다", + "errorSetupFailed": "볼트 설정에 실패했습니다", + "errorConnectFailed": "서버 연결에 실패했습니다.", + "mobileTitleCreate": "볼트 만들기", + "mobileTitleWelcome": "다시 오신 것을 환영합니다", + "mobileSubtitleCreate": "자격 증명을 보호할 마스터 비밀번호를 설정하세요", + "mobileSubtitleUnlock": "마스터 비밀번호를 입력하여 잠금을 해제하세요", + "placeholderMasterPassword": "마스터 비밀번호", + "placeholderConfirmPassword": "비밀번호 확인", + "labelMasterPassword": "마스터 비밀번호", + "labelConfirmPassword": "비밀번호 확인", + "placeholderEnterMaster": "마스터 비밀번호 입력", + "placeholderConfirmMaster": "마스터 비밀번호 확인", + "buttonCreateVault": "볼트 만들기", + "buttonUnlock": "잠금 해제", + "buttonUnlockVault": "볼트 잠금 해제", + "footerCreate": "비밀번호는 저장되지 않으며 복구할 수 없습니다", + "footerUnlock": "종단간 암호화로 보호됩니다", + "desktopTitleSetup": "비밀번호 볼트 설정", + "desktopTitleUnlock": "볼트 잠금 해제", + "desktopDescriptionSetup": "자격 증명을 보호할 마스터 비밀번호를 만드세요. 이 비밀번호는 저장되지 않으며 복구할 수 없습니다.", + "desktopDescriptionUnlock": "마스터 비밀번호를 입력하여 보안 자격 증명에 접근하세요." + }, + "list": { + "loading": "비밀번호를 불러오는 중...", + "emptyTitle": "볼트가 비어 있습니다", + "emptyHintMobile": "+ 버튼을 눌러 첫 번째 비밀번호를 추가하세요", + "emptyHintDesktop": "아직 저장된 비밀번호가 없습니다. 위의 \"비밀번호 추가\" 버튼을 클릭하여 시작하세요.", + "addPassword": "비밀번호 추가", + "searchPlaceholderMobile": "볼트 검색...", + "searchPlaceholderDesktop": "볼트 검색...", + "importExport": "가져오기 / 내보내기", + "lockVault": "볼트 잠금", + "allPasswordsTitle": "모든 비밀번호", + "passwordCount": "{count, plural, one {#개 비밀번호} other {#개 비밀번호}}", + "vaultHealthDrawerTitle": "볼트 상태", + "noResults": "\"{query}\"와(과) 일치하는 비밀번호가 없습니다", + "tableService": "서비스", + "tableUsername": "사용자 이름", + "tablePassword": "비밀번호", + "tableUrl": "URL", + "tableActions": "작업", + "gridViewAria": "그리드 보기", + "listViewAria": "목록 보기", + "passwordStrengthTitle": "비밀번호 강도", + "deleteDialogTitle": "비밀번호 삭제", + "deleteDialogDescription": "이 비밀번호를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "cancel": "취소", + "delete": "삭제", + "edit": "수정" + }, + "toasts": { + "copiedPassword": "비밀번호가 클립보드에 복사되었습니다", + "copiedUsername": "사용자 이름이 클립보드에 복사되었습니다", + "passwordDeleted": "비밀번호가 삭제되었습니다", + "deleteFailed": "비밀번호 삭제에 실패했습니다", + "vaultLocked": "볼트가 잠겼습니다", + "importedCount": "{count, plural, one {#개의 비밀번호를 가져왔습니다} other {#개의 비밀번호를 가져왔습니다}}" + }, + "form": { + "addTitle": "새 비밀번호 추가", + "editTitle": "비밀번호 수정", + "serviceName": "서비스 이름", + "usernameEmail": "사용자 이름 / 이메일", + "password": "비밀번호", + "urlOptional": "웹사이트 URL (선택 사항)", + "notesOptional": "메모 (선택 사항)", + "tags": "태그", + "placeholderService": "예: Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "비밀번호를 입력하거나 생성하세요", + "placeholderUrl": "https://...", + "placeholderNotes": "추가 세부 사항...", + "placeholderTags": "태그 추가 (Enter 또는 쉼표를 누르세요)", + "strength": "강도", + "strengthWeak": "약함", + "strengthMedium": "보통", + "strengthStrong": "강함", + "advancedGenerator": "고급 생성기", + "cancel": "취소", + "saving": "저장 중...", + "savePassword": "비밀번호 저장", + "updating": "업데이트 중...", + "updatePassword": "비밀번호 업데이트", + "addPasswordSrOnly": "비밀번호 추가", + "triggerAdd": "비밀번호 추가", + "toastSaved": "비밀번호가 저장되었습니다", + "toastSaveFailed": "비밀번호 저장에 실패했습니다", + "toastUpdated": "비밀번호가 업데이트되었습니다", + "toastUpdateFailed": "비밀번호 업데이트에 실패했습니다" + }, + "security": { + "vaultHealth": "볼트 상태", + "statusLabel": "상태", + "totalPasswords": "전체 비밀번호", + "activeEntries": "볼트의 활성 항목", + "protected": "보호됨", + "weakPasswords": "취약한 비밀번호", + "needingImprovement": "개선이 필요한 비밀번호", + "allGood": "모두 양호", + "reusedPasswords": "재사용된 비밀번호", + "usedMultipleTimes": "여러 번 사용된 비밀번호", + "unique": "고유", + "total": "전체", + "weak": "취약", + "reused": "재사용", + "totalCountLabel": "{count, plural, one {#개 비밀번호} other {#개 비밀번호}}", + "weakCountLabel": "{count}개 취약", + "allSecure": "모두 안전", + "scoreExcellent": "우수", + "scoreGood": "양호", + "scoreFair": "보통", + "scoreCritical": "위험" + }, + "importExport": { + "title": "볼트 가져오기 / 내보내기", + "description": "비밀번호 데이터를 관리하세요. 볼트를 JSON으로 내보내거나 JSON 파일에서 가져올 수 있습니다.", + "tabExport": "내보내기", + "tabImport": "가져오기", + "warningTitle": "경고", + "warningBody": "내보낸 파일에는 비밀번호가 평문으로 포함되어 있습니다. 이 파일을 안전하게 보관하고 사용 후 삭제하세요.", + "exportBlurb": "전체 볼트를 JSON 파일로 다운로드하세요. 모든 서비스, 사용자 이름, 비밀번호 및 메모가 포함됩니다.", + "downloadJson": "JSON 내보내기 다운로드", + "pasteLabel": "JSON 데이터 붙여넣기", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "이전에 내보낸 JSON 파일의 내용을 여기에 붙여넣으세요.", + "importing": "가져오는 중...", + "importPasswords": "비밀번호 가져오기", + "trigger": "가져오기 / 내보내기", + "toastExported": "볼트가 내보내졌습니다", + "toastExportFailed": "볼트 내보내기에 실패했습니다", + "toastImportFailed": "가져오기 실패: 잘못된 JSON 형식" + }, + "card": { + "copyUsername": "사용자 이름 복사", + "openUrl": "URL 열기", + "strength": "강도" + }, + "swipeable": { + "copy": "복사", + "delete": "삭제", + "copyPassword": "비밀번호 복사", + "copyUsername": "사용자 이름 복사", + "edit": "수정", + "deleteAction": "삭제" + }, + "floatingMenu": { + "lockVault": "볼트 잠금", + "importExport": "가져오기 / 내보내기", + "addPassword": "비밀번호 추가" + }, + "generator": { + "tabPassword": "비밀번호", + "tabPassphrase": "암호문", + "strength": "강도", + "lengthLabel": "길이: {length}자", + "lengthBadge": "{length}자", + "characterSets": "문자 집합", + "uppercase": "대문자", + "lowercase": "소문자", + "numbers": "숫자", + "symbols": "기호", + "wordCountLabel": "단어 수: {count}", + "wordCountBadge": "{count, plural, one {#개 단어} other {#개 단어}}", + "separator": "구분자", + "capitalization": "대문자화", + "includeNumber": "숫자 포함" + } + }, + "EnvironmentManager": { + "subtitle": "프로젝트와 환경별로 암호화된 환경 변수.", + "page": { + "loading": "로딩 중…", + "title": "환경 변수 관리자", + "loadFailed": "환경 세트를 불러오지 못했습니다" + }, + "list": { + "title": "환경", + "loading": "세트 불러오는 중…", + "emptyTitle": "환경 세트가 없습니다", + "emptyHint": "프로젝트와 환경마다 세트를 만드세요(예: 프로덕션 API 키). 업로드 전 브라우저에서 암호화됩니다.", + "searchPlaceholder": "프로젝트, 환경, 태그, 메모 검색…", + "tagFilterPlaceholder": "태그로 필터", + "variableCount": "{count, plural, other {#개 변수}}", + "updatedAgo": "업데이트 {time}", + "copyDotEnv": ".env 복사", + "edit": "편집", + "delete": "삭제", + "cancel": "취소", + "confirmDelete": "삭제", + "deleteDialogTitle": "환경 세트를 삭제할까요?", + "deleteDialogDescription": "계정에서 암호화된 데이터가 삭제됩니다. 되돌릴 수 없습니다.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env가 클립보드에 복사됨", + "deleted": "환경 세트가 삭제됨", + "deleteFailed": "삭제 실패" + }, + "form": { + "addTitle": "새 환경 세트", + "editTitle": "환경 세트 편집", + "triggerAdd": "환경 세트 추가", + "addSrOnly": "환경 세트 추가", + "project": "프로젝트", + "environment": "환경", + "placeholderProject": "예: billing-api", + "placeholderEnvironment": "예: production", + "pasteEnvLabel": ".env 붙여넣기", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "붙여넣은 줄 병합", + "variables": "변수", + "addRow": "행 추가", + "keyPlaceholder": "키", + "valuePlaceholder": "값", + "notesOptional": "메모(선택)", + "placeholderNotes": "로테이션 정책, 담당자, 링크…", + "tags": "태그", + "placeholderTags": "태그 추가(Enter 또는 쉼표)", + "cancel": "취소", + "save": "저장", + "saving": "저장 중…", + "update": "업데이트", + "updating": "업데이트 중…", + "toastSaved": "환경 세트가 저장됨", + "toastSaveFailed": "저장 실패", + "toastUpdated": "환경 세트가 업데이트됨", + "toastUpdateFailed": "업데이트 실패", + "toastPasteEmpty": "KEY=값 줄을 찾을 수 없음", + "toastPasteApplied": "붙여넣기에서 변수 {count}개 병합", + "toastProjectEnvRequired": "프로젝트와 환경은 필수입니다", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "로딩 중..." + }, + "manager": { + "foldersHeading": "폴더", + "tagsHeading": "태그", + "tagsMore": "+{count}개 더", + "tagsShowLess": "Show less", + "searchPlaceholder": "북마크 검색...", + "import": "가져오기", + "export": "내보내기", + "exportAsHtml": "HTML로 내보내기", + "exportAsJson": "JSON으로 내보내기", + "addBookmark": "북마크 추가", + "matchingQuery": "\"{query}\" 검색 결과", + "bookmarkCount": "{count, plural, one {#개 북마크} other {#개 북마크}}", + "allBookmarks": "모든 북마크", + "uncategorized": "미분류", + "unknownFolder": "알 수 없음" + }, + "card": { + "urlCopied": "URL이 클립보드에 복사되었습니다", + "bookmarkDeleted": "북마크가 삭제되었습니다", + "edit": "수정", + "delete": "삭제", + "copyUrl": "URL 복사", + "deleteConfirmTitle": "북마크를 삭제하시겠습니까?", + "deleteConfirmDescription": "\"{title}\"을(를) 영구적으로 삭제합니다.", + "cancel": "취소" + }, + "grid": { + "emptyTitle": "아직 북마크가 없습니다", + "emptyDescription": "첫 번째 북마크를 추가하거나 브라우저에서 가져와 시작하세요." + }, + "folderTree": { + "allBookmarks": "모든 북마크", + "addSubfolder": "하위 폴더 추가", + "rename": "이름 변경", + "deleteFolderTitle": "\"{name}\" 폴더를 삭제하시겠습니까?", + "deleteFolderDescription": "이 폴더와 모든 하위 폴더가 삭제됩니다. 이 폴더의 북마크는 미분류로 이동됩니다." + }, + "import": { + "title": "북마크 가져오기", + "description": "브라우저에서 내보낸 파일에서 북마크를 가져오세요.", + "parseError": "북마크 파일 분석에 실패했습니다. 유효한 북마크 내보내기 파일인지 확인하세요.", + "successToast": "{bookmarks}개 북마크와 {folders}개 폴더를 가져왔습니다", + "errorToast": "북마크 가져오기에 실패했습니다", + "chromeInstruction": "메뉴 → 북마크 → 북마크 관리자 → ⋮ → 북마크 내보내기", + "firefoxInstruction": "메뉴 → 북마크 → 북마크 관리 → 가져오기 및 백업 → 내보내기", + "safariInstruction": "파일 → 북마크 내보내기...", + "edgeInstruction": "메뉴 → 즐겨찾기 → ⋯ → 즐겨찾기 내보내기", + "dropZoneTitle": "여기에 북마크 파일을 드롭하세요", + "dropZoneHint": "또는 클릭하여 찾아보기 (HTML 또는 JSON)", + "selectedFileHint": "클릭하여 다른 파일 선택", + "errorTitle": "오류", + "previewTitle": "가져오기 미리보기", + "previewBookmarks": "{count, plural, one {#개 북마크} other {#개 북마크}}", + "previewFolders": "{count, plural, one {#개 폴더} other {#개 폴더}}", + "importModeLabel": "가져오기 모드", + "modeMerge": "기존 북마크와 병합", + "modeReplace": "기존 북마크 모두 교체", + "cancel": "취소", + "importButton": "가져오기", + "importButtonWithCount": "{count, plural, one {#개 북마크 가져오기} other {#개 북마크 가져오기}}" + }, + "addBookmark": { + "titleAdd": "북마크 추가", + "titleEdit": "북마크 수정", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "유효한 URL을 입력해 주세요", + "titleLabel": "제목", + "titlePlaceholder": "북마크 제목", + "titleRequired": "제목을 입력해 주세요", + "descriptionLabel": "설명 (선택 사항)", + "descriptionPlaceholder": "이 북마크에 대한 메모 추가", + "folderLabel": "폴더", + "folderPlaceholder": "폴더 선택", + "noFolder": "폴더 없음", + "tagsLabel": "태그", + "tagsPlaceholder": "태그 추가 (Enter를 누르세요)", + "toastUpdated": "북마크가 업데이트되었습니다", + "toastAdded": "북마크가 추가되었습니다", + "cancel": "취소", + "submitAdd": "북마크 추가", + "submitUpdate": "북마크 업데이트" + }, + "addFolder": { + "title": "폴더 만들기", + "nameLabel": "폴더 이름", + "namePlaceholder": "내 폴더", + "nameRequired": "폴더 이름을 입력해 주세요", + "parentLabel": "상위 폴더", + "parentPlaceholder": "상위 폴더 선택", + "rootParent": "루트 (상위 폴더 없음)", + "toastCreated": "폴더가 생성되었습니다", + "cancel": "취소", + "submit": "폴더 만들기" + }, + "editFolder": { + "title": "폴더 이름 변경", + "nameLabel": "폴더 이름", + "namePlaceholder": "폴더 이름", + "nameRequired": "폴더 이름을 입력해 주세요", + "toastRenamed": "폴더 이름이 변경되었습니다", + "cancel": "취소", + "save": "저장" + } + }, + "EmailValidator": { + "header": { + "title": "이메일 검증기", + "description": "이메일 주소의 구문, 도메인 및 전달 가능 여부를 검증하세요." + }, + "tabs": { + "single": "단일 검증", + "bulk": "대량 업로드" + }, + "single": { + "cardTitle": "단일 이메일 검증", + "cardDescription": "이메일 주소를 입력하여 전달 가능 여부와 평판을 확인하세요.", + "placeholder": "이메일 주소 입력...", + "clearAria": "이메일 지우기", + "validate": "검증", + "validating": "검증 중...", + "formatError": "유효한 이메일 주소를 입력해 주세요" + }, + "toasts": { + "errorTitle": "오류", + "enterEmail": "이메일 주소를 입력해 주세요", + "invalidFormatTitle": "잘못된 이메일 형식", + "invalidFormatDescription": "유효한 이메일 주소를 입력해 주세요", + "validateFailedDescription": "이메일 검증 중 오류가 발생했습니다", + "noEmailsTitle": "이메일을 찾을 수 없습니다", + "noEmailsDescription": "Excel에 'email' 열이 있는지 확인해 주세요.", + "fileTooLargeTitle": "파일이 너무 큽니다", + "fileTooLargeDescription": "{total}개의 이메일이 발견되었습니다. 처음 4000개만 검증됩니다.", + "successTitle": "성공", + "successValidated": "{count}개의 이메일을 검증했습니다.", + "batchFailedTitle": "대량 검증 실패", + "batchFailedDescription": "대량 검증 처리 중 오류가 발생했습니다.", + "noExportTitle": "내보낼 데이터가 없습니다", + "noExportValidOnly": "내보낼 유효한 이메일이 없습니다.", + "noExportAll": "내보낼 결과가 없습니다.", + "exportSuccessTitle": "내보내기 성공", + "exportSuccessExcelAll": "{count}개의 이메일을 Excel로 내보냈습니다.", + "exportSuccessExcelValid": "{count}개의 유효한 이메일을 Excel로 내보냈습니다.", + "exportSuccessCsvAll": "{count}개의 이메일을 CSV로 내보냈습니다.", + "exportSuccessCsvValid": "{count}개의 유효한 이메일을 CSV로 내보냈습니다." + }, + "disposableAlert": { + "title": "일회용 이메일 감지됨", + "description": "이 이메일 주소는 일회용 이메일 제공업체에 속합니다. 임시이거나 안전하지 않을 수 있습니다." + }, + "score": { + "reliabilityTitle": "신뢰도 점수", + "scoreLabel": "점수" + }, + "details": { + "sectionTitle": "검증 세부 사항" + }, + "validation": { + "syntaxLabel": "구문 유효", + "syntaxTooltip": "이메일 주소가 올바른 형식(예: user@domain.com)을 따르는지 확인합니다", + "domainLabel": "도메인 존재", + "domainTooltip": "이메일 주소의 도메인 이름이 실제로 존재하는지 확인합니다", + "mxLabel": "MX 레코드 존재", + "mxTooltip": "도메인에 이메일을 수신하도록 구성된 메일 교환(MX) 레코드가 있는지 확인합니다", + "mailboxLabel": "메일함 존재", + "mailboxTooltip": "메일 서버에 특정 메일함/계정이 존재하는지 확인합니다", + "disposableLabel": "일회용 이메일", + "disposableTooltip": "이 이메일이 임시/일회용 이메일 서비스에 속하는지 나타냅니다 (위험할 수 있음)", + "roleBasedLabel": "역할 기반 이메일", + "roleBasedTooltip": "이것이 개인 주소가 아닌 역할 기반 이메일(예: support@, info@)인지 식별합니다" + }, + "copy": { + "emailCopied": "이메일이 복사되었습니다!", + "copyTooltip": "이메일 복사" + }, + "bulk": { + "dropHere": "여기에 파일을 드롭하세요", + "dragHere": "파일을 여기에 드래그 앤 드롭하세요", + "uploadHint": "최대 4000개의 이메일을 검증하려면 'email' 열이 포함된 .xlsx, .xls 또는 .csv 파일을 업로드하세요.", + "filePrefix": "파일:", + "selectFile": "파일 선택", + "downloadTemplate": "템플릿 다운로드" + }, + "bulkLoading": { + "title": "이메일 검증 중...", + "batchProgress": "배치 {current}/{total} 처리 중..." + }, + "stats": { + "totalProcessed": "총 처리", + "valid": "유효", + "risky": "위험", + "invalid": "무효" + }, + "results": { + "detailedTitle": "상세 결과", + "searchPlaceholder": "이메일 검색...", + "newVerification": "새 검증", + "exportAll": "전체 내보내기", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "유효한 항목 내보내기 ({count})" + }, + "showingResults": "{total}개 중 {shown}개 표시", + "table": { + "email": "이메일", + "status": "상태", + "score": "점수", + "checks": "검사" + }, + "noSearchResults": "\"{query}\"와(과) 일치하는 결과가 없습니다", + "checksTooltip": { + "syntax": "구문", + "domain": "도메인", + "mx": "MX", + "mailbox": "메일함" + }, + "exportColumns": { + "email": "이메일", + "status": "상태", + "score": "점수", + "syntax": "구문", + "domain": "도메인", + "mx": "MX", + "mailbox": "메일함", + "disposable": "일회용", + "roleBased": "역할 기반", + "valid": "유효", + "invalid": "무효", + "yes": "예", + "no": "아니오" + }, + "sheets": { + "validEmails": "유효한 이메일", + "results": "결과", + "template": "템플릿" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL 탐색기", + "heroSubtitle": "데이터베이스에 연결하여 컬렉션을 관리하고, 쿼리를 실행하고, 데이터를 시각화하세요.", + "featureMultiTitle": "다중 연결", + "featureMultiDesc": "여러 데이터베이스 연결을 동시에 관리하세요.", + "featureMongoTitle": "MongoDB 지원", + "featureMongoDesc": "고급 필터링을 지원하는 네이티브 MongoDB 지원.", + "featureQueryTitle": "스마트 쿼리", + "featureQueryDesc": "구문 강조 기능을 갖춘 강력한 쿼리 빌더.", + "connectCta": "MongoDB에 연결", + "footerNote": "현재 {db}를 지원합니다. 더 많은 데이터베이스가 곧 추가됩니다.", + "footerDb": "MongoDB", + "selectCollectionTitle": "컬렉션을 선택하세요", + "selectCollectionDesc": "사이드바에서 컬렉션을 선택하여 문서를 보고 데이터 탐색을 시작하세요.", + "dialogAddConnection": "연결 추가", + "toastConnectionAdded": "연결이 추가되었습니다", + "toastFetchFailed": "{collection}의 문서를 가져오는 데 실패했습니다", + "toastDocDeleted": "문서가 삭제되었습니다", + "deleteDocTitle": "정말로 삭제하시겠습니까?", + "deleteDocDescription": "이 작업은 되돌릴 수 없습니다. 컬렉션에서 문서가 영구적으로 삭제됩니다.", + "closeAllTitle": "모든 탭을 닫으시겠습니까?", + "closeAllDescription": "모든 탭을 닫으시겠습니까? 쿼리 필터의 저장되지 않은 변경 사항이 손실됩니다.", + "closeAllConfirm": "모두 닫기", + "cancel": "취소", + "delete": "삭제" + }, + "connection": { + "titleEdit": "연결 수정", + "titleConnect": "MongoDB에 연결", + "subtitleEdit": "아래에서 연결 세부 사항을 업데이트하세요.", + "subtitleConnect": "MongoDB 연결 문자열을 입력하여 데이터 탐색을 시작하세요.", + "labelName": "이름", + "placeholderName": "예: 운영 DB", + "labelConnectionString": "연결 문자열", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "형식: mongodb://username:password@host:port/database", + "testConnection": "연결 테스트", + "connecting": "연결 중...", + "updateConnect": "업데이트 및 연결", + "connect": "연결", + "cancelEdit": "편집 취소", + "savedConnections": "저장된 연결", + "emptySaved": "아직 저장된 연결이 없습니다.", + "lastUsed": "마지막 사용: {time}", + "toastTestOk": "연결 성공!", + "toastTestFail": "연결 실패: {message}", + "toastUpdated": "연결이 업데이트되었습니다", + "confirmDelete": "이 저장된 연결을 삭제하시겠습니까?", + "toastDeletedConn": "연결이 삭제되었습니다", + "toastDeleteFail": "연결 삭제에 실패했습니다", + "copyStringTitle": "연결 문자열 복사", + "toastStringCopied": "연결 문자열이 복사되었습니다!", + "editTitle": "수정", + "deleteTitle": "삭제" + }, + "sidebar": { + "explorer": "탐색기", + "add": "추가", + "tooltipAdd": "새 연결 추가", + "tooltipRefresh": "연결 새로고침", + "searchPlaceholder": "검색...", + "loadingConnections": "연결을 불러오는 중...", + "noConnectionsFound": "연결을 찾을 수 없습니다", + "toastLoadFail": "연결 불러오기에 실패했습니다", + "toastConnectFail": "{name}에 연결하는 데 실패했습니다", + "toastCollectionsFail": "{name}의 컬렉션을 가져오는 데 실패했습니다", + "toastRenamed": "연결 이름이 변경되었습니다", + "toastRenameFail": "연결 이름 변경에 실패했습니다", + "confirmDeleteConnection": "\"{name}\" 연결을 삭제하시겠습니까?", + "toastDeleted": "연결이 삭제되었습니다", + "toastDeleteConnFail": "연결 삭제에 실패했습니다", + "confirmDropDb": "\"{name}\" 데이터베이스를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "toastDbDropped": "{name} 데이터베이스가 삭제되었습니다", + "confirmDropCollection": "\"{name}\" 컬렉션을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "toastCollectionDropped": "{name} 컬렉션이 삭제되었습니다", + "toastCollectionRenamed": "컬렉션 이름이 {name}(으)로 변경되었습니다", + "toastDatabaseRenamed": "데이터베이스 이름이 {name}(으)로 변경되었습니다", + "connecting": "연결 중...", + "connectionFailed": "연결 실패", + "noDatabases": "데이터베이스 없음", + "loading": "로딩 중...", + "noCollections": "컬렉션 없음", + "renameCollectionTitle": "컬렉션 이름 변경", + "renameDatabaseTitle": "데이터베이스 이름 변경", + "placeholderNewCollection": "새 컬렉션 이름", + "placeholderNewDatabase": "새 데이터베이스 이름", + "rename": "이름 변경", + "refresh": "새로고침", + "copyDbName": "DB 이름 복사", + "toastDbNameCopied": "데이터베이스 이름이 클립보드에 복사되었습니다", + "dropDatabase": "데이터베이스 삭제", + "dropCollection": "컬렉션 삭제", + "menuRename": "이름 변경", + "menuCopyConnectionString": "연결 문자열 복사", + "menuDeleteConnection": "연결 삭제", + "toastSidebarStringCopied": "연결 문자열이 복사되었습니다!", + "cancel": "취소", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n}개 문서", + "table": "테이블", + "json": "JSON", + "tree": "트리", + "tooltipTable": "테이블 보기", + "tooltipJson": "JSON 보기", + "tooltipTree": "트리 보기", + "itemsPerPage": "페이지당 항목", + "perPage": "{n} / 페이지", + "prevPage": "이전 페이지", + "nextPage": "다음 페이지", + "expandAll": "모두 펼치기", + "collapseAll": "모두 접기", + "refreshData": "데이터 새로고침", + "insert": "삽입", + "export": "내보내기", + "actions": "작업", + "invalidJson": "잘못된 JSON 콘텐츠", + "copiedClipboard": "클립보드에 복사되었습니다", + "docUpdated": "문서가 업데이트되었습니다", + "docInserted": "문서가 삽입되었습니다", + "invalidJsonShort": "잘못된 JSON", + "docCopied": "문서가 클립보드에 복사되었습니다", + "openFullEditor": "전체 편집기에서 열기", + "cellArray": "Array({length})", + "cellObject": "Object", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "쿼리와 일치하는 문서가 없습니다", + "emptyFilterHint": "필터를 조정하거나 쿼리를 지워 보세요.", + "clearFilter": "필터 지우기", + "emptyCollectionTitle": "이 컬렉션은 비어 있습니다", + "emptyCollectionHint": "문서를 삽입하여 시작하세요.", + "insertDocument": "문서 삽입", + "formatJson": "JSON 포맷", + "compactJson": "JSON 압축", + "copyClipboard": "클립보드에 복사", + "copyJson": "JSON 복사", + "duplicate": "복제", + "editDoc": "문서 수정", + "deleteDoc": "문서 삭제", + "documentLabel": "문서 {n}", + "editDialogTitle": "문서 수정", + "insertDialogTitle": "문서 삽입", + "viewValueTitle": "값 보기", + "saveChanges": "변경 사항 저장", + "close": "닫기", + "cancel": "취소", + "insertButton": "삽입", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "활성 필터 적용됨", + "filterActiveTooltip": "필터 활성", + "closeAll": "모두 닫기" + }, + "query": { + "migrateHistory": "쿼리 기록이 클라우드로 마이그레이션되었습니다", + "saveHistoryFail": "쿼리 기록 저장에 실패했습니다", + "deleteQueryFail": "쿼리 삭제에 실패했습니다", + "invalidJsonQuery": "잘못된 JSON 쿼리", + "placeholder": "예: { \"status\": \"active\" }", + "clearQuery": "쿼리 지우기", + "openAdvanced": "고급 편집기 열기", + "visualBuilder": "비주얼 쿼리 빌더", + "filterRules": "필터 규칙", + "addRule": "규칙 추가", + "noFilters": "필터가 적용되지 않았습니다. 문서를 필터링하려면 규칙을 추가하세요.", + "where": "조건", + "and": "그리고", + "fieldPlaceholder": "필드", + "opPlaceholder": "연산자", + "typePlaceholder": "타입", + "opRegex": "정규식", + "opIn": "포함", + "opNin": "미포함", + "opExists": "존재", + "typeAuto": "자동", + "typeString": "문자열", + "typeNumber": "숫자", + "typeBoolean": "불리언", + "typeDate": "날짜", + "typeObjectId": "ObjectId", + "true": "참", + "false": "거짓", + "valuePlaceholder": "값", + "valueListPlaceholder": "값1, 값2", + "clearFilters": "필터 지우기", + "applyFilters": "필터 적용", + "queryHistory": "쿼리 기록", + "noSavedQueries": "아직 저장된 쿼리가 없습니다", + "advancedEditorTitle": "고급 쿼리 편집기", + "templatesPlaceholder": "템플릿", + "templateEmptyAgg": "빈 집계", + "templateMatch": "$match (필터)", + "templateLookup": "$lookup (조인)", + "templateGroup": "$group (카운트/합계)", + "format": "포맷", + "formattedJson": "포맷된 JSON", + "runQuery": "쿼리 실행" + }, + "export": { + "selectFieldsError": "내보낼 필드를 하나 이상 선택해 주세요", + "success": "{filename}(으)로 내보내기 완료", + "fail": "데이터 내보내기에 실패했습니다", + "title": "데이터 내보내기", + "formatLabel": "내보내기 형식", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "필드 선택", + "selectAll": "모두 선택", + "fieldsSelected": "{count, plural, one {#개 필드 선택됨} other {#개 필드 선택됨}}", + "cancel": "취소", + "export": "내보내기", + "sheetName": "데이터" + }, + "jsonTree": { + "typeLabel": "타입: {type}", + "itemSummary": "{count, plural, one {#개 항목} other {#개 항목}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "새 요청", + "importedRequest": "가져온 요청", + "untitledRequest": "제목 없는 요청" + }, + "toasts": { + "curlImported": "cURL을 가져왔습니다", + "curlParseFailed": "cURL 명령 파싱에 실패했습니다", + "invalidJsonBody": "잘못된 JSON 본문", + "requestFailed": "요청 실패: {message}", + "curlPasted": "cURL이 붙여넣기 및 파싱되었습니다", + "responseCopied": "응답이 클립보드에 복사되었습니다", + "codeCopied": "코드가 클립보드에 복사되었습니다", + "copyFailed": "클립보드에 복사하지 못했습니다" + }, + "layout": { + "collections": "컬렉션", + "closeSidebar": "사이드바 닫기", + "openSidebar": "사이드바 열기", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "메서드", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "전송 중...", + "send": "전송", + "invalidJsonBodyHelp": "전송 불가: JSON 본문이 유효하지 않습니다" + }, + "requestTabs": { + "params": "매개변수", + "headers": "헤더", + "body": "본문", + "auth": "인증", + "bodyType": "본문 유형", + "selectType": "유형 선택", + "bodyNone": "없음", + "bodyJson": "JSON", + "bodyText": "텍스트", + "noBodyHint": "이 요청에는 본문 콘텐츠가 없습니다", + "switchToJson": "JSON 본문으로 전환", + "authType": "인증 유형", + "noAuth": "인증 없음", + "bearerToken": "Bearer 토큰", + "basicAuth": "Basic 인증", + "apiKey": "API 키", + "labelToken": "토큰", + "placeholderBearerToken": "Bearer 토큰", + "labelUsername": "사용자 이름", + "placeholderUsername": "사용자 이름", + "labelPassword": "비밀번호", + "placeholderPassword": "비밀번호", + "labelKey": "키", + "placeholderApiKeyName": "키 이름 (예: X-Api-Key)", + "labelValue": "값", + "placeholderValue": "값", + "addTo": "추가 위치", + "header": "헤더", + "queryParams": "쿼리 매개변수", + "noAuthHint": "이 요청은 인증을 사용하지 않습니다." + }, + "responsePanel": { + "emptyTitle": "탐색할 준비가 되셨나요?", + "emptyHint": "URL을 입력하고 전송을 클릭하여 여기에서 응답 데이터를 확인하세요.", + "responsePreviewAlt": "응답 미리보기", + "previewUnavailable": "이 콘텐츠 유형에 대한 미리보기를 사용할 수 없습니다", + "status": "상태", + "time": "시간", + "size": "크기", + "copyBody": "본문 복사", + "downloadResponse": "응답 다운로드", + "bodyTab": "본문", + "previewTab": "미리보기", + "headersTab": "헤더", + "binaryRawView": "이진 데이터는 원시 보기에서 표시할 수 없습니다.", + "errorStatusLabel": "오류", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "새 탭" + }, + "collectionsSidebar": { + "title": "API 리소스", + "newCollection": "새 컬렉션", + "tabCollections": "컬렉션", + "tabHistory": "기록", + "noCollectionsTitle": "컬렉션 없음", + "noCollectionsHint": "API 요청을 정리하기 위한 컬렉션을 만드세요.", + "createCollection": "컬렉션 만들기", + "newFolder": "새 폴더", + "rename": "이름 변경", + "delete": "삭제", + "emptyCollection": "빈 컬렉션", + "recentRequests": "최근 요청", + "clearAll": "모두 지우기", + "noHistoryTitle": "아직 기록이 없습니다", + "noHistoryHint": "요청을 전송하면 여기에 표시됩니다.", + "dialogNewFolderTitle": "새 폴더", + "dialogNewFolderDescription": "요청을 정리하기 위한 새 폴더를 만드세요.", + "labelFolderName": "폴더 이름", + "placeholderFolderName": "내 폴더", + "createFolder": "폴더 만들기", + "dialogNewCollectionTitle": "새 컬렉션", + "dialogNewCollectionDescription": "새 최상위 컬렉션을 만드세요.", + "labelCollectionName": "컬렉션 이름", + "placeholderCollectionName": "내 컬렉션", + "dialogRenameCollectionTitle": "컬렉션 이름 변경", + "dialogRenameCollectionDescription": "컬렉션의 새 이름을 입력하세요.", + "placeholderRenameCollection": "컬렉션 이름", + "renameAction": "이름 변경", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "새 폴더", + "delete": "삭제", + "emptyFolder": "빈 폴더" + }, + "environmentManager": { + "noEnvironment": "환경 없음", + "manageDialogTitle": "환경 관리", + "placeholderNewEnv": "새 환경", + "variablesHeading": "{name} 변수", + "addVariable": "변수 추가", + "columnVariable": "변수", + "columnValue": "값", + "placeholderKey": "키", + "placeholderValue": "값", + "selectEnvToEdit": "편집할 환경을 선택하세요" + }, + "codeGenerator": { + "triggerTitle": "코드 생성", + "dialogTitle": "코드 생성", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "복사", + "errorGenerating": "코드 생성 중 오류가 발생했습니다" + }, + "keyValueEditor": { + "columnKey": "키", + "columnValue": "값", + "placeholderKey": "키", + "placeholderValue": "값", + "addItem": "항목 추가" + }, + "importCurl": { + "trigger": "cURL 가져오기", + "dialogTitle": "cURL 가져오기", + "dialogDescription": "아래에 cURL 명령을 붙여넣어 새 요청으로 가져오세요.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "가져오기" + }, + "saveRequest": { + "trigger": "저장", + "dialogTitle": "요청 저장", + "dialogDescription": "이 요청을 컬렉션 폴더에 저장하세요.", + "labelName": "요청 이름", + "placeholderName": "내 요청", + "labelFolder": "폴더", + "placeholderFolder": "폴더를 선택하세요", + "save": "저장" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "편집 모드", + "viewOnlyMode": "보기 전용 모드", + "insertComponent": "구성 요소 삽입", + "addUnorderedList": "순서 없는 목록 추가", + "addOrderedList": "순서 있는 목록 추가", + "addTable": "테이블 추가" + }, + "addBlock": "블록 추가", + "media": { + "popoverTitle": "미디어 추가", + "singleImage": "단일 이미지", + "singleImageDesc": "이미지 하나 업로드", + "multipleImages": "여러 이미지", + "multipleImagesDesc": "이미지 그리드 업로드", + "video": "비디오", + "videoDesc": "비디오 파일 업로드", + "uploadingAlt": "업로드 중...", + "uploadFailedAlt": "업로드 실패" + }, + "export": { + "fabAria": "코드 내보내기", + "title": "코드 내보내기", + "description": "편집기 콘텐츠의 HTML 또는 JSON 출력을 복사하세요", + "preview": "미리보기", + "html": "HTML", + "json": "JSON", + "previewOptions": "미리보기 옵션", + "enhanceSpaces": "공백 향상", + "livePreview": "렌더링된 HTML 실시간 미리보기", + "htmlTailwind": "Tailwind CSS 클래스가 포함된 HTML", + "editorJson": "편집기 상태 (JSON)", + "copyHtml": "HTML 복사", + "copyJson": "JSON 복사", + "copied": "복사됨!" + }, + "template": { + "fabTitle": "템플릿 전환", + "title": "템플릿 선택", + "description": "미리 만들어진 레이아웃과 콘텐츠로 빠르게 시작할 템플릿을 선택하세요", + "catAll": "모든 템플릿", + "catProductivity": "생산성", + "catCreative": "크리에이티브", + "catBusiness": "비즈니스", + "catPersonal": "개인", + "noTemplates": "이 카테고리에는 템플릿이 없습니다", + "confirmTitle": "기존 콘텐츠를 교체하시겠습니까?", + "confirmDescription": "편집기에 기존 콘텐츠가 있습니다. 이 템플릿을 적용하면 현재 콘텐츠가 모두 교체됩니다.", + "cannotUndo": "이 작업은 되돌릴 수 없습니다.", + "cancel": "취소", + "replaceContent": "콘텐츠 교체" + }, + "command": { + "searchPlaceholder": "명령 검색...", + "noResults": "명령을 찾을 수 없습니다.", + "turnInto": "변환", + "h1": { + "label": "제목 1", + "description": "큰 섹션 제목", + "keywords": "h1,heading,title,large" + }, + "h2": { + "label": "제목 2", + "description": "중간 섹션 제목", + "keywords": "h2,heading,subtitle" + }, + "h3": { + "label": "제목 3", + "description": "작은 섹션 제목", + "keywords": "h3,heading,subheading" + }, + "h4": { + "label": "제목 4", + "description": "아주 작은 섹션 제목", + "keywords": "h4,heading" + }, + "h5": { + "label": "제목 5", + "description": "더 작은 제목", + "keywords": "h5,heading" + }, + "h6": { + "label": "제목 6", + "description": "가장 작은 제목", + "keywords": "h6,heading" + }, + "p": { + "label": "문단", + "description": "일반 텍스트 문단", + "keywords": "p,paragraph,text,normal" + }, + "code": { + "label": "코드 블록", + "description": "코드 스니펫", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "인용문", + "description": "블록 인용문", + "keywords": "quote,blockquote,citation" + }, + "li": { + "label": "글머리 기호 목록", + "description": "글머리 기호가 있는 간단한 목록 항목", + "keywords": "list,bullet,unordered,ul,li" + }, + "ol": { + "label": "번호 매기기 목록", + "description": "번호가 매겨진 목록 항목", + "keywords": "list,numbered,ordered,ol,li" + }, + "img": { + "label": "이미지", + "description": "이미지를 업로드하거나 삽입", + "keywords": "image,img,picture,photo,upload" + }, + "video": { + "label": "비디오", + "description": "비디오를 업로드하거나 삽입", + "keywords": "video,vid,movie,mp4,upload" + }, + "table": { + "label": "테이블", + "description": "테이블 만들기", + "keywords": "table,grid,rows,columns,cells" + } + }, + "toasts": { + "tableImported": "테이블 가져오기 완료", + "tableImportedDesc": "마크다운 테이블이 가져와졌습니다", + "mediaPasted": "미디어 붙여넣기 완료", + "pasteMixed": "이미지 {imageCount}개와 비디오 {videoCount}개가 붙여넣기되었습니다.", + "pasteImages": "이미지 {count}개가 붙여넣기되었습니다.", + "pasteVideos": "비디오 {count}개가 붙여넣기되었습니다.", + "pasteFailed": "붙여넣기 실패", + "unexpectedError": "예기치 않은 오류가 발생했습니다", + "uploadFailed": "업로드 실패" + }, + "blockBackground": { + "menuItem": "배경색 변경", + "dialogTitle": "블록 배경색", + "dialogDescription": "이 블록의 배경색을 선택하세요", + "tabPreset": "프리셋 색상", + "tabCustom": "사용자 지정 색상", + "sectionHeading": "배경색", + "apply": "적용", + "colors": { + "none": "없음", + "red": "빨강", + "orange": "주황", + "yellow": "노랑", + "green": "초록", + "blue": "파랑", + "indigo": "남색", + "purple": "보라", + "pink": "분홍", + "teal": "청록", + "cyan": "시안", + "gray": "회색" + } + }, + "tableDialog": { + "title": "테이블 만들기", + "description": "새 테이블을 만들거나 마크다운에서 가져오기", + "markdownCheckbox": "마크다운 테이블이 있습니다", + "pasteMarkdownLabel": "마크다운 테이블 붙여넣기", + "markdownPlaceholder": "| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "위에 마크다운 테이블을 붙여넣으세요. 헤더와 구분자 행이 포함되어야 합니다.", + "rows": "행", + "columns": "열", + "maxDimensions": "최대: 20행 × 10열", + "cancel": "취소", + "importTable": "테이블 가져오기", + "createTable": "테이블 만들기" + }, + "tableParse": { + "errors": { + "minRows": "테이블에는 최소 헤더 행과 구분자 행이 있어야 합니다", + "pipeFormat": "테이블 행은 |로 시작하고 끝나야 합니다", + "headerMinCol": "헤더 행에는 최소 하나의 열이 있어야 합니다", + "separatorRow": "두 번째 행은 구분자여야 합니다 (예: |---|---|)", + "rowColumnMismatch": "{row}행에 {got}개의 열이 있지만, {expected}개의 열이 예상됩니다", + "parseFailed": "마크다운 테이블 파싱에 실패했습니다" + } + }, + "link": { + "editLink": "링크 수정", + "addLink": "링크 추가", + "selectedText": "선택한 텍스트: \"{text}\"", + "linkUrl": "링크 URL", + "urlPlaceholder": "https://example.com", + "update": "업데이트", + "addLinkButton": "링크 추가", + "toastApplied": "링크 적용됨", + "toastLinkedTo": "링크됨: {url}", + "toastRemoved": "링크 제거됨", + "toastRemovedDesc": "선택 영역에서 링크가 제거되었습니다" + }, + "customClass": { + "devMode": "개발자 모드", + "toggleDevModeAria": "개발자 모드 전환", + "searchPlaceholderDev": "클래스 검색... (예: 'text', 'bg', 'flex')", + "searchPlaceholderUser": "스타일 검색... (예: 'red', 'bold', 'shadow')", + "appliesTitle": "적용: {className}", + "noMatches": "\"{query}\"와(과) 일치하는 클래스가 없습니다", + "toastClassReplaced": "클래스 교체됨", + "toastClassReplacedDesc": "\"{replaced}\"이(가) \"{className}\"(으)로 교체되었습니다", + "toastCustomClassApplied": "사용자 지정 클래스 적용됨", + "toastAppliedClass": "적용된 클래스: {className}" + }, + "insertComponents": { + "title": "구성 요소 삽입", + "description": "문서에 삽입할 구성 요소를 선택하세요", + "noComponents": "사용 가능한 구성 요소가 없습니다", + "categories": { + "media": "미디어", + "layout": "레이아웃", + "interactive": "인터랙티브", + "decoration": "장식" + }, + "freeImage": { + "name": "자유 이동 이미지", + "description": "어디든 배치하고 자유롭게 크기를 조절할 수 있는 이미지를 추가하세요" + } + } + }, + "JsonFormatter": { + "title": "JSON 포맷터", + "description": "텍스트 및 트리 보기에서 JSON을 포맷, 검증, 편집하세요.", + "documentNameText": "텍스트 문서 {n}", + "documentNameTree": "트리 문서 {n}", + "newDocument": "새로 만들기", + "copy": "복사", + "save": "저장", + "load": "불러오기", + "saving": "저장 중...", + "leftDocNameLabel": "왼쪽 문서 이름", + "rightDocNameLabel": "오른쪽 문서 이름", + "toastCopiedText": "텍스트 패널 복사됨", + "toastCopiedTree": "트리 패널 복사됨", + "toastCopyFailed": "JSON 복사 실패", + "toastLoginRequired": "문서를 저장하려면 로그인하세요", + "toastSaved": "문서가 저장되었습니다", + "toastSaveFailed": "문서 저장에 실패했습니다", + "format": "포맷", + "compact": "압축", + "sortKeys": "키 정렬", + "repair": "복구", + "query": "쿼리", + "toastInvalidJson": "잘못된 JSON", + "toastAlreadyValid": "JSON이 이미 유효합니다", + "toastRepaired": "JSON 복구됨", + "toastRepairFailed": "JSON을 복구할 수 없습니다" + }, + "JwtDecoder": { + "title": "JWT 디코더", + "subtitle": "헤더와 페이로드를 확인합니다. 만료는 exp 클레임을 사용합니다. 서버로 전송되지 않으며 서명은 검증되지 않습니다.", + "tokenLabel": "토큰", + "clear": "지우기", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "복사", + "expiryLabel": "만료 (exp)", + "signaturePresent": "서명 존재(검증 안 함)", + "noExp": "페이로드에 exp 클레임이 없습니다.", + "status": { + "expired": "만료됨", + "valid": "유효함" + }, + "fields": { + "unix": "Unix", + "local": "로컬", + "utc": "UTC" + }, + "claims": { + "issuedAt": "발급 (iat)", + "notBefore": "유효 시작 (nbf)" + }, + "panels": { + "header": "헤더", + "payload": "페이로드" + }, + "errors": { + "empty": "디코딩할 JWT를 붙여넣으세요.", + "segments": "JWT에는 최소 헤더와 페이로드(두 세그먼트)가 필요합니다.", + "base64": "헤더 또는 페이로드를 base64url 디코딩할 수 없습니다." + }, + "relative": { + "future": "{n}{unit} 후", + "past": "{n}{unit} 전" + }, + "units": { + "second": "{count, plural, one {초} other {초}}", + "minute": "{count, plural, one {분} other {분}}", + "hour": "{count, plural, one {시간} other {시간}}", + "day": "{count, plural, one {일} other {일}}" + }, + "tabs": { + "decode": "디코드", + "sign": "생성 / 서명" + }, + "signer": { + "algLabel": "알고리즘 (alg)", + "tabs": { + "payload": "페이로드", + "header": "헤더" + }, + "payloadLabel": "페이로드 JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "헤더 JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "시크릿 (HS*)", + "secretPlaceholder": "HMAC 시크릿을 붙여넣으세요…", + "privateKeyLabel": "개인 키 PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "\"none\" 알고리즘은 서명되지 않은 토큰을 생성합니다. 안전하지 않으므로 테스트에만 사용하세요.", + "sign": "서명", + "verify": "서명 검증", + "outputLabel": "토큰(컴팩트 JWS)", + "outputPlaceholder": "\"서명\"을 클릭하세요…", + "copyToken": "토큰 복사", + "copied": "복사됨", + "verifyKeyLabel": "검증 키(선택 사항)", + "verifyKeyPlaceholder": "RS*: 공개 키(SPKI) PEM을 붙여넣으세요. HS*: 비워두면 시크릿을 재사용합니다.", + "verifyKeyHint": "비어 있으면 검증 시 서명 키를 사용합니다(HS*에 유용).", + "verifyStatus": { + "valid": "서명이 유효합니다", + "invalid": "서명이 유효하지 않습니다" + }, + "securityNote": "모든 작업은 브라우저에서 로컬로 실행되며 키와 토큰은 업로드되지 않습니다." + }, + "signErrors": { + "invalidJson": "헤더와 페이로드는 유효한 JSON이어야 합니다.", + "unsupportedAlg": "지원되지 않는 알고리즘입니다.", + "missingKey": "이 알고리즘에는 서명 키가 필요합니다.", + "cryptoFailed": "서명에 실패했습니다. 키 형식을 확인하고 다시 시도하세요." + }, + "verifyErrors": { + "empty": "검증할 JWT를 입력하세요.", + "segments": "서명을 검증하려면 JWT가 정확히 3개 세그먼트여야 합니다.", + "unsupportedAlg": "헤더에 alg가 없거나 지원되지 않습니다.", + "missingKey": "이 알고리즘에는 검증 키가 필요합니다.", + "base64": "JWT 헤더 또는 서명을 디코딩할 수 없습니다.", + "cryptoFailed": "검증에 실패했습니다. 키 형식을 확인하고 다시 시도하세요." + } + }, + "CertificatePemDecoder": { + "title": "인증서 / PEM 디코더", + "subtitle": "X.509 인증서(-----BEGIN CERTIFICATE-----) 또는 CSR(-----BEGIN CERTIFICATE REQUEST-----)을 붙여넣으세요. 주체, 발급자, 날짜, SAN은 로컬에서 분석되며 업로드되지 않습니다.", + "pemLabel": "PEM 입력", + "clear": "지우기", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "분석 중…", + "copyHex": "지문 복사(콜론 없는 16진)", + "errors": { + "empty": "디코딩할 PEM 인증서 또는 CSR을 붙여넣으세요.", + "noBlock": "인증서 또는 CSR PEM 블록을 찾을 수 없습니다.", + "parseFailed": "이 PEM 데이터를 분석할 수 없습니다." + }, + "block": { + "title": "PEM 블록 {index}", + "certificate": "X.509 인증서", + "csr": "인증서 서명 요청" + }, + "fields": { + "subject": "주체", + "issuer": "발급자", + "notBefore": "이전에는 유효하지 않음", + "notAfter": "이후에는 유효하지 않음", + "serial": "일련 번호(16진)", + "sha256": "SHA-256 지문", + "sans": "주체 대체 이름", + "sansNone": "SAN 확장이 없습니다.", + "signatureAlgorithm": "서명 알고리즘", + "csrIssuerNote": "CSR에는 발급자가 없습니다(발급된 인증서에 표시됩니다).", + "csrValidityNote": "CSR에는 유효 기간이 없습니다(발급된 인증서에 표시됩니다)." + }, + "sanType": { + "dns": "DNS", + "dn": "디렉터리 이름", + "email": "이메일", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "등록 ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL 포매터", + "subtitle": "MySQL, PostgreSQL 또는 SQLite용 SQL을 보기 좋게 정리합니다. 브라우저에서 실행되며 업로드되지 않습니다.", + "dialectLabel": "방언", + "keywordsLabel": "키워드", + "indentLabel": "들여쓰기", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "대문자", + "lower": "소문자", + "preserve": "유지" + }, + "spaces": "{n}칸", + "format": "포맷", + "clear": "지우기", + "counter": "{current} / {max}자", + "inputPanel": "입력", + "formattedPanel": "결과", + "copy": "복사", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "“포맷”을 클릭…", + "errors": { + "tooLong": "입력은 최대 {max}자까지입니다.", + "couldNotFormat": "이 SQL을 포맷할 수 없습니다." + } + }, + "YamlFormatter": { + "title": "YAML 포매터", + "subtitle": "브라우저에서 YAML을 형식화, 유효성 검사, 변환합니다. 업로드되지 않습니다.", + "outputLabel": "출력", + "outputModes": { + "yaml": "YAML (형식화됨)", + "json": "JSON" + }, + "indentLabel": "들여쓰기", + "spaces": "{n} 칸", + "format": "형식화", + "clear": "지우기", + "copy": "복사", + "counter": "{current} / {max} 자", + "inputPanel": "입력", + "outputPanel": "출력", + "errors": { + "tooLong": "입력은 {max}자로 제한됩니다.", + "couldNotFormat": "이 YAML을 파싱할 수 없습니다." + } + }, + "GraphqlFormatter": { + "title": "GraphQL 포매터", + "subtitle": "브라우저에서 GraphQL 쿼리·뮤테이션·구독을 포맷합니다. 업로드되지 않습니다.", + "tabs": { + "format": "포맷", + "build": "쿼리 빌더" + }, + "outputModeLabel": "출력", + "outputModes": { + "pretty": "읽기 쉬운 형식", + "minify": "압축" + }, + "format": "포맷", + "clear": "지우기", + "counter": "{current} / {max}자", + "inputPanel": "입력", + "outputPanel": "출력", + "copy": "복사", + "builderHint": "문서 뼈대를 만든 뒤 입력 편집기에 적용합니다. 변수 정의는 GraphQL 구문(예: $id: ID!)을 사용하며 괄호는 선택 사항입니다.", + "builder": { + "operation": "오퍼레이션", + "operationName": "오퍼레이션 이름", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "변수 정의", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "선택 집합", + "apply": "입력에 적용", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "입력은 최대 {max}자까지 가능합니다.", + "couldNotFormat": "이 GraphQL을 파싱하거나 출력할 수 없습니다.", + "emptySelection": "적용하기 전에 선택 집합을 추가하세요." + } + }, + "RegexTester": { + "title": "정규식 테스터", + "subtitle": "텍스트에 대해 JavaScript 스타일 정규식을 실시간으로 실행합니다. 매칭은 강조 표시되며 브라우저 밖으로 나가지 않습니다.", + "patternLabel": "패턴", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g 전역", + "i": "i 대소문자 무시", + "m": "m 멀티라인", + "s": "s dotAll", + "u": "u 유니코드", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(비어 있음)", + "noFlags": "—", + "matches": "{count, plural, one {일치} other {일치}}", + "testTextLabel": "테스트 텍스트", + "counter": "{current} / {max}", + "clear": "지우기", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "텍스트는 최대 {max}자까지입니다.", + "invalid": "잘못된 정규식입니다.", + "runaway": "{max}개 일치 후 중지됨(패턴 폭주 가능성)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "타임스탬프 변환기", + "subtitle": "Unix 초(≤10자리) 또는 밀리초(더 긴 정수), ISO-8601, 혹은 브라우저가 파싱할 수 있는 문자열을 변환합니다. 모두 로컬에서 실행됩니다.", + "inputLabel": "입력", + "placeholder": "1744200000 또는 2025-04-09T12:00:00Z", + "now": "지금", + "copy": "복사", + "hint": "값을 붙여넣거나 지금을 클릭하세요.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "상대", + "human": "사람이 읽기 쉬움" + }, + "fields": { + "seconds": "초", + "milliseconds": "밀리초", + "utc": "UTC", + "local": "로컬", + "distance": "거리" + }, + "errors": { + "empty": "Unix 타임스탬프, ISO 문자열 또는 날짜 텍스트를 입력하세요.", + "invalidNumber": "숫자 타임스탬프가 올바르지 않습니다.", + "outOfRange": "JavaScript Date 범위를 벗어났습니다.", + "couldNotParse": "Unix/ISO-8601/브라우저 날짜 문자열로 파싱할 수 없습니다." + } + }, + "HttpStatusCodes": { + "title": "HTTP 상태 코드", + "subtitle": "HTTP 응답 상태 코드(1xx5xx)를 검색/탐색하세요. 짧은 설명과 RFC 참조를 제공합니다. 로컬에서 실행됩니다.", + "header": { + "title": "HTTP 상태 코드", + "description": "설명과 RFC 참조가 포함된 HTTP 응답 코드 검색형 레퍼런스입니다." + }, + "searchPlaceholder": "검색: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "클래스 필터…", + "classFilterAll": "전체 클래스", + "resultsCount": "{total}개 중 {count}개", + "table": { + "code": "Code", + "phrase": "문구", + "description": "설명", + "copy": "복사" + }, + "noResults": "“{query}”에 대한 결과가 없습니다.", + "specPrefix": "규격:", + "copied": "상태 코드를 복사했어요" + }, + "CronBuilder": { + "title": "Cron 표현식 빌더", + "subtitle": "표준 5필드 cron: minute hour day month weekday. 파서는 내부적으로 seconds=0을 사용합니다. 표현식 편집기에서는 선택적 6필드(sec min hour dom mon dow)도 지원합니다.", + "tabs": { + "builder": "비주얼 빌더", + "expression": "표현식" + }, + "presetsLabel": "프리셋", + "fieldsLabel": "필드", + "rawExpressionLabel": "원본 표현식", + "expressionHint": "포커스를 벗어나면 파싱하여 빌더와 동기화합니다. {count} {count, plural, one {토큰} other {토큰}}.", + "currentExpressionLabel": "현재 표현식", + "copy": "복사", + "quickPick": "빠른 선택", + "emptyExpression": "—", + "plainLanguageLabel": "자연어", + "nextRunsLabel": "다음 실행(로컬 시간)", + "fields": { + "minute": "분", + "hour": "시", + "dayOfMonth": "일", + "month": "월", + "dayOfWeek": "요일" + }, + "presets": { + "everyMinute": "매분", + "every5Minutes": "5분마다", + "every15Minutes": "15분마다", + "everyHour": "매시간(:00)", + "every6Hours": "6시간마다", + "dailyMidnight": "매일 자정", + "daily0900": "매일 09:00", + "weekdays0900": "평일 09:00", + "weeklySunday": "매주(일 00:00)", + "monthlyFirst": "매월(1일 00:00)", + "yearlyJan1": "매년(1월 1일 00:00)" + }, + "picks": { + "minute": { + "every": "매분" + }, + "hour": { + "every": "매시간" + }, + "dom": { + "any": "임의의 날", + "last": "마지막 날" + }, + "month": { + "every": "매월" + }, + "dow": { + "any": "임의의 요일", + "weekdays": "평일", + "weekend": "주말" + } + }, + "months": { + "jan": "1월", + "feb": "2월", + "mar": "3월", + "apr": "4월", + "may": "5월", + "jun": "6월", + "jul": "7월", + "aug": "8월", + "sep": "9월", + "oct": "10월", + "nov": "11월", + "dec": "12월" + }, + "weekdays": { + "sun": "일", + "mon": "월", + "tue": "화", + "wed": "수", + "thu": "목", + "fri": "금", + "sat": "토" + }, + "errors": { + "fieldCount": "5개 필드(minute hour day month weekday) 또는 초를 포함한 6개 필드를 기대합니다.", + "invalid": "잘못된 cron 표현식입니다." + } + }, + "Base64": { + "title": "Base64 인코더 / 디코더", + "subtitle": "텍스트를 Base64로 인코딩하거나 Base64 문자열을 디코딩합니다", + "upload": "업로드", + "clear": "지우기", + "modes": { + "encode": "인코딩", + "decode": "디코딩" + }, + "swapTitle": "입력/출력 바꾸기", + "panels": { + "plainText": "일반 텍스트", + "base64Input": "Base64 입력", + "base64Output": "Base64 출력", + "decodedText": "디코딩된 텍스트" + }, + "placeholders": { + "encode": "Base64로 인코딩할 텍스트를 입력…", + "decode": "디코딩할 Base64 문자열을 붙여넣기…" + }, + "dropHere": "여기에 파일을 놓으세요", + "charCount": "{count}자", + "download": { + "title": "출력 다운로드", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "클립보드로 복사", + "outputPlaceholder": "출력이 여기에 표시됩니다…", + "errors": { + "invalidBase64": "유효하지 않은 Base64 문자열입니다. 입력을 확인하세요.", + "encodeFailed": "인코딩에 실패했습니다." + } + }, + "UrlEncode": { + "title": "URL 인코더 / 디코더", + "subtitle": "encodeURIComponent / decodeURIComponent(UTF‑8)로 퍼센트 인코딩/디코딩", + "upload": "업로드", + "clear": "지우기", + "modes": { + "encode": "인코딩", + "decode": "디코딩" + }, + "swapTitle": "입력/출력 바꾸기", + "panels": { + "plainText": "일반 텍스트", + "encodedInput": "인코딩 입력", + "encodedOutput": "인코딩 출력", + "decodedText": "디코딩된 텍스트" + }, + "placeholders": { + "encode": "퍼센트 인코딩할 텍스트를 입력…", + "decode": "디코딩할 percent-encoded 텍스트를 붙여넣기…" + }, + "dropHere": "여기에 파일을 놓으세요", + "charCount": "{count}자", + "download": { + "title": "출력 다운로드", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "클립보드로 복사", + "outputPlaceholder": "출력이 여기에 표시됩니다…", + "errors": { + "invalidEncoding": "잘못된 퍼센트 인코딩입니다. 불완전한 % 쌍이나 잘못된 이스케이프를 확인하세요.", + "encodeFailed": "인코딩할 수 없습니다(입력에 잘못된 Unicode)." + } + }, + "UrlParser": { + "title": "URL 파서", + "subtitle": "URL을 프로토콜, 호스트, 경로, 쿼리 파라미터, 해시(프래그먼트)로 분해합니다.", + "upload": "업로드", + "clear": "지우기", + "panels": { + "input": "URL 입력", + "output": "파싱 결과" + }, + "placeholders": { + "input": "URL을 붙여넣기(예: https://example.com/path?foo=bar#section) 또는 호스트/경로(예: example.com/path)…" + }, + "dropHere": "여기에 파일을 놓으세요", + "charCount": "{count}자", + "outputPlaceholder": "분해된 구성요소가 여기에 표시됩니다…", + "fields": { + "protocol": "프로토콜", + "host": "호스트", + "path": "경로", + "hash": "해시", + "queryParams": "쿼리 파라미터" + }, + "paramCount": "{count}개", + "noParams": "쿼리 파라미터가 없습니다.", + "paramKey": "키", + "paramValue": "값", + "copyTitle": "클립보드로 복사", + "copyParamTitle": "key=value로 복사", + "download": { + "title": "JSON 다운로드", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "잘못된 URL입니다. 호스트명(example.com)을 포함하고 문자를 확인하세요." + } + }, + "UuidGenerator": { + "title": "UUID / ULID 생성기", + "subtitle": "브라우저에서 RFC 9562 UUID 또는 ULID를 생성합니다. 한 번에 최대 {max}개까지 일괄 내보내기.", + "format": "형식", + "kindHints": { + "ulid": "정렬 가능하고 URL 안전한 26자 식별자", + "uuid4": "무작위 (RFC 9562)", + "uuid7": "시간 순서(Unix ms + 무작위)", + "uuid1": "MAC/시간 기반 (RFC 9562)", + "uuid6": "재정렬된 그레고리력 시간 기반", + "uuid3": "이름 + 네임스페이스의 MD5 해시", + "uuid5": "이름 + 네임스페이스의 SHA‑1 해시", + "nil": "닐 UUID(모두 0, RFC 9562)" + }, + "countLabel": "개수 (1–{max})", + "nameLabel": "이름(입력 문자열)", + "namePlaceholder": "예: my.app/resource", + "bulkHint": "일괄 실행 시 #0, #1, … 을 덧붙여 각 ID를 고유하게 만듭니다(v3/v5는 결정적).", + "namespace": "네임스페이스", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "사용자 지정 UUID" + }, + "customNamespaceLabel": "네임스페이스 UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "생성", + "output": "출력", + "lines": "{count}줄", + "download": { + "title": ".txt 다운로드", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "모두 복사", + "uppercaseToggle": "대문자로 출력", + "hyphensToggle": "하이픈 표시", + "copyJsonTitle": "JSON 배열로 복사", + "outputPlaceholder": "\"생성\"을 클릭…", + "errors": { + "invalidCustomNamespace": "사용자 지정 네임스페이스는 유효한 UUID여야 합니다.", + "missingName": "UUID v3 및 v5에는 이름이 필요합니다.", + "unknown": "ID를 생성할 수 없습니다." + } + }, + "SecretApiKeyGenerator": { + "title": "비밀 / API 키 생성기", + "subtitle": "길이와 문자 집합을 지정해 암호학적 난수 문자열을 생성합니다. 실행당 최대 {max}개까지 대량보내기. UUID / ULID 생성기와 함께 쓰기 좋습니다.", + "alphabetLabel": "문자 집합", + "alphabetHint": "중복 문자는 제거됩니다. crypto.getRandomValues와 기각 샘플링으로 각 문자를 균일하게 선택합니다(모듈로 편향 없음).", + "alphabetCount": "고유 문자 {count}개", + "presetsLabel": "프리셋", + "preset": { + "base64url": "Base64url", + "alphanumeric": "영숫자", + "hex": "16진수(소문자)", + "asciiPrintable": "ASCII 인쇄 가능" + }, + "lengthLabel": "비밀당 길이(1–{maxLen})", + "countLabel": "개수(1–{maxBulk})", + "generate": "생성", + "output": "출력", + "lines": "{count}줄", + "download": { + "title": ".txt 다운로드", + "filename": "secrets.txt" + }, + "copyTitle": "모두 복사", + "outputPlaceholder": "\"생성\"을 클릭하세요…", + "entropy": "엔트로피 {bits}비트", + "strength": { + "weak": "약함", + "fair": "보통", + "strong": "강함" + }, + "excludeAmbiguous": "모호한 문자 제외", + "excludeAmbiguousHint": "{chars}를 제거합니다 — 잘못 읽기 쉽거나 셸에 부적합한 문자입니다.", + "prefixLabel": "키 접두사 (선택 사항)", + "errors": { + "emptyAlphabet": "문자 집합에 문자를 하나 이상 추가하세요.", + "alphabetTooLong": "중복 제거 후 문자 집합은 최대 {maxAlpha}자까지 가능합니다.", + "lengthOutOfRange": "길이는 1~{maxLen} 사이여야 합니다.", + "countOutOfRange": "개수는 1~{maxBulk} 사이여야 합니다.", + "unknown": "비밀을 생성할 수 없습니다." + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum 생성기", + "subtitle": "레이아웃과 목업을 위한 라틴어 플레이스홀더 텍스트를 생성합니다.", + "paragraphs": "단락", + "sentences": "문장", + "words": "단어", + "count": "개수", + "generate": "생성", + "copy": "복사", + "startWith": "'Lorem ipsum...'으로 시작", + "outputPlaceholder": "출력이 여기에 표시됩니다...", + "outputType": "출력 유형", + "bulletList": "글머리 기호 목록", + "htmlMarkup": "HTML 마크업", + "htmlMarkupHint": "출력을 단락으로 감싸거나 글머리 기호 모드에서 목록 사용", + "output": "출력", + "download": "다운로드" + }, + "ColorPicker": { + "title": "색상 선택기 및 변환기", + "subtitle": "HEX, RGB, HSL을 변환하고 조화로운 팔레트를 만드세요. 모든 처리는 브라우저에서 이루어집니다.", + "nativePicker": "기본 선택기", + "chooseColor": "색상 선택", + "eyeDropper": "스포이트", + "invalidHex": "유효하지 않은 HEX 색상입니다.", + "cssValues": "CSS 값", + "palettes": "팔레트", + "copied": "복사됨", + "clickToCopy": "클릭하여 복사", + "copyAllHex": "모든 HEX 복사", + "copy": "복사", + "copyColor": "{color} 복사", + "paletteShades": "음영", + "paletteComplementary": "보색", + "paletteTriadic": "삼원색", + "paletteAnalogous": "유사색", + "paletteSplit": "분할 보색", + "paletteSquare": "사각형" + }, + "ContrastChecker": { + "title": "대비 검사기", + "subtitle": "전경과 배경 사이의 WCAG 2.1 대비 비율을 계산합니다. 일반 텍스트와 큰 텍스트에 대한 AA·AAA 준수 여부를 표시합니다. 모두 브라우저에서 처리됩니다.", + "foreground": "텍스트 / 전경", + "background": "배경", + "hexPlaceholder": "#RRGGBB", + "swap": "색 바꾸기", + "chooseForeground": "전경색 선택", + "chooseBackground": "배경색 선택", + "eyeDropper": "스포이드", + "ratio": "대비 비율", + "copyRatio": "비율 복사", + "copied": "복사됨", + "previewHeading": "미리보기", + "previewBody": "다람쥐 헌 쳇바퀴에 타고파. 키스의 고유조건은 입술끼리 만나야 하고 특별한 기술은 필요치 않다.", + "previewCaption": "기본 크기의 예시 텍스트.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — 일반 텍스트(4.5:1)", + "levelAALarge": "AA — 큰 텍스트(3:1)", + "levelAAANormal": "AAA — 일반 텍스트(7:1)", + "levelAAALarge": "AAA — 큰 텍스트(4.5:1)", + "pass": "통과", + "fail": "미통과", + "invalidHex": "잘못된 HEX 색입니다.", + "largeTextHint": "큰 텍스트: 일반 18pt 이상 또는 굵게 14pt 이상(WCAG 정의)." + }, + "DiffChecker": { + "title": "차이 확인기", + "subtitle": "두 텍스트를 나란히 행 단위로 비교합니다. 모든 처리는 브라우저에서 이루어집니다.", + "rowsCount": "{count} 행", + "truncatedWarning": "처음 {max} 행을 표시합니다.", + "limitExceeded": "각 측면은 {max} 문자로 제한됩니다.", + "clearBoth": "모두 지우기", + "original": "원본", + "modified": "수정됨", + "originalPlaceholder": "원본 텍스트를 붙여넣으세요…", + "modifiedPlaceholder": "수정된 텍스트를 붙여넣으세요…", + "comparisonLabel": "나란히 비교", + "emptyState": "두 패널 모두에 텍스트를 입력하여 비교하세요." + }, + "JsonSchemaGenerator": { + "title": "JSON 스키마 생성기", + "subtitle": "유효한 JSON을 붙여넣어 JSON 스키마 또는 타입 모델을 생성합니다. 모든 처리는 브라우저에서 이루어집니다.", + "languageLabel": "출력 언어", + "inputLabel": "JSON 입력", + "inputPlaceholder": "JSON을 여기에 붙여넣기…", + "outputLabel": "생성된 출력", + "outputPlaceholder": "생성된 스키마나 타입이 여기에 표시됩니다…", + "parseError": "잘못된 JSON", + "clear": "지우기", + "copy": "복사", + "copied": "클립보드에 복사됨", + "copyFailed": "복사하지 못했습니다", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON 스키마(Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "편집기 로드 중…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "이미지를 Base64로", + "subtitle": "이미지를 삭제하고 데이터 URI 또는 ​​원시 Base64 문자열을 가져옵니다. 모든 것이 브라우저에 유지됩니다.", + "upload": "이미지 업로드", + "clear": "분명한", + "modes": { + "dataUri": "데이터 URI", + "rawString": "원시 베이스64" + }, + "panels": { + "imageInput": "이미지 입력", + "output": "Base64 출력" + }, + "dropHere": "여기에 이미지를 드롭하세요.", + "charCount": "{count}자", + "placeholders": { + "uploadImage": "이미지를 드래그 앤 드롭하거나 업로드를 클릭하여 시작하세요." + }, + "fileTooLarge": "파일이 너무 큽니다. 처리하면 브라우저가 정지될 수 있습니다.", + "copyTitle": "클립보드에 복사", + "download": { + "title": "텍스트 다운로드", + "filename": "이미지-base64.txt" + } + }, + "ImageCompressor": { + "title": "이미지 압축", + "subtitle": "JPEG·PNG·WebP 출력을 품질 조절로 로컬에서 줄입니다. 업로드되지 않습니다.", + "upload": "이미지 업로드", + "clear": "지우기", + "dropHere": "이미지를 여기에 놓으세요", + "placeholders": { + "uploadImage": "이미지를 끌어다 놓거나 클릭하여 파일을 선택하세요." + }, + "fileTooLarge": "파일이 너무 큽니다(최대 {maxMb}MB).", + "unsupportedFormat": "지원하지 않는 형식입니다. JPEG, PNG, WebP, GIF, AVIF 또는 BMP를 사용하세요.", + "compressFailed": "이 이미지를 압축할 수 없습니다. 다른 파일이나 형식을 시도하세요.", + "panels": { + "original": "원본", + "compressed": "압축 미리보기" + }, + "outputFormat": "출력 형식", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "품질", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG는 브라우저에서 무손실입니다. 품질 슬라이더는 JPEG와 WebP에만 적용됩니다.", + "sizes": { + "original": "원본: {size}", + "compressed": "압축: {size}", + "saved": "{percent}% 감소" + }, + "largerWarning": "출력이 입력보다 큽니다. 형식이나 품질을 바꿔 보세요.", + "download": "다운로드", + "downloadFilename": "compressed-image", + "processing": "압축 중…", + "emptyCompressed": "압축 미리보기가 여기에 표시됩니다." + }, + "CssGradientBuilder": { + "title": "CSS 그라디언트 빌더", + "subtitle": "각도 제어, 색상 중지 및 원클릭 CSS 복사 기능을 갖춘 시각적 CSS 그라데이션 빌더입니다.", + "preview": "그라데이션 미리보기", + "settings": "설정", + "type": "유형", + "linear": "선의", + "radial": "방사형", + "angle": "각도", + "stops": "컬러 정지", + "color": "색상", + "position": "위치 (%)", + "addStop": "경유지 추가", + "deleteStop": "정지 삭제", + "output": "CSS 출력", + "copy": "CSS 복사", + "copied": "복사되었습니다!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore 생성기", + "subtitle": "특정 기술 스택 또는 조합에 대해 병합된 .gitignore 파일을 즉시 생성합니다.", + "searchPlaceholder": "검색 스택(예: Node, Python, macOS)...", + "noResults": "스택을 찾을 수 없습니다.", + "selectedStacks": "선택된 스택", + "output": "미리보기 출력", + "copy": "클립보드에 복사", + "copied": "복사되었습니다!", + "download": ".gitignore 다운로드", + "downloading": "다운로드 중...", + "errorFetching": "상위 목록을 가져오지 못했습니다.", + "errorGenerating": "선택한 스택에서 .gitignore를 생성하지 못했습니다.", + "generating": "생성 중...", + "emptyState": ".gitignore를 생성하려면 하나 이상의 기술 스택을 선택하세요." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV 또는 Excel을 업로드하고 JSON을 편집한 뒤 CSV 또는 XLSX로 다운로드합니다. 표 형식 데이터만 지원합니다. Excel 날짜 셀은 JSON에서 ISO 타임스탬프로 변환됩니다.", + "dropTitle": "CSV 또는 Excel 파일을 놓으세요", + "dropHint": ".xlsx / .xls는 첫 번째 시트를 사용합니다. CSV에는 헤더 행이 있어야 합니다.", + "chooseFile": "파일 선택", + "jsonLabel": "JSON(객체 배열)", + "jsonPlaceholder": "객체의 JSON 배열을 붙여넣거나 CSV / Excel을 가져오기…", + "loadSample": "샘플 불러오기", + "actions": { + "format": "JSON 정렬", + "clear": "지우기", + "copy": "복사", + "copied": "복사됨", + "downloadCsv": "CSV 다운로드", + "downloadXlsx": "Excel 다운로드" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "데이터", + "toastLoadedCsv": "CSV를 편집기에 불러왔습니다", + "toastLoadedExcel": "Excel 파일을 편집기에 불러왔습니다", + "toastFormatted": "JSON이 정렬되었습니다", + "toastExportedCsv": "CSV를 다운로드했습니다", + "toastExportedXlsx": "Excel을 다운로드했습니다", + "toastCopied": "클립보드에 복사했습니다", + "footnote": "모든 처리는 브라우저에서 이루어집니다. JSON은 비어 있지 않은 객체 배열이어야 합니다. 중첩 값은 CSV 셀에 JSON 문자열로 직렬화됩니다.", + "errors": { + "unsupportedFile": ".csv, .xlsx 또는 .xls 파일을 사용하세요.", + "parseFailed": "이 파일을 분석할 수 없습니다.", + "invalidJson": "잘못된 JSON", + "exportFailed": "보내기 실패", + "copyFailed": "복사할 수 없습니다" + } + }, + "SnippetManager": { + "title": "코드 스니펫 관리자", + "subtitle": "브라우저에 스니펫을 저장하거나 로그인해 계정과 동기화하세요. 보기와 편집 전환, 한 번의 클릭으로 복사, 언어 선택 또는 자동 감지, JSON·SQL·Monaco 지원 코드 포맷을 사용할 수 있습니다.", + "snippets": "스니펫", + "newSnippet": "새 스니펫", + "newSnippetTitle": "새 스니펫", + "defaultSnippetTitle": "환영합니다", + "defaultSnippetCode": "// 코드 스니펫에 오신 것을 환영합니다\n// 보기/편집, 복사, 서식 및 자동 언어를 사용해 보세요\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "스니펫 검색…", + "searchAria": "스니펫 검색", + "emptySearch": "검색과 일치하는 스니펫이 없습니다.", + "snippetTitle": "제목", + "snippetTitlePlaceholder": "스니펫 이름", + "language": "언어", + "detectedHint": "강조: {lang}", + "badgeAuto": "자동 · {lang}", + "modeView": "보기", + "modeEdit": "편집", + "copy": "복사", + "copied": "복사됨", + "format": "서식", + "cancel": "취소", + "deleteTitle": "스니펫을 삭제할까요?", + "deleteDescription": "‘{title}’이(가) 이 브라우저에서 제거됩니다.", + "deleteConfirm": "삭제", + "editorAria": "스니펫 코드 편집기", + "editorLoading": "편집기 로드 중…", + "toastCopied": "클립보드에 복사됨", + "toastCopyFailed": "복사할 수 없습니다", + "toastNothingToCopy": "복사할 내용이 없습니다", + "toastFormatted": "서식이 적용되었습니다", + "toastFormatFailed": "서식을 적용할 수 없습니다", + "toastFormatJsonInvalid": "잘못된 JSON — 먼저 구문을 수정하세요", + "toastFormatUnsupported": "이 언어용 서식 도구가 없습니다 — JSON 또는 SQL 또는 편집 모드를 사용해 보세요", + "toastDeleted": "스니펫이 삭제되었습니다", + "toastLoadFailed": "서버에서 스니펫을 불러오지 못했습니다.", + "toastSyncFailed": "서버와 동기화하지 못했습니다.", + "languages": { + "auto": "자동 감지", + "plaintext": "일반 텍스트", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL 클라이언트", + "dialogTitle": "SQL 연결", + "mobileSheetTitle": "SQL 탐색기", + "newTabLabel": "새 쿼리 탭", + "vaultLockedTitle": "금고 잠김", + "vaultLockedDesc": "저장된 연결에 접근하려면 금고를 잠금 해제하세요.", + "heroTitle": "SQL 클라이언트", + "heroDesc": "PostgreSQL, MySQL 또는 MariaDB에 연결합니다. 자격 증명은 브라우저에서 암호화됩니다.", + "heroAddConnection": "연결 추가", + "selectTableTitle": "테이블 선택 또는 쿼리 탭 열기", + "selectTableDesc": "사이드바에서 테이블을 클릭하거나 ⌘↩을 사용하세요.", + "toastLoadFail": "연결 로드에 실패했습니다." + }, + "connection": { + "titleNew": "새 SQL 연결", + "titleEdit": "연결 편집", + "labelDbType": "데이터베이스 유형", + "labelName": "연결 이름", + "placeholderName": "내 프로덕션 DB", + "labelHostPort": "호스트 및 포트", + "labelDatabase": "데이터베이스", + "placeholderDatabase": "mydb", + "labelUsername": "사용자 이름", + "labelPassword": "비밀번호", + "labelSsl": "SSL / TLS 활성화", + "sslDesc": "서버 연결 암호화", + "securityNote": "자격 증명은 저장 전 브라우저에서 AES-256 암호화됩니다", + "btnTest": "연결 테스트", + "btnSave": "저장 및 연결", + "btnUpdate": "업데이트", + "btnCancelEdit": "편집 취소", + "savedConnections": "저장된 연결", + "emptySaved": "저장된 연결이 여기에 표시됩니다", + "confirmDelete": "이 연결을 삭제하시겠습니까?", + "toastFillFields": "테스트 전에 호스트와 데이터베이스를 입력하세요.", + "toastConnectOk": "연결됨! {version}", + "toastConnectFail": "연결 실패: {message}", + "toastUnlockVault": "먼저 금고를 잠금 해제하세요.", + "toastFieldsRequired": "호스트와 데이터베이스는 필수입니다.", + "toastUpdated": "연결이 업데이트되었습니다.", + "toastSaved": "연결이 저장되었습니다.", + "toastSaveFail": "저장 실패: {message}", + "toastDeleted": "연결이 삭제되었습니다.", + "toastDeleteFail": "연결 삭제에 실패했습니다." + }, + "sidebar": { + "title": "SQL 탐색기", + "tooltipAddConnection": "연결 추가", + "tooltipRefreshSchema": "스키마 새로고침", + "noConnections": "저장된 연결 없음", + "toastSchemaFail": "스키마 로드 실패: {message}" + }, + "editor": { + "btnRun": "실행", + "executing": "쿼리 실행 중…", + "emptyTitle": "쿼리를 실행하여 결과 보기", + "emptyHint": "⌘↩를 누르거나 실행 클릭", + "toastNoConnection": "활성 연결 없음." + }, + "results": { + "filterPlaceholder": "결과 필터…", + "rowCount": "{count}행", + "rowCountFiltered": "{filtered} / {total}행", + "exportCsv": "CSV", + "noRows": "반환된 행 없음" + } + }, + "QrCodeGenerator": { + "title": "QR 코드 생성기", + "subtitle": "텍스트나 URL을 PNG QR 코드로 바꿉니다. 렌더링은 모두 브라우저에서 처리됩니다.", + "contentLabel": "내용", + "contentPlaceholder": "https://example.com, 일반 텍스트, Wi‑Fi 문자열, vCard…", + "styleLabel": "스타일", + "dotTypeLabel": "점 모양", + "dotTypeSquare": "사각형", + "dotTypeDots": "점", + "dotTypeRounded": "둥근", + "dotTypeExtraRounded": "매우 둥근", + "dotTypeClassy": "클래시", + "dotTypeClassyRounded": "클래시 둥근", + "cornerSquareLabel": "모서리 사각형", + "cornerSquareDot": "점", + "cornerSquareSquare": "사각형", + "cornerSquareExtraRounded": "매우 둥근", + "cornerDotLabel": "모서리 점", + "cornerDotDot": "점", + "cornerDotSquare": "사각형", + "eccLabel": "오류 정정", + "eccL": "낮음 (~7%)", + "eccM": "중간 (~15%)", + "eccQ": "사분위 (~25%)", + "eccH": "높음 (~30%)", + "marginLabel": "여백(모듈)", + "widthLabel": "이미지 너비", + "widthEffective": "{px}px로 렌더링", + "darkColor": "어두운 모듈", + "lightColor": "밝은 모듈", + "resetColors": "색 초기화", + "logoLabel": "중앙 로고", + "logoMarginLabel": "여백", + "uploadLogo": "이미지 업로드", + "removeLogo": "로고 제거", + "downloadPng": "PNG 다운로드", + "downloadSvg": "SVG 다운로드", + "copyImage": "이미지 복사", + "copied": "복사됨", + "clear": "지우기", + "previewAlt": "QR 코드 미리보기", + "emptyHint": "내용을 입력하거나 붙여넣어 미리보기를 표시합니다.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "QR 코드를 만들 수 없습니다. 더 짧은 텍스트나 낮은 오류 정정을 시도하세요.", + "copyUnsupported": "브라우저가 이미지 클립보드 복사를 지원하지 않습니다.", + "copyFailed": "클립보드에 복사하지 못했습니다." + } + }, + "IpSubnetCalculator": { + "title": "IP / 서브넷 계산기", + "subtitle": "브라우저에서 IPv4·IPv6 CIDR 정보—외부 요청 없음.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "네트워크", + "placeholderIpv4": "10.0.0.0/24 또는 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 또는 2001:db8::1/64", + "hintIpv4": "CIDR(예 /24) 또는 IPv4 + 점 표기 서브넷 마스크.", + "hintIpv6": "접두사 길이(예 /64)를 포함하세요. 생략 시 /128.", + "copyValue": "값 복사", + "rows": { + "cidr": "CIDR", + "network": "네트워크 주소", + "netmask": "서브넷 마스크", + "wildcard": "와일드카드 마스크", + "broadcast": "브로드캐스트", + "firstHost": "첫 호스트", + "lastHost": "마지막 호스트", + "totalAddresses": "총 주소 수", + "usableHosts": "사용 가능 호스트", + "prefixLength": "접두사 길이(비트)", + "networkIpv6": "네트워크 주소", + "lastIpv6": "범위의 마지막 주소", + "totalAddressesIpv6": "서브넷 주소 수" + }, + "usableNote": { + "pointToPoint": "/31 포인트투포인트는 두 주소를 모두 호스트로 사용(RFC 3021).", + "singleHost": "/32는 단일 호스트." + }, + "ipv6RangeHint": { + "normal": "수학적 전체 범위를 표시합니다. 라우터 규칙은 다를 수 있습니다.", + "single": "/128은 단일 주소.", + "full": "/0은 전체 IPv6." + }, + "errors": { + "invalidIpv4": "IPv4 네트워크가 잘못되었습니다. CIDR 또는 주소+마스크.", + "invalidIpv6": "IPv6 네트워크가 잘못되었습니다. 예: 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "해시 생성기", + "subtitle": "텍스트나 파일용 MD5·SHA-1·SHA-2 다이제스트, 비밀번호용 bcrypt(텍스트 입력). 모두 브라우저에서 로컬로 계산됩니다.", + "algorithmLabel": "알고리즘", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "텍스트", + "modeFile": "파일", + "inputLabelText": "입력", + "placeholderText": "텍스트를 입력하거나 붙여넣기…", + "chooseFile": "파일 선택", + "clearFile": "지우기", + "fileHint": "파일은 메모리에서만 읽으며 최대 {maxMb} MB입니다. 업로드하지 않습니다.", + "outputLabel": "다이제스트(hex)", + "copyHash": "다이제스트 복사", + "copied": "복사됨", + "byteCount": "{count, plural, other {#바이트}}", + "charCount": "{count, plural, other {#자}}", + "emptyHint": "텍스트를 입력하거나 파일을 선택하세요.", + "securityNote": "새 보안 용도에는 MD5와 SHA-1은 권장하지 않습니다. SHA-256 또는 SHA-512를 사용하세요.", + "errors": { + "fileTooLarge": "파일이 {maxMb} MB보다 큽니다.", + "fileReadFailed": "파일을 읽을 수 없습니다." + }, + "bcryptRoundsLabel": "비용(라운드)", + "bcryptRoundsHint": "값이 클수록 느리지만 무차별 대입에 더 강합니다. 일반적으로 기본값은 10입니다.", + "fileTabDisabledTitle": "bcrypt는 텍스트 비밀번호만 해시하며 임의의 파일은 처리하지 않습니다.", + "placeholderBcrypt": "해시할 비밀번호를 입력하거나 붙여넣기…", + "outputLabelBcrypt": "bcrypt 해시", + "copyBcrypt": "해시 복사", + "emptyHintBcrypt": "bcrypt 해시를 계산하려면 비밀번호를 입력하세요.", + "securityNoteBcrypt": "bcrypt 출력에는 매번 무작위 솔트가 포함됩니다. 같은 비밀번호라도 매번 다른 문자열이 나옵니다. 운영 환경에서는 높은 비용을 사용하세요.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "암호화 연습장", + "subtitle": "브라우저의 AES-GCM: 암호 또는 원시 키로 JSON 번들에 암호화한 뒤 로컬에서 복호화합니다. 교육용이며 가끔 실용적 — 업로드 없음.", + "tabEncrypt": "암호화", + "tabDecrypt": "복호화", + "keyModeLabel": "키 자료", + "modePassphrase": "암호문구 (PBKDF2 + AES-GCM)", + "modeRawKey": "원시 AES 키 (hex 또는 Base64)", + "aesBitsLabel": "AES 키 길이", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "원시 키 인코딩", + "encodingAuto": "자동 (hex처럼 보이면 hex, 아니면 Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "원시 키", + "rawKeyPlaceholder": "32바이트 키는 64자 hex 또는 AES-256용 Base64 (길이는 위 AES 크기에 따름).", + "rawKeyPlaceholderDecrypt": "번들에 keyDerivation \"none\"일 때만 필요합니다.", + "passphraseLabel": "암호문구", + "passphrasePlaceholder": "암호문구는 키 유도를 위해 브라우저에서만 사용됩니다.", + "passphrasePlaceholderDecrypt": "번들이 PBKDF2-SHA256일 때만 필요합니다.", + "pbkdf2IterLabel": "PBKDF2 반복", + "pbkdf2Hint": "10,000~2,000,000으로 제한. 기본값은 일반적인 금고 설정과 유사합니다.", + "plaintextLabel": "평문", + "plaintextPlaceholder": "암호화할 메시지를 입력하거나 붙여넣기…", + "bundleInputLabel": "암호화된 번들 (JSON)", + "bundlePlaceholder": "암호화에서 나온 JSON 붙여넣기 (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "암호화된 번들", + "plaintextOutputLabel": "평문", + "encryptButton": "암호화", + "decryptButton": "복호화", + "copyBundle": "JSON 복사", + "copyPlaintext": "평문 복사", + "copied": "복사됨", + "emptyBundleHint": "암호화 후 암호화된 JSON이 여기에 표시됩니다.", + "emptyDecryptHint": "복호화된 텍스트가 여기에 표시됩니다.", + "decryptKeyHint": "keyDerivation이 \"none\"이면 원시 키, PBKDF2-SHA256이면 암호문구를 사용하세요. 번들에 솔트, 반복, IV, AES 길이가 저장됩니다.", + "securityNote": "키와 평문은 브라우저를 벗어나지 않습니다. 프로덕션 비밀은 전용 도구를 사용하세요.", + "errors": { + "emptyKey": "AES 크기에 맞는 원시 키를 입력하세요.", + "emptyPassphrase": "암호문구를 입력하세요.", + "badKeyMaterial": "키를 hex 또는 Base64로 읽을 수 없습니다.", + "wrongKeyLength": "키 길이가 선택한 AES(16 / 24 / 32바이트)와 맞지 않습니다.", + "invalidJson": "올바른 JSON이 아닙니다.", + "invalidBundle": "지원되는 암호 번들이 아닙니다.", + "decryptFailed": "복호화 실패 — 잘못된 비밀, 손상된 번들 또는 매개변수 불일치.", + "encryptFailed": "암호화 실패. Web Crypto와 입력을 확인하세요." + } + }, + "HmacGenerator": { + "title": "HMAC 생성기", + "subtitle": "웹훅 서명과 API 연동 테스트용. HMAC-SHA1/256/384/512를 hex 또는 Base64로—브라우저에서 로컬 계산.", + "secretLabel": "비밀 키", + "messageLabel": "메시지 / 페이로드", + "placeholderSecret": "서명 비밀값을 붙여넣기…", + "placeholderMessage": "검증할 원시 본문 또는 문자열을 붙여넣기(대개 웹훅 본문 바이트와 동일).", + "algorithmLabel": "HMAC 알고리즘", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "출력 형식", + "formatHex": "Hex(소문자)", + "formatBase64": "Base64", + "outputLabel": "서명", + "copyOutput": "서명 복사", + "copied": "복사됨", + "byteCountHint": "{count, plural, other {#바이트}} (UTF-8)", + "emptyHint": "HMAC을 계산하려면 비밀 키를 입력하세요.", + "securityNote": "서명 비밀값은 절대 공유하지 마세요. 이 페이지는 데이터를 업로드하지 않으며 모두 브라우저에서 실행됩니다.", + "errors": { + "cryptoFailed": "HMAC을 계산할 수 없습니다. Web Crypto 지원을 확인하고 다시 시도하세요.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA 코드 생성기", + "subtitle": "Base32 비밀키를 붙여넣어 현재 6자리 RFC 6238 코드(SHA-1, 30초)를 확인합니다. 브라우저에서만 처리—로그인 또는 MFA 테스트에 적합합니다.", + "secretLabel": "비밀키(Base32)", + "placeholderSecret": "비밀키를 붙여넣기(A–Z와 2–7 숫자, 인증 앱 설정 시와 동일)…", + "codeLabel": "현재 코드", + "copyCode": "코드 복사", + "copied": "복사됨", + "nextIn": "{seconds}초 후 갱신", + "standardHint": "30초 간격, 6자리, HMAC-SHA-1(Google Authenticator 호환).", + "digitsLabel": "자릿수", + "periodLabel": "주기", + "algorithmLabel": "알고리즘", + "periodSeconds": "{seconds}초", + "emptyHint": "6자리 코드를 만들려면 비밀키를 붙여넣으세요.", + "invalidHint": "코드를 보려면 비밀키를 수정하세요.", + "securityNote": "비밀번호처럼 다루세요. 비밀키는 브라우저를 벗어나지 않으며 업로드되지 않습니다.", + "importLabel": "otpauth:// URI에서 가져오기", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "가져오기", + "qrSectionTitle": "프로비저닝 QR 코드", + "qrHint": "인증 앱(Google Authenticator, 1Password, Aegis 등)으로 스캔하여 이 비밀 키를 추가하세요. QR 코드는 비밀 키를 인코딩합니다 — 비밀번호처럼 취급하세요.", + "accountLabel": "계정", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "발급자(선택 사항)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "QR 코드를 생성하려면 유효한 비밀 키를 입력하세요.", + "errors": { + "invalidSecret": "유효한 Base32 비밀키가 아닙니다.", + "cryptoFailed": "TOTP를 계산할 수 없습니다. Web Crypto를 확인하고 다시 시도하세요.", + "invalidUri": "유효한 otpauth:// TOTP URI가 아닙니다.", + "unsupportedUri": "이 URI는 도구에서 지원하지 않는 값을 사용합니다(자릿수는 6 또는 8이어야 하며, 주기는 15, 30, 60초 중 하나여야 합니다)." + } + }, + "MarkdownPreview": { + "title": "마크다운 미리보기 & HTML", + "subtitle": "HTML 내보내기 및 HTML → Markdown 변환이 포함된 실시간 Markdown 렌더러. 모든 것이 브라우저에서 실행됩니다.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown 입력", + "htmlInputLabel": "HTML 입력", + "previewLabel": "미리보기", + "htmlOutputLabel": "HTML 소스", + "mdOutputLabel": "Markdown 출력", + "inputPlaceholder": "# 안녕하세요\\n\\n여기에 **markdown**을 입력하세요…", + "htmlInputPlaceholder": "여기에 HTML을 붙여넣으세요…", + "copyHtml": "HTML 복사", + "copyMarkdown": "Markdown 복사", + "exportHtml": "HTML 내보내기", + "copied": "복사됨", + "charCount": "{count, plural, other {# 자}}", + "emptyHint": "Markdown을 입력하면 실시간 미리보기와 HTML 출력을 볼 수 있습니다.", + "emptyHintHtmlToMd": "HTML을 붙여넣으면 Markdown으로 변환됩니다.", + "securityNote": "모든 것이 브라우저에서 로컬로 실행됩니다. 업로드되는 것이 없습니다." + }, + "NumberBaseConverter": { + "title": "진법 변환기", + "subtitle": "2~36진법 정수 변환. 선택적 0x / 0b / 0o 접두사 지원. 모두 브라우저에서 로컬 계산.", + "inputBaseLabel": "입력 진법", + "outputBaseLabel": "출력 진법", + "inputLabel": "입력 숫자", + "placeholderInput": "선택한 입력 진법으로 정수를 입력…", + "outputLabel": "변환된 값", + "copyOutput": "결과 복사", + "copied": "복사됨", + "swap": "진법 바꾸기", + "byteCountHint": "{count, plural, other {#바이트}} (UTF-8 입력 길이)", + "emptyHint": "변환할 숫자를 입력하세요.", + "note": "9보다 큰 자리는 A–Z(대소문자 무시). 공백과 밑줄은 무시됩니다.", + "errors": { + "invalidNumber": "선택한 입력 진법에 맞지 않는 값입니다." + } + }, + "FormatConverter": { + "title": "포맷 변환기", + "subtitle": "JSON, YAML, TOML, XML 간을 어떤 방향으로도 변환하세요. 12가지 조합 모두 지원. 모든 것이 브라우저에서 실행됩니다.", + "fromLabel": "원본", + "toLabel": "대상", + "swap": "교환", + "swapTitle": "포맷을 교환하고 출력을 입력으로 사용", + "copy": "복사", + "copied": "복사됨", + "charCount": "{count, plural, other {# 자}}", + "emptyHint": "왼쪽에 입력을 붙여넣거나 입력하면 변환된 출력을 확인할 수 있습니다.", + "localNote": "모든 것이 브라우저에서 로컬로 실행됩니다. 아무것도 업로드되지 않습니다." + }, + "MimeTypeLookup": { + "title": "MIME 타입 조회", + "subtitle": "파일 확장자 또는 전체 파일명으로 MIME 타입(미디어 타입)을 조회합니다. 모든 작업은 브라우저에서 로컬로 실행됩니다.", + "inputLabel": "확장자 또는 파일명", + "placeholder": "예: .png, package.json, photo.jpeg", + "hint": "팁: 확장자(점 포함/미포함) 또는 전체 파일명을 붙여넣으세요.", + "clear": "지우기", + "exampleFilename": "예시 파일명 사용", + "exampleExtension": "예시 확장자 사용", + "resultLabel": "결과", + "normalizedLabel": "정규화된 입력:", + "mimeTypeLabel": "MIME 타입", + "contentTypeLabel": "Content-Type 헤더", + "charsetLabel": "문자셋", + "copyMimeType": "MIME 타입 복사", + "copy": "복사", + "copied": "복사됨", + "emptyHint": "확장자 또는 파일명을 입력하여 MIME 타입을 조회하세요.", + "notFound": "해당 입력에 대한 MIME 타입을 찾을 수 없습니다.", + "localNote": "모든 것이 브라우저에서 로컬로 실행됩니다. 아무것도 업로드되지 않습니다." + }, + "UserAgentParser": { + "title": "User-Agent 파서", + "subtitle": "User-Agent(UA) 문자열을 붙여넣고 브라우저, OS, 엔진, 디바이스, CPU 정보를 확인하세요. 모든 작업은 브라우저에서 로컬로 실행됩니다.", + "inputLabel": "User-Agent 문자열", + "placeholder": "여기에 User-Agent를 붙여넣으세요…", + "hint": "팁: 요청 헤더, 로그, 또는 브라우저 개발자 도구에서 UA를 복사할 수 있습니다.", + "clear": "지우기", + "exampleDesktop": "데스크톱 예시 사용", + "exampleMobile": "모바일 예시 사용", + "resultLabel": "분석 결과", + "browserLabel": "브라우저", + "osLabel": "운영체제", + "engineLabel": "엔진", + "deviceTypeLabel": "디바이스 유형", + "deviceVendorLabel": "제조사", + "deviceModelLabel": "모델", + "cpuArchLabel": "CPU 아키텍처", + "copyJson": "JSON 복사", + "copy": "복사", + "copied": "복사됨", + "emptyHint": "User-Agent 문자열을 붙여넣어 분석 결과를 확인하세요.", + "localNote": "모든 것이 브라우저에서 로컬로 실행됩니다. 아무것도 업로드되지 않습니다." + }, + "MockDataGenerator": { + "title": "모의 데이터 생성기", + "subtitle": "열을 정의하고 유형을 선택한 뒤 시드, Postman 또는 테스트용 데이터를 보냅니다. 최대 {max}행, 모두 브라우저에서 처리되며 업로드되지 않습니다.", + "mockarooLink": "Mockaroo 스타일 워크플로", + "rowsLabel": "행 수", + "rowsHint": "탭 반응성을 위해 한 번에 최대 {max}행입니다.", + "formatLabel": "형식", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "SQL INSERT", + "xml": "XML" + }, + "tableNameLabel": "SQL 테이블 이름", + "presetsLabel": "프리셋", + "presetPlaceholder": "프리셋 불러오기…", + "presetDefault": "시작(사용자)", + "presetUsers": "사용자 및 역할", + "presetProducts": "이커머스 SKU", + "presetApiLog": "HTTP 요청 로그", + "generate": "생성", + "warningDuplicateNames": "열 이름이 중복되었습니다. JSON 객체에서 뒤 열이 앞 열을 덮어씁니다. 이름을 바꾸세요.", + "schemaHeading": "스키마", + "addField": "열 추가", + "removeField": "열 제거", + "duplicateField": "열 복제", + "colName": "이름", + "colType": "유형", + "colOptions": "옵션", + "colBlank": "빈 값 %", + "optMin": "최소", + "optMax": "최대", + "optDecimals": "소수", + "optDateFrom": "시작일", + "optDateTo": "종료일", + "optValues": "a, b, c", + "optStep": "간격", + "optRowBase": "기준", + "output": "출력", + "outputPlaceholder": "생성을 클릭하여 데이터를 만드세요…", + "copy": "복사", + "download": "다운로드", + "copied": "복사됨", + "groups": { + "personal": "개인", + "location": "위치", + "internetApi": "인터넷 및 API", + "numbersIds": "숫자 및 ID", + "dateTime": "날짜 및 시간", + "textDesign": "텍스트 및 디자인", + "custom": "사용자 정의" + }, + "fieldTypes": { + "first_name": "이름", + "last_name": "성", + "full_name": "전체 이름", + "email": "Email", + "username": "사용자 이름", + "password": "비밀번호", + "phone": "전화", + "company": "회사", + "job_title": "직함", + "avatar_url": "아바타 URL", + "city": "도시", + "country": "국가", + "country_code": "국가 코드(ISO)", + "street_address": "주소", + "zip_code": "우편번호", + "latitude": "위도", + "longitude": "경도", + "url": "URL", + "domain": "도메인", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "정수", + "float": "실수", + "boolean": "불리언", + "uuid": "UUID", + "credit_card": "테스트 카드", + "semver": "SemVer", + "row_index": "행 번호", + "sequence": "시퀀스", + "date": "날짜", + "datetime": "날짜/시간", + "unix_timestamp": "Unix 타임스탬프", + "timezone": "시간대", + "word": "단어", + "sentence": "문장", + "paragraph": "단락", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "통화 코드", + "language_code": "언어 태그", + "custom_list": "사용자 정의 목록" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG 최적화", + "subtitle": "SVG를 붙여넣고 브라우저에서 SVGO로 축소한 뒤 UTF-8 크기를 전후로 비교합니다.", + "inputLabel": "입력", + "outputLabel": "최적화됨", + "placeholder": " 조각 또는 전체 파일을 붙여넣기…", + "outputPlaceholder": "최적화된 SVG가 여기에 표시됩니다…", + "chars": "{count}자 • {bytes}바이트(UTF-8)", + "preview": "미리보기", + "localNote": "모든 처리는 브라우저에서 로컬로 수행됩니다. 업로드되지 않습니다.", + "stats": { + "title": "크기(UTF-8)", + "before": "이전", + "after": "이후", + "savedBytes": "{count}바이트 절약({percent}%)", + "noChange": "변화 없음 — 이미 최소이거나 출력이 비어 있습니다." + }, + "actions": { + "clear": "지우기", + "sample": "샘플 불러오기", + "copy": "복사", + "copied": "복사됨", + "download": "다운로드" + }, + "errors": { + "optimizeFailed": "이 SVG를 최적화할 수 없습니다." + } + }, + "UnitConverter": { + "title": "단위 변환기", + "subtitle": "43개 카테고리323개 단위 간 변환", + "category": "카테고리", + "from": "변환 전", + "to": "변환 후", + "value": "값", + "result": "결과", + "swapUnits": "단위 전환", + "placeholder": "값 입력", + "copyResult": "결과 복사", + "searchCategory": "카테고리 검색...", + "selectCategory": "카테고리 선택", + "noResults": "카테고리를 찾을 수 없습니다." + }, + "DockerComposeGenerator": { + "title": "Docker Compose 생성기", + "subtitle": "인기 있는 데이터베이스, 캐시, 메시지 큐, 리버스 프록시, 관측 가능성을 선택해 로컬 개발용 docker-compose.yml 을 복사하세요.", + "searchPlaceholder": "검색 (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "서비스", + "selectedCount": "{count, plural, other {#개 서비스}}", + "outputTitle": "docker-compose.yml", + "copy": "복사", + "copied": "복사됨!", + "download": "다운로드", + "emptyState": "Compose YAML 을 생성할 서비스를 하나 이상 선택하세요.", + "hintPorts": "기본 호스트 포트는 충돌을 줄이도록 배치되어 있습니다. 바인드 오류 시 변경하세요. 공유 환경 전에 데모 비밀번호를 교체하세요.", + "categories": { + "databases": "데이터베이스 및 저장소", + "cache": "캐시 및 인메모리", + "queues": "큐 및 메시징", + "search": "검색 및 로그", + "storage": "오브젝트 스토리지", + "proxy": "리버스 프록시 및 Ingress", + "observability": "메트릭 및 추적", + "mail": "이메일(로컬 개발)", + "admin": "관리 및 BI UI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "지우기" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey(Redis 호환)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda(Kafka 호환)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO(S3 호환)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit(SMTP 캡처)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault(개발 서버)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg(Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "타일을 밀어서 합치면 2048에 도달합니다.", + "score": "점수", + "best": "최고 기록", + "newGame": "새 게임", + "howToPlay": "타일을 이동하려면 화살표 키 (또는 모바일에서 스와이프) 를 사용하십시오. 같은 숫자를 가진 타일이 합쳐집니다. 2048에 도달하여 획득하세요!", + "youWin": "2048에 도달했습니다!", + "keepGoing": "계속 이대로만 하세요.", + "gameOver": "게임 오버", + "tryAgain": "다시 시도", + "gameOverMessage": "더 이상 이동할 수 없습니다.", + "undo": "실행 취소" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "모든 스테이지를 완료했어요.", + "time": "시간", + "best": "최고 기록", + "difficulty": { + "easy": "간단", + "medium": "중간", + "hard": "어려움" + }, + "stage": "단계", + "solved": "해결됨", + "score": "점수", + "nextStage": "이벤트 규칙:\n1. 이벤트 기간에 각 라인과 대응되는 수량의 블러드 다이아몬드를 소모하면 대응되는 아이템을 획득할 수 있습니다. \n2. 각 라인의 아이템을 한 번만 구매할 수 있으며 중복으로 구매할 수 없습니다.\n3. 획득한 아이템은 당신의 \"내 아이템\"으로 바로 발급됩니다.", + "replay": "재생", + "erase": "지우기", + "howToPlay": "셀을 클릭한 다음 숫자를 누르거나 1-9를 누릅니다. 백스페이스 또는 삭제는 셀을 지웁니다. 굵은 숫자는 단서입니다. 모든 행, 열 및 3 × 3 상자에 반복 없이 1-9가 포함되도록 그리드를 채웁니다.", + "pencil": "연필", + "undo": "실행 취소", + "hint": "힌트", + "hintsUsed": "힌트 사용", + "mistakes": "실수", + "failed": "실패!", + "mistakeMode": "3실수 모드" + }, + "Snake": { + "title": "스네이크", + "subtitle": "먹이를 먹고, 성장하고, 벽과 자신을 피하세요", + "score": "점수", + "best": "최고", + "startPrompt": "화살표 키를 누르세요", + "startButton": "게임 시작", + "gameOver": "게임 오버", + "playAgain": "다시 플레이", + "howToPlay": "화살표 키나 WASD로 이동. 먹어서 성장하세요. 50점마다 속도가 빨라집니다.", + "diffEasy": "쉬움", + "diffNormal": "보통", + "diffHard": "어려움", + "diffInfinite": "무한", + "orTap": "또는 아래를 탭하세요", + "howToPlayInfinite": "벽이 연결됩니다 — 벽으로 죽지 않습니다. 먹어서 성장하세요.", + "shieldActive": "실드 활성", + "speedBoost": "속도 부스트", + "powerSpeed": "속도 x2", + "powerShield": "죽음 한 번 면제", + "powerShrink": "꼬리 단축" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis 연결", + "heroTitle": "Redis Commander", + "heroDesc": "Redis 인스턴스에 연결합니다. 키를 탐색하고 값을 편집하며 명령을 실행합니다. 자격 증명은 브라우저에서 암호화됩니다.", + "heroAddConnection": "연결 추가", + "vaultLockedTitle": "볼트 잠김", + "vaultLockedDesc": "저장된 연결에 액세스하려면 볼트를 잠금 해제하세요." + } + }, + "Minesweeper": { + "title": "지뢰찾기", + "subtitle": "지뢰를 터뜨리지 않고 모든 안전한 칸을 찾으세요", + "mines": "지뢰", + "newGame": "새 게임", + "win": "승리!", + "loss": "게임 오버!", + "playAgain": "다시 플레이", + "tryAgain": "다시 시도", + "startPrompt": "아무 칸이나 클릭하여 시작하세요", + "difficulty": { + "beginner": "초급", + "intermediate": "중급", + "expert": "상급" + } + }, + "Tetris": { + "title": "테트리스", + "subtitle": "떨어지는 블록을 배치하여 줄을 완성하세요", + "next": "다음", + "score": "점수", + "best": "최고", + "lines": "라인", + "level": "레벨", + "pause": "일시정지", + "resume": "재개", + "start": "시작", + "restart": "다시 시작", + "paused": "일시정지됨", + "gameOver": "게임 오버" + }, + "SshKeyGenerator": { + "title": "SSH / RSA 키 생성기", + "subtitle": "브라우저에서 Ed25519 또는 RSA 키 쌍을 생성합니다. OpenSSH 및 PEM 형식으로 개인 키와 공개 키를 다운로드하세요. 아무것도 업로드되지 않습니다.", + "keyTypeLabel": "키 유형", + "recommended": "권장", + "ed25519Desc": "빠르고 작고 현대적입니다. GitHub, GitLab 및 모든 주요 SSH 서버에서 지원됩니다.", + "rsa2048Desc": "광범위하게 호환됩니다. 대부분의 용도에 안전합니다.", + "rsa4096Desc": "레거시 시스템을 위한 최대 호환성과 강도.", + "commentLabel": "댓글 (선택 사항)", + "commentHint": "OpenSSH 공개 키 줄에 추가됩니다.", + "generateBtn": "키 쌍 생성", + "generating": "생성 중…", + "generatingLong": "생성 중 (잠시 시간이 걸릴 수 있습니다)…", + "copy": "복사", + "download": "다운로드", + "privateKeyLabel": "개인 키 (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "공개 키 (OpenSSH 형식)", + "publicKeySpkiLabel": "공개 키 (SPKI PEM)", + "browserNote": "모든 키는 브라우저에서 생성됩니다. 어떤 서버에도 전송되지 않습니다.", + "errors": { + "generationFailed": "키 생성 실패" + } + }, + "DnsLookup": { + "title": "DNS 조회", + "subtitle": "도메인의 DNS 레코드를 조회합니다 — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "도메인 입력 (예: github.com)", + "lookupBtn": "조회", + "looking": "조회 중…", + "refreshBtn": "새로고침", + "recordTypesLabel": "레코드 유형", + "allBtn": "전체", + "tryLabel": "시도:", + "errors": { + "lookupFailed": "DNS 조회에 실패했습니다" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt 생성 및 검증", + "subtitle": "bcrypt로 비밀번호를 해시하고 해시를 검증 — 모든 작업이 브라우저 안에서 이루어집니다", + "tabs": { + "hash": "해시", + "verify": "검증" + }, + "showPassword": "비밀번호 표시", + "hidePassword": "비밀번호 숨기기", + "copy": "복사", + "copied": "복사됨", + "hash": { + "passwordLabel": "비밀번호", + "passwordPlaceholder": "해시할 비밀번호를 입력하세요…", + "costLabel": "비용 계수", + "costHint": "비용이 높을수록 각 단계의 작업량이 두 배가 됩니다. 프로덕션에서는 10~12가 일반적입니다.", + "slowWarning": "비용 계수가 13 이상이면 계산에 몇 초가 걸릴 수 있습니다.", + "generate": "해시 생성", + "generating": "해시 중…", + "resultLabel": "Bcrypt 해시", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "해시 구조", + "algorithm": "알고리즘", + "cost": "비용", + "salt": "솔트 (22자)", + "hash": "해시 (31자)" + } + }, + "verify": { + "passwordLabel": "비밀번호", + "passwordPlaceholder": "확인할 비밀번호를 입력하세요…", + "hashLabel": "Bcrypt 해시", + "hashPlaceholder": "$2b$10$…", + "idle": "비밀번호와 bcrypt 해시를 입력하여 비교합니다.", + "invalidHash": "유효한 bcrypt 해시가 아닌 것 같습니다.", + "checking": "비교 중…", + "match": "일치 — 이 비밀번호가 이 해시를 생성합니다.", + "noMatch": "불일치 — 이 비밀번호는 이 해시를 생성하지 않습니다." + } + }, + "ChmodCalculator": { + "title": "Chmod 계산기", + "subtitle": "Unix 파일 권한을 체크박스, 8진수, 기호 표기 간에 변환합니다", + "grid": { + "title": "권한", + "read": "읽기", + "write": "쓰기", + "execute": "실행", + "owner": "소유자", + "group": "그룹", + "others": "기타" + }, + "special": { + "title": "특수 비트", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "8진수", + "placeholder": "755 또는 4755", + "invalid": "3자리 또는 4자리 8진수 (0-7)를 입력하세요." + }, + "symbolic": { + "label": "기호 표기", + "placeholder": "rwxr-xr-x", + "invalid": "rwxr-xr-x처럼 9자를 입력하세요 (s/S, t/T 허용)." + }, + "output": { + "title": "명령어", + "copy": "복사", + "copied": "복사됨" + }, + "explain": { + "line": "{cls}은(는) {perms} 수 있습니다.", + "none": "{cls}에는 권한이 없습니다.", + "setuid": "Setuid: 파일이 소유자의 권한으로 실행됩니다.", + "setgid": "Setgid: 파일이 그룹의 권한으로 실행됩니다 (디렉터리의 새 파일은 해당 그룹을 상속합니다).", + "sticky": "Sticky: 이 디렉터리 안에서는 파일 소유자만 파일을 삭제하거나 이름을 바꿀 수 있습니다." + }, + "presets": { + "title": "일반 프리셋", + "octal": "8진수", + "permissions": "권한", + "description": "일반적인 용도", + "p644": "일반 파일: 소유자 쓰기, 모두 읽기.", + "p755": "실행 파일 및 디렉터리: 모두 읽고 진입 가능.", + "p600": "비공개 파일: 소유자만 읽고 쓰기.", + "p700": "비공개 실행 파일 및 디렉터리: 소유자 전용.", + "p664": "그룹 쓰기 가능 파일.", + "p775": "그룹 쓰기 가능 디렉터리 및 실행 파일.", + "p400": "소유자 읽기 전용 (예: SSH 개인 키)." + } + }, + "CodeScreenshot": { + "title": "코드 스크린샷", + "subtitle": "코드를 아름답고 공유하기 좋은 이미지로 변환", + "languageLabel": "언어", + "autoDetect": "자동 감지", + "backgroundLabel": "배경", + "paddingLabel": "여백", + "filenameLabel": "파일 이름", + "filenamePlaceholder": "example.ts", + "windowLabel": "창 점", + "themeLabel": "창 테마", + "themeDark": "다크", + "themeLight": "라이트", + "scaleLabel": "내보내기 배율", + "copyImage": "이미지 복사", + "copied": "복사됨", + "exportPng": "PNG 내보내기", + "inputLabel": "코드", + "detected": "감지됨: {language}", + "clear": "지우기", + "placeholder": "여기에 코드를 붙여넣으세요…", + "previewLabel": "미리보기", + "emptyPreview": "코드 미리보기가 여기에 표시됩니다", + "exportFailed": "이미지 내보내기에 실패했습니다", + "copyUnsupported": "이 브라우저에서는 이미지 복사가 지원되지 않습니다", + "copyFailed": "이미지 복사에 실패했습니다", + "backgrounds": { + "ocean": "오션", + "sunset": "선셋", + "candy": "캔디", + "forest": "포레스트", + "midnight": "미드나이트", + "gold": "골드", + "aurora": "오로라", + "slate": "슬레이트", + "transparent": "투명" + } + }, + "CssGenerators": { + "title": "CSS 생성기", + "subtitle": "Box-shadow, border-radius, 유동적 clamp() — CSS를 복사하세요.", + "preview": "미리보기", + "output": "CSS", + "copy": "복사", + "copied": "복사됨", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "유동적 clamp()" + }, + "boxShadow": { + "presets": "프리셋", + "layer": "레이어", + "addLayer": "레이어 추가", + "removeLayer": "레이어 제거", + "x": "X", + "y": "Y", + "blur": "흐림", + "spread": "확산", + "color": "색상", + "alpha": "투명도", + "inset": "안쪽", + "presetNames": { + "subtleCard": "은은한 카드", + "elevated": "떠 있는", + "hardOffset": "하드 오프셋", + "neonGlow": "네온 글로우", + "inner": "안쪽", + "layeredSmooth": "겹친 부드러움" + } + }, + "borderRadius": { + "modeLabel": "모드", + "modes": { + "simple": "모서리별", + "blob": "유기적 블롭" + }, + "unit": "단위", + "linkCorners": "모서리 연결", + "randomize": "무작위", + "horizontal": "수평 반경", + "vertical": "수직 반경", + "corners": { + "tl": "왼쪽 위", + "tr": "오른쪽 위", + "br": "오른쪽 아래", + "bl": "왼쪽 아래" + } + }, + "clamp": { + "minFont": "최소 글꼴 (px)", + "maxFont": "최대 글꼴 (px)", + "minViewport": "최소 뷰포트 (px)", + "maxViewport": "최대 뷰포트 (px)", + "rootFont": "루트 글꼴 (px)", + "viewportWidth": "시뮬레이션 뷰포트", + "computedSize": "계산된 크기", + "slope": "기울기", + "intercept": "절편", + "previewText": "The quick brown fox", + "invalid": "유효하고 서로 다른 뷰포트 범위를 입력하세요." + } + }, + "ExifViewer": { + "title": "EXIF 뷰어 및 제거", + "subtitle": "사진 메타데이터를 확인하고 브라우저에서 제거합니다", + "dropTitle": "여기에 이미지를 놓으세요", + "dropHint": "또는 클릭하여 찾아보기 — 이미지는 기기를 벗어나지 않습니다", + "browse": "이미지 선택", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "메타데이터 읽는 중…", + "parseFailed": "이 이미지를 읽지 못했습니다", + "clear": "이미지 제거", + "gpsWarning": "GPS 위치 정보 포함", + "openMap": "OpenStreetMap에서 보기", + "noMetadata": "이 이미지에서 삽입된 메타데이터를 찾지 못했습니다.", + "facts": { + "name": "이름", + "type": "형식", + "dimensions": "크기", + "size": "용량" + }, + "groups": { + "camera": "카메라", + "exposure": "노출", + "timestamps": "타임스탬프", + "gps": "GPS 위치", + "software": "소프트웨어 및 기타" + }, + "gpsCard": { + "latitude": "위도", + "longitude": "경도", + "altitude": "고도" + }, + "raw": { + "title": "모든 메타데이터", + "copyJson": "JSON 복사", + "copied": "복사됨" + }, + "remove": { + "title": "메타데이터 제거", + "jpegDescription": "이미지를 재압축하지 않고 EXIF, XMP, IPTC, 주석을 무손실로 제거합니다.", + "reencodeDescription": "이미지를 다시 인코딩하여 삽입된 메타데이터를 제거합니다. 픽셀이 재압축됩니다.", + "keepIcc": "색상 프로필 (ICC) 유지", + "stripButton": "메타데이터 제거", + "reencodeButton": "다시 인코딩 후 제거", + "unsupported": "이 파일 형식에서는 메타데이터 제거가 지원되지 않습니다.", + "failed": "메타데이터 제거에 실패했습니다", + "before": "이전", + "after": "이후", + "download": "정리된 이미지 다운로드" + } + }, + "FaviconGenerator": { + "title": "Favicon 생성기", + "subtitle": "하나의 이미지로 favicon과 앱 아이콘을 만듭니다", + "dropTitle": "여기에 이미지를 놓으세요", + "dropHint": "PNG, JPEG 또는 SVG — 클릭하여 찾아보기", + "browse": "이미지 선택", + "clear": "이미지 제거", + "backgroundLabel": "배경", + "transparent": "투명", + "solidColor": "단색", + "cornerLabel": "모서리 모양", + "cornerNone": "사각형", + "cornerRounded": "둥근 모서리", + "cornerCircle": "원형", + "maskPaddingLabel": "마스커블 여백", + "maskable": "maskable", + "previewLabel": "미리보기", + "copy": "복사", + "headSnippet": "HTML head 스니펫", + "downloadIco": "favicon.ico 다운로드", + "downloadZip": "모두 다운로드 (.zip)" + }, + "JsonDiff": { + "title": "JSON 비교", + "subtitle": "두 JSON 문서를 의미론적, 구조 인식 방식으로 비교 — 키 순서와 서식은 무시됩니다", + "options": { + "ignoreArrayOrder": "배열 순서 무시" + }, + "loadBothSamples": "샘플 불러오기", + "sample": "샘플", + "format": "포맷", + "clear": "지우기", + "formatError": "포맷할 수 없음: 입력이 유효한 JSON이 아닙니다", + "placeholder": "여기에 JSON을 붙여넣거나 입력하세요…", + "panels": { + "left": "원본 (왼쪽)", + "right": "수정됨 (오른쪽)", + "results": "차이" + }, + "summary": { + "added": "{count}개 추가", + "removed": "{count}개 제거", + "changed": "{count}개 변경" + }, + "types": { + "added": "추가됨", + "removed": "제거됨", + "changed": "변경됨" + }, + "copyChanges": "JSON으로 복사", + "copied": "복사됨", + "empty": "두 패널에 JSON을 붙여넣어 차이를 확인하세요.", + "identical": "두 문서는 의미론적으로 동일합니다." + }, + "JsonpathPlayground": { + "title": "JSONPath 플레이그라운드", + "subtitle": "JSONPath 또는 JMESPath로 JSON 문서를 쿼리하고 결과를 실시간으로 확인합니다", + "engineLabel": "엔진", + "queryLabel": "쿼리", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "치트시트", + "cheats": { + "jpAllTitles": "모든 책 제목", + "jpAllAuthors": "문서 어디에나 있는 모든 저자", + "jpFirstBook": "첫 번째 책", + "jpLastBook": "마지막 책", + "jpSlice": "처음 두 권 (슬라이스)", + "jpFilterPrice": "40 미만인 책", + "jpFilterInStock": "재고 있는 책의 제목", + "jpRecursive": "모든 값 재귀적으로", + "jmAllTitles": "모든 책 제목", + "jmFirstAuthor": "첫 번째 책의 저자", + "jmLastBook": "마지막 책", + "jmSlice": "처음 두 권 (슬라이스)", + "jmFilterPrice": "40 미만인 책의 제목", + "jmCount": "재고 있는 책 세기", + "jmProjection": "각 책을 새 객체로 재구성", + "jmSort": "가격순으로 정렬한 제목" + }, + "panels": { + "input": "JSON 입력", + "results": "결과" + }, + "sample": "샘플", + "format": "포맷", + "clear": "지우기", + "formatError": "포맷할 수 없음: 입력이 유효한 JSON이 아닙니다", + "placeholder": "여기에 JSON을 붙여넣거나 입력하세요…", + "outputPlaceholder": "일치 항목이 여기에 표시됩니다…", + "matchCount": "{count, plural, one {일치 #개} other {일치 #개}}", + "copy": "복사", + "copied": "복사됨" + }, + "KeycodeInspector": { + "title": "키코드 검사기", + "subtitle": "아무 키나 눌러 JavaScript 키보드 이벤트를 검사합니다", + "capture": { + "prompt": "아무 키나 누르세요…", + "ready": "수신 중 — 키보드에서 아무 키나 누르세요", + "clickToFocus": "여기를 클릭하여 포커스한 다음 아무 키나 누르세요" + }, + "hero": { + "spaceKey": "(스페이스)", + "keyLabel": "event.key", + "repeat": "반복" + }, + "copyJson": "JSON 복사", + "copiedJson": "이벤트 JSON을 클립보드에 복사했습니다", + "deprecated": "더 이상 사용되지 않음", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "표준", + "left": "왼쪽", + "right": "오른쪽", + "numpad": "숫자 패드", + "unknown": "알 수 없음" + }, + "history": { + "title": "기록", + "clear": "기록 지우기", + "empty": "키 입력이 여기에 표시됩니다." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown 표 생성기", + "subtitle": "그리드를 편집하고, 스프레드시트에서 붙여넣고, 깔끔하게 정렬된 Markdown 또는 HTML 표를 복사합니다", + "addRow": "행 추가", + "addColumn": "열 추가", + "importLabel": "가져오기", + "importPlaceholder": "TSV (Excel / Google Sheets) 또는 CSV를 여기에 붙여넣으세요…", + "importButton": "그리드로 가져오기", + "pasteClipboard": "클립보드에서 붙여넣기", + "importEmpty": "가져올 항목이 없습니다 — 먼저 행을 붙여넣으세요", + "clipboardError": "클립보드에서 읽을 수 없습니다", + "clear": "지우기", + "panels": { + "editor": "표 편집기", + "output": "출력" + }, + "align": { + "left": "왼쪽 정렬", + "center": "가운데 정렬", + "right": "오른쪽 정렬" + }, + "headerPlaceholder": "헤더", + "cellPlaceholder": "셀", + "removeRow": "행 제거", + "removeColumn": "열 제거", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "복사", + "copied": "복사됨" + }, + "TimezoneConverter": { + "title": "시간대 변환기", + "subtitle": "회의 계획을 위해 하나의 순간을 전 세계 시각으로 변환합니다.", + "dateTimeLabel": "날짜 및 시간", + "now": "지금", + "sourceZoneLabel": "원본 시간대", + "selectZone": "시간대 선택", + "searchZone": "시간대 검색...", + "noZone": "시간대를 찾을 수 없습니다.", + "scrubLabel": "시간 이동", + "reset": "재설정", + "targetsLabel": "대상 시간대", + "addZone": "시간대 추가", + "remove": "제거", + "localBadge": "로컬", + "invalidDate": "유효한 날짜와 시간을 입력하세요.", + "bands": { + "work": "근무 시간 (09:00–18:00)", + "edge": "경계 시간 (이른 아침 / 저녁)", + "night": "야간" + } + }, + "TokenCounter": { + "title": "LLM 토큰 카운터", + "subtitle": "토큰을 세고, 청크를 시각화하며, API 비용을 추정합니다.", + "inputLabel": "텍스트", + "sample": "샘플", + "clear": "지우기", + "placeholder": "토큰을 셀 텍스트, 코드 또는 JSON을 붙여넣으세요...", + "encoding": "인코딩", + "loading": "토크나이저 불러오는 중...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o 시리즈)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "토큰", + "characters": "문자", + "words": "단어", + "ratio": "토큰/단어", + "ratioSub": "대략" + }, + "claudeRow": "Claude (근사치)", + "estimate": "추정", + "viz": { + "title": "토큰 시각화", + "toggle": "표시", + "hint": "전환하여 각 토큰을 청크로 색상 표시합니다.", + "empty": "텍스트를 입력하여 토큰을 시각화하세요.", + "truncated": "전체 {total}개 중 처음 {limit}개 토큰을 표시하고 있습니다." + }, + "cost": { + "title": "비용 계산기", + "disclaimer": "편집 가능한 기본값 — 현재 가격을 확인하세요", + "model": "모델", + "expectedOutput": "예상 출력 토큰", + "customInput": "입력 $/MTok", + "customOutput": "출력 $/MTok", + "inputCost": "입력 비용", + "outputCost": "출력 비용", + "total": "합계", + "per1000": "1000회 호출당" + } + }, + "WebhookTester": { + "title": "Webhook 테스터", + "subtitle": "임시 URL을 만들고 수신되는 webhook 요청을 실시간으로 검사합니다.", + "intro": { + "title": "webhook URL을 만들어 요청 캡처를 시작하세요", + "hint": "추측할 수 없는 URL이 발급되어 수신되는 모든 요청을 기록합니다. 24시간 후에 만료됩니다." + }, + "createBtn": "webhook URL 만들기", + "creating": "만드는 중…", + "urlLabel": "내 webhook URL", + "expiresIn": "{countdown} 후 만료", + "copyUrlBtn": "URL 복사", + "copied": "복사됨", + "clearBtn": "지우기", + "newBinBtn": "새 URL", + "requestsTitle": "요청", + "selectPrompt": "요청을 선택하여 세부 정보를 확인하세요.", + "expiredNotice": "이 webhook URL은 만료되었습니다. 계속 테스트하려면 새로 만드세요.", + "base64Badge": "base64", + "truncatedBadge": "잘림", + "empty": { + "title": "요청을 기다리는 중…", + "hint": "URL로 요청을 보내면 즉시 여기에 표시됩니다. 이렇게 해보세요:" + }, + "detail": { + "receivedAt": "수신 시각", + "source": "출발지 IP", + "contentType": "Content-Type", + "query": "쿼리 매개변수", + "headers": "헤더", + "body": "본문", + "copyBody": "본문 복사", + "emptyBody": "본문 없음" + }, + "errors": { + "createFailed": "webhook URL 생성에 실패했습니다. 다시 시도하세요.", + "clearFailed": "요청을 지우지 못했습니다. 다시 시도하세요." + } + }, + "WebsocketTester": { + "title": "WebSocket 테스터", + "subtitle": "WebSocket 엔드포인트에 연결하여 메시지를 실시간으로 전송, 수신, 검사합니다", + "urlLabel": "엔드포인트 URL", + "protocolsLabel": "하위 프로토콜", + "protocolsPlaceholder": "선택 사항, 쉼표로 구분", + "connect": "연결", + "disconnect": "연결 해제", + "status": { + "disconnected": "연결 해제됨", + "connecting": "연결 중", + "connected": "연결됨", + "error": "오류" + }, + "messages": { + "connecting": "{url}에 연결 중…", + "connected": "연결이 설정되었습니다", + "connectedWithProtocol": "연결이 설정되었습니다 (프로토콜: {protocol})", + "invalidUrl": "잘못된 URL — ws:// 또는 wss://로 시작해야 합니다", + "connectFailed": "연결을 열지 못했습니다", + "socketError": "연결 오류가 발생했습니다", + "notConnected": "연결되지 않음 — 먼저 연결하세요", + "binaryMessage": "바이너리 메시지를 수신했습니다" + }, + "log": { + "title": "메시지 로그", + "empty": "아직 메시지가 없습니다. 연결하고 전송을 시작하세요.", + "copyAll": "모두 복사", + "download": ".txt로 다운로드", + "clear": "로그 지우기", + "prettyPrint": "JSON 정렬 표시", + "showRaw": "원본 텍스트 표시" + }, + "composer": { + "placeholder": "전송할 메시지를 입력하세요…", + "send": "전송", + "formatJson": "JSON으로 포맷", + "invalidJson": "메시지가 유효한 JSON이 아닙니다", + "hint": "Cmd/Ctrl+Enter로 전송" + }, + "ping": { + "label": "자동 ping", + "seconds": "초", + "intervalTitle": "Ping 간격 (초)", + "payloadPlaceholder": "ping 페이로드" + } + }, + "WhoisLookup": { + "title": "Whois 조회", + "subtitle": "RDAP를 통해 도메인이나 IP 주소의 등록 정보를 조회합니다", + "placeholder": "example.com 또는 8.8.8.8", + "lookupBtn": "조회", + "looking": "조회 중…", + "refreshBtn": "새로고침", + "tryLabel": "예시:", + "copy": "복사", + "copied": "복사됨", + "notAvailable": "사용할 수 없음", + "summary": { + "registrar": "등록 대행자", + "network": "네트워크" + }, + "dates": { + "created": "생성일", + "updated": "수정일", + "expires": "만료일" + }, + "sections": { + "status": "상태", + "nameservers": "네임서버", + "contacts": "연락처", + "rawRdap": "원본 RDAP" + }, + "contacts": { + "roles": "역할", + "name": "이름", + "organization": "조직" + }, + "errors": { + "invalidTarget": "유효한 도메인 이름이나 IP 주소를 입력하세요", + "lookupFailed": "조회 실패 (상태 {status})" + } + } +} diff --git a/apps/desktop-ui/messages/ms.json b/apps/desktop-ui/messages/ms.json new file mode 100644 index 00000000..ff115d0b --- /dev/null +++ b/apps/desktop-ui/messages/ms.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Papan Pemuka", + "settings": "Tetapan", + "apiClient": "Klien API", + "httpStatusCodes": "Kod status HTTP", + "passwordManager": "Pengurus Kata Laluan", + "environmentManager": "Pengurus persekitaran", + "bookmarks": "Penanda Halaman", + "jsonFormatter": "Pemformat JSON", + "emailValidator": "Pengesah E-mel", + "nosqlExplorer": "Penjelajah Pangkalan Data", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Taman permainan penyulitanan", + "jwtDecoder": "Penyahkod JWT", + "certificatePemDecoder": "Pengecam sijil / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Pemformat GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Penghurai URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Rahsia / kunci API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Pemeriksa kontras", + "jsonSchemaGenerator": "Penjana Skema JSON", + "imageToBase64": "Imej ke Base64", + "imageCompressor": "Pemampat Imej", + "cssGradientBuilder": "Pembina Kecerunan CSS", + "gitignoreGenerator": "Penjana .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Petikan kod", + "qrCodeGenerator": "Penjana kod QR", + "dnsLookup": "Carian DNS", + "ipSubnetCalculator": "Kalkulator IP / subnet", + "hashGenerator": "Penjana hash", + "hmacGenerator": "Penjana HMAC", + "totpGenerator": "Kod TOTP / 2FA", + "markdownPreview": "Pratonton Markdown", + "numberBaseConverter": "Penukar asas", + "formatConverter": "Penukar Format", + "mockDataGenerator": "Penjana Data Mock", + "svgOptimizer": "Pengoptimum SVG", + "unitConverter": "Penukar Unit", + "dockerComposeGenerator": "Penjana Docker Compose", + "breakRoom": "Bilik Rehat", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Pemformat YAML", + "sshKeyGenerator": "Penjana Kekunci SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Kalkulator Chmod", + "codeScreenshot": "Tangkap Skrin Kod", + "cssGenerators": "Penjana CSS", + "exifViewer": "Pemapar EXIF", + "faviconGenerator": "Penjana Favicon", + "jsonDiff": "Beza JSON", + "jsonpathPlayground": "Ruang Uji JSONPath", + "keycodeInspector": "Pemeriksa Kod Kekunci", + "markdownTableGenerator": "Jadual Markdown", + "timezoneConverter": "Penukar Zon Waktu", + "tokenCounter": "Pengira Token", + "webhookTester": "Penguji Webhook", + "websocketTester": "Penguji WebSocket", + "whoisLookup": "Carian Whois" + }, + "Help": { + "title": "Bantuan & dokumentasi", + "subtitle": "Bagaimana MyDevTools berfungsi, apa yang dibuat oleh setiap aplikasi, dan bagaimana keselamatan serta penyulitan melindungi data anda.", + "tabs": { + "overview": "Gambaran keseluruhan", + "apps": "Aplikasi", + "security": "Keselamatan", + "tips": "Petua" + }, + "overview": { + "whatIs": { + "title": "Apakah MyDevTools?", + "description": "Ruang kerja pembangun dengan log masuk: papan pemuka dan set alat untuk tugasan harian.", + "p1": "Selepas anda log masuk, aplikasi mewujudkan sesi dengan MyDevTools API supaya alat boleh memuat dan menyimpan data anda. Bar sisi menyenaraikan semua aplikasi; anda boleh menyembunyikan alat yang tidak digunakan dari", + "settingsLink": "Tetapan", + "p1After": ".", + "p2": "Sesetengah ciri menggunakan kata laluan induk (atau kunci vault global) supaya rahsia seperti kata laluan dan rentetan sambungan disulitkan pada peranti anda sebelum sampai ke pelayan." + }, + "quickLinks": { + "title": "Pautan pantas", + "dashboard": "Papan Pemuka", + "settings": "Tetapan" + } + }, + "apps": { + "intro": "Buka mana-mana alat dari bar sisi atau papan pemuka. Di bawah ialah panduan ringkas untuk setiap aplikasi.", + "open": "Buka", + "howItWorks": "Cara ia berfungsi", + "generic": "Gunakan alat ini dari bar sisi untuk aliran kerja khususnya." + }, + "security": { + "session": { + "title": "Log masuk dan sesi API", + "description": "Identiti dan cara pelayar berkomunikasi dengan backend.", + "items": { + "authentication": "Pengesahan menggunakan Firebase untuk log masuk. Selepas log masuk, aplikasi menukar token ID Firebase anda kepada kuki API.", + "accessTokens": "Token akses ialah JWT yang ditandatangani pelayan (HS256) untuk membuktikan pengguna yang memanggil API.", + "refreshTokens": "Token segar semula disimpan dalam kuki HttpOnly; pelayan menyimpan hash SHA-256 token, bukan token mentah." + } + }, + "encryption": { + "title": "Penyulitan yang perlu diketahui", + "description": "Kripto sisi klien untuk vault dan data sambungan sensitif.", + "p1": "Untuk pengurus kata laluan, pengurus persekitaran dan sambungan tersimpan dalam NoSQL Explorer, aplikasi web menggunakan Web Crypto API pelayar:", + "items": { + "kdf": "PBKDF2 dengan SHA-256, 100,000 iterasi dan salt rawak menjana kunci 256-bit daripada kata laluan induk anda.", + "aes": "AES-256-GCM menyulitkan data; IV 12 bait rawak digunakan bagi setiap penyulitan.", + "scope": "Kunci terbitan kekal dalam pelayar (tidak boleh diekstrak). Pelayan hanya menerima ciphertext dan IV, bukan kata laluan induk atau rahsia dalam teks biasa." + }, + "warning": "Jika anda terlupa kata laluan induk, data yang disulitkan tidak boleh dipulihkan. Simpan dengan selamat." + }, + "logout": { + "title": "Log keluar dan data tempatan", + "body": "Log keluar akan mengosongkan rahsia dalam ingatan, status vault, dan (jika berkenaan) bahan kunci induk daripada IndexedDB serta menamatkan sesi API. Data yang hanya berada dalam storan tempatan peranti mungkin kekal sehingga anda memadamkannya dalam pelayar." + } + }, + "tips": { + "title": "Menggunakan MyDevTools dengan selamat", + "items": { + "masterPassword": "Gunakan kata laluan induk yang kuat dan unik untuk ciri vault.", + "apiClient": "Anggap API Client seperti klien HTTP biasa: elakkan menampal rahsia produksi ke persekitaran kongsi jika anda tidak tahu siapa yang boleh melihatnya.", + "nosql": "Penjelajah Pangkalan Data menyambung ke pangkalan data yang anda konfigurasi; gunakan akaun keistimewaan minimum dan peraturan rangkaian jika boleh.", + "json": "Pemformat JSON berjalan secara tempatan dalam pelayar—masih elakkan menampal data yang sangat sensitif pada mesin kongsi.", + "settings": "Sesuaikan alat yang kelihatan dalam Tetapan untuk memastikan bar sisi kekal fokus." + } + } + }, + "Settings": { + "title": "Tetapan", + "language": "Bahasa", + "spanish": "Spanish", + "english": "English", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalan", + "chineseSimplified": "Chinese (Simplified)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "German", + "danish": "Danish", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukraine" + }, + "SettingsPage": { + "title": "Tetapan", + "subtitle": "Urus tetapan akaun dan keutamaan anda.", + "userProfile": { + "title": "Profil Pengguna", + "description": "Maklumat peribadi dan butiran akaun anda.", + "avatarAlt": "Pengguna", + "anonymousUser": "Pengguna Tanpa Nama", + "noEmailProvided": "Tiada e-mel disediakan", + "notLoggedIn": "Belum log masuk." + }, + "tools": { + "title": "Alat & Ciri", + "description": "Pilih alat yang dipaparkan di bar sisi navigasi anda." + }, + "appearance": { + "title": "Penampilan", + "description": "Sesuaikan rupa aplikasi pada peranti anda.", + "themePreference": "Keutamaan Tema", + "themes": { + "light": "Cerah", + "dark": "Gelap", + "system": "Sistem" + } + }, + "language": { + "title": "Bahasa", + "description": "Tetapkan bahasa yang digunakan di seluruh platform.", + "selectLanguage": "Pilih Bahasa", + "selectPlaceholder": "Pilih bahasa", + "helpText": "Menukar bahasa anda akan mengemas kini menu dan label di seluruh platform.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spanish)", + "ar": "عربى (Arabic)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Greek)", + "de": "Deutsch (German)", + "da": "Dansk (Danish)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Itali (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "noResults": "Tiada alat yang sepadan dengan carian anda", + "noResultsHint": "Cuba kata kunci lain atau kosongkan penapis.", + "filterAll": "Semua", + "loginCta": { + "description": "Log masuk untuk menjejaki alat yang baru-baru ini digunakan." + }, + "greeting": { + "morning": "Selamat pagi", + "afternoon": "Selamat petang", + "evening": "Selamat malam" + }, + "commaName": ", {name}", + "welcomeBack": "Selamat kembali", + "welcomeBackNamed": "Selamat kembali, {name}", + "tagline": "Apa yang ingin anda bina hari ini?", + "brandName": "MyDevTools", + "stats": { + "tools": "Alat", + "favorites": "Kegemaran", + "pinned": "Disematkan", + "recent": "Baru Digunakan" + }, + "sections": { + "recentlyUsed": "Baru Digunakan", + "favorites": "Kegemaran", + "whatsNew": "Yang Baru", + "pinned": "Disematkan" + }, + "groups": { + "apps": "Aplikasi" + }, + "toolCard": { + "defaultDescription": "Terokai alat ini untuk fungsi yang lebih baik.", + "launchTool": "Lancar Alat" + }, + "tools": { + "toDo": { + "title": "Tugasan", + "description": "Urus senarai tugasan anda dengan cekap." + }, + "notes": { + "title": "Nota", + "description": "Tangkap idea dan susun fikiran anda." + }, + "passwordManager": { + "title": "Pengurus Kata Laluan", + "description": "Simpan dan urus kata laluan anda dengan selamat." + }, + "environmentManager": { + "title": "Pengurus persekitaran", + "description": "Simpan pemboleh ubah gaya .env mengikut projek dan persekitaran dengan teg." + }, + "bookmarks": { + "title": "Penanda Halaman", + "description": "Urus dan susun penanda halaman anda." + }, + "apiClient": { + "title": "Klien API", + "description": "Uji dan nyahpepijat permintaan HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Terokai pangkalan data MongoDB." + }, + "emailValidator": { + "title": "Pengesah E-mel", + "description": "Sahkan dan pengesahan alamat e-mel." + }, + "encryptionPlayground": { + "title": "Taman permainan penyulitanan", + "description": "AES-GCM dengan kunci mentah atau frasa laluan; bungkusan JSON, Web Crypto hanya dalam pelayar." + }, + "jwtDecoder": { + "title": "Penyahkod JWT", + "description": "Nyahkod pengepala, payload dan tamat tempoh JWT." + }, + "certificatePemDecoder": { + "title": "Pengecam sijil / PEM", + "description": "Nyahkod sijil X.509 dan CSR: subjek, penerbit, kesahan, dan SAN." + }, + "sshKeyGenerator": { + "title": "Penjana Kunci SSH / RSA", + "description": "Jana pasangan kunci Ed25519 atau RSA dalam pelayar. Muat turun dalam format OpenSSH dan PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Pemformat GraphQL", + "description": "Format GraphQL dengan pensyntaksan sorotan dan pembina pertanyaan ringkas." + }, + "yamlFormatter": { + "title": "Pemformat YAML", + "description": "Format, sahkan dan tukarkan YAML ke JSON dalam pelayar." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "formatConverter": { + "title": "Penukar Format", + "description": "Tukar antara JSON, YAML, TOML, dan XML dalam mana-mana arah. 12 gabungan disokong." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Penghurai URL", + "description": "Pecahkan URL kepada protokol, hos, laluan, parameter kueri, dan hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Rahsia / kunci API", + "description": "Rentetan rawak kriptografi dengan abjad, panjang dan eksport pukal boleh dikonfigurasi." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Pemeriksa kontras", + "description": "Nisbah kontras WCAG 2.1 teks dan latar (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Penjana Skema JSON", + "description": "Inferens skema JSON dan model berjenis daripada sampel JSON." + }, + "imageToBase64": { + "title": "Imej ke Base64", + "description": "Tukar imej kepada URI Data atau rentetan Base64 mentah." + }, + "imageCompressor": { + "title": "Pemampat Imej", + "description": "Mampatkan JPEG, PNG dan WebP secara setempat dengan peluncur kualiti." + }, + "cssGradientBuilder": { + "title": "Pembina Kecerunan CSS", + "description": "Pembina kecerunan CSS visual dengan kawalan sudut dan hentian warna." + }, + "gitignoreGenerator": { + "title": "Penjana .gitignore", + "description": "Hasilkan fail .gitignore yang digabungkan serta-merta daripada 500+ susunan teknologi." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Tukar fail CSV atau Excel kepada JSON dan eksport semula ke CSV atau XLSX." + }, + "snippetManager": { + "title": "Petikan kod", + "description": "Simpan petikan dengan sorotan sintaks, pengesanan bahasa automatik dan pemformatan." + }, + "qrCodeGenerator": { + "title": "Penjana kod QR", + "description": "Cipta kod QR PNG daripada teks atau URL dalam pelayar." + }, + "ipSubnetCalculator": { + "title": "Kalkulator IP / subnet", + "description": "IPv4 dan IPv6 CIDR: topeng rangkaian, julat, dan bilangan alamat." + }, + "dnsLookup": { + "title": "Carian DNS", + "description": "Pertanyaan rekod DNS untuk mana-mana domain — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Penjana hash", + "description": "MD5, SHA-1 dan SHA-2 untuk teks atau fail." + }, + "hmacGenerator": { + "title": "Penjana HMAC", + "description": "Tandatangani webhook dan uji integrasi API dengan HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Penjana kod TOTP / 2FA", + "description": "Tampal rahsia Base32 daripada aplikasi pengesah; kod enam digit disegar setiap 30 s—berguna untuk ujian log masuk dan MFA." + }, + "markdownPreview": { + "title": "Pratonton Markdown", + "description": "Pemapar Markdown langsung dengan eksport HTML dan penukaran HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Penukar asas nombor", + "description": "Tukar integer antara asas 2–36." + }, + "mockDataGenerator": { + "title": "Penjana Data Mock", + "description": "Takrifkan lajur, pilih jenis data, dan eksport set data untuk benih, Postman atau ujian. Sehingga 5000 baris, semuanya dalam pelayar — tiada muat naik." + }, + "httpStatusCodes": { + "title": "Kod Status HTTP", + "description": "Cari kod status HTTP dengan penerangan (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Pengoptimum SVG", + "description": "Tampal SVG, buang noise dengan SVGO dan lihat penjimatan saiz UTF-8." + }, + "unitConverter": { + "title": "Penukar Unit", + "description": "Tukar antara 323 unit merentasi 43 kategori sains dan kejuruteraan." + }, + "dockerComposeGenerator": { + "title": "Penjana Docker Compose", + "description": "Pilih pangkalan data, cache, baris gilir, proksi dan imej kebolehcerapan; eksport docker-compose.yml untuk setempat." + }, + "game2048": { + "title": "2048", + "description": "Luncur dan gabungkan jubin untuk mencapai 2048. Permainan teka-teki nombor klasik." + }, + "sudoku": { + "title": "Stone GNOME", + "description": "Teka-teki nombor klasik. 3 tahap kesukaran, 150 peringkat setiap satu." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Semak kunci, periksa nilai dan jalankan arahan pada mana-mana contoh Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Penjana & Penyemak Bcrypt", + "description": "Cincang kata laluan dengan bcrypt dan sahkan cincangan sedia ada." + }, + "chmodCalculator": { + "title": "Kalkulator Chmod", + "description": "Tukar keizinan Unix antara oktal, simbolik dan kotak semak." + }, + "codeScreenshot": { + "title": "Tangkap Skrin Kod", + "description": "Tukar kod sumber menjadi imej cantik yang boleh dikongsi." + }, + "cssGenerators": { + "title": "Penjana CSS", + "description": "Penjana box-shadow, border-radius dan clamp() bendalir." + }, + "exifViewer": { + "title": "Pemapar & Pembuang EXIF", + "description": "Periksa dan buang metadata EXIF foto secara setempat." + }, + "faviconGenerator": { + "title": "Penjana Favicon", + "description": "Jana favicon, ikon aplikasi dan cebisan manifest." + }, + "jsonDiff": { + "title": "Beza JSON", + "description": "Perbandingan bermakna dan peka struktur bagi dua dokumen JSON." + }, + "jsonpathPlayground": { + "title": "Ruang Uji JSONPath", + "description": "Kueri JSON dengan JSONPath atau JMESPath dan lihat keputusan secara langsung." + }, + "keycodeInspector": { + "title": "Pemeriksa Kod Kekunci", + "description": "Tekan satu kekunci untuk lihat kod peristiwa JavaScript dan pengubah suainya." + }, + "markdownTableGenerator": { + "title": "Penjana Jadual Markdown", + "description": "Bina jadual Markdown dan HTML dalam penyunting grid seperti hamparan." + }, + "timezoneConverter": { + "title": "Penukar Zon Waktu", + "description": "Tukar satu masa merentas banyak zon dengan petunjuk anjakan hari dan waktu kerja." + }, + "tokenCounter": { + "title": "Pengira Token LLM", + "description": "Kira token LLM, visualkan cebisan dan anggarkan kos API." + }, + "webhookTester": { + "title": "Penguji Webhook", + "description": "Tangkap dan periksa permintaan webhook masuk secara masa nyata." + }, + "websocketTester": { + "title": "Penguji WebSocket", + "description": "Sambung ke titik akhir WebSocket dan bertukar mesej secara masa nyata." + }, + "whoisLookup": { + "title": "Carian Whois", + "description": "Cari butiran pendaftaran domain dan IP melalui RDAP." + } + }, + "tabs": { + "apps": "Aplikasi", + "analytics": "Analitik", + "ui": "Antara muka" + }, + "analytics": { + "title": "Data anda sekilas", + "subtitle": "Kiraan disegerakkan dengan akaun anda pada pelayan.", + "loadError": "Tidak dapat memuatkan analitik.", + "retry": "Cuba lagi", + "loginHint": "Log masuk untuk melihat analitik penggunaan dan pilihan antara muka.", + "passwordEntries": "Entri kata laluan", + "bookmarks": "Penanda halaman", + "bookmarkFolders": "Folder penanda", + "tasksTotal": "Tugas", + "tasksCompleted": "Selesai", + "tasksOngoing": "Sedang berjalan", + "tasksNotStarted": "Belum dimulakan", + "projects": "Projek", + "nosqlConnections": "Sambungan pangkalan data", + "notes": "Nota", + "apiClientCollections": "Koleksi API", + "apiClientEnvironments": "Persekitaran API", + "apiClientHistory": "Entri sejarah API", + "jsonFormatterDocuments": "Dokumen pemformat JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Petikan kod", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Pilihan antara muka", + "subtitle": "Tema, bahasa dan alat yang kelihatan di bar sisi — daripada tetapan akaun.", + "loadError": "Tidak dapat memuatkan pilihan.", + "loginHint": "Log masuk untuk melihat pilihan antara muka yang disimpan.", + "theme": "Tema", + "accent": "Warna aksen", + "locale": "Bahasa", + "enabledTools": "Alat kelihatan", + "favoriteTools": "Alat kegemaran", + "toolsWithUsage": "Alat dengan penjejakan penggunaan" + }, + "signIn": "Log masuk", + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Memuatkan...", + "myTasksTitle": "Tugasan Saya", + "statsLine": "{total} tugasan • {percent}% selesai", + "statsLineDesktop": "{total} jumlah • {percent}% selesai" + }, + "filters": { + "all": "Semua", + "total": "Jumlah", + "allProjects": "Semua Projek", + "export": "Eksport", + "kanban": "Kanban", + "list": "Senarai", + "searchPlaceholder": "Cari tugasan...", + "searchAria": "Cari tugasan", + "clearSearchAria": "Kosongkan carian", + "foundTasks": "Dijumpai {count} {count, plural, one {tugasan} other {tugasan}}", + "filterByAllAria": "Tapis mengikut Semua", + "filterByStatusAria": "Tapis mengikut {status}" + }, + "status": { + "not-started": { + "label": "Belum Dimulakan", + "description": "Tugasan yang belum dimulakan" + }, + "ongoing": { + "label": "Sedang Berjalan", + "description": "Tugasan yang sedang dalam proses" + }, + "completed": { + "label": "Selesai", + "description": "Tugasan yang telah siap" + } + }, + "priorities": { + "high": "Tinggi", + "medium": "Sederhana", + "low": "Rendah" + }, + "form": { + "addNewTaskLabel": "Tambah tugasan baharu", + "multilinePlaceholder": "Tambah butiran tugasan... (Shift+Enter untuk baris baharu)", + "singlelinePlaceholder": "Apa yang perlu dilakukan? Tekan Enter untuk tambah", + "taskInputAria": "Input tugasan", + "projectPlaceholder": "Projek", + "selectProject": "Pilih Projek", + "errorSelectProject": "Sila pilih projek atau cipta yang baharu untuk menambah tugasan.", + "hintMultiline": "Shift+Enter untuk baris baharu, Enter untuk tambah", + "hintSingleline": "Enter untuk tambah", + "shortcutsAria": "Pintasan papan kekunci", + "shortcutsLabel": "Pintasan", + "shortcutAddTask": "Tambah tugasan", + "shortcutNewLine": "Baris baharu", + "shortcutClear": "Kosongkan", + "addButton": "Tambah" + }, + "list": { + "ariaLabel": "Senarai tugasan", + "loading": "Memuatkan tugasan anda...", + "emptyTitle": "Tiada tugasan dijumpai", + "emptyPrimary": "Mulakan dengan menambah tugasan pertama anda di atas! Tekan Enter untuk menambah tugasan dengan pantas.", + "emptySecondary": "Cuba laraskan penapis atau pertanyaan carian anda." + }, + "mobileDrawer": { + "addTaskSrOnly": "Tambah Tugasan", + "addNewTaskTitle": "Tambah Tugasan Baharu", + "addNewTaskDescription": "Cipta tugasan baharu untuk menjejaki kemajuan anda.", + "cancel": "Batal" + }, + "kanban": { + "ariaLabel": "Papan Kanban", + "loading": "Memuatkan tugasan anda...", + "dropHere": "Lepaskan tugasan di sini", + "noTasksYet": "Belum ada tugasan", + "emptyColumnAria": "Lajur kosong", + "columnAria": "Lajur {title} dengan {count} tugasan", + "countInColumnAria": "{count} tugasan dalam {title}" + }, + "taskItem": { + "confirmDelete": "Adakah anda pasti mahu memadam tugasan ini?", + "statusPlaceholder": "Status", + "changeStatusAria": "Tukar status tugasan", + "taskOptionsAria": "Pilihan tugasan", + "editTask": "Edit Tugasan", + "copyTask": "Salin Tugasan", + "copied": "Disalin!", + "deleteTask": "Padam Tugasan", + "statusHeading": "Status", + "editTaskAria": "Edit tugasan" + }, + "editDialog": { + "title": "Edit Tugasan", + "description": "Kemas kini butiran tugasan, tambah subtugasan, tetapkan keutamaan dan lain-lain.", + "taskTitleLabel": "Tajuk Tugasan *", + "taskTitlePlaceholder": "Apa yang perlu dilakukan?", + "descriptionLabel": "Penerangan", + "descriptionPlaceholder": "Tambah butiran lanjut tentang tugasan ini...", + "statusLabel": "Status", + "priorityLabel": "Keutamaan", + "dueDateLabel": "Tarikh Akhir", + "pickDate": "Pilih tarikh", + "clearDate": "Kosongkan Tarikh", + "projectLabel": "Projek", + "selectProjectPlaceholder": "Pilih projek", + "noProject": "Tiada Projek", + "timeEstimateLabel": "Anggaran Masa (minit)", + "timeEstimatePlaceholder": "Berapa lama ini akan mengambil masa?", + "tagsLabel": "Tag", + "customTagNamePlaceholder": "Nama tag tersuai", + "subtasksLabel": "Subtugasan", + "addSubtaskPlaceholder": "Tambah subtugasan...", + "cancel": "Batal", + "saving": "Menyimpan...", + "saveChanges": "Simpan Perubahan" + }, + "export": { + "title": "Eksport Tugasan", + "description": "Eksport tugasan anda ke format JSON, CSV, atau Excel.", + "projectLabel": "Projek", + "statusLabel": "Status", + "tagLabel": "Tag", + "allProjects": "Semua Projek", + "allStatuses": "Semua Status", + "allTags": "Semua Tag", + "exportAsJsonTitle": "Eksport sebagai JSON", + "exportAsJsonDescription": "Eksport data tugasan termasuk subtugasan, tag, dan metadata.", + "exportJson": "Eksport JSON", + "exportAsCsvExcelTitle": "Eksport sebagai CSV / Excel", + "exportAsCsvExcelDescription": "Eksport tugasan untuk aplikasi hamparan.", + "exportCsv": "Eksport CSV", + "exportExcel": "Eksport Excel", + "close": "Tutup", + "toastNoTasks": "Tiada tugasan untuk dieksport dengan penapis semasa", + "toastExportedJson": "Dieksport {count} tugasan ke JSON!", + "toastExportedCsv": "Dieksport {count} tugasan ke CSV!", + "toastExportedExcel": "Dieksport {count} tugasan ke Excel!", + "toastFailedJson": "Gagal mengeksport ke JSON", + "toastFailedCsv": "Gagal mengeksport ke CSV", + "toastFailedExcel": "Gagal mengeksport ke Excel", + "unknownProject": "Projek Tidak Diketahui" + }, + "ack": { + "taskAddedTitle": "Tugasan berjaya ditambah", + "taskAddedFailedTitle": "Gagal menambah tugasan", + "taskUpdatedTitle": "Tugasan berjaya dikemas kini", + "taskUpdatedFailedTitle": "Gagal mengemas kini tugasan", + "taskStatusUpdateFailedTitle": "Gagal mengemas kini status tugasan", + "taskMovedTitle": "Tugasan dipindahkan ke {status}", + "tryAgain": "Sila cuba lagi.", + "taskDeletedTitle": "Tugasan \"{text}\" dipadam", + "undo": "Buat Asal", + "taskRestoredTitle": "Tugasan dipulihkan", + "taskRestoredSuccessTitle": "Tugasan berjaya dipulihkan", + "taskRestoreFailedTitle": "Gagal memulihkan tugasan. Sila cuba lagi.", + "taskDeleteFailedTitle": "Gagal memadam tugasan. Sila cuba lagi.", + "tasksImportedTitle": "Berjaya mengimport {count} {count, plural, one {tugasan} other {tugasan}}!", + "tasksImportFailedTitle": "Gagal mengimport tugasan. Sila cuba lagi.", + "unknownProject": "Projek Tidak Diketahui", + "projectCreatedTitle": "Projek berjaya dicipta", + "projectCreateFailedTitle": "Gagal mencipta projek", + "projectUpdatedTitle": "Projek berjaya dikemas kini", + "projectUpdateFailedTitle": "Gagal mengemas kini projek", + "projectDeletedTitle": "Projek berjaya dipadam", + "projectDeleteFailedTitle": "Gagal memadam projek" + } + }, + "Notes": { + "page": { + "loading": "Memuatkan..." + }, + "layout": { + "mobileNavTitle": "Navigasi Nota", + "openMenuAria": "Buka menu nota" + }, + "sidebar": { + "title": "Nota", + "createNoteAria": "Cipta nota", + "searchPlaceholder": "Cari nota...", + "loading": "Memuatkan...", + "noNotesFound": "Tiada nota dijumpai.", + "noNotesYet": "Belum ada nota. Klik + untuk mencipta satu.", + "inParent": "dalam {parentTitle}", + "untitled": "Tanpa Tajuk", + "addSubPage": "Tambah sub-halaman", + "delete": "Padam", + "deleteConfirmTitle": "Padam nota?", + "deleteConfirmDescription": "Adakah anda pasti mahu memadam \"{title}\"? Tindakan ini tidak boleh dibuat asal.", + "cancel": "Batal", + "confirmDelete": "Padam", + "noteOptionsAria": "Pilihan nota" + }, + "editor": { + "emptyState": "Pilih nota atau cipta yang baharu untuk bermula.", + "titlePlaceholder": "Tanpa Tajuk", + "saving": "Menyimpan…", + "ready": "Sedia", + "saved": "Disimpan {time}", + "stats": "{words} perkataan · {read} min bacaan", + "template": "Templat", + "applyTemplate": "Guna templat", + "exportMdLabel": ".md", + "exportMd": "Eksport sebagai Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Eksport sebagai HTML", + "focusMode": "Mod fokus", + "exitFocusMode": "Keluar mod fokus", + "save": "Simpan", + "addTag": "Tambah tag…", + "removeTag": "Buang tag {tag}", + "loading": "Memuatkan…", + "searchHint": "untuk mencari", + "focusHint": "mod fokus", + "notePath": "Laluan nota" + }, + "context": { + "authRequiredError": "Pengguna tidak disahkan", + "defaultTitle": "Tanpa Tajuk", + "cloudSyncImageError": "Hidupkan Penyegerakan Awan untuk menyimpan imej nota. Nota teks kekal setempat." + } + }, + "PasswordManager": { + "page": { + "loading": "Memuatkan...", + "title": "Pengurus Kata Laluan", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Gagal menyahsulit kata laluan", + "toastLoadFailed": "Gagal memuatkan kata laluan", + "toastUnlockedAuto": "Peti besi dibuka secara automatik", + "toastAutoUnlockFailed": "Buka kunci automatik gagal. Sila buka kunci secara manual.", + "toastConnectFailed": "Gagal menyambung ke pelayan", + "toastAutoUnlockWarning": "Peti besi dibuka, tetapi buka kunci automatik mungkin tidak berfungsi semasa muat semula", + "errorIncorrectPassword": "Kata laluan induk tidak betul", + "errorUnlockFailed": "Gagal membuka kunci peti besi", + "errorPasswordsMismatch": "Kata laluan tidak sepadan", + "errorPasswordTooShort": "Kata laluan mestilah sekurang-kurangnya 8 aksara", + "errorSetupFailed": "Gagal menyediakan peti besi", + "errorConnectFailed": "Gagal menyambung ke pelayan.", + "mobileTitleCreate": "Cipta peti besi anda", + "mobileTitleWelcome": "Selamat kembali", + "mobileSubtitleCreate": "Tetapkan kata laluan induk untuk melindungi kelayakan anda", + "mobileSubtitleUnlock": "Masukkan kata laluan induk anda untuk membuka kunci", + "placeholderMasterPassword": "Kata laluan induk", + "placeholderConfirmPassword": "Sahkan kata laluan", + "labelMasterPassword": "Kata laluan induk", + "labelConfirmPassword": "Sahkan kata laluan", + "placeholderEnterMaster": "Masukkan kata laluan induk", + "placeholderConfirmMaster": "Sahkan kata laluan induk", + "buttonCreateVault": "Cipta peti besi", + "buttonUnlock": "Buka kunci", + "buttonUnlockVault": "Buka kunci peti besi", + "footerCreate": "Kata laluan anda tidak pernah disimpan dan tidak boleh dipulihkan", + "footerUnlock": "Dijamin dengan penyulitan hujung-ke-hujung", + "desktopTitleSetup": "Sediakan peti besi kata laluan", + "desktopTitleUnlock": "Buka kunci peti besi", + "desktopDescriptionSetup": "Cipta kata laluan induk untuk mengamankan kelayakan anda. Kata laluan ini tidak pernah disimpan dan tidak boleh dipulihkan.", + "desktopDescriptionUnlock": "Masukkan kata laluan induk anda untuk mengakses kelayakan yang dilindungi." + }, + "list": { + "loading": "Memuatkan kata laluan anda...", + "emptyTitle": "Peti besi kosong", + "emptyHintMobile": "Ketik butang + untuk menambah kata laluan pertama anda", + "emptyHintDesktop": "Anda belum menyimpan sebarang kata laluan. Klik butang \"Tambah kata laluan\" di atas untuk bermula.", + "addPassword": "Tambah kata laluan", + "searchPlaceholderMobile": "Cari peti besi anda...", + "searchPlaceholderDesktop": "Cari peti besi...", + "importExport": "Import / eksport", + "lockVault": "Kunci peti besi", + "allPasswordsTitle": "Semua kata laluan", + "passwordCount": "{count, plural, one {# kata laluan} other {# kata laluan}}", + "vaultHealthDrawerTitle": "Kesihatan peti besi", + "noResults": "Tiada kata laluan dijumpai sepadan dengan \"{query}\"", + "tableService": "Perkhidmatan", + "tableUsername": "Nama Pengguna", + "tablePassword": "Kata Laluan", + "tableUrl": "URL", + "tableActions": "Tindakan", + "gridViewAria": "Paparan grid", + "listViewAria": "Paparan senarai", + "passwordStrengthTitle": "Kekuatan kata laluan", + "deleteDialogTitle": "Padam kata laluan", + "deleteDialogDescription": "Adakah anda pasti mahu memadam kata laluan ini? Tindakan ini tidak boleh dibuat asal.", + "cancel": "Batal", + "delete": "Padam", + "edit": "Edit" + }, + "toasts": { + "copiedPassword": "Kata laluan disalin ke papan klip", + "copiedUsername": "Nama pengguna disalin ke papan klip", + "passwordDeleted": "Kata laluan dipadam", + "deleteFailed": "Gagal memadam kata laluan", + "vaultLocked": "Peti besi dikunci", + "importedCount": "Berjaya mengimport {count, plural, one {# kata laluan} other {# kata laluan}}" + }, + "form": { + "addTitle": "Tambah kata laluan baharu", + "editTitle": "Edit kata laluan", + "serviceName": "Nama perkhidmatan", + "usernameEmail": "Nama pengguna / e-mel", + "password": "Kata laluan", + "urlOptional": "URL laman web (pilihan)", + "notesOptional": "Nota (pilihan)", + "tags": "Tag", + "placeholderService": "cth. Netflix", + "placeholderUsername": "pengguna@contoh.com", + "placeholderPassword": "Masukkan atau jana kata laluan", + "placeholderUrl": "https://...", + "placeholderNotes": "Butiran tambahan...", + "placeholderTags": "Tambah tag (tekan Enter atau koma)", + "strength": "Kekuatan", + "strengthWeak": "Lemah", + "strengthMedium": "Sederhana", + "strengthStrong": "Kuat", + "advancedGenerator": "Penjana lanjutan", + "cancel": "Batal", + "saving": "Menyimpan...", + "savePassword": "Simpan kata laluan", + "updating": "Mengemas kini...", + "updatePassword": "Kemas kini kata laluan", + "addPasswordSrOnly": "Tambah kata laluan", + "triggerAdd": "Tambah kata laluan", + "toastSaved": "Kata laluan berjaya disimpan", + "toastSaveFailed": "Gagal menyimpan kata laluan", + "toastUpdated": "Kata laluan berjaya dikemas kini", + "toastUpdateFailed": "Gagal mengemas kini kata laluan" + }, + "security": { + "vaultHealth": "Kesihatan peti besi", + "statusLabel": "Status", + "totalPasswords": "Jumlah kata laluan", + "activeEntries": "Entri aktif dalam peti besi anda", + "protected": "Dilindungi", + "weakPasswords": "Kata laluan lemah", + "needingImprovement": "Kata laluan yang perlu diperbaiki", + "allGood": "Semua baik", + "reusedPasswords": "Kata laluan berulang", + "usedMultipleTimes": "Kata laluan yang digunakan berulang kali", + "unique": "Unik", + "total": "Jumlah", + "weak": "Lemah", + "reused": "Berulang", + "totalCountLabel": "{count, plural, one {# kata laluan} other {# kata laluan}}", + "weakCountLabel": "{count} lemah", + "allSecure": "Semua selamat", + "scoreExcellent": "Cemerlang", + "scoreGood": "Baik", + "scoreFair": "Sederhana", + "scoreCritical": "Kritikal" + }, + "importExport": { + "title": "Import / eksport peti besi", + "description": "Urus data kata laluan anda. Anda boleh mengeksport peti besi ke JSON atau mengimport daripada fail JSON.", + "tabExport": "Eksport", + "tabImport": "Import", + "warningTitle": "Amaran", + "warningBody": "Fail yang dieksport mengandungi kata laluan anda dalam teks biasa. Simpan fail ini dengan selamat dan padam selepas digunakan.", + "exportBlurb": "Muat turun seluruh peti besi anda sebagai fail JSON. Ini termasuk semua perkhidmatan, nama pengguna, kata laluan, dan nota.", + "downloadJson": "Muat turun eksport JSON", + "pasteLabel": "Tampal data JSON", + "pastePlaceholder": "[{\"service\": \"Contoh\", \"username\": \"pengguna\", \"password\": \"123\", ...}]", + "pasteHint": "Tampal kandungan fail JSON yang telah dieksport sebelum ini di sini.", + "importing": "Mengimport...", + "importPasswords": "Import kata laluan", + "trigger": "Import / eksport", + "toastExported": "Peti besi berjaya dieksport", + "toastExportFailed": "Gagal mengeksport peti besi", + "toastImportFailed": "Gagal mengimport: format JSON tidak sah" + }, + "card": { + "copyUsername": "Salin nama pengguna", + "openUrl": "Buka URL", + "strength": "Kekuatan" + }, + "swipeable": { + "copy": "Salin", + "delete": "Padam", + "copyPassword": "Salin kata laluan", + "copyUsername": "Salin nama pengguna", + "edit": "Edit", + "deleteAction": "Padam" + }, + "floatingMenu": { + "lockVault": "Kunci peti besi", + "importExport": "Import / eksport", + "addPassword": "Tambah kata laluan" + }, + "generator": { + "tabPassword": "Kata Laluan", + "tabPassphrase": "Frasa Laluan", + "strength": "Kekuatan", + "lengthLabel": "Panjang: {length} aksara", + "lengthBadge": "{length} aksara", + "characterSets": "Set aksara", + "uppercase": "Huruf besar", + "lowercase": "Huruf kecil", + "numbers": "Nombor", + "symbols": "Simbol", + "wordCountLabel": "Bilangan perkataan: {count}", + "wordCountBadge": "{count, plural, one {# perkataan} other {# perkataan}}", + "separator": "Pemisah", + "capitalization": "Huruf besar", + "includeNumber": "Sertakan nombor" + } + }, + "EnvironmentManager": { + "subtitle": "Pemboleh ubah persekitaran disulitkan mengikut projek dan persekitaran.", + "page": { + "loading": "Memuatkan…", + "title": "Pengurus persekitaran", + "loadFailed": "Gagal memuatkan set persekitaran" + }, + "list": { + "title": "Persekitaran", + "loading": "Memuatkan set anda…", + "emptyTitle": "Tiada set persekitaran lagi", + "emptyHint": "Cipta set untuk setiap projek dan persekitaran (cth. kunci API pengeluaran). Semua disulitkan dalam pelayar sebelum dimuat naik.", + "searchPlaceholder": "Cari projek, persekitaran, teg, nota…", + "tagFilterPlaceholder": "Tapis mengikut teg", + "variableCount": "{count, plural, other {# pemboleh ubah}}", + "updatedAgo": "Dikemas kini {time}", + "copyDotEnv": "Salin .env", + "edit": "Edit", + "delete": "Padam", + "cancel": "Batal", + "confirmDelete": "Padam", + "deleteDialogTitle": "Padam set persekitaran?", + "deleteDialogDescription": "Ini memadam blob disulitkan daripada akaun anda. Tidak boleh dibuat asal.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env disalin ke papan keratan", + "deleted": "Set persekitaran dipadam", + "deleteFailed": "Gagal memadam" + }, + "form": { + "addTitle": "Set persekitaran baharu", + "editTitle": "Edit set persekitaran", + "triggerAdd": "Tambah set persekitaran", + "addSrOnly": "Tambah set persekitaran", + "project": "Projek", + "environment": "Persekitaran", + "placeholderProject": "cth. billing-api", + "placeholderEnvironment": "cth. production", + "pasteEnvLabel": "Tampal .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Gabung baris yang ditampal", + "variables": "Pemboleh ubah", + "addRow": "Tambah baris", + "keyPlaceholder": "KUNCI", + "valuePlaceholder": "nilai", + "notesOptional": "Nota (pilihan)", + "placeholderNotes": "Dasar putaran, pemilik, pautan…", + "tags": "Teg", + "placeholderTags": "Tambah teg (Enter atau koma)", + "cancel": "Batal", + "save": "Simpan", + "saving": "Menyimpan…", + "update": "Kemas kini", + "updating": "Mengemas kini…", + "toastSaved": "Set persekitaran disimpan", + "toastSaveFailed": "Gagal menyimpan", + "toastUpdated": "Set persekitaran dikemas kini", + "toastUpdateFailed": "Gagal mengemas kini", + "toastPasteEmpty": "Tiada baris KUNCI=nilai", + "toastPasteApplied": "{count} pemboleh ubah digabung daripada tampalan", + "toastProjectEnvRequired": "Projek dan persekitaran diperlukan", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Memuatkan..." + }, + "manager": { + "foldersHeading": "Folder", + "tagsHeading": "Tag", + "tagsMore": "+{count} lagi", + "tagsShowLess": "Show less", + "searchPlaceholder": "Cari penanda halaman anda...", + "import": "Import", + "export": "Eksport", + "exportAsHtml": "Eksport sebagai HTML", + "exportAsJson": "Eksport sebagai JSON", + "addBookmark": "Tambah Penanda Halaman", + "matchingQuery": "sepadan \"{query}\"", + "bookmarkCount": "{count, plural, one {# penanda halaman} other {# penanda halaman}}", + "allBookmarks": "Semua Penanda Halaman", + "uncategorized": "Tidak Dikategorikan", + "unknownFolder": "Tidak Diketahui" + }, + "card": { + "urlCopied": "URL disalin ke papan klip", + "bookmarkDeleted": "Penanda halaman dipadam", + "edit": "Edit", + "delete": "Padam", + "copyUrl": "Salin URL", + "deleteConfirmTitle": "Padam penanda halaman?", + "deleteConfirmDescription": "Ini akan memadam \"{title}\" secara kekal.", + "cancel": "Batal" + }, + "grid": { + "emptyTitle": "Belum ada penanda halaman", + "emptyDescription": "Tambah penanda halaman pertama anda atau import dari pelayar anda untuk bermula." + }, + "folderTree": { + "allBookmarks": "Semua Penanda Halaman", + "addSubfolder": "Tambah Subfolder", + "rename": "Namakan Semula", + "deleteFolderTitle": "Padam folder \"{name}\"?", + "deleteFolderDescription": "Ini akan memadam folder dan semua subfoldernya. Penanda halaman dalam folder ini akan dipindahkan ke Tidak Dikategorikan." + }, + "import": { + "title": "Import Penanda Halaman", + "description": "Import penanda halaman dari fail eksport pelayar anda.", + "parseError": "Gagal menghurai fail penanda halaman. Sila pastikan ia adalah eksport penanda halaman yang sah.", + "successToast": "Diimport {bookmarks} penanda halaman dan {folders} folder", + "errorToast": "Gagal mengimport penanda halaman", + "chromeInstruction": "Menu → Penanda Halaman → Pengurus Penanda Halaman → ⋮ → Eksport Penanda Halaman", + "firefoxInstruction": "Menu → Penanda Halaman → Urus Penanda Halaman → Import dan Sandaran → Eksport", + "safariInstruction": "Fail → Eksport Penanda Halaman...", + "edgeInstruction": "Menu → Kegemaran → ⋯ → Eksport Kegemaran", + "dropZoneTitle": "Lepaskan fail penanda halaman anda di sini", + "dropZoneHint": "atau klik untuk melayari (HTML atau JSON)", + "selectedFileHint": "Klik untuk memilih fail lain", + "errorTitle": "Ralat", + "previewTitle": "Pratonton Import", + "previewBookmarks": "{count, plural, one {# penanda halaman} other {# penanda halaman}}", + "previewFolders": "{count, plural, one {# folder} other {# folder}}", + "importModeLabel": "Mod Import", + "modeMerge": "Gabungkan dengan penanda halaman sedia ada", + "modeReplace": "Gantikan semua penanda halaman sedia ada", + "cancel": "Batal", + "importButton": "Import", + "importButtonWithCount": "Import {count, plural, one {# penanda halaman} other {# penanda halaman}}" + }, + "addBookmark": { + "titleAdd": "Tambah Penanda Halaman", + "titleEdit": "Edit Penanda Halaman", + "urlLabel": "URL", + "urlPlaceholder": "https://contoh.com", + "urlInvalid": "Sila masukkan URL yang sah", + "titleLabel": "Tajuk", + "titlePlaceholder": "Tajuk penanda halaman", + "titleRequired": "Sila masukkan tajuk", + "descriptionLabel": "Penerangan (pilihan)", + "descriptionPlaceholder": "Tambah nota tentang penanda halaman ini", + "folderLabel": "Folder", + "folderPlaceholder": "Pilih folder", + "noFolder": "Tiada folder", + "tagsLabel": "Tag", + "tagsPlaceholder": "Tambah tag (tekan Enter)", + "toastUpdated": "Penanda halaman dikemas kini", + "toastAdded": "Penanda halaman ditambah", + "cancel": "Batal", + "submitAdd": "Tambah Penanda Halaman", + "submitUpdate": "Kemas Kini Penanda Halaman" + }, + "addFolder": { + "title": "Cipta Folder", + "nameLabel": "Nama Folder", + "namePlaceholder": "Folder Saya", + "nameRequired": "Sila masukkan nama folder", + "parentLabel": "Folder Induk", + "parentPlaceholder": "Pilih folder induk", + "rootParent": "Akar (Tiada induk)", + "toastCreated": "Folder dicipta", + "cancel": "Batal", + "submit": "Cipta Folder" + }, + "editFolder": { + "title": "Namakan Semula Folder", + "nameLabel": "Nama Folder", + "namePlaceholder": "Nama folder", + "nameRequired": "Sila masukkan nama folder", + "toastRenamed": "Folder dinamakan semula", + "cancel": "Batal", + "save": "Simpan" + } + }, + "EmailValidator": { + "header": { + "title": "Pengesah E-mel", + "description": "Sahkan dan pengesahan alamat e-mel untuk sintaks, domain, dan kebolehhantaran." + }, + "tabs": { + "single": "Pengesahan Tunggal", + "bulk": "Muat Naik Pukal" + }, + "single": { + "cardTitle": "Pengesahan E-mel Tunggal", + "cardDescription": "Masukkan alamat e-mel untuk menyemak kebolehhantaran dan reputasinya.", + "placeholder": "Masukkan alamat e-mel...", + "clearAria": "Kosongkan e-mel", + "validate": "Sahkan", + "validating": "Mengesahkan...", + "formatError": "Sila masukkan alamat e-mel yang sah" + }, + "toasts": { + "errorTitle": "Ralat", + "enterEmail": "Sila masukkan alamat e-mel", + "invalidFormatTitle": "Format E-mel Tidak Sah", + "invalidFormatDescription": "Sila masukkan alamat e-mel yang sah", + "validateFailedDescription": "Ralat berlaku semasa mengesahkan e-mel", + "noEmailsTitle": "Tiada E-mel Dijumpai", + "noEmailsDescription": "Sila pastikan Excel anda mempunyai lajur 'email'.", + "fileTooLargeTitle": "Fail Terlalu Besar", + "fileTooLargeDescription": "Dijumpai {total} e-mel. Hanya 4000 pertama yang akan disahkan.", + "successTitle": "Berjaya", + "successValidated": "Berjaya mengesahkan {count} e-mel.", + "batchFailedTitle": "Pengesahan Pukal Gagal", + "batchFailedDescription": "Ralat berlaku semasa memproses pengesahan pukal.", + "noExportTitle": "Tiada Data untuk Dieksport", + "noExportValidOnly": "Tiada e-mel sah dijumpai untuk dieksport.", + "noExportAll": "Tiada keputusan untuk dieksport.", + "exportSuccessTitle": "Eksport Berjaya", + "exportSuccessExcelAll": "Dieksport {count} e-mel ke Excel.", + "exportSuccessExcelValid": "Dieksport {count} e-mel sah ke Excel.", + "exportSuccessCsvAll": "Dieksport {count} e-mel ke CSV.", + "exportSuccessCsvValid": "Dieksport {count} e-mel sah ke CSV." + }, + "disposableAlert": { + "title": "E-mel Pakai Buang Dikesan", + "description": "Alamat e-mel ini kepunyaan pembekal e-mel pakai buang. Ia mungkin sementara atau tidak selamat." + }, + "score": { + "reliabilityTitle": "Skor Kebolehpercayaan", + "scoreLabel": "Skor" + }, + "details": { + "sectionTitle": "Butiran Pengesahan" + }, + "validation": { + "syntaxLabel": "Sintaks Sah", + "syntaxTooltip": "Menyemak sama ada alamat e-mel mengikut format yang betul (cth., pengguna@domain.com)", + "domainLabel": "Domain Wujud", + "domainTooltip": "Mengesahkan bahawa nama domain dalam alamat e-mel benar-benar wujud", + "mxLabel": "Rekod MX Dijumpai", + "mxTooltip": "Menyemak sama ada domain mempunyai rekod pertukaran mel (MX) yang dikonfigurasi untuk menerima e-mel", + "mailboxLabel": "Peti Mel Wujud", + "mailboxTooltip": "Mengesahkan bahawa peti mel/akaun tertentu wujud pada pelayan mel", + "disposableLabel": "E-mel Pakai Buang", + "disposableTooltip": "Menunjukkan sama ada e-mel ini kepunyaan perkhidmatan e-mel sementara/pakai buang (mungkin berisiko)", + "roleBasedLabel": "E-mel Berasaskan Peranan", + "roleBasedTooltip": "Mengenal pasti sama ada ini adalah e-mel berasaskan peranan (cth., sokongan@, info@) dan bukan alamat peribadi" + }, + "copy": { + "emailCopied": "E-mel disalin!", + "copyTooltip": "Salin e-mel" + }, + "bulk": { + "dropHere": "Lepaskan fail anda di sini", + "dragHere": "Seret dan lepaskan fail anda di sini", + "uploadHint": "Muat naik fail .xlsx, .xls, atau .csv yang mengandungi lajur 'email' untuk mengesahkan sehingga 4000 e-mel.", + "filePrefix": "Fail:", + "selectFile": "Pilih Fail", + "downloadTemplate": "Muat Turun Templat" + }, + "bulkLoading": { + "title": "Mengesahkan E-mel...", + "batchProgress": "Memproses kumpulan {current} daripada {total}..." + }, + "stats": { + "totalProcessed": "Jumlah Diproses", + "valid": "Sah", + "risky": "Berisiko", + "invalid": "Tidak Sah" + }, + "results": { + "detailedTitle": "Keputusan Terperinci", + "searchPlaceholder": "Cari e-mel...", + "newVerification": "Pengesahan Baharu", + "exportAll": "Eksport Semua", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Eksport Sah ({count})" + }, + "showingResults": "Menunjukkan {shown} daripada {total} keputusan", + "table": { + "email": "E-mel", + "status": "Status", + "score": "Skor", + "checks": "Semakan" + }, + "noSearchResults": "Tiada keputusan dijumpai sepadan dengan \"{query}\"", + "checksTooltip": { + "syntax": "Sintaks", + "domain": "Domain", + "mx": "MX", + "mailbox": "Peti Mel" + }, + "exportColumns": { + "email": "E-mel", + "status": "Status", + "score": "Skor", + "syntax": "Sintaks", + "domain": "Domain", + "mx": "MX", + "mailbox": "Peti Mel", + "disposable": "Pakai Buang", + "roleBased": "Berasaskan Peranan", + "valid": "Sah", + "invalid": "Tidak Sah", + "yes": "Ya", + "no": "Tidak" + }, + "sheets": { + "validEmails": "E-mel Sah", + "results": "Keputusan", + "template": "Templat" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "Penjelajah NoSQL", + "heroSubtitle": "Sambung ke pangkalan data anda untuk mengurus koleksi, laksanakan pertanyaan, dan visualisasikan data anda.", + "featureMultiTitle": "Berbilang Sambungan", + "featureMultiDesc": "Urus berbilang sambungan pangkalan data secara serentak.", + "featureMongoTitle": "Sokongan MongoDB", + "featureMongoDesc": "Sokongan asli untuk MongoDB dengan penapisan lanjutan.", + "featureQueryTitle": "Pertanyaan Pintar", + "featureQueryDesc": "Pembina pertanyaan yang berkuasa dengan penyorotan sintaks.", + "connectCta": "Sambung ke MongoDB", + "footerNote": "Pada masa ini menyokong {db}. Lebih banyak pangkalan data akan datang.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Pilih Koleksi", + "selectCollectionDesc": "Pilih koleksi dari bar sisi untuk melihat dokumen dan mula meneroka data anda.", + "dialogAddConnection": "Tambah Sambungan", + "toastConnectionAdded": "Sambungan ditambah", + "toastFetchFailed": "Gagal mengambil dokumen untuk {collection}", + "toastDocDeleted": "Dokumen dipadam", + "deleteDocTitle": "Adakah anda benar-benar pasti?", + "deleteDocDescription": "Tindakan ini tidak boleh dibuat asal. Ini akan memadam dokumen secara kekal dari koleksi anda.", + "closeAllTitle": "Tutup semua tab?", + "closeAllDescription": "Adakah anda pasti mahu menutup semua tab? Sebarang perubahan yang tidak disimpan dalam penapis pertanyaan akan hilang.", + "closeAllConfirm": "Tutup Semua", + "cancel": "Batal", + "delete": "Padam" + }, + "connection": { + "titleEdit": "Edit Sambungan", + "titleConnect": "Sambung ke MongoDB", + "subtitleEdit": "Kemas kini butiran sambungan anda di bawah.", + "subtitleConnect": "Masukkan rentetan sambungan MongoDB anda untuk mula meneroka data anda.", + "labelName": "Nama", + "placeholderName": "cth. DB Pengeluaran", + "labelConnectionString": "Rentetan Sambungan", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://namapengguna:katalaluan@hos:port/pangkalandata", + "testConnection": "Uji Sambungan", + "connecting": "Menyambung...", + "updateConnect": "Kemas Kini & Sambung", + "connect": "Sambung", + "cancelEdit": "Batal Edit", + "savedConnections": "Sambungan Tersimpan", + "emptySaved": "Belum ada sambungan tersimpan.", + "lastUsed": "Terakhir digunakan {time}", + "toastTestOk": "Sambungan berjaya!", + "toastTestFail": "Sambungan gagal: {message}", + "toastUpdated": "Sambungan dikemas kini", + "confirmDelete": "Adakah anda pasti mahu memadam sambungan tersimpan ini?", + "toastDeletedConn": "Sambungan dipadam", + "toastDeleteFail": "Gagal memadam sambungan", + "copyStringTitle": "Salin rentetan sambungan", + "toastStringCopied": "Rentetan sambungan disalin!", + "editTitle": "Edit", + "deleteTitle": "Padam" + }, + "sidebar": { + "explorer": "Penjelajah", + "add": "Tambah", + "tooltipAdd": "Tambah sambungan baharu", + "tooltipRefresh": "Segar semula sambungan", + "searchPlaceholder": "Cari...", + "loadingConnections": "Memuatkan sambungan...", + "noConnectionsFound": "Tiada sambungan dijumpai", + "toastLoadFail": "Gagal memuatkan sambungan", + "toastConnectFail": "Gagal menyambung ke {name}", + "toastCollectionsFail": "Gagal mengambil koleksi untuk {name}", + "toastRenamed": "Sambungan dinamakan semula", + "toastRenameFail": "Gagal menamakan semula sambungan", + "confirmDeleteConnection": "Adakah anda pasti mahu memadam sambungan \"{name}\"?", + "toastDeleted": "Sambungan dipadam", + "toastDeleteConnFail": "Gagal memadam sambungan", + "confirmDropDb": "Adakah anda pasti mahu menggugurkan pangkalan data \"{name}\"? Tindakan ini tidak boleh dibuat asal.", + "toastDbDropped": "Pangkalan data {name} digugurkan", + "confirmDropCollection": "Adakah anda pasti mahu menggugurkan koleksi \"{name}\"? Tindakan ini tidak boleh dibuat asal.", + "toastCollectionDropped": "Koleksi {name} digugurkan", + "toastCollectionRenamed": "Koleksi dinamakan semula kepada {name}", + "toastDatabaseRenamed": "Pangkalan data dinamakan semula kepada {name}", + "connecting": "Menyambung...", + "connectionFailed": "Sambungan gagal", + "noDatabases": "Tiada pangkalan data", + "loading": "Memuatkan...", + "noCollections": "Tiada koleksi", + "renameCollectionTitle": "Namakan Semula Koleksi", + "renameDatabaseTitle": "Namakan Semula Pangkalan Data", + "placeholderNewCollection": "Nama koleksi baharu", + "placeholderNewDatabase": "Nama pangkalan data baharu", + "rename": "Namakan Semula", + "refresh": "Segar Semula", + "copyDbName": "Salin Nama DB", + "toastDbNameCopied": "Nama pangkalan data disalin ke papan klip", + "dropDatabase": "Gugurkan Pangkalan Data", + "dropCollection": "Gugurkan Koleksi", + "menuRename": "Namakan Semula", + "menuCopyConnectionString": "Salin Rentetan Sambungan", + "menuDeleteConnection": "Padam Sambungan", + "toastSidebarStringCopied": "Rentetan sambungan disalin!", + "cancel": "Batal", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} dokumen", + "table": "Jadual", + "json": "JSON", + "tree": "Pokok", + "tooltipTable": "Paparan Jadual", + "tooltipJson": "Paparan JSON", + "tooltipTree": "Paparan Pokok", + "itemsPerPage": "Item setiap halaman", + "perPage": "{n} / halaman", + "prevPage": "Halaman Sebelumnya", + "nextPage": "Halaman Seterusnya", + "expandAll": "Kembangkan Semua", + "collapseAll": "Kuncupkan Semua", + "refreshData": "Segar Semula Data", + "insert": "Sisipkan", + "export": "Eksport", + "actions": "Tindakan", + "invalidJson": "Kandungan JSON tidak sah", + "copiedClipboard": "Disalin ke papan klip", + "docUpdated": "Dokumen berjaya dikemas kini", + "docInserted": "Dokumen berjaya disisipkan", + "invalidJsonShort": "JSON tidak sah", + "docCopied": "Dokumen disalin ke papan klip", + "openFullEditor": "Buka dalam editor penuh", + "cellArray": "Tatasusunan({length})", + "cellObject": "Objek", + "cellArrayBracket": "Tatasusunan[{length}]", + "emptyFilterTitle": "Tiada dokumen sepadan dengan pertanyaan anda", + "emptyFilterHint": "Cuba laraskan penapis anda atau kosongkan pertanyaan.", + "clearFilter": "Kosongkan Penapis", + "emptyCollectionTitle": "Koleksi ini kosong", + "emptyCollectionHint": "Sisipkan dokumen untuk bermula.", + "insertDocument": "Sisipkan Dokumen", + "formatJson": "Format JSON", + "compactJson": "JSON Padat", + "copyClipboard": "Salin ke Papan Klip", + "copyJson": "Salin JSON", + "duplicate": "Gandakan", + "editDoc": "Edit Dokumen", + "deleteDoc": "Padam Dokumen", + "documentLabel": "Dokumen {n}", + "editDialogTitle": "Edit Dokumen", + "insertDialogTitle": "Sisipkan Dokumen", + "viewValueTitle": "Lihat Nilai", + "saveChanges": "Simpan Perubahan", + "close": "Tutup", + "cancel": "Batal", + "insertButton": "Sisipkan", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Penapis aktif digunakan", + "filterActiveTooltip": "Penapis aktif", + "closeAll": "Tutup Semua" + }, + "query": { + "migrateHistory": "Sejarah pertanyaan dipindahkan ke awan", + "saveHistoryFail": "Gagal menyimpan sejarah pertanyaan", + "deleteQueryFail": "Gagal memadam pertanyaan", + "invalidJsonQuery": "Pertanyaan JSON tidak sah", + "placeholder": "cth. { \"status\": \"aktif\" }", + "clearQuery": "Kosongkan pertanyaan", + "openAdvanced": "Buka Editor Lanjutan", + "visualBuilder": "Pembina Pertanyaan Visual", + "filterRules": "Peraturan Penapis", + "addRule": "Tambah Peraturan", + "noFilters": "Tiada penapis digunakan. Tambah peraturan untuk menapis dokumen.", + "where": "DI MANA", + "and": "DAN", + "fieldPlaceholder": "Medan", + "opPlaceholder": "Op", + "typePlaceholder": "Jenis", + "opRegex": "Regex", + "opIn": "Dalam", + "opNin": "Tidak Dalam", + "opExists": "Wujud", + "typeAuto": "Auto", + "typeString": "Rentetan", + "typeNumber": "Nombor", + "typeBoolean": "Boolean", + "typeDate": "Tarikh", + "typeObjectId": "ObjectId", + "true": "Benar", + "false": "Palsu", + "valuePlaceholder": "Nilai", + "valueListPlaceholder": "nilai1, nilai2", + "clearFilters": "Kosongkan Penapis", + "applyFilters": "Gunakan Penapis", + "queryHistory": "Sejarah Pertanyaan", + "noSavedQueries": "Belum ada pertanyaan tersimpan", + "advancedEditorTitle": "Editor Pertanyaan Lanjutan", + "templatesPlaceholder": "Templat", + "templateEmptyAgg": "Pengagregatan Kosong", + "templateMatch": "$match (Penapis)", + "templateLookup": "$lookup (Cantum)", + "templateGroup": "$group (Kira/Jumlah)", + "format": "Format", + "formattedJson": "JSON Terformat", + "runQuery": "Jalankan Pertanyaan" + }, + "export": { + "selectFieldsError": "Sila pilih sekurang-kurangnya satu medan untuk dieksport", + "success": "Berjaya dieksport ke {filename}", + "fail": "Gagal mengeksport data", + "title": "Eksport Data", + "formatLabel": "Format Eksport", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Pilih Medan", + "selectAll": "Pilih Semua", + "fieldsSelected": "{count, plural, one {# medan dipilih} other {# medan dipilih}}", + "cancel": "Batal", + "export": "Eksport", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Jenis: {type}", + "itemSummary": "{count, plural, one {# item} other {# item}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Permintaan Baharu", + "importedRequest": "Permintaan Diimport", + "untitledRequest": "Permintaan Tanpa Tajuk" + }, + "toasts": { + "curlImported": "cURL berjaya diimport", + "curlParseFailed": "Gagal menghurai arahan cURL", + "invalidJsonBody": "Badan JSON tidak sah", + "requestFailed": "Permintaan gagal: {message}", + "curlPasted": "cURL ditampal dan dihurai dengan berjaya", + "responseCopied": "Respons disalin ke papan klip", + "codeCopied": "Kod disalin ke papan klip", + "copyFailed": "Gagal menyalin ke papan klip" + }, + "layout": { + "collections": "Koleksi", + "closeSidebar": "Tutup bar sisi", + "openSidebar": "Buka bar sisi", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Kaedah", + "urlPlaceholder": "https://api.contoh.com/v1/...", + "sending": "Menghantar...", + "send": "Hantar", + "invalidJsonBodyHelp": "Tidak boleh hantar: kandungan JSON tidak sah" + }, + "requestTabs": { + "params": "Parameter", + "headers": "Pengepala", + "body": "Badan", + "auth": "Pengesahan", + "bodyType": "Jenis badan", + "selectType": "Pilih jenis", + "bodyNone": "Tiada", + "bodyJson": "JSON", + "bodyText": "Teks", + "noBodyHint": "Tiada kandungan badan untuk permintaan ini", + "switchToJson": "Tukar ke badan JSON", + "authType": "Jenis pengesahan", + "noAuth": "Tiada pengesahan", + "bearerToken": "Token Bearer", + "basicAuth": "Pengesahan asas", + "apiKey": "Kunci API", + "labelToken": "Token", + "placeholderBearerToken": "Token Bearer", + "labelUsername": "Nama pengguna", + "placeholderUsername": "Nama pengguna", + "labelPassword": "Kata laluan", + "placeholderPassword": "Kata laluan", + "labelKey": "Kunci", + "placeholderApiKeyName": "Nama kunci (cth. X-Api-Key)", + "labelValue": "Nilai", + "placeholderValue": "Nilai", + "addTo": "Tambah ke", + "header": "Pengepala", + "queryParams": "Parameter pertanyaan", + "noAuthHint": "Permintaan ini tidak menggunakan pengesahan." + }, + "responsePanel": { + "emptyTitle": "Sedia untuk meneroka?", + "emptyHint": "Masukkan URL dan klik Hantar untuk melihat data respons di sini.", + "responsePreviewAlt": "Pratonton respons", + "previewUnavailable": "Pratonton tidak tersedia untuk jenis kandungan ini", + "status": "Status", + "time": "Masa", + "size": "Saiz", + "copyBody": "Salin badan", + "downloadResponse": "Muat turun respons", + "bodyTab": "Badan", + "previewTab": "Pratonton", + "headersTab": "Pengepala", + "binaryRawView": "Data binari tidak boleh dipaparkan dalam paparan mentah.", + "errorStatusLabel": "Ralat", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Tab baharu" + }, + "collectionsSidebar": { + "title": "Sumber API", + "newCollection": "Koleksi baharu", + "tabCollections": "Koleksi", + "tabHistory": "Sejarah", + "noCollectionsTitle": "Tiada koleksi", + "noCollectionsHint": "Cipta koleksi untuk mengatur permintaan API anda.", + "createCollection": "Cipta koleksi", + "newFolder": "Folder baharu", + "rename": "Namakan semula", + "delete": "Padam", + "emptyCollection": "Koleksi kosong", + "recentRequests": "Permintaan terkini", + "clearAll": "Kosongkan semua", + "noHistoryTitle": "Belum ada sejarah", + "noHistoryHint": "Hantar permintaan untuk melihatnya di sini.", + "dialogNewFolderTitle": "Folder baharu", + "dialogNewFolderDescription": "Cipta folder baharu untuk mengatur permintaan anda.", + "labelFolderName": "Nama folder", + "placeholderFolderName": "Folder saya", + "createFolder": "Cipta folder", + "dialogNewCollectionTitle": "Koleksi baharu", + "dialogNewCollectionDescription": "Cipta koleksi peringkat atas baharu.", + "labelCollectionName": "Nama koleksi", + "placeholderCollectionName": "Koleksi saya", + "dialogRenameCollectionTitle": "Namakan semula koleksi", + "dialogRenameCollectionDescription": "Masukkan nama baharu untuk koleksi.", + "placeholderRenameCollection": "Nama koleksi", + "renameAction": "Namakan semula", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Folder baharu", + "delete": "Padam", + "emptyFolder": "Folder kosong" + }, + "environmentManager": { + "noEnvironment": "Tiada persekitaran", + "manageDialogTitle": "Urus persekitaran", + "placeholderNewEnv": "Persekitaran baharu", + "variablesHeading": "Pembolehubah {name}", + "addVariable": "Tambah pembolehubah", + "columnVariable": "Pembolehubah", + "columnValue": "Nilai", + "placeholderKey": "Kunci", + "placeholderValue": "Nilai", + "selectEnvToEdit": "Pilih persekitaran untuk diedit" + }, + "codeGenerator": { + "triggerTitle": "Jana kod", + "dialogTitle": "Jana kod", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Salin", + "errorGenerating": "Ralat menjana kod" + }, + "keyValueEditor": { + "columnKey": "Kunci", + "columnValue": "Nilai", + "placeholderKey": "Kunci", + "placeholderValue": "Nilai", + "addItem": "Tambah item" + }, + "importCurl": { + "trigger": "Import cURL", + "dialogTitle": "Import cURL", + "dialogDescription": "Tampal arahan cURL anda di bawah untuk mengimportnya sebagai permintaan baharu.", + "placeholder": "curl -X POST https://api.contoh.com/data -d '...'", + "import": "Import" + }, + "saveRequest": { + "trigger": "Simpan", + "dialogTitle": "Simpan permintaan", + "dialogDescription": "Simpan permintaan ini ke folder koleksi.", + "labelName": "Nama permintaan", + "placeholderName": "Permintaan saya", + "labelFolder": "Folder", + "placeholderFolder": "Pilih folder", + "save": "Simpan" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Mod Edit", + "viewOnlyMode": "Mod Lihat Sahaja", + "insertComponent": "Sisipkan komponen", + "addUnorderedList": "Tambah senarai tidak bertertib", + "addOrderedList": "Tambah senarai bertertib", + "addTable": "Tambah jadual" + }, + "addBlock": "Tambah blok", + "media": { + "popoverTitle": "Tambah media", + "singleImage": "Imej Tunggal", + "singleImageDesc": "Muat naik satu imej", + "multipleImages": "Berbilang Imej", + "multipleImagesDesc": "Muat naik grid imej", + "video": "Video", + "videoDesc": "Muat naik fail video", + "uploadingAlt": "Memuat naik...", + "uploadFailedAlt": "Muat naik gagal" + }, + "export": { + "fabAria": "Eksport kod", + "title": "Eksport Kod", + "description": "Salin output HTML atau JSON kandungan editor anda", + "preview": "Pratonton", + "html": "HTML", + "json": "JSON", + "previewOptions": "Pilihan Pratonton", + "enhanceSpaces": "Tingkatkan Ruang", + "livePreview": "Pratonton langsung HTML yang dirender", + "htmlTailwind": "HTML dengan kelas CSS Tailwind", + "editorJson": "Keadaan editor sebagai JSON", + "copyHtml": "Salin HTML", + "copyJson": "Salin JSON", + "copied": "Disalin!" + }, + "template": { + "fabTitle": "Tukar Templat", + "title": "Pilih Templat", + "description": "Pilih templat untuk bermula dengan pantas menggunakan susun atur dan kandungan pra-bina", + "catAll": "Semua Templat", + "catProductivity": "Produktiviti", + "catCreative": "Kreatif", + "catBusiness": "Perniagaan", + "catPersonal": "Peribadi", + "noTemplates": "Tiada templat dijumpai dalam kategori ini", + "confirmTitle": "Gantikan Kandungan Sedia Ada?", + "confirmDescription": "Anda mempunyai kandungan sedia ada dalam editor. Menggunakan templat ini akan menggantikan semua kandungan semasa.", + "cannotUndo": "Tindakan ini tidak boleh dibuat asal.", + "cancel": "Batal", + "replaceContent": "Gantikan Kandungan" + }, + "command": { + "searchPlaceholder": "Cari arahan...", + "noResults": "Tiada arahan dijumpai.", + "turnInto": "Tukar kepada", + "h1": { + "label": "Tajuk 1", + "description": "Tajuk bahagian besar", + "keywords": "h1,tajuk,heading,besar" + }, + "h2": { + "label": "Tajuk 2", + "description": "Tajuk bahagian sederhana", + "keywords": "h2,tajuk,subtajuk" + }, + "h3": { + "label": "Tajuk 3", + "description": "Tajuk bahagian kecil", + "keywords": "h3,tajuk,subtajuk" + }, + "h4": { + "label": "Tajuk 4", + "description": "Tajuk bahagian sangat kecil", + "keywords": "h4,tajuk" + }, + "h5": { + "label": "Tajuk 5", + "description": "Tajuk lebih kecil", + "keywords": "h5,tajuk" + }, + "h6": { + "label": "Tajuk 6", + "description": "Tajuk terkecil", + "keywords": "h6,tajuk" + }, + "p": { + "label": "Perenggan", + "description": "Perenggan teks biasa", + "keywords": "p,perenggan,teks,biasa" + }, + "code": { + "label": "Blok Kod", + "description": "Coretan kod", + "keywords": "kod,blok kod,coretan,pre" + }, + "blockquote": { + "label": "Petikan", + "description": "Petikan blok", + "keywords": "petikan,blockquote,kutipan" + }, + "li": { + "label": "Senarai Berbulet", + "description": "Item senarai dengan bulet", + "keywords": "senarai,bulet,tidak bertertib,ul,li" + }, + "ol": { + "label": "Senarai Bernombor", + "description": "Item senarai bernombor", + "keywords": "senarai,bernombor,bertertib,ol,li" + }, + "img": { + "label": "Imej", + "description": "Muat naik atau benamkan imej", + "keywords": "imej,img,gambar,foto,muat naik" + }, + "video": { + "label": "Video", + "description": "Muat naik atau benamkan video", + "keywords": "video,vid,filem,mp4,muat naik" + }, + "table": { + "label": "Jadual", + "description": "Cipta jadual", + "keywords": "jadual,grid,baris,lajur,sel" + } + }, + "toasts": { + "tableImported": "Jadual Diimport", + "tableImportedDesc": "Jadual markdown telah berjaya diimport", + "mediaPasted": "Media ditampal", + "pasteMixed": "{imageCount} imej dan {videoCount} video ditampal dengan berjaya.", + "pasteImages": "{count} imej ditampal dengan berjaya.", + "pasteVideos": "{count} video ditampal dengan berjaya.", + "pasteFailed": "Tampal gagal", + "unexpectedError": "Ralat tidak dijangka berlaku", + "uploadFailed": "Muat naik gagal" + }, + "blockBackground": { + "menuItem": "Tukar Warna Latar Belakang", + "dialogTitle": "Warna Latar Belakang Blok", + "dialogDescription": "Pilih warna latar belakang untuk blok ini", + "tabPreset": "Warna Pratetap", + "tabCustom": "Warna Tersuai", + "sectionHeading": "Warna Latar Belakang", + "apply": "Guna", + "colors": { + "none": "Tiada", + "red": "Merah", + "orange": "Jingga", + "yellow": "Kuning", + "green": "Hijau", + "blue": "Biru", + "indigo": "Indigo", + "purple": "Ungu", + "pink": "Merah Jambu", + "teal": "Teal", + "cyan": "Sian", + "gray": "Kelabu" + } + }, + "tableDialog": { + "title": "Cipta Jadual", + "description": "Cipta jadual baharu atau import dari markdown", + "markdownCheckbox": "Saya mempunyai jadual markdown", + "pasteMarkdownLabel": "Tampal Jadual Markdown", + "markdownPlaceholder": "| Pengepala 1 | Pengepala 2 | Pengepala 3 |\n|----------|----------|----------|\n| Sel 1 | Sel 2 | Sel 3 |\n| Sel 4 | Sel 5 | Sel 6 |", + "markdownHint": "Tampal jadual markdown anda di atas. Mesti termasuk baris pengepala dan pemisah.", + "rows": "Baris", + "columns": "Lajur", + "maxDimensions": "Maksimum: 20 baris × 10 lajur", + "cancel": "Batal", + "importTable": "Import Jadual", + "createTable": "Cipta Jadual" + }, + "tableParse": { + "errors": { + "minRows": "Jadual mesti mempunyai sekurang-kurangnya baris pengepala dan baris pemisah", + "pipeFormat": "Baris jadual mesti bermula dan berakhir dengan |", + "headerMinCol": "Baris pengepala mesti mempunyai sekurang-kurangnya satu lajur", + "separatorRow": "Baris kedua mestilah pemisah (cth., |---|---|)", + "rowColumnMismatch": "Baris {row} mempunyai {got} lajur, dijangkakan {expected}", + "parseFailed": "Gagal menghurai jadual markdown" + } + }, + "link": { + "editLink": "Edit Pautan", + "addLink": "Tambah Pautan", + "selectedText": "Teks dipilih: \"{text}\"", + "linkUrl": "URL Pautan", + "urlPlaceholder": "https://contoh.com", + "update": "Kemas Kini", + "addLinkButton": "Tambah Pautan", + "toastApplied": "Pautan Digunakan", + "toastLinkedTo": "Dipautkan ke: {url}", + "toastRemoved": "Pautan Dibuang", + "toastRemovedDesc": "Pautan telah dibuang dari pilihan" + }, + "customClass": { + "devMode": "Mod Pembangun", + "toggleDevModeAria": "Togol mod pembangun", + "searchPlaceholderDev": "Cari kelas... (cth., 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Cari gaya... (cth., 'merah', 'tebal', 'bayang')", + "appliesTitle": "Menggunakan: {className}", + "noMatches": "Tiada kelas dijumpai sepadan dengan \"{query}\"", + "toastClassReplaced": "Kelas Digantikan", + "toastClassReplacedDesc": "Digantikan \"{replaced}\" dengan \"{className}\"", + "toastCustomClassApplied": "Kelas Tersuai Digunakan", + "toastAppliedClass": "Kelas digunakan: {className}" + }, + "insertComponents": { + "title": "Sisipkan Komponen", + "description": "Pilih komponen untuk disisipkan ke dalam dokumen anda", + "noComponents": "Tiada komponen tersedia", + "categories": { + "media": "Media", + "layout": "Susun Atur", + "interactive": "Interaktif", + "decoration": "Hiasan" + }, + "freeImage": { + "name": "Imej Pergerakan Bebas", + "description": "Tambah imej yang boleh diletakkan di mana-mana dan diubah saiz dengan bebas" + } + } + }, + "JsonFormatter": { + "title": "Pemformat JSON", + "description": "Format, sahkan dan sunting JSON dalam paparan teks dan pokok.", + "documentNameText": "Dokumen teks {n}", + "documentNameTree": "Dokumen pokok {n}", + "newDocument": "Baharu", + "copy": "Salin", + "save": "Simpan", + "load": "Muat", + "saving": "Menyimpan...", + "leftDocNameLabel": "Nama dokumen kiri", + "rightDocNameLabel": "Nama dokumen kanan", + "toastCopiedText": "Panel teks disalin", + "toastCopiedTree": "Panel pokok disalin", + "toastCopyFailed": "Gagal menyalin JSON", + "toastLoginRequired": "Sila log masuk untuk menyimpan dokumen", + "toastSaved": "Dokumen disimpan", + "toastSaveFailed": "Gagal menyimpan dokumen", + "format": "Format", + "compact": "Padatkan", + "sortKeys": "Susun kunci", + "repair": "Baiki", + "query": "Pertanyaan", + "toastInvalidJson": "JSON tidak sah", + "toastAlreadyValid": "JSON sudah sah", + "toastRepaired": "JSON dibaiki", + "toastRepairFailed": "JSON tidak dapat dibaiki" + }, + "JwtDecoder": { + "title": "Penyahkod JWT", + "subtitle": "Periksa header dan payload. Tamat tempoh menggunakan tuntutan exp. Tiada apa dihantar ke pelayan; tandatangan tidak disahkan.", + "tokenLabel": "Token", + "clear": "Kosongkan", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Salin", + "expiryLabel": "Tamat tempoh (exp)", + "signaturePresent": "Tandatangan ada (tidak disahkan)", + "noExp": "Tiada tuntutan exp dalam payload.", + "status": { + "expired": "Tamat tempoh", + "valid": "Sah" + }, + "fields": { + "unix": "Unix", + "local": "Tempatan", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Dikeluarkan (iat)", + "notBefore": "Bukan sebelum (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Tampal JWT untuk dinyahkod.", + "segments": "JWT perlukan sekurang-kurangnya header dan payload (dua segmen).", + "base64": "Tidak dapat nyahkod base64url bagi header atau payload." + }, + "relative": { + "future": "dalam {n} {unit}", + "past": "{n} {unit} lalu" + }, + "units": { + "second": "{count, plural, one {saat} other {saat}}", + "minute": "{count, plural, one {minit} other {minit}}", + "hour": "{count, plural, one {jam} other {jam}}", + "day": "{count, plural, one {hari} other {hari}}" + }, + "tabs": { + "decode": "Nyahkod", + "sign": "Penjana / penandatangan" + }, + "signer": { + "algLabel": "Algoritma (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "JSON payload", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON header", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Rahsia (HS*)", + "secretPlaceholder": "Tampal rahsia HMAC anda…", + "privateKeyLabel": "Kunci peribadi PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Algoritma \"none\" menghasilkan token tanpa tandatangan. Ini tidak selamat dan hanya untuk ujian.", + "sign": "Tandatangan", + "verify": "Sahkan tandatangan", + "outputLabel": "Token (JWS padat)", + "outputPlaceholder": "Klik \"Tandatangan\"…", + "copyToken": "Salin token", + "copied": "Disalin", + "verifyKeyLabel": "Kunci pengesahan (pilihan)", + "verifyKeyPlaceholder": "Untuk RS*: tampal kunci awam (SPKI) PEM. Untuk HS*: biarkan kosong untuk guna rahsia yang sama.", + "verifyKeyHint": "Jika kosong, pengesahan menggunakan kunci tandatangan (berguna untuk HS*).", + "verifyStatus": { + "valid": "Tandatangan sah", + "invalid": "Tandatangan tidak sah" + }, + "securityNote": "Semuanya berjalan secara tempatan dalam pelayar; kunci dan token tidak dimuat naik." + }, + "signErrors": { + "invalidJson": "Header dan payload mesti JSON yang sah.", + "unsupportedAlg": "Algoritma tidak disokong.", + "missingKey": "Kunci tandatangan diperlukan untuk algoritma ini.", + "cryptoFailed": "Tandatangan gagal. Semak format kunci dan cuba lagi." + }, + "verifyErrors": { + "empty": "Masukkan JWT untuk disahkan.", + "segments": "JWT mesti mempunyai tepat 3 segmen untuk mengesahkan tandatangan.", + "unsupportedAlg": "Algoritma tidak disokong atau alg tiada dalam header.", + "missingKey": "Kunci pengesahan diperlukan untuk algoritma ini.", + "base64": "Tidak dapat menyahkod header atau tandatangan JWT.", + "cryptoFailed": "Pengesahan gagal. Semak format kunci dan cuba lagi." + } + }, + "CertificatePemDecoder": { + "title": "Pengecam sijil / PEM", + "subtitle": "Tampal sijil X.509 (-----BEGIN CERTIFICATE-----) atau CSR (-----BEGIN CERTIFICATE REQUEST-----). Subjek, penerbit, tarikh, dan SAN dihuraikan secara setempat; tiada muat naik.", + "pemLabel": "Input PEM", + "clear": "Padam", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Menghuraikan…", + "copyHex": "Salin cap jari (hex tanpa titik bertindih)", + "errors": { + "empty": "Tampal sijil PEM atau CSR untuk dinyahkod.", + "noBlock": "Tiada blok PEM sijil atau CSR ditemui.", + "parseFailed": "Tidak dapat menghuraikan data PEM ini." + }, + "block": { + "title": "Blok PEM {index}", + "certificate": "Sijil X.509", + "csr": "Permintaan tandatangan sijil" + }, + "fields": { + "subject": "Subjek", + "issuer": "Penerbit", + "notBefore": "Tidak sah sebelum", + "notAfter": "Tidak sah selepas", + "serial": "Nombor siri (hex)", + "sha256": "Cap jari SHA-256", + "sans": "Nama alternatif subjek", + "sansNone": "Tiada sambungan SAN.", + "signatureAlgorithm": "Algoritma tandatangan", + "csrIssuerNote": "CSR tidak termasuk penerbit (kelihatan pada sijil yang dikeluarkan).", + "csrValidityNote": "CSR tidak termasuk tarikh kesahan (kelihatan pada sijil yang dikeluarkan)." + }, + "sanType": { + "dns": "DNS", + "dn": "Nama direktori", + "email": "E-mel", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID berdaftar", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Pemformat SQL", + "subtitle": "Cantikkan SQL untuk MySQL, PostgreSQL atau SQLite. Berjalan dalam pelayar; tiada muat naik.", + "dialectLabel": "Dialek", + "keywordsLabel": "Kata kunci", + "indentLabel": "Indent", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "HURUF BESAR", + "lower": "huruf kecil", + "preserve": "Kekalkan" + }, + "spaces": "{n} ruang", + "format": "Format", + "clear": "Kosongkan", + "counter": "{current} / {max} aksara", + "inputPanel": "Input", + "formattedPanel": "Diformat", + "copy": "Salin", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klik \"Format\"…", + "errors": { + "tooLong": "Input dihadkan kepada {max} aksara.", + "couldNotFormat": "Tidak dapat memformat SQL ini." + } + }, + "YamlFormatter": { + "title": "Pemformat YAML", + "subtitle": "Format, sahkan dan tukarkan YAML dalam pelayar anda. Tiada yang dimuat naik.", + "outputLabel": "Output", + "outputModes": { + "yaml": "YAML (diformatkan)", + "json": "JSON" + }, + "indentLabel": "Inden", + "spaces": "{n} ruang", + "format": "Format", + "clear": "Kosongkan", + "copy": "Salin", + "counter": "{current} / {max} aksara", + "inputPanel": "Input", + "outputPanel": "Output", + "errors": { + "tooLong": "Input terhad kepada {max} aksara.", + "couldNotFormat": "Tidak dapat menghurai YAML ini." + } + }, + "GraphqlFormatter": { + "title": "Pemformat GraphQL", + "subtitle": "Format pertanyaan, mutasi dan langganan GraphQL dalam pelayar. Tiada apa dimuat naik.", + "tabs": { + "format": "Format", + "build": "Pembina pertanyaan" + }, + "outputModeLabel": "Output", + "outputModes": { + "pretty": "Cetakan mudah baca", + "minify": "Perminikan" + }, + "format": "Format", + "clear": "Kosongkan", + "counter": "{current} / {max} aksara", + "inputPanel": "Input", + "outputPanel": "Output", + "copy": "Salin", + "builderHint": "Bina rangka dokumen kemudian gunakannya pada editor. Definisi pemboleh ubah menggunakan sintaks GraphQL (cth. $id: ID!) — kurungan pilihan.", + "builder": { + "operation": "Operasi", + "operationName": "Nama operasi", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definisi pemboleh ubah", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Set pilihan", + "apply": "Gunakan pada input", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Input terhad kepada {max} aksara.", + "couldNotFormat": "Tidak dapat menghuraikan atau mencetak GraphQL ini.", + "emptySelection": "Tambah set pilihan sebelum menggunakan." + } + }, + "RegexTester": { + "title": "Penguji regex", + "subtitle": "Regex gaya JavaScript secara langsung pada teks anda. Padanan diserlahkan; tiada apa keluar dari pelayar.", + "patternLabel": "Corak", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i abaikan huruf besar/kecil", + "m": "m multiline", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(kosong)", + "noFlags": "—", + "matches": "{count, plural, one {padanan} other {padanan}}", + "testTextLabel": "Teks ujian", + "counter": "{current} / {max}", + "clear": "Kosongkan", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Teks dihadkan kepada {max} aksara.", + "invalid": "Ungkapan regular tidak sah.", + "runaway": "Dihentikan selepas {max} padanan (mungkin corak runaway)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Penukar cap masa", + "subtitle": "Saat Unix (≤10 digit) atau milisaat (integer lebih panjang), ISO-8601, atau rentetan yang boleh diparse oleh pelayar. Semuanya berjalan secara tempatan.", + "inputLabel": "Input", + "placeholder": "1744200000 atau 2025-04-09T12:00:00Z", + "now": "Sekarang", + "copy": "Salin", + "hint": "Tampal nilai atau klik Sekarang.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relatif", + "human": "Mudah dibaca" + }, + "fields": { + "seconds": "Saat", + "milliseconds": "Milisaat", + "utc": "UTC", + "local": "Tempatan", + "distance": "Jarak" + }, + "errors": { + "empty": "Masukkan cap masa Unix, rentetan ISO atau teks tarikh.", + "invalidNumber": "Cap masa berangka tidak sah.", + "outOfRange": "Di luar julat untuk JavaScript Date.", + "couldNotParse": "Tidak dapat diparse sebagai Unix, ISO-8601 atau tarikh pelayar." + } + }, + "HttpStatusCodes": { + "title": "Kod status HTTP", + "subtitle": "Cari dan semak imbas kod status respons HTTP (1xx5xx) dengan penerangan ringkas dan rujukan RFC. Berjalan secara setempat.", + "header": { + "title": "Kod status HTTP", + "description": "Rujukan boleh cari untuk kod respons HTTP dengan penerangan dan rujukan RFC." + }, + "searchPlaceholder": "Cari: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Tapis kelas…", + "classFilterAll": "Semua kelas", + "resultsCount": "{count} daripada {total}", + "table": { + "code": "Code", + "phrase": "Frasa", + "description": "Penerangan", + "copy": "Salin" + }, + "noResults": "Tiada padanan untuk “{query}”.", + "specPrefix": "Spes.:", + "copied": "Kod disalin" + }, + "CronBuilder": { + "title": "Pembina ungkapan Cron", + "subtitle": "Cron standard 5 medan: minute hour day month weekday. Parser menggunakan seconds=0 secara dalaman. Format pilihan 6 medan (sec min hour dom mon dow) berfungsi dalam editor ungkapan.", + "tabs": { + "builder": "Pembina visual", + "expression": "Ungkapan" + }, + "presetsLabel": "Pratetap", + "fieldsLabel": "Medan", + "rawExpressionLabel": "Ungkapan mentah", + "expressionHint": "Keluar dari medan untuk menghurai dan menyegerakkan pembina. {count} {count, plural, one {token} other {token}}.", + "currentExpressionLabel": "Ungkapan semasa", + "copy": "Salin", + "quickPick": "Pilihan pantas", + "emptyExpression": "—", + "plainLanguageLabel": "Bahasa biasa", + "nextRunsLabel": "Larian seterusnya (masa tempatan)", + "fields": { + "minute": "Minit", + "hour": "Jam", + "dayOfMonth": "Hari dalam bulan", + "month": "Bulan", + "dayOfWeek": "Hari dalam minggu" + }, + "presets": { + "everyMinute": "Setiap minit", + "every5Minutes": "Setiap 5 minit", + "every15Minutes": "Setiap 15 minit", + "everyHour": "Setiap jam (pada :00)", + "every6Hours": "Setiap 6 jam", + "dailyMidnight": "Harian tengah malam", + "daily0900": "Harian 09:00", + "weekdays0900": "Hari bekerja 09:00", + "weeklySunday": "Mingguan (Ahad 00:00)", + "monthlyFirst": "Bulanan (1hb 00:00)", + "yearlyJan1": "Tahunan (1 Jan 00:00)" + }, + "picks": { + "minute": { + "every": "setiap minit" + }, + "hour": { + "every": "setiap jam" + }, + "dom": { + "any": "mana-mana hari", + "last": "hari terakhir" + }, + "month": { + "every": "setiap bulan" + }, + "dow": { + "any": "mana-mana hari", + "weekdays": "hari bekerja", + "weekend": "hujung minggu" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mac", + "apr": "Apr", + "may": "Mei", + "jun": "Jun", + "jul": "Jul", + "aug": "Ogos", + "sep": "Sep", + "oct": "Okt", + "nov": "Nov", + "dec": "Dis" + }, + "weekdays": { + "sun": "Ahd", + "mon": "Isn", + "tue": "Sel", + "wed": "Rab", + "thu": "Kha", + "fri": "Jum", + "sat": "Sab" + }, + "errors": { + "fieldCount": "Dijangka 5 medan (minute hour day month weekday) atau 6 termasuk saat.", + "invalid": "Ungkapan cron tidak sah." + } + }, + "Base64": { + "title": "Pengekod / penyahkod Base64", + "subtitle": "Pengekodan teks ke Base64 atau nyahkod rentetan Base64", + "upload": "Muat naik", + "clear": "Kosongkan", + "modes": { + "encode": "Kod", + "decode": "Nyahkod" + }, + "swapTitle": "Tukar input dan output", + "panels": { + "plainText": "Teks biasa", + "base64Input": "Input Base64", + "base64Output": "Output Base64", + "decodedText": "Teks dinyahkod" + }, + "placeholders": { + "encode": "Masukkan teks untuk dikodkan ke Base64…", + "decode": "Tampal rentetan Base64 untuk dinyahkod…" + }, + "dropHere": "Lepaskan fail di sini", + "charCount": "{count} aksara", + "download": { + "title": "Muat turun output", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Salin ke papan klip", + "outputPlaceholder": "Output akan muncul di sini…", + "errors": { + "invalidBase64": "Rentetan Base64 tidak sah. Semak input.", + "encodeFailed": "Gagal mengod input." + } + }, + "UrlEncode": { + "title": "Pengekod / penyahkod URL", + "subtitle": "Pengekodan peratus/nyahkod dengan encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Muat naik", + "clear": "Kosongkan", + "modes": { + "encode": "Kod", + "decode": "Nyahkod" + }, + "swapTitle": "Tukar input dan output", + "panels": { + "plainText": "Teks biasa", + "encodedInput": "Input berkod", + "encodedOutput": "Output berkod", + "decodedText": "Teks dinyahkod" + }, + "placeholders": { + "encode": "Masukkan teks untuk peratus-kod…", + "decode": "Tampal teks percent-encoded untuk nyahkod…" + }, + "dropHere": "Lepaskan fail di sini", + "charCount": "{count} aksara", + "download": { + "title": "Muat turun output", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Salin ke papan klip", + "outputPlaceholder": "Output akan muncul di sini…", + "errors": { + "invalidEncoding": "Peratus-pengekodan tidak sah. Semak pasangan % tidak lengkap atau escape yang salah.", + "encodeFailed": "Tidak dapat mengod (Unicode tidak sah dalam input)." + } + }, + "UrlParser": { + "title": "Penghurai URL", + "subtitle": "Pecahkan URL kepada protokol, hos, laluan, parameter kueri, dan hash (fragmen).", + "upload": "Muat naik", + "clear": "Kosongkan", + "panels": { + "input": "Input URL", + "output": "Output dihuraikan" + }, + "placeholders": { + "input": "Tampal URL (cth. https://example.com/path?foo=bar#section) atau hos/laluan (cth. example.com/path)…" + }, + "dropHere": "Lepaskan fail di sini", + "charCount": "{count} aksara", + "outputPlaceholder": "Komponen yang dihuraikan akan muncul di sini…", + "fields": { + "protocol": "Protokol", + "host": "Hos", + "path": "Laluan", + "hash": "Hash", + "queryParams": "Parameter kueri" + }, + "paramCount": "{count} parameter", + "noParams": "Tiada parameter kueri.", + "paramKey": "Kunci", + "paramValue": "Nilai", + "copyTitle": "Salin ke papan klip", + "copyParamTitle": "Salin sebagai key=value", + "download": { + "title": "Muat turun JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL tidak sah. Pastikan ada hostname (example.com) dan semak aksara yang tidak dibenarkan." + } + }, + "UuidGenerator": { + "title": "Penjana UUID / ULID", + "subtitle": "Jana UUID (RFC 9562) atau ULID dalam pelayar. Eksport pukal hingga {max} setiap kali.", + "format": "Format", + "kindHints": { + "ulid": "Pengecam 26 aksara yang boleh diisih dan selamat untuk URL", + "uuid4": "Rawak (RFC 9562)", + "uuid7": "Teratur mengikut masa, ms Unix + rawak", + "uuid1": "Berasaskan MAC/masa (RFC 9562)", + "uuid6": "Masa Gregorian disusun semula", + "uuid3": "Hash MD5 nama + namespace", + "uuid5": "Hash SHA‑1 nama + namespace", + "nil": "UUID nol (semua sifar, RFC 9562)" + }, + "countLabel": "Bilangan (1–{max})", + "nameLabel": "Nama (rentetan input)", + "namePlaceholder": "cth. my.app/resource", + "bulkHint": "Mod pukal menambah #0, #1, … supaya setiap ID berbeza (v3/v5 deterministik).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID tersuai" + }, + "customNamespaceLabel": "UUID namespace", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Jana", + "output": "Output", + "lines": "{count} baris", + "download": { + "title": "Muat turun .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Salin semua", + "uppercaseToggle": "Output huruf besar", + "hyphensToggle": "Tunjukkan sempang", + "copyJsonTitle": "Salin sebagai array JSON", + "outputPlaceholder": "Klik \"Jana\"…", + "errors": { + "invalidCustomNamespace": "Namespace tersuai mestilah UUID yang sah.", + "missingName": "Nama diperlukan untuk UUID v3 dan v5.", + "unknown": "Tidak dapat menjana ID." + } + }, + "SecretApiKeyGenerator": { + "title": "Penjana rahsia / kunci API", + "subtitle": "Jana rentetan rawak kriptografi dengan panjang dan abjad boleh konfigurasi. Eksport pukal sehingga {max} setiap jalanan. Berpasangan dengan penjana UUID / ULID.", + "alphabetLabel": "Abjad", + "alphabetHint": "Watak pendua dibuang. Setiap simbol dipilih secara seragam dengan crypto.getRandomValues dan pensampelan penolakan (tiada bias modulo).", + "alphabetCount": "{count} simbol unik", + "presetsLabel": "Pratetap", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumerik", + "hex": "Perenambelasan (huruf kecil)", + "asciiPrintable": "ASCII boleh cetak" + }, + "lengthLabel": "Panjang setiap rahsia (1–{maxLen})", + "countLabel": "Bilangan (1–{maxBulk})", + "generate": "Jana", + "output": "Output", + "lines": "{count} baris", + "download": { + "title": "Muat turun .txt", + "filename": "rahsia.txt" + }, + "copyTitle": "Salin semua", + "outputPlaceholder": "Klik \"Jana\"…", + "entropy": "{bits} bit entropi", + "strength": { + "weak": "Lemah", + "fair": "Sederhana", + "strong": "Kuat" + }, + "excludeAmbiguous": "Kecualikan aksara mengelirukan", + "excludeAmbiguousHint": "Mengalih keluar {chars} — aksara yang mudah disalah baca atau tidak sesuai untuk shell.", + "prefixLabel": "Awalan kunci (pilihan)", + "errors": { + "emptyAlphabet": "Tambah sekurang-kurangnya satu aksara pada abjad.", + "alphabetTooLong": "Selepas membuang pendua, abjad boleh mempunyai sehingga {maxAlpha} aksara.", + "lengthOutOfRange": "Panjang mestilah antara 1 dan {maxLen}.", + "countOutOfRange": "Bilangan mestilah antara 1 dan {maxBulk}.", + "unknown": "Tidak dapat menjana rahsia." + } + }, + "LoremIpsum": { + "title": "Penjana Lorem Ipsum", + "subtitle": "Jana teks Latin pemegang tempat untuk susun atur dan mockup anda.", + "paragraphs": "Perenggan", + "sentences": "Ayat", + "words": "Perkataan", + "count": "Bilangan", + "generate": "Jana", + "copy": "Salin", + "startWith": "Mulakan dengan 'Lorem ipsum...'", + "outputPlaceholder": "Output akan muncul di sini...", + "outputType": "Jenis output", + "bulletList": "Senarai poin", + "htmlMarkup": "Markup HTML", + "htmlMarkupHint": "Balut output dalam perenggan, atau gunakan senarai untuk mod poin", + "output": "Output", + "download": "Muat turun" + }, + "ColorPicker": { + "title": "Pemilih dan penukar warna", + "subtitle": "Tukar HEX, RGB dan HSL; bina palet harmoni. Semua pemprosesan berlaku dalam pelayar anda.", + "nativePicker": "Pemilih asli", + "chooseColor": "Pilih warna", + "eyeDropper": "Penitis mata", + "invalidHex": "Warna HEX tidak sah.", + "cssValues": "Nilai CSS", + "palettes": "Palet", + "copied": "Disalin", + "clickToCopy": "Klik untuk salin", + "copyAllHex": "Salin semua HEX", + "copy": "Salin", + "copyColor": "Salin {color}", + "paletteShades": "Warna-warna", + "paletteComplementary": "Komplementari", + "paletteTriadic": "Triadi", + "paletteAnalogous": "Analog", + "paletteSplit": "Komp. berpecah", + "paletteSquare": "Segi empat" + }, + "ContrastChecker": { + "title": "Pemeriksa kontras", + "subtitle": "Mengira nisbah kontras WCAG 2.1 antara latar depan dan latar belakang. Semak AA dan AAA untuk teks biasa dan besar. Semuanya dalam pelayar anda.", + "foreground": "Teks / latar depan", + "background": "Latar belakang", + "hexPlaceholder": "#RRGGBB", + "swap": "Tukar warna", + "chooseForeground": "Pilih warna latar depan", + "chooseBackground": "Pilih warna latar belakang", + "eyeDropper": "Pipet", + "ratio": "Nisbah kontras", + "copyRatio": "Salin nisbah", + "copied": "Disalin", + "previewHeading": "Pratonton", + "previewBody": "Muzaffar mempunyai 1246 dawai biola tempaannya di Qatar. The quick brown fox jumps over the lazy dog — contoh teks campuran.", + "previewCaption": "Contoh teks pada saiz lalai.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — teks biasa (4,5:1)", + "levelAALarge": "AA — teks besar (3:1)", + "levelAAANormal": "AAA — teks biasa (7:1)", + "levelAAALarge": "AAA — teks besar (4,5:1)", + "pass": "Lulus", + "fail": "Gagal", + "invalidHex": "Warna HEX tidak sah.", + "largeTextHint": "Teks besar: 18 pt+ biasa atau 14 pt+ tebal (takrifan WCAG)." + }, + "DiffChecker": { + "title": "Pemeriksa Perbezaan", + "subtitle": "Perbandingan baris demi baris dua teks secara bersebelahan. Semua kekal dalam pelayar anda.", + "rowsCount": "{count} baris", + "truncatedWarning": "Menunjukkan {max} baris pertama.", + "limitExceeded": "Setiap sisi terhad kepada {max} aksara.", + "clearBoth": "Padamkan kedua-dua", + "original": "Asal", + "modified": "Diubah suai", + "originalPlaceholder": "Tampal teks asal…", + "modifiedPlaceholder": "Tampal teks yang diubah suai…", + "comparisonLabel": "Perbandingan bersebelahan", + "emptyState": "Masukkan teks dalam kedua-dua panel untuk membandingkan." + }, + "JsonSchemaGenerator": { + "title": "Penjana Skema JSON", + "subtitle": "Tampal JSON yang sah untuk menjana skema JSON atau model berjenis. Semuanya berjalan dalam pelayar anda.", + "languageLabel": "Bahasa output", + "inputLabel": "Input JSON", + "inputPlaceholder": "Tampal JSON di sini…", + "outputLabel": "Output yang dijana", + "outputPlaceholder": "Skema atau jenis yang dijana akan dipaparkan di sini…", + "parseError": "JSON tidak sah", + "clear": "Kosongkan", + "copy": "Salin", + "copied": "Disalin ke papan keratan", + "copyFailed": "Tidak dapat menyalin", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Skema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Memuatkan editor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Imej ke Base64", + "subtitle": "Lepaskan imej, dapatkan URI data atau rentetan Base64 mentah. Semuanya kekal dalam penyemak imbas anda.", + "upload": "Muat Naik Imej", + "clear": "Jelas", + "modes": { + "dataUri": "URI data", + "rawString": "Pangkalan Mentah64" + }, + "panels": { + "imageInput": "Input Imej", + "output": "Output Base64" + }, + "dropHere": "Lepaskan gambar di sini", + "charCount": "{count} aksara", + "placeholders": { + "uploadImage": "Seret dan lepas imej atau klik muat naik untuk bermula." + }, + "fileTooLarge": "Fail terlalu besar. Pemprosesan mungkin membekukan penyemak imbas anda.", + "copyTitle": "Salin ke papan keratan", + "download": { + "title": "Muat turun teks", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Pemampat Imej", + "subtitle": "Kecilkan output JPEG, PNG atau WebP secara setempat dengan kawalan kualiti — tiada muat naik.", + "upload": "Muat naik imej", + "clear": "Kosongkan", + "dropHere": "Lepaskan imej di sini", + "placeholders": { + "uploadImage": "Seret dan lepas imej, atau klik untuk memilih fail." + }, + "fileTooLarge": "Fail terlalu besar (maks {maxMb} MB).", + "unsupportedFormat": "Jenis fail tidak disokong. Gunakan JPEG, PNG, WebP, GIF, AVIF atau BMP.", + "compressFailed": "Tidak dapat memampatkan imej ini. Cuba fail atau format lain.", + "panels": { + "original": "Asal", + "compressed": "Pratonton termampat" + }, + "outputFormat": "Format output", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kualiti", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG tiada rugi dalam pelayar; peluncur kualiti hanya untuk JPEG dan WebP.", + "sizes": { + "original": "Asal: {size}", + "compressed": "Termampat: {size}", + "saved": "{percent}% lebih kecil" + }, + "largerWarning": "Output lebih besar daripada input — cuba format atau kualiti lain.", + "download": "Muat turun", + "downloadFilename": "imej-termampat", + "processing": "Memampatkan…", + "emptyCompressed": "Pratonton termampat dipaparkan di sini." + }, + "CssGradientBuilder": { + "title": "Pembina Kecerunan CSS", + "subtitle": "Pembina kecerunan CSS visual dengan kawalan sudut, hentian warna dan salinan CSS satu klik.", + "preview": "Pratonton Kecerunan", + "settings": "tetapan", + "type": "taip", + "linear": "Linear", + "radial": "Jejari", + "angle": "sudut", + "stops": "Warna Berhenti", + "color": "warna", + "position": "Kedudukan (%)", + "addStop": "Tambah Berhenti", + "deleteStop": "Padam Berhenti", + "output": "Output CSS", + "copy": "Salin CSS", + "copied": "disalin!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Penjana .gitignore", + "subtitle": "Hasilkan fail .gitignore yang digabungkan serta-merta untuk susunan atau gabungan teknologi tertentu.", + "searchPlaceholder": "Cari tindanan (cth. Node, Python, macOS)...", + "noResults": "Tiada tindanan ditemui.", + "selectedStacks": "Timbunan Terpilih", + "output": "Pratonton Output", + "copy": "Salin ke Papan Klip", + "copied": "disalin!", + "download": "Muat turun .giignore", + "downloading": "Memuat turun...", + "errorFetching": "Gagal mengambil senarai teratas.", + "errorGenerating": "Gagal menjana .gitignore daripada tindanan yang dipilih.", + "generating": "Menjana...", + "emptyState": "Pilih satu atau lebih tindanan teknologi untuk menjana .gitignore anda." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Muat naik CSV atau Excel, edit JSON, kemudian muat turun CSV atau XLSX. Data jadual sahaja; tarikh Excel menjadi cap masa ISO dalam JSON.", + "dropTitle": "Lepaskan fail CSV atau Excel", + "dropHint": "Untuk .xlsx / .xls, helaian pertama digunakan. CSV mesti ada baris pengepala.", + "chooseFile": "Pilih fail", + "jsonLabel": "JSON (tatasusunan objek)", + "jsonPlaceholder": "Tampal tatasusunan JSON objek atau import CSV / Excel…", + "loadSample": "Muat contoh", + "actions": { + "format": "Format JSON", + "clear": "Kosongkan", + "copy": "Salin", + "copied": "Disalin", + "downloadCsv": "Muat turun CSV", + "downloadXlsx": "Muat turun Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Data", + "toastLoadedCsv": "CSV dimuatkan ke editor", + "toastLoadedExcel": "Excel dimuatkan ke editor", + "toastFormatted": "JSON diformat", + "toastExportedCsv": "CSV dimuat turun", + "toastExportedXlsx": "Excel dimuat turun", + "toastCopied": "Disalin ke papan keratan", + "footnote": "Semua berjalan dalam pelayar. JSON mesti tatasusunan objek yang tidak kosong. Nilai bersarang disirikan sebagai JSON dalam sel CSV.", + "errors": { + "unsupportedFile": "Guna fail .csv, .xlsx atau .xls.", + "parseFailed": "Tidak dapat menghuraikan fail ini.", + "invalidJson": "JSON tidak sah", + "exportFailed": "Eksport gagal", + "copyFailed": "Tidak dapat menyalin" + } + }, + "SnippetManager": { + "title": "Pengurus petikan kod", + "subtitle": "Simpan petikan dalam pelayar atau log masuk untuk menyelaraskannya dengan akaun anda. Tukar antara lihat dan sunting, salin satu klik, pilih bahasa atau auto kesan, dan format JSON, SQL atau kod yang disokong Monaco.", + "snippets": "Petikan", + "newSnippet": "Petikan baharu", + "newSnippetTitle": "Petikan baharu", + "defaultSnippetTitle": "Selamat datang", + "defaultSnippetCode": "// Selamat datang ke petikan kod\n// Cuba lihat vs sunting, salin, format dan bahasa automatik\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Cari petikan…", + "searchAria": "Cari petikan", + "emptySearch": "Tiada petikan sepadan.", + "snippetTitle": "Tajuk", + "snippetTitlePlaceholder": "Nama petikan", + "language": "Bahasa", + "detectedHint": "Sorotan sebagai {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Lihat", + "modeEdit": "Sunting", + "copy": "Salin", + "copied": "Disalin", + "format": "Format", + "cancel": "Batal", + "deleteTitle": "Padam petikan?", + "deleteDescription": "“{title}” akan dibuang daripada pelayar ini.", + "deleteConfirm": "Padam", + "editorAria": "Editor kod petikan", + "editorLoading": "Memuatkan editor…", + "toastCopied": "Disalin ke papan keratan", + "toastCopyFailed": "Tidak dapat menyalin", + "toastNothingToCopy": "Tiada untuk disalin", + "toastFormatted": "Telah diformat", + "toastFormatFailed": "Tidak dapat memformat", + "toastFormatJsonInvalid": "JSON tidak sah — betulkan sintaks dahulu", + "toastFormatUnsupported": "Tiada pemformat untuk bahasa ini — cuba JSON atau SQL atau mod sunting", + "toastDeleted": "Petikan dipadam", + "toastLoadFailed": "Tidak dapat memuatkan petikan dari pelayan.", + "toastSyncFailed": "Tidak dapat menyelaraskan dengan pelayan.", + "languages": { + "auto": "Auto kesan", + "plaintext": "Teks biasa", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Klien SQL", + "dialogTitle": "Sambungan SQL", + "mobileSheetTitle": "Penjelajah SQL", + "newTabLabel": "Tab pertanyaan baru", + "vaultLockedTitle": "Peti dikunci", + "vaultLockedDesc": "Buka kunci peti untuk mengakses sambungan tersimpan.", + "heroTitle": "Klien SQL", + "heroDesc": "Sambung ke PostgreSQL, MySQL atau MariaDB. Kelayakan disulitkan dalam pelayar anda.", + "heroAddConnection": "Tambah sambungan", + "selectTableTitle": "Pilih jadual atau buka tab pertanyaan", + "selectTableDesc": "Klik jadual dalam bar sisi atau guna ⌘↩.", + "toastLoadFail": "Gagal memuatkan sambungan." + }, + "connection": { + "titleNew": "Sambungan SQL baharu", + "titleEdit": "Edit sambungan", + "labelDbType": "Jenis pangkalan data", + "labelName": "Nama sambungan", + "placeholderName": "DB produksi saya", + "labelHostPort": "Hos & Port", + "labelDatabase": "Pangkalan data", + "placeholderDatabase": "mydb", + "labelUsername": "Nama pengguna", + "labelPassword": "Kata laluan", + "labelSsl": "Aktifkan SSL / TLS", + "sslDesc": "Sulitkan sambungan ke pelayan", + "securityNote": "Kelayakan disulitkan AES-256 dalam pelayar sebelum disimpan", + "btnTest": "Uji sambungan", + "btnSave": "Simpan & Sambung", + "btnUpdate": "Kemas kini", + "btnCancelEdit": "Batal pengeditan", + "savedConnections": "Sambungan tersimpan", + "emptySaved": "Sambungan tersimpan akan muncul di sini", + "confirmDelete": "Padam sambungan ini?", + "toastFillFields": "Isi hos dan pangkalan data sebelum menguji.", + "toastConnectOk": "Disambungkan! {version}", + "toastConnectFail": "Sambungan gagal: {message}", + "toastUnlockVault": "Buka kunci peti dahulu.", + "toastFieldsRequired": "Hos dan pangkalan data diperlukan.", + "toastUpdated": "Sambungan dikemas kini.", + "toastSaved": "Sambungan disimpan.", + "toastSaveFail": "Gagal menyimpan: {message}", + "toastDeleted": "Sambungan dipadam.", + "toastDeleteFail": "Gagal memadam sambungan." + }, + "sidebar": { + "title": "Penjelajah SQL", + "tooltipAddConnection": "Tambah sambungan", + "tooltipRefreshSchema": "Muat semula skema", + "noConnections": "Tiada sambungan tersimpan lagi", + "toastSchemaFail": "Gagal memuatkan skema: {message}" + }, + "editor": { + "btnRun": "Jalankan", + "executing": "Menjalankan pertanyaan…", + "emptyTitle": "Jalankan pertanyaan untuk melihat keputusan", + "emptyHint": "Tekan ⌘↩ atau klik Jalankan", + "toastNoConnection": "Tiada sambungan aktif." + }, + "results": { + "filterPlaceholder": "Tapis keputusan…", + "rowCount": "{count} baris", + "rowCountFiltered": "{filtered} / {total} baris", + "exportCsv": "CSV", + "noRows": "Tiada baris dikembalikan" + } + }, + "QrCodeGenerator": { + "title": "Penjana kod QR", + "subtitle": "Tukar sebarang teks atau URL kepada kod QR PNG. Semua dipaparkan secara tempatan dalam pelayar.", + "contentLabel": "Kandungan", + "contentPlaceholder": "https://example.com, teks biasa, rentetan Wi‑Fi, vCard…", + "styleLabel": "Gaya", + "dotTypeLabel": "Bentuk titik", + "dotTypeSquare": "Segi empat", + "dotTypeDots": "Titik", + "dotTypeRounded": "Membulat", + "dotTypeExtraRounded": "Sangat membulat", + "dotTypeClassy": "Elegan", + "dotTypeClassyRounded": "Elegan membulat", + "cornerSquareLabel": "Segi empat sudut", + "cornerSquareDot": "Titik", + "cornerSquareSquare": "Segi empat", + "cornerSquareExtraRounded": "Sangat membulat", + "cornerDotLabel": "Titik sudut", + "cornerDotDot": "Titik", + "cornerDotSquare": "Segi empat", + "eccLabel": "Pembetulan ralat", + "eccL": "Rendah (~7%)", + "eccM": "Sederhana (~15%)", + "eccQ": "Suku (~25%)", + "eccH": "Tinggi (~30%)", + "marginLabel": "Margin (modul)", + "widthLabel": "Lebar imej", + "widthEffective": "Dipaparkan pada {px} px", + "darkColor": "Modul gelap", + "lightColor": "Modul cerah", + "resetColors": "Set semula warna", + "logoLabel": "Logo tengah", + "logoMarginLabel": "Jidar", + "uploadLogo": "Muat naik gambar", + "removeLogo": "Buang logo", + "downloadPng": "Muat turun PNG", + "downloadSvg": "Muat turun SVG", + "copyImage": "Salin imej", + "copied": "Disalin", + "clear": "Padam", + "previewAlt": "Pratonton kod QR", + "emptyHint": "Taip atau tampal kandungan untuk pratonton.", + "filename": "kod-qr.png", + "errors": { + "generationFailed": "Tidak dapat menjana kod QR. Cuba teks lebih pendek atau pembetulan lebih rendah.", + "copyUnsupported": "Pelayar tidak menyokong menyalin imej ke papan keratan.", + "copyFailed": "Tidak dapat menyalin ke papan keratan." + } + }, + "IpSubnetCalculator": { + "title": "Kalkulator IP / subnet", + "subtitle": "Butiran CIDR IPv4 dan IPv6 dalam pelayar—tiada permintaan luaran.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Rangkaian", + "placeholderIpv4": "10.0.0.0/24 atau 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 atau 2001:db8::1/64", + "hintIpv4": "Gunakan CIDR (cth. /24) atau IPv4 + topeng bertitik.", + "hintIpv6": "Sertakan panjang awalan (cth. /64). Jika tidak, /128.", + "copyValue": "Salin nilai", + "rows": { + "cidr": "CIDR", + "network": "Alamat rangkaian", + "netmask": "Topeng subnet", + "wildcard": "Topeng wildcard", + "broadcast": "Broadcast", + "firstHost": "Hos pertama", + "lastHost": "Hos terakhir", + "totalAddresses": "Jumlah alamat", + "usableHosts": "Hos boleh guna", + "prefixLength": "Panjang awalan (bit)", + "networkIpv6": "Alamat rangkaian", + "lastIpv6": "Alamat terakhir dalam julat", + "totalAddressesIpv6": "Alamat dalam subnet" + }, + "usableNote": { + "pointToPoint": "Pautan /31 titik-ke-titik menggunakan kedua-dua alamat sebagai hos (RFC 3021).", + "singleHost": "/32 ialah satu hos." + }, + "ipv6RangeHint": { + "normal": "Julat matematik penuh dipaparkan; peraturan penghala mungkin berbeza.", + "single": "/128 ialah satu alamat.", + "full": "/0 merangkumi IPv6 sepenuhnya." + }, + "errors": { + "invalidIpv4": "Rangkaian IPv4 tidak sah. Cuba CIDR atau alamat + topeng.", + "invalidIpv6": "Rangkaian IPv6 tidak sah. Gunakan alamat/awalan, cth. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Penjana hash", + "subtitle": "Pencernaan MD5, SHA-1 dan SHA-2 untuk teks atau fail; bcrypt untuk kata laluan (input teks). Semua dikira secara setempat dalam pelayar.", + "algorithmLabel": "Algoritma", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Teks", + "modeFile": "Fail", + "inputLabelText": "Input", + "placeholderText": "Taip atau tampal teks…", + "chooseFile": "Pilih fail", + "clearFile": "Padam", + "fileHint": "Fail dibaca dalam memori sahaja; maks {maxMb} MB. Tiada muat naik.", + "outputLabel": "Cincangan (hex)", + "copyHash": "Salin cincangan", + "copied": "Disalin", + "byteCount": "{count, plural, other {# bait}}", + "charCount": "{count, plural, other {# aksara}}", + "emptyHint": "Masukkan teks atau pilih fail.", + "securityNote": "MD5 dan SHA-1 tidak disyorkan untuk keselamatan baharu; gunakan SHA-256 atau SHA-512.", + "errors": { + "fileTooLarge": "Fail melebihi {maxMb} MB.", + "fileReadFailed": "Tidak dapat membaca fail." + }, + "bcryptRoundsLabel": "Kos (pusingan)", + "bcryptRoundsHint": "Nilai lebih tinggi lebih perlahan dan lebih tahan brute force. Lalai biasa ialah 10.", + "fileTabDisabledTitle": "bcrypt hanya menghash kata laluan daripada teks, bukan fail sewenang-wenang.", + "placeholderBcrypt": "Taip atau tampal kata laluan untuk di-hash…", + "outputLabelBcrypt": "Hash bcrypt", + "copyBcrypt": "Salin hash", + "emptyHintBcrypt": "Masukkan kata laluan untuk mengira hash bcrypt.", + "securityNoteBcrypt": "Setiap output bcrypt merangkumi garam rawak; kata laluan yang sama menghasilkan rentetan berbeza setiap kali. Ini dijangka. Gunakan kos tinggi dalam produksi.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Taman permainan penyulitanan", + "subtitle": "AES-GCM dalam pelayar: sulitkan ke bungkusan JSON dengan frasa laluan atau kunci mentah, kemudian nyahsulitkan secara setempat. Bersifat pendidikan dan kadang praktikal — tiada muat naik.", + "tabEncrypt": "Sulitkan", + "tabDecrypt": "Nyahsulitkan", + "keyModeLabel": "Bahan kunci", + "modePassphrase": "Frasa laluan (PBKDF2 + AES-GCM)", + "modeRawKey": "Kunci AES mentah (hex atau Base64)", + "aesBitsLabel": "Panjang kunci AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Pengekodan kunci mentah", + "encodingAuto": "Auto (hex jika kelihatan seperti hex, jika tidak Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Kunci mentah", + "rawKeyPlaceholder": "Kunci 32 bait sebagai 64 aksara hex, atau Base64 untuk AES-256 (panjang bergantung pada saiz AES di atas).", + "rawKeyPlaceholderDecrypt": "Hanya diperlukan jika bungkusan mempunyai keyDerivation \"none\".", + "passphraseLabel": "Frasa laluan", + "passphrasePlaceholder": "Frasa laluan digunakan hanya dalam pelayar untuk menurunkan kunci.", + "passphrasePlaceholderDecrypt": "Hanya diperlukan jika bungkusan menggunakan PBKDF2-SHA256.", + "pbkdf2IterLabel": "Lelaran PBKDF2", + "pbkdf2Hint": "Antara 10,000 dan 2,000,000. Lalai sejajar dengan tetapan peti biasa.", + "plaintextLabel": "Teks jelas", + "plaintextPlaceholder": "Taip atau tampal mesej untuk disulitkan…", + "bundleInputLabel": "Bungkusan disulitkan (JSON)", + "bundlePlaceholder": "Tampal JSON daripada Sulitkan (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Bungkusan disulitkan", + "plaintextOutputLabel": "Teks jelas", + "encryptButton": "Sulitkan", + "decryptButton": "Nyahsulitkan", + "copyBundle": "Salin JSON", + "copyPlaintext": "Salin teks jelas", + "copied": "Disalin", + "emptyBundleHint": "JSON disulitkan dipaparkan di sini selepas penyulitanan.", + "emptyDecryptHint": "Teks dinyahsulitkan dipaparkan di sini.", + "decryptKeyHint": "Gunakan kunci mentah jika keyDerivation ialah \"none\"; frasa laluan untuk PBKDF2-SHA256. Bungkusan menyimpan garam, lelaran, IV dan panjang AES.", + "securityNote": "Kunci dan teks jelas tidak meninggalkan pelayar anda. Untuk rahsia pengeluaran, gunakan alat khusus.", + "errors": { + "emptyKey": "Masukkan kunci mentah yang sepadan dengan saiz AES.", + "emptyPassphrase": "Masukkan frasa laluan.", + "badKeyMaterial": "Kunci tidak boleh dibaca sebagai hex atau Base64.", + "wrongKeyLength": "Panjang kunci tidak sepadan dengan AES (16 / 24 / 32 bait).", + "invalidJson": "JSON tidak sah.", + "invalidBundle": "JSON ini bukan bungkusan penyulitanan yang disokong.", + "decryptFailed": "Nyahsulitanan gagal — rahsia salah, bungkusan rosak atau parameter tidak sepadan.", + "encryptFailed": "Penyulitanan gagal. Semak Web Crypto dan input." + } + }, + "HmacGenerator": { + "title": "Penjana HMAC", + "subtitle": "Untuk menandatangani webhook dan menguji integrasi API. HMAC-SHA1/256/384/512 dalam hex atau Base64—dikira secara setempat dalam pelayar.", + "secretLabel": "Kunci rahsia", + "messageLabel": "Mesej / muatan", + "placeholderSecret": "Tampal rahsia penandatanganan…", + "placeholderMessage": "Tampal badan mentah atau rentetan untuk diautentikasi (selalunya bait webhook yang tepat).", + "algorithmLabel": "Algoritma HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Format output", + "formatHex": "Hex (huruf kecil)", + "formatBase64": "Base64", + "outputLabel": "Tandatangan", + "copyOutput": "Salin tandatangan", + "copied": "Disalin", + "byteCountHint": "{count, plural, other {# bait}} (UTF-8)", + "emptyHint": "Masukkan kunci rahsia untuk mengira HMAC.", + "securityNote": "Jangan kongsi rahsia penandatanganan. Halaman ini tidak memuat naik data—semua berjalan dalam pelayar.", + "errors": { + "cryptoFailed": "Tidak dapat mengira HMAC. Semak sokongan Web Crypto dan cuba lagi.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Penjana kod TOTP / 2FA", + "subtitle": "Tampal rahsia Base32 dan dapatkan kod enam digit RFC 6238 semasa (SHA-1, 30 s). Berjalan secara setempat dalam pelayar—berguna untuk menguji log masuk atau MFA.", + "secretLabel": "Rahsia (Base32)", + "placeholderSecret": "Tampal kunci rahsia (huruf A–Z dan digit 2–7, seperti semasa menyediakan pengesah)…", + "codeLabel": "Kod semasa", + "copyCode": "Salin kod", + "copied": "Disalin", + "nextIn": "Disegar dalam {seconds} s", + "standardHint": "Langkah masa 30 s, 6 digit, HMAC-SHA-1 (serasi Google Authenticator).", + "digitsLabel": "Digit", + "periodLabel": "Tempoh", + "algorithmLabel": "Algoritma", + "periodSeconds": "{seconds} s", + "emptyHint": "Tampal rahsia untuk menjana kod enam digit.", + "invalidHint": "Betulkan rahsia untuk melihat kod.", + "securityNote": "Layannya seperti kata laluan. Rahsia tidak meninggalkan pelayar; tiada muat naik.", + "importLabel": "Import daripada URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Import", + "qrSectionTitle": "Kod QR peruntukan", + "qrHint": "Imbas dengan apl pengesah (Google Authenticator, 1Password, Aegis…) untuk menambah rahsia ini. Kod QR ini mengekod rahsia tersebut — anggap ia seperti kata laluan.", + "accountLabel": "Akaun", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Pengeluar (pilihan)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Masukkan rahsia yang sah untuk menjana kod QR.", + "errors": { + "invalidSecret": "Bukan rahsia Base32 yang sah.", + "cryptoFailed": "Tidak dapat mengira TOTP. Semak Web Crypto dan cuba lagi.", + "invalidUri": "Ini bukan URI otpauth:// TOTP yang sah.", + "unsupportedUri": "URI ini menggunakan nilai yang tidak disokong oleh alat ini (digit mestilah 6 atau 8; tempoh 15, 30, atau 60 saat)." + } + }, + "MarkdownPreview": { + "title": "Pratonton Markdown & HTML", + "subtitle": "Pemapar Markdown langsung dengan eksport HTML dan penukaran HTML → Markdown. Semua berjalan dalam pelayar anda.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Input Markdown", + "htmlInputLabel": "Input HTML", + "previewLabel": "Pratonton", + "htmlOutputLabel": "Sumber HTML", + "mdOutputLabel": "Output Markdown", + "inputPlaceholder": "# Helo\\n\\nTaip **markdown** anda di sini…", + "htmlInputPlaceholder": "Tampal HTML anda di sini…", + "copyHtml": "Salin HTML", + "copyMarkdown": "Salin Markdown", + "exportHtml": "Eksport HTML", + "copied": "Disalin", + "charCount": "{count, plural, other {# aksara}}", + "emptyHint": "Mulakan menaip Markdown untuk melihat pratonton langsung dan output HTML.", + "emptyHintHtmlToMd": "Tampal HTML untuk menukarnya kepada Markdown.", + "securityNote": "Semua berjalan secara setempat dalam pelayar anda. Tiada apa yang dimuat naik." + }, + "NumberBaseConverter": { + "title": "Penukar asas nombor", + "subtitle": "Tukar integer antara asas 2–36. Awalan pilihan 0x / 0b / 0o. Semua dikira secara setempat dalam pelayar.", + "inputBaseLabel": "Dari asas", + "outputBaseLabel": "Ke asas", + "inputLabel": "Nombor input", + "placeholderInput": "Masukkan integer dalam asas input yang dipilih…", + "outputLabel": "Nilai ditukar", + "copyOutput": "Salin hasil", + "copied": "Disalin", + "swap": "Tukar asas", + "byteCountHint": "{count, plural, other {# bait}} (panjang UTF-8)", + "emptyHint": "Masukkan nombor untuk ditukar.", + "note": "Digit melebihi 9 menggunakan A–Z (tidak peka huruf). Ruang dan garis bawah diabaikan.", + "errors": { + "invalidNumber": "Nilai tidak sah untuk asas input yang dipilih." + } + }, + "FormatConverter": { + "title": "Penukar Format", + "subtitle": "Tukar antara JSON, YAML, TOML dan XML dalam mana-mana arah. Semua 12 kombinasi. Semua berjalan di pelayar anda.", + "fromLabel": "Dari", + "toLabel": "Ke", + "swap": "Tukar", + "swapTitle": "Tukar format dan gunakan output sebagai input", + "copy": "Salin", + "copied": "Disalin", + "charCount": "{count, plural, other {# aksara}}", + "emptyHint": "Tampal atau taip input anda di sebelah kiri untuk melihat output yang ditukar.", + "localNote": "Semua berjalan secara tempatan di pelayar anda. Tiada apa yang dimuat naik." + }, + "MimeTypeLookup": { + "title": "Carian jenis MIME", + "subtitle": "Cari jenis MIME (jenis media) untuk sambungan fail atau nama fail penuh. Semua berjalan secara tempatan di pelayar anda.", + "inputLabel": "Sambungan atau nama fail", + "placeholder": "cth .png, package.json, photo.jpeg", + "hint": "Tip: tampal sambungan (dengan atau tanpa titik) atau nama fail penuh.", + "clear": "Kosongkan", + "exampleFilename": "Guna contoh nama fail", + "exampleExtension": "Guna contoh sambungan", + "resultLabel": "Keputusan", + "normalizedLabel": "Input dinormalkan:", + "mimeTypeLabel": "Jenis MIME", + "contentTypeLabel": "Pengepala Content-Type", + "charsetLabel": "Set aksara", + "copyMimeType": "Salin jenis MIME", + "copy": "Salin", + "copied": "Disalin", + "emptyHint": "Masukkan sambungan atau nama fail untuk mencari jenis MIME.", + "notFound": "Tiada jenis MIME ditemui untuk input tersebut.", + "localNote": "Semua berjalan secara tempatan di pelayar anda. Tiada apa yang dimuat naik." + }, + "UserAgentParser": { + "title": "Penghurai User-Agent", + "subtitle": "Tampal rentetan User-Agent (UA) dan dapatkan pecahan pelayar, OS, enjin, peranti dan CPU. Semua berjalan secara tempatan di pelayar anda.", + "inputLabel": "Rentetan User-Agent", + "placeholder": "Tampal User-Agent anda di sini…", + "hint": "Tip: anda boleh salin UA daripada pengepala permintaan, log, atau devtools pelayar.", + "clear": "Kosongkan", + "exampleDesktop": "Guna contoh desktop", + "exampleMobile": "Guna contoh mudah alih", + "resultLabel": "Pecahan", + "browserLabel": "Pelayar", + "osLabel": "Sistem operasi", + "engineLabel": "Enjin", + "deviceTypeLabel": "Jenis peranti", + "deviceVendorLabel": "Pengeluar peranti", + "deviceModelLabel": "Model peranti", + "cpuArchLabel": "Seni bina CPU", + "copyJson": "Salin JSON", + "copy": "Salin", + "copied": "Disalin", + "emptyHint": "Tampal rentetan User-Agent untuk melihat pecahan.", + "localNote": "Semua berjalan secara tempatan di pelayar anda. Tiada apa yang dimuat naik." + }, + "MockDataGenerator": { + "title": "Penjana Data Mock", + "subtitle": "Takrifkan lajur, pilih jenis data, dan eksport set data untuk benih, Postman atau ujian. Sehingga {max} baris, semuanya dalam pelayar — tiada muat naik.", + "mockarooLink": "Aliran kerja ala Mockaroo", + "rowsLabel": "Baris", + "rowsHint": "Maksimum {max} baris setiap penjanaan supaya tab kekal responsif.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nama jadual SQL", + "presetsLabel": "Pratetap", + "presetPlaceholder": "Muat pratetap…", + "presetDefault": "Permulaan (pengguna)", + "presetUsers": "Pengguna & peranan", + "presetProducts": "SKU e-dagang", + "presetApiLog": "Log permintaan HTTP", + "generate": "Jana", + "warningDuplicateNames": "Nama lajur pendua: lajur kemudian menimpa yang terdahulu dalam objek JSON. Namakan semula.", + "schemaHeading": "Skema", + "addField": "Tambah lajur", + "removeField": "Buang lajur", + "duplicateField": "Duplikasi lajur", + "colName": "Nama", + "colType": "Jenis", + "colOptions": "Pilihan", + "colBlank": "Kosong %", + "optMin": "min", + "optMax": "maks", + "optDecimals": "perpuluhan", + "optDateFrom": "dari tarikh", + "optDateTo": "hingga tarikh", + "optValues": "a, b, c", + "optStep": "langkah", + "optRowBase": "asas", + "output": "Output", + "outputPlaceholder": "Klik Jana untuk membina data…", + "copy": "Salin", + "download": "Muat turun", + "copied": "Disalin", + "groups": { + "personal": "Peribadi", + "location": "Lokasi", + "internetApi": "Internet & API", + "numbersIds": "Nombor & ID", + "dateTime": "Tarikh & masa", + "textDesign": "Teks & reka bentuk", + "custom": "Tersuai" + }, + "fieldTypes": { + "first_name": "Nama pertama", + "last_name": "Nama keluarga", + "full_name": "Nama penuh", + "email": "Email", + "username": "Nama pengguna", + "password": "Kata laluan", + "phone": "Telefon", + "company": "Syarikat", + "job_title": "Jawatan", + "avatar_url": "URL avatar", + "city": "Bandar", + "country": "Negara", + "country_code": "Kod negara (ISO)", + "street_address": "Alamat", + "zip_code": "Poskod", + "latitude": "Latitud", + "longitude": "Longitud", + "url": "URL", + "domain": "Domain", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Integer", + "float": "Perpuluhan", + "boolean": "Boolean", + "uuid": "UUID", + "credit_card": "Kad kredit (ujian)", + "semver": "SemVer", + "row_index": "Indeks baris", + "sequence": "Jujukan", + "date": "Tarikh", + "datetime": "Tarikh & masa", + "unix_timestamp": "Setem masa Unix", + "timezone": "Zon masa", + "word": "Perkataan", + "sentence": "Ayat", + "paragraph": "Perenggan", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Kod mata wang", + "language_code": "Tag bahasa", + "custom_list": "Senarai tersuai" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Pengoptimum SVG", + "subtitle": "Tampal SVG, minify dengan SVGO dalam pelayar dan bandingkan saiz UTF-8 sebelum & selepas.", + "inputLabel": "Input", + "outputLabel": "Dioptimumkan", + "placeholder": "Tampal serpihan atau keseluruhan fail…", + "outputPlaceholder": "SVG teroptimum dipaparkan di sini…", + "chars": "{count} aksara • {bytes} bait (UTF-8)", + "preview": "Pratonton", + "localNote": "Semua diproses secara tempatan dalam pelayar. Tiada muat naik.", + "stats": { + "title": "Saiz (UTF-8)", + "before": "Sebelum", + "after": "Selepas", + "savedBytes": "Jimat {count} bait ({percent}%)", + "noChange": "Tiada pengurangan — sudah minimum atau output kosong." + }, + "actions": { + "clear": "Kosongkan", + "sample": "Muat contoh", + "copy": "Salin", + "copied": "Disalin", + "download": "Muat turun" + }, + "errors": { + "optimizeFailed": "Tidak dapat mengoptimumkan SVG ini." + } + }, + "UnitConverter": { + "title": "Penukar Unit", + "subtitle": "Tukar antara 323 unit merentasi 43 kategori", + "category": "Kategori", + "from": "Dari", + "to": "Ke", + "value": "Nilai", + "result": "Keputusan", + "swapUnits": "Tukar unit", + "placeholder": "Masukkan nilai", + "copyResult": "Salin keputusan", + "searchCategory": "Cari kategori...", + "selectCategory": "Pilih kategori", + "noResults": "Tiada kategori dijumpai." + }, + "DockerComposeGenerator": { + "title": "Penjana Docker Compose", + "subtitle": "Pilih pangkalan data popular, cache, baris gilir mesej, proksi songsang dan kebolehcerapan — salin docker-compose.yml untuk pembangunan setempat.", + "searchPlaceholder": "Cari (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Perkhidmatan", + "selectedCount": "{count, plural, other {# perkhidmatan}}", + "outputTitle": "docker-compose.yml", + "copy": "Salin", + "copied": "Disalin!", + "download": "Muat turun", + "emptyState": "Pilih satu atau lebih perkhidmatan untuk menjana YAML Compose.", + "hintPorts": "Port hos lalai mengurangkan konflik; ubah jika Docker melaporkan port digunakan. Ganti kata laluan demo sebelum persekitaran dikongsi.", + "categories": { + "databases": "Pangkalan data & storan", + "cache": "Cache & dalam memori", + "queues": "Baris gilir & pemesejan", + "search": "Carian & log", + "storage": "Storan objek", + "proxy": "Proksi songsang & ingress", + "observability": "Metrik & penjejakan", + "mail": "E-mel (pembangunan setempat)", + "admin": "Antara muka pentadbir & BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Kosongkan" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (serasi Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (serasi Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (serasi S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (tangkap SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (pelayan dev)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Gelongsor jubin dan gabungkannya untuk mencapai 2048", + "score": "Markah", + "best": "Terbaik", + "newGame": "Permaina_n Baru", + "howToPlay": "Gunakan kekunci anak panah (atau leret pada telefon bimbit) untuk mengalihkan jubin. Jubin dengan nombor yang sama bergabung. Capai 2048 untuk menang!", + "youWin": "Anda telah mencapai 2048!", + "keepGoing": "Teruskan", + "gameOver": "Permainan Tamat", + "tryAgain": "Cuba Sekali Lagi", + "gameOverMessage": "Tiada lagi pergerakan yang tinggal.", + "undo": "Batal" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "peringkat selesai", + "time": "Masa", + "best": "Terbaik", + "difficulty": { + "easy": "Mudah", + "medium": "Sederhana", + "hard": "Susah" + }, + "stage": "Tahap", + "solved": "TELAH DISELESAIKAN", + "score": "Markah", + "nextStage": "Peringkat seterusnya", + "replay": "Penapis", + "erase": "Padam", + "howToPlay": "Klik sel, kemudian ketik nombor atau tekan 1–9. Backspace atau Delete membersihkan sel. Nombor tebal adalah petunjuk. Isi grid supaya setiap baris, lajur, dan kotak 3×3 mengandungi 1–9 tanpa ulangan.", + "pencil": "Pencil", + "undo": "Undo", + "hint": "Hint", + "hintsUsed": "hints used", + "mistakes": "Mistakes", + "failed": "Failed!", + "mistakeMode": "3-mistake mode" + }, + "Snake": { + "title": "Ular", + "subtitle": "Makan makanan, membesar, elak dinding dan diri sendiri", + "score": "Skor", + "best": "Terbaik", + "startPrompt": "Tekan mana-mana kekunci anak panah", + "startButton": "Mulakan Permainan", + "gameOver": "Permainan Tamat", + "playAgain": "Main Semula", + "howToPlay": "Gunakan kekunci anak panah atau WASD. Makan untuk membesar. Kelajuan meningkat setiap 50 mata.", + "diffEasy": "Mudah", + "diffNormal": "Biasa", + "diffHard": "Susah", + "diffInfinite": "Tanpa Had", + "orTap": "atau ketik di bawah", + "howToPlayInfinite": "Dinding bersambung — tiada kematian dinding. Makan untuk membesar.", + "shieldActive": "Shield Active", + "speedBoost": "Speed Boost", + "powerSpeed": "Speed x2 (10 ticks)", + "powerShield": "Survive one death", + "powerShrink": "Shortens tail" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Sambungan Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Sambung ke contoh Redis. Semak kunci, edit nilai dan jalankan arahan. Kelayakan disulitkan dalam pelayar anda.", + "heroAddConnection": "Tambah Sambungan", + "vaultLockedTitle": "Peti besi dikunci", + "vaultLockedDesc": "Buka kunci peti besi untuk mengakses sambungan yang disimpan." + } + }, + "Minesweeper": { + "title": "Penyapu Min", + "subtitle": "Cari semua sel selamat tanpa meletupkan min", + "mines": "Min", + "newGame": "Permainan baharu", + "win": "Anda menang!", + "loss": "Kalah!", + "playAgain": "Main lagi", + "tryAgain": "Cuba lagi", + "startPrompt": "Klik mana-mana sel untuk mula", + "difficulty": { + "beginner": "Pemula", + "intermediate": "Pertengahan", + "expert": "Pakar" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Susun blok yang jatuh untuk melengkapkan baris", + "next": "Seterusnya", + "score": "Skor", + "best": "Terbaik", + "lines": "Baris", + "level": "Tahap", + "pause": "Jeda", + "resume": "Sambung", + "start": "Mula", + "restart": "Mulakan semula", + "paused": "Dijeda", + "gameOver": "Permainan tamat" + }, + "SshKeyGenerator": { + "title": "Penjana Kekunci SSH / RSA", + "subtitle": "Jana pasangan kekunci Ed25519 atau RSA dalam pelayar anda. Muat turun kekunci peribadi dan awam dalam format OpenSSH dan PEM. Tiada apa yang dimuat naik.", + "keyTypeLabel": "Jenis Kekunci", + "recommended": "disyorkan", + "ed25519Desc": "Pantas, kecil dan moden. Disokong oleh GitHub, GitLab dan semua pelayan SSH utama.", + "rsa2048Desc": "Serasi secara meluas. Selamat untuk kebanyakan kegunaan.", + "rsa4096Desc": "Keserasian dan kekuatan maksimum untuk sistem lama.", + "commentLabel": "Komen (pilihan)", + "commentHint": "Ditambah pada baris kekunci awam OpenSSH.", + "generateBtn": "Jana Pasangan Kekunci", + "generating": "Menjana…", + "generatingLong": "Menjana (mungkin mengambil masa sebentar)…", + "copy": "Salin", + "download": "Muat turun", + "privateKeyLabel": "Kekunci Peribadi (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Kekunci Awam (format OpenSSH)", + "publicKeySpkiLabel": "Kekunci Awam (SPKI PEM)", + "browserNote": "Semua kekunci dijana dalam pelayar anda. Tiada apa yang dihantar ke mana-mana pelayan.", + "errors": { + "generationFailed": "Penjanaan kekunci gagal" + } + }, + "DnsLookup": { + "title": "Carian DNS", + "subtitle": "Pertanyaan rekod DNS untuk mana-mana domain — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Masukkan domain (cth. github.com)", + "lookupBtn": "Cari", + "looking": "Mencari…", + "refreshBtn": "Muat semula", + "recordTypesLabel": "Jenis rekod", + "allBtn": "Semua", + "tryLabel": "Cuba:", + "errors": { + "lookupFailed": "Carian DNS gagal" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Penjana & Penyemak Bcrypt", + "subtitle": "Cincang kata laluan dengan bcrypt dan sahkan cincangan — semuanya kekal dalam pelayar anda", + "tabs": { + "hash": "Cincang", + "verify": "Sahkan" + }, + "showPassword": "Tunjukkan kata laluan", + "hidePassword": "Sembunyikan kata laluan", + "copy": "Salin", + "copied": "Disalin", + "hash": { + "passwordLabel": "Kata laluan", + "passwordPlaceholder": "Masukkan kata laluan untuk dicincang…", + "costLabel": "Faktor kos", + "costHint": "Kos lebih tinggi menggandakan kerja setiap langkah. 10-12 ialah pilihan biasa untuk pengeluaran.", + "slowWarning": "Faktor kos 13 dan ke atas boleh mengambil beberapa saat untuk dikira.", + "generate": "Jana cincangan", + "generating": "Mencincang…", + "resultLabel": "Cincangan Bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomi cincangan", + "algorithm": "algoritma", + "cost": "kos", + "salt": "salt (22 aksara)", + "hash": "cincangan (31 aksara)" + } + }, + "verify": { + "passwordLabel": "Kata laluan", + "passwordPlaceholder": "Masukkan kata laluan untuk disemak…", + "hashLabel": "Cincangan Bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Masukkan kata laluan dan cincangan bcrypt untuk membandingkannya.", + "invalidHash": "Ini tidak kelihatan seperti cincangan bcrypt yang sah.", + "checking": "Membandingkan…", + "match": "Padan — kata laluan menghasilkan cincangan ini.", + "noMatch": "Tidak padan — kata laluan tidak menghasilkan cincangan ini." + } + }, + "ChmodCalculator": { + "title": "Kalkulator Chmod", + "subtitle": "Tukar keizinan fail Unix antara kotak semak, oktal dan tatatanda simbolik", + "grid": { + "title": "Keizinan", + "read": "Baca", + "write": "Tulis", + "execute": "Laksana", + "owner": "Pemilik", + "group": "Kumpulan", + "others": "Lain-lain" + }, + "special": { + "title": "Bit khas", + "setuid": "setuid", + "setgid": "setgid", + "sticky": "sticky" + }, + "octal": { + "label": "Oktal", + "placeholder": "755 atau 4755", + "invalid": "Masukkan 3 atau 4 digit oktal (0-7)." + }, + "symbolic": { + "label": "Simbolik", + "placeholder": "rwxr-xr-x", + "invalid": "Masukkan 9 aksara seperti rwxr-xr-x (s/S, t/T dibenarkan)." + }, + "output": { + "title": "Arahan", + "copy": "Salin", + "copied": "Disalin" + }, + "explain": { + "line": "{cls} boleh {perms}.", + "none": "{cls} tiada sebarang keizinan.", + "setuid": "setuid: fail berjalan dengan keistimewaan pemiliknya.", + "setgid": "setgid: fail berjalan dengan keistimewaan kumpulannya (fail baharu dalam direktori mewarisi kumpulannya).", + "sticky": "sticky: hanya pemilik fail boleh memadam atau menamakan semulanya di dalam direktori ini." + }, + "presets": { + "title": "Praset lazim", + "octal": "Oktal", + "permissions": "Keizinan", + "description": "Kegunaan tipikal", + "p644": "Fail biasa: pemilik menulis, semua orang membaca.", + "p755": "Boleh laku dan direktori: semua orang boleh baca dan masuk.", + "p600": "Fail peribadi: hanya pemilik membaca dan menulis.", + "p700": "Boleh laku dan direktori peribadi: pemilik sahaja.", + "p664": "Fail boleh ditulis oleh kumpulan.", + "p775": "Direktori dan boleh laku yang boleh ditulis oleh kumpulan.", + "p400": "Baca sahaja untuk pemilik (cth. kunci peribadi SSH)." + } + }, + "CodeScreenshot": { + "title": "Tangkap Skrin Kod", + "subtitle": "Tukar kod menjadi imej cantik yang boleh dikongsi", + "languageLabel": "Bahasa", + "autoDetect": "Auto kesan", + "backgroundLabel": "Latar belakang", + "paddingLabel": "Pelapik", + "filenameLabel": "Nama fail", + "filenamePlaceholder": "example.ts", + "windowLabel": "Titik tetingkap", + "themeLabel": "Tema tetingkap", + "themeDark": "Gelap", + "themeLight": "Cerah", + "scaleLabel": "Skala eksport", + "copyImage": "Salin imej", + "copied": "Disalin", + "exportPng": "Eksport PNG", + "inputLabel": "Kod", + "detected": "Dikesan: {language}", + "clear": "Kosongkan", + "placeholder": "Tampal kod anda di sini…", + "previewLabel": "Pratonton", + "emptyPreview": "Pratonton kod anda akan muncul di sini", + "exportFailed": "Gagal mengeksport imej", + "copyUnsupported": "Menyalin imej tidak disokong dalam pelayar ini", + "copyFailed": "Gagal menyalin imej", + "backgrounds": { + "ocean": "Lautan", + "sunset": "Senja", + "candy": "Gula-gula", + "forest": "Hutan", + "midnight": "Tengah malam", + "gold": "Emas", + "aurora": "Aurora", + "slate": "Batu tulis", + "transparent": "Lutsinar" + } + }, + "CssGenerators": { + "title": "Penjana CSS", + "subtitle": "box-shadow, border-radius dan clamp() bendalir — salin CSS.", + "preview": "Pratonton", + "output": "CSS", + "copy": "Salin", + "copied": "Disalin", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() bendalir" + }, + "boxShadow": { + "presets": "Praset", + "layer": "Lapisan", + "addLayer": "Tambah lapisan", + "removeLayer": "Buang lapisan", + "x": "X", + "y": "Y", + "blur": "Kabur", + "spread": "Sebaran", + "color": "Warna", + "alpha": "Alfa", + "inset": "Terbenam", + "presetNames": { + "subtleCard": "Kad halus", + "elevated": "Tinggi", + "hardOffset": "Ofset tegas", + "neonGlow": "Sinar neon", + "inner": "Dalaman", + "layeredSmooth": "Berlapis licin" + } + }, + "borderRadius": { + "modeLabel": "Mod", + "modes": { + "simple": "Setiap sudut", + "blob": "Blob organik" + }, + "unit": "Unit", + "linkCorners": "Pautkan sudut", + "randomize": "Rawakkan", + "horizontal": "Jejari mendatar", + "vertical": "Jejari menegak", + "corners": { + "tl": "Kiri atas", + "tr": "Kanan atas", + "br": "Kanan bawah", + "bl": "Kiri bawah" + } + }, + "clamp": { + "minFont": "Fon min (px)", + "maxFont": "Fon maks (px)", + "minViewport": "Viewport min (px)", + "maxViewport": "Viewport maks (px)", + "rootFont": "Fon akar (px)", + "viewportWidth": "Viewport simulasi", + "computedSize": "Saiz terkira", + "slope": "Cerun", + "intercept": "Pintasan", + "previewText": "Teks contoh pratonton", + "invalid": "Masukkan had viewport yang sah dan berbeza." + } + }, + "ExifViewer": { + "title": "Pemapar & Pembuang EXIF", + "subtitle": "Periksa metadata foto dan buang ia dalam pelayar anda", + "dropTitle": "Lepaskan imej di sini", + "dropHint": "atau klik untuk semak lalu — imej anda tidak pernah meninggalkan peranti anda", + "browse": "Pilih imej", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Membaca metadata…", + "parseFailed": "Gagal membaca imej ini", + "clear": "Buang imej", + "gpsWarning": "Mengandungi lokasi GPS", + "openMap": "Lihat pada OpenStreetMap", + "noMetadata": "Tiada metadata terbenam ditemui dalam imej ini.", + "facts": { + "name": "Nama", + "type": "Jenis", + "dimensions": "Dimensi", + "size": "Saiz" + }, + "groups": { + "camera": "Kamera", + "exposure": "Dedahan", + "timestamps": "Cap masa", + "gps": "Lokasi GPS", + "software": "Perisian & Lain-lain" + }, + "gpsCard": { + "latitude": "Latitud", + "longitude": "Longitud", + "altitude": "Altitud" + }, + "raw": { + "title": "Semua metadata", + "copyJson": "Salin JSON", + "copied": "Disalin" + }, + "remove": { + "title": "Buang metadata", + "jpegDescription": "Buang EXIF, XMP, IPTC dan komen tanpa kehilangan dan tanpa memampatkan semula imej.", + "reencodeDescription": "Kod semula imej untuk membuang metadata terbenam. Ini memampatkan semula piksel.", + "keepIcc": "Kekalkan profil warna (ICC)", + "stripButton": "Buang metadata", + "reencodeButton": "Kod semula & buang", + "unsupported": "Pembuangan metadata tidak disokong untuk jenis fail ini.", + "failed": "Gagal membuang metadata", + "before": "Sebelum", + "after": "Selepas", + "download": "Muat turun imej yang dibersihkan" + } + }, + "FaviconGenerator": { + "title": "Penjana Favicon", + "subtitle": "Cipta favicon dan ikon aplikasi daripada satu imej", + "dropTitle": "Lepaskan imej di sini", + "dropHint": "PNG, JPEG atau SVG — klik untuk semak lalu", + "browse": "Pilih imej", + "clear": "Buang imej", + "backgroundLabel": "Latar belakang", + "transparent": "Lutsinar", + "solidColor": "Warna pejal", + "cornerLabel": "Bentuk sudut", + "cornerNone": "Segi empat sama", + "cornerRounded": "Bulat", + "cornerCircle": "Bulatan", + "maskPaddingLabel": "Pelapik boleh topeng", + "maskable": "maskable", + "previewLabel": "Pratonton", + "copy": "Salin", + "headSnippet": "Cebisan HTML head", + "downloadIco": "Muat turun favicon.ico", + "downloadZip": "Muat turun semua (.zip)" + }, + "JsonDiff": { + "title": "Beza JSON", + "subtitle": "Perbandingan bermakna dan peka struktur bagi dua dokumen JSON — susunan kunci dan pemformatan diabaikan", + "options": { + "ignoreArrayOrder": "Abaikan susunan tatasusunan" + }, + "loadBothSamples": "Muat contoh", + "sample": "Contoh", + "format": "Format", + "clear": "Kosongkan", + "formatError": "Tidak boleh format: input bukan JSON yang sah", + "placeholder": "Tampal atau taip JSON di sini…", + "panels": { + "left": "Asal (kiri)", + "right": "Diubah suai (kanan)", + "results": "Perbezaan" + }, + "summary": { + "added": "{count} ditambah", + "removed": "{count} dibuang", + "changed": "{count} berubah" + }, + "types": { + "added": "Ditambah", + "removed": "Dibuang", + "changed": "Berubah" + }, + "copyChanges": "Salin sebagai JSON", + "copied": "Disalin", + "empty": "Tampal JSON ke dalam kedua-dua panel untuk melihat perbezaannya.", + "identical": "Kedua-dua dokumen adalah sama dari segi makna." + }, + "JsonpathPlayground": { + "title": "Ruang Uji JSONPath", + "subtitle": "Kueri dokumen JSON dengan JSONPath atau JMESPath dan lihat keputusan secara langsung", + "engineLabel": "Enjin", + "queryLabel": "Kueri", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Helaian rujukan", + "cheats": { + "jpAllTitles": "Semua tajuk buku", + "jpAllAuthors": "Setiap pengarang, di mana-mana dalam dokumen", + "jpFirstBook": "Buku pertama", + "jpLastBook": "Buku terakhir", + "jpSlice": "Dua buku pertama (hirisan)", + "jpFilterPrice": "Buku lebih murah daripada 40", + "jpFilterInStock": "Tajuk buku yang ada dalam stok", + "jpRecursive": "Semua nilai, secara rekursif", + "jmAllTitles": "Semua tajuk buku", + "jmFirstAuthor": "Pengarang buku pertama", + "jmLastBook": "Buku terakhir", + "jmSlice": "Dua buku pertama (hirisan)", + "jmFilterPrice": "Tajuk buku lebih murah daripada 40", + "jmCount": "Kira buku dalam stok", + "jmProjection": "Bentuk semula setiap buku menjadi objek baharu", + "jmSort": "Tajuk disusun mengikut harga" + }, + "panels": { + "input": "Input JSON", + "results": "Keputusan" + }, + "sample": "Contoh", + "format": "Format", + "clear": "Kosongkan", + "formatError": "Tidak boleh format: input bukan JSON yang sah", + "placeholder": "Tampal atau taip JSON di sini…", + "outputPlaceholder": "Padanan akan muncul di sini…", + "matchCount": "{count, plural, one {# padanan} other {# padanan}}", + "copy": "Salin", + "copied": "Disalin" + }, + "KeycodeInspector": { + "title": "Pemeriksa Kod Kekunci", + "subtitle": "Tekan mana-mana kekunci untuk memeriksa peristiwa papan kekunci JavaScriptnya", + "capture": { + "prompt": "Tekan mana-mana kekunci…", + "ready": "Mendengar — tekan mana-mana kekunci pada papan kekunci anda", + "clickToFocus": "Klik di sini untuk memfokus, kemudian tekan mana-mana kekunci" + }, + "hero": { + "spaceKey": "(Space)", + "keyLabel": "event.key", + "repeat": "ulang" + }, + "copyJson": "Salin JSON", + "copiedJson": "JSON peristiwa disalin ke papan keratan", + "deprecated": "usang", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Piawai", + "left": "Kiri", + "right": "Kanan", + "numpad": "Pad nombor", + "unknown": "Tidak diketahui" + }, + "history": { + "title": "Sejarah", + "clear": "Kosongkan sejarah", + "empty": "Tekanan kekunci akan muncul di sini." + } + }, + "MarkdownTableGenerator": { + "title": "Penjana Jadual Markdown", + "subtitle": "Sunting grid, tampal daripada hamparan dan salin jadual Markdown atau HTML yang tersusun kemas", + "addRow": "Tambah baris", + "addColumn": "Tambah lajur", + "importLabel": "Import", + "importPlaceholder": "Tampal TSV (dari Excel / Google Sheets) atau CSV di sini…", + "importButton": "Import ke dalam grid", + "pasteClipboard": "Tampal dari papan keratan", + "importEmpty": "Tiada apa untuk diimport — tampal beberapa baris dahulu", + "clipboardError": "Tidak dapat membaca daripada papan keratan", + "clear": "Kosongkan", + "panels": { + "editor": "Penyunting jadual", + "output": "Output" + }, + "align": { + "left": "Jajar kiri", + "center": "Jajar tengah", + "right": "Jajar kanan" + }, + "headerPlaceholder": "Pengepala", + "cellPlaceholder": "Sel", + "removeRow": "Buang baris", + "removeColumn": "Buang lajur", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Salin", + "copied": "Disalin" + }, + "TimezoneConverter": { + "title": "Penukar Zon Waktu", + "subtitle": "Tukar satu detik merentas dunia untuk perancangan mesyuarat.", + "dateTimeLabel": "Tarikh & masa", + "now": "Sekarang", + "sourceZoneLabel": "Zon waktu sumber", + "selectZone": "Pilih zon", + "searchZone": "Cari zon waktu...", + "noZone": "Tiada zon waktu ditemui.", + "scrubLabel": "Anjak masa", + "reset": "Set semula", + "targetsLabel": "Zon sasaran", + "addZone": "Tambah zon", + "remove": "Buang", + "localBadge": "Setempat", + "invalidDate": "Masukkan tarikh dan masa yang sah.", + "bands": { + "work": "Waktu kerja (09:00–18:00)", + "edge": "Waktu sempadan (awal pagi / petang)", + "night": "Malam" + } + }, + "TokenCounter": { + "title": "Pengira Token LLM", + "subtitle": "Kira token, visualkan cebisan dan anggarkan kos API.", + "inputLabel": "Teks", + "sample": "Contoh", + "clear": "Kosongkan", + "placeholder": "Tampal teks, kod atau JSON untuk mengira token...", + "encoding": "Pengekodan", + "loading": "Memuatkan tokenizer...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-series)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Token", + "characters": "Aksara", + "words": "Perkataan", + "ratio": "Token/perkataan", + "ratioSub": "anggaran" + }, + "claudeRow": "Claude (anggaran)", + "estimate": "anggaran", + "viz": { + "title": "Visualisasi token", + "toggle": "Tunjuk", + "hint": "Togol untuk mewarnakan setiap token sebagai satu cebisan.", + "empty": "Masukkan teks untuk memvisualkan token.", + "truncated": "Memaparkan {limit} pertama daripada {total} token." + }, + "cost": { + "title": "Kalkulator kos", + "disclaimer": "nilai lalai boleh disunting — sahkan harga semasa", + "model": "Model", + "expectedOutput": "Token output dijangka", + "customInput": "Input $/MTok", + "customOutput": "Output $/MTok", + "inputCost": "Kos input", + "outputCost": "Kos output", + "total": "Jumlah", + "per1000": "Setiap 1000 panggilan" + } + }, + "WebhookTester": { + "title": "Penguji Webhook", + "subtitle": "Cipta URL sementara dan periksa permintaan webhook masuk secara langsung.", + "intro": { + "title": "Cipta URL webhook untuk mula menangkap permintaan", + "hint": "Anda akan mendapat URL yang tidak boleh diteka yang merekod setiap permintaan masuk. Ia luput selepas 24 jam." + }, + "createBtn": "Cipta URL webhook", + "creating": "Mencipta…", + "urlLabel": "URL webhook anda", + "expiresIn": "Luput dalam {countdown}", + "copyUrlBtn": "Salin URL", + "copied": "Disalin", + "clearBtn": "Kosongkan", + "newBinBtn": "URL baharu", + "requestsTitle": "Permintaan", + "selectPrompt": "Pilih satu permintaan untuk memeriksa butirannya.", + "expiredNotice": "URL webhook ini telah luput. Cipta yang baharu untuk terus menguji.", + "base64Badge": "base64", + "truncatedBadge": "dipangkas", + "empty": { + "title": "Menunggu permintaan…", + "hint": "Hantar permintaan ke URL anda dan ia akan muncul di sini serta-merta. Cuba ini:" + }, + "detail": { + "receivedAt": "Diterima", + "source": "IP sumber", + "contentType": "Content-Type", + "query": "Parameter kueri", + "headers": "Pengepala", + "body": "Badan", + "copyBody": "Salin badan", + "emptyBody": "Tiada badan" + }, + "errors": { + "createFailed": "Gagal mencipta URL webhook. Sila cuba lagi.", + "clearFailed": "Gagal mengosongkan permintaan. Sila cuba lagi." + } + }, + "WebsocketTester": { + "title": "Penguji WebSocket", + "subtitle": "Sambung ke titik akhir WebSocket dan hantar, terima serta periksa mesej secara langsung", + "urlLabel": "URL titik akhir", + "protocolsLabel": "Subprotokol", + "protocolsPlaceholder": "pilihan, dipisahkan koma", + "connect": "Sambung", + "disconnect": "Putuskan sambungan", + "status": { + "disconnected": "Terputus", + "connecting": "Menyambung", + "connected": "Tersambung", + "error": "Ralat" + }, + "messages": { + "connecting": "Menyambung ke {url}…", + "connected": "Sambungan diwujudkan", + "connectedWithProtocol": "Sambungan diwujudkan (protokol: {protocol})", + "invalidUrl": "URL tidak sah — mesti bermula dengan ws:// atau wss://", + "connectFailed": "Gagal membuka sambungan", + "socketError": "Ralat sambungan berlaku", + "notConnected": "Tidak tersambung — sambung dahulu", + "binaryMessage": "Mesej binari diterima" + }, + "log": { + "title": "Log Mesej", + "empty": "Belum ada mesej. Sambung dan mula menghantar.", + "copyAll": "Salin semua", + "download": "Muat turun sebagai .txt", + "clear": "Kosongkan log", + "prettyPrint": "Format JSON", + "showRaw": "Tunjuk teks mentah" + }, + "composer": { + "placeholder": "Taip mesej untuk dihantar…", + "send": "Hantar", + "formatJson": "Format sebagai JSON", + "invalidJson": "Mesej bukan JSON yang sah", + "hint": "Cmd/Ctrl+Enter untuk hantar" + }, + "ping": { + "label": "Auto-ping", + "seconds": "saat", + "intervalTitle": "Selang ping dalam saat", + "payloadPlaceholder": "muatan ping" + } + }, + "WhoisLookup": { + "title": "Carian Whois", + "subtitle": "Cari butiran pendaftaran bagi mana-mana domain atau alamat IP melalui RDAP", + "placeholder": "example.com atau 8.8.8.8", + "lookupBtn": "Cari", + "looking": "Mencari…", + "refreshBtn": "Muat semula", + "tryLabel": "Cuba:", + "copy": "Salin", + "copied": "Disalin", + "notAvailable": "Tidak tersedia", + "summary": { + "registrar": "Pendaftar", + "network": "Rangkaian" + }, + "dates": { + "created": "Dicipta", + "updated": "Dikemas kini", + "expires": "Luput" + }, + "sections": { + "status": "Status", + "nameservers": "Pelayan nama", + "contacts": "Kenalan", + "rawRdap": "RDAP mentah" + }, + "contacts": { + "roles": "Peranan", + "name": "Nama", + "organization": "Organisasi" + }, + "errors": { + "invalidTarget": "Masukkan nama domain atau alamat IP yang sah", + "lookupFailed": "Carian gagal (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/nb.json b/apps/desktop-ui/messages/nb.json new file mode 100644 index 00000000..736722fa --- /dev/null +++ b/apps/desktop-ui/messages/nb.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Dashbord", + "settings": "Innstillinger", + "apiClient": "API-klient", + "httpStatusCodes": "HTTP-statuskoder", + "passwordManager": "Passordbehandler", + "environmentManager": "Miljøbehandler", + "bookmarks": "Bokmerker", + "jsonFormatter": "JSON-formaterer", + "emailValidator": "E-postvalidering", + "nosqlExplorer": "Databaseutforsker", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Krypteringslekeplass", + "jwtDecoder": "JWT-dekoder", + "certificatePemDecoder": "Sertifikat- / PEM-dekoder", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL-formaterer", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL-parser", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Hemmelighet / API-nøkkel", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Kontrastkontroll", + "jsonSchemaGenerator": "JSON-skjemagenerator", + "imageToBase64": "Bilde til Base64", + "imageCompressor": "Bildekomprimering", + "cssGradientBuilder": "CSS Gradient Builder", + "gitignoreGenerator": ".gitignore Generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Kodeutdrag", + "qrCodeGenerator": "QR-kodegenerator", + "dnsLookup": "DNS-oppslag", + "ipSubnetCalculator": "IP-/subnettkalkulator", + "hashGenerator": "Hash-generator", + "hmacGenerator": "HMAC-generator", + "totpGenerator": "TOTP / 2FA-kode", + "markdownPreview": "Markdown-forhåndsvisning", + "numberBaseConverter": "Grunntall-konverter", + "formatConverter": "Formatkonverterer", + "mockDataGenerator": "Mock-datagenerator", + "svgOptimizer": "SVG-optimalisering", + "unitConverter": "Enhetsomregner", + "dockerComposeGenerator": "Docker Compose-generator", + "breakRoom": "Pauserom", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAML-formaterer", + "sshKeyGenerator": "SSH / RSA nøkkelgenerator", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod-kalkulator", + "codeScreenshot": "Kodeskjermbilde", + "cssGenerators": "CSS-generatorer", + "exifViewer": "EXIF-visning", + "faviconGenerator": "Favicon-generator", + "jsonDiff": "JSON-diff", + "jsonpathPlayground": "JSONPath-lekeplass", + "keycodeInspector": "Tastekode-inspektør", + "markdownTableGenerator": "Markdown-tabell", + "timezoneConverter": "Tidssonekonverterer", + "tokenCounter": "Token-teller", + "webhookTester": "Webhook-tester", + "websocketTester": "WebSocket-tester", + "whoisLookup": "Whois-oppslag" + }, + "Help": { + "title": "Hjelp og dokumentasjon", + "subtitle": "Hvordan MyDevTools henger sammen, hva hver app gjør, og hvordan sikkerhet og kryptering beskytter dataene dine.", + "tabs": { + "overview": "Oversikt", + "apps": "Apper", + "security": "Sikkerhet", + "tips": "Tips" + }, + "overview": { + "whatIs": { + "title": "Hva er MyDevTools?", + "description": "Et utvikler‑workspace med innlogging: et dashbord og et sett med verktøy for hverdagsoppgaver.", + "p1": "Når du logger inn, oppretter appen en økt med MyDevTools API slik at verktøy kan laste og lagre dataene dine. Sidebaren viser alle apper; du kan skjule verktøy du ikke bruker i", + "settingsLink": "Innstillinger", + "p1After": ".", + "p2": "Noen funksjoner bruker et hovedpassord (eller global vault‑nøkkel) slik at hemmeligheter som passord og tilkoblingsstrenger krypteres på enheten din før de når serveren." + }, + "quickLinks": { + "title": "Hurtiglenker", + "dashboard": "Dashbord", + "settings": "Innstillinger" + } + }, + "apps": { + "intro": "Åpne et verktøy fra sidebaren eller dashbordet. Nedenfor er en kort guide per app.", + "open": "Åpne", + "howItWorks": "Slik fungerer det", + "generic": "Bruk dette verktøyet fra sidebaren for den dedikerte arbeidsflyten." + }, + "security": { + "session": { + "title": "Innlogging og API‑økt", + "description": "Identitet og hvordan nettleseren snakker med backenden din.", + "items": { + "authentication": "Autentisering bruker Firebase for innlogging. Etter innlogging bytter appen Firebase ID‑tokenet ditt mot API‑cookies.", + "accessTokens": "Tilgangstoken er JWT-er signert på serveren (HS256) og beviser hvilken bruker som kaller API-et.", + "refreshTokens": "Oppfriskningstoken lagres i HttpOnly cookies; serveren lagrer en SHA-256 hash av tokenet, ikke selve tokenet." + } + }, + "encryption": { + "title": "Kryptering du bør kjenne til", + "description": "Klientside‑krypto for vault og sensitive tilkoblingsdata.", + "p1": "For passordbehandleren, miljøbehandleren og lagrede tilkoblinger i NoSQL Explorer bruker webappen nettleserens Web Crypto API:", + "items": { + "kdf": "PBKDF2 med SHA-256, 100 000 iterasjoner og tilfeldig salt avleder en 256-bit nøkkel fra hovedpassordet ditt.", + "aes": "AES-256-GCM krypterer data; en tilfeldig 12-byte IV brukes for hver kryptering.", + "scope": "Den avledede nøkkelen blir i nettleseren (ikke-eksporterbar). Serveren mottar kun ciphertext og IV-er, ikke hovedpassordet ditt eller hemmeligheter i klartekst." + }, + "warning": "Hvis du glemmer hovedpassordet, kan krypterte data ikke gjenopprettes. Oppbevar det trygt." + }, + "logout": { + "title": "Logg ut og lokale data", + "body": "Når du logger ut, ryddes hemmeligheter i minnet, vault‑status og (der det gjelder) hovednøkkelmateriale fra IndexedDB, og API‑økten avsluttes. Data som bare ligger i lokal lagring kan bli værende til du sletter det i nettleseren." + } + }, + "tips": { + "title": "Bruk MyDevTools sikkert", + "items": { + "masterPassword": "Bruk et sterkt og unikt hovedpassord for vault‑funksjoner.", + "apiClient": "Behandle API‑klienten som enhver HTTP‑klient: unngå å lime inn produksjonshemmeligheter i delte miljøer hvis du ikke vet hvem som kan se dem.", + "nosql": "Databaseutforskeren kobler til databaser du konfigurerer; bruk brukere med minst mulig rettigheter og nettverksregler når mulig.", + "json": "JSON‑formatereren kjører lokalt i nettleseren—unngå likevel å lime inn svært sensitive data på delte maskiner.", + "settings": "Tilpass synlige verktøy i Innstillinger for å holde sidebaren fokusert." + } + } + }, + "Settings": { + "title": "Innstillinger", + "language": "Språk", + "spanish": "Spansk", + "english": "Engelsk", + "french": "Français", + "arabic": "عربى", + "catalan": "Katalansk", + "chineseSimplified": "Kinesisk (forenklet)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Tysk", + "danish": "Dansk", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukrainsk", + "polish": "Polsk" + }, + "SettingsPage": { + "title": "Innstillinger", + "subtitle": "Administrer kontoinnstillinger og preferanser.", + "userProfile": { + "title": "Brukerprofil", + "description": "Din personlige informasjon og kontodetaljer.", + "avatarAlt": "Bruker", + "anonymousUser": "Anonym bruker", + "noEmailProvided": "Ingen e-post oppgitt", + "notLoggedIn": "Ikke innlogget." + }, + "tools": { + "title": "Verktøy og funksjoner", + "description": "Velg hvilke verktøy som vises i navigasjonssidefeltet." + }, + "appearance": { + "title": "Utseende", + "description": "Tilpass hvordan applikasjonen ser ut på enheten din.", + "themePreference": "Temapreferanse", + "themes": { + "light": "Lys", + "dark": "Mørk", + "system": "System" + } + }, + "language": { + "title": "Språk", + "description": "Angi språket som brukes på plattformen.", + "selectLanguage": "Velg språk", + "selectPlaceholder": "Velg et språk", + "helpText": "Endring av språk vil oppdatere menyer og etiketter på hele plattformen.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spansk)", + "ar": "عربى (Arabisk)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Gresk)", + "de": "Deutsch (Tysk)", + "da": "Dansk", + "af": "Afrikaans", + "fa": "فارسی", + "nb": "Norsk", + "id": "Bahasa Indonesia", + "it": "Italiensk (Italiano)", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "God morgen", + "afternoon": "God ettermiddag", + "evening": "God kveld" + }, + "commaName": ", {name}", + "welcomeBack": "Velkommen tilbake", + "welcomeBackNamed": "Velkommen tilbake, {name}", + "tagline": "Hva vil du bygge i dag?", + "brandName": "MyDevTools", + "stats": { + "tools": "Verktøy", + "favorites": "Favoritter", + "pinned": "Festet", + "recent": "Nylig brukt" + }, + "sections": { + "recentlyUsed": "Nylig brukt", + "favorites": "Favoritter", + "whatsNew": "Nyheter", + "pinned": "Festet" + }, + "groups": { + "apps": "Apper" + }, + "toolCard": { + "defaultDescription": "Utforsk dette verktøyet for bedre funksjonalitet.", + "launchTool": "Start verktøy" + }, + "tools": { + "toDo": { + "title": "Oppgaver", + "description": "Administrer oppgavelistene dine effektivt." + }, + "notes": { + "title": "Notater", + "description": "Fang opp ideer og organiser tankene dine." + }, + "passwordManager": { + "title": "Passordbehandler", + "description": "Lagre og administrer passordene dine sikkert." + }, + "environmentManager": { + "title": "Miljøbehandler", + "description": "Lagre .env-lignende variabler per prosjekt og miljø med tagger." + }, + "bookmarks": { + "title": "Bokmerker", + "description": "Administrer og organiser bokmerkene dine." + }, + "apiClient": { + "title": "API-klient", + "description": "Test og feilsøk HTTP-forespørsler." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Utforsk MongoDB-databaser." + }, + "emailValidator": { + "title": "E-postvalidering", + "description": "Verifiser og valider e-postadresser." + }, + "encryptionPlayground": { + "title": "Krypteringslekeplass", + "description": "AES-GCM med rå nøkkel eller passordfrase; JSON-pakker, Web Crypto kun i nettleseren." + }, + "jwtDecoder": { + "title": "JWT-dekoder", + "description": "Dekod JWT-header, payload og utløp." + }, + "certificatePemDecoder": { + "title": "Sertifikat- / PEM-dekoder", + "description": "Dekod X.509-sertifikater og CSR-er: emne, utsteder, gyldighet og SAN." + }, + "sshKeyGenerator": { + "title": "SSH / RSA-nøkkelgenerator", + "description": "Generer Ed25519- eller RSA-nøkkelpar i nettleseren. Last ned i OpenSSH- og PEM-format." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL-formaterer", + "description": "Formater GraphQL med syntaksutheving og en enkel spørringsbygger." + }, + "yamlFormatter": { + "title": "YAML-formaterer", + "description": "Formater, valider og konverter YAML til JSON i nettleseren." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL-parser", + "description": "Del opp en URL i protokoll, vert, sti, spørringsparametre og hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Hemmelighet / API-nøkkel", + "description": "Kryptografisk tilfeldige strenger med konfigurerbart alfabet, lengde og masseeksport." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Kontrastkontroll", + "description": "WCAG 2.1-kontrastforhold mellom tekst og bakgrunn (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "JSON-skjemagenerator", + "description": "Utled et JSON-skjema og typede modeller fra eksempel-JSON." + }, + "imageToBase64": { + "title": "Bilde til Base64", + "description": "Konverter bilder til data-URI eller rå Base64-strenger." + }, + "imageCompressor": { + "title": "Bildekomprimering", + "description": "Komprimer JPEG, PNG og WebP lokalt med kvalitetsglidebryter." + }, + "cssGradientBuilder": { + "title": "CSS Gradient Builder", + "description": "Visuell CSS-gradientbygger med vinkelkontroll og fargestopper." + }, + "gitignoreGenerator": { + "title": ".gitignore Generator", + "description": "Generer sammenslåtte .gitignore-filer umiddelbart fra 500+ teknologistabler." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Konverter CSV- eller Excel-filer til JSON og eksporter tilbake til CSV eller XLSX." + }, + "snippetManager": { + "title": "Kodeutdrag", + "description": "Lagre utdrag med syntaksutheving, automatisk språkgjenkjenning og formatering." + }, + "qrCodeGenerator": { + "title": "QR-kodegenerator", + "description": "Lag PNG QR-koder fra tekst eller URL i nettleseren." + }, + "ipSubnetCalculator": { + "title": "IP-/subnettkalkulator", + "description": "IPv4 og IPv6 CIDR: nettmaske, intervaller og adresser." + }, + "dnsLookup": { + "title": "DNS-oppslag", + "description": "Spør etter DNS-poster for et hvilket som helst domene — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Hash-generator", + "description": "MD5, SHA-1 og SHA-2 for tekst eller filer." + }, + "hmacGenerator": { + "title": "HMAC-generator", + "description": "Signer webhooks og test API-integrasjoner med HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "TOTP / 2FA-kodegenerator", + "description": "Lim inn en Base32-hemmelighet fra autentiseringsappen; seksifret kode oppdateres hvert 30. sekund—nyttig for å teste innlogging og MFA." + }, + "markdownPreview": { + "title": "Markdown-forhåndsvisning", + "description": "Live Markdown-renderer med HTML-eksport og HTML → Markdown-konvertering." + }, + "numberBaseConverter": { + "title": "Grunntall-konverter", + "description": "Konverter heltall mellom baser 2–36." + }, + "formatConverter": { + "title": "Formatkonverterer", + "description": "Konverter mellom JSON, YAML, TOML og XML i alle retninger. Alle 12 kombinasjoner støttes." + }, + "mockDataGenerator": { + "title": "Mock-datagenerator", + "description": "Definer kolonner, velg datatyper og eksporter datasett for seeds, Postman eller tester. Opptil 5000 rader, alt i nettleseren — ingenting lastes opp." + }, + "httpStatusCodes": { + "title": "HTTP-statuskoder", + "description": "Søk etter HTTP-statuskoder med beskrivelser (1xx–5xx)." + }, + "svgOptimizer": { + "title": "SVG-optimalisering", + "description": "Lim inn SVG, fjern støy med SVGO og se UTF-8-størrelsesbesparelse." + }, + "unitConverter": { + "title": "Enhetsomregner", + "description": "Konverter mellom 323 enheter på tvers av 43 vitenskapelige og tekniske kategorier." + }, + "dockerComposeGenerator": { + "title": "Docker Compose-generator", + "description": "Velg databaser, hurtigminne, køer, proxyer og observabilitetsbilder; eksporter docker-compose.yml for lokale stakk." + }, + "game2048": { + "title": "2048", + "description": "Skyv og slå sammen brikker for å nå 2048. Klassisk tallpuslespill." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klassisk tallpuslespill. 3 vanskelighetsgrader, 150 nivåer hver." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Bla gjennom nøkler, undersøk verdier og kjør kommandoer på enhver Redis-instans." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt-generator og -kontroll", + "description": "Hash passord med bcrypt, og verifiser eksisterende hasher." + }, + "chmodCalculator": { + "title": "Chmod-kalkulator", + "description": "Konverter Unix-tillatelser mellom oktal, symbolsk notasjon og avkrysningsbokser." + }, + "codeScreenshot": { + "title": "Kodeskjermbilde", + "description": "Gjør kildekode om til vakre, delbare bilder." + }, + "cssGenerators": { + "title": "CSS-generatorer", + "description": "Generatorer for box-shadow, border-radius og flytende clamp()." + }, + "exifViewer": { + "title": "EXIF-visning og -fjerning", + "description": "Inspiser og fjern EXIF-metadata fra bilder lokalt." + }, + "faviconGenerator": { + "title": "Favicon-generator", + "description": "Generer favicons, app-ikoner og manifest-utdrag." + }, + "jsonDiff": { + "title": "JSON-diff", + "description": "Semantisk, strukturbevisst sammenligning av to JSON-dokumenter." + }, + "jsonpathPlayground": { + "title": "JSONPath-lekeplass", + "description": "Spør JSON med JSONPath eller JMESPath, og se resultater live." + }, + "keycodeInspector": { + "title": "Tastekode-inspektør", + "description": "Trykk på en tast for å se JavaScript-hendelseskodene og modifikatorene dens." + }, + "markdownTableGenerator": { + "title": "Markdown-tabellgenerator", + "description": "Bygg Markdown- og HTML-tabeller i en regnearklignende rutenettredigerer." + }, + "timezoneConverter": { + "title": "Tidssonekonverterer", + "description": "Konverter et tidspunkt på tvers av mange soner med dagsforskyvning og arbeidstidsmarkører." + }, + "tokenCounter": { + "title": "LLM-token-teller", + "description": "Tell LLM-tokens, visualiser chunker, og estimer API-kostnad." + }, + "webhookTester": { + "title": "Webhook-tester", + "description": "Fang opp og inspiser innkommende webhook-forespørsler i sanntid." + }, + "websocketTester": { + "title": "WebSocket-tester", + "description": "Koble til WebSocket-endepunkter, og utveksle meldinger i sanntid." + }, + "whoisLookup": { + "title": "Whois-oppslag", + "description": "Slå opp registreringsdetaljer for domene og IP via RDAP." + } + }, + "tabs": { + "apps": "Apper", + "analytics": "Analyse", + "ui": "Grensesnitt" + }, + "analytics": { + "title": "Dataene dine i et øyeblikk", + "subtitle": "Tall synkronisert med kontoen din på serveren.", + "loadError": "Kunne ikke laste analyse.", + "retry": "Prøv igjen", + "loginHint": "Logg inn for å se bruksanalyse og grensesnittpreferanser.", + "passwordEntries": "Passordoppføringer", + "bookmarks": "Bokmerker", + "bookmarkFolders": "Bokmerkemapper", + "tasksTotal": "Oppgaver", + "tasksCompleted": "Fullført", + "tasksOngoing": "Pågående", + "tasksNotStarted": "Ikke startet", + "projects": "Prosjekter", + "nosqlConnections": "DB-tilkoblinger", + "notes": "Notater", + "apiClientCollections": "API-samlinger", + "apiClientEnvironments": "API-miljøer", + "apiClientHistory": "API-historikkoppføringer", + "jsonFormatterDocuments": "JSON-formateringsdokumenter", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Kodeutdrag", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Grensesnittpreferanser", + "subtitle": "Tema, språk og synlige verktøy i sidefeltet — fra kontoinnstillingene.", + "loadError": "Kunne ikke laste preferanser.", + "loginHint": "Logg inn for å se lagrede grensesnittpreferanser.", + "theme": "Tema", + "accent": "Aksentfarge", + "locale": "Språk", + "enabledTools": "Synlige verktøy", + "favoriteTools": "Favorittverktøy", + "toolsWithUsage": "Verktøy med bruksstatistikk" + }, + "signIn": "Logg inn", + "noResults": "Ingen verktøy samsvarer med søket ditt", + "noResultsHint": "Prøv et annet søkeord eller fjern filteret.", + "filterAll": "Alle", + "loginCta": { + "description": "Logg inn for å spore verktøyene du nylig har brukt." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Laster...", + "myTasksTitle": "Mine oppgaver", + "statsLine": "{total} oppgaver • {percent}% fullført", + "statsLineDesktop": "{total} totalt • {percent}% fullført" + }, + "filters": { + "all": "Alle", + "total": "Totalt", + "allProjects": "Alle prosjekter", + "export": "Eksporter", + "kanban": "Kanban", + "list": "Liste", + "searchPlaceholder": "Søk i oppgaver...", + "searchAria": "Søk i oppgaver", + "clearSearchAria": "Tøm søk", + "foundTasks": "Fant {count} {count, plural, one {oppgave} other {oppgaver}}", + "filterByAllAria": "Filtrer etter alle", + "filterByStatusAria": "Filtrer etter {status}" + }, + "status": { + "not-started": { + "label": "Ikke startet", + "description": "Oppgaver som ikke er startet ennå" + }, + "ongoing": { + "label": "Pågående", + "description": "Oppgaver som er i gang" + }, + "completed": { + "label": "Fullført", + "description": "Oppgaver som er ferdige" + } + }, + "priorities": { + "high": "Høy", + "medium": "Middels", + "low": "Lav" + }, + "form": { + "addNewTaskLabel": "Legg til ny oppgave", + "multilinePlaceholder": "Legg til oppgavedetaljer... (Shift+Enter for ny linje)", + "singlelinePlaceholder": "Hva må gjøres? Trykk Enter for å legge til", + "taskInputAria": "Oppgavefelt", + "projectPlaceholder": "Prosjekt", + "selectProject": "Velg prosjekt", + "errorSelectProject": "Velg et prosjekt eller opprett et nytt for å legge til en oppgave.", + "hintMultiline": "Shift+Enter for ny linje, Enter for å legge til", + "hintSingleline": "Enter for å legge til", + "shortcutsAria": "Tastatursnarveier", + "shortcutsLabel": "Snarveier", + "shortcutAddTask": "Legg til oppgave", + "shortcutNewLine": "Ny linje", + "shortcutClear": "Tøm", + "addButton": "Legg til" + }, + "list": { + "ariaLabel": "Oppgaveliste", + "loading": "Laster oppgavene dine...", + "emptyTitle": "Ingen oppgaver funnet", + "emptyPrimary": "Kom i gang ved å legge til din første oppgave ovenfor! Trykk Enter for å legge til oppgaver raskt.", + "emptySecondary": "Prøv å justere filtrene eller søket ditt." + }, + "mobileDrawer": { + "addTaskSrOnly": "Legg til oppgave", + "addNewTaskTitle": "Legg til ny oppgave", + "addNewTaskDescription": "Opprett en ny oppgave for å spore fremdriften din.", + "cancel": "Avbryt" + }, + "kanban": { + "ariaLabel": "Kanban-tavle", + "loading": "Laster oppgavene dine...", + "dropHere": "Slipp oppgave her", + "noTasksYet": "Ingen oppgaver ennå", + "emptyColumnAria": "Tom kolonne", + "columnAria": "{title}-kolonne med {count} oppgaver", + "countInColumnAria": "{count} oppgaver i {title}" + }, + "taskItem": { + "confirmDelete": "Er du sikker på at du vil slette denne oppgaven?", + "statusPlaceholder": "Status", + "changeStatusAria": "Endre oppgavestatus", + "taskOptionsAria": "Oppgavealternativer", + "editTask": "Rediger oppgave", + "copyTask": "Kopier oppgave", + "copied": "Kopiert!", + "deleteTask": "Slett oppgave", + "statusHeading": "Status", + "editTaskAria": "Rediger oppgave" + }, + "editDialog": { + "title": "Rediger oppgave", + "description": "Oppdater oppgavedetaljer, legg til deloppgaver, sett prioriteringer og mer.", + "taskTitleLabel": "Oppgavetittel *", + "taskTitlePlaceholder": "Hva må gjøres?", + "descriptionLabel": "Beskrivelse", + "descriptionPlaceholder": "Legg til flere detaljer om denne oppgaven...", + "statusLabel": "Status", + "priorityLabel": "Prioritet", + "dueDateLabel": "Forfallsdato", + "pickDate": "Velg en dato", + "clearDate": "Fjern dato", + "projectLabel": "Prosjekt", + "selectProjectPlaceholder": "Velg et prosjekt", + "noProject": "Ingen prosjekt", + "timeEstimateLabel": "Tidsestimat (minutter)", + "timeEstimatePlaceholder": "Hvor lang tid vil dette ta?", + "tagsLabel": "Etiketter", + "customTagNamePlaceholder": "Egendefinert etikettnavn", + "subtasksLabel": "Deloppgaver", + "addSubtaskPlaceholder": "Legg til en deloppgave...", + "cancel": "Avbryt", + "saving": "Lagrer...", + "saveChanges": "Lagre endringer" + }, + "export": { + "title": "Eksporter oppgaver", + "description": "Eksporter oppgavene dine til JSON-, CSV- eller Excel-format.", + "projectLabel": "Prosjekt", + "statusLabel": "Status", + "tagLabel": "Etikett", + "allProjects": "Alle prosjekter", + "allStatuses": "Alle statuser", + "allTags": "Alle etiketter", + "exportAsJsonTitle": "Eksporter som JSON", + "exportAsJsonDescription": "Eksporter oppgavedata inkludert deloppgaver, etiketter og metadata.", + "exportJson": "Eksporter JSON", + "exportAsCsvExcelTitle": "Eksporter som CSV / Excel", + "exportAsCsvExcelDescription": "Eksporter oppgaver for regnearkapplikasjoner.", + "exportCsv": "Eksporter CSV", + "exportExcel": "Eksporter Excel", + "close": "Lukk", + "toastNoTasks": "Ingen oppgaver å eksportere med gjeldende filtre", + "toastExportedJson": "Eksporterte {count} oppgaver til JSON!", + "toastExportedCsv": "Eksporterte {count} oppgaver til CSV!", + "toastExportedExcel": "Eksporterte {count} oppgaver til Excel!", + "toastFailedJson": "Kunne ikke eksportere til JSON", + "toastFailedCsv": "Kunne ikke eksportere til CSV", + "toastFailedExcel": "Kunne ikke eksportere til Excel", + "unknownProject": "Ukjent prosjekt" + }, + "ack": { + "taskAddedTitle": "Oppgave lagt til", + "taskAddedFailedTitle": "Kunne ikke legge til oppgave", + "taskUpdatedTitle": "Oppgave oppdatert", + "taskUpdatedFailedTitle": "Kunne ikke oppdatere oppgave", + "taskStatusUpdateFailedTitle": "Kunne ikke oppdatere oppgavestatus", + "taskMovedTitle": "Oppgave flyttet til {status}", + "tryAgain": "Vennligst prøv igjen.", + "taskDeletedTitle": "Oppgave «{text}» slettet", + "undo": "Angre", + "taskRestoredTitle": "Oppgave gjenopprettet", + "taskRestoredSuccessTitle": "Oppgave gjenopprettet", + "taskRestoreFailedTitle": "Kunne ikke gjenopprette oppgave. Vennligst prøv igjen.", + "taskDeleteFailedTitle": "Kunne ikke slette oppgave. Vennligst prøv igjen.", + "tasksImportedTitle": "Importerte {count} {count, plural, one {oppgave} other {oppgaver}}!", + "tasksImportFailedTitle": "Kunne ikke importere oppgaver. Vennligst prøv igjen.", + "unknownProject": "Ukjent prosjekt", + "projectCreatedTitle": "Prosjekt opprettet", + "projectCreateFailedTitle": "Kunne ikke opprette prosjekt", + "projectUpdatedTitle": "Prosjekt oppdatert", + "projectUpdateFailedTitle": "Kunne ikke oppdatere prosjekt", + "projectDeletedTitle": "Prosjekt slettet", + "projectDeleteFailedTitle": "Kunne ikke slette prosjekt" + } + }, + "Notes": { + "page": { + "loading": "Laster..." + }, + "layout": { + "mobileNavTitle": "Notatnavigasjon", + "openMenuAria": "Åpne notatmeny" + }, + "sidebar": { + "title": "Notater", + "createNoteAria": "Opprett notat", + "searchPlaceholder": "Søk i notater...", + "loading": "Laster...", + "noNotesFound": "Ingen notater funnet.", + "noNotesYet": "Ingen notater ennå. Klikk + for å opprette et.", + "inParent": "i {parentTitle}", + "untitled": "Uten tittel", + "addSubPage": "Legg til underside", + "delete": "Slett", + "deleteConfirmTitle": "Slette notat?", + "deleteConfirmDescription": "Er du sikker på at du vil slette «{title}»? Denne handlingen kan ikke angres.", + "cancel": "Avbryt", + "confirmDelete": "Slett", + "noteOptionsAria": "Notatalternativer" + }, + "editor": { + "emptyState": "Velg et notat eller opprett et nytt for å komme i gang.", + "titlePlaceholder": "Uten tittel", + "saving": "Lagrer…", + "ready": "Klar", + "saved": "Lagret {time}", + "stats": "{words} ord · {read} min lesing", + "template": "Mal", + "applyTemplate": "Bruk mal", + "exportMdLabel": ".md", + "exportMd": "Eksporter som Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Eksporter som HTML", + "focusMode": "Fokusmodus", + "exitFocusMode": "Avslutt fokusmodus", + "save": "Lagre", + "addTag": "Legg til etikett…", + "removeTag": "Fjern etikett {tag}", + "loading": "Laster…", + "searchHint": "for å søke", + "focusHint": "fokusmodus", + "notePath": "Notatsti" + }, + "context": { + "authRequiredError": "Bruker ikke autentisert", + "defaultTitle": "Uten tittel", + "cloudSyncImageError": "Slå på skysynkronisering for å lagre notatbilder. Tekstnotater forblir lokale." + } + }, + "PasswordManager": { + "page": { + "loading": "Laster...", + "title": "Passordbehandler", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Kunne ikke dekryptere passord", + "toastLoadFailed": "Kunne ikke laste passord", + "toastUnlockedAuto": "Hvelv låst opp automatisk", + "toastAutoUnlockFailed": "Automatisk opplåsing mislyktes. Vennligst lås opp manuelt.", + "toastConnectFailed": "Kunne ikke koble til server", + "toastAutoUnlockWarning": "Hvelv låst opp, men automatisk opplåsing fungerer kanskje ikke ved oppdatering", + "errorIncorrectPassword": "Feil hovedpassord", + "errorUnlockFailed": "Kunne ikke låse opp hvelvet", + "errorPasswordsMismatch": "Passordene stemmer ikke overens", + "errorPasswordTooShort": "Passordet må være minst 8 tegn", + "errorSetupFailed": "Kunne ikke sette opp hvelv", + "errorConnectFailed": "Kunne ikke koble til server.", + "mobileTitleCreate": "Opprett hvelvet ditt", + "mobileTitleWelcome": "Velkommen tilbake", + "mobileSubtitleCreate": "Sett et hovedpassord for å beskytte legitimasjonen din", + "mobileSubtitleUnlock": "Skriv inn hovedpassordet ditt for å låse opp", + "placeholderMasterPassword": "Hovedpassord", + "placeholderConfirmPassword": "Bekreft passord", + "labelMasterPassword": "Hovedpassord", + "labelConfirmPassword": "Bekreft passord", + "placeholderEnterMaster": "Skriv inn hovedpassord", + "placeholderConfirmMaster": "Bekreft hovedpassord", + "buttonCreateVault": "Opprett hvelv", + "buttonUnlock": "Lås opp", + "buttonUnlockVault": "Lås opp hvelv", + "footerCreate": "Passordet ditt lagres aldri og kan ikke gjenopprettes", + "footerUnlock": "Sikret med ende-til-ende-kryptering", + "desktopTitleSetup": "Sett opp passordhvelv", + "desktopTitleUnlock": "Lås opp hvelv", + "desktopDescriptionSetup": "Opprett et hovedpassord for å sikre legitimasjonen din. Dette passordet lagres aldri og kan ikke gjenopprettes.", + "desktopDescriptionUnlock": "Skriv inn hovedpassordet ditt for å få tilgang til dine sikrede legitimasjoner." + }, + "list": { + "loading": "Laster passordene dine...", + "emptyTitle": "Hvelvet er tomt", + "emptyHintMobile": "Trykk på +-knappen for å legge til ditt første passord", + "emptyHintDesktop": "Du har ikke lagret noen passord ennå. Klikk på «Legg til passord»-knappen ovenfor for å komme i gang.", + "addPassword": "Legg til passord", + "searchPlaceholderMobile": "Søk i hvelvet ditt...", + "searchPlaceholderDesktop": "Søk i hvelv...", + "importExport": "Importer / eksporter", + "lockVault": "Lås hvelv", + "allPasswordsTitle": "Alle passord", + "passwordCount": "{count, plural, one {# passord} other {# passord}}", + "vaultHealthDrawerTitle": "Hvelvhelse", + "noResults": "Ingen passord funnet som matcher «{query}»", + "tableService": "Tjeneste", + "tableUsername": "Brukernavn", + "tablePassword": "Passord", + "tableUrl": "URL", + "tableActions": "Handlinger", + "gridViewAria": "Rutenettvisning", + "listViewAria": "Listevisning", + "passwordStrengthTitle": "Passordstyrke", + "deleteDialogTitle": "Slett passord", + "deleteDialogDescription": "Er du sikker på at du vil slette dette passordet? Denne handlingen kan ikke angres.", + "cancel": "Avbryt", + "delete": "Slett", + "edit": "Rediger" + }, + "toasts": { + "copiedPassword": "Passord kopiert til utklippstavle", + "copiedUsername": "Brukernavn kopiert til utklippstavle", + "passwordDeleted": "Passord slettet", + "deleteFailed": "Kunne ikke slette passord", + "vaultLocked": "Hvelv låst", + "importedCount": "Importerte {count, plural, one {# passord} other {# passord}}" + }, + "form": { + "addTitle": "Legg til nytt passord", + "editTitle": "Rediger passord", + "serviceName": "Tjenestenavn", + "usernameEmail": "Brukernavn / e-post", + "password": "Passord", + "urlOptional": "Nettside-URL (valgfritt)", + "notesOptional": "Notater (valgfritt)", + "tags": "Etiketter", + "placeholderService": "f.eks. Netflix", + "placeholderUsername": "bruker@eksempel.no", + "placeholderPassword": "Skriv inn eller generer passord", + "placeholderUrl": "https://...", + "placeholderNotes": "Tilleggsdetaljer...", + "placeholderTags": "Legg til etiketter (trykk Enter eller komma)", + "strength": "Styrke", + "strengthWeak": "Svak", + "strengthMedium": "Middels", + "strengthStrong": "Sterk", + "advancedGenerator": "Avansert generator", + "cancel": "Avbryt", + "saving": "Lagrer...", + "savePassword": "Lagre passord", + "updating": "Oppdaterer...", + "updatePassword": "Oppdater passord", + "addPasswordSrOnly": "Legg til passord", + "triggerAdd": "Legg til passord", + "toastSaved": "Passord lagret", + "toastSaveFailed": "Kunne ikke lagre passord", + "toastUpdated": "Passord oppdatert", + "toastUpdateFailed": "Kunne ikke oppdatere passord" + }, + "security": { + "vaultHealth": "Hvelvhelse", + "statusLabel": "Status", + "totalPasswords": "Totalt antall passord", + "activeEntries": "Aktive oppføringer i hvelvet ditt", + "protected": "Beskyttet", + "weakPasswords": "Svake passord", + "needingImprovement": "Passord som trenger forbedring", + "allGood": "Alt bra", + "reusedPasswords": "Gjenbrukte passord", + "usedMultipleTimes": "Passord brukt flere ganger", + "unique": "Unike", + "total": "Totalt", + "weak": "Svake", + "reused": "Gjenbrukte", + "totalCountLabel": "{count, plural, one {# passord} other {# passord}}", + "weakCountLabel": "{count} svake", + "allSecure": "Alle sikre", + "scoreExcellent": "Utmerket", + "scoreGood": "Bra", + "scoreFair": "Middels", + "scoreCritical": "Kritisk" + }, + "importExport": { + "title": "Importer / eksporter hvelv", + "description": "Administrer passorddataene dine. Du kan eksportere hvelvet ditt til JSON eller importere fra en JSON-fil.", + "tabExport": "Eksporter", + "tabImport": "Importer", + "warningTitle": "Advarsel", + "warningBody": "Eksporterte filer inneholder passordene dine i klartekst. Oppbevar denne filen trygt og slett den etter bruk.", + "exportBlurb": "Last ned hele hvelvet ditt som en JSON-fil. Dette inkluderer alle tjenester, brukernavn, passord og notater.", + "downloadJson": "Last ned JSON-eksport", + "pasteLabel": "Lim inn JSON-data", + "pastePlaceholder": "[{\"service\": \"Eksempel\", \"username\": \"bruker\", \"password\": \"123\", ...}]", + "pasteHint": "Lim inn innholdet i en tidligere eksportert JSON-fil her.", + "importing": "Importerer...", + "importPasswords": "Importer passord", + "trigger": "Importer / eksporter", + "toastExported": "Hvelv eksportert", + "toastExportFailed": "Kunne ikke eksportere hvelv", + "toastImportFailed": "Kunne ikke importere: ugyldig JSON-format" + }, + "card": { + "copyUsername": "Kopier brukernavn", + "openUrl": "Åpne URL", + "strength": "Styrke" + }, + "swipeable": { + "copy": "Kopier", + "delete": "Slett", + "copyPassword": "Kopier passord", + "copyUsername": "Kopier brukernavn", + "edit": "Rediger", + "deleteAction": "Slett" + }, + "floatingMenu": { + "lockVault": "Lås hvelv", + "importExport": "Importer / eksporter", + "addPassword": "Legg til passord" + }, + "generator": { + "tabPassword": "Passord", + "tabPassphrase": "Passfrase", + "strength": "Styrke", + "lengthLabel": "Lengde: {length} tegn", + "lengthBadge": "{length} tegn", + "characterSets": "Tegnsett", + "uppercase": "Store bokstaver", + "lowercase": "Små bokstaver", + "numbers": "Tall", + "symbols": "Symboler", + "wordCountLabel": "Antall ord: {count}", + "wordCountBadge": "{count, plural, one {# ord} other {# ord}}", + "separator": "Skilletegn", + "capitalization": "Store forbokstaver", + "includeNumber": "Inkluder tall" + } + }, + "EnvironmentManager": { + "subtitle": "Krypterte miljøvariabler per prosjekt og miljø.", + "page": { + "loading": "Laster…", + "title": "Miljøbehandler", + "loadFailed": "Kunne ikke laste miljøsett" + }, + "list": { + "title": "Miljøer", + "loading": "Laster settene dine…", + "emptyTitle": "Ingen miljøsett ennå", + "emptyHint": "Opprett et sett for hvert prosjekt og miljø (f.eks. produksjons-API-nøkler). Alt krypteres i nettleseren før opplasting.", + "searchPlaceholder": "Søk prosjekt, miljø, tagger, notater…", + "tagFilterPlaceholder": "Filtrer på tagg", + "variableCount": "{count, plural, one {# variabel} other {# variabler}}", + "updatedAgo": "Oppdatert {time}", + "copyDotEnv": "Kopier .env", + "edit": "Rediger", + "delete": "Slett", + "cancel": "Avbryt", + "confirmDelete": "Slett", + "deleteDialogTitle": "Slette miljøsett?", + "deleteDialogDescription": "Dette fjerner den krypterte bloben fra kontoen din. Kan ikke angres.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env kopiert til utklippstavle", + "deleted": "Miljøsett slettet", + "deleteFailed": "Kunne ikke slette" + }, + "form": { + "addTitle": "Nytt miljøsett", + "editTitle": "Rediger miljøsett", + "triggerAdd": "Legg til miljøsett", + "addSrOnly": "Legg til miljøsett", + "project": "Prosjekt", + "environment": "Miljø", + "placeholderProject": "f.eks. billing-api", + "placeholderEnvironment": "f.eks. production", + "pasteEnvLabel": "Lim inn .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Slå sammen limte linjer", + "variables": "Variabler", + "addRow": "Legg til rad", + "keyPlaceholder": "NØKKEL", + "valuePlaceholder": "verdi", + "notesOptional": "Notater (valgfritt)", + "placeholderNotes": "Rotasjonspolicy, eier, lenker…", + "tags": "Tagger", + "placeholderTags": "Legg til tagger (Enter eller komma)", + "cancel": "Avbryt", + "save": "Lagre", + "saving": "Lagrer…", + "update": "Oppdater", + "updating": "Oppdaterer…", + "toastSaved": "Miljøsett lagret", + "toastSaveFailed": "Kunne ikke lagre", + "toastUpdated": "Miljøsett oppdatert", + "toastUpdateFailed": "Kunne ikke oppdatere", + "toastPasteEmpty": "Ingen NØKKEL=verdi-linjer funnet", + "toastPasteApplied": "{count} variabler slått sammen fra liming", + "toastProjectEnvRequired": "Prosjekt og miljø er påkrevd", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Laster..." + }, + "manager": { + "foldersHeading": "Mapper", + "tagsHeading": "Etiketter", + "tagsMore": "+{count} flere", + "tagsShowLess": "Show less", + "searchPlaceholder": "Søk i bokmerkene dine...", + "import": "Importer", + "export": "Eksporter", + "exportAsHtml": "Eksporter som HTML", + "exportAsJson": "Eksporter som JSON", + "addBookmark": "Legg til bokmerke", + "matchingQuery": "som matcher «{query}»", + "bookmarkCount": "{count, plural, one {# bokmerke} other {# bokmerker}}", + "allBookmarks": "Alle bokmerker", + "uncategorized": "Ukategorisert", + "unknownFolder": "Ukjent" + }, + "card": { + "urlCopied": "URL kopiert til utklippstavle", + "bookmarkDeleted": "Bokmerke slettet", + "edit": "Rediger", + "delete": "Slett", + "copyUrl": "Kopier URL", + "deleteConfirmTitle": "Slette bokmerke?", + "deleteConfirmDescription": "Dette vil permanent slette «{title}».", + "cancel": "Avbryt" + }, + "grid": { + "emptyTitle": "Ingen bokmerker ennå", + "emptyDescription": "Legg til ditt første bokmerke eller importer fra nettleseren din for å komme i gang." + }, + "folderTree": { + "allBookmarks": "Alle bokmerker", + "addSubfolder": "Legg til undermappe", + "rename": "Gi nytt navn", + "deleteFolderTitle": "Slette mappen «{name}»?", + "deleteFolderDescription": "Dette vil slette mappen og alle undermapper. Bokmerker i disse mappene flyttes til Ukategorisert." + }, + "import": { + "title": "Importer bokmerker", + "description": "Importer bokmerker fra nettleserens eksporterte fil.", + "parseError": "Kunne ikke analysere bokmerkefilen. Kontroller at det er en gyldig bokmerkeeksport.", + "successToast": "Importerte {bookmarks} bokmerker og {folders} mapper", + "errorToast": "Kunne ikke importere bokmerker", + "chromeInstruction": "Meny → Bokmerker → Bokmerkebehandler → ⋮ → Eksporter bokmerker", + "firefoxInstruction": "Meny → Bokmerker → Administrer bokmerker → Importer og sikkerhetskopier → Eksporter", + "safariInstruction": "Fil → Eksporter bokmerker...", + "edgeInstruction": "Meny → Favoritter → ⋯ → Eksporter favoritter", + "dropZoneTitle": "Slipp bokmerkefilen din her", + "dropZoneHint": "eller klikk for å bla (HTML eller JSON)", + "selectedFileHint": "Klikk for å velge en annen fil", + "errorTitle": "Feil", + "previewTitle": "Forhåndsvisning av import", + "previewBookmarks": "{count, plural, one {# bokmerke} other {# bokmerker}}", + "previewFolders": "{count, plural, one {# mappe} other {# mapper}}", + "importModeLabel": "Importmodus", + "modeMerge": "Slå sammen med eksisterende bokmerker", + "modeReplace": "Erstatt alle eksisterende bokmerker", + "cancel": "Avbryt", + "importButton": "Importer", + "importButtonWithCount": "Importer {count, plural, one {# bokmerke} other {# bokmerker}}" + }, + "addBookmark": { + "titleAdd": "Legg til bokmerke", + "titleEdit": "Rediger bokmerke", + "urlLabel": "URL", + "urlPlaceholder": "https://eksempel.no", + "urlInvalid": "Vennligst skriv inn en gyldig URL", + "titleLabel": "Tittel", + "titlePlaceholder": "Bokmerketittel", + "titleRequired": "Vennligst skriv inn en tittel", + "descriptionLabel": "Beskrivelse (valgfritt)", + "descriptionPlaceholder": "Legg til et notat om dette bokmerket", + "folderLabel": "Mappe", + "folderPlaceholder": "Velg mappe", + "noFolder": "Ingen mappe", + "tagsLabel": "Etiketter", + "tagsPlaceholder": "Legg til etiketter (trykk Enter)", + "toastUpdated": "Bokmerke oppdatert", + "toastAdded": "Bokmerke lagt til", + "cancel": "Avbryt", + "submitAdd": "Legg til bokmerke", + "submitUpdate": "Oppdater bokmerke" + }, + "addFolder": { + "title": "Opprett mappe", + "nameLabel": "Mappenavn", + "namePlaceholder": "Min mappe", + "nameRequired": "Vennligst skriv inn et mappenavn", + "parentLabel": "Overordnet mappe", + "parentPlaceholder": "Velg overordnet mappe", + "rootParent": "Rot (ingen overordnet)", + "toastCreated": "Mappe opprettet", + "cancel": "Avbryt", + "submit": "Opprett mappe" + }, + "editFolder": { + "title": "Gi nytt navn til mappe", + "nameLabel": "Mappenavn", + "namePlaceholder": "Mappenavn", + "nameRequired": "Vennligst skriv inn et mappenavn", + "toastRenamed": "Mappe omdøpt", + "cancel": "Avbryt", + "save": "Lagre" + } + }, + "EmailValidator": { + "header": { + "title": "E-postvalidering", + "description": "Verifiser og valider e-postadresser for syntaks, domene og leverbarhet." + }, + "tabs": { + "single": "Enkeltverifisering", + "bulk": "Masseopplasting" + }, + "single": { + "cardTitle": "Enkelt e-postverifisering", + "cardDescription": "Skriv inn en e-postadresse for å sjekke leverbarhet og omdømme.", + "placeholder": "Skriv inn e-postadresse...", + "clearAria": "Tøm e-post", + "validate": "Valider", + "validating": "Validerer...", + "formatError": "Vennligst skriv inn en gyldig e-postadresse" + }, + "toasts": { + "errorTitle": "Feil", + "enterEmail": "Vennligst skriv inn en e-postadresse", + "invalidFormatTitle": "Ugyldig e-postformat", + "invalidFormatDescription": "Vennligst skriv inn en gyldig e-postadresse", + "validateFailedDescription": "Det oppstod en feil under validering av e-posten", + "noEmailsTitle": "Ingen e-poster funnet", + "noEmailsDescription": "Kontroller at Excel-filen har en «email»-kolonne.", + "fileTooLargeTitle": "Filen er for stor", + "fileTooLargeDescription": "Fant {total} e-poster. Bare de første 4000 blir validert.", + "successTitle": "Suksess", + "successValidated": "Validerte {count} e-poster.", + "batchFailedTitle": "Massevalidering mislyktes", + "batchFailedDescription": "Det oppstod en feil under behandling av massevalidering.", + "noExportTitle": "Ingen data å eksportere", + "noExportValidOnly": "Ingen gyldige e-poster funnet å eksportere.", + "noExportAll": "Ingen resultater å eksportere.", + "exportSuccessTitle": "Eksport vellykket", + "exportSuccessExcelAll": "Eksporterte {count} e-poster til Excel.", + "exportSuccessExcelValid": "Eksporterte {count} gyldige e-poster til Excel.", + "exportSuccessCsvAll": "Eksporterte {count} e-poster til CSV.", + "exportSuccessCsvValid": "Eksporterte {count} gyldige e-poster til CSV." + }, + "disposableAlert": { + "title": "Engangse-post oppdaget", + "description": "Denne e-postadressen tilhører en engangs-e-postleverandør. Den kan være midlertidig eller usikker." + }, + "score": { + "reliabilityTitle": "Pålitelighetspoeng", + "scoreLabel": "Poeng" + }, + "details": { + "sectionTitle": "Valideringsdetaljer" + }, + "validation": { + "syntaxLabel": "Syntaks gyldig", + "syntaxTooltip": "Sjekker om e-postadressen følger riktig format (f.eks. bruker@domene.no)", + "domainLabel": "Domene eksisterer", + "domainTooltip": "Verifiserer at domenenavnet i e-postadressen faktisk eksisterer", + "mxLabel": "MX-poster funnet", + "mxTooltip": "Sjekker om domenet har MX-poster (mail exchange) konfigurert for å motta e-post", + "mailboxLabel": "Postboks eksisterer", + "mailboxTooltip": "Verifiserer at den spesifikke postboksen/kontoen eksisterer på e-postserveren", + "disposableLabel": "Engangse-post", + "disposableTooltip": "Indikerer om denne e-posten tilhører en midlertidig/engangs-e-posttjeneste (kan være risikabelt)", + "roleBasedLabel": "Rollebasert e-post", + "roleBasedTooltip": "Identifiserer om dette er en rollebasert e-post (f.eks. support@, info@) i stedet for en personlig adresse" + }, + "copy": { + "emailCopied": "E-post kopiert!", + "copyTooltip": "Kopier e-post" + }, + "bulk": { + "dropHere": "Slipp filen din her", + "dragHere": "Dra og slipp filen din her", + "uploadHint": "Last opp en .xlsx-, .xls- eller .csv-fil med en «email»-kolonne for å validere opptil 4000 e-poster.", + "filePrefix": "Fil:", + "selectFile": "Velg fil", + "downloadTemplate": "Last ned mal" + }, + "bulkLoading": { + "title": "Validerer e-poster...", + "batchProgress": "Behandler parti {current} av {total}..." + }, + "stats": { + "totalProcessed": "Totalt behandlet", + "valid": "Gyldig", + "risky": "Risikabelt", + "invalid": "Ugyldig" + }, + "results": { + "detailedTitle": "Detaljerte resultater", + "searchPlaceholder": "Søk i e-poster...", + "newVerification": "Ny verifisering", + "exportAll": "Eksporter alle", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Eksporter gyldige ({count})" + }, + "showingResults": "Viser {shown} av {total} resultater", + "table": { + "email": "E-post", + "status": "Status", + "score": "Poeng", + "checks": "Sjekker" + }, + "noSearchResults": "Ingen resultater funnet som matcher «{query}»", + "checksTooltip": { + "syntax": "Syntaks", + "domain": "Domene", + "mx": "MX", + "mailbox": "Postboks" + }, + "exportColumns": { + "email": "E-post", + "status": "Status", + "score": "Poeng", + "syntax": "Syntaks", + "domain": "Domene", + "mx": "MX", + "mailbox": "Postboks", + "disposable": "Engangse-post", + "roleBased": "Rollebasert", + "valid": "Gyldig", + "invalid": "Ugyldig", + "yes": "Ja", + "no": "Nei" + }, + "sheets": { + "validEmails": "Gyldige e-poster", + "results": "Resultater", + "template": "Mal" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL-utforsker", + "heroSubtitle": "Koble til databasene dine for å administrere samlinger, kjøre spørringer og visualisere dataene dine.", + "featureMultiTitle": "Multi-tilkobling", + "featureMultiDesc": "Administrer flere databasetilkoblinger samtidig.", + "featureMongoTitle": "MongoDB-støtte", + "featureMongoDesc": "Innebygd støtte for MongoDB med avansert filtrering.", + "featureQueryTitle": "Smart spørring", + "featureQueryDesc": "Kraftig spørringsbygger med syntaksutheving.", + "connectCta": "Koble til MongoDB", + "footerNote": "Støtter for øyeblikket {db}. Flere databaser kommer snart.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Velg en samling", + "selectCollectionDesc": "Velg en samling fra sidefeltet for å se dokumenter og begynne å utforske dataene dine.", + "dialogAddConnection": "Legg til tilkobling", + "toastConnectionAdded": "Tilkobling lagt til", + "toastFetchFailed": "Kunne ikke hente dokumenter for {collection}", + "toastDocDeleted": "Dokument slettet", + "deleteDocTitle": "Er du helt sikker?", + "deleteDocDescription": "Denne handlingen kan ikke angres. Dette vil permanent slette dokumentet fra samlingen din.", + "closeAllTitle": "Lukke alle faner?", + "closeAllDescription": "Er du sikker på at du vil lukke alle faner? Eventuelle ulagrede endringer i spørringsfiltre vil gå tapt.", + "closeAllConfirm": "Lukk alle", + "cancel": "Avbryt", + "delete": "Slett" + }, + "connection": { + "titleEdit": "Rediger tilkobling", + "titleConnect": "Koble til MongoDB", + "subtitleEdit": "Oppdater tilkoblingsdetaljene dine nedenfor.", + "subtitleConnect": "Skriv inn MongoDB-tilkoblingsstrengen din for å begynne å utforske dataene dine.", + "labelName": "Navn", + "placeholderName": "f.eks. Produksjons-DB", + "labelConnectionString": "Tilkoblingsstreng", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://brukernavn:passord@vert:port/database", + "testConnection": "Test tilkobling", + "connecting": "Kobler til...", + "updateConnect": "Oppdater og koble til", + "connect": "Koble til", + "cancelEdit": "Avbryt redigering", + "savedConnections": "Lagrede tilkoblinger", + "emptySaved": "Ingen lagrede tilkoblinger ennå.", + "lastUsed": "Sist brukt {time}", + "toastTestOk": "Tilkobling vellykket!", + "toastTestFail": "Tilkobling mislyktes: {message}", + "toastUpdated": "Tilkobling oppdatert", + "confirmDelete": "Er du sikker på at du vil slette denne lagrede tilkoblingen?", + "toastDeletedConn": "Tilkobling slettet", + "toastDeleteFail": "Kunne ikke slette tilkobling", + "copyStringTitle": "Kopier tilkoblingsstreng", + "toastStringCopied": "Tilkoblingsstreng kopiert!", + "editTitle": "Rediger", + "deleteTitle": "Slett" + }, + "sidebar": { + "explorer": "Utforsker", + "add": "Legg til", + "tooltipAdd": "Legg til ny tilkobling", + "tooltipRefresh": "Oppdater tilkoblinger", + "searchPlaceholder": "Søk...", + "loadingConnections": "Laster tilkoblinger...", + "noConnectionsFound": "Ingen tilkoblinger funnet", + "toastLoadFail": "Kunne ikke laste tilkoblinger", + "toastConnectFail": "Kunne ikke koble til {name}", + "toastCollectionsFail": "Kunne ikke hente samlinger for {name}", + "toastRenamed": "Tilkobling omdøpt", + "toastRenameFail": "Kunne ikke gi nytt navn til tilkobling", + "confirmDeleteConnection": "Er du sikker på at du vil slette tilkoblingen «{name}»?", + "toastDeleted": "Tilkobling slettet", + "toastDeleteConnFail": "Kunne ikke slette tilkobling", + "confirmDropDb": "Er du sikker på at du vil slette databasen «{name}»? Denne handlingen kan ikke angres.", + "toastDbDropped": "Database {name} slettet", + "confirmDropCollection": "Er du sikker på at du vil slette samlingen «{name}»? Denne handlingen kan ikke angres.", + "toastCollectionDropped": "Samling {name} slettet", + "toastCollectionRenamed": "Samling omdøpt til {name}", + "toastDatabaseRenamed": "Database omdøpt til {name}", + "connecting": "Kobler til...", + "connectionFailed": "Tilkobling mislyktes", + "noDatabases": "Ingen databaser", + "loading": "Laster...", + "noCollections": "Ingen samlinger", + "renameCollectionTitle": "Gi nytt navn til samling", + "renameDatabaseTitle": "Gi nytt navn til database", + "placeholderNewCollection": "Nytt samlingsnavn", + "placeholderNewDatabase": "Nytt databasenavn", + "rename": "Gi nytt navn", + "refresh": "Oppdater", + "copyDbName": "Kopier DB-navn", + "toastDbNameCopied": "Databasenavn kopiert til utklippstavle", + "dropDatabase": "Slett database", + "dropCollection": "Slett samling", + "menuRename": "Gi nytt navn", + "menuCopyConnectionString": "Kopier tilkoblingsstreng", + "menuDeleteConnection": "Slett tilkobling", + "toastSidebarStringCopied": "Tilkoblingsstreng kopiert!", + "cancel": "Avbryt", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} dok.", + "table": "Tabell", + "json": "JSON", + "tree": "Tre", + "tooltipTable": "Tabellvisning", + "tooltipJson": "JSON-visning", + "tooltipTree": "Trevisning", + "itemsPerPage": "Elementer per side", + "perPage": "{n} / side", + "prevPage": "Forrige side", + "nextPage": "Neste side", + "expandAll": "Utvid alle", + "collapseAll": "Skjul alle", + "refreshData": "Oppdater data", + "insert": "Sett inn", + "export": "Eksporter", + "actions": "Handlinger", + "invalidJson": "Ugyldig JSON-innhold", + "copiedClipboard": "Kopiert til utklippstavle", + "docUpdated": "Dokument oppdatert", + "docInserted": "Dokument satt inn", + "invalidJsonShort": "Ugyldig JSON", + "docCopied": "Dokument kopiert til utklippstavle", + "openFullEditor": "Åpne i fullskjermredigering", + "cellArray": "Array({length})", + "cellObject": "Objekt", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Ingen dokumenter matcher spørringen din", + "emptyFilterHint": "Prøv å justere filteret eller tøm spørringen.", + "clearFilter": "Tøm filter", + "emptyCollectionTitle": "Denne samlingen er tom", + "emptyCollectionHint": "Sett inn et dokument for å komme i gang.", + "insertDocument": "Sett inn dokument", + "formatJson": "Formater JSON", + "compactJson": "Kompakt JSON", + "copyClipboard": "Kopier til utklippstavle", + "copyJson": "Kopier JSON", + "duplicate": "Dupliser", + "editDoc": "Rediger dokument", + "deleteDoc": "Slett dokument", + "documentLabel": "Dokument {n}", + "editDialogTitle": "Rediger dokument", + "insertDialogTitle": "Sett inn dokument", + "viewValueTitle": "Se verdi", + "saveChanges": "Lagre endringer", + "close": "Lukk", + "cancel": "Avbryt", + "insertButton": "Sett inn", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Aktivt filter brukt", + "filterActiveTooltip": "Filter aktivt", + "closeAll": "Lukk alle" + }, + "query": { + "migrateHistory": "Migrerte spørringshistorikk til skyen", + "saveHistoryFail": "Kunne ikke lagre spørringshistorikk", + "deleteQueryFail": "Kunne ikke slette spørring", + "invalidJsonQuery": "Ugyldig JSON-spørring", + "placeholder": "f.eks. { \"status\": \"active\" }", + "clearQuery": "Tøm spørring", + "openAdvanced": "Åpne avansert redigering", + "visualBuilder": "Visuell spørringsbygger", + "filterRules": "Filterregler", + "addRule": "Legg til regel", + "noFilters": "Ingen filtre brukt. Legg til en regel for å filtrere dokumenter.", + "where": "HVOR", + "and": "OG", + "fieldPlaceholder": "Felt", + "opPlaceholder": "Op", + "typePlaceholder": "Type", + "opRegex": "Regex", + "opIn": "I", + "opNin": "Ikke i", + "opExists": "Eksisterer", + "typeAuto": "Auto", + "typeString": "Streng", + "typeNumber": "Tall", + "typeBoolean": "Boolsk", + "typeDate": "Dato", + "typeObjectId": "ObjectId", + "true": "Sann", + "false": "Usann", + "valuePlaceholder": "Verdi", + "valueListPlaceholder": "verdi1, verdi2", + "clearFilters": "Tøm filtre", + "applyFilters": "Bruk filtre", + "queryHistory": "Spørringshistorikk", + "noSavedQueries": "Ingen lagrede spørringer ennå", + "advancedEditorTitle": "Avansert spørringsredigering", + "templatesPlaceholder": "Maler", + "templateEmptyAgg": "Tom aggregering", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Sammenkobling)", + "templateGroup": "$group (Telling/Sum)", + "format": "Formater", + "formattedJson": "Formatert JSON", + "runQuery": "Kjør spørring" + }, + "export": { + "selectFieldsError": "Vennligst velg minst ett felt å eksportere", + "success": "Eksporterte til {filename}", + "fail": "Kunne ikke eksportere data", + "title": "Eksporter data", + "formatLabel": "Eksportformat", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Velg felt", + "selectAll": "Velg alle", + "fieldsSelected": "{count, plural, one {# felt valgt} other {# felt valgt}}", + "cancel": "Avbryt", + "export": "Eksporter", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Type: {type}", + "itemSummary": "{count, plural, one {# element} other {# elementer}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Ny forespørsel", + "importedRequest": "Importert forespørsel", + "untitledRequest": "Forespørsel uten tittel" + }, + "toasts": { + "curlImported": "cURL importert", + "curlParseFailed": "Kunne ikke analysere cURL-kommando", + "invalidJsonBody": "Ugyldig JSON-kropp", + "requestFailed": "Forespørsel mislyktes: {message}", + "curlPasted": "cURL limt inn og analysert", + "responseCopied": "Svar kopiert til utklippstavle", + "codeCopied": "Kode kopiert til utklippstavle", + "copyFailed": "Kopiering til utklippstavlen mislyktes" + }, + "layout": { + "collections": "Samlinger", + "closeSidebar": "Lukk sidefelt", + "openSidebar": "Åpne sidefelt", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Metode", + "urlPlaceholder": "https://api.eksempel.no/v1/...", + "sending": "Sender...", + "send": "Send", + "invalidJsonBodyHelp": "Kan ikke sende: JSON-kroppen er ugyldig" + }, + "requestTabs": { + "params": "Parametere", + "headers": "Overskrifter", + "body": "Kropp", + "auth": "Autentisering", + "bodyType": "Kroppstype", + "selectType": "Velg type", + "bodyNone": "Ingen", + "bodyJson": "JSON", + "bodyText": "Tekst", + "noBodyHint": "Ingen kroppsinnhold for denne forespørselen", + "switchToJson": "Bytt til JSON-kropp", + "authType": "Autentiseringstype", + "noAuth": "Ingen autentisering", + "bearerToken": "Bearer-token", + "basicAuth": "Grunnleggende autentisering", + "apiKey": "API-nøkkel", + "labelToken": "Token", + "placeholderBearerToken": "Bearer-token", + "labelUsername": "Brukernavn", + "placeholderUsername": "Brukernavn", + "labelPassword": "Passord", + "placeholderPassword": "Passord", + "labelKey": "Nøkkel", + "placeholderApiKeyName": "Nøkkelnavn (f.eks. X-Api-Key)", + "labelValue": "Verdi", + "placeholderValue": "Verdi", + "addTo": "Legg til i", + "header": "Overskrift", + "queryParams": "Spørringsparametere", + "noAuthHint": "Denne forespørselen bruker ikke autentisering." + }, + "responsePanel": { + "emptyTitle": "Klar til å utforske?", + "emptyHint": "Skriv inn en URL og klikk Send for å se svardataene her.", + "responsePreviewAlt": "Forhåndsvisning av svar", + "previewUnavailable": "Forhåndsvisning ikke tilgjengelig for denne innholdstypen", + "status": "Status", + "time": "Tid", + "size": "Størrelse", + "copyBody": "Kopier kropp", + "downloadResponse": "Last ned svar", + "bodyTab": "Kropp", + "previewTab": "Forhåndsvisning", + "headersTab": "Overskrifter", + "binaryRawView": "Binære data kan ikke vises i råvisning.", + "errorStatusLabel": "Feil", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Ny fane" + }, + "collectionsSidebar": { + "title": "API-ressurser", + "newCollection": "Ny samling", + "tabCollections": "Samlinger", + "tabHistory": "Historikk", + "noCollectionsTitle": "Ingen samlinger", + "noCollectionsHint": "Opprett en samling for å organisere API-forespørslene dine.", + "createCollection": "Opprett samling", + "newFolder": "Ny mappe", + "rename": "Gi nytt navn", + "delete": "Slett", + "emptyCollection": "Tom samling", + "recentRequests": "Nylige forespørsler", + "clearAll": "Tøm alle", + "noHistoryTitle": "Ingen historikk ennå", + "noHistoryHint": "Send en forespørsel for å se den her.", + "dialogNewFolderTitle": "Ny mappe", + "dialogNewFolderDescription": "Opprett en ny mappe for å organisere forespørslene dine.", + "labelFolderName": "Mappenavn", + "placeholderFolderName": "Min mappe", + "createFolder": "Opprett mappe", + "dialogNewCollectionTitle": "Ny samling", + "dialogNewCollectionDescription": "Opprett en ny toppnivåsamling.", + "labelCollectionName": "Samlingsnavn", + "placeholderCollectionName": "Min samling", + "dialogRenameCollectionTitle": "Gi nytt navn til samling", + "dialogRenameCollectionDescription": "Skriv inn et nytt navn for samlingen.", + "placeholderRenameCollection": "Samlingsnavn", + "renameAction": "Gi nytt navn", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Ny mappe", + "delete": "Slett", + "emptyFolder": "Tom mappe" + }, + "environmentManager": { + "noEnvironment": "Ingen miljø", + "manageDialogTitle": "Administrer miljøer", + "placeholderNewEnv": "Nytt miljø", + "variablesHeading": "{name}-variabler", + "addVariable": "Legg til variabel", + "columnVariable": "Variabel", + "columnValue": "Verdi", + "placeholderKey": "Nøkkel", + "placeholderValue": "Verdi", + "selectEnvToEdit": "Velg et miljø å redigere" + }, + "codeGenerator": { + "triggerTitle": "Generer kode", + "dialogTitle": "Generer kode", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Kopier", + "errorGenerating": "Feil ved generering av kode" + }, + "keyValueEditor": { + "columnKey": "Nøkkel", + "columnValue": "Verdi", + "placeholderKey": "Nøkkel", + "placeholderValue": "Verdi", + "addItem": "Legg til element" + }, + "importCurl": { + "trigger": "Importer cURL", + "dialogTitle": "Importer cURL", + "dialogDescription": "Lim inn cURL-kommandoen din nedenfor for å importere den som en ny forespørsel.", + "placeholder": "curl -X POST https://api.eksempel.no/data -d '...'", + "import": "Importer" + }, + "saveRequest": { + "trigger": "Lagre", + "dialogTitle": "Lagre forespørsel", + "dialogDescription": "Lagre denne forespørselen i en samlingsmappe.", + "labelName": "Forespørselsnavn", + "placeholderName": "Min forespørsel", + "labelFolder": "Mappe", + "placeholderFolder": "Velg en mappe", + "save": "Lagre" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Redigeringsmodus", + "viewOnlyMode": "Visningsmodus", + "insertComponent": "Sett inn komponent", + "addUnorderedList": "Legg til punktliste", + "addOrderedList": "Legg til nummerert liste", + "addTable": "Legg til tabell" + }, + "addBlock": "Legg til blokk", + "media": { + "popoverTitle": "Legg til media", + "singleImage": "Enkeltbilde", + "singleImageDesc": "Last opp ett bilde", + "multipleImages": "Flere bilder", + "multipleImagesDesc": "Last opp bilderutenett", + "video": "Video", + "videoDesc": "Last opp videofil", + "uploadingAlt": "Laster opp...", + "uploadFailedAlt": "Opplasting mislyktes" + }, + "export": { + "fabAria": "Eksporter kode", + "title": "Eksporter kode", + "description": "Kopier HTML- eller JSON-utdataene fra redigeringsinnholdet ditt", + "preview": "Forhåndsvisning", + "html": "HTML", + "json": "JSON", + "previewOptions": "Forhåndsvisningsalternativer", + "enhanceSpaces": "Forbedre mellomrom", + "livePreview": "Direktevisning av rendret HTML", + "htmlTailwind": "HTML med Tailwind CSS-klasser", + "editorJson": "Redigeringstilstand som JSON", + "copyHtml": "Kopier HTML", + "copyJson": "Kopier JSON", + "copied": "Kopiert!" + }, + "template": { + "fabTitle": "Bytt mal", + "title": "Velg en mal", + "description": "Velg en mal for å komme raskt i gang med forhåndsbygde oppsett og innhold", + "catAll": "Alle maler", + "catProductivity": "Produktivitet", + "catCreative": "Kreativt", + "catBusiness": "Forretning", + "catPersonal": "Personlig", + "noTemplates": "Ingen maler funnet i denne kategorien", + "confirmTitle": "Erstatte eksisterende innhold?", + "confirmDescription": "Du har eksisterende innhold i redigeringsprogrammet. Bruk av denne malen vil erstatte alt gjeldende innhold.", + "cannotUndo": "Denne handlingen kan ikke angres.", + "cancel": "Avbryt", + "replaceContent": "Erstatt innhold" + }, + "command": { + "searchPlaceholder": "Søk i kommandoer...", + "noResults": "Ingen kommandoer funnet.", + "turnInto": "Gjør om til", + "h1": { + "label": "Overskrift 1", + "description": "Stor seksjonsoverskrift", + "keywords": "h1,overskrift,tittel,stor" + }, + "h2": { + "label": "Overskrift 2", + "description": "Mellomstor seksjonsoverskrift", + "keywords": "h2,overskrift,undertittel" + }, + "h3": { + "label": "Overskrift 3", + "description": "Liten seksjonsoverskrift", + "keywords": "h3,overskrift,underoverskrift" + }, + "h4": { + "label": "Overskrift 4", + "description": "Veldig liten seksjonsoverskrift", + "keywords": "h4,overskrift" + }, + "h5": { + "label": "Overskrift 5", + "description": "Mindre overskrift", + "keywords": "h5,overskrift" + }, + "h6": { + "label": "Overskrift 6", + "description": "Minste overskrift", + "keywords": "h6,overskrift" + }, + "p": { + "label": "Avsnitt", + "description": "Vanlig tekstavsnitt", + "keywords": "p,avsnitt,tekst,normal" + }, + "code": { + "label": "Kodeblokk", + "description": "Kodesnutt", + "keywords": "kode,kodeblokk,snutt,pre" + }, + "blockquote": { + "label": "Sitat", + "description": "Blokksitat", + "keywords": "sitat,blokksitat,sitering" + }, + "li": { + "label": "Punktliste", + "description": "Enkelt listeelement med punkt", + "keywords": "liste,punkt,usortert,ul,li" + }, + "ol": { + "label": "Nummerert liste", + "description": "Nummerert listeelement", + "keywords": "liste,nummerert,sortert,ol,li" + }, + "img": { + "label": "Bilde", + "description": "Last opp eller legg inn et bilde", + "keywords": "bilde,img,foto,opplasting" + }, + "video": { + "label": "Video", + "description": "Last opp eller legg inn en video", + "keywords": "video,film,mp4,opplasting" + }, + "table": { + "label": "Tabell", + "description": "Opprett en tabell", + "keywords": "tabell,rutenett,rader,kolonner,celler" + } + }, + "toasts": { + "tableImported": "Tabell importert", + "tableImportedDesc": "Markdown-tabellen er importert", + "mediaPasted": "Media limt inn", + "pasteMixed": "{imageCount} bilde(r) og {videoCount} video(er) limt inn.", + "pasteImages": "{count} bilde(r) limt inn.", + "pasteVideos": "{count} video(er) limt inn.", + "pasteFailed": "Innliming mislyktes", + "unexpectedError": "En uventet feil oppstod", + "uploadFailed": "Opplasting mislyktes" + }, + "blockBackground": { + "menuItem": "Endre bakgrunnsfarge", + "dialogTitle": "Blokkbakgrunnsfarge", + "dialogDescription": "Velg en bakgrunnsfarge for denne blokken", + "tabPreset": "Forhåndsdefinerte farger", + "tabCustom": "Egendefinert farge", + "sectionHeading": "Bakgrunnsfarger", + "apply": "Bruk", + "colors": { + "none": "Ingen", + "red": "Rød", + "orange": "Oransje", + "yellow": "Gul", + "green": "Grønn", + "blue": "Blå", + "indigo": "Indigo", + "purple": "Lilla", + "pink": "Rosa", + "teal": "Blågrønn", + "cyan": "Cyan", + "gray": "Grå" + } + }, + "tableDialog": { + "title": "Opprett tabell", + "description": "Opprett en ny tabell eller importer fra markdown", + "markdownCheckbox": "Jeg har en markdown-tabell", + "pasteMarkdownLabel": "Lim inn markdown-tabell", + "markdownPlaceholder": "| Overskrift 1 | Overskrift 2 | Overskrift 3 |\n|----------|----------|----------|\n| Celle 1 | Celle 2 | Celle 3 |\n| Celle 4 | Celle 5 | Celle 6 |", + "markdownHint": "Lim inn markdown-tabellen din ovenfor. Må inkludere overskrift og skillerader.", + "rows": "Rader", + "columns": "Kolonner", + "maxDimensions": "Maksimum: 20 rader × 10 kolonner", + "cancel": "Avbryt", + "importTable": "Importer tabell", + "createTable": "Opprett tabell" + }, + "tableParse": { + "errors": { + "minRows": "Tabellen må ha minst en overskriftsrad og en skillerad", + "pipeFormat": "Tabellrader må starte og slutte med |", + "headerMinCol": "Overskriftsraden må ha minst én kolonne", + "separatorRow": "Andre rad må være en skillerad (f.eks. |---|---|)", + "rowColumnMismatch": "Rad {row} har {got} kolonner, forventet {expected}", + "parseFailed": "Kunne ikke analysere markdown-tabell" + } + }, + "link": { + "editLink": "Rediger lenke", + "addLink": "Legg til lenke", + "selectedText": "Valgt tekst: «{text}»", + "linkUrl": "Lenke-URL", + "urlPlaceholder": "https://eksempel.no", + "update": "Oppdater", + "addLinkButton": "Legg til lenke", + "toastApplied": "Lenke brukt", + "toastLinkedTo": "Lenket til: {url}", + "toastRemoved": "Lenke fjernet", + "toastRemovedDesc": "Lenken er fjernet fra valget" + }, + "customClass": { + "devMode": "Utviklermodus", + "toggleDevModeAria": "Bytt utviklermodus", + "searchPlaceholderDev": "Søk i klasser... (f.eks. «text», «bg», «flex»)", + "searchPlaceholderUser": "Søk i stiler... (f.eks. «rød», «fet», «skygge»)", + "appliesTitle": "Bruker: {className}", + "noMatches": "Ingen klasser funnet som matcher «{query}»", + "toastClassReplaced": "Klasse erstattet", + "toastClassReplacedDesc": "Erstattet «{replaced}» med «{className}»", + "toastCustomClassApplied": "Egendefinert klasse brukt", + "toastAppliedClass": "Brukt klasse: {className}" + }, + "insertComponents": { + "title": "Sett inn komponent", + "description": "Velg en komponent å sette inn i dokumentet ditt", + "noComponents": "Ingen komponenter tilgjengelig", + "categories": { + "media": "Media", + "layout": "Oppsett", + "interactive": "Interaktivt", + "decoration": "Dekorasjon" + }, + "freeImage": { + "name": "Fritt bevegelig bilde", + "description": "Legg til et bilde som kan plasseres hvor som helst og endres fritt" + } + } + }, + "JsonFormatter": { + "title": "JSON-formaterer", + "description": "Formater, valider og rediger JSON i tekst- og trevisning.", + "documentNameText": "Tekstdokument {n}", + "documentNameTree": "Tredokument {n}", + "newDocument": "Ny", + "copy": "Kopier", + "save": "Lagre", + "load": "Last inn", + "saving": "Lagrer...", + "leftDocNameLabel": "Navn på venstre dokument", + "rightDocNameLabel": "Navn på høyre dokument", + "toastCopiedText": "Tekstpanel kopiert", + "toastCopiedTree": "Trepanel kopiert", + "toastCopyFailed": "Kunne ikke kopiere JSON", + "toastLoginRequired": "Logg inn for å lagre dokumenter", + "toastSaved": "Dokument lagret", + "toastSaveFailed": "Kunne ikke lagre dokument", + "format": "Formater", + "compact": "Komprimer", + "sortKeys": "Sorter nøkler", + "repair": "Reparer", + "query": "Spørring", + "toastInvalidJson": "Ugyldig JSON", + "toastAlreadyValid": "JSON er allerede gyldig", + "toastRepaired": "JSON reparert", + "toastRepairFailed": "Kunne ikke reparere JSON" + }, + "JwtDecoder": { + "title": "JWT-dekoder", + "subtitle": "Inspiser header og payload. Utløp bruker exp-claim. Ingenting sendes til en server; signaturen verifiseres ikke.", + "tokenLabel": "Token", + "clear": "Tøm", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopier", + "expiryLabel": "Utløp (exp)", + "signaturePresent": "Signatur til stede (ikke verifisert)", + "noExp": "Ingen exp-claim i payload.", + "status": { + "expired": "Utløpt", + "valid": "Gyldig" + }, + "fields": { + "unix": "Unix", + "local": "Lokal", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Utstedt (iat)", + "notBefore": "Ikke før (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Lim inn en JWT for å dekode.", + "segments": "En JWT trenger minst header og payload (to segmenter).", + "base64": "Kunne ikke base64url-dekode header eller payload." + }, + "relative": { + "future": "om {n} {unit}", + "past": "for {n} {unit} siden" + }, + "units": { + "second": "{count, plural, one {sekund} other {sekunder}}", + "minute": "{count, plural, one {minutt} other {minutter}}", + "hour": "{count, plural, one {time} other {timer}}", + "day": "{count, plural, one {dag} other {dager}}" + }, + "tabs": { + "decode": "Dekod", + "sign": "Generator / signering" + }, + "signer": { + "algLabel": "Algoritme (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Hemmelighet (HS*)", + "secretPlaceholder": "Lim inn HMAC-hemmeligheten…", + "privateKeyLabel": "Privat nøkkel PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "\"none\"-algoritmen lager et usignert token. Dette er usikkert og bør kun brukes til testing.", + "sign": "Signer", + "verify": "Verifiser signatur", + "outputLabel": "Token (kompakt JWS)", + "outputPlaceholder": "Klikk \"Signer\"…", + "copyToken": "Kopier token", + "copied": "Kopiert", + "verifyKeyLabel": "Verifikasjonsnøkkel (valgfritt)", + "verifyKeyPlaceholder": "For RS*: lim inn offentlig nøkkel (SPKI) PEM. For HS*: la stå tomt for å gjenbruke hemmeligheten.", + "verifyKeyHint": "Hvis tom, bruker verifisering signeringsnøkkelen (nyttig for HS*).", + "verifyStatus": { + "valid": "Signaturen er gyldig", + "invalid": "Signaturen er ugyldig" + }, + "securityNote": "Alt kjører lokalt i nettleseren; nøkler og tokens lastes ikke opp." + }, + "signErrors": { + "invalidJson": "Header og payload må være gyldig JSON.", + "unsupportedAlg": "Algoritmen støttes ikke.", + "missingKey": "Signeringsnøkkel kreves for denne algoritmen.", + "cryptoFailed": "Signering mislyktes. Sjekk nøkkelformatet og prøv igjen." + }, + "verifyErrors": { + "empty": "Skriv inn en JWT for å verifisere.", + "segments": "En JWT må ha nøyaktig 3 segmenter for å verifisere en signatur.", + "unsupportedAlg": "Algoritmen støttes ikke eller mangler alg i header.", + "missingKey": "Verifikasjonsnøkkel kreves for denne algoritmen.", + "base64": "Kunne ikke dekode JWT-header eller signatur.", + "cryptoFailed": "Verifisering mislyktes. Sjekk nøkkelformatet og prøv igjen." + } + }, + "CertificatePemDecoder": { + "title": "Sertifikat- / PEM-dekoder", + "subtitle": "Lim inn et X.509-sertifikat (-----BEGIN CERTIFICATE-----) eller en CSR (-----BEGIN CERTIFICATE REQUEST-----). Emne, utsteder, datoer og SAN analyseres lokalt; ingenting lastes opp.", + "pemLabel": "PEM-inndata", + "clear": "Tøm", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Parser…", + "copyHex": "Kopier fingeravtrykk (hex uten kolon)", + "errors": { + "empty": "Lim inn et PEM-sertifikat eller en CSR for å dekode.", + "noBlock": "Fant ingen PEM-blokk for sertifikat eller CSR.", + "parseFailed": "Kunne ikke parse disse PEM-dataene." + }, + "block": { + "title": "PEM-blokk {index}", + "certificate": "X.509-sertifikat", + "csr": "Sertifikatsigneringsforespørsel" + }, + "fields": { + "subject": "Emne", + "issuer": "Utsteder", + "notBefore": "Ugyldig før", + "notAfter": "Ugyldig etter", + "serial": "Serienummer (hex)", + "sha256": "SHA-256-fingeravtrykk", + "sans": "Alternative emnenavn", + "sansNone": "Ingen SAN-utvidelse.", + "signatureAlgorithm": "Signaturalgoritme", + "csrIssuerNote": "CSR-er har ikke utsteder (det står på det utstedte sertifikatet).", + "csrValidityNote": "CSR-er har ikke gyldighetsdatoer (de står på det utstedte sertifikatet)." + }, + "sanType": { + "dns": "DNS", + "dn": "Katalognavn", + "email": "E-post", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Registrert ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL-formaterer", + "subtitle": "Formatter SQL for MySQL, PostgreSQL eller SQLite. Kjører i nettleseren; ingenting lastes opp.", + "dialectLabel": "Dialekt", + "keywordsLabel": "Nøkkelord", + "indentLabel": "Innrykk", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "STORE", + "lower": "små", + "preserve": "Behold" + }, + "spaces": "{n} mellomrom", + "format": "Formater", + "clear": "Tøm", + "counter": "{current} / {max} tegn", + "inputPanel": "Inndata", + "formattedPanel": "Formatert", + "copy": "Kopier", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klikk \"Formater\"…", + "errors": { + "tooLong": "Inndata er begrenset til {max} tegn.", + "couldNotFormat": "Kunne ikke formatere denne SQL-en." + } + }, + "YamlFormatter": { + "title": "YAML-formaterer", + "subtitle": "Formater, valider og konverter YAML i nettleseren. Ingenting lastes opp.", + "outputLabel": "Utdata", + "outputModes": { + "yaml": "YAML (formatert)", + "json": "JSON" + }, + "indentLabel": "Innrykk", + "spaces": "{n} mellomrom", + "format": "Formater", + "clear": "Tøm", + "copy": "Kopier", + "counter": "{current} / {max} tegn", + "inputPanel": "Inndata", + "outputPanel": "Utdata", + "errors": { + "tooLong": "Inndata er begrenset til {max} tegn.", + "couldNotFormat": "Kunne ikke tolke denne YAML." + } + }, + "GraphqlFormatter": { + "title": "GraphQL-formaterer", + "subtitle": "Formater GraphQL-spørsmål, mutasjoner og abonnementer i nettleseren. Ingenting lastes opp.", + "tabs": { + "format": "Formater", + "build": "Spørringsbygger" + }, + "outputModeLabel": "Utdata", + "outputModes": { + "pretty": "Lesbar utskrift", + "minify": "Minifiser" + }, + "format": "Formater", + "clear": "Tøm", + "counter": "{current} / {max} tegn", + "inputPanel": "Inndata", + "outputPanel": "Utdata", + "copy": "Kopier", + "builderHint": "Bygg et skjelett og bruk det i editoren. Variabeldefinisjoner i GraphQL-syntaks (f.eks. $id: ID!) — parenteser er valgfrie.", + "builder": { + "operation": "Operasjon", + "operationName": "Operasjonsnavn", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Variabeldefinisjoner", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Utvalg", + "apply": "Bruk på inndata", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Inndata er begrenset til {max} tegn.", + "couldNotFormat": "Kunne ikke parse eller skrive ut denne GraphQL.", + "emptySelection": "Legg til et utvalg før du bruker." + } + }, + "RegexTester": { + "title": "Regex-tester", + "subtitle": "JavaScript-lignende regex live mot teksten din. Treff markeres; ingenting forlater nettleseren.", + "patternLabel": "Mønster", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i ignorer store/små bokstaver", + "m": "m multiline", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(tom)", + "noFlags": "—", + "matches": "{count, plural, one {treff} other {treff}}", + "testTextLabel": "Testtekst", + "counter": "{current} / {max}", + "clear": "Tøm", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Teksten er begrenset til {max} tegn.", + "invalid": "Ugyldig regulært uttrykk.", + "runaway": "Stoppet etter {max} treff (mulig løpsk mønster)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Timestamp-konverter", + "subtitle": "Unix-sekunder (≤10 sifre) eller millisekunder (lengre heltall), ISO-8601 eller strenger som nettleseren kan tolke. Alt kjører lokalt.", + "inputLabel": "Inndata", + "placeholder": "1744200000 eller 2025-04-09T12:00:00Z", + "now": "Nå", + "copy": "Kopier", + "hint": "Lim inn en verdi eller klikk .", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativ", + "human": "Lesbart" + }, + "fields": { + "seconds": "Sekunder", + "milliseconds": "Millisekunder", + "utc": "UTC", + "local": "Lokal", + "distance": "Avstand" + }, + "errors": { + "empty": "Skriv inn et Unix-timestamp, ISO-streng eller datotekst.", + "invalidNumber": "Ugyldig numerisk timestamp.", + "outOfRange": "Utenfor område for JavaScript Date.", + "couldNotParse": "Kunne ikke tolke som Unix, ISO-8601 eller nettleser-dato." + } + }, + "HttpStatusCodes": { + "title": "HTTP-statuskoder", + "subtitle": "Søk og bla i HTTP-statuskoder for svar (1xx5xx) med korte beskrivelser og RFC-referanser. Kjøres lokalt.", + "header": { + "title": "HTTP-statuskoder", + "description": "Søkbar referanse for HTTP-svarskoder med beskrivelser og RFC-referanser." + }, + "searchPlaceholder": "Søk: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtrer klasse…", + "classFilterAll": "Alle klasser", + "resultsCount": "{count} av {total}", + "table": { + "code": "Code", + "phrase": "Frase", + "description": "Beskrivelse", + "copy": "Kopier" + }, + "noResults": "Ingen treff for “{query}”.", + "specPrefix": "Spes.:", + "copied": "Kode kopiert" + }, + "CronBuilder": { + "title": "Cron-uttrykbygger", + "subtitle": "Standard 5-felts cron: minute hour day month weekday. Parseren bruker seconds=0 internt. Valgfritt 6-felts format (sec min hour dom mon dow) fungerer i uttrykkseditoren.", + "tabs": { + "builder": "Visuell bygger", + "expression": "Uttrykk" + }, + "presetsLabel": "Forvalg", + "fieldsLabel": "Felter", + "rawExpressionLabel": "Rått uttrykk", + "expressionHint": "Forlat feltet for å tolke og synkronisere byggeren. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Gjeldende uttrykk", + "copy": "Kopier", + "quickPick": "Hurtigvalg", + "emptyExpression": "—", + "plainLanguageLabel": "Klartekst", + "nextRunsLabel": "Neste kjøringer (lokal tid)", + "fields": { + "minute": "Minutt", + "hour": "Time", + "dayOfMonth": "Dag i måneden", + "month": "Måned", + "dayOfWeek": "Ukedag" + }, + "presets": { + "everyMinute": "Hvert minutt", + "every5Minutes": "Hvert 5. minutt", + "every15Minutes": "Hvert 15. minutt", + "everyHour": "Hver time (på :00)", + "every6Hours": "Hver 6. time", + "dailyMidnight": "Daglig ved midnatt", + "daily0900": "Daglig kl. 09:00", + "weekdays0900": "Hverdager kl. 09:00", + "weeklySunday": "Ukentlig (søndag 00:00)", + "monthlyFirst": "Månedlig (1. 00:00)", + "yearlyJan1": "Årlig (1. jan 00:00)" + }, + "picks": { + "minute": { + "every": "hvert minutt" + }, + "hour": { + "every": "hver time" + }, + "dom": { + "any": "valgfri dag", + "last": "siste dag" + }, + "month": { + "every": "hver måned" + }, + "dow": { + "any": "valgfri ukedag", + "weekdays": "hverdager", + "weekend": "helg" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mar", + "apr": "Apr", + "may": "Mai", + "jun": "Jun", + "jul": "Jul", + "aug": "Aug", + "sep": "Sep", + "oct": "Okt", + "nov": "Nov", + "dec": "Des" + }, + "weekdays": { + "sun": "Søn", + "mon": "Man", + "tue": "Tir", + "wed": "Ons", + "thu": "Tor", + "fri": "Fre", + "sat": "Lør" + }, + "errors": { + "fieldCount": "Forventet 5 felter (minute hour day month weekday) eller 6 inkludert sekunder.", + "invalid": "Ugyldig cron-uttrykk." + } + }, + "Base64": { + "title": "Base64-koder / dekoder", + "subtitle": "Kode tekst til Base64 eller dekode Base64-strenger", + "upload": "Last opp", + "clear": "Tøm", + "modes": { + "encode": "Kod", + "decode": "Dekod" + }, + "swapTitle": "Bytt input og output", + "panels": { + "plainText": "Klartekst", + "base64Input": "Base64-inndata", + "base64Output": "Base64-utdata", + "decodedText": "Dekodet tekst" + }, + "placeholders": { + "encode": "Skriv inn tekst for å kode til Base64…", + "decode": "Lim inn Base64 for å dekode…" + }, + "dropHere": "Slipp fil her", + "charCount": "{count} tegn", + "download": { + "title": "Last ned output", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Kopier til utklippstavlen", + "outputPlaceholder": "Output vises her…", + "errors": { + "invalidBase64": "Ugyldig Base64-streng. Sjekk input.", + "encodeFailed": "Klarte ikke å kode input." + } + }, + "UrlEncode": { + "title": "URL-koder / dekoder", + "subtitle": "Prosentkoding/-dekoding med encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Last opp", + "clear": "Tøm", + "modes": { + "encode": "Kod", + "decode": "Dekod" + }, + "swapTitle": "Bytt input og output", + "panels": { + "plainText": "Klartekst", + "encodedInput": "Kodet input", + "encodedOutput": "Kodet output", + "decodedText": "Dekodet tekst" + }, + "placeholders": { + "encode": "Skriv inn tekst for prosentkoding…", + "decode": "Lim inn prosentkodet tekst for dekoding…" + }, + "dropHere": "Slipp fil her", + "charCount": "{count} tegn", + "download": { + "title": "Last ned output", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Kopier til utklippstavlen", + "outputPlaceholder": "Output vises her…", + "errors": { + "invalidEncoding": "Ugyldig prosentkoding. Sjekk ufullstendige %‑par eller escapes.", + "encodeFailed": "Kunne ikke kode (ugyldig Unicode i input)." + } + }, + "UrlParser": { + "title": "URL-parser", + "subtitle": "Del opp en URL i protokoll, vert, sti, spørringsparametre og hash (fragment).", + "upload": "Last opp", + "clear": "Tøm", + "panels": { + "input": "URL-inndata", + "output": "Resultat" + }, + "placeholders": { + "input": "Lim inn en URL (f.eks. https://example.com/path?foo=bar#section) eller vert/sti (f.eks. example.com/path)…" + }, + "dropHere": "Slipp fil her", + "charCount": "{count} tegn", + "outputPlaceholder": "De analyserte komponentene vises her…", + "fields": { + "protocol": "Protokoll", + "host": "Vert", + "path": "Sti", + "hash": "Hash", + "queryParams": "Spørringsparametre" + }, + "paramCount": "{count} parametre", + "noParams": "Ingen spørringsparametre.", + "paramKey": "Nøkkel", + "paramValue": "Verdi", + "copyTitle": "Kopier til utklippstavlen", + "copyParamTitle": "Kopier som key=value", + "download": { + "title": "Last ned JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Ugyldig URL. Inkluder et vertsnavn (example.com) og sjekk tegn." + } + }, + "UuidGenerator": { + "title": "UUID-/ULID-generator", + "subtitle": "Generer RFC 9562-UUID-er eller ULID-er i nettleseren. Masseeksport opptil {max} per kjøring.", + "format": "Format", + "kindHints": { + "ulid": "Sorterbar, URL-sikker identifikator på 26 tegn", + "uuid4": "Tilfeldig (RFC 9562)", + "uuid7": "Tidsordnet, Unix ms + tilfeldig", + "uuid1": "MAC-/tidsbasert (RFC 9562)", + "uuid6": "Omordnet gregoriansk tidsbasert", + "uuid3": "MD5-hash av navn + namespace", + "uuid5": "SHA‑1-hash av navn + namespace", + "nil": "Nil-UUID (kun nuller, RFC 9562)" + }, + "countLabel": "Antall (1–{max})", + "nameLabel": "Navn (inndatastreng)", + "namePlaceholder": "f.eks. my.app/resource", + "bulkHint": "I batch legges #0, #1, … til slik at hver ID er unik (v3/v5 er deterministiske).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Egendefinert UUID" + }, + "customNamespaceLabel": "Namespace-UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Generer", + "output": "Utdata", + "lines": "{count} linjer", + "download": { + "title": "Last ned .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Kopier alt", + "uppercaseToggle": "Utdata med store bokstaver", + "hyphensToggle": "Vis bindestreker", + "copyJsonTitle": "Kopier som JSON-array", + "outputPlaceholder": "Klikk \"Generer\"…", + "errors": { + "invalidCustomNamespace": "Egendefinert namespace må være en gyldig UUID.", + "missingName": "Navn er påkrevd for UUID v3 og v5.", + "unknown": "Kunne ikke generere ID-er." + } + }, + "SecretApiKeyGenerator": { + "title": "Generator for hemmelighet / API-nøkkel", + "subtitle": "Generer kryptografisk tilfeldige strenger med konfigurerbar lengde og alfabet. Masseeksport opptil {max} per kjøring. Supplerer UUID-/ULID-generator.", + "alphabetLabel": "Alfabet", + "alphabetHint": "Duplikater fjernes. Hvert symbol velges uniformt med crypto.getRandomValues og avvisningsutvalg (ingen modulo-skjevhet).", + "alphabetCount": "{count} unike symboler", + "presetsLabel": "Forhåndsinnstillinger", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumerisk", + "hex": "Hex (små bokstaver)", + "asciiPrintable": "ASCII utskrivbar" + }, + "lengthLabel": "Lengde per hemmelighet (1–{maxLen})", + "countLabel": "Antall (1–{maxBulk})", + "generate": "Generer", + "output": "Utdata", + "lines": "{count} linjer", + "download": { + "title": "Last ned .txt", + "filename": "hemmeligheter.txt" + }, + "copyTitle": "Kopier alt", + "outputPlaceholder": "Klikk på «Generer»…", + "entropy": "{bits} bits entropi", + "strength": { + "weak": "Svak", + "fair": "Middels", + "strong": "Sterk" + }, + "excludeAmbiguous": "Utelat tvetydige tegn", + "excludeAmbiguousHint": "Fjerner {chars} — tegn som lett leses feil eller er fiendtlige mot skallet.", + "prefixLabel": "Nøkkelprefiks (valgfritt)", + "errors": { + "emptyAlphabet": "Legg til minst ett tegn i alfabetet.", + "alphabetTooLong": "Etter fjerning av duplikater kan alfabetet ha maks {maxAlpha} tegn.", + "lengthOutOfRange": "Lengden må være mellom 1 og {maxLen}.", + "countOutOfRange": "Antall må være mellom 1 og {maxBulk}.", + "unknown": "Kunne ikke generere hemmeligheter." + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum Generator", + "subtitle": "Generer latinsk plassholdertekst for dine oppsett og prototyper.", + "paragraphs": "Avsnitt", + "sentences": "Setninger", + "words": "Ord", + "count": "Antall", + "generate": "Generer", + "copy": "Kopier", + "startWith": "Start med 'Lorem ipsum...'", + "outputPlaceholder": "Output vises her...", + "outputType": "Utdatatype", + "bulletList": "Punktliste", + "htmlMarkup": "HTML-markup", + "htmlMarkupHint": "Pakk inn output i avsnitt, eller bruk en liste for punktliste-modus", + "output": "Utdata", + "download": "Last ned" + }, + "ColorPicker": { + "title": "Fargevelger og konverter", + "subtitle": "Konverter HEX, RGB og HSL; bygg harmoniske paletter. All behandling skjer i nettleseren din.", + "nativePicker": "Innebygd velger", + "chooseColor": "Velg farge", + "eyeDropper": "Øyedråper", + "invalidHex": "Ugyldig HEX-farge.", + "cssValues": "CSS-verdier", + "palettes": "Paletter", + "copied": "Kopiert", + "clickToCopy": "Klikk for å kopiere", + "copyAllHex": "Kopier alle HEX", + "copy": "Kopier", + "copyColor": "Kopier {color}", + "paletteShades": "Nyanser", + "paletteComplementary": "Komplementær", + "paletteTriadic": "Triadisk", + "paletteAnalogous": "Analog", + "paletteSplit": "Delt kompl.", + "paletteSquare": "Kvadratisk" + }, + "ContrastChecker": { + "title": "Kontrastkontroll", + "subtitle": "Beregner WCAG 2.1-kontrastforholdet mellom forgrunn og bakgrunn. Viser AA og AAA for normal og stor tekst. Alt skjer i nettleseren.", + "foreground": "Tekst / forgrunn", + "background": "Bakgrunn", + "hexPlaceholder": "#RRGGBB", + "swap": "Bytt farger", + "chooseForeground": "Velg forgrunnsfarge", + "chooseBackground": "Velg bakgrunnsfarge", + "eyeDropper": "Pipette", + "ratio": "Kontrastforhold", + "copyRatio": "Kopier forhold", + "copied": "Kopiert", + "previewHeading": "Forhåndsvisning", + "previewBody": "Vår sære Zulu fra badeøya spilte jo whist og quickstep i min taxi. Høvdingens kjære squaw får litt pizza i Mexico by.", + "previewCaption": "Eksempeltekst i standardstørrelse.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — normal tekst (4,5:1)", + "levelAALarge": "AA — stor tekst (3:1)", + "levelAAANormal": "AAA — normal tekst (7:1)", + "levelAAALarge": "AAA — stor tekst (4,5:1)", + "pass": "Bestått", + "fail": "Ikke bestått", + "invalidHex": "Ugyldig HEX-farge.", + "largeTextHint": "Stor tekst: 18 pt+ vanlig eller 14 pt+ fet (WCAG-definisjoner)." + }, + "DiffChecker": { + "title": "Diff-sjekker", + "subtitle": "Side-ved-side-linjediff av to tekster. Alt forblir i nettleseren din.", + "rowsCount": "{count} rader", + "truncatedWarning": "Viser de første {max} radene.", + "limitExceeded": "Hver side er begrenset til {max} tegn.", + "clearBoth": "Tøm begge", + "original": "Original", + "modified": "Endret", + "originalPlaceholder": "Lim inn original tekst…", + "modifiedPlaceholder": "Lim inn endret tekst…", + "comparisonLabel": "Side-ved-side-sammenligning", + "emptyState": "Skriv inn tekst i begge paneler for å sammenligne." + }, + "JsonSchemaGenerator": { + "title": "JSON-skjemagenerator", + "subtitle": "Lim inn gyldig JSON for å generere JSON-skjema eller typede modeller. Alt kjører i nettleseren.", + "languageLabel": "Utgangsspråk", + "inputLabel": "JSON-inndata", + "inputPlaceholder": "Lim inn JSON her…", + "outputLabel": "Generert utdata", + "outputPlaceholder": "Skjemaet eller de genererte typene vises her…", + "parseError": "Ugyldig JSON", + "clear": "Tøm", + "copy": "Kopier", + "copied": "Kopiert til utklippstavlen", + "copyFailed": "Kunne ikke kopiere", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-skjema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Laster inn redigeringsprogrammet…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Bilde til Base64", + "subtitle": "Slipp et bilde, få en data-URI eller rå Base64-streng. Alt forblir i nettleseren din.", + "upload": "Last opp bilde", + "clear": "Klar", + "modes": { + "dataUri": "Data-URI", + "rawString": "Raw Base64" + }, + "panels": { + "imageInput": "Bildeinngang", + "output": "Base64-utgang" + }, + "dropHere": "Slipp bildet her", + "charCount": "{count} tegn", + "placeholders": { + "uploadImage": "Dra og slipp et bilde eller klikk på last opp for å starte." + }, + "fileTooLarge": "Filen er for stor. Behandling kan fryse nettleseren din.", + "copyTitle": "Kopier til utklippstavlen", + "download": { + "title": "Last ned tekst", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Bildekomprimering", + "subtitle": "Forminsk JPEG-, PNG- eller WebP-resultat lokalt med kvalitetskontroll — ingenting lastes opp.", + "upload": "Last opp bilde", + "clear": "Tøm", + "dropHere": "Slipp bildet her", + "placeholders": { + "uploadImage": "Dra og slipp et bilde, eller klikk for å velge fil." + }, + "fileTooLarge": "Filen er for stor (maks {maxMb} MB).", + "unsupportedFormat": "Filtypen støttes ikke. Bruk JPEG, PNG, WebP, GIF, AVIF eller BMP.", + "compressFailed": "Kunne ikke komprimere bildet. Prøv en annen fil eller et annet format.", + "panels": { + "original": "Original", + "compressed": "Komprimert forhåndsvisning" + }, + "outputFormat": "Utdataformat", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kvalitet", + "qualityPercent": "{value} %", + "qualityPngHint": "PNG er tapsfritt i nettleseren; kvalitetsglidebryteren gjelder bare JPEG og WebP.", + "sizes": { + "original": "Original: {size}", + "compressed": "Komprimert: {size}", + "saved": "{percent} % mindre" + }, + "largerWarning": "Utdata er større enn inndata — prøv et annet format eller en annen kvalitet.", + "download": "Last ned", + "downloadFilename": "komprimert-bilde", + "processing": "Komprimerer…", + "emptyCompressed": "Komprimert forhåndsvisning vises her." + }, + "CssGradientBuilder": { + "title": "CSS Gradient Builder", + "subtitle": "Visuell CSS-gradientbygger med vinkelkontroll, fargestopper og ett-klikks CSS-kopi.", + "preview": "Forhåndsvisning av gradient", + "settings": "Innstillinger", + "type": "Type", + "linear": "Lineær", + "radial": "Radial", + "angle": "Vinkel", + "stops": "Farge stopper", + "color": "Farge", + "position": "Stilling (%)", + "addStop": "Legg til Stopp", + "deleteStop": "Slett Stopp", + "output": "CSS-utgang", + "copy": "Kopier CSS", + "copied": "Kopiert!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Generator", + "subtitle": "Generer sammenslåtte .gitignore-filer umiddelbart for en spesifikk teknisk stack eller kombinasjon.", + "searchPlaceholder": "Søk i stabler (f.eks. Node, Python, macOS)...", + "noResults": "Ingen stabler funnet.", + "selectedStacks": "Utvalgte stabler", + "output": "Forhåndsvis utdata", + "copy": "Kopier til utklippstavlen", + "copied": "Kopiert!", + "download": "Last ned .gitignore", + "downloading": "Laster ned...", + "errorFetching": "Kunne ikke hente topplisten.", + "errorGenerating": "Kunne ikke generere .gitignore fra valgte stabler.", + "generating": "Genererer...", + "emptyState": "Velg én eller flere teknologistabler for å generere .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Last opp CSV eller Excel, rediger JSON, og last ned CSV eller XLSX. Kun tabell data; Excel-datoer blir ISO-tidsstempler i JSON.", + "dropTitle": "Slipp en CSV- eller Excel-fil", + "dropHint": "Første ark brukes for .xlsx / .xls. CSV må ha en overskriftsrad.", + "chooseFile": "Velg fil", + "jsonLabel": "JSON (array av objekter)", + "jsonPlaceholder": "Lim inn JSON-array av objekter, eller importer CSV / Excel…", + "loadSample": "Last inn eksempel", + "actions": { + "format": "Formater JSON", + "clear": "Tøm", + "copy": "Kopier", + "copied": "Kopiert", + "downloadCsv": "Last ned CSV", + "downloadXlsx": "Last ned Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Data", + "toastLoadedCsv": "CSV lastet inn i redigeringsprogrammet", + "toastLoadedExcel": "Excel lastet inn i redigeringsprogrammet", + "toastFormatted": "JSON formatert", + "toastExportedCsv": "CSV lastet ned", + "toastExportedXlsx": "Excel lastet ned", + "toastCopied": "Kopiert til utklippstavle", + "footnote": "Alt skjer i nettleseren. JSON må være en ikke-tom array av objekter. Nestede verdier serialiseres som JSON i CSV-celler.", + "errors": { + "unsupportedFile": "Bruk en .csv-, .xlsx- eller .xls-fil.", + "parseFailed": "Kunne ikke lese filen.", + "invalidJson": "Ugyldig JSON", + "exportFailed": "Eksport mislyktes", + "copyFailed": "Kunne ikke kopiere" + } + }, + "SnippetManager": { + "title": "Kodeutklipp-administrator", + "subtitle": "Lagre utdrag i nettleseren eller logg inn for å synkronisere med kontoen din. Bytt mellom visning og redigering, kopier med ett klikk, velg språk eller automatisk gjenkjenning, og formater JSON, SQL eller Monaco-støttet kode.", + "snippets": "Utdrag", + "newSnippet": "Nytt utdrag", + "newSnippetTitle": "Nytt utdrag", + "defaultSnippetTitle": "Velkommen", + "defaultSnippetCode": "// Velkommen til kodeutdrag\n// Prøv visning vs redigering, kopier, formater og automatisk språk\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Søk i utdrag…", + "searchAria": "Søk i utdrag", + "emptySearch": "Ingen utdrag samsvarer med søket.", + "snippetTitle": "Tittel", + "snippetTitlePlaceholder": "Navn på utdrag", + "language": "Språk", + "detectedHint": "Utheving som {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Vis", + "modeEdit": "Rediger", + "copy": "Kopier", + "copied": "Kopiert", + "format": "Formater", + "cancel": "Avbryt", + "deleteTitle": "Slette utdrag?", + "deleteDescription": "«{title}» fjernes fra denne nettleseren.", + "deleteConfirm": "Slett", + "editorAria": "Kodeeditor for utdrag", + "editorLoading": "Laster editor…", + "toastCopied": "Kopiert til utklippstavle", + "toastCopyFailed": "Kunne ikke kopiere", + "toastNothingToCopy": "Ingenting å kopiere", + "toastFormatted": "Formatert", + "toastFormatFailed": "Kunne ikke formatere", + "toastFormatJsonInvalid": "Ugyldig JSON — rett syntaksen først", + "toastFormatUnsupported": "Ingen formattering for dette språket — prøv JSON eller SQL eller redigeringsmodus", + "toastDeleted": "Utdrag slettet", + "toastLoadFailed": "Kunne ikke laste utdrag fra serveren.", + "toastSyncFailed": "Kunne ikke synkronisere med serveren.", + "languages": { + "auto": "Automatisk gjenkjenning", + "plaintext": "Ren tekst", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL-klient", + "dialogTitle": "SQL-tilkoblinger", + "mobileSheetTitle": "SQL-utforsker", + "newTabLabel": "Ny spørringsfane", + "vaultLockedTitle": "Hvelv låst", + "vaultLockedDesc": "Lås opp hvelvet for å få tilgang til lagrede tilkoblinger.", + "heroTitle": "SQL-klient", + "heroDesc": "Koble til PostgreSQL, MySQL eller MariaDB. Legitimasjon krypteres i nettleseren.", + "heroAddConnection": "Legg til tilkobling", + "selectTableTitle": "Velg en tabell eller åpne en spørringsfane", + "selectTableDesc": "Klikk på en tabell i sidefeltet eller bruk ⌘↩.", + "toastLoadFail": "Lasting av tilkoblinger mislyktes." + }, + "connection": { + "titleNew": "Ny SQL-tilkobling", + "titleEdit": "Rediger tilkobling", + "labelDbType": "Databasetype", + "labelName": "Tilkoblingsnavn", + "placeholderName": "Min produksjonsdatabase", + "labelHostPort": "Vert og port", + "labelDatabase": "Database", + "placeholderDatabase": "mydb", + "labelUsername": "Brukernavn", + "labelPassword": "Passord", + "labelSsl": "Aktiver SSL / TLS", + "sslDesc": "Krypter tilkoblingen til serveren", + "securityNote": "Legitimasjon krypteres AES-256 i nettleseren før lagring", + "btnTest": "Test tilkobling", + "btnSave": "Lagre og koble til", + "btnUpdate": "Oppdater", + "btnCancelEdit": "Avbryt redigering", + "savedConnections": "Lagrede tilkoblinger", + "emptySaved": "Lagrede tilkoblinger vises her", + "confirmDelete": "Slett denne tilkoblingen?", + "toastFillFields": "Fyll inn vert og database før testing.", + "toastConnectOk": "Tilkoblet! {version}", + "toastConnectFail": "Tilkobling mislyktes: {message}", + "toastUnlockVault": "Lås opp hvelvet først.", + "toastFieldsRequired": "Vert og database er påkrevd.", + "toastUpdated": "Tilkobling oppdatert.", + "toastSaved": "Tilkobling lagret.", + "toastSaveFail": "Lagring mislyktes: {message}", + "toastDeleted": "Tilkobling slettet.", + "toastDeleteFail": "Sletting av tilkobling mislyktes." + }, + "sidebar": { + "title": "SQL-utforsker", + "tooltipAddConnection": "Legg til tilkobling", + "tooltipRefreshSchema": "Oppdater skjema", + "noConnections": "Ingen lagrede tilkoblinger ennå", + "toastSchemaFail": "Lasting av skjema mislyktes: {message}" + }, + "editor": { + "btnRun": "Kjør", + "executing": "Utfører spørring…", + "emptyTitle": "Kjør en spørring for å se resultater", + "emptyHint": "Trykk ⌘↩ eller klikk Kjør", + "toastNoConnection": "Ingen aktiv tilkobling." + }, + "results": { + "filterPlaceholder": "Filtrer resultater…", + "rowCount": "{count} {count, plural, one {rad} other {rader}}", + "rowCountFiltered": "{filtered} / {total} rader", + "exportCsv": "CSV", + "noRows": "Ingen rader returnert" + } + }, + "QrCodeGenerator": { + "title": "QR-kodegenerator", + "subtitle": "Gjør tekst eller URL om til en PNG QR-kode. Alt rendres lokalt i nettleseren.", + "contentLabel": "Innhold", + "contentPlaceholder": "https://example.com, ren tekst, Wi‑Fi-streng, vCard…", + "styleLabel": "Stil", + "dotTypeLabel": "Punktform", + "dotTypeSquare": "Kvadrat", + "dotTypeDots": "Prikker", + "dotTypeRounded": "Avrundet", + "dotTypeExtraRounded": "Ekstra avrundet", + "dotTypeClassy": "Elegant", + "dotTypeClassyRounded": "Elegant avrundet", + "cornerSquareLabel": "Hjørnekvadrat", + "cornerSquareDot": "Prikk", + "cornerSquareSquare": "Kvadrat", + "cornerSquareExtraRounded": "Ekstra avrundet", + "cornerDotLabel": "Hjørneprikker", + "cornerDotDot": "Prikk", + "cornerDotSquare": "Kvadrat", + "eccLabel": "Feilretting", + "eccL": "Lav (~7%)", + "eccM": "Middels (~15%)", + "eccQ": "Kvartil (~25%)", + "eccH": "Høy (~30%)", + "marginLabel": "Margin (moduler)", + "widthLabel": "Bildebredde", + "widthEffective": "Rendres med {px} px", + "darkColor": "Mørke moduler", + "lightColor": "Lyse moduler", + "resetColors": "Tilbakestill farger", + "logoLabel": "Midtlogo", + "logoMarginLabel": "Marg", + "uploadLogo": "Last opp bilde", + "removeLogo": "Fjern logo", + "downloadPng": "Last ned PNG", + "downloadSvg": "Last ned SVG", + "copyImage": "Kopier bilde", + "copied": "Kopiert", + "clear": "Tøm", + "previewAlt": "Forhåndsvisning av QR-kode", + "emptyHint": "Skriv eller lim inn innhold for å se QR-koden.", + "filename": "qr-kode.png", + "errors": { + "generationFailed": "Kunne ikke generere QR-koden. Prøv kortere tekst eller lavere feilretting.", + "copyUnsupported": "Nettleseren støtter ikke kopiering av bilder til utklippstavlen.", + "copyFailed": "Kunne ikke kopiere til utklippstavlen." + } + }, + "IpSubnetCalculator": { + "title": "IP-/subnettkalkulator", + "subtitle": "IPv4- og IPv6-CIDR i nettleseren—ingen eksterne kall.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Nettverk", + "placeholderIpv4": "10.0.0.0/24 eller 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 eller 2001:db8::1/64", + "hintIpv4": "Bruk CIDR (f.eks. /24) eller IPv4-adresse + punktum-nettmaske.", + "hintIpv6": "Angi prefikslengde (f.eks. /64). Uten angivelse antas /128.", + "copyValue": "Kopier verdi", + "rows": { + "cidr": "CIDR", + "network": "Nettverksadresse", + "netmask": "Subnetmaske", + "wildcard": "Wildcard-maske", + "broadcast": "Broadcast", + "firstHost": "Første vert", + "lastHost": "Siste vert", + "totalAddresses": "Adresser totalt", + "usableHosts": "Brukbare verter", + "prefixLength": "Prefikslengde (biter)", + "networkIpv6": "Nettverksadresse", + "lastIpv6": "Siste adresse i området", + "totalAddressesIpv6": "Adresser i subnett" + }, + "usableNote": { + "pointToPoint": "/31 punkt-til-punkt bruker begge adresser som verter (RFC 3021).", + "singleHost": "/32 er én vertsadresse." + }, + "ipv6RangeHint": { + "normal": "Viser hele det matematiske området; ruteregler kan variere.", + "single": "/128 er én adresse.", + "full": "/0 dekker hele IPv6." + }, + "errors": { + "invalidIpv4": "Ugyldig IPv4-nettverk. Prøv CIDR eller adresse + maske.", + "invalidIpv6": "Ugyldig IPv6-nettverk. Bruk adresse/prefiks, f.eks. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Hash-generator", + "subtitle": "MD5-, SHA-1- og SHA-2-summer for tekst eller filer; bcrypt for passord (tekstinndata). Alt kjører lokalt i nettleseren.", + "algorithmLabel": "Algoritme", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Tekst", + "modeFile": "Fil", + "inputLabelText": "Inndata", + "placeholderText": "Skriv eller lim inn tekst…", + "chooseFile": "Velg fil", + "clearFile": "Tøm", + "fileHint": "Filer leses kun i minnet; maks {maxMb} MB. Ingen opplasting.", + "outputLabel": "Digest (hex)", + "copyHash": "Kopier digest", + "copied": "Kopiert", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# tegn} other {# tegn}}", + "emptyHint": "Skriv inn tekst eller velg en fil.", + "securityNote": "MD5 og SHA-1 anbefales ikke for nye sikkerhetsformål; bruk SHA-256 eller SHA-512.", + "errors": { + "fileTooLarge": "Filen er større enn {maxMb} MB.", + "fileReadFailed": "Kunne ikke lese filen." + }, + "bcryptRoundsLabel": "Kostnad (runder)", + "bcryptRoundsHint": "Høyere verdier er tregere og mer motstandsdyktige. Vanlig standard er 10.", + "fileTabDisabledTitle": "bcrypt hasher bare passord fra tekst, ikke vilkårlige filer.", + "placeholderBcrypt": "Skriv eller lim inn et passord…", + "outputLabelBcrypt": "bcrypt-hash", + "copyBcrypt": "Kopier hash", + "emptyHintBcrypt": "Skriv inn et passord for å beregne bcrypt-hash.", + "securityNoteBcrypt": "Hver bcrypt-utdata inneholder tilfeldig salt; samme passord gir en ny streng hver gang. Dette er forventet. Bruk høy kostnad i produksjon.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Krypteringslekeplass", + "subtitle": "AES-GCM i nettleseren: krypter til en JSON-pakke med passordfrase eller rå nøkkel og dekrypter lokalt. Lærerikt og av og til praktisk — ingenting lastes opp.", + "tabEncrypt": "Krypter", + "tabDecrypt": "Dekrypter", + "keyModeLabel": "Nøkkelmateriale", + "modePassphrase": "Passordfrase (PBKDF2 + AES-GCM)", + "modeRawKey": "Rå AES-nøkkel (hex eller Base64)", + "aesBitsLabel": "AES-nøkkellengde", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Koding av rå nøkkel", + "encodingAuto": "Auto (hex hvis det ser ut som hex, ellers Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Rå nøkkel", + "rawKeyPlaceholder": "32-byte nøkkel som 64 hex-tegn eller Base64 for AES-256 (lengden avhenger av AES-størrelsen over).", + "rawKeyPlaceholderDecrypt": "Bare nødvendig hvis pakken har keyDerivation \"none\".", + "passphraseLabel": "Passordfrase", + "passphrasePlaceholder": "Passordfrase brukes bare i nettleseren for å utlede nøkkelen.", + "passphrasePlaceholderDecrypt": "Bare nødvendig hvis pakken bruker PBKDF2-SHA256.", + "pbkdf2IterLabel": "PBKDF2-iterasjoner", + "pbkdf2Hint": "Mellom 10 000 og 2 000 000. Standard samsvarer med vanlig hvelv-oppsett.", + "plaintextLabel": "Klartekst", + "plaintextPlaceholder": "Skriv eller lim inn melding som skal krypteres…", + "bundleInputLabel": "Kryptert pakke (JSON)", + "bundlePlaceholder": "Lim inn JSON fra Krypter (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Kryptert pakke", + "plaintextOutputLabel": "Klartekst", + "encryptButton": "Krypter", + "decryptButton": "Dekrypter", + "copyBundle": "Kopier JSON", + "copyPlaintext": "Kopier klartekst", + "copied": "Kopiert", + "emptyBundleHint": "Kryptert JSON vises her etter kryptering.", + "emptyDecryptHint": "Dekryptert tekst vises her.", + "decryptKeyHint": "Bruk rå nøkkel hvis keyDerivation er \"none\"; passordfrase ved PBKDF2-SHA256. Pakken lagrer salt, iterasjoner, IV og AES-lengde.", + "securityNote": "Nøkler og klartekst forlater ikke nettleseren. Bruk dedikerte verktøy for produksjonshemmeligheter.", + "errors": { + "emptyKey": "Skriv inn en rå nøkkel som matcher AES-størrelsen.", + "emptyPassphrase": "Skriv inn en passordfrase.", + "badKeyMaterial": "Nøkkelen kunne ikke leses som hex eller Base64.", + "wrongKeyLength": "Nøkkellengden matcher ikke AES (16 / 24 / 32 byte).", + "invalidJson": "Ugyldig JSON.", + "invalidBundle": "Denne JSON-en er ikke en støttet krypteringspakke.", + "decryptFailed": "Dekryptering mislyktes — feil hemmelighet, ødelagt pakke eller feil parametre.", + "encryptFailed": "Kryptering mislyktes. Sjekk Web Crypto og inndata." + } + }, + "HmacGenerator": { + "title": "HMAC-generator", + "subtitle": "For signering av webhooks og testing av API-integrasjoner. HMAC-SHA1/256/384/512 i hex eller Base64—beregnet lokalt i nettleseren.", + "secretLabel": "Hemmelig nøkkel", + "messageLabel": "Melding / nyttelast", + "placeholderSecret": "Lim inn signeringshemmelighet…", + "placeholderMessage": "Lim inn råtekst eller streng som skal verifiseres (ofte nøyaktige webhook-bytes).", + "algorithmLabel": "HMAC-algoritme", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Utdataformat", + "formatHex": "Hex (små bokstaver)", + "formatBase64": "Base64", + "outputLabel": "Signatur", + "copyOutput": "Kopier signatur", + "copied": "Kopiert", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8)", + "emptyHint": "Skriv inn en hemmelig nøkkel for å beregne HMAC.", + "securityNote": "Del aldri signeringshemmeligheten. Denne siden laster ikke opp data—alt kjører i nettleseren.", + "errors": { + "cryptoFailed": "Kunne ikke beregne HMAC. Sjekk Web Crypto-støtte og prøv igjen.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA-kodegenerator", + "subtitle": "Lim inn Base32-hemmeligheten og få gjeldende seksifret RFC 6238-kode (SHA-1, 30 s). Kjører lokalt i nettleseren—nyttig for testing av innlogging eller MFA.", + "secretLabel": "Hemmelighet (Base32)", + "placeholderSecret": "Lim inn den hemmelige nøkkelen (bokstaver A–Z og sifrene 2–7, som ved oppsett av autentisering)…", + "codeLabel": "Gjeldende kode", + "copyCode": "Kopier kode", + "copied": "Kopiert", + "nextIn": "Oppdateres om {seconds} s", + "standardHint": "30 s tidssteg, 6 siffer, HMAC-SHA-1 (kompatibel med Google Authenticator).", + "digitsLabel": "Sifre", + "periodLabel": "Periode", + "algorithmLabel": "Algoritme", + "periodSeconds": "{seconds} s", + "emptyHint": "Lim inn en hemmelighet for å generere en seksifret kode.", + "invalidHint": "Rett hemmeligheten for å se koden.", + "securityNote": "Behandle som et passord. Hemmeligheten forlater ikke nettleseren; ingenting lastes opp.", + "importLabel": "Importer fra otpauth://-URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importer", + "qrSectionTitle": "QR-kode for klargjøring", + "qrHint": "Skann med en autentiseringsapp (Google Authenticator, 1Password, Aegis…) for å legge til denne hemmeligheten. QR-koden koder hemmeligheten — behandle den som et passord.", + "accountLabel": "Konto", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Utsteder (valgfritt)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Skriv inn en gyldig hemmelighet for å generere en QR-kode.", + "errors": { + "invalidSecret": "Ugyldig Base32-hemmelighet.", + "cryptoFailed": "Kunne ikke beregne TOTP. Sjekk Web Crypto og prøv igjen.", + "invalidUri": "Dette er ikke en gyldig otpauth:// TOTP-URI.", + "unsupportedUri": "Denne URI-en bruker verdier verktøyet ikke støtter (sifre må være 6 eller 8; periode 15, 30 eller 60 s)." + } + }, + "MarkdownPreview": { + "title": "Markdown-forhåndsvisning & HTML", + "subtitle": "Live Markdown-renderer med HTML-eksport og HTML → Markdown-konvertering. Alt kjører i nettleseren din.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown-inndata", + "htmlInputLabel": "HTML-inndata", + "previewLabel": "Forhåndsvisning", + "htmlOutputLabel": "HTML-kilde", + "mdOutputLabel": "Markdown-utdata", + "inputPlaceholder": "# Hei\\n\\nSkriv inn **markdown** her…", + "htmlInputPlaceholder": "Lim inn HTML her…", + "copyHtml": "Kopier HTML", + "copyMarkdown": "Kopier Markdown", + "exportHtml": "Eksporter HTML", + "copied": "Kopiert", + "charCount": "{count, plural, one {# tegn} other {# tegn}}", + "emptyHint": "Begynn å skrive Markdown for å se live-forhåndsvisning og HTML-utdata.", + "emptyHintHtmlToMd": "Lim inn HTML for å konvertere det til Markdown.", + "securityNote": "Alt kjører lokalt i nettleseren din. Ingenting lastes opp." + }, + "NumberBaseConverter": { + "title": "Grunntall-konverter", + "subtitle": "Konverter heltall mellom baser 2–36. Valgfrie prefikser 0x / 0b / 0o. Alt kjører lokalt i nettleseren.", + "inputBaseLabel": "Fra base", + "outputBaseLabel": "Til base", + "inputLabel": "Inndata", + "placeholderInput": "Skriv inn et heltall i valgt inndata-base…", + "outputLabel": "Konvertert verdi", + "copyOutput": "Kopier resultat", + "copied": "Kopiert", + "swap": "Bytt baser", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8-lengde)", + "emptyHint": "Skriv inn et tall for å konvertere.", + "note": "Siffer over 9 bruker bokstaver A–Z (ikke skille mellom store/små). Mellomrom og understrek ignoreres.", + "errors": { + "invalidNumber": "Verdien er ikke gyldig for valgt inndata-base." + } + }, + "FormatConverter": { + "title": "Formatkonverterer", + "subtitle": "Konverter mellom JSON, YAML, TOML og XML i hvilken som helst retning. Alle 12 kombinasjoner. Alt kjører i nettleseren din.", + "fromLabel": "Fra", + "toLabel": "Til", + "swap": "Bytt", + "swapTitle": "Bytt formater og bruk utdata som inndata", + "copy": "Kopier", + "copied": "Kopiert", + "charCount": "{count, plural, one {# tegn} other {# tegn}}", + "emptyHint": "Lim inn eller skriv inn inndataene dine til venstre for å se det konverterte utdataet.", + "localNote": "Alt kjører lokalt i nettleseren din. Ingenting lastes opp." + }, + "MimeTypeLookup": { + "title": "MIME-type oppslag", + "subtitle": "Slå opp MIME-type (medietype) for en filendelse eller et fullt filnavn. Alt kjører lokalt i nettleseren din.", + "inputLabel": "Filendelse eller filnavn", + "placeholder": "f.eks. .png, package.json, photo.jpeg", + "hint": "Tips: lim inn enten en endelse (med eller uten punktum) eller et fullt filnavn.", + "clear": "Tøm", + "exampleFilename": "Bruk eksempel-filnavn", + "exampleExtension": "Bruk eksempel-endelse", + "resultLabel": "Resultat", + "normalizedLabel": "Normalisert input:", + "mimeTypeLabel": "MIME-type", + "contentTypeLabel": "Content-Type-header", + "charsetLabel": "Tegnsett", + "copyMimeType": "Kopier MIME-type", + "copy": "Kopier", + "copied": "Kopiert", + "emptyHint": "Skriv inn en endelse eller et filnavn for å slå opp MIME-typen.", + "notFound": "Ingen MIME-type funnet for denne inndataen.", + "localNote": "Alt kjører lokalt i nettleseren din. Ingenting lastes opp." + }, + "UserAgentParser": { + "title": "User-Agent-parser", + "subtitle": "Lim inn en User-Agent (UA)-streng og få en oversikt over nettleser, OS, engine, enhet og CPU. Alt kjører lokalt i nettleseren din.", + "inputLabel": "User-Agent-streng", + "placeholder": "Lim inn User-Agent her…", + "hint": "Tips: du kan kopiere en UA fra request-headers, logger eller nettleserens devtools.", + "clear": "Tøm", + "exampleDesktop": "Bruk desktop-eksempel", + "exampleMobile": "Bruk mobil-eksempel", + "resultLabel": "Oversikt", + "browserLabel": "Nettleser", + "osLabel": "Operativsystem", + "engineLabel": "Engine", + "deviceTypeLabel": "Enhetstype", + "deviceVendorLabel": "Produsent", + "deviceModelLabel": "Modell", + "cpuArchLabel": "CPU-arkitektur", + "copyJson": "Kopier JSON", + "copy": "Kopier", + "copied": "Kopiert", + "emptyHint": "Lim inn en User-Agent-streng for å se oversikten.", + "localNote": "Alt kjører lokalt i nettleseren din. Ingenting lastes opp." + }, + "MockDataGenerator": { + "title": "Mock-datagenerator", + "subtitle": "Definer kolonner, velg datatyper og eksporter datasett for seeds, Postman eller tester. Opptil {max} rader, alt i nettleseren — ingenting lastes opp.", + "mockarooLink": "Mockaroo-lignende arbeidsflyt", + "rowsLabel": "Rader", + "rowsHint": "Maks {max} rader per kjøring for å holde fanen responsiv.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "SQL-tabellnavn", + "presetsLabel": "Maler", + "presetPlaceholder": "Last inn mal…", + "presetDefault": "Start (bruker)", + "presetUsers": "Brukere og roller", + "presetProducts": "E-handels-SKU-er", + "presetApiLog": "HTTP-forespørselog", + "generate": "Generer", + "warningDuplicateNames": "Dupliserte kolonnenavn: senere kolonner overskriver tidligere i JSON-objekter. Gi nytt navn.", + "schemaHeading": "Skjema", + "addField": "Legg til kolonne", + "removeField": "Fjern kolonne", + "duplicateField": "Dupliser kolonne", + "colName": "Navn", + "colType": "Type", + "colOptions": "Alternativer", + "colBlank": "Tom %", + "optMin": "min", + "optMax": "maks", + "optDecimals": "des", + "optDateFrom": "fra dato", + "optDateTo": "til dato", + "optValues": "a, b, c", + "optStep": "steg", + "optRowBase": "basis", + "output": "Utdata", + "outputPlaceholder": "Klikk Generer for å opprette data…", + "copy": "Kopier", + "download": "Last ned", + "copied": "Kopiert", + "groups": { + "personal": "Personlig", + "location": "Sted", + "internetApi": "Internet og API", + "numbersIds": "Tall og ID-er", + "dateTime": "Dato og klokkeslett", + "textDesign": "Tekst og design", + "custom": "Tilpasset" + }, + "fieldTypes": { + "first_name": "Fornavn", + "last_name": "Etternavn", + "full_name": "Fullt navn", + "email": "Email", + "username": "Brukernavn", + "password": "Passord", + "phone": "Telefon", + "company": "Bedrift", + "job_title": "Stilling", + "avatar_url": "Avatar-URL", + "city": "By", + "country": "Land", + "country_code": "Landskode (ISO)", + "street_address": "Adresse", + "zip_code": "Postnummer", + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "url": "URL", + "domain": "Domene", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Heltall", + "float": "Desimaltall", + "boolean": "Boolsk", + "uuid": "UUID", + "credit_card": "Testkort", + "semver": "SemVer", + "row_index": "Radindeks", + "sequence": "Sekvens", + "date": "Dato", + "datetime": "Dato og klokkeslett", + "unix_timestamp": "Unix-tidsstempel", + "timezone": "Tidssone", + "word": "Ord", + "sentence": "Setning", + "paragraph": "Avsnitt", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Valutakode", + "language_code": "Språkkode", + "custom_list": "Tilpasset liste" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG-optimalisering", + "subtitle": "Lim inn SVG, minifiser med SVGO i nettleseren og sammenlign UTF-8-størrelse før og etter.", + "inputLabel": "Inndata", + "outputLabel": "Optimalisert", + "placeholder": "Lim inn et -utdrag eller hele filen…", + "outputPlaceholder": "Optimalisert SVG vises her…", + "chars": "{count} tegn • {bytes} byte (UTF-8)", + "preview": "Forhåndsvisning", + "localNote": "All behandling skjer lokalt i nettleseren. Ingenting lastes opp.", + "stats": { + "title": "Størrelse (UTF-8)", + "before": "Før", + "after": "Etter", + "savedBytes": "{count} byte spart ({percent}%)", + "noChange": "Ingen reduksjon — allerede minimal eller tom utdata." + }, + "actions": { + "clear": "Tøm", + "sample": "Last eksempel", + "copy": "Kopier", + "copied": "Kopiert", + "download": "Last ned" + }, + "errors": { + "optimizeFailed": "Kunne ikke optimalisere denne SVG-en." + } + }, + "UnitConverter": { + "title": "Enhetsomregner", + "subtitle": "Konverter mellom 323 enheter på tvers av 43 kategorier", + "category": "Kategori", + "from": "Fra", + "to": "Til", + "value": "Verdi", + "result": "Resultat", + "swapUnits": "Bytt enheter", + "placeholder": "Skriv inn verdi", + "copyResult": "Kopier resultat", + "searchCategory": "Søk kategori...", + "selectCategory": "Velg en kategori", + "noResults": "Ingen kategori funnet." + }, + "DockerComposeGenerator": { + "title": "Docker Compose-generator", + "subtitle": "Velg populære databaser, cache, meldingskøer, omvendte proxyer og observabilitet — kopier en docker-compose.yml klar for lokal utvikling.", + "searchPlaceholder": "Søk (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Tjenester", + "selectedCount": "{count, plural, one {# tjeneste} other {# tjenester}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopier", + "copied": "Kopiert!", + "download": "Last ned", + "emptyState": "Velg én eller flere tjenester for å generere Compose-YAML.", + "hintPorts": "Standardvertsporter reduserer konflikter; endre hvis Docker melder opptatt port. Bytt demopassord før delte miljøer.", + "categories": { + "databases": "Databaser og lagring", + "cache": "Cache og in-memory", + "queues": "Køer og meldinger", + "search": "Søk og logger", + "storage": "Objektlagring", + "proxy": "Omvendte proxyer og ingress", + "observability": "Målinger og sporing", + "mail": "E-post (lokal utvikling)", + "admin": "Admin- og BI-grensesnitt" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Tøm" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (Redis-kompatibel)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (Kafka-kompatibel)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (S3-kompatibel)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (SMTP-fangst)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (dev-server)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Skyv fliser og slå dem sammen for å nå 2048", + "score": "Poengsum", + "best": "Best", + "newGame": "Nytt spill", + "howToPlay": "Bruk piltastene (eller sveip på mobilen) for å flytte brikker. Brikker med samme tall slås sammen. Nå 2048 for å vinne!", + "youWin": "Du har nådd 2048!", + "keepGoing": "Fortsett slik!", + "gameOver": "Spill slutt", + "tryAgain": "Prøv igjen", + "gameOverMessage": "Ingen flere trekk igjen.", + "undo": "Angre" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "Alle faser er fullført!", + "time": "Tid", + "best": "Best", + "difficulty": { + "easy": "Easy", + "medium": "Medium", + "hard": "Hardhet" + }, + "stage": "Fase", + "solved": "Løst", + "score": "Poengsum", + "nextStage": "Neste fase", + "replay": "Spill av på nytt", + "erase": "Slett", + "howToPlay": "Klikk på en celle, og trykk deretter på et tall eller trykk på 1–9. Tilbaketasten eller Delete-tasten fjerner cellen. Uthevede tall er ledetråder. Fyll rutenettet slik at hver rad, kolonne og 3×3-boks inneholder 1–9 uten repetisjoner.", + "pencil": "Pencil", + "undo": "Undo", + "hint": "Hint", + "hintsUsed": "hints used", + "mistakes": "Mistakes", + "failed": "Failed!", + "mistakeMode": "3-mistake mode" + }, + "Snake": { + "title": "Slange", + "subtitle": "Spis mat, voks, unngå vegger og deg selv", + "score": "Poeng", + "best": "Best", + "startPrompt": "Trykk en piletast", + "startButton": "Start spill", + "gameOver": "Spill over", + "playAgain": "Spill igjen", + "howToPlay": "Bruk piltaster eller WASD. Spis mat for å vokse. Hastighet øker hvert 50. poeng.", + "diffEasy": "Lett", + "diffNormal": "Normal", + "diffHard": "Vanskelig", + "diffInfinite": "Uendelig", + "orTap": "eller trykk nedenfor", + "howToPlayInfinite": "Vegger går rundt — ingen veggdød. Spis mat for å vokse.", + "shieldActive": "Shield Active", + "speedBoost": "Speed Boost", + "powerSpeed": "Speed x2 (10 ticks)", + "powerShield": "Survive one death", + "powerShrink": "Shortens tail" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis-tilkoblinger", + "heroTitle": "Redis Commander", + "heroDesc": "Koble til Redis-instanser. Bla gjennom nøkler, rediger verdier og kjør kommandoer. Legitimasjon krypteres i nettleseren din.", + "heroAddConnection": "Legg til tilkobling", + "vaultLockedTitle": "Hvelvet er låst", + "vaultLockedDesc": "Lås opp hvelvet for å få tilgang til lagrede tilkoblinger." + } + }, + "Minesweeper": { + "title": "Minesveiper", + "subtitle": "Finn alle trygge ruter uten å utløse en mine", + "mines": "Miner", + "newGame": "Nytt spill", + "win": "Du vant!", + "loss": "Du tapte!", + "playAgain": "Spill igjen", + "tryAgain": "Prøv igjen", + "startPrompt": "Klikk på en rute for å starte", + "difficulty": { + "beginner": "Nybegynner", + "intermediate": "Middels", + "expert": "Ekspert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Sorter de fallende blokkene for å fullføre rader", + "next": "Neste", + "score": "Poeng", + "best": "Beste", + "lines": "Linjer", + "level": "Nivå", + "pause": "Pause", + "resume": "Fortsett", + "start": "Start", + "restart": "Start på nytt", + "paused": "Pauset", + "gameOver": "Spillet er over" + }, + "SshKeyGenerator": { + "title": "SSH / RSA nøkkelgenerator", + "subtitle": "Generer Ed25519- eller RSA-nøkkelpar i nettleseren din. Last ned private og offentlige nøkler i OpenSSH- og PEM-formater. Ingenting lastes opp.", + "keyTypeLabel": "Nøkkeltype", + "recommended": "anbefalt", + "ed25519Desc": "Rask, liten og moderne. Støttet av GitHub, GitLab og alle store SSH-servere.", + "rsa2048Desc": "Bredt kompatibel. Trygg for de fleste bruksområder.", + "rsa4096Desc": "Maksimal kompatibilitet og styrke for eldre systemer.", + "commentLabel": "Kommentar (valgfritt)", + "commentHint": "Legges til i OpenSSH-nøkkellinjen.", + "generateBtn": "Generer nøkkelpar", + "generating": "Genererer…", + "generatingLong": "Genererer (kan ta et øyeblikk)…", + "copy": "Kopier", + "download": "Last ned", + "privateKeyLabel": "Privat nøkkel (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Offentlig nøkkel (OpenSSH-format)", + "publicKeySpkiLabel": "Offentlig nøkkel (SPKI PEM)", + "browserNote": "Alle nøkler genereres i nettleseren din. Ingenting sendes til noen server.", + "errors": { + "generationFailed": "Nøkkelgenerering mislyktes" + } + }, + "DnsLookup": { + "title": "DNS-oppslag", + "subtitle": "Spør etter DNS-poster for et hvilket som helst domene — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Skriv inn domene (f.eks. github.com)", + "lookupBtn": "Oppslag", + "looking": "Oppslag pågår…", + "refreshBtn": "Oppdater", + "recordTypesLabel": "Posttyper", + "allBtn": "Alle", + "tryLabel": "Prøv:", + "errors": { + "lookupFailed": "DNS-oppslag mislyktes" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt-generator og -kontroll", + "subtitle": "Hash passord med bcrypt, og verifiser hasher — alt forblir i nettleseren din", + "tabs": { + "hash": "Hash", + "verify": "Verifiser" + }, + "showPassword": "Vis passord", + "hidePassword": "Skjul passord", + "copy": "Kopier", + "copied": "Kopiert", + "hash": { + "passwordLabel": "Passord", + "passwordPlaceholder": "Skriv inn et passord som skal hashes…", + "costLabel": "Kostnadsfaktor", + "costHint": "Høyere kostnad dobler arbeidet per trinn. 10–12 er et vanlig valg for produksjon.", + "slowWarning": "Kostnadsfaktorer på 13 og høyere kan ta flere sekunder å beregne.", + "generate": "Generer hash", + "generating": "Hasher…", + "resultLabel": "Bcrypt-hash", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Hash-anatomi", + "algorithm": "algoritme", + "cost": "kostnad", + "salt": "salt (22 tegn)", + "hash": "hash (31 tegn)" + } + }, + "verify": { + "passwordLabel": "Passord", + "passwordPlaceholder": "Skriv inn passordet som skal kontrolleres…", + "hashLabel": "Bcrypt-hash", + "hashPlaceholder": "$2b$10$…", + "idle": "Skriv inn et passord og en bcrypt-hash for å sammenligne dem.", + "invalidHash": "Dette ser ikke ut som en gyldig bcrypt-hash.", + "checking": "Sammenligner…", + "match": "Treff — passordet produserer denne hashen.", + "noMatch": "Ingen treff — passordet produserer ikke denne hashen." + } + }, + "ChmodCalculator": { + "title": "Chmod-kalkulator", + "subtitle": "Konverter Unix-filtillatelser mellom avkrysningsbokser, oktal og symbolsk notasjon", + "grid": { + "title": "Tillatelser", + "read": "Les", + "write": "Skriv", + "execute": "Kjør", + "owner": "Eier", + "group": "Gruppe", + "others": "Andre" + }, + "special": { + "title": "Spesielle bits", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Oktal", + "placeholder": "755 eller 4755", + "invalid": "Skriv inn 3 eller 4 oktale sifre (0–7)." + }, + "symbolic": { + "label": "Symbolsk", + "placeholder": "rwxr-xr-x", + "invalid": "Skriv inn 9 tegn som rwxr-xr-x (s/S, t/T tillatt)." + }, + "output": { + "title": "Kommando", + "copy": "Kopier", + "copied": "Kopiert" + }, + "explain": { + "line": "{cls} kan {perms}.", + "none": "{cls} har ingen tillatelser.", + "setuid": "Setuid: filen kjører med eierens rettigheter.", + "setgid": "Setgid: filen kjører med gruppens rettigheter (nye filer i en mappe arver gruppen dens).", + "sticky": "Sticky: bare en fils eier kan slette eller gi den nytt navn i denne mappen." + }, + "presets": { + "title": "Vanlige forhåndsinnstillinger", + "octal": "Oktal", + "permissions": "Tillatelser", + "description": "Typisk bruk", + "p644": "Vanlige filer: eieren skriver, alle leser.", + "p755": "Kjørbare filer og mapper: alle kan lese og gå inn.", + "p600": "Private filer: bare eieren leser og skriver.", + "p700": "Private kjørbare filer og mapper: bare eieren.", + "p664": "Gruppe-skrivbare filer.", + "p775": "Gruppe-skrivbare mapper og kjørbare filer.", + "p400": "Skrivebeskyttet for eieren (f.eks. private SSH-nøkler)." + } + }, + "CodeScreenshot": { + "title": "Kodeskjermbilde", + "subtitle": "Gjør kode om til vakre, delbare bilder", + "languageLabel": "Språk", + "autoDetect": "Automatisk gjenkjenning", + "backgroundLabel": "Bakgrunn", + "paddingLabel": "Innvendig marg", + "filenameLabel": "Filnavn", + "filenamePlaceholder": "example.ts", + "windowLabel": "Vindusprikker", + "themeLabel": "Vindustema", + "themeDark": "Mørkt", + "themeLight": "Lyst", + "scaleLabel": "Eksportskala", + "copyImage": "Kopier bilde", + "copied": "Kopiert", + "exportPng": "Eksporter PNG", + "inputLabel": "Kode", + "detected": "Gjenkjent: {language}", + "clear": "Tøm", + "placeholder": "Lim inn koden din her…", + "previewLabel": "Forhåndsvisning", + "emptyPreview": "Forhåndsvisningen av koden din vises her", + "exportFailed": "Kunne ikke eksportere bilde", + "copyUnsupported": "Kopiering av bilder støttes ikke i denne nettleseren", + "copyFailed": "Kunne ikke kopiere bilde", + "backgrounds": { + "ocean": "Hav", + "sunset": "Solnedgang", + "candy": "Godteri", + "forest": "Skog", + "midnight": "Midnatt", + "gold": "Gull", + "aurora": "Nordlys", + "slate": "Skifer", + "transparent": "Gjennomsiktig" + } + }, + "CssGenerators": { + "title": "CSS-generatorer", + "subtitle": "Box-shadow, border-radius og flytende clamp() — kopier CSS-en.", + "preview": "Forhåndsvisning", + "output": "CSS", + "copy": "Kopier", + "copied": "Kopiert", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Flytende clamp()" + }, + "boxShadow": { + "presets": "Forhåndsinnstillinger", + "layer": "Lag", + "addLayer": "Legg til lag", + "removeLayer": "Fjern lag", + "x": "X", + "y": "Y", + "blur": "Uskarphet", + "spread": "Spredning", + "color": "Farge", + "alpha": "Alfa", + "inset": "Innvendig", + "presetNames": { + "subtleCard": "Diskret kort", + "elevated": "Hevet", + "hardOffset": "Hard forskyvning", + "neonGlow": "Neonglød", + "inner": "Innvendig", + "layeredSmooth": "Lagdelt myk" + } + }, + "borderRadius": { + "modeLabel": "Modus", + "modes": { + "simple": "Per hjørne", + "blob": "Organisk klatt" + }, + "unit": "Enhet", + "linkCorners": "Koble hjørner", + "randomize": "Tilfeldiggjør", + "horizontal": "Horisontale radier", + "vertical": "Vertikale radier", + "corners": { + "tl": "Øverst til venstre", + "tr": "Øverst til høyre", + "br": "Nederst til høyre", + "bl": "Nederst til venstre" + } + }, + "clamp": { + "minFont": "Min. skrift (px)", + "maxFont": "Maks. skrift (px)", + "minViewport": "Min. viewport (px)", + "maxViewport": "Maks. viewport (px)", + "rootFont": "Rotskrift (px)", + "viewportWidth": "Simulert viewport", + "computedSize": "Beregnet størrelse", + "slope": "Stigning", + "intercept": "Skjæringspunkt", + "previewText": "Vår sære Zulu fra badeøya", + "invalid": "Skriv inn gyldige, ulike viewport-grenser." + } + }, + "ExifViewer": { + "title": "EXIF-visning og -fjerning", + "subtitle": "Inspiser bilders metadata, og fjern dem i nettleseren din", + "dropTitle": "Slipp et bilde her", + "dropHint": "eller klikk for å bla — bildet ditt forlater aldri enheten din", + "browse": "Velg bilde", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Leser metadata…", + "parseFailed": "Kunne ikke lese dette bildet", + "clear": "Fjern bilde", + "gpsWarning": "Inneholder GPS-posisjon", + "openMap": "Vis på OpenStreetMap", + "noMetadata": "Ingen innebygde metadata ble funnet i dette bildet.", + "facts": { + "name": "Navn", + "type": "Type", + "dimensions": "Dimensjoner", + "size": "Størrelse" + }, + "groups": { + "camera": "Kamera", + "exposure": "Eksponering", + "timestamps": "Tidsstempler", + "gps": "GPS-posisjon", + "software": "Programvare og annet" + }, + "gpsCard": { + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "altitude": "Høyde" + }, + "raw": { + "title": "Alle metadata", + "copyJson": "Kopier JSON", + "copied": "Kopiert" + }, + "remove": { + "title": "Fjern metadata", + "jpegDescription": "Fjern EXIF, XMP, IPTC og kommentarer tapsfritt uten å komprimere bildet på nytt.", + "reencodeDescription": "Kod om bildet for å fjerne innebygde metadata. Dette komprimerer pikslene på nytt.", + "keepIcc": "Behold fargeprofil (ICC)", + "stripButton": "Fjern metadata", + "reencodeButton": "Kod om og fjern", + "unsupported": "Fjerning av metadata støttes ikke for denne filtypen.", + "failed": "Kunne ikke fjerne metadata", + "before": "Før", + "after": "Etter", + "download": "Last ned renset bilde" + } + }, + "FaviconGenerator": { + "title": "Favicon-generator", + "subtitle": "Lag favicons og app-ikoner fra ett bilde", + "dropTitle": "Slipp et bilde her", + "dropHint": "PNG, JPEG eller SVG — klikk for å bla", + "browse": "Velg bilde", + "clear": "Fjern bilde", + "backgroundLabel": "Bakgrunn", + "transparent": "Gjennomsiktig", + "solidColor": "Heldekkende", + "cornerLabel": "Hjørneform", + "cornerNone": "Firkantet", + "cornerRounded": "Avrundet", + "cornerCircle": "Sirkel", + "maskPaddingLabel": "Maskerbar marg", + "maskable": "maskable", + "previewLabel": "Forhåndsvisning", + "copy": "Kopier", + "headSnippet": "HTML head-utdrag", + "downloadIco": "Last ned favicon.ico", + "downloadZip": "Last ned alle (.zip)" + }, + "JsonDiff": { + "title": "JSON-diff", + "subtitle": "Semantisk, strukturbevisst sammenligning av to JSON-dokumenter — nøkkelrekkefølge og formatering ignoreres", + "options": { + "ignoreArrayOrder": "Ignorer array-rekkefølge" + }, + "loadBothSamples": "Last eksempler", + "sample": "Eksempel", + "format": "Formater", + "clear": "Tøm", + "formatError": "Kan ikke formatere: inndata er ikke gyldig JSON", + "placeholder": "Lim inn eller skriv JSON her…", + "panels": { + "left": "Original (venstre)", + "right": "Endret (høyre)", + "results": "Forskjeller" + }, + "summary": { + "added": "{count} lagt til", + "removed": "{count} fjernet", + "changed": "{count} endret" + }, + "types": { + "added": "Lagt til", + "removed": "Fjernet", + "changed": "Endret" + }, + "copyChanges": "Kopier som JSON", + "copied": "Kopiert", + "empty": "Lim inn JSON i begge panelene for å se forskjellene deres.", + "identical": "De to dokumentene er semantisk identiske." + }, + "JsonpathPlayground": { + "title": "JSONPath-lekeplass", + "subtitle": "Spør JSON-dokumenter med JSONPath eller JMESPath, og se resultater live", + "engineLabel": "Motor", + "queryLabel": "Spørring", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Jukselapp", + "cheats": { + "jpAllTitles": "Alle boktitler", + "jpAllAuthors": "Hver forfatter, hvor som helst i dokumentet", + "jpFirstBook": "Første bok", + "jpLastBook": "Siste bok", + "jpSlice": "De to første bøkene (utsnitt)", + "jpFilterPrice": "Bøker billigere enn 40", + "jpFilterInStock": "Titler på bøker på lager", + "jpRecursive": "Alle verdier, rekursivt", + "jmAllTitles": "Alle boktitler", + "jmFirstAuthor": "Forfatter av den første boken", + "jmLastBook": "Siste bok", + "jmSlice": "De to første bøkene (utsnitt)", + "jmFilterPrice": "Titler på bøker billigere enn 40", + "jmCount": "Antall bøker på lager", + "jmProjection": "Omform hver bok til et nytt objekt", + "jmSort": "Titler sortert etter pris" + }, + "panels": { + "input": "JSON-inndata", + "results": "Resultater" + }, + "sample": "Eksempel", + "format": "Formater", + "clear": "Tøm", + "formatError": "Kan ikke formatere: inndata er ikke gyldig JSON", + "placeholder": "Lim inn eller skriv JSON her…", + "outputPlaceholder": "Treff vises her…", + "matchCount": "{count, plural, one {# treff} other {# treff}}", + "copy": "Kopier", + "copied": "Kopiert" + }, + "KeycodeInspector": { + "title": "Tastekode-inspektør", + "subtitle": "Trykk på en hvilken som helst tast for å inspisere JavaScript-tastaturhendelsen dens", + "capture": { + "prompt": "Trykk på en hvilken som helst tast…", + "ready": "Lytter — trykk på en hvilken som helst tast på tastaturet", + "clickToFocus": "Klikk her for å fokusere, og trykk deretter på en hvilken som helst tast" + }, + "hero": { + "spaceKey": "(Mellomrom)", + "keyLabel": "event.key", + "repeat": "gjentakelse" + }, + "copyJson": "Kopier JSON", + "copiedJson": "Hendelses-JSON kopiert til utklippstavlen", + "deprecated": "utdatert", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standard", + "left": "Venstre", + "right": "Høyre", + "numpad": "Numerisk tastatur", + "unknown": "Ukjent" + }, + "history": { + "title": "Historikk", + "clear": "Tøm historikk", + "empty": "Tastetrykk vises her." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown-tabellgenerator", + "subtitle": "Rediger et rutenett, lim inn fra et regneark, og kopier en pent justert Markdown- eller HTML-tabell", + "addRow": "Legg til rad", + "addColumn": "Legg til kolonne", + "importLabel": "Importer", + "importPlaceholder": "Lim inn TSV (fra Excel / Google Sheets) eller CSV her…", + "importButton": "Importer til rutenett", + "pasteClipboard": "Lim inn fra utklippstavlen", + "importEmpty": "Ingenting å importere — lim inn noen rader først", + "clipboardError": "Kunne ikke lese fra utklippstavlen", + "clear": "Tøm", + "panels": { + "editor": "Tabellredigerer", + "output": "Utdata" + }, + "align": { + "left": "Venstrejuster", + "center": "Midtstill", + "right": "Høyrejuster" + }, + "headerPlaceholder": "Overskrift", + "cellPlaceholder": "Celle", + "removeRow": "Fjern rad", + "removeColumn": "Fjern kolonne", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopier", + "copied": "Kopiert" + }, + "TimezoneConverter": { + "title": "Tidssonekonverterer", + "subtitle": "Konverter ett øyeblikk på tvers av verden for møteplanlegging.", + "dateTimeLabel": "Dato og tid", + "now": "Nå", + "sourceZoneLabel": "Kilde-tidssone", + "selectZone": "Velg sone", + "searchZone": "Søk tidssoner...", + "noZone": "Ingen tidssone funnet.", + "scrubLabel": "Forskyv tid", + "reset": "Tilbakestill", + "targetsLabel": "Målsoner", + "addZone": "Legg til sone", + "remove": "Fjern", + "localBadge": "Lokal", + "invalidDate": "Skriv inn en gyldig dato og tid.", + "bands": { + "work": "Arbeidstid (09:00–18:00)", + "edge": "Randtimer (tidlig / kveld)", + "night": "Natt" + } + }, + "TokenCounter": { + "title": "LLM-token-teller", + "subtitle": "Tell tokens, visualiser chunker, og estimer API-kostnad.", + "inputLabel": "Tekst", + "sample": "Eksempel", + "clear": "Tøm", + "placeholder": "Lim inn tekst, kode eller JSON for å telle tokens...", + "encoding": "Koding", + "loading": "Laster tokenizer...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-serien)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Tegn", + "words": "Ord", + "ratio": "Tokens/ord", + "ratioSub": "omtrent" + }, + "claudeRow": "Claude (omtrentlig)", + "estimate": "estimat", + "viz": { + "title": "Token-visualisering", + "toggle": "Vis", + "hint": "Slå på for å farge hvert token som en chunk.", + "empty": "Skriv inn tekst for å visualisere tokens.", + "truncated": "Viser de første {limit} av {total} tokens." + }, + "cost": { + "title": "Kostnadskalkulator", + "disclaimer": "redigerbare standardverdier — verifiser gjeldende priser", + "model": "Modell", + "expectedOutput": "Forventede output-tokens", + "customInput": "Input $/MTok", + "customOutput": "Output $/MTok", + "inputCost": "Input-kostnad", + "outputCost": "Output-kostnad", + "total": "Totalt", + "per1000": "Per 1000 kall" + } + }, + "WebhookTester": { + "title": "Webhook-tester", + "subtitle": "Opprett en midlertidig URL, og inspiser innkommende webhook-forespørsler live.", + "intro": { + "title": "Opprett en webhook-URL for å begynne å fange opp forespørsler", + "hint": "Du får en ugjettbar URL som registrerer hver innkommende forespørsel. Den utløper etter 24 timer." + }, + "createBtn": "Opprett webhook-URL", + "creating": "Oppretter…", + "urlLabel": "Din webhook-URL", + "expiresIn": "Utløper om {countdown}", + "copyUrlBtn": "Kopier URL", + "copied": "Kopiert", + "clearBtn": "Tøm", + "newBinBtn": "Ny URL", + "requestsTitle": "Forespørsler", + "selectPrompt": "Velg en forespørsel for å inspisere detaljene.", + "expiredNotice": "Denne webhook-URL-en har utløpt. Opprett en ny for å fortsette å teste.", + "base64Badge": "base64", + "truncatedBadge": "avkortet", + "empty": { + "title": "Venter på forespørsler…", + "hint": "Send en forespørsel til URL-en din, så vises den her umiddelbart. Prøv dette:" + }, + "detail": { + "receivedAt": "Mottatt", + "source": "Kilde-IP", + "contentType": "Content-Type", + "query": "Spørringsparametere", + "headers": "Headere", + "body": "Body", + "copyBody": "Kopier body", + "emptyBody": "Ingen body" + }, + "errors": { + "createFailed": "Kunne ikke opprette webhook-URL. Prøv igjen.", + "clearFailed": "Kunne ikke tømme forespørsler. Prøv igjen." + } + }, + "WebsocketTester": { + "title": "WebSocket-tester", + "subtitle": "Koble til et WebSocket-endepunkt, og send, motta og inspiser meldinger live", + "urlLabel": "Endepunkt-URL", + "protocolsLabel": "Underprotokoller", + "protocolsPlaceholder": "valgfritt, kommaseparert", + "connect": "Koble til", + "disconnect": "Koble fra", + "status": { + "disconnected": "Frakoblet", + "connecting": "Kobler til", + "connected": "Tilkoblet", + "error": "Feil" + }, + "messages": { + "connecting": "Kobler til {url}…", + "connected": "Tilkobling opprettet", + "connectedWithProtocol": "Tilkobling opprettet (protokoll: {protocol})", + "invalidUrl": "Ugyldig URL — må begynne med ws:// eller wss://", + "connectFailed": "Kunne ikke åpne tilkoblingen", + "socketError": "Det oppstod en tilkoblingsfeil", + "notConnected": "Ikke tilkoblet — koble til først", + "binaryMessage": "Mottok en binær melding" + }, + "log": { + "title": "Meldingslogg", + "empty": "Ingen meldinger ennå. Koble til, og begynn å sende.", + "copyAll": "Kopier alle", + "download": "Last ned som .txt", + "clear": "Tøm logg", + "prettyPrint": "Formater JSON pent", + "showRaw": "Vis rå tekst" + }, + "composer": { + "placeholder": "Skriv en melding som skal sendes…", + "send": "Send", + "formatJson": "Formater som JSON", + "invalidJson": "Meldingen er ikke gyldig JSON", + "hint": "Cmd/Ctrl+Enter for å sende" + }, + "ping": { + "label": "Auto-ping", + "seconds": "sek", + "intervalTitle": "Ping-intervall i sekunder", + "payloadPlaceholder": "ping-payload" + } + }, + "WhoisLookup": { + "title": "Whois-oppslag", + "subtitle": "Slå opp registreringsdetaljer for et hvilket som helst domene eller IP-adresse via RDAP", + "placeholder": "example.com eller 8.8.8.8", + "lookupBtn": "Slå opp", + "looking": "Slår opp…", + "refreshBtn": "Oppdater", + "tryLabel": "Prøv:", + "copy": "Kopier", + "copied": "Kopiert", + "notAvailable": "Ikke tilgjengelig", + "summary": { + "registrar": "Registrar", + "network": "Nettverk" + }, + "dates": { + "created": "Opprettet", + "updated": "Oppdatert", + "expires": "Utløper" + }, + "sections": { + "status": "Status", + "nameservers": "Navnetjenere", + "contacts": "Kontakter", + "rawRdap": "Rå RDAP" + }, + "contacts": { + "roles": "Roller", + "name": "Navn", + "organization": "Organisasjon" + }, + "errors": { + "invalidTarget": "Skriv inn et gyldig domenenavn eller IP-adresse", + "lookupFailed": "Oppslag mislyktes (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/nl.json b/apps/desktop-ui/messages/nl.json new file mode 100644 index 00000000..23dfa617 --- /dev/null +++ b/apps/desktop-ui/messages/nl.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Dashboard", + "settings": "Instellingen", + "apiClient": "API-client", + "httpStatusCodes": "HTTP-statuscodes", + "passwordManager": "Wachtwoordbeheer", + "environmentManager": "Omgevingsbeheer", + "bookmarks": "Bladwijzers", + "jsonFormatter": "JSON-formatter", + "emailValidator": "E-mailvalidator", + "nosqlExplorer": "Databaseverkenner", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Encryptie-speeltuin", + "jwtDecoder": "JWT-decoder", + "certificatePemDecoder": "Certificaat- / PEM-decoder", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL-formatter", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL-parser", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Geheim / API-sleutel", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Contrastcontrole", + "jsonSchemaGenerator": "JSON-schema-generator", + "imageToBase64": "Afbeelding naar Base64", + "imageCompressor": "Afbeeldingscompressie", + "cssGradientBuilder": "CSS-verloopbouwer", + "gitignoreGenerator": ".gitignore-generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Codefragmenten", + "qrCodeGenerator": "QR-codegenerator", + "dnsLookup": "DNS-opzoeking", + "ipSubnetCalculator": "IP-/subnetcalculator", + "hashGenerator": "Hash-generator", + "hmacGenerator": "HMAC-generator", + "totpGenerator": "TOTP / 2FA-code", + "markdownPreview": "Markdown-voorbeeld", + "numberBaseConverter": "Grondtal-converter", + "formatConverter": "Formaatconverter", + "mockDataGenerator": "Mock-datagenerator", + "svgOptimizer": "SVG-optimalisatie", + "unitConverter": "Eenhedenconverter", + "dockerComposeGenerator": "Docker Compose-generator", + "breakRoom": "Pauzeruimte", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAML-formatter", + "sshKeyGenerator": "SSH / RSA-sleutelgenerator", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod-calculator", + "codeScreenshot": "Code-screenshot", + "cssGenerators": "CSS-generatoren", + "exifViewer": "EXIF-viewer", + "faviconGenerator": "Favicon-generator", + "jsonDiff": "JSON-diff", + "jsonpathPlayground": "JSONPath-playground", + "keycodeInspector": "Keycode-inspecteur", + "markdownTableGenerator": "Markdown-tabel", + "timezoneConverter": "Tijdzone-converter", + "tokenCounter": "Token-teller", + "webhookTester": "Webhook-tester", + "websocketTester": "WebSocket-tester", + "whoisLookup": "Whois-opzoeking" + }, + "Help": { + "title": "Help en documentatie", + "subtitle": "Hoe MyDevTools in elkaar zit, wat elke app doet en hoe beveiliging en versleuteling je gegevens beschermen.", + "tabs": { + "overview": "Overzicht", + "apps": "Apps", + "security": "Beveiliging", + "tips": "Tips" + }, + "overview": { + "whatIs": { + "title": "Wat is MyDevTools?", + "description": "Een developer-workspace met login: een dashboard en een set tools voor dagelijks gebruik.", + "p1": "Na het inloggen maakt de app een sessie met de MyDevTools API zodat tools je data kunnen laden en opslaan. De zijbalk toont alle apps; tools die je niet gebruikt kun je verbergen via", + "settingsLink": "Instellingen", + "p1After": ".", + "p2": "Sommige functies gebruiken een masterwachtwoord (of globale vault-sleutel) zodat geheimen zoals wachtwoorden en connection strings op je apparaat worden versleuteld voordat ze de server bereiken." + }, + "quickLinks": { + "title": "Snelle links", + "dashboard": "Dashboard", + "settings": "Instellingen" + } + }, + "apps": { + "intro": "Open een tool via de zijbalk of het dashboard. Hieronder staat een korte gids per app.", + "open": "Openen", + "howItWorks": "Hoe het werkt", + "generic": "Gebruik deze tool via de zijbalk voor de bijbehorende workflow." + }, + "security": { + "session": { + "title": "Inloggen en API-sessie", + "description": "Identiteit en hoe de browser met je backend praat.", + "items": { + "authentication": "Authenticatie gebruikt Firebase voor inloggen. Na login wisselt de app je Firebase ID-token om voor API-cookies.", + "accessTokens": "Access tokens zijn server-ondertekende JWT’s (HS256) en tonen welke gebruiker de API aanroept.", + "refreshTokens": "Refresh tokens worden opgeslagen in HttpOnly cookies; de server bewaart een SHA-256 hash van de refresh token, niet de ruwe token." + } + }, + "encryption": { + "title": "Versleuteling die je moet kennen", + "description": "Client-side crypto voor vault en gevoelige connectiegegevens.", + "p1": "Voor de wachtwoordbeheerder, omgevingsbeheer en opgeslagen verbindingen in NoSQL Explorer gebruikt de webapp de Web Crypto API van de browser:", + "items": { + "kdf": "PBKDF2 met SHA-256, 100.000 iteraties en een willekeurige salt leidt een 256-bit sleutel af uit je masterwachtwoord.", + "aes": "AES-256-GCM versleutelt payloads; per versleuteling wordt een willekeurige 12-byte IV gebruikt.", + "scope": "De afgeleide sleutel blijft in de browser (niet exporteerbaar). De server ontvangt alleen ciphertext en IV’s, niet je masterwachtwoord of plaintext secrets." + }, + "warning": "Als je het masterwachtwoord vergeet, kunnen versleutelde gegevens niet worden hersteld. Bewaar het veilig." + }, + "logout": { + "title": "Uitloggen en lokale data", + "body": "Uitloggen wist geheimen in het geheugen, vault-status en (waar van toepassing) masterkey-materiaal uit IndexedDB en beëindigt de API-sessie. Data die alleen in lokale opslag staat kan blijven totdat je die in de browser wist." + } + }, + "tips": { + "title": "MyDevTools veilig gebruiken", + "items": { + "masterPassword": "Gebruik een sterk, uniek masterwachtwoord voor vault-functies.", + "apiClient": "Behandel de API-client als elke HTTP-client: plak geen productiegeheimen in gedeelde omgevingen als je niet weet wie ze kan zien.", + "nosql": "De Databaseverkenner maakt verbinding met databases die jij configureert; gebruik accounts met minimale rechten en netwerkregels waar mogelijk.", + "json": "De JSON-formatter werkt lokaal in de browser—plak alsnog geen zeer gevoelige data op gedeelde apparaten.", + "settings": "Pas zichtbare tools aan in Instellingen om de zijbalk gefocust te houden." + } + } + }, + "Settings": { + "title": "Instellingen", + "language": "Taal", + "spanish": "Spaans", + "english": "Engels", + "french": "Frans", + "arabic": "عربى", + "catalan": "Catalaans", + "chineseSimplified": "Chinees (Vereenvoudigd)", + "czech": "Tsjechisch", + "greek": "Ελληνικά", + "german": "Duits", + "danish": "Deens", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "vietnamese": "Tiếng Việt", + "ukrainian": "Oekraïens", + "polish": "Pools" + }, + "SettingsPage": { + "title": "Instellingen", + "subtitle": "Beheer je accountinstellingen en voorkeuren.", + "userProfile": { + "title": "Gebruikersprofiel", + "description": "Je persoonlijke gegevens en accountdetails.", + "avatarAlt": "Gebruiker", + "anonymousUser": "Anonieme gebruiker", + "noEmailProvided": "Geen e-mailadres opgegeven", + "notLoggedIn": "Niet ingelogd." + }, + "tools": { + "title": "Tools & functies", + "description": "Kies welke tools in je navigatiebalk verschijnen." + }, + "appearance": { + "title": "Weergave", + "description": "Pas het uiterlijk van de applicatie aan op je apparaat.", + "themePreference": "Themavoorkeur", + "themes": { + "light": "Licht", + "dark": "Donker", + "system": "Systeem" + } + }, + "language": { + "title": "Taal", + "description": "Stel de taal in die op het platform wordt gebruikt.", + "selectLanguage": "Taal selecteren", + "selectPlaceholder": "Selecteer een taal", + "helpText": "Het wijzigen van de taal werkt menu's en labels op het hele platform bij.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spaans)", + "ar": "عربى (Arabisch)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Grieks)", + "de": "Deutsch (Duits)", + "da": "Dansk (Deens)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italiaans (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Goedemorgen", + "afternoon": "Goedemiddag", + "evening": "Goedenavond" + }, + "commaName": ", {name}", + "welcomeBack": "Welkom terug", + "welcomeBackNamed": "Welkom terug, {name}", + "tagline": "Wat wil je vandaag bouwen?", + "brandName": "MyDevTools", + "stats": { + "tools": "Tools", + "favorites": "Favorieten", + "pinned": "Vastgemaakt", + "recent": "Recent gebruikt" + }, + "sections": { + "recentlyUsed": "Recent gebruikt", + "favorites": "Favorieten", + "whatsNew": "Wat is er nieuw", + "pinned": "Vastgemaakt" + }, + "groups": { + "apps": "Apps" + }, + "toolCard": { + "defaultDescription": "Ontdek deze tool voor betere functionaliteit.", + "launchTool": "Tool starten" + }, + "tools": { + "toDo": { + "title": "Taken", + "description": "Beheer je takenlijsten efficiënt." + }, + "notes": { + "title": "Notities", + "description": "Leg ideeën vast en organiseer je gedachten." + }, + "passwordManager": { + "title": "Wachtwoordbeheer", + "description": "Bewaar en beheer je wachtwoorden veilig." + }, + "environmentManager": { + "title": "Omgevingsbeheer", + "description": "Sla .env-achtige variabelen per project en omgeving op met tags." + }, + "bookmarks": { + "title": "Bladwijzers", + "description": "Beheer en organiseer je bladwijzers." + }, + "apiClient": { + "title": "API-client", + "description": "Test en debug HTTP-verzoeken." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Verken MongoDB-databases." + }, + "emailValidator": { + "title": "E-mailvalidator", + "description": "Verifieer en valideer e-mailadressen." + }, + "encryptionPlayground": { + "title": "Encryptie-speeltuin", + "description": "AES-GCM met ruwe sleutel of wachtwoordzin; JSON-bundels, Web Crypto alleen in de browser." + }, + "jwtDecoder": { + "title": "JWT-decoder", + "description": "Decodeer JWT-header, payload en verloopdatum." + }, + "certificatePemDecoder": { + "title": "Certificaat- / PEM-decoder", + "description": "Decodeer X.509-certificaten en CSR’s: subject, uitgever, geldigheid en SAN’s." + }, + "sshKeyGenerator": { + "title": "SSH / RSA-sleutelgenerator", + "description": "Genereer Ed25519- of RSA-sleutelparen in uw browser. Download in OpenSSH- en PEM-formaat." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL-formatter", + "description": "Formatteer GraphQL met syntax highlighting en een eenvoudige querybouwer." + }, + "yamlFormatter": { + "title": "YAML-formatter", + "description": "Formatteer, valideer en converteer YAML naar JSON in de browser." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL-parser", + "description": "Splits een URL op in protocol, host, pad, queryparameters en hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Geheim / API-sleutel", + "description": "Cryptografisch willekeurige strings met configureerbaar alfabet, lengte en bulkexport." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Contrastcontrole", + "description": "WCAG 2.1-contrastverhouding tussen tekst en achtergrond (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "JSON-schema-generator", + "description": "Leid een JSON-schema en getypeerde modellen af van voorbeeld-JSON." + }, + "imageToBase64": { + "title": "Afbeelding naar Base64", + "description": "Converteer afbeeldingen naar gegevens-URI of onbewerkte Base64-tekenreeksen." + }, + "imageCompressor": { + "title": "Afbeeldingscompressie", + "description": "Comprimeer JPEG, PNG en WebP lokaal met een kwaliteitsschuifregelaar." + }, + "cssGradientBuilder": { + "title": "CSS-verloopbouwer", + "description": "Visuele CSS-gradiëntbouwer met hoekcontrole en kleurstops." + }, + "gitignoreGenerator": { + "title": ".gitignore-generator", + "description": "Genereer direct samengevoegde .gitignore-bestanden uit meer dan 500 tech-stacks." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Zet CSV- of Excel-bestanden om naar JSON en exporteer terug naar CSV of XLSX." + }, + "snippetManager": { + "title": "Codefragmenten", + "description": "Bewaar fragmenten met syntaxisaccentuering, automatische taaldetectie en opmaak." + }, + "qrCodeGenerator": { + "title": "QR-codegenerator", + "description": "Maak PNG QR-codes van tekst of URL’s in de browser." + }, + "ipSubnetCalculator": { + "title": "IP-/subnetcalculator", + "description": "IPv4 en IPv6 CIDR: netmasker, bereiken en adressen." + }, + "dnsLookup": { + "title": "DNS-opzoeking", + "description": "Zoek DNS-records op voor elk domein — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Hash-generator", + "description": "MD5, SHA-1 en SHA-2 voor tekst of bestanden." + }, + "hmacGenerator": { + "title": "HMAC-generator", + "description": "Webhooks ondertekenen en API-integraties testen met HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "TOTP / 2FA-codegenerator", + "description": "Plak een Base32-geheim van je authenticator; de zescijferige code vernieuwt elke 30 s—handig om inloggen en MFA te testen." + }, + "markdownPreview": { + "title": "Markdown-voorbeeld", + "description": "Live Markdown-renderer met HTML-export en HTML → Markdown-conversie." + }, + "numberBaseConverter": { + "title": "Grondtal-converter", + "description": "Converteer gehele getallen tussen grondtallen 2–36." + }, + "formatConverter": { + "title": "Formaatconverter", + "description": "Converteer tussen JSON, YAML, TOML en XML in elke richting. Alle 12 combinaties worden ondersteund." + }, + "mockDataGenerator": { + "title": "Mock-datagenerator", + "description": "Definieer kolommen, kies gegevenstypen en exporteer fixtures voor seeds, Postman of tests. Tot 5000 rijen, alles in je browser — er wordt niets geüpload." + }, + "httpStatusCodes": { + "title": "HTTP-statuscodes", + "description": "Zoek HTTP-statuscodes met beschrijvingen (1xx–5xx)." + }, + "svgOptimizer": { + "title": "SVG-optimalisatie", + "description": "Plak SVG, verwijder ruis met SVGO en zie UTF-8-groottebesparing." + }, + "unitConverter": { + "title": "Eenhedenconverter", + "description": "Converteren tussen 323 eenheden in 43 wetenschappelijke en technische categorieën." + }, + "dockerComposeGenerator": { + "title": "Docker Compose-generator", + "description": "Kies databases, caches, wachtrijen, proxy’s en observability-images; exporteer een docker-compose.yml voor lokale stacks." + }, + "game2048": { + "title": "2048", + "description": "Schuif en voeg tegels samen om 2048 te bereiken. Klassiek nummerpuzzelspel." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klassieke nummerpuzzel. 3 moeilijkheidsgraden, elk 150 levels." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Blader door sleutels, bekijk waarden en voer opdrachten uit op elke Redis-instantie." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt-generator en -controle", + "description": "Hash wachtwoorden met bcrypt en verifieer bestaande hashes." + }, + "chmodCalculator": { + "title": "Chmod-calculator", + "description": "Zet Unix-rechten om tussen octaal, symbolisch en selectievakjes." + }, + "codeScreenshot": { + "title": "Code-screenshot", + "description": "Zet broncode om in mooie, deelbare afbeeldingen." + }, + "cssGenerators": { + "title": "CSS-generatoren", + "description": "Generatoren voor box-shadow, border-radius en vloeiende clamp()." + }, + "exifViewer": { + "title": "EXIF-viewer en -verwijderaar", + "description": "Inspecteer en verwijder EXIF-metadata van foto's lokaal." + }, + "faviconGenerator": { + "title": "Favicon-generator", + "description": "Genereer favicons, app-iconen en manifest-snippets." + }, + "jsonDiff": { + "title": "JSON-diff", + "description": "Semantische, structuurbewuste vergelijking van twee JSON-documenten." + }, + "jsonpathPlayground": { + "title": "JSONPath-playground", + "description": "Bevraag JSON met JSONPath of JMESPath en zie de resultaten live." + }, + "keycodeInspector": { + "title": "Keycode-inspecteur", + "description": "Druk op een toets om de JavaScript-event-codes en modifiers te zien." + }, + "markdownTableGenerator": { + "title": "Markdown-tabelgenerator", + "description": "Maak Markdown- en HTML-tabellen in een spreadsheetachtige rasteditor." + }, + "timezoneConverter": { + "title": "Tijdzone-converter", + "description": "Zet een tijd om naar veel zones, met aanwijzingen voor dagverschuiving en werktijden." + }, + "tokenCounter": { + "title": "LLM-token-teller", + "description": "Tel LLM-tokens, visualiseer chunks en schat de API-kosten." + }, + "webhookTester": { + "title": "Webhook-tester", + "description": "Vang inkomende webhook-verzoeken op en inspecteer ze in realtime." + }, + "websocketTester": { + "title": "WebSocket-tester", + "description": "Verbind met WebSocket-endpoints en wissel berichten uit in realtime." + }, + "whoisLookup": { + "title": "Whois-opzoeking", + "description": "Zoek registratiegegevens van een domein of IP op via RDAP." + } + }, + "tabs": { + "apps": "Apps", + "analytics": "Analyse", + "ui": "Interface" + }, + "analytics": { + "title": "Jouw data in één oogopslag", + "subtitle": "Tellingen gesynchroniseerd met je account op de server.", + "loadError": "Kan statistieken niet laden.", + "retry": "Opnieuw", + "loginHint": "Log in om gebruiksanalyses en interfacevoorkeuren te zien.", + "passwordEntries": "Wachtwoorditems", + "bookmarks": "Bladwijzers", + "bookmarkFolders": "Bladwijzermappen", + "tasksTotal": "Taken", + "tasksCompleted": "Voltooid", + "tasksOngoing": "Bezig", + "tasksNotStarted": "Niet gestart", + "projects": "Projecten", + "nosqlConnections": "DB-verbindingen", + "notes": "Notities", + "apiClientCollections": "API-collecties", + "apiClientEnvironments": "API-omgevingen", + "apiClientHistory": "API-geschiedenisitems", + "jsonFormatterDocuments": "JSON-formatteerdocumenten", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Codefragmenten", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Interfacevoorkeuren", + "subtitle": "Thema, taal en zichtbare tools in de zijbalk — vanuit je accountinstellingen.", + "loadError": "Kan voorkeuren niet laden.", + "loginHint": "Log in om je opgeslagen interfacevoorkeuren te zien.", + "theme": "Thema", + "accent": "Accentkleur", + "locale": "Taal", + "enabledTools": "Zichtbare tools", + "favoriteTools": "Favoriete tools", + "toolsWithUsage": "Tools met gebruiksstatistieken" + }, + "signIn": "Inloggen", + "noResults": "Geen tools komen overeen met je zoekopdracht", + "noResultsHint": "Probeer een ander trefwoord of wis het filter.", + "filterAll": "Alles", + "loginCta": { + "description": "Log in om je recent gebruikte tools bij te houden." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Laden...", + "myTasksTitle": "Mijn taken", + "statsLine": "{total} taken • {percent}% voltooid", + "statsLineDesktop": "{total} totaal • {percent}% voltooid" + }, + "filters": { + "all": "Alles", + "total": "Totaal", + "allProjects": "Alle projecten", + "export": "Exporteren", + "kanban": "Kanban", + "list": "Lijst", + "searchPlaceholder": "Taken zoeken...", + "searchAria": "Taken zoeken", + "clearSearchAria": "Zoekopdracht wissen", + "foundTasks": "{count} {count, plural, one {taak} other {taken}} gevonden", + "filterByAllAria": "Filteren op alles", + "filterByStatusAria": "Filteren op {status}" + }, + "status": { + "not-started": { + "label": "Niet gestart", + "description": "Taken die nog niet zijn gestart" + }, + "ongoing": { + "label": "Bezig", + "description": "Taken die momenteel in uitvoering zijn" + }, + "completed": { + "label": "Voltooid", + "description": "Taken die zijn afgerond" + } + }, + "priorities": { + "high": "Hoog", + "medium": "Gemiddeld", + "low": "Laag" + }, + "form": { + "addNewTaskLabel": "Nieuwe taak toevoegen", + "multilinePlaceholder": "Voeg taakdetails toe... (Shift+Enter voor nieuwe regel)", + "singlelinePlaceholder": "Wat moet er gedaan worden? Druk op Enter om toe te voegen", + "taskInputAria": "Taakinvoer", + "projectPlaceholder": "Project", + "selectProject": "Project selecteren", + "errorSelectProject": "Selecteer een project of maak een nieuw project aan om een taak toe te voegen.", + "hintMultiline": "Shift+Enter voor nieuwe regel, Enter om toe te voegen", + "hintSingleline": "Enter om toe te voegen", + "shortcutsAria": "Sneltoetsen", + "shortcutsLabel": "Sneltoetsen", + "shortcutAddTask": "Taak toevoegen", + "shortcutNewLine": "Nieuwe regel", + "shortcutClear": "Wissen", + "addButton": "Toevoegen" + }, + "list": { + "ariaLabel": "Takenlijst", + "loading": "Je taken laden...", + "emptyTitle": "Geen taken gevonden", + "emptyPrimary": "Begin door hierboven je eerste taak toe te voegen! Druk op Enter om snel taken toe te voegen.", + "emptySecondary": "Probeer je filters of zoekopdracht aan te passen." + }, + "mobileDrawer": { + "addTaskSrOnly": "Taak toevoegen", + "addNewTaskTitle": "Nieuwe taak toevoegen", + "addNewTaskDescription": "Maak een nieuwe taak aan om je voortgang bij te houden.", + "cancel": "Annuleren" + }, + "kanban": { + "ariaLabel": "Kanbanbord", + "loading": "Je taken laden...", + "dropHere": "Taak hier neerzetten", + "noTasksYet": "Nog geen taken", + "emptyColumnAria": "Lege kolom", + "columnAria": "{title} kolom met {count} taken", + "countInColumnAria": "{count} taken in {title}" + }, + "taskItem": { + "confirmDelete": "Weet je zeker dat je deze taak wilt verwijderen?", + "statusPlaceholder": "Status", + "changeStatusAria": "Taakstatus wijzigen", + "taskOptionsAria": "Taakopties", + "editTask": "Taak bewerken", + "copyTask": "Taak kopiëren", + "copied": "Gekopieerd!", + "deleteTask": "Taak verwijderen", + "statusHeading": "Status", + "editTaskAria": "Taak bewerken" + }, + "editDialog": { + "title": "Taak bewerken", + "description": "Werk taakdetails bij, voeg subtaken toe, stel prioriteiten in en meer.", + "taskTitleLabel": "Taaknaam *", + "taskTitlePlaceholder": "Wat moet er gedaan worden?", + "descriptionLabel": "Beschrijving", + "descriptionPlaceholder": "Voeg meer details toe over deze taak...", + "statusLabel": "Status", + "priorityLabel": "Prioriteit", + "dueDateLabel": "Deadline", + "pickDate": "Kies een datum", + "clearDate": "Datum wissen", + "projectLabel": "Project", + "selectProjectPlaceholder": "Selecteer een project", + "noProject": "Geen project", + "timeEstimateLabel": "Tijdschatting (minuten)", + "timeEstimatePlaceholder": "Hoe lang duurt dit?", + "tagsLabel": "Tags", + "customTagNamePlaceholder": "Aangepaste tagnaam", + "subtasksLabel": "Subtaken", + "addSubtaskPlaceholder": "Voeg een subtaak toe...", + "cancel": "Annuleren", + "saving": "Opslaan...", + "saveChanges": "Wijzigingen opslaan" + }, + "export": { + "title": "Taken exporteren", + "description": "Exporteer je taken naar JSON-, CSV- of Excel-formaat.", + "projectLabel": "Project", + "statusLabel": "Status", + "tagLabel": "Tag", + "allProjects": "Alle projecten", + "allStatuses": "Alle statussen", + "allTags": "Alle tags", + "exportAsJsonTitle": "Exporteren als JSON", + "exportAsJsonDescription": "Exporteer taakgegevens inclusief subtaken, tags en metadata.", + "exportJson": "JSON exporteren", + "exportAsCsvExcelTitle": "Exporteren als CSV / Excel", + "exportAsCsvExcelDescription": "Exporteer taken voor spreadsheet-applicaties.", + "exportCsv": "CSV exporteren", + "exportExcel": "Excel exporteren", + "close": "Sluiten", + "toastNoTasks": "Geen taken om te exporteren met huidige filters", + "toastExportedJson": "{count} taken geëxporteerd naar JSON!", + "toastExportedCsv": "{count} taken geëxporteerd naar CSV!", + "toastExportedExcel": "{count} taken geëxporteerd naar Excel!", + "toastFailedJson": "Exporteren naar JSON mislukt", + "toastFailedCsv": "Exporteren naar CSV mislukt", + "toastFailedExcel": "Exporteren naar Excel mislukt", + "unknownProject": "Onbekend project" + }, + "ack": { + "taskAddedTitle": "Taak succesvol toegevoegd", + "taskAddedFailedTitle": "Taak toevoegen mislukt", + "taskUpdatedTitle": "Taak succesvol bijgewerkt", + "taskUpdatedFailedTitle": "Taak bijwerken mislukt", + "taskStatusUpdateFailedTitle": "Taakstatus bijwerken mislukt", + "taskMovedTitle": "Taak verplaatst naar {status}", + "tryAgain": "Probeer het opnieuw.", + "taskDeletedTitle": "Taak \"{text}\" verwijderd", + "undo": "Ongedaan maken", + "taskRestoredTitle": "Taak hersteld", + "taskRestoredSuccessTitle": "Taak succesvol hersteld", + "taskRestoreFailedTitle": "Taak herstellen mislukt. Probeer het opnieuw.", + "taskDeleteFailedTitle": "Taak verwijderen mislukt. Probeer het opnieuw.", + "tasksImportedTitle": "{count} {count, plural, one {taak} other {taken}} succesvol geïmporteerd!", + "tasksImportFailedTitle": "Taken importeren mislukt. Probeer het opnieuw.", + "unknownProject": "Onbekend project", + "projectCreatedTitle": "Project succesvol aangemaakt", + "projectCreateFailedTitle": "Project aanmaken mislukt", + "projectUpdatedTitle": "Project succesvol bijgewerkt", + "projectUpdateFailedTitle": "Project bijwerken mislukt", + "projectDeletedTitle": "Project succesvol verwijderd", + "projectDeleteFailedTitle": "Project verwijderen mislukt" + } + }, + "Notes": { + "page": { + "loading": "Laden..." + }, + "layout": { + "mobileNavTitle": "Notitienavigatie", + "openMenuAria": "Notitiemenu openen" + }, + "sidebar": { + "title": "Notities", + "createNoteAria": "Notitie aanmaken", + "searchPlaceholder": "Notities zoeken...", + "loading": "Laden...", + "noNotesFound": "Geen notities gevonden.", + "noNotesYet": "Nog geen notities. Klik op + om er een aan te maken.", + "inParent": "in {parentTitle}", + "untitled": "Naamloos", + "addSubPage": "Subpagina toevoegen", + "delete": "Verwijderen", + "deleteConfirmTitle": "Notitie verwijderen?", + "deleteConfirmDescription": "Weet je zeker dat je \"{title}\" wilt verwijderen? Dit kan niet ongedaan worden gemaakt.", + "cancel": "Annuleren", + "confirmDelete": "Verwijderen", + "noteOptionsAria": "Notitieopties" + }, + "editor": { + "emptyState": "Selecteer een notitie of maak een nieuwe aan om te beginnen.", + "titlePlaceholder": "Naamloos", + "saving": "Opslaan…", + "ready": "Gereed", + "saved": "Opgeslagen {time}", + "stats": "{words} woorden · {read} min lezen", + "template": "Sjabloon", + "applyTemplate": "Sjabloon toepassen", + "exportMdLabel": ".md", + "exportMd": "Exporteren als Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Exporteren als HTML", + "focusMode": "Focusmodus", + "exitFocusMode": "Focusmodus verlaten", + "save": "Opslaan", + "addTag": "Tag toevoegen…", + "removeTag": "Tag {tag} verwijderen", + "loading": "Laden…", + "searchHint": "om te zoeken", + "focusHint": "focusmodus", + "notePath": "Notitiepad" + }, + "context": { + "authRequiredError": "Gebruiker niet geauthenticeerd", + "defaultTitle": "Naamloos", + "cloudSyncImageError": "Schakel cloudsynchronisatie in om notitieafbeeldingen op te slaan. Tekstnotities blijven lokaal." + } + }, + "PasswordManager": { + "page": { + "loading": "Laden...", + "title": "Wachtwoordbeheer", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Wachtwoorden ontsleutelen mislukt", + "toastLoadFailed": "Wachtwoorden laden mislukt", + "toastUnlockedAuto": "Kluis automatisch ontgrendeld", + "toastAutoUnlockFailed": "Automatisch ontgrendelen mislukt. Ontgrendel handmatig.", + "toastConnectFailed": "Verbinding met server mislukt", + "toastAutoUnlockWarning": "Kluis ontgrendeld, maar automatisch ontgrendelen werkt mogelijk niet bij herladen", + "errorIncorrectPassword": "Onjuist hoofdwachtwoord", + "errorUnlockFailed": "Kluis ontgrendelen mislukt", + "errorPasswordsMismatch": "Wachtwoorden komen niet overeen", + "errorPasswordTooShort": "Wachtwoord moet minimaal 8 tekens bevatten", + "errorSetupFailed": "Kluis instellen mislukt", + "errorConnectFailed": "Verbinding met server mislukt.", + "mobileTitleCreate": "Maak je kluis aan", + "mobileTitleWelcome": "Welkom terug", + "mobileSubtitleCreate": "Stel een hoofdwachtwoord in om je gegevens te beschermen", + "mobileSubtitleUnlock": "Voer je hoofdwachtwoord in om te ontgrendelen", + "placeholderMasterPassword": "Hoofdwachtwoord", + "placeholderConfirmPassword": "Wachtwoord bevestigen", + "labelMasterPassword": "Hoofdwachtwoord", + "labelConfirmPassword": "Wachtwoord bevestigen", + "placeholderEnterMaster": "Voer hoofdwachtwoord in", + "placeholderConfirmMaster": "Bevestig hoofdwachtwoord", + "buttonCreateVault": "Kluis aanmaken", + "buttonUnlock": "Ontgrendelen", + "buttonUnlockVault": "Kluis ontgrendelen", + "footerCreate": "Je wachtwoord wordt nooit opgeslagen en kan niet worden hersteld", + "footerUnlock": "Beveiligd met end-to-end-versleuteling", + "desktopTitleSetup": "Wachtwoordkluis instellen", + "desktopTitleUnlock": "Kluis ontgrendelen", + "desktopDescriptionSetup": "Maak een hoofdwachtwoord aan om je gegevens te beveiligen. Dit wachtwoord wordt nooit opgeslagen en kan niet worden hersteld.", + "desktopDescriptionUnlock": "Voer je hoofdwachtwoord in om toegang te krijgen tot je beveiligde gegevens." + }, + "list": { + "loading": "Je wachtwoorden laden...", + "emptyTitle": "Kluis is leeg", + "emptyHintMobile": "Tik op de + knop om je eerste wachtwoord toe te voegen", + "emptyHintDesktop": "Je hebt nog geen wachtwoorden opgeslagen. Klik op de knop \"Wachtwoord toevoegen\" hierboven om te beginnen.", + "addPassword": "Wachtwoord toevoegen", + "searchPlaceholderMobile": "Doorzoek je kluis...", + "searchPlaceholderDesktop": "Kluis doorzoeken...", + "importExport": "Importeren / exporteren", + "lockVault": "Kluis vergrendelen", + "allPasswordsTitle": "Alle wachtwoorden", + "passwordCount": "{count, plural, one {# wachtwoord} other {# wachtwoorden}}", + "vaultHealthDrawerTitle": "Kluisgezondheid", + "noResults": "Geen wachtwoorden gevonden voor \"{query}\"", + "tableService": "Service", + "tableUsername": "Gebruikersnaam", + "tablePassword": "Wachtwoord", + "tableUrl": "URL", + "tableActions": "Acties", + "gridViewAria": "Rasterweergave", + "listViewAria": "Lijstweergave", + "passwordStrengthTitle": "Wachtwoordsterkte", + "deleteDialogTitle": "Wachtwoord verwijderen", + "deleteDialogDescription": "Weet je zeker dat je dit wachtwoord wilt verwijderen? Dit kan niet ongedaan worden gemaakt.", + "cancel": "Annuleren", + "delete": "Verwijderen", + "edit": "Bewerken" + }, + "toasts": { + "copiedPassword": "Wachtwoord gekopieerd naar klembord", + "copiedUsername": "Gebruikersnaam gekopieerd naar klembord", + "passwordDeleted": "Wachtwoord verwijderd", + "deleteFailed": "Wachtwoord verwijderen mislukt", + "vaultLocked": "Kluis vergrendeld", + "importedCount": "{count, plural, one {# wachtwoord} other {# wachtwoorden}} succesvol geïmporteerd" + }, + "form": { + "addTitle": "Nieuw wachtwoord toevoegen", + "editTitle": "Wachtwoord bewerken", + "serviceName": "Servicenaam", + "usernameEmail": "Gebruikersnaam / e-mail", + "password": "Wachtwoord", + "urlOptional": "Website-URL (optioneel)", + "notesOptional": "Notities (optioneel)", + "tags": "Tags", + "placeholderService": "bijv. Netflix", + "placeholderUsername": "gebruiker@voorbeeld.nl", + "placeholderPassword": "Voer wachtwoord in of genereer er een", + "placeholderUrl": "https://...", + "placeholderNotes": "Extra details...", + "placeholderTags": "Voeg tags toe (druk op Enter of komma)", + "strength": "Sterkte", + "strengthWeak": "Zwak", + "strengthMedium": "Gemiddeld", + "strengthStrong": "Sterk", + "advancedGenerator": "Geavanceerde generator", + "cancel": "Annuleren", + "saving": "Opslaan...", + "savePassword": "Wachtwoord opslaan", + "updating": "Bijwerken...", + "updatePassword": "Wachtwoord bijwerken", + "addPasswordSrOnly": "Wachtwoord toevoegen", + "triggerAdd": "Wachtwoord toevoegen", + "toastSaved": "Wachtwoord succesvol opgeslagen", + "toastSaveFailed": "Wachtwoord opslaan mislukt", + "toastUpdated": "Wachtwoord succesvol bijgewerkt", + "toastUpdateFailed": "Wachtwoord bijwerken mislukt" + }, + "security": { + "vaultHealth": "Kluisgezondheid", + "statusLabel": "Status", + "totalPasswords": "Totaal wachtwoorden", + "activeEntries": "Actieve items in je kluis", + "protected": "Beschermd", + "weakPasswords": "Zwakke wachtwoorden", + "needingImprovement": "Wachtwoorden die verbetering nodig hebben", + "allGood": "Alles goed", + "reusedPasswords": "Hergebruikte wachtwoorden", + "usedMultipleTimes": "Wachtwoorden die meerdere keren worden gebruikt", + "unique": "Uniek", + "total": "Totaal", + "weak": "Zwak", + "reused": "Hergebruikt", + "totalCountLabel": "{count, plural, one {# wachtwoord} other {# wachtwoorden}}", + "weakCountLabel": "{count} zwak", + "allSecure": "Alles beveiligd", + "scoreExcellent": "Uitstekend", + "scoreGood": "Goed", + "scoreFair": "Redelijk", + "scoreCritical": "Kritiek" + }, + "importExport": { + "title": "Kluis importeren / exporteren", + "description": "Beheer je wachtwoordgegevens. Je kunt je kluis exporteren naar JSON of importeren vanuit een JSON-bestand.", + "tabExport": "Exporteren", + "tabImport": "Importeren", + "warningTitle": "Waarschuwing", + "warningBody": "Geëxporteerde bestanden bevatten je wachtwoorden in platte tekst. Bewaar dit bestand veilig en verwijder het na gebruik.", + "exportBlurb": "Download je hele kluis als een JSON-bestand. Dit omvat alle services, gebruikersnamen, wachtwoorden en notities.", + "downloadJson": "JSON-export downloaden", + "pasteLabel": "JSON-gegevens plakken", + "pastePlaceholder": "[{\"service\": \"Voorbeeld\", \"username\": \"gebruiker\", \"password\": \"123\", ...}]", + "pasteHint": "Plak hier de inhoud van een eerder geëxporteerd JSON-bestand.", + "importing": "Importeren...", + "importPasswords": "Wachtwoorden importeren", + "trigger": "Importeren / exporteren", + "toastExported": "Kluis succesvol geëxporteerd", + "toastExportFailed": "Kluis exporteren mislukt", + "toastImportFailed": "Importeren mislukt: ongeldig JSON-formaat" + }, + "card": { + "copyUsername": "Gebruikersnaam kopiëren", + "openUrl": "URL openen", + "strength": "Sterkte" + }, + "swipeable": { + "copy": "Kopiëren", + "delete": "Verwijderen", + "copyPassword": "Wachtwoord kopiëren", + "copyUsername": "Gebruikersnaam kopiëren", + "edit": "Bewerken", + "deleteAction": "Verwijderen" + }, + "floatingMenu": { + "lockVault": "Kluis vergrendelen", + "importExport": "Importeren / exporteren", + "addPassword": "Wachtwoord toevoegen" + }, + "generator": { + "tabPassword": "Wachtwoord", + "tabPassphrase": "Wachtwoordzin", + "strength": "Sterkte", + "lengthLabel": "Lengte: {length} tekens", + "lengthBadge": "{length} tekens", + "characterSets": "Tekensets", + "uppercase": "Hoofdletters", + "lowercase": "Kleine letters", + "numbers": "Cijfers", + "symbols": "Symbolen", + "wordCountLabel": "Aantal woorden: {count}", + "wordCountBadge": "{count, plural, one {# woord} other {# woorden}}", + "separator": "Scheidingsteken", + "capitalization": "Hoofdlettergebruik", + "includeNumber": "Cijfer toevoegen" + } + }, + "EnvironmentManager": { + "subtitle": "Versleutelde omgevingsvariabelen per project en omgeving.", + "page": { + "loading": "Laden…", + "title": "Omgevingsbeheer", + "loadFailed": "Omgevingssets laden mislukt" + }, + "list": { + "title": "Omgevingen", + "loading": "Uw sets laden…", + "emptyTitle": "Nog geen omgevingssets", + "emptyHint": "Maak een set per project en omgeving (bijv. productie-API-sleutels). Alles wordt in uw browser versleuteld vóór upload.", + "searchPlaceholder": "Zoek project, omgeving, tags, notities…", + "tagFilterPlaceholder": "Filter op tag", + "variableCount": "{count, plural, one {# variabele} other {# variabelen}}", + "updatedAgo": "Bijgewerkt {time}", + "copyDotEnv": ".env kopiëren", + "edit": "Bewerken", + "delete": "Verwijderen", + "cancel": "Annuleren", + "confirmDelete": "Verwijderen", + "deleteDialogTitle": "Omgevingsset verwijderen?", + "deleteDialogDescription": "Hiermee wordt de versleutelde blob uit uw account verwijderd. Kan niet ongedaan worden gemaakt.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env naar klembord gekopieerd", + "deleted": "Omgevingsset verwijderd", + "deleteFailed": "Verwijderen mislukt" + }, + "form": { + "addTitle": "Nieuwe omgevingsset", + "editTitle": "Omgevingsset bewerken", + "triggerAdd": "Omgevingsset toevoegen", + "addSrOnly": "Omgevingsset toevoegen", + "project": "Project", + "environment": "Omgeving", + "placeholderProject": "bijv. billing-api", + "placeholderEnvironment": "bijv. production", + "pasteEnvLabel": ".env plakken", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Geplakte regels samenvoegen", + "variables": "Variabelen", + "addRow": "Rij toevoegen", + "keyPlaceholder": "SLEUTEL", + "valuePlaceholder": "waarde", + "notesOptional": "Notities (optioneel)", + "placeholderNotes": "Rotatiebeleid, eigenaar, links…", + "tags": "Tags", + "placeholderTags": "Tags toevoegen (Enter of komma)", + "cancel": "Annuleren", + "save": "Opslaan", + "saving": "Opslaan…", + "update": "Bijwerken", + "updating": "Bijwerken…", + "toastSaved": "Omgevingsset opgeslagen", + "toastSaveFailed": "Opslaan mislukt", + "toastUpdated": "Omgevingsset bijgewerkt", + "toastUpdateFailed": "Bijwerken mislukt", + "toastPasteEmpty": "Geen SLEUTEL=waarde-regels gevonden", + "toastPasteApplied": "{count} variabelen samengevoegd uit plakken", + "toastProjectEnvRequired": "Project en omgeving zijn verplicht", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Laden..." + }, + "manager": { + "foldersHeading": "Mappen", + "tagsHeading": "Tags", + "tagsMore": "+{count} meer", + "tagsShowLess": "Show less", + "searchPlaceholder": "Zoek je bladwijzers...", + "import": "Importeren", + "export": "Exporteren", + "exportAsHtml": "Exporteren als HTML", + "exportAsJson": "Exporteren als JSON", + "addBookmark": "Bladwijzer toevoegen", + "matchingQuery": "overeenkomend met \"{query}\"", + "bookmarkCount": "{count, plural, one {# bladwijzer} other {# bladwijzers}}", + "allBookmarks": "Alle bladwijzers", + "uncategorized": "Niet gecategoriseerd", + "unknownFolder": "Onbekend" + }, + "card": { + "urlCopied": "URL gekopieerd naar klembord", + "bookmarkDeleted": "Bladwijzer verwijderd", + "edit": "Bewerken", + "delete": "Verwijderen", + "copyUrl": "URL kopiëren", + "deleteConfirmTitle": "Bladwijzer verwijderen?", + "deleteConfirmDescription": "Dit verwijdert \"{title}\" permanent.", + "cancel": "Annuleren" + }, + "grid": { + "emptyTitle": "Nog geen bladwijzers", + "emptyDescription": "Voeg je eerste bladwijzer toe of importeer ze vanuit je browser om te beginnen." + }, + "folderTree": { + "allBookmarks": "Alle bladwijzers", + "addSubfolder": "Submap toevoegen", + "rename": "Hernoemen", + "deleteFolderTitle": "Map \"{name}\" verwijderen?", + "deleteFolderDescription": "Dit verwijdert de map en alle submappen. Bladwijzers in deze mappen worden verplaatst naar Niet gecategoriseerd." + }, + "import": { + "title": "Bladwijzers importeren", + "description": "Importeer bladwijzers vanuit het exportbestand van je browser.", + "parseError": "Bladwijzerbestand kan niet worden verwerkt. Controleer of het een geldig bladwijzer-exportbestand is.", + "successToast": "{bookmarks} bladwijzers en {folders} mappen geïmporteerd", + "errorToast": "Bladwijzers importeren mislukt", + "chromeInstruction": "Menu → Bladwijzers → Bladwijzerbeheer → ⋮ → Bladwijzers exporteren", + "firefoxInstruction": "Menu → Bladwijzers → Bladwijzers beheren → Importeren en back-up → Exporteren", + "safariInstruction": "Archief → Exporteer bladwijzers...", + "edgeInstruction": "Menu → Favorieten → ⋯ → Favorieten exporteren", + "dropZoneTitle": "Sleep je bladwijzerbestand hierheen", + "dropZoneHint": "of klik om te bladeren (HTML of JSON)", + "selectedFileHint": "Klik om een ander bestand te selecteren", + "errorTitle": "Fout", + "previewTitle": "Importvoorbeeld", + "previewBookmarks": "{count, plural, one {# bladwijzer} other {# bladwijzers}}", + "previewFolders": "{count, plural, one {# map} other {# mappen}}", + "importModeLabel": "Importmodus", + "modeMerge": "Samenvoegen met bestaande bladwijzers", + "modeReplace": "Alle bestaande bladwijzers vervangen", + "cancel": "Annuleren", + "importButton": "Importeren", + "importButtonWithCount": "{count, plural, one {# bladwijzer} other {# bladwijzers}} importeren" + }, + "addBookmark": { + "titleAdd": "Bladwijzer toevoegen", + "titleEdit": "Bladwijzer bewerken", + "urlLabel": "URL", + "urlPlaceholder": "https://voorbeeld.nl", + "urlInvalid": "Voer een geldige URL in", + "titleLabel": "Titel", + "titlePlaceholder": "Bladwijzertitel", + "titleRequired": "Voer een titel in", + "descriptionLabel": "Beschrijving (optioneel)", + "descriptionPlaceholder": "Voeg een opmerking toe over deze bladwijzer", + "folderLabel": "Map", + "folderPlaceholder": "Selecteer map", + "noFolder": "Geen map", + "tagsLabel": "Tags", + "tagsPlaceholder": "Voeg tags toe (druk op Enter)", + "toastUpdated": "Bladwijzer bijgewerkt", + "toastAdded": "Bladwijzer toegevoegd", + "cancel": "Annuleren", + "submitAdd": "Bladwijzer toevoegen", + "submitUpdate": "Bladwijzer bijwerken" + }, + "addFolder": { + "title": "Map aanmaken", + "nameLabel": "Mapnaam", + "namePlaceholder": "Mijn map", + "nameRequired": "Voer een mapnaam in", + "parentLabel": "Bovenliggende map", + "parentPlaceholder": "Selecteer bovenliggende map", + "rootParent": "Hoofdmap (geen bovenliggende)", + "toastCreated": "Map aangemaakt", + "cancel": "Annuleren", + "submit": "Map aanmaken" + }, + "editFolder": { + "title": "Map hernoemen", + "nameLabel": "Mapnaam", + "namePlaceholder": "Mapnaam", + "nameRequired": "Voer een mapnaam in", + "toastRenamed": "Map hernoemd", + "cancel": "Annuleren", + "save": "Opslaan" + } + }, + "EmailValidator": { + "header": { + "title": "E-mailvalidator", + "description": "Verifieer en valideer e-mailadressen op syntaxis, domein en bezorgbaarheid." + }, + "tabs": { + "single": "Enkele verificatie", + "bulk": "Bulk uploaden" + }, + "single": { + "cardTitle": "Enkele e-mailverificatie", + "cardDescription": "Voer een e-mailadres in om de bezorgbaarheid en reputatie te controleren.", + "placeholder": "Voer e-mailadres in...", + "clearAria": "E-mail wissen", + "validate": "Valideren", + "validating": "Valideren...", + "formatError": "Voer een geldig e-mailadres in" + }, + "toasts": { + "errorTitle": "Fout", + "enterEmail": "Voer een e-mailadres in", + "invalidFormatTitle": "Ongeldig e-mailformaat", + "invalidFormatDescription": "Voer een geldig e-mailadres in", + "validateFailedDescription": "Er is een fout opgetreden bij het valideren van het e-mailadres", + "noEmailsTitle": "Geen e-mailadressen gevonden", + "noEmailsDescription": "Zorg ervoor dat je Excel een 'email'-kolom heeft.", + "fileTooLargeTitle": "Bestand te groot", + "fileTooLargeDescription": "{total} e-mailadressen gevonden. Alleen de eerste 4000 worden gevalideerd.", + "successTitle": "Succes", + "successValidated": "{count} e-mailadressen succesvol gevalideerd.", + "batchFailedTitle": "Batchvalidatie mislukt", + "batchFailedDescription": "Er is een fout opgetreden bij het verwerken van de bulkvalidatie.", + "noExportTitle": "Geen gegevens om te exporteren", + "noExportValidOnly": "Geen geldige e-mailadressen gevonden om te exporteren.", + "noExportAll": "Geen resultaten om te exporteren.", + "exportSuccessTitle": "Export geslaagd", + "exportSuccessExcelAll": "{count} e-mailadressen geëxporteerd naar Excel.", + "exportSuccessExcelValid": "{count} geldige e-mailadressen geëxporteerd naar Excel.", + "exportSuccessCsvAll": "{count} e-mailadressen geëxporteerd naar CSV.", + "exportSuccessCsvValid": "{count} geldige e-mailadressen geëxporteerd naar CSV." + }, + "disposableAlert": { + "title": "Wegwerp-e-mail gedetecteerd", + "description": "Dit e-mailadres behoort tot een wegwerp-e-mailprovider. Het kan tijdelijk of onveilig zijn." + }, + "score": { + "reliabilityTitle": "Betrouwbaarheidsscore", + "scoreLabel": "Score" + }, + "details": { + "sectionTitle": "Validatiedetails" + }, + "validation": { + "syntaxLabel": "Syntaxis geldig", + "syntaxTooltip": "Controleert of het e-mailadres het juiste formaat heeft (bijv. gebruiker@domein.nl)", + "domainLabel": "Domein bestaat", + "domainTooltip": "Verifieert dat de domeinnaam in het e-mailadres daadwerkelijk bestaat", + "mxLabel": "MX-records gevonden", + "mxTooltip": "Controleert of het domein mail exchange (MX) records heeft geconfigureerd om e-mails te ontvangen", + "mailboxLabel": "Mailbox bestaat", + "mailboxTooltip": "Verifieert dat de specifieke mailbox/account bestaat op de mailserver", + "disposableLabel": "Wegwerp-e-mail", + "disposableTooltip": "Geeft aan of dit e-mailadres behoort tot een tijdelijke/wegwerp e-mailservice (kan riskant zijn)", + "roleBasedLabel": "Rolgebaseerd e-mailadres", + "roleBasedTooltip": "Identificeert of dit een rolgebaseerd e-mailadres is (bijv. support@, info@) in plaats van een persoonlijk adres" + }, + "copy": { + "emailCopied": "E-mailadres gekopieerd!", + "copyTooltip": "E-mailadres kopiëren" + }, + "bulk": { + "dropHere": "Sleep je bestand hierheen", + "dragHere": "Sleep je bestand hierheen", + "uploadHint": "Upload een .xlsx-, .xls- of .csv-bestand met een 'email'-kolom om tot 4000 e-mailadressen te valideren.", + "filePrefix": "Bestand:", + "selectFile": "Bestand selecteren", + "downloadTemplate": "Sjabloon downloaden" + }, + "bulkLoading": { + "title": "E-mailadressen valideren...", + "batchProgress": "Batch {current} van {total} verwerken..." + }, + "stats": { + "totalProcessed": "Totaal verwerkt", + "valid": "Geldig", + "risky": "Riskant", + "invalid": "Ongeldig" + }, + "results": { + "detailedTitle": "Gedetailleerde resultaten", + "searchPlaceholder": "E-mailadressen zoeken...", + "newVerification": "Nieuwe verificatie", + "exportAll": "Alles exporteren", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Geldige exporteren ({count})" + }, + "showingResults": "{shown} van {total} resultaten weergegeven", + "table": { + "email": "E-mail", + "status": "Status", + "score": "Score", + "checks": "Controles" + }, + "noSearchResults": "Geen resultaten gevonden voor \"{query}\"", + "checksTooltip": { + "syntax": "Syntaxis", + "domain": "Domein", + "mx": "MX", + "mailbox": "Mailbox" + }, + "exportColumns": { + "email": "E-mail", + "status": "Status", + "score": "Score", + "syntax": "Syntaxis", + "domain": "Domein", + "mx": "MX", + "mailbox": "Mailbox", + "disposable": "Wegwerp", + "roleBased": "Rolgebaseerd", + "valid": "Geldig", + "invalid": "Ongeldig", + "yes": "Ja", + "no": "Nee" + }, + "sheets": { + "validEmails": "Geldige e-mails", + "results": "Resultaten", + "template": "Sjabloon" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL-verkenner", + "heroSubtitle": "Maak verbinding met je databases om collecties te beheren, query's uit te voeren en je gegevens te visualiseren.", + "featureMultiTitle": "Meerdere verbindingen", + "featureMultiDesc": "Beheer meerdere databaseverbindingen tegelijkertijd.", + "featureMongoTitle": "MongoDB-ondersteuning", + "featureMongoDesc": "Native ondersteuning voor MongoDB met geavanceerd filteren.", + "featureQueryTitle": "Slimme query", + "featureQueryDesc": "Krachtige querybouwer met syntaxismarkering.", + "connectCta": "Verbinden met MongoDB", + "footerNote": "Ondersteunt momenteel {db}. Meer databases volgen binnenkort.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Selecteer een collectie", + "selectCollectionDesc": "Kies een collectie uit de zijbalk om documenten te bekijken en je gegevens te verkennen.", + "dialogAddConnection": "Verbinding toevoegen", + "toastConnectionAdded": "Verbinding toegevoegd", + "toastFetchFailed": "Documenten ophalen mislukt voor {collection}", + "toastDocDeleted": "Document verwijderd", + "deleteDocTitle": "Weet je het zeker?", + "deleteDocDescription": "Deze actie kan niet ongedaan worden gemaakt. Dit verwijdert het document permanent uit je collectie.", + "closeAllTitle": "Alle tabbladen sluiten?", + "closeAllDescription": "Weet je zeker dat je alle tabbladen wilt sluiten? Niet-opgeslagen wijzigingen in queryfilters gaan verloren.", + "closeAllConfirm": "Alles sluiten", + "cancel": "Annuleren", + "delete": "Verwijderen" + }, + "connection": { + "titleEdit": "Verbinding bewerken", + "titleConnect": "Verbinden met MongoDB", + "subtitleEdit": "Werk je verbindingsgegevens hieronder bij.", + "subtitleConnect": "Voer je MongoDB-verbindingsreeks in om je gegevens te verkennen.", + "labelName": "Naam", + "placeholderName": "bijv. Productie-DB", + "labelConnectionString": "Verbindingsreeks", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Formaat: mongodb://gebruikersnaam:wachtwoord@host:poort/database", + "testConnection": "Verbinding testen", + "connecting": "Verbinden...", + "updateConnect": "Bijwerken & verbinden", + "connect": "Verbinden", + "cancelEdit": "Bewerking annuleren", + "savedConnections": "Opgeslagen verbindingen", + "emptySaved": "Nog geen opgeslagen verbindingen.", + "lastUsed": "Laatst gebruikt {time}", + "toastTestOk": "Verbinding geslaagd!", + "toastTestFail": "Verbinding mislukt: {message}", + "toastUpdated": "Verbinding bijgewerkt", + "confirmDelete": "Weet je zeker dat je deze opgeslagen verbinding wilt verwijderen?", + "toastDeletedConn": "Verbinding verwijderd", + "toastDeleteFail": "Verbinding verwijderen mislukt", + "copyStringTitle": "Verbindingsreeks kopiëren", + "toastStringCopied": "Verbindingsreeks gekopieerd!", + "editTitle": "Bewerken", + "deleteTitle": "Verwijderen" + }, + "sidebar": { + "explorer": "Verkenner", + "add": "Toevoegen", + "tooltipAdd": "Nieuwe verbinding toevoegen", + "tooltipRefresh": "Verbindingen vernieuwen", + "searchPlaceholder": "Zoeken...", + "loadingConnections": "Verbindingen laden...", + "noConnectionsFound": "Geen verbindingen gevonden", + "toastLoadFail": "Verbindingen laden mislukt", + "toastConnectFail": "Verbinden met {name} mislukt", + "toastCollectionsFail": "Collecties ophalen mislukt voor {name}", + "toastRenamed": "Verbinding hernoemd", + "toastRenameFail": "Verbinding hernoemen mislukt", + "confirmDeleteConnection": "Weet je zeker dat je verbinding \"{name}\" wilt verwijderen?", + "toastDeleted": "Verbinding verwijderd", + "toastDeleteConnFail": "Verbinding verwijderen mislukt", + "confirmDropDb": "Weet je zeker dat je database \"{name}\" wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "toastDbDropped": "Database {name} verwijderd", + "confirmDropCollection": "Weet je zeker dat je collectie \"{name}\" wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "toastCollectionDropped": "Collectie {name} verwijderd", + "toastCollectionRenamed": "Collectie hernoemd naar {name}", + "toastDatabaseRenamed": "Database hernoemd naar {name}", + "connecting": "Verbinden...", + "connectionFailed": "Verbinding mislukt", + "noDatabases": "Geen databases", + "loading": "Laden...", + "noCollections": "Geen collecties", + "renameCollectionTitle": "Collectie hernoemen", + "renameDatabaseTitle": "Database hernoemen", + "placeholderNewCollection": "Nieuwe collectienaam", + "placeholderNewDatabase": "Nieuwe databasenaam", + "rename": "Hernoemen", + "refresh": "Vernieuwen", + "copyDbName": "DB-naam kopiëren", + "toastDbNameCopied": "Databasenaam gekopieerd naar klembord", + "dropDatabase": "Database verwijderen", + "dropCollection": "Collectie verwijderen", + "menuRename": "Hernoemen", + "menuCopyConnectionString": "Verbindingsreeks kopiëren", + "menuDeleteConnection": "Verbinding verwijderen", + "toastSidebarStringCopied": "Verbindingsreeks gekopieerd!", + "cancel": "Annuleren", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} docs", + "table": "Tabel", + "json": "JSON", + "tree": "Boom", + "tooltipTable": "Tabelweergave", + "tooltipJson": "JSON-weergave", + "tooltipTree": "Boomweergave", + "itemsPerPage": "Items per pagina", + "perPage": "{n} / pagina", + "prevPage": "Vorige pagina", + "nextPage": "Volgende pagina", + "expandAll": "Alles uitvouwen", + "collapseAll": "Alles invouwen", + "refreshData": "Gegevens vernieuwen", + "insert": "Invoegen", + "export": "Exporteren", + "actions": "Acties", + "invalidJson": "Ongeldige JSON-inhoud", + "copiedClipboard": "Gekopieerd naar klembord", + "docUpdated": "Document succesvol bijgewerkt", + "docInserted": "Document succesvol ingevoegd", + "invalidJsonShort": "Ongeldige JSON", + "docCopied": "Document gekopieerd naar klembord", + "openFullEditor": "Openen in volledige editor", + "cellArray": "Array({length})", + "cellObject": "Object", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Geen documenten komen overeen met je query", + "emptyFilterHint": "Probeer je filter aan te passen of de query te wissen.", + "clearFilter": "Filter wissen", + "emptyCollectionTitle": "Deze collectie is leeg", + "emptyCollectionHint": "Voeg een document in om te beginnen.", + "insertDocument": "Document invoegen", + "formatJson": "JSON formatteren", + "compactJson": "Compacte JSON", + "copyClipboard": "Kopiëren naar klembord", + "copyJson": "JSON kopiëren", + "duplicate": "Dupliceren", + "editDoc": "Document bewerken", + "deleteDoc": "Document verwijderen", + "documentLabel": "Document {n}", + "editDialogTitle": "Document bewerken", + "insertDialogTitle": "Document invoegen", + "viewValueTitle": "Waarde bekijken", + "saveChanges": "Wijzigingen opslaan", + "close": "Sluiten", + "cancel": "Annuleren", + "insertButton": "Invoegen", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Actief filter toegepast", + "filterActiveTooltip": "Filter actief", + "closeAll": "Alles sluiten" + }, + "query": { + "migrateHistory": "Querygeschiedenis gemigreerd naar cloud", + "saveHistoryFail": "Querygeschiedenis opslaan mislukt", + "deleteQueryFail": "Query verwijderen mislukt", + "invalidJsonQuery": "Ongeldige JSON-query", + "placeholder": "bijv. { \"status\": \"active\" }", + "clearQuery": "Query wissen", + "openAdvanced": "Geavanceerde editor openen", + "visualBuilder": "Visuele querybouwer", + "filterRules": "Filterregels", + "addRule": "Regel toevoegen", + "noFilters": "Geen filters toegepast. Voeg een regel toe om documenten te filteren.", + "where": "WAAR", + "and": "EN", + "fieldPlaceholder": "Veld", + "opPlaceholder": "Op", + "typePlaceholder": "Type", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Niet in", + "opExists": "Bestaat", + "typeAuto": "Auto", + "typeString": "Tekst", + "typeNumber": "Nummer", + "typeBoolean": "Boolean", + "typeDate": "Datum", + "typeObjectId": "ObjectId", + "true": "Waar", + "false": "Onwaar", + "valuePlaceholder": "Waarde", + "valueListPlaceholder": "waarde1, waarde2", + "clearFilters": "Filters wissen", + "applyFilters": "Filters toepassen", + "queryHistory": "Querygeschiedenis", + "noSavedQueries": "Nog geen opgeslagen query's", + "advancedEditorTitle": "Geavanceerde query-editor", + "templatesPlaceholder": "Sjablonen", + "templateEmptyAgg": "Lege aggregatie", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Join)", + "templateGroup": "$group (Tel/Som)", + "format": "Formatteren", + "formattedJson": "Geformatteerde JSON", + "runQuery": "Query uitvoeren" + }, + "export": { + "selectFieldsError": "Selecteer minimaal één veld om te exporteren", + "success": "Succesvol geëxporteerd naar {filename}", + "fail": "Exporteren van gegevens mislukt", + "title": "Gegevens exporteren", + "formatLabel": "Exportformaat", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Velden selecteren", + "selectAll": "Alles selecteren", + "fieldsSelected": "{count, plural, one {# veld geselecteerd} other {# velden geselecteerd}}", + "cancel": "Annuleren", + "export": "Exporteren", + "sheetName": "Gegevens" + }, + "jsonTree": { + "typeLabel": "Type: {type}", + "itemSummary": "{count, plural, one {# item} other {# items}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Nieuw verzoek", + "importedRequest": "Geïmporteerd verzoek", + "untitledRequest": "Naamloos verzoek" + }, + "toasts": { + "curlImported": "cURL succesvol geïmporteerd", + "curlParseFailed": "cURL-opdracht kan niet worden verwerkt", + "invalidJsonBody": "Ongeldige JSON-body", + "requestFailed": "Verzoek mislukt: {message}", + "curlPasted": "cURL geplakt en succesvol verwerkt", + "responseCopied": "Antwoord gekopieerd naar klembord", + "codeCopied": "Code gekopieerd naar klembord", + "copyFailed": "Kopiëren naar klembord mislukt" + }, + "layout": { + "collections": "Collecties", + "closeSidebar": "Zijbalk sluiten", + "openSidebar": "Zijbalk openen", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Methode", + "urlPlaceholder": "https://api.voorbeeld.nl/v1/...", + "sending": "Verzenden...", + "send": "Verzenden", + "invalidJsonBodyHelp": "Kan niet verzenden: de JSON-body is ongeldig" + }, + "requestTabs": { + "params": "Parameters", + "headers": "Headers", + "body": "Body", + "auth": "Auth", + "bodyType": "Bodytype", + "selectType": "Selecteer type", + "bodyNone": "Geen", + "bodyJson": "JSON", + "bodyText": "Tekst", + "noBodyHint": "Geen body-inhoud voor dit verzoek", + "switchToJson": "Overschakelen naar JSON-body", + "authType": "Auth-type", + "noAuth": "Geen auth", + "bearerToken": "Bearer-token", + "basicAuth": "Basisauthenticatie", + "apiKey": "API-sleutel", + "labelToken": "Token", + "placeholderBearerToken": "Bearer-token", + "labelUsername": "Gebruikersnaam", + "placeholderUsername": "Gebruikersnaam", + "labelPassword": "Wachtwoord", + "placeholderPassword": "Wachtwoord", + "labelKey": "Sleutel", + "placeholderApiKeyName": "Sleutelnaam (bijv. X-Api-Key)", + "labelValue": "Waarde", + "placeholderValue": "Waarde", + "addTo": "Toevoegen aan", + "header": "Header", + "queryParams": "Queryparameters", + "noAuthHint": "Dit verzoek gebruikt geen authenticatie." + }, + "responsePanel": { + "emptyTitle": "Klaar om te verkennen?", + "emptyHint": "Voer een URL in en klik op Verzenden om de antwoordgegevens hier te zien.", + "responsePreviewAlt": "Antwoordvoorbeeld", + "previewUnavailable": "Voorbeeld niet beschikbaar voor dit inhoudstype", + "status": "Status", + "time": "Tijd", + "size": "Grootte", + "copyBody": "Body kopiëren", + "downloadResponse": "Antwoord downloaden", + "bodyTab": "Body", + "previewTab": "Voorbeeld", + "headersTab": "Headers", + "binaryRawView": "Binaire gegevens kunnen niet worden weergegeven in ruwe weergave.", + "errorStatusLabel": "Fout", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Nieuw tabblad" + }, + "collectionsSidebar": { + "title": "API-bronnen", + "newCollection": "Nieuwe collectie", + "tabCollections": "Collecties", + "tabHistory": "Geschiedenis", + "noCollectionsTitle": "Geen collecties", + "noCollectionsHint": "Maak een collectie aan om je API-verzoeken te organiseren.", + "createCollection": "Collectie aanmaken", + "newFolder": "Nieuwe map", + "rename": "Hernoemen", + "delete": "Verwijderen", + "emptyCollection": "Lege collectie", + "recentRequests": "Recente verzoeken", + "clearAll": "Alles wissen", + "noHistoryTitle": "Nog geen geschiedenis", + "noHistoryHint": "Verzend een verzoek om het hier te zien.", + "dialogNewFolderTitle": "Nieuwe map", + "dialogNewFolderDescription": "Maak een nieuwe map aan om je verzoeken te organiseren.", + "labelFolderName": "Mapnaam", + "placeholderFolderName": "Mijn map", + "createFolder": "Map aanmaken", + "dialogNewCollectionTitle": "Nieuwe collectie", + "dialogNewCollectionDescription": "Maak een nieuwe hoofdcollectie aan.", + "labelCollectionName": "Collectienaam", + "placeholderCollectionName": "Mijn collectie", + "dialogRenameCollectionTitle": "Collectie hernoemen", + "dialogRenameCollectionDescription": "Voer een nieuwe naam in voor de collectie.", + "placeholderRenameCollection": "Collectienaam", + "renameAction": "Hernoemen", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Nieuwe map", + "delete": "Verwijderen", + "emptyFolder": "Lege map" + }, + "environmentManager": { + "noEnvironment": "Geen omgeving", + "manageDialogTitle": "Omgevingen beheren", + "placeholderNewEnv": "Nieuwe omgeving", + "variablesHeading": "{name} variabelen", + "addVariable": "Variabele toevoegen", + "columnVariable": "Variabele", + "columnValue": "Waarde", + "placeholderKey": "Sleutel", + "placeholderValue": "Waarde", + "selectEnvToEdit": "Selecteer een omgeving om te bewerken" + }, + "codeGenerator": { + "triggerTitle": "Code genereren", + "dialogTitle": "Code genereren", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Kopiëren", + "errorGenerating": "Fout bij het genereren van code" + }, + "keyValueEditor": { + "columnKey": "Sleutel", + "columnValue": "Waarde", + "placeholderKey": "Sleutel", + "placeholderValue": "Waarde", + "addItem": "Item toevoegen" + }, + "importCurl": { + "trigger": "cURL importeren", + "dialogTitle": "cURL importeren", + "dialogDescription": "Plak je cURL-opdracht hieronder om het als een nieuw verzoek te importeren.", + "placeholder": "curl -X POST https://api.voorbeeld.nl/data -d '...'", + "import": "Importeren" + }, + "saveRequest": { + "trigger": "Opslaan", + "dialogTitle": "Verzoek opslaan", + "dialogDescription": "Sla dit verzoek op in een collectiemap.", + "labelName": "Verzoeknaam", + "placeholderName": "Mijn verzoek", + "labelFolder": "Map", + "placeholderFolder": "Selecteer een map", + "save": "Opslaan" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Bewerkingsmodus", + "viewOnlyMode": "Alleen-lezenmode", + "insertComponent": "Component invoegen", + "addUnorderedList": "Ongeordende lijst toevoegen", + "addOrderedList": "Geordende lijst toevoegen", + "addTable": "Tabel toevoegen" + }, + "addBlock": "Blok toevoegen", + "media": { + "popoverTitle": "Media toevoegen", + "singleImage": "Enkele afbeelding", + "singleImageDesc": "Upload één afbeelding", + "multipleImages": "Meerdere afbeeldingen", + "multipleImagesDesc": "Upload afbeeldingsraster", + "video": "Video", + "videoDesc": "Upload videobestand", + "uploadingAlt": "Uploaden...", + "uploadFailedAlt": "Upload mislukt" + }, + "export": { + "fabAria": "Code exporteren", + "title": "Code exporteren", + "description": "Kopieer de HTML- of JSON-uitvoer van je editorinhoud", + "preview": "Voorbeeld", + "html": "HTML", + "json": "JSON", + "previewOptions": "Voorbeeldopties", + "enhanceSpaces": "Spaties verbeteren", + "livePreview": "Live voorbeeld van gerenderde HTML", + "htmlTailwind": "HTML met Tailwind CSS-klassen", + "editorJson": "Editorstatus als JSON", + "copyHtml": "HTML kopiëren", + "copyJson": "JSON kopiëren", + "copied": "Gekopieerd!" + }, + "template": { + "fabTitle": "Sjabloon wisselen", + "title": "Kies een sjabloon", + "description": "Selecteer een sjabloon om snel aan de slag te gaan met kant-en-klare lay-outs en inhoud", + "catAll": "Alle sjablonen", + "catProductivity": "Productiviteit", + "catCreative": "Creatief", + "catBusiness": "Zakelijk", + "catPersonal": "Persoonlijk", + "noTemplates": "Geen sjablonen gevonden in deze categorie", + "confirmTitle": "Bestaande inhoud vervangen?", + "confirmDescription": "Je hebt bestaande inhoud in de editor. Het toepassen van dit sjabloon vervangt alle huidige inhoud.", + "cannotUndo": "Deze actie kan niet ongedaan worden gemaakt.", + "cancel": "Annuleren", + "replaceContent": "Inhoud vervangen" + }, + "command": { + "searchPlaceholder": "Opdrachten zoeken...", + "noResults": "Geen opdrachten gevonden.", + "turnInto": "Omzetten naar", + "h1": { + "label": "Kop 1", + "description": "Grote sectiekop", + "keywords": "h1,kop,titel,groot" + }, + "h2": { + "label": "Kop 2", + "description": "Middelgrote sectiekop", + "keywords": "h2,kop,ondertitel" + }, + "h3": { + "label": "Kop 3", + "description": "Kleine sectiekop", + "keywords": "h3,kop,subkop" + }, + "h4": { + "label": "Kop 4", + "description": "Kleinere sectiekop", + "keywords": "h4,kop" + }, + "h5": { + "label": "Kop 5", + "description": "Nog kleinere kop", + "keywords": "h5,kop" + }, + "h6": { + "label": "Kop 6", + "description": "Kleinste kop", + "keywords": "h6,kop" + }, + "p": { + "label": "Paragraaf", + "description": "Normale tekstalinea", + "keywords": "p,paragraaf,tekst,normaal" + }, + "code": { + "label": "Codeblok", + "description": "Codefragment", + "keywords": "code,codeblok,fragment,pre" + }, + "blockquote": { + "label": "Citaat", + "description": "Blokcitaat", + "keywords": "citaat,blokcitaat,verwijzing" + }, + "li": { + "label": "Opsommingslijst", + "description": "Lijstitem met opsommingsteken", + "keywords": "lijst,opsomming,ongeordend,ul,li" + }, + "ol": { + "label": "Genummerde lijst", + "description": "Genummerd lijstitem", + "keywords": "lijst,genummerd,geordend,ol,li" + }, + "img": { + "label": "Afbeelding", + "description": "Upload of voeg een afbeelding in", + "keywords": "afbeelding,img,foto,upload" + }, + "video": { + "label": "Video", + "description": "Upload of voeg een video in", + "keywords": "video,vid,film,mp4,upload" + }, + "table": { + "label": "Tabel", + "description": "Maak een tabel", + "keywords": "tabel,raster,rijen,kolommen,cellen" + } + }, + "toasts": { + "tableImported": "Tabel geïmporteerd", + "tableImportedDesc": "Markdowntabel is succesvol geïmporteerd", + "mediaPasted": "Media geplakt", + "pasteMixed": "{imageCount} afbeelding(en) en {videoCount} video('s) succesvol geplakt.", + "pasteImages": "{count} afbeelding(en) succesvol geplakt.", + "pasteVideos": "{count} video('s) succesvol geplakt.", + "pasteFailed": "Plakken mislukt", + "unexpectedError": "Er is een onverwachte fout opgetreden", + "uploadFailed": "Upload mislukt" + }, + "blockBackground": { + "menuItem": "Achtergrondkleur wijzigen", + "dialogTitle": "Blokachtergrondkleur", + "dialogDescription": "Kies een achtergrondkleur voor dit blok", + "tabPreset": "Vooraf ingestelde kleuren", + "tabCustom": "Aangepaste kleur", + "sectionHeading": "Achtergrondkleuren", + "apply": "Toepassen", + "colors": { + "none": "Geen", + "red": "Rood", + "orange": "Oranje", + "yellow": "Geel", + "green": "Groen", + "blue": "Blauw", + "indigo": "Indigo", + "purple": "Paars", + "pink": "Roze", + "teal": "Groenblauw", + "cyan": "Cyaan", + "gray": "Grijs" + } + }, + "tableDialog": { + "title": "Tabel aanmaken", + "description": "Maak een nieuwe tabel of importeer vanuit markdown", + "markdownCheckbox": "Ik heb een markdowntabel", + "pasteMarkdownLabel": "Markdowntabel plakken", + "markdownPlaceholder": "| Kop 1 | Kop 2 | Kop 3 |\n|----------|----------|----------|\n| Cel 1 | Cel 2 | Cel 3 |\n| Cel 4 | Cel 5 | Cel 6 |", + "markdownHint": "Plak je markdowntabel hierboven. Moet kop- en scheidingsrijen bevatten.", + "rows": "Rijen", + "columns": "Kolommen", + "maxDimensions": "Maximum: 20 rijen × 10 kolommen", + "cancel": "Annuleren", + "importTable": "Tabel importeren", + "createTable": "Tabel aanmaken" + }, + "tableParse": { + "errors": { + "minRows": "Tabel moet minimaal een koprij en scheidingsrij bevatten", + "pipeFormat": "Tabelrijen moeten beginnen en eindigen met |", + "headerMinCol": "Koprij moet minimaal één kolom bevatten", + "separatorRow": "Tweede rij moet een scheidingsrij zijn (bijv. |---|---|)", + "rowColumnMismatch": "Rij {row} heeft {got} kolommen, verwacht {expected}", + "parseFailed": "Markdowntabel kan niet worden verwerkt" + } + }, + "link": { + "editLink": "Link bewerken", + "addLink": "Link toevoegen", + "selectedText": "Geselecteerde tekst: \"{text}\"", + "linkUrl": "Link-URL", + "urlPlaceholder": "https://voorbeeld.nl", + "update": "Bijwerken", + "addLinkButton": "Link toevoegen", + "toastApplied": "Link toegepast", + "toastLinkedTo": "Gelinkt naar: {url}", + "toastRemoved": "Link verwijderd", + "toastRemovedDesc": "Link is verwijderd uit selectie" + }, + "customClass": { + "devMode": "Ontwikkelaarsmodus", + "toggleDevModeAria": "Ontwikkelaarsmodus wisselen", + "searchPlaceholderDev": "Klassen zoeken... (bijv. 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Stijlen zoeken... (bijv. 'rood', 'vet', 'schaduw')", + "appliesTitle": "Past toe: {className}", + "noMatches": "Geen klassen gevonden voor \"{query}\"", + "toastClassReplaced": "Klasse vervangen", + "toastClassReplacedDesc": "\"{replaced}\" vervangen door \"{className}\"", + "toastCustomClassApplied": "Aangepaste klasse toegepast", + "toastAppliedClass": "Klasse toegepast: {className}" + }, + "insertComponents": { + "title": "Component invoegen", + "description": "Kies een component om in je document in te voegen", + "noComponents": "Geen componenten beschikbaar", + "categories": { + "media": "Media", + "layout": "Lay-out", + "interactive": "Interactief", + "decoration": "Decoratie" + }, + "freeImage": { + "name": "Vrij beweegbare afbeelding", + "description": "Voeg een afbeelding toe die overal geplaatst en vrij vergroot of verkleind kan worden" + } + } + }, + "JsonFormatter": { + "title": "JSON-formatter", + "description": "Formatteer, valideer en bewerk JSON in tekst- en boomweergave.", + "documentNameText": "Tekstdocument {n}", + "documentNameTree": "Boomdocument {n}", + "newDocument": "Nieuw", + "copy": "Kopiëren", + "save": "Opslaan", + "load": "Laden", + "saving": "Opslaan...", + "leftDocNameLabel": "Naam linkerdocument", + "rightDocNameLabel": "Naam rechterdocument", + "toastCopiedText": "Tekstpaneel gekopieerd", + "toastCopiedTree": "Boompaneel gekopieerd", + "toastCopyFailed": "Kan JSON niet kopiëren", + "toastLoginRequired": "Log in om documenten op te slaan", + "toastSaved": "Document opgeslagen", + "toastSaveFailed": "Document opslaan mislukt", + "format": "Formatteren", + "compact": "Compact maken", + "sortKeys": "Sleutels sorteren", + "repair": "Repareren", + "query": "Query", + "toastInvalidJson": "Ongeldige JSON", + "toastAlreadyValid": "JSON is al geldig", + "toastRepaired": "JSON gerepareerd", + "toastRepairFailed": "Kan JSON niet repareren" + }, + "JwtDecoder": { + "title": "JWT-decoder", + "subtitle": "Bekijk header en payload. Vervaldatum gebruikt de exp-claim. Er wordt niets naar een server gestuurd; de handtekening wordt niet geverifieerd.", + "tokenLabel": "Token", + "clear": "Wissen", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopiëren", + "expiryLabel": "Vervaldatum (exp)", + "signaturePresent": "Handtekening aanwezig (niet geverifieerd)", + "noExp": "Geen exp-claim in de payload.", + "status": { + "expired": "Verlopen", + "valid": "Geldig" + }, + "fields": { + "unix": "Unix", + "local": "Lokaal", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Uitgegeven op (iat)", + "notBefore": "Niet vóór (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Plak een JWT om te decoderen.", + "segments": "Een JWT heeft minimaal een header en payload nodig (twee segmenten).", + "base64": "Header of payload kon niet base64url-gedecodeerd worden." + }, + "relative": { + "future": "over {n} {unit}", + "past": "{n} {unit} geleden" + }, + "units": { + "second": "{count, plural, one {seconde} other {seconden}}", + "minute": "{count, plural, one {minuut} other {minuten}}", + "hour": "{count, plural, one {uur} other {uur}}", + "day": "{count, plural, one {dag} other {dagen}}" + }, + "tabs": { + "decode": "Decoderen", + "sign": "Generator / ondertekenaar" + }, + "signer": { + "algLabel": "Algoritme (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload-JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header-JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Secret (HS*)", + "secretPlaceholder": "Plak je HMAC-secret…", + "privateKeyLabel": "Privésleutel PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Het \"none\"-algoritme maakt een niet-ondertekend token. Dit is onveilig en alleen bedoeld voor testen.", + "sign": "Ondertekenen", + "verify": "Handtekening verifiëren", + "outputLabel": "Token (compacte JWS)", + "outputPlaceholder": "Klik op \"Ondertekenen\"…", + "copyToken": "Token kopiëren", + "copied": "Gekopieerd", + "verifyKeyLabel": "Verificatiesleutel (optioneel)", + "verifyKeyPlaceholder": "Voor RS*: plak de publieke sleutel (SPKI) PEM. Voor HS*: leeg laten om het secret te hergebruiken.", + "verifyKeyHint": "Indien leeg gebruikt verificatie de onderteken-sleutel (handig voor HS*).", + "verifyStatus": { + "valid": "Handtekening geldig", + "invalid": "Handtekening ongeldig" + }, + "securityNote": "Alles draait lokaal in je browser; sleutels en tokens worden niet geüpload." + }, + "signErrors": { + "invalidJson": "Header en payload moeten geldige JSON zijn.", + "unsupportedAlg": "Niet-ondersteund algoritme.", + "missingKey": "Voor dit algoritme is een onderteken-sleutel vereist.", + "cryptoFailed": "Ondertekenen mislukt. Controleer het sleutel-formaat en probeer opnieuw." + }, + "verifyErrors": { + "empty": "Voer een JWT in om te verifiëren.", + "segments": "Een JWT moet precies 3 segmenten hebben om een handtekening te verifiëren.", + "unsupportedAlg": "Niet-ondersteund of ontbrekend alg in de header.", + "missingKey": "Voor dit algoritme is een verificatiesleutel vereist.", + "base64": "Kon de JWT-header of handtekening niet decoderen.", + "cryptoFailed": "Verificatie mislukt. Controleer het sleutel-formaat en probeer opnieuw." + } + }, + "CertificatePemDecoder": { + "title": "Certificaat- / PEM-decoder", + "subtitle": "Plak een X.509-certificaat (-----BEGIN CERTIFICATE-----) of een CSR (-----BEGIN CERTIFICATE REQUEST-----). Subject, uitgever, datums en SAN’s worden lokaal verwerkt; er wordt niets geüpload.", + "pemLabel": "PEM-invoer", + "clear": "Wissen", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Bezig met verwerken…", + "copyHex": "Vingerafdruk kopiëren (hex zonder dubbele punten)", + "errors": { + "empty": "Plak een PEM-certificaat of CSR om te decoderen.", + "noBlock": "Geen PEM-blok voor certificaat of CSR gevonden.", + "parseFailed": "Deze PEM-gegevens konden niet worden verwerkt." + }, + "block": { + "title": "PEM-blok {index}", + "certificate": "X.509-certificaat", + "csr": "Certificaatondertekeningsaanvraag" + }, + "fields": { + "subject": "Subject", + "issuer": "Uitgever", + "notBefore": "Niet geldig vóór", + "notAfter": "Niet geldig na", + "serial": "Serienummer (hex)", + "sha256": "SHA-256-vingerafdruk", + "sans": "Onderwerpalternatieve namen", + "sansNone": "Geen SAN-extensie.", + "signatureAlgorithm": "Handtekeningalgoritme", + "csrIssuerNote": "CSR’s bevatten geen uitgever (die staat op het uitgegeven certificaat).", + "csrValidityNote": "CSR’s bevatten geen geldigheidsdata (die staan op het uitgegeven certificaat)." + }, + "sanType": { + "dns": "DNS", + "dn": "Directorynaam", + "email": "E-mail", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Geregistreerde ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL-formatter", + "subtitle": "Maak SQL netjes voor MySQL, PostgreSQL of SQLite. Draait in je browser; er wordt niets geüpload.", + "dialectLabel": "Dialect", + "keywordsLabel": "Trefwoorden", + "indentLabel": "Inspringing", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "HOOFDLETTERS", + "lower": "kleine letters", + "preserve": "Behouden" + }, + "spaces": "{n} spaties", + "format": "Formatteren", + "clear": "Wissen", + "counter": "{current} / {max} tekens", + "inputPanel": "Invoer", + "formattedPanel": "Geformatteerd", + "copy": "Kopiëren", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klik op \"Formatteren\"…", + "errors": { + "tooLong": "Invoer is beperkt tot {max} tekens.", + "couldNotFormat": "Kon deze SQL niet formatteren." + } + }, + "YamlFormatter": { + "title": "YAML-formatter", + "subtitle": "Formatteer, valideer en converteer YAML in uw browser. Er wordt niets geüpload.", + "outputLabel": "Uitvoer", + "outputModes": { + "yaml": "YAML (geformatteerd)", + "json": "JSON" + }, + "indentLabel": "Inspringing", + "spaces": "{n} spaties", + "format": "Formatteren", + "clear": "Wissen", + "copy": "Kopiëren", + "counter": "{current} / {max} tekens", + "inputPanel": "Invoer", + "outputPanel": "Uitvoer", + "errors": { + "tooLong": "De invoer is beperkt tot {max} tekens.", + "couldNotFormat": "Kan deze YAML niet verwerken." + } + }, + "GraphqlFormatter": { + "title": "GraphQL-formatter", + "subtitle": "Formatteer GraphQL-query’s, mutaties en subscriptions in je browser. Er wordt niets geüpload.", + "tabs": { + "format": "Formatteren", + "build": "Querybouwer" + }, + "outputModeLabel": "Uitvoer", + "outputModes": { + "pretty": "Leesbare afdruk", + "minify": "Minificeren" + }, + "format": "Formatteren", + "clear": "Wissen", + "counter": "{current} / {max} tekens", + "inputPanel": "Invoer", + "outputPanel": "Uitvoer", + "copy": "Kopiëren", + "builderHint": "Stel een skelet samen en zet het in de editor. Variabeledefinities in GraphQL-syntaxis (bijv. $id: ID!) — haakjes optioneel.", + "builder": { + "operation": "Operatie", + "operationName": "Operatienaam", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Variabeledefinities", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Selectie", + "apply": "Toepassen op invoer", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Invoer is beperkt tot {max} tekens.", + "couldNotFormat": "Deze GraphQL kon niet worden geparsed of afgedrukt.", + "emptySelection": "Voeg eerst een selectie toe." + } + }, + "RegexTester": { + "title": "Regex-tester", + "subtitle": "JavaScript-achtige regex live op je tekst. Matches worden gemarkeerd; niets verlaat je browser.", + "patternLabel": "Patroon", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g globaal", + "i": "i hoofd-/kleine letters negeren", + "m": "m multiregel", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(leeg)", + "noFlags": "—", + "matches": "{count, plural, one {match} other {matches}}", + "testTextLabel": "Testtekst", + "counter": "{current} / {max}", + "clear": "Wissen", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Tekst is beperkt tot {max} tekens.", + "invalid": "Ongeldige reguliere expressie.", + "runaway": "Gestopt na {max} matches (mogelijk ontspoord patroon)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Timestamp-converter", + "subtitle": "Unix-seconden (≤10 cijfers) of milliseconden (langere integers), ISO-8601 of strings die je browser kan parsen. Alles draait lokaal.", + "inputLabel": "Invoer", + "placeholder": "1744200000 of 2025-04-09T12:00:00Z", + "now": "Nu", + "copy": "Kopiëren", + "hint": "Plak een waarde of klik op Nu.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relatief", + "human": "Leesbaar" + }, + "fields": { + "seconds": "Seconden", + "milliseconds": "Milliseconden", + "utc": "UTC", + "local": "Lokaal", + "distance": "Afstand" + }, + "errors": { + "empty": "Voer een Unix-timestamp, ISO-string of datumtekst in.", + "invalidNumber": "Ongeldige numerieke timestamp.", + "outOfRange": "Buiten bereik voor JavaScript Date.", + "couldNotParse": "Kon niet parsen als Unix, ISO-8601 of browser-datumstring." + } + }, + "HttpStatusCodes": { + "title": "HTTP-statuscodes", + "subtitle": "Zoek en bekijk HTTP-responsstatuscodes (1xx5xx) met korte beschrijvingen en RFC-verwijzingen. Werkt lokaal.", + "header": { + "title": "HTTP-statuscodes", + "description": "Doorzoekbare referentie van HTTP-responscodes met beschrijvingen en RFC-verwijzingen." + }, + "searchPlaceholder": "Zoeken: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Klasse filteren…", + "classFilterAll": "Alle klassen", + "resultsCount": "{count} van {total}", + "table": { + "code": "Code", + "phrase": "Zin", + "description": "Beschrijving", + "copy": "Kopiëren" + }, + "noResults": "Geen resultaten voor “{query}”.", + "specPrefix": "Spec:", + "copied": "Code gekopieerd" + }, + "CronBuilder": { + "title": "Cron-expressiebouwer", + "subtitle": "Standaard 5-veld cron: minute hour day month weekday. De parser gebruikt intern seconds=0. Optioneel 6-veld (sec min hour dom mon dow) werkt in de expressie-editor.", + "tabs": { + "builder": "Visuele bouwer", + "expression": "Expressie" + }, + "presetsLabel": "Voorinstellingen", + "fieldsLabel": "Velden", + "rawExpressionLabel": "Ruwe expressie", + "expressionHint": "Verlaat het veld om te parsen en de bouwer te synchroniseren. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Huidige expressie", + "copy": "Kopiëren", + "quickPick": "Snelle keuze", + "emptyExpression": "—", + "plainLanguageLabel": "Gewone taal", + "nextRunsLabel": "Volgende runs (lokale tijd)", + "fields": { + "minute": "Minuut", + "hour": "Uur", + "dayOfMonth": "Dag van maand", + "month": "Maand", + "dayOfWeek": "Dag van week" + }, + "presets": { + "everyMinute": "Elke minuut", + "every5Minutes": "Elke 5 minuten", + "every15Minutes": "Elke 15 minuten", + "everyHour": "Elk uur (op :00)", + "every6Hours": "Elke 6 uur", + "dailyMidnight": "Dagelijks om middernacht", + "daily0900": "Dagelijks om 09:00", + "weekdays0900": "Werkdagen om 09:00", + "weeklySunday": "Wekelijks (zondag 00:00)", + "monthlyFirst": "Maandelijks (1e 00:00)", + "yearlyJan1": "Jaarlijks (1 jan 00:00)" + }, + "picks": { + "minute": { + "every": "elke minuut" + }, + "hour": { + "every": "elk uur" + }, + "dom": { + "any": "elke dag", + "last": "laatste dag" + }, + "month": { + "every": "elke maand" + }, + "dow": { + "any": "elke weekdag", + "weekdays": "werkdagen", + "weekend": "weekend" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mrt", + "apr": "Apr", + "may": "Mei", + "jun": "Jun", + "jul": "Jul", + "aug": "Aug", + "sep": "Sep", + "oct": "Okt", + "nov": "Nov", + "dec": "Dec" + }, + "weekdays": { + "sun": "Zo", + "mon": "Ma", + "tue": "Di", + "wed": "Wo", + "thu": "Do", + "fri": "Vr", + "sat": "Za" + }, + "errors": { + "fieldCount": "Verwacht 5 velden (minute hour day month weekday) of 6 inclusief seconden.", + "invalid": "Ongeldige cron-expressie." + } + }, + "Base64": { + "title": "Base64-encoder / -decoder", + "subtitle": "Encodeer tekst naar Base64 of decodeer Base64-strings", + "upload": "Uploaden", + "clear": "Wissen", + "modes": { + "encode": "Encoderen", + "decode": "Decoderen" + }, + "swapTitle": "Invoer en uitvoer omwisselen", + "panels": { + "plainText": "Platte tekst", + "base64Input": "Base64-invoer", + "base64Output": "Base64-uitvoer", + "decodedText": "Gedecodeerde tekst" + }, + "placeholders": { + "encode": "Voer tekst in om naar Base64 te encoderen…", + "decode": "Plak Base64 om te decoderen…" + }, + "dropHere": "Zet bestand hier neer", + "charCount": "{count} tekens", + "download": { + "title": "Uitvoer downloaden", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Kopiëren naar klembord", + "outputPlaceholder": "Uitvoer verschijnt hier…", + "errors": { + "invalidBase64": "Ongeldige Base64-string. Controleer je invoer.", + "encodeFailed": "Kon invoer niet encoderen." + } + }, + "UrlEncode": { + "title": "URL-encoder / -decoder", + "subtitle": "Procentcoderen/decoderen met encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Uploaden", + "clear": "Wissen", + "modes": { + "encode": "Encoderen", + "decode": "Decoderen" + }, + "swapTitle": "Invoer en uitvoer omwisselen", + "panels": { + "plainText": "Platte tekst", + "encodedInput": "Gecodeerde invoer", + "encodedOutput": "Gecodeerde uitvoer", + "decodedText": "Gedecodeerde tekst" + }, + "placeholders": { + "encode": "Voer tekst in om te percent-encoderen…", + "decode": "Plak percent-encoded tekst om te decoderen…" + }, + "dropHere": "Zet bestand hier neer", + "charCount": "{count} tekens", + "download": { + "title": "Uitvoer downloaden", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Kopiëren naar klembord", + "outputPlaceholder": "Uitvoer verschijnt hier…", + "errors": { + "invalidEncoding": "Ongeldige percent-codering. Controleer onvolledige %‑paren of escapes.", + "encodeFailed": "Kon niet encoderen (ongeldige Unicode in invoer)." + } + }, + "UrlParser": { + "title": "URL-parser", + "subtitle": "Splits een URL op in protocol, host, pad, queryparameters en hash (fragment).", + "upload": "Uploaden", + "clear": "Wissen", + "panels": { + "input": "URL-invoer", + "output": "Resultaat" + }, + "placeholders": { + "input": "Plak een URL (bijv. https://example.com/path?foo=bar#section) of host/pad (bijv. example.com/path)…" + }, + "dropHere": "Zet bestand hier neer", + "charCount": "{count} tekens", + "outputPlaceholder": "De geparseerde onderdelen verschijnen hier…", + "fields": { + "protocol": "Protocol", + "host": "Host", + "path": "Pad", + "hash": "Hash", + "queryParams": "Queryparameters" + }, + "paramCount": "{count} parameters", + "noParams": "Geen queryparameters.", + "paramKey": "Sleutel", + "paramValue": "Waarde", + "copyTitle": "Kopiëren naar klembord", + "copyParamTitle": "Kopiëren als key=value", + "download": { + "title": "JSON downloaden", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Ongeldige URL. Voeg een hostname toe (example.com) en controleer tekens." + } + }, + "UuidGenerator": { + "title": "UUID-/ULID-generator", + "subtitle": "Genereer RFC 9562-UUID’s of ULID’s in de browser. Bulkexport tot {max} per run.", + "format": "Formaat", + "kindHints": { + "ulid": "Sorteerbare, URL-veilige identificatie van 26 tekens", + "uuid4": "Willekeurig (RFC 9562)", + "uuid7": "Tijdsgeordend, Unix-ms + willekeurig", + "uuid1": "MAC-/tijdgebaseerd (RFC 9562)", + "uuid6": "Herschikte gregoriaanse tijd", + "uuid3": "MD5-hash van naam + namespace", + "uuid5": "SHA‑1-hash van naam + namespace", + "nil": "Nil-UUID (allemaal nullen, RFC 9562)" + }, + "countLabel": "Aantal (1–{max})", + "nameLabel": "Naam (invoerstring)", + "namePlaceholder": "bijv. my.app/resource", + "bulkHint": "Bij bulk wordt #0, #1, … toegevoegd zodat elke ID uniek is (v3/v5 zijn deterministisch).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Aangepaste UUID" + }, + "customNamespaceLabel": "Namespace-UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Genereren", + "output": "Uitvoer", + "lines": "{count} regels", + "download": { + "title": ".txt downloaden", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Alles kopiëren", + "uppercaseToggle": "Uitvoer in hoofdletters", + "hyphensToggle": "Toon koppeltekens", + "copyJsonTitle": "Kopiëren als JSON-array", + "outputPlaceholder": "Klik op \"Genereren\"…", + "errors": { + "invalidCustomNamespace": "Aangepaste namespace moet een geldige UUID zijn.", + "missingName": "Naam is vereist voor UUID v3 en v5.", + "unknown": "Kon geen ID’s genereren." + } + }, + "SecretApiKeyGenerator": { + "title": "Generator voor geheim / API-sleutel", + "subtitle": "Genereer cryptografisch willekeurige strings met configureerbare lengte en alfabet. Bulkexport tot {max} per run. Past bij de UUID-/ULID-generator.", + "alphabetLabel": "Alfabet", + "alphabetHint": "Dubbele tekens worden verwijderd. Elk symbool wordt uniform gekozen met crypto.getRandomValues en rejection sampling (geen modulo-bias).", + "alphabetCount": "{count} unieke symbolen", + "presetsLabel": "Voorinstellingen", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumeriek", + "hex": "Hex (kleine letters)", + "asciiPrintable": "ASCII afdrukbaar" + }, + "lengthLabel": "Lengte per geheim (1–{maxLen})", + "countLabel": "Aantal (1–{maxBulk})", + "generate": "Genereren", + "output": "Uitvoer", + "lines": "{count} regels", + "download": { + "title": ".txt downloaden", + "filename": "geheimen.txt" + }, + "copyTitle": "Alles kopiëren", + "outputPlaceholder": "Klik op \"Genereren\"…", + "entropy": "{bits} bits entropie", + "strength": { + "weak": "Zwak", + "fair": "Redelijk", + "strong": "Sterk" + }, + "excludeAmbiguous": "Dubbelzinnige tekens uitsluiten", + "excludeAmbiguousHint": "Verwijdert {chars} — tekens die makkelijk verkeerd gelezen worden of shell-onvriendelijk zijn.", + "prefixLabel": "Sleutelvoorvoegsel (optioneel)", + "errors": { + "emptyAlphabet": "Voeg minstens één teken toe aan het alfabet.", + "alphabetTooLong": "Na het verwijderen van duplicaten mag het alfabet hoogstens {maxAlpha} tekens bevatten.", + "lengthOutOfRange": "De lengte moet tussen 1 en {maxLen} liggen.", + "countOutOfRange": "Het aantal moet tussen 1 en {maxBulk} liggen.", + "unknown": "Geheimen konden niet worden gegenereerd." + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum Generator", + "subtitle": "Genereer Latijnse plaatsaanduidingstekst voor je lay-outs en mockups.", + "paragraphs": "Alinea's", + "sentences": "Zinnen", + "words": "Woorden", + "count": "Aantal", + "generate": "Genereer", + "copy": "Kopieer", + "startWith": "Begin met 'Lorem ipsum...'", + "outputPlaceholder": "Uitvoer verschijnt hier...", + "outputType": "Uitvoertype", + "bulletList": "Opsommingslijst", + "htmlMarkup": "HTML-opmaak", + "htmlMarkupHint": "Uitvoer in alinea's inpakken of een lijst gebruiken voor de opsommingstekenmodus", + "output": "Uitvoer", + "download": "Downloaden" + }, + "ColorPicker": { + "title": "Kleurkiezer en converter", + "subtitle": "Converteer HEX, RGB en HSL; maak harmonische paletten. Alle verwerking vindt plaats in je browser.", + "nativePicker": "Ingebouwde kiezer", + "chooseColor": "Kies een kleur", + "eyeDropper": "Pipet", + "invalidHex": "Ongeldige HEX-kleur.", + "cssValues": "CSS-waarden", + "palettes": "Paletten", + "copied": "Gekopieerd", + "clickToCopy": "Klik om te kopiëren", + "copyAllHex": "Alle HEX kopiëren", + "copy": "Kopiëren", + "copyColor": "{color} kopiëren", + "paletteShades": "Tinten", + "paletteComplementary": "Complementair", + "paletteTriadic": "Triadisch", + "paletteAnalogous": "Analoog", + "paletteSplit": "Gedeeld compl.", + "paletteSquare": "Vierkant" + }, + "ContrastChecker": { + "title": "Contrastcontrole", + "subtitle": "Berekent de WCAG 2.1-contrastverhouding tussen voorgrond en achtergrond. Toont AA en AAA voor normale en grote tekst. Alles in uw browser.", + "foreground": "Tekst / voorgrond", + "background": "Achtergrond", + "hexPlaceholder": "#RRGGBB", + "swap": "Kleuren omwisselen", + "chooseForeground": "Voorgrondkleur kiezen", + "chooseBackground": "Achtergrondkleur kiezen", + "eyeDropper": "Pipet", + "ratio": "Contrastverhouding", + "copyRatio": "Verhouding kopiëren", + "copied": "Gekopieerd", + "previewHeading": "Voorbeeld", + "previewBody": "Pa’s wijze lynx bezag vroom het fikse aquaduct. Quartz-fex, judge my vow: Sphinx of black quartz, judge my vow.", + "previewCaption": "Voorbeeldtekst op standaardgrootte.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — normale tekst (4,5:1)", + "levelAALarge": "AA — grote tekst (3:1)", + "levelAAANormal": "AAA — normale tekst (7:1)", + "levelAAALarge": "AAA — grote tekst (4,5:1)", + "pass": "Voldoet", + "fail": "Voldoet niet", + "invalidHex": "Ongeldige HEX-kleur.", + "largeTextHint": "Grote tekst: 18 pt+ normaal of 14 pt+ vet (WCAG-definities)." + }, + "DiffChecker": { + "title": "Verschilcontrole", + "subtitle": "Regel-voor-regel vergelijking van twee teksten naast elkaar. Alles blijft in uw browser.", + "rowsCount": "{count} rijen", + "truncatedWarning": "Eerste {max} rijen worden weergegeven.", + "limitExceeded": "Elke zijde is beperkt tot {max} tekens.", + "clearBoth": "Beide wissen", + "original": "Origineel", + "modified": "Gewijzigd", + "originalPlaceholder": "Plak originele tekst…", + "modifiedPlaceholder": "Plak gewijzigde tekst…", + "comparisonLabel": "Vergelijking naast elkaar", + "emptyState": "Voer tekst in beide panelen in om te vergelijken." + }, + "JsonSchemaGenerator": { + "title": "JSON-schema-generator", + "subtitle": "Plak geldige JSON om een JSON-schema of getypeerde modellen te genereren. Alles draait in je browser.", + "languageLabel": "Uitvoertaal", + "inputLabel": "JSON-invoer", + "inputPlaceholder": "Plak JSON hier…", + "outputLabel": "Gegenereerde uitvoer", + "outputPlaceholder": "Het schema of de gegenereerde typen verschijnen hier…", + "parseError": "Ongeldige JSON", + "clear": "Wissen", + "copy": "Kopiëren", + "copied": "Gekopieerd naar klembord", + "copyFailed": "Kopiëren mislukt", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Editor laden…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Afbeelding naar Base64", + "subtitle": "Zet een afbeelding neer, haal een gegevens-URI of onbewerkte Base64-tekenreeks op. Alles blijft in uw browser.", + "upload": "Afbeelding uploaden", + "clear": "Duidelijk", + "modes": { + "dataUri": "Gegevens-URI", + "rawString": "Ruwe Basis64" + }, + "panels": { + "imageInput": "Beeldinvoer", + "output": "Base64-uitvoer" + }, + "dropHere": "Zet de afbeelding hier neer", + "charCount": "{tel} tekens", + "placeholders": { + "uploadImage": "Sleep een afbeelding en zet deze neer of klik op uploaden om te starten." + }, + "fileTooLarge": "Bestand is te groot. Bij het verwerken kan uw browser vastlopen.", + "copyTitle": "Kopiëren naar klembord", + "download": { + "title": "Tekst downloaden", + "filename": "afbeelding-base64.txt" + } + }, + "ImageCompressor": { + "title": "Afbeeldingscompressie", + "subtitle": "Verklein JPEG-, PNG- of WebP-uitvoer lokaal met kwaliteitsregeling — er wordt niets geüpload.", + "upload": "Afbeelding uploaden", + "clear": "Wissen", + "dropHere": "Sleep afbeelding hierheen", + "placeholders": { + "uploadImage": "Sleep een afbeelding of klik om een bestand te kiezen." + }, + "fileTooLarge": "Bestand is te groot (max. {maxMb} MB).", + "unsupportedFormat": "Bestandstype wordt niet ondersteund. Gebruik JPEG, PNG, WebP, GIF, AVIF of BMP.", + "compressFailed": "Deze afbeelding kan niet worden gecomprimeerd. Probeer een ander bestand of formaat.", + "panels": { + "original": "Origineel", + "compressed": "Gecomprimeerde preview" + }, + "outputFormat": "Uitvoerformaat", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kwaliteit", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG is in de browser verliesvrij; de kwaliteitsschuif geldt alleen voor JPEG en WebP.", + "sizes": { + "original": "Origineel: {size}", + "compressed": "Gecomprimeerd: {size}", + "saved": "{percent}% kleiner" + }, + "largerWarning": "Uitvoer is groter dan invoer — probeer een ander formaat of kwaliteit.", + "download": "Downloaden", + "downloadFilename": "gecomprimeerde-afbeelding", + "processing": "Comprimeren…", + "emptyCompressed": "Gecomprimeerde preview verschijnt hier." + }, + "CssGradientBuilder": { + "title": "CSS-verloopbouwer", + "subtitle": "Visuele CSS-gradiëntbouwer met hoekcontrole, kleurstops en CSS-kopie met één klik.", + "preview": "Verloopvoorbeeld", + "settings": "Instellingen", + "type": "Type", + "linear": "Lineair", + "radial": "Radiaal", + "angle": "Hoek", + "stops": "Kleur stopt", + "color": "Kleur", + "position": "Positie (%)", + "addStop": "Stop toevoegen", + "deleteStop": "Stop verwijderen", + "output": "CSS-uitvoer", + "copy": "Kopieer CSS", + "copied": "Gekopieerd!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore-generator", + "subtitle": "Genereer direct samengevoegde .gitignore-bestanden voor een specifieke tech-stack of combinatie.", + "searchPlaceholder": "Zoekstapels (bijv. Node, Python, macOS)...", + "noResults": "Geen stapels gevonden.", + "selectedStacks": "Geselecteerde stapels", + "output": "Voorbeeld van uitvoer", + "copy": "Kopiëren naar Klembord", + "copied": "Gekopieerd!", + "download": "Download .gitignore", + "downloading": "Downloaden...", + "errorFetching": "Kan de toplijst niet ophalen.", + "errorGenerating": "Kan .gitignore niet genereren van geselecteerde stapels.", + "generating": "Genereren...", + "emptyState": "Selecteer een of meer tech-stacks om uw .gitignore te genereren." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Upload CSV of Excel, bewerk JSON en download CSV of XLSX. Alleen tabelgegevens; Excel-datums worden ISO-tijdstempels in JSON.", + "dropTitle": "Sleep een CSV- of Excel-bestand hierheen", + "dropHint": "Voor .xlsx / .xls wordt het eerste werkblad gebruikt. CSV moet een kopregel hebben.", + "chooseFile": "Bestand kiezen", + "jsonLabel": "JSON (array van objecten)", + "jsonPlaceholder": "Plak een JSON-array van objecten of importeer CSV / Excel…", + "loadSample": "Voorbeeld laden", + "actions": { + "format": "JSON formatteren", + "clear": "Wissen", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "downloadCsv": "CSV downloaden", + "downloadXlsx": "Excel downloaden" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Gegevens", + "toastLoadedCsv": "CSV geladen in editor", + "toastLoadedExcel": "Excel-bestand geladen in editor", + "toastFormatted": "JSON geformatteerd", + "toastExportedCsv": "CSV gedownload", + "toastExportedXlsx": "Excel gedownload", + "toastCopied": "Gekopieerd naar klembord", + "footnote": "Alles draait in je browser. JSON moet een niet-lege array van objecten zijn. Geneste waarden worden als JSON in CSV-cellen gezet.", + "errors": { + "unsupportedFile": "Gebruik een .csv-, .xlsx- of .xls-bestand.", + "parseFailed": "Dit bestand kon niet worden gelezen.", + "invalidJson": "Ongeldige JSON", + "exportFailed": "Exporteren mislukt", + "copyFailed": "Kopiëren mislukt" + } + }, + "SnippetManager": { + "title": "Codefragmentbeheer", + "subtitle": "Bewaar snippets in je browser of log in om ze met je account te synchroniseren. Wissel tussen bekijken en bewerken, kopieer met één klik, kies een taal of automatische detectie en formatteer JSON, SQL of door Monaco ondersteunde code.", + "snippets": "Fragmenten", + "newSnippet": "Nieuw fragment", + "newSnippetTitle": "Nieuw fragment", + "defaultSnippetTitle": "Welkom", + "defaultSnippetCode": "// Welkom bij codefragmenten\n// Probeer bekijken vs bewerken, kopiëren, formatteren en automatische taal\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Fragmenten zoeken…", + "searchAria": "Fragmenten zoeken", + "emptySearch": "Geen fragmenten komen overeen met je zoekopdracht.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Fragmentnaam", + "language": "Taal", + "detectedHint": "Markering als {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Bekijken", + "modeEdit": "Bewerken", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "format": "Formatteren", + "cancel": "Annuleren", + "deleteTitle": "Fragment verwijderen?", + "deleteDescription": "‘{title}’ wordt uit deze browser verwijderd.", + "deleteConfirm": "Verwijderen", + "editorAria": "Fragmentcode-editor", + "editorLoading": "Editor laden…", + "toastCopied": "Gekopieerd naar klembord", + "toastCopyFailed": "Kopiëren mislukt", + "toastNothingToCopy": "Niets om te kopiëren", + "toastFormatted": "Geformatteerd", + "toastFormatFailed": "Formatteren mislukt", + "toastFormatJsonInvalid": "Ongeldige JSON — los eerst de syntaxis op", + "toastFormatUnsupported": "Geen formatter voor deze taal — probeer JSON of SQL, of bewerkingsmodus", + "toastDeleted": "Fragment verwijderd", + "toastLoadFailed": "Snippets konden niet van de server worden geladen.", + "toastSyncFailed": "Synchroniseren met de server is mislukt.", + "languages": { + "auto": "Automatisch detecteren", + "plaintext": "Platte tekst", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL-client", + "dialogTitle": "SQL-verbindingen", + "mobileSheetTitle": "SQL-verkenner", + "newTabLabel": "Nieuw querytabblad", + "vaultLockedTitle": "Kluis vergrendeld", + "vaultLockedDesc": "Ontgrendel de kluis om toegang te krijgen tot opgeslagen verbindingen.", + "heroTitle": "SQL-client", + "heroDesc": "Maak verbinding met PostgreSQL, MySQL of MariaDB. Inloggegevens worden in de browser versleuteld.", + "heroAddConnection": "Verbinding toevoegen", + "selectTableTitle": "Selecteer een tabel of open een querytabblad", + "selectTableDesc": "Klik op een tabel in de zijbalk of gebruik ⌘↩.", + "toastLoadFail": "Verbindingen laden mislukt." + }, + "connection": { + "titleNew": "Nieuwe SQL-verbinding", + "titleEdit": "Verbinding bewerken", + "labelDbType": "Databasetype", + "labelName": "Verbindingsnaam", + "placeholderName": "Mijn productiedatabase", + "labelHostPort": "Host en poort", + "labelDatabase": "Database", + "placeholderDatabase": "mydb", + "labelUsername": "Gebruikersnaam", + "labelPassword": "Wachtwoord", + "labelSsl": "SSL / TLS inschakelen", + "sslDesc": "Verbinding met de server versleutelen", + "securityNote": "Inloggegevens worden AES-256 versleuteld in de browser vóór opslag", + "btnTest": "Verbinding testen", + "btnSave": "Opslaan en verbinden", + "btnUpdate": "Bijwerken", + "btnCancelEdit": "Bewerken annuleren", + "savedConnections": "Opgeslagen verbindingen", + "emptySaved": "Opgeslagen verbindingen verschijnen hier", + "confirmDelete": "Deze verbinding verwijderen?", + "toastFillFields": "Vul host en database in vóór het testen.", + "toastConnectOk": "Verbonden! {version}", + "toastConnectFail": "Verbinding mislukt: {message}", + "toastUnlockVault": "Ontgrendel eerst de kluis.", + "toastFieldsRequired": "Host en database zijn verplicht.", + "toastUpdated": "Verbinding bijgewerkt.", + "toastSaved": "Verbinding opgeslagen.", + "toastSaveFail": "Opslaan mislukt: {message}", + "toastDeleted": "Verbinding verwijderd.", + "toastDeleteFail": "Verbinding verwijderen mislukt." + }, + "sidebar": { + "title": "SQL-verkenner", + "tooltipAddConnection": "Verbinding toevoegen", + "tooltipRefreshSchema": "Schema vernieuwen", + "noConnections": "Nog geen verbindingen opgeslagen", + "toastSchemaFail": "Schema laden mislukt: {message}" + }, + "editor": { + "btnRun": "Uitvoeren", + "executing": "Query uitvoeren…", + "emptyTitle": "Voer een query uit om resultaten te zien", + "emptyHint": "Druk op ⌘↩ of klik Uitvoeren", + "toastNoConnection": "Geen actieve verbinding." + }, + "results": { + "filterPlaceholder": "Resultaten filteren…", + "rowCount": "{count} {count, plural, one {rij} other {rijen}}", + "rowCountFiltered": "{filtered} / {total} rijen", + "exportCsv": "CSV", + "noRows": "Geen rijen geretourneerd" + } + }, + "QrCodeGenerator": { + "title": "QR-codegenerator", + "subtitle": "Zet tekst of een URL om in een PNG QR-code. Alles wordt lokaal in de browser gerenderd.", + "contentLabel": "Inhoud", + "contentPlaceholder": "https://voorbeeld.nl, platte tekst, Wi‑Fi-tekenreeks, vCard…", + "styleLabel": "Stijl", + "dotTypeLabel": "Puntvorm", + "dotTypeSquare": "Vierkant", + "dotTypeDots": "Punten", + "dotTypeRounded": "Afgerond", + "dotTypeExtraRounded": "Extra afgerond", + "dotTypeClassy": "Elegant", + "dotTypeClassyRounded": "Elegant afgerond", + "cornerSquareLabel": "Hoekvierkanten", + "cornerSquareDot": "Punt", + "cornerSquareSquare": "Vierkant", + "cornerSquareExtraRounded": "Extra afgerond", + "cornerDotLabel": "Hoekpunten", + "cornerDotDot": "Punt", + "cornerDotSquare": "Vierkant", + "eccLabel": "Foutcorrectie", + "eccL": "Laag (~7%)", + "eccM": "Gemiddeld (~15%)", + "eccQ": "Kwartiel (~25%)", + "eccH": "Hoog (~30%)", + "marginLabel": "Marge (modules)", + "widthLabel": "Beeldbreedte", + "widthEffective": "Wordt gerenderd op {px} px", + "darkColor": "Donkere modules", + "lightColor": "Lichte modules", + "resetColors": "Kleuren resetten", + "logoLabel": "Centraal logo", + "logoMarginLabel": "Marge", + "uploadLogo": "Afbeelding uploaden", + "removeLogo": "Logo verwijderen", + "downloadPng": "PNG downloaden", + "downloadSvg": "SVG downloaden", + "copyImage": "Afbeelding kopiëren", + "copied": "Gekopieerd", + "clear": "Wissen", + "previewAlt": "Voorbeeld QR-code", + "emptyHint": "Typ of plak inhoud om een voorbeeld te zien.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "Kan deze QR-code niet genereren. Probeer kortere tekst of lagere correctie.", + "copyUnsupported": "Je browser ondersteunt het kopiëren van afbeeldingen niet.", + "copyFailed": "Kopiëren naar klembord mislukt." + } + }, + "IpSubnetCalculator": { + "title": "IP-/subnetcalculator", + "subtitle": "IPv4- en IPv6-CIDR in de browser—geen externe netwerkverzoeken.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Netwerk", + "placeholderIpv4": "10.0.0.0/24 of 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 of 2001:db8::1/64", + "hintIpv4": "Gebruik CIDR (bijv. /24) of IPv4 met decimale netmasker.", + "hintIpv6": "Geef de prefixlengte (bijv. /64). Zonder invoer wordt /128 aangenomen.", + "copyValue": "Waarde kopiëren", + "rows": { + "cidr": "CIDR", + "network": "Netwerkadres", + "netmask": "Subnetmasker", + "wildcard": "Wildcard-masker", + "broadcast": "Broadcast", + "firstHost": "Eerste host", + "lastHost": "Laatste host", + "totalAddresses": "Totaal adressen", + "usableHosts": "Bruikbare hosts", + "prefixLength": "Prefixlengte (bits)", + "networkIpv6": "Netwerkadres", + "lastIpv6": "Laatste adres in bereik", + "totalAddressesIpv6": "Adressen in subnet" + }, + "usableNote": { + "pointToPoint": "/31 point-to-point gebruikt beide adressen als hosts (RFC 3021).", + "singleHost": "/32 is één hostadres." + }, + "ipv6RangeHint": { + "normal": "Toont het volledige wiskundige bereik; routerregels kunnen afwijken.", + "single": "/128 is één adres.", + "full": "/0 dek heel IPv6." + }, + "errors": { + "invalidIpv4": "Ongeldig IPv4-netwerk. Probeer CIDR of adres + masker.", + "invalidIpv6": "Ongeldig IPv6-netwerk. Gebruik adres/prefix, bijv. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Hash-generator", + "subtitle": "MD5-, SHA-1- en SHA-2-digest voor tekst of bestanden; bcrypt voor wachtwoorden (tekstinvoer). Alles lokaal in je browser.", + "algorithmLabel": "Algoritme", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Tekst", + "modeFile": "Bestand", + "inputLabelText": "Invoer", + "placeholderText": "Typ of plak tekst…", + "chooseFile": "Bestand kiezen", + "clearFile": "Wissen", + "fileHint": "Bestanden worden alleen in geheugen gelezen; max {maxMb} MB. Geen upload.", + "outputLabel": "Digest (hex)", + "copyHash": "Digest kopiëren", + "copied": "Gekopieerd", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# teken} other {# tekens}}", + "emptyHint": "Voer tekst in of kies een bestand.", + "securityNote": "MD5 en SHA-1 worden niet aanbevolen voor nieuwe beveiliging; gebruik SHA-256 of SHA-512.", + "errors": { + "fileTooLarge": "Bestand groter dan {maxMb} MB.", + "fileReadFailed": "Kan bestand niet lezen." + }, + "bcryptRoundsLabel": "Kosten (rondes)", + "bcryptRoundsHint": "Hogere waarden zijn trager en sterker tegen brute force. Vaak standaard 10.", + "fileTabDisabledTitle": "bcrypt hasht alleen wachtwoorden uit tekst, geen willekeurige bestanden.", + "placeholderBcrypt": "Typ of plak een wachtwoord om te hashen…", + "outputLabelBcrypt": "bcrypt-hash", + "copyBcrypt": "Hash kopiëren", + "emptyHintBcrypt": "Voer een wachtwoord in om een bcrypt-hash te berekenen.", + "securityNoteBcrypt": "Elke bcrypt-uitvoer bevat een willekeurig zout; hetzelfde wachtwoord geeft elke keer een andere string. Dat is verwacht. Gebruik hoge kosten in productie.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Encryptie-speeltuin", + "subtitle": "AES-GCM in de browser: versleutel naar een JSON-bundel met wachtwoordzin of ruwe sleutel en ontsleutel lokaal. Educatief en soms praktisch — er wordt niets geüpload.", + "tabEncrypt": "Versleutelen", + "tabDecrypt": "Ontsleutelen", + "keyModeLabel": "Sleutelmateriaal", + "modePassphrase": "Wachtwoordzin (PBKDF2 + AES-GCM)", + "modeRawKey": "Ruwe AES-sleutel (hex of Base64)", + "aesBitsLabel": "AES-sleutellengte", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Codering ruwe sleutel", + "encodingAuto": "Auto (hex als het op hex lijkt, anders Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Ruwe sleutel", + "rawKeyPlaceholder": "32-byte sleutel als 64 hextekens, of Base64 voor AES-256 (lengte hangt af van gekozen AES-grootte).", + "rawKeyPlaceholderDecrypt": "Alleen nodig als de bundel keyDerivation \"none\" heeft.", + "passphraseLabel": "Wachtwoordzin", + "passphrasePlaceholder": "Wachtwoordzin alleen in uw browser om de sleutel af te leiden.", + "passphrasePlaceholderDecrypt": "Alleen nodig als de bundel PBKDF2-SHA256 gebruikt.", + "pbkdf2IterLabel": "PBKDF2-iteraties", + "pbkdf2Hint": "Begrensd tussen 10.000 en 2.000.000. Standaard sluit aan bij gangbare kluis-instellingen.", + "plaintextLabel": "Klare tekst", + "plaintextPlaceholder": "Typ of plak het bericht om te versleutelen…", + "bundleInputLabel": "Versleutelde bundel (JSON)", + "bundlePlaceholder": "Plak de JSON van Versleutelen (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Versleutelde bundel", + "plaintextOutputLabel": "Klare tekst", + "encryptButton": "Versleutelen", + "decryptButton": "Ontsleutelen", + "copyBundle": "JSON kopiëren", + "copyPlaintext": "Klare tekst kopiëren", + "copied": "Gekopieerd", + "emptyBundleHint": "Versleutelde JSON verschijnt hier na versleutelen.", + "emptyDecryptHint": "Ontsleutelde tekst verschijnt hier.", + "decryptKeyHint": "Gebruik ruwe sleutel als keyDerivation \"none\" is; wachtwoordzin bij PBKDF2-SHA256. De bundel bevat salt, iteraties, IV en AES-lengte.", + "securityNote": "Sleutels en klare tekst verlaten uw browser niet. Gebruik voor productiegeheimen liever gespecialiseerde tools.", + "errors": { + "emptyKey": "Voer een ruwe sleutel in die bij de AES-grootte past.", + "emptyPassphrase": "Voer een wachtwoordzin in.", + "badKeyMaterial": "Sleutel kon niet als hex of Base64 worden gelezen.", + "wrongKeyLength": "Sleutellengte komt niet overeen met gekozen AES (16 / 24 / 32 bytes).", + "invalidJson": "Geen geldige JSON.", + "invalidBundle": "Deze JSON is geen ondersteunde versleutelingsbundel.", + "decryptFailed": "Ontsleuteling mislukt — verkeerd geheim, beschadigde bundel of verkeerde parameters.", + "encryptFailed": "Versleuteling mislukt. Controleer Web Crypto en invoer." + } + }, + "HmacGenerator": { + "title": "HMAC-generator", + "subtitle": "Voor het ondertekenen van webhooks en het testen van API-integraties. HMAC-SHA1/256/384/512 in hex of Base64—lokaal in je browser berekend.", + "secretLabel": "Geheime sleutel", + "messageLabel": "Bericht / payload", + "placeholderSecret": "Plak je signing secret…", + "placeholderMessage": "Plak de ruwe body of tekenreeks om te verifiëren (vaak de exacte webhook-bytes).", + "algorithmLabel": "HMAC-algoritme", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Uitvoerformaat", + "formatHex": "Hex (kleine letters)", + "formatBase64": "Base64", + "outputLabel": "Handtekening", + "copyOutput": "Handtekening kopiëren", + "copied": "Gekopieerd", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8)", + "emptyHint": "Voer een geheime sleutel in om HMAC te berekenen.", + "securityNote": "Deel je signing secret nooit. Deze pagina uploadt geen gegevens—alles draait in je browser.", + "errors": { + "cryptoFailed": "HMAC kon niet worden berekend. Controleer Web Crypto-ondersteuning en probeer opnieuw.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA-codegenerator", + "subtitle": "Plak het Base32-geheim en zie de actuele zescijferige RFC 6238-code (SHA-1, 30 s). Alles lokaal in je browser—handig voor het testen van login of MFA.", + "secretLabel": "Geheim (Base32)", + "placeholderSecret": "Plak de geheime sleutel (letters A–Z en cijfers 2–7, zoals bij het instellen van de authenticator)…", + "codeLabel": "Huidige code", + "copyCode": "Code kopiëren", + "copied": "Gekopieerd", + "nextIn": "Vernieuwt over {seconds} s", + "standardHint": "30 s tijdstap, 6 cijfers, HMAC-SHA-1 (compatibel met Google Authenticator).", + "digitsLabel": "Cijfers", + "periodLabel": "Periode", + "algorithmLabel": "Algoritme", + "periodSeconds": "{seconds} s", + "emptyHint": "Plak een geheim om een zescijferige code te maken.", + "invalidHint": "Corrigeer het geheim om de code te zien.", + "securityNote": "Behandel dit als een wachtwoord. Het geheim verlaat je browser niet; er wordt niets geüpload.", + "importLabel": "Importeren vanuit otpauth://-URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importeren", + "qrSectionTitle": "QR-code voor provisioning", + "qrHint": "Scan met een authenticator-app (Google Authenticator, 1Password, Aegis…) om dit geheim toe te voegen. De QR-code codeert het geheim — behandel het als een wachtwoord.", + "accountLabel": "Account", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Uitgever (optioneel)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Voer een geldig geheim in om een QR-code te genereren.", + "errors": { + "invalidSecret": "Dit is geen geldig Base32-geheim.", + "cryptoFailed": "TOTP kon niet worden berekend. Controleer Web Crypto en probeer opnieuw.", + "invalidUri": "Dit is geen geldige otpauth:// TOTP-URI.", + "unsupportedUri": "Deze URI gebruikt waarden die de tool niet ondersteunt (cijfers moeten 6 of 8 zijn; periode 15, 30 of 60 s)." + } + }, + "MarkdownPreview": { + "title": "Markdown-voorbeeld & HTML", + "subtitle": "Live Markdown-renderer met HTML-export en HTML → Markdown-conversie. Alles draait in uw browser.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown-invoer", + "htmlInputLabel": "HTML-invoer", + "previewLabel": "Voorbeeld", + "htmlOutputLabel": "HTML-bron", + "mdOutputLabel": "Markdown-uitvoer", + "inputPlaceholder": "# Hallo\\n\\nVoer uw **markdown** hier in…", + "htmlInputPlaceholder": "Plak uw HTML hier…", + "copyHtml": "HTML kopiëren", + "copyMarkdown": "Markdown kopiëren", + "exportHtml": "HTML exporteren", + "copied": "Gekopieerd", + "charCount": "{count, plural, one {# teken} other {# tekens}}", + "emptyHint": "Begin met het typen van Markdown om de live preview en HTML-uitvoer te zien.", + "emptyHintHtmlToMd": "Plak HTML om het naar Markdown te converteren.", + "securityNote": "Alles wordt lokaal in uw browser uitgevoerd. Er wordt niets geüpload." + }, + "NumberBaseConverter": { + "title": "Grondtal-converter", + "subtitle": "Converteer gehele getallen tussen grondtallen 2–36. Optionele prefixen 0x / 0b / 0o. Alles lokaal in je browser.", + "inputBaseLabel": "Van grondtal", + "outputBaseLabel": "Naar grondtal", + "inputLabel": "Invoergetal", + "placeholderInput": "Voer een geheel getal in het gekozen invoergrondtal in…", + "outputLabel": "Geconverteerde waarde", + "copyOutput": "Resultaat kopiëren", + "copied": "Gekopieerd", + "swap": "Grondtallen wisselen", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8-lengte)", + "emptyHint": "Voer een getal in om te converteren.", + "note": "Cijfers boven 9 gebruiken letters A–Z (hoofdletterongevoelig). Spaties en underscores worden genegeerd.", + "errors": { + "invalidNumber": "Ongeldige waarde voor het gekozen invoergrondtal." + } + }, + "FormatConverter": { + "title": "Formaatconverter", + "subtitle": "Converteer tussen JSON, YAML, TOML en XML in elke richting. Alle 12 combinaties. Alles draait in uw browser.", + "fromLabel": "Van", + "toLabel": "Naar", + "swap": "Omwisselen", + "swapTitle": "Formaten omwisselen en uitvoer als invoer gebruiken", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "charCount": "{count, plural, one {# teken} other {# tekens}}", + "emptyHint": "Plak of typ uw invoer aan de linkerkant om de geconverteerde uitvoer te zien.", + "localNote": "Alles draait lokaal in uw browser. Er wordt niets geüpload." + }, + "MimeTypeLookup": { + "title": "MIME-type opzoeken", + "subtitle": "Zoek het MIME-type (mediatype) op voor een extensie of volledige bestandsnaam. Alles draait lokaal in uw browser.", + "inputLabel": "Extensie of bestandsnaam", + "placeholder": "bijv. .png, package.json, photo.jpeg", + "hint": "Tip: plak een extensie (met of zonder punt) of een volledige bestandsnaam.", + "clear": "Wissen", + "exampleFilename": "Voorbeeldbestandsnaam gebruiken", + "exampleExtension": "Voorbeeldextensie gebruiken", + "resultLabel": "Resultaat", + "normalizedLabel": "Genormaliseerde invoer:", + "mimeTypeLabel": "MIME-type", + "contentTypeLabel": "Content-Type-header", + "charsetLabel": "Tekenset", + "copyMimeType": "MIME-type kopiëren", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "emptyHint": "Voer een extensie of bestandsnaam in om het MIME-type op te zoeken.", + "notFound": "Geen MIME-type gevonden voor deze invoer.", + "localNote": "Alles draait lokaal in uw browser. Er wordt niets geüpload." + }, + "UserAgentParser": { + "title": "User-Agent-parser", + "subtitle": "Plak een User-Agent (UA)-string en krijg een uitsplitsing van browser, OS, engine, apparaat en CPU. Alles draait lokaal in uw browser.", + "inputLabel": "User-Agent-string", + "placeholder": "Plak uw User-Agent hier…", + "hint": "Tip: u kunt een UA kopiëren uit request-headers, logs of de browser-devtools.", + "clear": "Wissen", + "exampleDesktop": "Desktopvoorbeeld gebruiken", + "exampleMobile": "Mobiel voorbeeld gebruiken", + "resultLabel": "Uitsplitsing", + "browserLabel": "Browser", + "osLabel": "Besturingssysteem", + "engineLabel": "Engine", + "deviceTypeLabel": "Apparaattype", + "deviceVendorLabel": "Fabrikant", + "deviceModelLabel": "Model", + "cpuArchLabel": "CPU-architectuur", + "copyJson": "JSON kopiëren", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "emptyHint": "Plak een User-Agent-string om de uitsplitsing te zien.", + "localNote": "Alles draait lokaal in uw browser. Er wordt niets geüpload." + }, + "MockDataGenerator": { + "title": "Mock-datagenerator", + "subtitle": "Definieer kolommen, kies gegevenstypen en exporteer fixtures voor seeds, Postman of tests. Tot {max} rijen, alles in je browser — er wordt niets geüpload.", + "mockarooLink": "Mockaroo-achtige workflow", + "rowsLabel": "Rijen", + "rowsHint": "Maximaal {max} rijen per run om het tabblad vlot te houden.", + "formatLabel": "Formaat", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "SQL INSERT", + "xml": "XML" + }, + "tableNameLabel": "SQL-tabelnaam", + "presetsLabel": "Sjablonen", + "presetPlaceholder": "Sjabloon laden…", + "presetDefault": "Start (gebruiker)", + "presetUsers": "Gebruikers & rollen", + "presetProducts": "E-commerce-SKU’s", + "presetApiLog": "HTTP-aanvraaglogboek", + "generate": "Genereren", + "warningDuplicateNames": "Dubbele kolomnamen: latere kolommen overschrijven eerdere in JSON-objecten. Hernoem ze.", + "schemaHeading": "Schema", + "addField": "Kolom toevoegen", + "removeField": "Kolom verwijderen", + "duplicateField": "Kolom dupliceren", + "colName": "Naam", + "colType": "Type", + "colOptions": "Opties", + "colBlank": "Leeg %", + "optMin": "min", + "optMax": "max", + "optDecimals": "dec", + "optDateFrom": "van datum", + "optDateTo": "tot datum", + "optValues": "a, b, c", + "optStep": "stap", + "optRowBase": "basis", + "output": "Uitvoer", + "outputPlaceholder": "Klik op Genereren om gegevens te maken…", + "copy": "Kopiëren", + "download": "Downloaden", + "copied": "Gekopieerd", + "groups": { + "personal": "Persoonlijk", + "location": "Locatie", + "internetApi": "Internet & API", + "numbersIds": "Getallen & ID’s", + "dateTime": "Datum & tijd", + "textDesign": "Tekst & vormgeving", + "custom": "Aangepast" + }, + "fieldTypes": { + "first_name": "Voornaam", + "last_name": "Achternaam", + "full_name": "Volledige naam", + "email": "Email", + "username": "Gebruikersnaam", + "password": "Wachtwoord", + "phone": "Telefoon", + "company": "Bedrijf", + "job_title": "Functietitel", + "avatar_url": "Avatar-URL", + "city": "Stad", + "country": "Land", + "country_code": "Landcode (ISO)", + "street_address": "Adres", + "zip_code": "Postcode", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "url": "URL", + "domain": "Domein", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Geheel getal", + "float": "Kommagetal", + "boolean": "Booleaans", + "uuid": "UUID", + "credit_card": "Creditcard (test)", + "semver": "SemVer", + "row_index": "Rijindex", + "sequence": "Reeks", + "date": "Datum", + "datetime": "Datum en tijd", + "unix_timestamp": "Unix-tijdstempel", + "timezone": "Tijdzone", + "word": "Woord", + "sentence": "Zin", + "paragraph": "Alinea", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Valutacode", + "language_code": "Taalcode", + "custom_list": "Aangepaste lijst" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG-optimalisatie", + "subtitle": "Plak SVG, minificeer met SVGO in je browser en vergelijk UTF-8-grootte voor en na.", + "inputLabel": "Invoer", + "outputLabel": "Geoptimaliseerd", + "placeholder": "Plak een -fragment of heel bestand…", + "outputPlaceholder": "Geoptimaliseerde SVG verschijnt hier…", + "chars": "{count} tekens • {bytes} bytes (UTF-8)", + "preview": "Voorbeeld", + "localNote": "Alles lokaal in je browser. Niets wordt geüpload.", + "stats": { + "title": "Grootte (UTF-8)", + "before": "Voor", + "after": "Na", + "savedBytes": "{count} bytes bespaard ({percent}%)", + "noChange": "Geen reductie — al minimaal of lege uitvoer." + }, + "actions": { + "clear": "Wissen", + "sample": "Voorbeeld laden", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "download": "Downloaden" + }, + "errors": { + "optimizeFailed": "Deze SVG kon niet worden geoptimaliseerd." + } + }, + "UnitConverter": { + "title": "Eenhedenconverter", + "subtitle": "Converteren tussen 323 eenheden in 43 categorieën", + "category": "Categorie", + "from": "Van", + "to": "Naar", + "value": "Waarde", + "result": "Resultaat", + "swapUnits": "Eenheden omwisselen", + "placeholder": "Voer waarde in", + "copyResult": "Resultaat kopiëren", + "searchCategory": "Zoek categorie...", + "selectCategory": "Selecteer een categorie", + "noResults": "Geen categorie gevonden." + }, + "DockerComposeGenerator": { + "title": "Docker Compose-generator", + "subtitle": "Kies populaire databases, caches, messaging, reverse proxy’s en observability — kopieer een werkend docker-compose.yml voor lokale ontwikkeling.", + "searchPlaceholder": "Zoeken (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Services", + "selectedCount": "{count, plural, one {# service} other {# services}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopiëren", + "copied": "Gekopieerd!", + "download": "Downloaden", + "emptyState": "Selecteer een of meer services om Compose-YAML te genereren.", + "hintPorts": "Standaard hostpoorten beperken conflicten; pas aan als Docker een poortfout meldt. Vervang demowachtwoorden vóór gedeelde omgevingen.", + "categories": { + "databases": "Databases & opslag", + "cache": "Cache & in-memory", + "queues": "Wachtrijen & messaging", + "search": "Zoeken & logs", + "storage": "Objectopslag", + "proxy": "Reverse proxy’s & ingress", + "observability": "Metrics & tracing", + "mail": "E-mail (lokaal)", + "admin": "Admin- & BI-interfaces" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Wissen" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (Redis-compatibel)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (Kafka-compatibel)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (S3-compatibel)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (SMTP-vangst)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (dev-server)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Schuif tegels en voeg ze samen om 2048 te bereiken", + "score": "Score", + "best": "Beste", + "newGame": "Nieuw Spel", + "howToPlay": "Gebruik de pijltjestoetsen (of veeg op mobiel) om tegels te verplaatsen. Tegels met hetzelfde nummer samenvoegen. Bereik 2048 om te winnen!", + "youWin": "Je hebt 2048 bereikt!", + "keepGoing": "Doorgaan", + "gameOver": "Game Over", + "tryAgain": "Probeer het opnieuw", + "gameOverMessage": "GEEN ZETTEN MEER!", + "undo": "Ongedaan maken" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "voltooide fasen", + "time": "Tijd", + "best": "Beste", + "difficulty": { + "easy": "Gemakkelijk", + "medium": "Gemiddeld", + "hard": "Directe" + }, + "stage": "Stadium", + "solved": "Solved", + "score": "Score", + "nextStage": "Het vervolg", + "replay": "Opnieuw afspelen", + "erase": "wissen", + "howToPlay": "Klik op een cel en tik vervolgens op een nummer of druk op 1–9. Backspace of Delete wist de cel. Vetgedrukte getallen zijn aanwijzingen. Vul het raster zodat elke rij, kolom en 3×3 doos 1–9 bevat zonder herhalingen.", + "pencil": "Potlood", + "undo": "Ongedaan maken", + "hint": "Hint", + "hintsUsed": "hints gebruikt", + "mistakes": "Fouten", + "failed": "Mislukt!", + "mistakeMode": "3-fout modus" + }, + "Snake": { + "title": "Snake", + "subtitle": "Eet voedsel, groei, vermijd muren en jezelf", + "score": "Score", + "best": "Beste", + "startPrompt": "Druk een pijltoets", + "startButton": "Spel starten", + "gameOver": "Spel voorbij", + "playAgain": "Opnieuw spelen", + "howToPlay": "Gebruik pijltoetsen of WASD. Eet om te groeien. Snelheid neemt toe elke 50 punten.", + "diffEasy": "Makkelijk", + "diffNormal": "Normaal", + "diffHard": "Moeilijk", + "diffInfinite": "Oneindig", + "orTap": "of tik hieronder", + "howToPlayInfinite": "Muren lopen door — geen muurdood. Eet om te groeien.", + "shieldActive": "Schild actief", + "speedBoost": "Snelheidsboost", + "powerSpeed": "Snelheid x2", + "powerShield": "Overleef één dood", + "powerShrink": "Verkort staart" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis-verbindingen", + "heroTitle": "Redis Commander", + "heroDesc": "Maak verbinding met Redis-instanties. Blader door sleutels, bewerk waarden en voer commando's uit. Inloggegevens worden versleuteld in uw browser.", + "heroAddConnection": "Verbinding toevoegen", + "vaultLockedTitle": "Kluis vergrendeld", + "vaultLockedDesc": "Ontgrendel uw kluis om toegang te krijgen tot opgeslagen verbindingen." + } + }, + "Minesweeper": { + "title": "Mijnenveger", + "subtitle": "Vind alle veilige cellen zonder een mijn te laten ontploffen", + "mines": "Mijnen", + "newGame": "Nieuw spel", + "win": "Gewonnen!", + "loss": "Verloren!", + "playAgain": "Opnieuw spelen", + "tryAgain": "Opnieuw proberen", + "startPrompt": "Klik op een cel om te beginnen", + "difficulty": { + "beginner": "Beginner", + "intermediate": "Gemiddeld", + "expert": "Expert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Rangschik de vallende blokken om rijen te voltooien", + "next": "Volgende", + "score": "Score", + "best": "Beste", + "lines": "Rijen", + "level": "Niveau", + "pause": "Pauzeren", + "resume": "Hervatten", + "start": "Start", + "restart": "Herstarten", + "paused": "Gepauzeerd", + "gameOver": "Game over" + }, + "SshKeyGenerator": { + "title": "SSH / RSA-sleutelgenerator", + "subtitle": "Genereer Ed25519- of RSA-sleutelparen in je browser. Download privé- en openbare sleutels in OpenSSH- en PEM-formaten. Er wordt niets geüpload.", + "keyTypeLabel": "Sleuteltype", + "recommended": "aanbevolen", + "ed25519Desc": "Snel, klein en modern. Ondersteund door GitHub, GitLab en alle grote SSH-servers.", + "rsa2048Desc": "Breed compatibel. Veilig voor de meeste toepassingen.", + "rsa4096Desc": "Maximale compatibiliteit en sterkte voor verouderde systemen.", + "commentLabel": "Opmerking (optioneel)", + "commentHint": "Toegevoegd aan de OpenSSH openbare sleutelregel.", + "generateBtn": "Genereer sleutelpaar", + "generating": "Genereren…", + "generatingLong": "Genereren (kan even duren)…", + "copy": "Kopiëren", + "download": "Downloaden", + "privateKeyLabel": "Privésleutel (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Openbare sleutel (OpenSSH-formaat)", + "publicKeySpkiLabel": "Openbare sleutel (SPKI PEM)", + "browserNote": "Alle sleutels worden gegenereerd in je browser. Er wordt niets naar een server gestuurd.", + "errors": { + "generationFailed": "Sleutelgeneratie mislukt" + } + }, + "DnsLookup": { + "title": "DNS-opzoeking", + "subtitle": "Zoek DNS-records op voor elk domein — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Voer een domein in (bijv. github.com)", + "lookupBtn": "Opzoeken", + "looking": "Bezig met opzoeken…", + "refreshBtn": "Vernieuwen", + "recordTypesLabel": "Recordtypen", + "allBtn": "Alles", + "tryLabel": "Probeer:", + "errors": { + "lookupFailed": "DNS-opzoeking mislukt" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt-generator en -controle", + "subtitle": "Hash wachtwoorden met bcrypt en verifieer hashes — alles blijft in je browser", + "tabs": { + "hash": "Hashen", + "verify": "Verifiëren" + }, + "showPassword": "Wachtwoord tonen", + "hidePassword": "Wachtwoord verbergen", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "hash": { + "passwordLabel": "Wachtwoord", + "passwordPlaceholder": "Voer een wachtwoord in om te hashen…", + "costLabel": "Kostenfactor", + "costHint": "Een hogere kostenfactor verdubbelt het werk per stap. 10-12 is een gangbare keuze voor productie.", + "slowWarning": "Kostenfactoren van 13 en hoger kunnen enkele seconden duren om te berekenen.", + "generate": "Hash genereren", + "generating": "Bezig met hashen…", + "resultLabel": "Bcrypt-hash", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Opbouw van de hash", + "algorithm": "algoritme", + "cost": "kosten", + "salt": "salt (22 tekens)", + "hash": "hash (31 tekens)" + } + }, + "verify": { + "passwordLabel": "Wachtwoord", + "passwordPlaceholder": "Voer het te controleren wachtwoord in…", + "hashLabel": "Bcrypt-hash", + "hashPlaceholder": "$2b$10$…", + "idle": "Voer een wachtwoord en een bcrypt-hash in om ze te vergelijken.", + "invalidHash": "Dit lijkt geen geldige bcrypt-hash.", + "checking": "Bezig met vergelijken…", + "match": "Komt overeen — het wachtwoord levert deze hash op.", + "noMatch": "Komt niet overeen — het wachtwoord levert deze hash niet op." + } + }, + "ChmodCalculator": { + "title": "Chmod-calculator", + "subtitle": "Zet Unix-bestandsrechten om tussen selectievakjes, octaal en symbolische notatie", + "grid": { + "title": "Rechten", + "read": "Lezen", + "write": "Schrijven", + "execute": "Uitvoeren", + "owner": "Eigenaar", + "group": "Groep", + "others": "Anderen" + }, + "special": { + "title": "Speciale bits", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Octaal", + "placeholder": "755 of 4755", + "invalid": "Voer 3 of 4 octale cijfers (0-7) in." + }, + "symbolic": { + "label": "Symbolisch", + "placeholder": "rwxr-xr-x", + "invalid": "Voer 9 tekens in zoals rwxr-xr-x (s/S, t/T toegestaan)." + }, + "output": { + "title": "Commando", + "copy": "Kopiëren", + "copied": "Gekopieerd" + }, + "explain": { + "line": "{cls} kan {perms}.", + "none": "{cls} heeft geen rechten.", + "setuid": "Setuid: het bestand draait met de rechten van de eigenaar.", + "setgid": "Setgid: het bestand draait met de rechten van zijn groep (nieuwe bestanden in een map erven de groep ervan).", + "sticky": "Sticky: alleen de eigenaar van een bestand kan het verwijderen of hernoemen binnen deze map." + }, + "presets": { + "title": "Veelgebruikte presets", + "octal": "Octaal", + "permissions": "Rechten", + "description": "Typisch gebruik", + "p644": "Gewone bestanden: eigenaar schrijft, iedereen leest.", + "p755": "Uitvoerbare bestanden en mappen: iedereen kan lezen en openen.", + "p600": "Privébestanden: alleen de eigenaar leest en schrijft.", + "p700": "Privé uitvoerbare bestanden en mappen: alleen de eigenaar.", + "p664": "Bestanden die door de groep beschrijfbaar zijn.", + "p775": "Mappen en uitvoerbare bestanden die door de groep beschrijfbaar zijn.", + "p400": "Alleen-lezen voor de eigenaar (bijv. SSH-privésleutels)." + } + }, + "CodeScreenshot": { + "title": "Code-screenshot", + "subtitle": "Zet code om in mooie, deelbare afbeeldingen", + "languageLabel": "Taal", + "autoDetect": "Automatisch detecteren", + "backgroundLabel": "Achtergrond", + "paddingLabel": "Opvulling", + "filenameLabel": "Bestandsnaam", + "filenamePlaceholder": "example.ts", + "windowLabel": "Vensterstippen", + "themeLabel": "Vensterthema", + "themeDark": "Donker", + "themeLight": "Licht", + "scaleLabel": "Exportschaal", + "copyImage": "Afbeelding kopiëren", + "copied": "Gekopieerd", + "exportPng": "PNG exporteren", + "inputLabel": "Code", + "detected": "Gedetecteerd: {language}", + "clear": "Wissen", + "placeholder": "Plak hier je code…", + "previewLabel": "Voorbeeld", + "emptyPreview": "Het voorbeeld van je code verschijnt hier", + "exportFailed": "Afbeelding exporteren mislukt", + "copyUnsupported": "Afbeeldingen kopiëren wordt niet ondersteund in deze browser", + "copyFailed": "Afbeelding kopiëren mislukt", + "backgrounds": { + "ocean": "Oceaan", + "sunset": "Zonsondergang", + "candy": "Snoep", + "forest": "Bos", + "midnight": "Middernacht", + "gold": "Goud", + "aurora": "Poollicht", + "slate": "Leisteen", + "transparent": "Transparant" + } + }, + "CssGenerators": { + "title": "CSS-generatoren", + "subtitle": "Box-shadow, border-radius en vloeiende clamp() — kopieer de CSS.", + "preview": "Voorbeeld", + "output": "CSS", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Vloeiende clamp()" + }, + "boxShadow": { + "presets": "Presets", + "layer": "Laag", + "addLayer": "Laag toevoegen", + "removeLayer": "Laag verwijderen", + "x": "X", + "y": "Y", + "blur": "Vervaging", + "spread": "Spreiding", + "color": "Kleur", + "alpha": "Alfa", + "inset": "Binnen", + "presetNames": { + "subtleCard": "Subtiele kaart", + "elevated": "Verhoogd", + "hardOffset": "Harde verschuiving", + "neonGlow": "Neongloed", + "inner": "Binnen", + "layeredSmooth": "Gelaagd zacht" + } + }, + "borderRadius": { + "modeLabel": "Modus", + "modes": { + "simple": "Per hoek", + "blob": "Organische blob" + }, + "unit": "Eenheid", + "linkCorners": "Hoeken koppelen", + "randomize": "Willekeurig", + "horizontal": "Horizontale radii", + "vertical": "Verticale radii", + "corners": { + "tl": "Linksboven", + "tr": "Rechtsboven", + "br": "Rechtsonder", + "bl": "Linksonder" + } + }, + "clamp": { + "minFont": "Min. lettergrootte (px)", + "maxFont": "Max. lettergrootte (px)", + "minViewport": "Min. viewport (px)", + "maxViewport": "Max. viewport (px)", + "rootFont": "Root-lettergrootte (px)", + "viewportWidth": "Gesimuleerde viewport", + "computedSize": "Berekende grootte", + "slope": "Helling", + "intercept": "Snijpunt", + "previewText": "De snelle bruine vos", + "invalid": "Voer geldige, verschillende viewport-grenzen in." + } + }, + "ExifViewer": { + "title": "EXIF-viewer en -verwijderaar", + "subtitle": "Inspecteer foto-metadata en verwijder ze in je browser", + "dropTitle": "Sleep hier een afbeelding", + "dropHint": "of klik om te bladeren — je afbeelding verlaat nooit je apparaat", + "browse": "Afbeelding kiezen", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Metadata lezen…", + "parseFailed": "Deze afbeelding kon niet worden gelezen", + "clear": "Afbeelding verwijderen", + "gpsWarning": "Bevat GPS-locatie", + "openMap": "Bekijk op OpenStreetMap", + "noMetadata": "Er is geen ingesloten metadata gevonden in deze afbeelding.", + "facts": { + "name": "Naam", + "type": "Type", + "dimensions": "Afmetingen", + "size": "Grootte" + }, + "groups": { + "camera": "Camera", + "exposure": "Belichting", + "timestamps": "Tijdstempels", + "gps": "GPS-locatie", + "software": "Software en overig" + }, + "gpsCard": { + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "altitude": "Hoogte" + }, + "raw": { + "title": "Alle metadata", + "copyJson": "JSON kopiëren", + "copied": "Gekopieerd" + }, + "remove": { + "title": "Metadata verwijderen", + "jpegDescription": "Verwijder EXIF, XMP, IPTC en opmerkingen zonder verlies, zonder de afbeelding opnieuw te comprimeren.", + "reencodeDescription": "Codeer de afbeelding opnieuw om ingesloten metadata te verwijderen. Hierbij worden de pixels opnieuw gecomprimeerd.", + "keepIcc": "Kleurprofiel (ICC) behouden", + "stripButton": "Metadata verwijderen", + "reencodeButton": "Opnieuw coderen en wissen", + "unsupported": "Metadata verwijderen wordt niet ondersteund voor dit bestandstype.", + "failed": "Metadata verwijderen mislukt", + "before": "Voor", + "after": "Na", + "download": "Opgeschoonde afbeelding downloaden" + } + }, + "FaviconGenerator": { + "title": "Favicon-generator", + "subtitle": "Maak favicons en app-iconen uit één afbeelding", + "dropTitle": "Sleep hier een afbeelding", + "dropHint": "PNG, JPEG of SVG — klik om te bladeren", + "browse": "Afbeelding kiezen", + "clear": "Afbeelding verwijderen", + "backgroundLabel": "Achtergrond", + "transparent": "Transparant", + "solidColor": "Effen", + "cornerLabel": "Hoekvorm", + "cornerNone": "Vierkant", + "cornerRounded": "Afgerond", + "cornerCircle": "Cirkel", + "maskPaddingLabel": "Maskable-opvulling", + "maskable": "maskable", + "previewLabel": "Voorbeeld", + "copy": "Kopiëren", + "headSnippet": "HTML-head-snippet", + "downloadIco": "favicon.ico downloaden", + "downloadZip": "Alles downloaden (.zip)" + }, + "JsonDiff": { + "title": "JSON-diff", + "subtitle": "Semantische, structuurbewuste vergelijking van twee JSON-documenten — sleutelvolgorde en opmaak worden genegeerd", + "options": { + "ignoreArrayOrder": "Arrayvolgorde negeren" + }, + "loadBothSamples": "Voorbeelden laden", + "sample": "Voorbeeld laden", + "format": "Formatteren", + "clear": "Wissen", + "formatError": "Kan niet formatteren: de invoer is geen geldige JSON", + "placeholder": "Plak of typ hier JSON…", + "panels": { + "left": "Origineel (links)", + "right": "Gewijzigd (rechts)", + "results": "Verschillen" + }, + "summary": { + "added": "{count} toegevoegd", + "removed": "{count} verwijderd", + "changed": "{count} gewijzigd" + }, + "types": { + "added": "Toegevoegd", + "removed": "Verwijderd", + "changed": "Gewijzigd" + }, + "copyChanges": "Kopiëren als JSON", + "copied": "Gekopieerd", + "empty": "Plak JSON in beide panelen om hun verschillen te zien.", + "identical": "De twee documenten zijn semantisch identiek." + }, + "JsonpathPlayground": { + "title": "JSONPath-playground", + "subtitle": "Bevraag JSON-documenten met JSONPath of JMESPath en zie de resultaten live", + "engineLabel": "Engine", + "queryLabel": "Query", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Spiekbriefje", + "cheats": { + "jpAllTitles": "Alle boektitels", + "jpAllAuthors": "Elke auteur, overal in het document", + "jpFirstBook": "Eerste boek", + "jpLastBook": "Laatste boek", + "jpSlice": "De eerste twee boeken (slice)", + "jpFilterPrice": "Boeken goedkoper dan 40", + "jpFilterInStock": "Titels van boeken op voorraad", + "jpRecursive": "Alle waarden, recursief", + "jmAllTitles": "Alle boektitels", + "jmFirstAuthor": "Auteur van het eerste boek", + "jmLastBook": "Laatste boek", + "jmSlice": "De eerste twee boeken (slice)", + "jmFilterPrice": "Titels van boeken goedkoper dan 40", + "jmCount": "Boeken op voorraad tellen", + "jmProjection": "Elk boek omvormen tot een nieuw object", + "jmSort": "Titels gesorteerd op prijs" + }, + "panels": { + "input": "JSON-invoer", + "results": "Resultaten" + }, + "sample": "Voorbeeld laden", + "format": "Formatteren", + "clear": "Wissen", + "formatError": "Kan niet formatteren: de invoer is geen geldige JSON", + "placeholder": "Plak of typ hier JSON…", + "outputPlaceholder": "Overeenkomsten verschijnen hier…", + "matchCount": "{count, plural, one {# overeenkomst} other {# overeenkomsten}}", + "copy": "Kopiëren", + "copied": "Gekopieerd" + }, + "KeycodeInspector": { + "title": "Keycode-inspecteur", + "subtitle": "Druk op een willekeurige toets om het JavaScript-toetsenbordevent te inspecteren", + "capture": { + "prompt": "Druk op een willekeurige toets…", + "ready": "Aan het luisteren — druk op een willekeurige toets op je toetsenbord", + "clickToFocus": "Klik hier om te focussen en druk daarna op een willekeurige toets" + }, + "hero": { + "spaceKey": "(Spatie)", + "keyLabel": "event.key", + "repeat": "herhaling" + }, + "copyJson": "JSON kopiëren", + "copiedJson": "Event-JSON naar het klembord gekopieerd", + "deprecated": "verouderd", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standaard", + "left": "Links", + "right": "Rechts", + "numpad": "Numeriek toetsenblok", + "unknown": "Onbekend" + }, + "history": { + "title": "Geschiedenis", + "clear": "Geschiedenis wissen", + "empty": "Toetsaanslagen verschijnen hier." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown-tabelgenerator", + "subtitle": "Bewerk een raster, plak uit een spreadsheet en kopieer een netjes uitgelijnde Markdown- of HTML-tabel", + "addRow": "Rij toevoegen", + "addColumn": "Kolom toevoegen", + "importLabel": "Importeren", + "importPlaceholder": "Plak hier TSV (uit Excel / Google Sheets) of CSV…", + "importButton": "Importeren in raster", + "pasteClipboard": "Plakken van klembord", + "importEmpty": "Niets om te importeren — plak eerst een paar rijen", + "clipboardError": "Kon niet van het klembord lezen", + "clear": "Wissen", + "panels": { + "editor": "Tabeleditor", + "output": "Uitvoer" + }, + "align": { + "left": "Links uitlijnen", + "center": "Centreren", + "right": "Rechts uitlijnen" + }, + "headerPlaceholder": "Koptekst", + "cellPlaceholder": "Cel", + "removeRow": "Rij verwijderen", + "removeColumn": "Kolom verwijderen", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopiëren", + "copied": "Gekopieerd" + }, + "TimezoneConverter": { + "title": "Tijdzone-converter", + "subtitle": "Zet één moment om over de hele wereld voor het plannen van vergaderingen.", + "dateTimeLabel": "Datum en tijd", + "now": "Nu", + "sourceZoneLabel": "Brontijdzone", + "selectZone": "Zone selecteren", + "searchZone": "Tijdzones zoeken...", + "noZone": "Geen tijdzone gevonden.", + "scrubLabel": "Tijd verschuiven", + "reset": "Opnieuw instellen", + "targetsLabel": "Doeltijdzones", + "addZone": "Zone toevoegen", + "remove": "Verwijderen", + "localBadge": "Lokaal", + "invalidDate": "Voer een geldige datum en tijd in.", + "bands": { + "work": "Werktijden (09:00–18:00)", + "edge": "Randuren (vroeg / avond)", + "night": "Nacht" + } + }, + "TokenCounter": { + "title": "LLM-token-teller", + "subtitle": "Tel tokens, visualiseer chunks en schat de API-kosten.", + "inputLabel": "Tekst", + "sample": "Voorbeeld laden", + "clear": "Wissen", + "placeholder": "Plak tekst, code of JSON om tokens te tellen...", + "encoding": "Codering", + "loading": "Tokenizer laden...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-serie)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Tekens", + "words": "Woorden", + "ratio": "Tokens/woord", + "ratioSub": "ong." + }, + "claudeRow": "Claude (bij benadering)", + "estimate": "schatting", + "viz": { + "title": "Token-visualisatie", + "toggle": "Tonen", + "hint": "Schakel in om elk token als een chunk in te kleuren.", + "empty": "Voer tekst in om tokens te visualiseren.", + "truncated": "De eerste {limit} van {total} tokens worden getoond." + }, + "cost": { + "title": "Kostencalculator", + "disclaimer": "bewerkbare standaardwaarden — controleer de actuele prijzen", + "model": "Model", + "expectedOutput": "Verwachte uitvoertokens", + "customInput": "Invoer $/MTok", + "customOutput": "Uitvoer $/MTok", + "inputCost": "Invoerkosten", + "outputCost": "Uitvoerkosten", + "total": "Totaal", + "per1000": "Per 1000 aanroepen" + } + }, + "WebhookTester": { + "title": "Webhook-tester", + "subtitle": "Maak een tijdelijke URL en inspecteer inkomende webhook-verzoeken live.", + "intro": { + "title": "Maak een webhook-URL om verzoeken te gaan vastleggen", + "hint": "Je krijgt een niet te raden URL die elk inkomend verzoek registreert. Deze verloopt na 24 uur." + }, + "createBtn": "Webhook-URL maken", + "creating": "Bezig met maken…", + "urlLabel": "Jouw webhook-URL", + "expiresIn": "Verloopt over {countdown}", + "copyUrlBtn": "URL kopiëren", + "copied": "Gekopieerd", + "clearBtn": "Wissen", + "newBinBtn": "Nieuwe URL", + "requestsTitle": "Verzoeken", + "selectPrompt": "Selecteer een verzoek om de details te inspecteren.", + "expiredNotice": "Deze webhook-URL is verlopen. Maak een nieuwe om verder te testen.", + "base64Badge": "base64", + "truncatedBadge": "afgekapt", + "empty": { + "title": "Wachten op verzoeken…", + "hint": "Stuur een verzoek naar je URL en het verschijnt hier onmiddellijk. Probeer dit:" + }, + "detail": { + "receivedAt": "Ontvangen", + "source": "Bron-IP", + "contentType": "Content-Type", + "query": "Query-parameters", + "headers": "Headers", + "body": "Body", + "copyBody": "Body kopiëren", + "emptyBody": "Geen body" + }, + "errors": { + "createFailed": "Webhook-URL maken mislukt. Probeer het opnieuw.", + "clearFailed": "Verzoeken wissen mislukt. Probeer het opnieuw." + } + }, + "WebsocketTester": { + "title": "WebSocket-tester", + "subtitle": "Verbind met een WebSocket-endpoint en verzend, ontvang en inspecteer berichten live", + "urlLabel": "Endpoint-URL", + "protocolsLabel": "Subprotocollen", + "protocolsPlaceholder": "optioneel, gescheiden door komma's", + "connect": "Verbinden", + "disconnect": "Verbinding verbreken", + "status": { + "disconnected": "Verbinding verbroken", + "connecting": "Verbinden", + "connected": "Verbonden", + "error": "Fout" + }, + "messages": { + "connecting": "Verbinden met {url}…", + "connected": "Verbinding tot stand gebracht", + "connectedWithProtocol": "Verbinding tot stand gebracht (protocol: {protocol})", + "invalidUrl": "Ongeldige URL — moet beginnen met ws:// of wss://", + "connectFailed": "Verbinding openen mislukt", + "socketError": "Er is een verbindingsfout opgetreden", + "notConnected": "Niet verbonden — verbind eerst", + "binaryMessage": "Een binair bericht ontvangen" + }, + "log": { + "title": "Berichtenlogboek", + "empty": "Nog geen berichten. Verbind en begin met verzenden.", + "copyAll": "Alles kopiëren", + "download": "Downloaden als .txt", + "clear": "Logboek wissen", + "prettyPrint": "JSON opmaken", + "showRaw": "Ruwe tekst tonen" + }, + "composer": { + "placeholder": "Typ een bericht om te verzenden…", + "send": "Verzenden", + "formatJson": "Opmaken als JSON", + "invalidJson": "Het bericht is geen geldige JSON", + "hint": "Cmd/Ctrl+Enter om te verzenden" + }, + "ping": { + "label": "Auto-ping", + "seconds": "sec", + "intervalTitle": "Ping-interval in seconden", + "payloadPlaceholder": "ping-payload" + } + }, + "WhoisLookup": { + "title": "Whois-opzoeking", + "subtitle": "Zoek registratiegegevens van elk domein of IP-adres op via RDAP", + "placeholder": "example.com of 8.8.8.8", + "lookupBtn": "Opzoeken", + "looking": "Bezig met opzoeken…", + "refreshBtn": "Vernieuwen", + "tryLabel": "Probeer:", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "notAvailable": "Niet beschikbaar", + "summary": { + "registrar": "Registrar", + "network": "Netwerk" + }, + "dates": { + "created": "Aangemaakt", + "updated": "Bijgewerkt", + "expires": "Verloopt" + }, + "sections": { + "status": "Status", + "nameservers": "Nameservers", + "contacts": "Contacten", + "rawRdap": "Ruwe RDAP" + }, + "contacts": { + "roles": "Rollen", + "name": "Naam", + "organization": "Organisatie" + }, + "errors": { + "invalidTarget": "Voer een geldige domeinnaam of IP-adres in", + "lookupFailed": "Opzoeken mislukt (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/pl.json b/apps/desktop-ui/messages/pl.json new file mode 100644 index 00000000..2eb8eccb --- /dev/null +++ b/apps/desktop-ui/messages/pl.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Dashboard", + "settings": "Settings", + "apiClient": "API Client", + "httpStatusCodes": "Kody statusu HTTP", + "passwordManager": "Password Manager", + "environmentManager": "Menedżer środowisk", + "bookmarks": "Bookmarks", + "jsonFormatter": "Formatowanie JSON", + "emailValidator": "Email Validator", + "nosqlExplorer": "Eksplorator baz danych", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Piaskownica szyfrowania", + "jwtDecoder": "Dekoder JWT", + "certificatePemDecoder": "Dekoder certyfikatu / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Formatter GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Parser URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Sekret / klucz API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Sprawdzanie kontrastu", + "jsonSchemaGenerator": "Generator schematu JSON", + "imageToBase64": "Obraz do Base64", + "imageCompressor": "Kompresor obrazów", + "cssGradientBuilder": "Kreator gradientów CSS", + "gitignoreGenerator": "Generator .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Fragmenty kodu", + "qrCodeGenerator": "Generator kodów QR", + "dnsLookup": "Wyszukiwanie DNS", + "ipSubnetCalculator": "Kalkulator IP / podsieci", + "hashGenerator": "Generator skrótów", + "hmacGenerator": "Generator HMAC", + "totpGenerator": "Kod TOTP / 2FA", + "markdownPreview": "Podgląd Markdown", + "numberBaseConverter": "Konwerter systemów", + "formatConverter": "Konwerter formatów", + "mockDataGenerator": "Generator danych testowych", + "svgOptimizer": "Optymalizator SVG", + "unitConverter": "Konwerter jednostek", + "dockerComposeGenerator": "Generator Docker Compose", + "breakRoom": "Pomieszczenie socjalne", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Formater YAML", + "sshKeyGenerator": "Generator kluczy SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Kalkulator Chmod", + "codeScreenshot": "Zrzut kodu", + "cssGenerators": "Generatory CSS", + "exifViewer": "Podgląd EXIF", + "faviconGenerator": "Generator favicon", + "jsonDiff": "Porównanie JSON", + "jsonpathPlayground": "Piaskownica JSONPath", + "keycodeInspector": "Inspektor klawiszy", + "markdownTableGenerator": "Tabela Markdown", + "timezoneConverter": "Konwerter stref czasowych", + "tokenCounter": "Licznik tokenów", + "webhookTester": "Tester webhooków", + "websocketTester": "Tester WebSocket", + "whoisLookup": "Zapytanie Whois" + }, + "Help": { + "title": "Pomoc i dokumentacja", + "subtitle": "Jak działa MyDevTools, co robi każda aplikacja oraz jak bezpieczeństwo i szyfrowanie chronią Twoje dane.", + "tabs": { + "overview": "Przegląd", + "apps": "Aplikacje", + "security": "Bezpieczeństwo", + "tips": "Wskazówki" + }, + "overview": { + "whatIs": { + "title": "Czym jest MyDevTools?", + "description": "Środowisko dla programistów z logowaniem: dashboard i zestaw narzędzi na co dzień.", + "p1": "Po zalogowaniu aplikacja ustanawia sesję z API MyDevTools, dzięki czemu narzędzia mogą wczytywać i zapisywać Twoje dane. Pasek boczny pokazuje wszystkie aplikacje; nieużywane narzędzia możesz ukryć w", + "settingsLink": "Settings", + "p1After": ".", + "p2": "Niektóre funkcje używają hasła głównego (lub globalnego klucza sejfu), aby sekrety takie jak hasła i connection stringi były szyfrowane na Twoim urządzeniu zanim trafią na serwer." + }, + "quickLinks": { + "title": "Szybkie linki", + "dashboard": "Dashboard", + "settings": "Settings" + } + }, + "apps": { + "intro": "Otwórz dowolne narzędzie z paska bocznego lub dashboardu. Poniżej krótki opis każdej aplikacji.", + "open": "Otwórz", + "howItWorks": "Jak to działa", + "generic": "Korzystaj z tego narzędzia z paska bocznego w jego dedykowanym workflow." + }, + "security": { + "session": { + "title": "Logowanie i sesja API", + "description": "Tożsamość i sposób, w jaki przeglądarka komunikuje się z backendem.", + "items": { + "authentication": "Uwierzytelnianie korzysta z Firebase. Po zalogowaniu aplikacja wymienia Firebase ID token na ciasteczka API.", + "accessTokens": "Tokeny dostępu to podpisane na serwerze JWT (HS256), które potwierdzają użytkownika wywołującego API.", + "refreshTokens": "Tokeny odświeżania są przechowywane w HttpOnly cookies; serwer trzyma hash SHA-256 tokenu, a nie sam token." + } + }, + "encryption": { + "title": "Szyfrowanie, które warto znać", + "description": "Kryptografia po stronie klienta dla sejfu i wrażliwych danych połączeń.", + "p1": "Dla menedżera haseł, menedżera środowisk i zapisanych połączeń w NoSQL Explorer aplikacja webowa używa Web Crypto API przeglądarki:", + "items": { + "kdf": "PBKDF2 z SHA-256, 100 000 iteracji i losową solą wyprowadza 256-bitowy klucz z hasła głównego.", + "aes": "AES-256-GCM szyfruje dane; dla każdego szyfrowania używany jest losowy 12-bajtowy IV.", + "scope": "Wyprowadzony klucz pozostaje w przeglądarce (nie da się go wyeksportować). Serwer otrzymuje tylko szyfrogram i IV, nie hasło główne ani sekrety w postaci jawnej." + }, + "warning": "Jeśli zapomnisz hasła głównego, zaszyfrowanych danych nie da się odzyskać. Przechowuj je bezpiecznie." + }, + "logout": { + "title": "Wylogowanie i dane lokalne", + "body": "Wylogowanie czyści sekrety z pamięci, stan sejfu i (jeśli dotyczy) materiał klucza głównego z IndexedDB oraz kończy sesję API. Dane zapisane tylko w local storage mogą pozostać, dopóki nie wyczyścisz ich w przeglądarce." + } + }, + "tips": { + "title": "Bezpieczne korzystanie z MyDevTools", + "items": { + "masterPassword": "Używaj silnego, unikalnego hasła głównego dla funkcji sejfu.", + "apiClient": "Traktuj API Client jak każdy klient HTTP: nie wklejaj sekretów produkcyjnych do współdzielonych środowisk, jeśli nie wiesz, kto może je zobaczyć.", + "nosql": "Eksplorator baz danych łączy się z bazami, które konfigurujesz; używaj kont z minimalnymi uprawnieniami i reguł sieciowych, jeśli to możliwe.", + "json": "Formatowanie JSON działa lokalnie w przeglądarce—mimo to unikaj wklejania bardzo wrażliwych danych na współdzielonych urządzeniach.", + "settings": "Dostosuj widoczne narzędzia w Settings, aby pasek boczny był bardziej przejrzysty." + } + } + }, + "Settings": { + "title": "Settings", + "language": "Language", + "spanish": "Hiszpański", + "english": "Angielski", + "french": "Français", + "arabic": "عربى", + "catalan": "Kataloński", + "chineseSimplified": "Chiński (uproszczony)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Niemiecki", + "danish": "Duński", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Indonezyjski", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukraiński" + }, + "SettingsPage": { + "title": "Settings", + "subtitle": "Manage your account settings and preferences.", + "userProfile": { + "title": "User Profile", + "description": "Your personal information and account details.", + "avatarAlt": "User", + "anonymousUser": "Anonymous User", + "noEmailProvided": "No email provided", + "notLoggedIn": "Not logged in." + }, + "tools": { + "title": "Tools & Features", + "description": "Choose which tools appear in your navigation sidebar." + }, + "appearance": { + "title": "Appearance", + "description": "Customize how the application looks on your device.", + "themePreference": "Theme Preference", + "themes": { + "light": "Light", + "dark": "Dark", + "system": "System" + } + }, + "language": { + "title": "Language", + "description": "Set the language used across the platform.", + "selectLanguage": "Select Language", + "selectPlaceholder": "Select a language", + "helpText": "Changing your language will update menus and labels across the platform.", + "languages": { + "en": "English (angielski)", + "fr": "Français", + "es": "Español (hiszpański)", + "id": "Bahasa Indonesia (indonezyjski)", + "it": "Włoski (Italiano)", + "nb": "Norsk", + "ar": "عربى (Arabic)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (grecki)", + "de": "Deutsch (niemiecki)", + "da": "Dansk (duński)", + "af": "Afrikaans", + "fa": "فارسی", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Good morning", + "afternoon": "Good afternoon", + "evening": "Good evening" + }, + "commaName": ", {name}", + "welcomeBack": "Welcome back", + "welcomeBackNamed": "Welcome back, {name}", + "tagline": "What would you like to build today?", + "brandName": "MyDevTools", + "stats": { + "tools": "Tools", + "favorites": "Favorites", + "pinned": "Pinned", + "recent": "Recently Used" + }, + "sections": { + "recentlyUsed": "Recently Used", + "favorites": "Favorites", + "whatsNew": "What's New", + "pinned": "Pinned" + }, + "groups": { + "apps": "Apps" + }, + "toolCard": { + "defaultDescription": "Explore this tool for better functionality.", + "launchTool": "Launch Tool" + }, + "tools": { + "toDo": { + "title": "Tasks", + "description": "Manage your to-do lists efficiently." + }, + "notes": { + "title": "Notes", + "description": "Capture ideas and organize your thoughts." + }, + "passwordManager": { + "title": "Password Manager", + "description": "Securely store and manage your passwords." + }, + "environmentManager": { + "title": "Menedżer środowisk", + "description": "Przechowuj zmienne w stylu .env według projektu i środowiska z tagami." + }, + "bookmarks": { + "title": "Bookmarks", + "description": "Manage and organize your bookmarks." + }, + "apiClient": { + "title": "API Client", + "description": "Test and debug HTTP requests." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Explore MongoDB and SQL databases." + }, + "emailValidator": { + "title": "Email Validator", + "description": "Verify and validate email addresses." + }, + "encryptionPlayground": { + "title": "Piaskownica szyfrowania", + "description": "AES-GCM z surowym kluczem lub hasłem; pakiety JSON, Web Crypto tylko w przeglądarce." + }, + "jwtDecoder": { + "title": "Dekoder JWT", + "description": "Dekoduj nagłówek, payload i wygaśnięcie JWT." + }, + "certificatePemDecoder": { + "title": "Dekoder certyfikatu / PEM", + "description": "Dekoduj certyfikaty X.509 i CSR: podmiot, wystawca, ważność i SAN." + }, + "sshKeyGenerator": { + "title": "Generator kluczy SSH / RSA", + "description": "Generuj pary kluczy Ed25519 lub RSA w przeglądarce. Pobierz w formatach OpenSSH i PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Formatter GraphQL", + "description": "Formatuj GraphQL z podświetlaniem składni i prostym edytorem zapytań." + }, + "yamlFormatter": { + "title": "Formatyzator YAML", + "description": "Formatuj, weryfikuj i konwertuj YAML do JSON w przeglądarce." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Parser URL", + "description": "Podziel adres URL na protokół, host, ścieżkę, parametry zapytania i hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Sekret / klucz API", + "description": "Kryptograficznie losowe ciągi z konfigurowalnym alfabetem, długością i eksportem zbiorczym." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Sprawdzanie kontrastu", + "description": "Współczynnik kontrastu WCAG 2.1 tekstu i tła (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Generator schematu JSON", + "description": "Wywnioskuj schemat JSON i modele typowane z przykładowego JSON." + }, + "imageToBase64": { + "title": "Obraz do Base64", + "description": "Konwertuj obrazy na URI danych lub surowe ciągi Base64." + }, + "imageCompressor": { + "title": "Kompresor obrazów", + "description": "Kompresuj JPEG, PNG i WebP lokalnie suwakiem jakości." + }, + "cssGradientBuilder": { + "title": "Kreator gradientów CSS", + "description": "Wizualny kreator gradientów CSS z kontrolą kąta i przystankami kolorów." + }, + "gitignoreGenerator": { + "title": "Generator .gitignore", + "description": "Natychmiast generuj scalone pliki .gitignore z ponad 500 stosów technologii." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Konwertuj pliki CSV lub Excel na JSON i eksportuj z powrotem do CSV lub XLSX." + }, + "snippetManager": { + "title": "Fragmenty kodu", + "description": "Zapisuj fragmenty z podświetlaniem składni, autowykrywaniem języka i formatowaniem." + }, + "qrCodeGenerator": { + "title": "Generator kodów QR", + "description": "Twórz kody QR PNG z tekstu lub adresu URL w przeglądarce." + }, + "ipSubnetCalculator": { + "title": "Kalkulator IP / podsieci", + "description": "IPv4 i IPv6 CIDR: maska, zakresy i liczba adresów." + }, + "dnsLookup": { + "title": "Wyszukiwanie DNS", + "description": "Zapytaj rekordy DNS dla dowolnej domeny — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Generator skrótów", + "description": "MD5, SHA-1 i SHA-2 dla tekstu lub plików." + }, + "hmacGenerator": { + "title": "Generator HMAC", + "description": "Podpisuj webhooki i testuj integracje API za pomocą HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Generator kodu TOTP / 2FA", + "description": "Wklej tajny klucz Base32 z aplikacji uwierzytelniającej; sześciocyfrowy kod odświeża się co 30 s—wygodne przy testach logowania i MFA." + }, + "markdownPreview": { + "title": "Podgląd Markdown", + "description": "Podgląd Markdown na żywo z eksportem HTML i konwersją HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Konwerter podstaw liczbowych", + "description": "Konwertuj liczby całkowite między podstawami 2–36." + }, + "formatConverter": { + "title": "Konwerter formatów", + "description": "Konwertuj między JSON, YAML, TOML i XML w dowolnym kierunku. Obsługiwane jest 12 kombinacji." + }, + "mockDataGenerator": { + "title": "Generator danych testowych", + "description": "Zdefiniuj kolumny, wybierz typy i eksportuj zestawy danych do seedów, Postmana lub testów. Do 5000 wierszy, wszystko w przeglądarce — nic nie jest wysyłane." + }, + "httpStatusCodes": { + "title": "Kody statusu HTTP", + "description": "Wyszukuj kody statusu HTTP z opisami (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Optymalizator SVG", + "description": "Wklej SVG, usuń zbędne elementy przez SVGO i zobacz redukcję rozmiaru UTF-8." + }, + "unitConverter": { + "title": "Konwerter jednostek", + "description": "Przeliczaj między 323 jednostkami w 43 kategoriach naukowych i technicznych." + }, + "dockerComposeGenerator": { + "title": "Generator Docker Compose", + "description": "Wybierz bazy danych, pamięć podręczną, kolejki, proxy i obserwowalność; eksportuj docker-compose.yml dla lokalnych stacków." + }, + "game2048": { + "title": "2048", + "description": "Przesuń i połącz płytki, aby osiągnąć 2048 rok. Klasyczna gra logiczna z liczbami." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klasyczna łamigłówka z numerem. 3 poziomy trudności, po 150 etapów." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Przeglądaj klucze, sprawdzaj wartości i uruchamiaj polecenia na dowolnej instancji Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Generator i weryfikator Bcrypt", + "description": "Haszuj hasła za pomocą bcrypt i weryfikuj istniejące hasze." + }, + "chmodCalculator": { + "title": "Kalkulator Chmod", + "description": "Konwertuj uprawnienia Unix między postacią ósemkową, symboliczną i polami wyboru." + }, + "codeScreenshot": { + "title": "Zrzut kodu", + "description": "Zamień kod źródłowy w piękne obrazy gotowe do udostępnienia." + }, + "cssGenerators": { + "title": "Generatory CSS", + "description": "Generatory box-shadow, border-radius i płynnego clamp()." + }, + "exifViewer": { + "title": "Podgląd i usuwanie EXIF", + "description": "Sprawdzaj i usuwaj metadane EXIF ze zdjęć lokalnie." + }, + "faviconGenerator": { + "title": "Generator favicon", + "description": "Generuj favicony, ikony aplikacji i fragmenty manifestu." + }, + "jsonDiff": { + "title": "Porównanie JSON", + "description": "Semantyczne, uwzględniające strukturę porównanie dwóch dokumentów JSON." + }, + "jsonpathPlayground": { + "title": "Piaskownica JSONPath", + "description": "Odpytuj JSON za pomocą JSONPath lub JMESPath i oglądaj wyniki na żywo." + }, + "keycodeInspector": { + "title": "Inspektor klawiszy", + "description": "Naciśnij klawisz, aby zobaczyć jego kody zdarzenia JavaScript i modyfikatory." + }, + "markdownTableGenerator": { + "title": "Generator tabel Markdown", + "description": "Twórz tabele Markdown i HTML w edytorze siatki na wzór arkusza." + }, + "timezoneConverter": { + "title": "Konwerter stref czasowych", + "description": "Konwertuj czas między wieloma strefami z podpowiedziami o zmianie dnia i godzinach pracy." + }, + "tokenCounter": { + "title": "Licznik tokenów LLM", + "description": "Licz tokeny LLM, wizualizuj chunki i szacuj koszt API." + }, + "webhookTester": { + "title": "Tester webhooków", + "description": "Przechwytuj i sprawdzaj przychodzące żądania webhooków w czasie rzeczywistym." + }, + "websocketTester": { + "title": "Tester WebSocket", + "description": "Łącz się z endpointami WebSocket i wymieniaj wiadomości w czasie rzeczywistym." + }, + "whoisLookup": { + "title": "Zapytanie Whois", + "description": "Sprawdzaj dane rejestracyjne domen i adresów IP przez RDAP." + } + }, + "tabs": { + "apps": "Aplikacje", + "analytics": "Analityka", + "ui": "Interfejs" + }, + "analytics": { + "title": "Twoje dane w skrócie", + "subtitle": "Liczby zsynchronizowane z kontem na serwerze.", + "loadError": "Nie udało się wczytać statystyk.", + "retry": "Ponów", + "loginHint": "Zaloguj się, aby zobaczyć analitykę i preferencje interfejsu.", + "passwordEntries": "Wpisy haseł", + "bookmarks": "Zakładki", + "bookmarkFolders": "Foldery zakładek", + "tasksTotal": "Zadania", + "tasksCompleted": "Ukończone", + "tasksOngoing": "W toku", + "tasksNotStarted": "Nierozpoczęte", + "projects": "Projekty", + "nosqlConnections": "Połączenia z BD", + "notes": "Notatki", + "apiClientCollections": "Kolekcje API", + "apiClientEnvironments": "Środowiska API", + "apiClientHistory": "Wpisy historii API", + "jsonFormatterDocuments": "Dokumenty formatu JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Fragmenty kodu", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Preferencje interfejsu", + "subtitle": "Motyw, język i widoczne narzędzia na pasku bocznym — z ustawień konta.", + "loadError": "Nie udało się wczytać preferencji.", + "loginHint": "Zaloguj się, aby zobaczyć zapisane preferencje interfejsu.", + "theme": "Motyw", + "accent": "Kolor akcentu", + "locale": "Język", + "enabledTools": "Widoczne narzędzia", + "favoriteTools": "Ulubione narzędzia", + "toolsWithUsage": "Narzędzia ze statystyką użycia" + }, + "signIn": "Zaloguj się", + "noResults": "Żadne narzędzie nie pasuje do Twojego wyszukiwania", + "noResultsHint": "Spróbuj innego słowa kluczowego lub wyczyść filtr.", + "filterAll": "Wszystkie", + "loginCta": { + "description": "Zaloguj się, aby śledzić ostatnio używane narzędzia." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Loading...", + "myTasksTitle": "My Tasks", + "statsLine": "{total} tasks • {percent}% complete", + "statsLineDesktop": "{total} total • {percent}% complete" + }, + "filters": { + "all": "All", + "total": "Total", + "allProjects": "All Projects", + "export": "Export", + "kanban": "Kanban", + "list": "List", + "searchPlaceholder": "Search tasks...", + "searchAria": "Search tasks", + "clearSearchAria": "Clear search", + "foundTasks": "Found {count} {count, plural, one {task} other {tasks}}", + "filterByAllAria": "Filter by All", + "filterByStatusAria": "Filter by {status}" + }, + "status": { + "not-started": { + "label": "Not Started", + "description": "Tasks that haven't been started yet" + }, + "ongoing": { + "label": "Ongoing", + "description": "Tasks currently in progress" + }, + "completed": { + "label": "Completed", + "description": "Tasks that are finished" + } + }, + "priorities": { + "high": "High", + "medium": "Medium", + "low": "Low" + }, + "form": { + "addNewTaskLabel": "Add new task", + "multilinePlaceholder": "Add task details... (Shift+Enter for new line)", + "singlelinePlaceholder": "What needs to be done? Press Enter to add", + "taskInputAria": "Task input", + "projectPlaceholder": "Project", + "selectProject": "Select Project", + "errorSelectProject": "Please select a project or create a new one to add a task.", + "hintMultiline": "Shift+Enter for new line, Enter to add", + "hintSingleline": "Enter to add", + "shortcutsAria": "Keyboard shortcuts", + "shortcutsLabel": "Shortcuts", + "shortcutAddTask": "Add task", + "shortcutNewLine": "New line", + "shortcutClear": "Clear", + "addButton": "Add" + }, + "list": { + "ariaLabel": "Task list", + "loading": "Loading your tasks...", + "emptyTitle": "No tasks found", + "emptyPrimary": "Get started by adding your first task above! Press Enter to quickly add tasks.", + "emptySecondary": "Try adjusting your filters or search query." + }, + "mobileDrawer": { + "addTaskSrOnly": "Add Task", + "addNewTaskTitle": "Add New Task", + "addNewTaskDescription": "Create a new task to track your progress.", + "cancel": "Cancel" + }, + "kanban": { + "ariaLabel": "Kanban board", + "loading": "Loading your tasks...", + "dropHere": "Drop task here", + "noTasksYet": "No tasks yet", + "emptyColumnAria": "Empty column", + "columnAria": "{title} column with {count} tasks", + "countInColumnAria": "{count} tasks in {title}" + }, + "taskItem": { + "confirmDelete": "Are you sure you want to delete this task?", + "statusPlaceholder": "Status", + "changeStatusAria": "Change task status", + "taskOptionsAria": "Task options", + "editTask": "Edit Task", + "copyTask": "Copy Task", + "copied": "Copied!", + "deleteTask": "Delete Task", + "statusHeading": "Status", + "editTaskAria": "Edit task" + }, + "editDialog": { + "title": "Edit Task", + "description": "Update task details, add subtasks, set priorities and more.", + "taskTitleLabel": "Task Title *", + "taskTitlePlaceholder": "What needs to be done?", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Add more details about this task...", + "statusLabel": "Status", + "priorityLabel": "Priority", + "dueDateLabel": "Due Date", + "pickDate": "Pick a date", + "clearDate": "Clear Date", + "projectLabel": "Project", + "selectProjectPlaceholder": "Select a project", + "noProject": "No Project", + "timeEstimateLabel": "Time Estimate (minutes)", + "timeEstimatePlaceholder": "How long will this take?", + "tagsLabel": "Tags", + "customTagNamePlaceholder": "Custom tag name", + "subtasksLabel": "Subtasks", + "addSubtaskPlaceholder": "Add a subtask...", + "cancel": "Cancel", + "saving": "Saving...", + "saveChanges": "Save Changes" + }, + "export": { + "title": "Export Tasks", + "description": "Export your tasks to JSON, CSV, or Excel formats.", + "projectLabel": "Project", + "statusLabel": "Status", + "tagLabel": "Tag", + "allProjects": "All Projects", + "allStatuses": "All Statuses", + "allTags": "All Tags", + "exportAsJsonTitle": "Export as JSON", + "exportAsJsonDescription": "Export task data including subtasks, tags, and metadata.", + "exportJson": "Export JSON", + "exportAsCsvExcelTitle": "Export as CSV / Excel", + "exportAsCsvExcelDescription": "Export tasks for spreadsheet applications.", + "exportCsv": "Export CSV", + "exportExcel": "Export Excel", + "close": "Close", + "toastNoTasks": "No tasks to export with current filters", + "toastExportedJson": "Exported {count} tasks to JSON!", + "toastExportedCsv": "Exported {count} tasks to CSV!", + "toastExportedExcel": "Exported {count} tasks to Excel!", + "toastFailedJson": "Failed to export to JSON", + "toastFailedCsv": "Failed to export to CSV", + "toastFailedExcel": "Failed to export to Excel", + "unknownProject": "Unknown Project" + }, + "ack": { + "taskAddedTitle": "Task added successfully", + "taskAddedFailedTitle": "Failed to add task", + "taskUpdatedTitle": "Task updated successfully", + "taskUpdatedFailedTitle": "Failed to update task", + "taskStatusUpdateFailedTitle": "Failed to update task status", + "taskMovedTitle": "Task moved to {status}", + "tryAgain": "Please try again.", + "taskDeletedTitle": "Task \"{text}\" deleted", + "undo": "Undo", + "taskRestoredTitle": "Task restored", + "taskRestoredSuccessTitle": "Task restored successfully", + "taskRestoreFailedTitle": "Failed to restore task. Please try again.", + "taskDeleteFailedTitle": "Failed to delete task. Please try again.", + "tasksImportedTitle": "Successfully imported {count} {count, plural, one {task} other {tasks}}!", + "tasksImportFailedTitle": "Failed to import tasks. Please try again.", + "unknownProject": "Unknown Project", + "projectCreatedTitle": "Project created successfully", + "projectCreateFailedTitle": "Failed to create project", + "projectUpdatedTitle": "Project updated successfully", + "projectUpdateFailedTitle": "Failed to update project", + "projectDeletedTitle": "Project deleted successfully", + "projectDeleteFailedTitle": "Failed to delete project" + } + }, + "Notes": { + "page": { + "loading": "Loading..." + }, + "layout": { + "mobileNavTitle": "Notes Navigation", + "openMenuAria": "Open notes menu" + }, + "sidebar": { + "title": "Notes", + "createNoteAria": "Create note", + "searchPlaceholder": "Search notes...", + "loading": "Loading...", + "noNotesFound": "No notes found.", + "noNotesYet": "No notes yet. Click + to create one.", + "inParent": "in {parentTitle}", + "untitled": "Untitled", + "addSubPage": "Add sub-page", + "delete": "Delete", + "deleteConfirmTitle": "Delete note?", + "deleteConfirmDescription": "Are you sure you want to delete \"{title}\"? This action cannot be undone.", + "cancel": "Cancel", + "confirmDelete": "Delete", + "noteOptionsAria": "Note options" + }, + "editor": { + "emptyState": "Select a note or create a new one to get started.", + "titlePlaceholder": "Untitled", + "saving": "Zapisywanie…", + "ready": "Gotowe", + "saved": "Zapisano {time}", + "stats": "{words} słów · {read} min czytania", + "template": "Szablon", + "applyTemplate": "Zastosuj szablon", + "exportMdLabel": ".md", + "exportMd": "Eksportuj jako Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Eksportuj jako HTML", + "focusMode": "Tryb skupienia", + "exitFocusMode": "Wyjdź z trybu skupienia", + "save": "Zapisz", + "addTag": "Dodaj tag…", + "removeTag": "Usuń tag {tag}", + "loading": "Ładowanie…", + "searchHint": "aby wyszukać", + "focusHint": "tryb skupienia", + "notePath": "Ścieżka notatki" + }, + "context": { + "authRequiredError": "User not authenticated", + "defaultTitle": "Untitled", + "cloudSyncImageError": "Włącz synchronizację w chmurze, aby przechowywać obrazy notatek. Notatki tekstowe pozostają lokalne." + } + }, + "PasswordManager": { + "page": { + "loading": "Loading...", + "title": "Password Manager", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Failed to decrypt passwords", + "toastLoadFailed": "Failed to load passwords", + "toastUnlockedAuto": "Vault unlocked automatically", + "toastAutoUnlockFailed": "Auto-unlock failed. Please unlock manually.", + "toastConnectFailed": "Failed to connect to server", + "toastAutoUnlockWarning": "Vault unlocked, but auto-unlock may not work on reload", + "errorIncorrectPassword": "Incorrect master password", + "errorUnlockFailed": "Failed to unlock vault", + "errorPasswordsMismatch": "Passwords do not match", + "errorPasswordTooShort": "Password must be at least 8 characters", + "errorSetupFailed": "Failed to set up vault", + "errorConnectFailed": "Failed to connect to server.", + "mobileTitleCreate": "Create your vault", + "mobileTitleWelcome": "Welcome back", + "mobileSubtitleCreate": "Set a master password to protect your credentials", + "mobileSubtitleUnlock": "Enter your master password to unlock", + "placeholderMasterPassword": "Master password", + "placeholderConfirmPassword": "Confirm password", + "labelMasterPassword": "Master password", + "labelConfirmPassword": "Confirm password", + "placeholderEnterMaster": "Enter master password", + "placeholderConfirmMaster": "Confirm master password", + "buttonCreateVault": "Create vault", + "buttonUnlock": "Unlock", + "buttonUnlockVault": "Unlock vault", + "footerCreate": "Your password is never stored and cannot be recovered", + "footerUnlock": "Secured with end-to-end encryption", + "desktopTitleSetup": "Set up password vault", + "desktopTitleUnlock": "Unlock vault", + "desktopDescriptionSetup": "Create a master password to secure your credentials. This password is never stored and cannot be recovered.", + "desktopDescriptionUnlock": "Enter your master password to access your secured credentials." + }, + "list": { + "loading": "Loading your passwords...", + "emptyTitle": "Vault is empty", + "emptyHintMobile": "Tap the + button to add your first password", + "emptyHintDesktop": "You have not stored any passwords yet. Click the \"Add password\" button above to get started.", + "addPassword": "Add password", + "searchPlaceholderMobile": "Search your vault...", + "searchPlaceholderDesktop": "Search vault...", + "importExport": "Import / export", + "lockVault": "Lock vault", + "allPasswordsTitle": "All passwords", + "passwordCount": "{count, plural, one {# password} other {# passwords}}", + "vaultHealthDrawerTitle": "Vault health", + "noResults": "No passwords found matching \"{query}\"", + "tableService": "Service", + "tableUsername": "Username", + "tablePassword": "Password", + "tableUrl": "URL", + "tableActions": "Actions", + "gridViewAria": "Grid view", + "listViewAria": "List view", + "passwordStrengthTitle": "Password strength", + "deleteDialogTitle": "Delete password", + "deleteDialogDescription": "Are you sure you want to delete this password? This action cannot be undone.", + "cancel": "Cancel", + "delete": "Delete", + "edit": "Edit" + }, + "toasts": { + "copiedPassword": "Password copied to clipboard", + "copiedUsername": "Username copied to clipboard", + "passwordDeleted": "Password deleted", + "deleteFailed": "Failed to delete password", + "vaultLocked": "Vault locked", + "importedCount": "Successfully imported {count, plural, one {# password} other {# passwords}}" + }, + "form": { + "addTitle": "Add new password", + "editTitle": "Edit password", + "serviceName": "Service name", + "usernameEmail": "Username / email", + "password": "Password", + "urlOptional": "Website URL (optional)", + "notesOptional": "Notes (optional)", + "tags": "Tags", + "placeholderService": "e.g. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Enter or generate password", + "placeholderUrl": "https://...", + "placeholderNotes": "Additional details...", + "placeholderTags": "Add tags (press Enter or comma)", + "strength": "Strength", + "strengthWeak": "Weak", + "strengthMedium": "Medium", + "strengthStrong": "Strong", + "advancedGenerator": "Advanced generator", + "cancel": "Cancel", + "saving": "Saving...", + "savePassword": "Save password", + "updating": "Updating...", + "updatePassword": "Update password", + "addPasswordSrOnly": "Add password", + "triggerAdd": "Add password", + "toastSaved": "Password saved successfully", + "toastSaveFailed": "Failed to save password", + "toastUpdated": "Password updated successfully", + "toastUpdateFailed": "Failed to update password" + }, + "security": { + "vaultHealth": "Vault health", + "statusLabel": "Status", + "totalPasswords": "Total passwords", + "activeEntries": "Active entries in your vault", + "protected": "Protected", + "weakPasswords": "Weak passwords", + "needingImprovement": "Passwords needing improvement", + "allGood": "All good", + "reusedPasswords": "Reused passwords", + "usedMultipleTimes": "Passwords used multiple times", + "unique": "Unique", + "total": "Total", + "weak": "Weak", + "reused": "Reused", + "totalCountLabel": "{count, plural, one {# password} other {# passwords}}", + "weakCountLabel": "{count} weak", + "allSecure": "All secure", + "scoreExcellent": "Excellent", + "scoreGood": "Good", + "scoreFair": "Fair", + "scoreCritical": "Critical" + }, + "importExport": { + "title": "Import / export vault", + "description": "Manage your password data. You can export your vault to JSON or import from a JSON file.", + "tabExport": "Export", + "tabImport": "Import", + "warningTitle": "Warning", + "warningBody": "Exported files contain your passwords in plain text. Keep this file safe and delete it after use.", + "exportBlurb": "Download your entire vault as a JSON file. This includes all services, usernames, passwords, and notes.", + "downloadJson": "Download JSON export", + "pasteLabel": "Paste JSON data", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Paste the content of a previously exported JSON file here.", + "importing": "Importing...", + "importPasswords": "Import passwords", + "trigger": "Import / export", + "toastExported": "Vault exported successfully", + "toastExportFailed": "Failed to export vault", + "toastImportFailed": "Failed to import: invalid JSON format" + }, + "card": { + "copyUsername": "Copy username", + "openUrl": "Open URL", + "strength": "Strength" + }, + "swipeable": { + "copy": "Copy", + "delete": "Delete", + "copyPassword": "Copy password", + "copyUsername": "Copy username", + "edit": "Edit", + "deleteAction": "Delete" + }, + "floatingMenu": { + "lockVault": "Lock vault", + "importExport": "Import / export", + "addPassword": "Add password" + }, + "generator": { + "tabPassword": "Password", + "tabPassphrase": "Passphrase", + "strength": "Strength", + "lengthLabel": "Length: {length} characters", + "lengthBadge": "{length} chars", + "characterSets": "Character sets", + "uppercase": "Uppercase", + "lowercase": "Lowercase", + "numbers": "Numbers", + "symbols": "Symbols", + "wordCountLabel": "Word count: {count}", + "wordCountBadge": "{count, plural, one {# word} other {# words}}", + "separator": "Separator", + "capitalization": "Capitalization", + "includeNumber": "Include number" + } + }, + "EnvironmentManager": { + "subtitle": "Zaszyfrowane zmienne środowiskowe według projektu i środowiska.", + "page": { + "loading": "Ładowanie…", + "title": "Menedżer środowisk", + "loadFailed": "Nie udało się wczytać zestawów środowisk" + }, + "list": { + "title": "Środowiska", + "loading": "Wczytywanie zestawów…", + "emptyTitle": "Brak zestawów środowisk", + "emptyHint": "Utwórz zestaw dla każdego projektu i środowiska (np. klucze API produkcji). Wszystko jest szyfrowane w przeglądarce przed wysłaniem.", + "searchPlaceholder": "Szukaj projektu, środowiska, tagów, notatek…", + "tagFilterPlaceholder": "Filtruj według tagu", + "variableCount": "{count, plural, one {# zmienna} few {# zmienne} other {# zmiennych}}", + "updatedAgo": "Zaktualizowano {time}", + "copyDotEnv": "Kopiuj .env", + "edit": "Edytuj", + "delete": "Usuń", + "cancel": "Anuluj", + "confirmDelete": "Usuń", + "deleteDialogTitle": "Usunąć zestaw środowisk?", + "deleteDialogDescription": "To usuwa zaszyfrowane dane z konta. Operacji nie cofniesz.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env skopiowano do schowka", + "deleted": "Zestaw środowisk usunięty", + "deleteFailed": "Nie udało się usunąć" + }, + "form": { + "addTitle": "Nowy zestaw środowisk", + "editTitle": "Edytuj zestaw środowisk", + "triggerAdd": "Dodaj zestaw środowisk", + "addSrOnly": "Dodaj zestaw środowisk", + "project": "Projekt", + "environment": "Środowisko", + "placeholderProject": "np. billing-api", + "placeholderEnvironment": "np. production", + "pasteEnvLabel": "Wklej .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Scal wklejone linie", + "variables": "Zmienne", + "addRow": "Dodaj wiersz", + "keyPlaceholder": "KLUCZ", + "valuePlaceholder": "wartość", + "notesOptional": "Notatki (opcjonalnie)", + "placeholderNotes": "Polityka rotacji, właściciel, linki…", + "tags": "Tagi", + "placeholderTags": "Dodaj tagi (Enter lub przecinek)", + "cancel": "Anuluj", + "save": "Zapisz", + "saving": "Zapisywanie…", + "update": "Aktualizuj", + "updating": "Aktualizowanie…", + "toastSaved": "Zestaw środowisk zapisany", + "toastSaveFailed": "Zapis nie powiódł się", + "toastUpdated": "Zestaw środowisk zaktualizowany", + "toastUpdateFailed": "Aktualizacja nie powiodła się", + "toastPasteEmpty": "Nie znaleziono linii KLUCZ=wartość", + "toastPasteApplied": "Scalono {count} zmiennych ze wklejki", + "toastProjectEnvRequired": "Projekt i środowisko są wymagane", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Loading..." + }, + "manager": { + "foldersHeading": "Folders", + "tagsHeading": "Tags", + "tagsMore": "+{count} more", + "tagsShowLess": "Show less", + "searchPlaceholder": "Search your bookmarks...", + "import": "Import", + "export": "Export", + "exportAsHtml": "Export as HTML", + "exportAsJson": "Export as JSON", + "addBookmark": "Add Bookmark", + "matchingQuery": "matching \"{query}\"", + "bookmarkCount": "{count, plural, one {# bookmark} other {# bookmarks}}", + "allBookmarks": "All Bookmarks", + "uncategorized": "Uncategorized", + "unknownFolder": "Unknown" + }, + "card": { + "urlCopied": "URL copied to clipboard", + "bookmarkDeleted": "Bookmark deleted", + "edit": "Edit", + "delete": "Delete", + "copyUrl": "Copy URL", + "deleteConfirmTitle": "Delete bookmark?", + "deleteConfirmDescription": "This will permanently delete \"{title}\".", + "cancel": "Cancel" + }, + "grid": { + "emptyTitle": "No bookmarks yet", + "emptyDescription": "Add your first bookmark or import from your browser to get started." + }, + "folderTree": { + "allBookmarks": "All Bookmarks", + "addSubfolder": "Add Subfolder", + "rename": "Rename", + "deleteFolderTitle": "Delete folder \"{name}\"?", + "deleteFolderDescription": "This will delete the folder and all its subfolders. Bookmarks in these folders will be moved to Uncategorized." + }, + "import": { + "title": "Import Bookmarks", + "description": "Import bookmarks from your browser's exported file.", + "parseError": "Failed to parse bookmark file. Please make sure it's a valid bookmark export.", + "successToast": "Imported {bookmarks} bookmarks and {folders} folders", + "errorToast": "Failed to import bookmarks", + "chromeInstruction": "Menu → Bookmarks → Bookmark Manager → ⋮ → Export Bookmarks", + "firefoxInstruction": "Menu → Bookmarks → Manage Bookmarks → Import and Backup → Export", + "safariInstruction": "File → Export Bookmarks...", + "edgeInstruction": "Menu → Favorites → ⋯ → Export Favorites", + "dropZoneTitle": "Drop your bookmark file here", + "dropZoneHint": "or click to browse (HTML or JSON)", + "selectedFileHint": "Click to select a different file", + "errorTitle": "Error", + "previewTitle": "Import Preview", + "previewBookmarks": "{count, plural, one {# bookmark} other {# bookmarks}}", + "previewFolders": "{count, plural, one {# folder} other {# folders}}", + "importModeLabel": "Import Mode", + "modeMerge": "Merge with existing bookmarks", + "modeReplace": "Replace all existing bookmarks", + "cancel": "Cancel", + "importButton": "Import", + "importButtonWithCount": "Import {count, plural, one {# bookmark} other {# bookmarks}}" + }, + "addBookmark": { + "titleAdd": "Add Bookmark", + "titleEdit": "Edit Bookmark", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Please enter a valid URL", + "titleLabel": "Title", + "titlePlaceholder": "Bookmark title", + "titleRequired": "Please enter a title", + "descriptionLabel": "Description (optional)", + "descriptionPlaceholder": "Add a note about this bookmark", + "folderLabel": "Folder", + "folderPlaceholder": "Select folder", + "noFolder": "No folder", + "tagsLabel": "Tags", + "tagsPlaceholder": "Add tags (press Enter)", + "toastUpdated": "Bookmark updated", + "toastAdded": "Bookmark added", + "cancel": "Cancel", + "submitAdd": "Add Bookmark", + "submitUpdate": "Update Bookmark" + }, + "addFolder": { + "title": "Create Folder", + "nameLabel": "Folder Name", + "namePlaceholder": "My Folder", + "nameRequired": "Please enter a folder name", + "parentLabel": "Parent Folder", + "parentPlaceholder": "Select parent folder", + "rootParent": "Root (No parent)", + "toastCreated": "Folder created", + "cancel": "Cancel", + "submit": "Create Folder" + }, + "editFolder": { + "title": "Rename Folder", + "nameLabel": "Folder Name", + "namePlaceholder": "Folder name", + "nameRequired": "Please enter a folder name", + "toastRenamed": "Folder renamed", + "cancel": "Cancel", + "save": "Save" + } + }, + "EmailValidator": { + "header": { + "title": "Email Validator", + "description": "Verify and validate email addresses for syntax, domain, and deliverability." + }, + "tabs": { + "single": "Single Verification", + "bulk": "Bulk Upload" + }, + "single": { + "cardTitle": "Single Email Verification", + "cardDescription": "Enter an email address to check its deliverability and reputation.", + "placeholder": "Enter email address...", + "clearAria": "Clear email", + "validate": "Validate", + "validating": "Validating...", + "formatError": "Please enter a valid email address" + }, + "toasts": { + "errorTitle": "Error", + "enterEmail": "Please enter an email address", + "invalidFormatTitle": "Invalid Email Format", + "invalidFormatDescription": "Please enter a valid email address", + "validateFailedDescription": "An error occurred while validating the email", + "noEmailsTitle": "No Emails Found", + "noEmailsDescription": "Please ensure your Excel has an 'email' column.", + "fileTooLargeTitle": "File Too Large", + "fileTooLargeDescription": "Found {total} emails. Only the first 4000 will be validated.", + "successTitle": "Success", + "successValidated": "Validated {count} emails successfully.", + "batchFailedTitle": "Batch Validation Failed", + "batchFailedDescription": "An error occurred while processing bulk validation.", + "noExportTitle": "No Data to Export", + "noExportValidOnly": "No valid emails found to export.", + "noExportAll": "No results to export.", + "exportSuccessTitle": "Export Successful", + "exportSuccessExcelAll": "Exported {count} emails to Excel.", + "exportSuccessExcelValid": "Exported {count} valid emails to Excel.", + "exportSuccessCsvAll": "Exported {count} emails to CSV.", + "exportSuccessCsvValid": "Exported {count} valid emails to CSV." + }, + "disposableAlert": { + "title": "Disposable Email Detected", + "description": "This email address belongs to a disposable email provider. It may be temporary or unsafe." + }, + "score": { + "reliabilityTitle": "Reliability Score", + "scoreLabel": "Score" + }, + "details": { + "sectionTitle": "Validation Details" + }, + "validation": { + "syntaxLabel": "Syntax Valid", + "syntaxTooltip": "Checks if the email address follows the correct format (e.g., user@domain.com)", + "domainLabel": "Domain Exists", + "domainTooltip": "Verifies that the domain name in the email address actually exists", + "mxLabel": "MX Records Found", + "mxTooltip": "Checks if the domain has mail exchange (MX) records configured to receive emails", + "mailboxLabel": "Mailbox Exists", + "mailboxTooltip": "Verifies that the specific mailbox/account exists on the mail server", + "disposableLabel": "Disposable Email", + "disposableTooltip": "Indicates if this email belongs to a temporary/disposable email service (may be risky)", + "roleBasedLabel": "Role Based Email", + "roleBasedTooltip": "Identifies if this is a role-based email (e.g., support@, info@) rather than a personal address" + }, + "copy": { + "emailCopied": "Email copied!", + "copyTooltip": "Copy email" + }, + "bulk": { + "dropHere": "Drop your file here", + "dragHere": "Drag and drop your file here", + "uploadHint": "Upload a .xlsx, .xls, or .csv file containing an 'email' column to validate up to 4000 emails.", + "filePrefix": "File:", + "selectFile": "Select File", + "downloadTemplate": "Download Template" + }, + "bulkLoading": { + "title": "Validating Emails...", + "batchProgress": "Processing batch {current} of {total}..." + }, + "stats": { + "totalProcessed": "Total Processed", + "valid": "Valid", + "risky": "Risky", + "invalid": "Invalid" + }, + "results": { + "detailedTitle": "Detailed Results", + "searchPlaceholder": "Search emails...", + "newVerification": "New Verification", + "exportAll": "Export All", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Export Valid ({count})" + }, + "showingResults": "Showing {shown} of {total} results", + "table": { + "email": "Email", + "status": "Status", + "score": "Score", + "checks": "Checks" + }, + "noSearchResults": "No results found matching \"{query}\"", + "checksTooltip": { + "syntax": "Syntax", + "domain": "Domain", + "mx": "MX", + "mailbox": "Mailbox" + }, + "exportColumns": { + "email": "Email", + "status": "Status", + "score": "Score", + "syntax": "Syntax", + "domain": "Domain", + "mx": "MX", + "mailbox": "Mailbox", + "disposable": "Disposable", + "roleBased": "RoleBased", + "valid": "Valid", + "invalid": "Invalid", + "yes": "Yes", + "no": "No" + }, + "sheets": { + "validEmails": "Valid Emails", + "results": "Results", + "template": "Template" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Explorer", + "heroSubtitle": "Connect to your databases to manage collections, execute queries, and visualize your data.", + "featureMultiTitle": "Multi-Connection", + "featureMultiDesc": "Manage multiple database connections simultaneously.", + "featureMongoTitle": "MongoDB Support", + "featureMongoDesc": "Native support for MongoDB with advanced filtering.", + "featureQueryTitle": "Smart Query", + "featureQueryDesc": "Powerful query builder with syntax highlighting.", + "connectCta": "Connect to MongoDB", + "footerNote": "Currently supports {db}. More databases coming soon.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Select a Collection", + "selectCollectionDesc": "Choose a collection from the sidebar to view documents and start exploring your data.", + "dialogAddConnection": "Add Connection", + "toastConnectionAdded": "Connection added", + "toastFetchFailed": "Failed to fetch documents for {collection}", + "toastDocDeleted": "Document deleted", + "deleteDocTitle": "Are you absolutely sure?", + "deleteDocDescription": "This action cannot be undone. This will permanently delete the document from your collection.", + "closeAllTitle": "Close all tabs?", + "closeAllDescription": "Are you sure you want to close all tabs? Any unsaved changes in query filters will be lost.", + "closeAllConfirm": "Close All", + "cancel": "Cancel", + "delete": "Delete" + }, + "connection": { + "titleEdit": "Edit Connection", + "titleConnect": "Connect to MongoDB", + "subtitleEdit": "Update your connection details below.", + "subtitleConnect": "Enter your MongoDB connection string to start exploring your data.", + "labelName": "Name", + "placeholderName": "e.g. Production DB", + "labelConnectionString": "Connection String", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://username:password@host:port/database", + "testConnection": "Test Connection", + "connecting": "Connecting...", + "updateConnect": "Update & Connect", + "connect": "Connect", + "cancelEdit": "Cancel Edit", + "savedConnections": "Saved Connections", + "emptySaved": "No saved connections yet.", + "lastUsed": "Last used {time}", + "toastTestOk": "Connection successful!", + "toastTestFail": "Connection failed: {message}", + "toastUpdated": "Connection updated", + "confirmDelete": "Are you sure you want to delete this saved connection?", + "toastDeletedConn": "Connection deleted", + "toastDeleteFail": "Failed to delete connection", + "copyStringTitle": "Copy connection string", + "toastStringCopied": "Connection string copied!", + "editTitle": "Edit", + "deleteTitle": "Delete" + }, + "sidebar": { + "explorer": "Explorer", + "add": "Add", + "tooltipAdd": "Add new connection", + "tooltipRefresh": "Refresh connections", + "searchPlaceholder": "Search...", + "loadingConnections": "Loading connections...", + "noConnectionsFound": "No connections found", + "toastLoadFail": "Failed to load connections", + "toastConnectFail": "Failed to connect to {name}", + "toastCollectionsFail": "Failed to fetch collections for {name}", + "toastRenamed": "Connection renamed", + "toastRenameFail": "Failed to rename connection", + "confirmDeleteConnection": "Are you sure you want to delete connection \"{name}\"?", + "toastDeleted": "Connection deleted", + "toastDeleteConnFail": "Failed to delete connection", + "confirmDropDb": "Are you sure you want to drop database \"{name}\"? This action cannot be undone.", + "toastDbDropped": "Database {name} dropped", + "confirmDropCollection": "Are you sure you want to drop collection \"{name}\"? This action cannot be undone.", + "toastCollectionDropped": "Collection {name} dropped", + "toastCollectionRenamed": "Collection renamed to {name}", + "toastDatabaseRenamed": "Database renamed to {name}", + "connecting": "Connecting...", + "connectionFailed": "Connection failed", + "noDatabases": "No databases", + "loading": "Loading...", + "noCollections": "No collections", + "renameCollectionTitle": "Rename Collection", + "renameDatabaseTitle": "Rename Database", + "placeholderNewCollection": "New collection name", + "placeholderNewDatabase": "New database name", + "rename": "Rename", + "refresh": "Refresh", + "copyDbName": "Copy DB Name", + "toastDbNameCopied": "Database name copied to clipboard", + "dropDatabase": "Drop Database", + "dropCollection": "Drop Collection", + "menuRename": "Rename", + "menuCopyConnectionString": "Copy Connection String", + "menuDeleteConnection": "Delete Connection", + "toastSidebarStringCopied": "Connection string copied!", + "cancel": "Cancel", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} docs", + "table": "Table", + "json": "JSON", + "tree": "Tree", + "tooltipTable": "Table View", + "tooltipJson": "JSON View", + "tooltipTree": "Tree View", + "itemsPerPage": "Items per page", + "perPage": "{n} / page", + "prevPage": "Previous Page", + "nextPage": "Next Page", + "expandAll": "Expand All", + "collapseAll": "Collapse All", + "refreshData": "Refresh Data", + "insert": "Insert", + "export": "Export", + "actions": "Actions", + "invalidJson": "Invalid JSON content", + "copiedClipboard": "Copied to clipboard", + "docUpdated": "Document updated successfully", + "docInserted": "Document inserted successfully", + "invalidJsonShort": "Invalid JSON", + "docCopied": "Document copied to clipboard", + "openFullEditor": "Open in full editor", + "cellArray": "Array({length})", + "cellObject": "Object", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "No documents match your query", + "emptyFilterHint": "Try adjusting your filter or clearing the query.", + "clearFilter": "Clear Filter", + "emptyCollectionTitle": "This collection is empty", + "emptyCollectionHint": "Insert a document to get started.", + "insertDocument": "Insert Document", + "formatJson": "Format JSON", + "compactJson": "Compact JSON", + "copyClipboard": "Copy to Clipboard", + "copyJson": "Copy JSON", + "duplicate": "Duplicate", + "editDoc": "Edit Document", + "deleteDoc": "Delete Document", + "documentLabel": "Document {n}", + "editDialogTitle": "Edit Document", + "insertDialogTitle": "Insert Document", + "viewValueTitle": "View Value", + "saveChanges": "Save Changes", + "close": "Close", + "cancel": "Cancel", + "insertButton": "Insert", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Active filter applied", + "filterActiveTooltip": "Filter active", + "closeAll": "Close All" + }, + "query": { + "migrateHistory": "Migrated query history to cloud", + "saveHistoryFail": "Failed to save query history", + "deleteQueryFail": "Failed to delete query", + "invalidJsonQuery": "Invalid JSON query", + "placeholder": "e.g. { \"status\": \"active\" }", + "clearQuery": "Clear query", + "openAdvanced": "Open Advanced Editor", + "visualBuilder": "Visual Query Builder", + "filterRules": "Filter Rules", + "addRule": "Add Rule", + "noFilters": "No filters applied. Add a rule to filter documents.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "Field", + "opPlaceholder": "Op", + "typePlaceholder": "Type", + "opRegex": "Regex", + "opIn": "In", + "opNin": "Not In", + "opExists": "Exists", + "typeAuto": "Auto", + "typeString": "String", + "typeNumber": "Number", + "typeBoolean": "Boolean", + "typeDate": "Date", + "typeObjectId": "ObjectId", + "true": "True", + "false": "False", + "valuePlaceholder": "Value", + "valueListPlaceholder": "value1, value2", + "clearFilters": "Clear Filters", + "applyFilters": "Apply Filters", + "queryHistory": "Query History", + "noSavedQueries": "No saved queries yet", + "advancedEditorTitle": "Advanced Query Editor", + "templatesPlaceholder": "Templates", + "templateEmptyAgg": "Empty Aggregation", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Join)", + "templateGroup": "$group (Count/Sum)", + "format": "Format", + "formattedJson": "Formatted JSON", + "runQuery": "Run Query" + }, + "export": { + "selectFieldsError": "Please select at least one field to export", + "success": "Successfully exported to {filename}", + "fail": "Failed to export data", + "title": "Export Data", + "formatLabel": "Export Format", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Select Fields", + "selectAll": "Select All", + "fieldsSelected": "{count, plural, one {# field selected} other {# fields selected}}", + "cancel": "Cancel", + "export": "Export", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Type: {type}", + "itemSummary": "{count, plural, one {# item} other {# items}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "New Request", + "importedRequest": "Imported Request", + "untitledRequest": "Untitled Request" + }, + "toasts": { + "curlImported": "cURL imported successfully", + "curlParseFailed": "Failed to parse cURL command", + "invalidJsonBody": "Invalid JSON body", + "requestFailed": "Request failed: {message}", + "curlPasted": "cURL pasted and parsed successfully", + "responseCopied": "Response copied to clipboard", + "codeCopied": "Code copied to clipboard", + "copyFailed": "Kopiowanie do schowka nie powiodło się" + }, + "layout": { + "collections": "Collections", + "closeSidebar": "Close sidebar", + "openSidebar": "Open sidebar", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Method", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Sending...", + "send": "Send", + "invalidJsonBodyHelp": "Nie można wysłać: treść JSON jest nieprawidłowa" + }, + "requestTabs": { + "params": "Params", + "headers": "Headers", + "body": "Body", + "auth": "Auth", + "bodyType": "Body type", + "selectType": "Select type", + "bodyNone": "None", + "bodyJson": "JSON", + "bodyText": "Text", + "noBodyHint": "No body content for this request", + "switchToJson": "Switch to JSON body", + "authType": "Auth type", + "noAuth": "No auth", + "bearerToken": "Bearer token", + "basicAuth": "Basic auth", + "apiKey": "API key", + "labelToken": "Token", + "placeholderBearerToken": "Bearer token", + "labelUsername": "Username", + "placeholderUsername": "Username", + "labelPassword": "Password", + "placeholderPassword": "Password", + "labelKey": "Key", + "placeholderApiKeyName": "Key name (e.g. X-Api-Key)", + "labelValue": "Value", + "placeholderValue": "Value", + "addTo": "Add to", + "header": "Header", + "queryParams": "Query params", + "noAuthHint": "This request does not use authentication." + }, + "responsePanel": { + "emptyTitle": "Ready to explore?", + "emptyHint": "Enter a URL and click Send to see the response data here.", + "responsePreviewAlt": "Response preview", + "previewUnavailable": "Preview not available for this content type", + "status": "Status", + "time": "Time", + "size": "Size", + "copyBody": "Copy body", + "downloadResponse": "Download response", + "bodyTab": "Body", + "previewTab": "Preview", + "headersTab": "Headers", + "binaryRawView": "Binary data cannot be displayed in raw view.", + "errorStatusLabel": "Error", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "New tab" + }, + "collectionsSidebar": { + "title": "API resources", + "newCollection": "New collection", + "tabCollections": "Collections", + "tabHistory": "History", + "noCollectionsTitle": "No collections", + "noCollectionsHint": "Create a collection to organize your API requests.", + "createCollection": "Create collection", + "newFolder": "New folder", + "rename": "Rename", + "delete": "Delete", + "emptyCollection": "Empty collection", + "recentRequests": "Recent requests", + "clearAll": "Clear all", + "noHistoryTitle": "No history yet", + "noHistoryHint": "Send a request to see it here.", + "dialogNewFolderTitle": "New folder", + "dialogNewFolderDescription": "Create a new folder to organize your requests.", + "labelFolderName": "Folder name", + "placeholderFolderName": "My folder", + "createFolder": "Create folder", + "dialogNewCollectionTitle": "New collection", + "dialogNewCollectionDescription": "Create a new top-level collection.", + "labelCollectionName": "Collection name", + "placeholderCollectionName": "My collection", + "dialogRenameCollectionTitle": "Rename collection", + "dialogRenameCollectionDescription": "Enter a new name for the collection.", + "placeholderRenameCollection": "Collection name", + "renameAction": "Rename", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "New folder", + "delete": "Delete", + "emptyFolder": "Empty folder" + }, + "environmentManager": { + "noEnvironment": "No environment", + "manageDialogTitle": "Manage environments", + "placeholderNewEnv": "New environment", + "variablesHeading": "{name} variables", + "addVariable": "Add variable", + "columnVariable": "Variable", + "columnValue": "Value", + "placeholderKey": "Key", + "placeholderValue": "Value", + "selectEnvToEdit": "Select an environment to edit" + }, + "codeGenerator": { + "triggerTitle": "Generate code", + "dialogTitle": "Generate code", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Copy", + "errorGenerating": "Error generating code" + }, + "keyValueEditor": { + "columnKey": "Key", + "columnValue": "Value", + "placeholderKey": "Key", + "placeholderValue": "Value", + "addItem": "Add item" + }, + "importCurl": { + "trigger": "Import cURL", + "dialogTitle": "Import cURL", + "dialogDescription": "Paste your cURL command below to import it as a new request.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Import" + }, + "saveRequest": { + "trigger": "Save", + "dialogTitle": "Save request", + "dialogDescription": "Save this request to a collection folder.", + "labelName": "Request name", + "placeholderName": "My request", + "labelFolder": "Folder", + "placeholderFolder": "Select a folder", + "save": "Save" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Edit Mode", + "viewOnlyMode": "View Only Mode", + "insertComponent": "Insert component", + "addUnorderedList": "Add unordered list", + "addOrderedList": "Add ordered list", + "addTable": "Add table" + }, + "addBlock": "Add block", + "media": { + "popoverTitle": "Add media", + "singleImage": "Single Image", + "singleImageDesc": "Upload one image", + "multipleImages": "Multiple Images", + "multipleImagesDesc": "Upload image grid", + "video": "Video", + "videoDesc": "Upload video file", + "uploadingAlt": "Uploading...", + "uploadFailedAlt": "Upload failed" + }, + "export": { + "fabAria": "Export code", + "title": "Export Code", + "description": "Copy the HTML or JSON output of your editor content", + "preview": "Preview", + "html": "HTML", + "json": "JSON", + "previewOptions": "Preview Options", + "enhanceSpaces": "Enhance Spaces", + "livePreview": "Live preview of rendered HTML", + "htmlTailwind": "HTML with Tailwind CSS classes", + "editorJson": "Editor state as JSON", + "copyHtml": "Copy HTML", + "copyJson": "Copy JSON", + "copied": "Copied!" + }, + "template": { + "fabTitle": "Switch Template", + "title": "Choose a Template", + "description": "Select a template to get started quickly with pre-built layouts and content", + "catAll": "All Templates", + "catProductivity": "Productivity", + "catCreative": "Creative", + "catBusiness": "Business", + "catPersonal": "Personal", + "noTemplates": "No templates found in this category", + "confirmTitle": "Replace Existing Content?", + "confirmDescription": "You have existing content in the editor. Applying this template will replace all current content.", + "cannotUndo": "This action cannot be undone.", + "cancel": "Cancel", + "replaceContent": "Replace Content" + }, + "command": { + "searchPlaceholder": "Search commands...", + "noResults": "No commands found.", + "turnInto": "Turn into", + "h1": { + "label": "Heading 1", + "description": "Big section heading", + "keywords": "h1,heading,title,large" + }, + "h2": { + "label": "Heading 2", + "description": "Medium section heading", + "keywords": "h2,heading,subtitle" + }, + "h3": { + "label": "Heading 3", + "description": "Small section heading", + "keywords": "h3,heading,subheading" + }, + "h4": { + "label": "Heading 4", + "description": "Tiny section heading", + "keywords": "h4,heading" + }, + "h5": { + "label": "Heading 5", + "description": "Smaller heading", + "keywords": "h5,heading" + }, + "h6": { + "label": "Heading 6", + "description": "Smallest heading", + "keywords": "h6,heading" + }, + "p": { + "label": "Paragraph", + "description": "Regular text paragraph", + "keywords": "p,paragraph,text,normal" + }, + "code": { + "label": "Code Block", + "description": "Code snippet", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "Quote", + "description": "Block quote", + "keywords": "quote,blockquote,citation" + }, + "li": { + "label": "Bulleted List", + "description": "Simple list item with bullet", + "keywords": "list,bullet,unordered,ul,li" + }, + "ol": { + "label": "Numbered List", + "description": "Numbered list item", + "keywords": "list,numbered,ordered,ol,li" + }, + "img": { + "label": "Image", + "description": "Upload or embed an image", + "keywords": "image,img,picture,photo,upload" + }, + "video": { + "label": "Video", + "description": "Upload or embed a video", + "keywords": "video,vid,movie,mp4,upload" + }, + "table": { + "label": "Table", + "description": "Create a table", + "keywords": "table,grid,rows,columns,cells" + } + }, + "toasts": { + "tableImported": "Table Imported", + "tableImportedDesc": "Markdown table has been imported successfully", + "mediaPasted": "Media pasted", + "pasteMixed": "{imageCount} image(s) and {videoCount} video(s) pasted successfully.", + "pasteImages": "{count} image(s) pasted successfully.", + "pasteVideos": "{count} video(s) pasted successfully.", + "pasteFailed": "Paste failed", + "unexpectedError": "An unexpected error occurred", + "uploadFailed": "Upload failed" + }, + "blockBackground": { + "menuItem": "Change Background Color", + "dialogTitle": "Block Background Color", + "dialogDescription": "Choose a background color for this block", + "tabPreset": "Preset Colors", + "tabCustom": "Custom Color", + "sectionHeading": "Background Colors", + "apply": "Apply", + "colors": { + "none": "None", + "red": "Red", + "orange": "Orange", + "yellow": "Yellow", + "green": "Green", + "blue": "Blue", + "indigo": "Indigo", + "purple": "Purple", + "pink": "Pink", + "teal": "Teal", + "cyan": "Cyan", + "gray": "Gray" + } + }, + "tableDialog": { + "title": "Create Table", + "description": "Create a new table or import from markdown", + "markdownCheckbox": "I have a markdown table", + "pasteMarkdownLabel": "Paste Markdown Table", + "markdownPlaceholder": "| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "Paste your markdown table above. Must include header and separator rows.", + "rows": "Rows", + "columns": "Columns", + "maxDimensions": "Maximum: 20 rows × 10 columns", + "cancel": "Cancel", + "importTable": "Import Table", + "createTable": "Create Table" + }, + "tableParse": { + "errors": { + "minRows": "Table must have at least a header row and separator row", + "pipeFormat": "Table rows must start and end with |", + "headerMinCol": "Header row must have at least one column", + "separatorRow": "Second row must be a separator (e.g., |---|---|)", + "rowColumnMismatch": "Row {row} has {got} columns, expected {expected}", + "parseFailed": "Failed to parse markdown table" + } + }, + "link": { + "editLink": "Edit Link", + "addLink": "Add Link", + "selectedText": "Selected text: \"{text}\"", + "linkUrl": "Link URL", + "urlPlaceholder": "https://example.com", + "update": "Update", + "addLinkButton": "Add Link", + "toastApplied": "Link Applied", + "toastLinkedTo": "Linked to: {url}", + "toastRemoved": "Link Removed", + "toastRemovedDesc": "Link has been removed from selection" + }, + "customClass": { + "devMode": "Dev Mode", + "toggleDevModeAria": "Toggle dev mode", + "searchPlaceholderDev": "Search classes... (e.g., 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Search styles... (e.g., 'red', 'bold', 'shadow')", + "appliesTitle": "Applies: {className}", + "noMatches": "No classes found matching \"{query}\"", + "toastClassReplaced": "Class Replaced", + "toastClassReplacedDesc": "Replaced \"{replaced}\" with \"{className}\"", + "toastCustomClassApplied": "Custom Class Applied", + "toastAppliedClass": "Applied class: {className}" + }, + "insertComponents": { + "title": "Insert Component", + "description": "Choose a component to insert into your document", + "noComponents": "No components available", + "categories": { + "media": "Media", + "layout": "Layout", + "interactive": "Interactive", + "decoration": "Decoration" + }, + "freeImage": { + "name": "Free Movement Image", + "description": "Add an image that can be positioned anywhere and resized freely" + } + } + }, + "JsonFormatter": { + "title": "Formatowanie JSON", + "description": "Formatuj, waliduj i edytuj JSON w widoku tekstowym i drzewiastym.", + "documentNameText": "Dokument tekstowy {n}", + "documentNameTree": "Dokument drzewiasty {n}", + "newDocument": "Nowy", + "copy": "Kopiuj", + "save": "Zapisz", + "load": "Wczytaj", + "saving": "Zapisywanie...", + "leftDocNameLabel": "Nazwa lewego dokumentu", + "rightDocNameLabel": "Nazwa prawego dokumentu", + "toastCopiedText": "Skopiowano panel tekstowy", + "toastCopiedTree": "Skopiowano panel drzewa", + "toastCopyFailed": "Nie udało się skopiować JSON", + "toastLoginRequired": "Zaloguj się, aby zapisywać dokumenty", + "toastSaved": "Dokument zapisany", + "toastSaveFailed": "Nie udało się zapisać dokumentu", + "format": "Formatuj", + "compact": "Kompaktuj", + "sortKeys": "Sortuj klucze", + "repair": "Napraw", + "query": "Zapytanie", + "toastInvalidJson": "Nieprawidłowy JSON", + "toastAlreadyValid": "JSON jest już poprawny", + "toastRepaired": "JSON naprawiony", + "toastRepairFailed": "Nie można naprawić JSON" + }, + "JwtDecoder": { + "title": "Dekoder JWT", + "subtitle": "Podglądaj nagłówek i payload. Wygaśnięcie używa claim exp. Nic nie jest wysyłane na serwer; podpis nie jest weryfikowany.", + "tokenLabel": "Token", + "clear": "Wyczyść", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopiuj", + "expiryLabel": "Wygaśnięcie (exp)", + "signaturePresent": "Podpis obecny (niezweryfikowany)", + "noExp": "Brak claim exp w payload.", + "status": { + "expired": "Wygasł", + "valid": "Ważny" + }, + "fields": { + "unix": "Unix", + "local": "Lokalny", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Wydano (iat)", + "notBefore": "Nie wcześniej niż (nbf)" + }, + "panels": { + "header": "Nagłówek", + "payload": "Payload" + }, + "errors": { + "empty": "Wklej JWT do dekodowania.", + "segments": "JWT musi mieć co najmniej nagłówek i payload (dwa segmenty).", + "base64": "Nie udało się zdekodować nagłówka lub payload (base64url)." + }, + "relative": { + "future": "za {n} {unit}", + "past": "{n} {unit} temu" + }, + "units": { + "second": "{count, plural, one {sekundę} few {sekundy} many {sekund} other {sekund}}", + "minute": "{count, plural, one {minutę} few {minuty} many {minut} other {minut}}", + "hour": "{count, plural, one {godzinę} few {godziny} many {godzin} other {godzin}}", + "day": "{count, plural, one {dzień} few {dni} many {dni} other {dni}}" + }, + "tabs": { + "decode": "Dekoduj", + "sign": "Generator / podpisywanie" + }, + "signer": { + "algLabel": "Algorytm (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "JSON payload", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON header", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Sekret (HS*)", + "secretPlaceholder": "Wklej sekret HMAC…", + "privateKeyLabel": "Klucz prywatny PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Algorytm \"none\" tworzy token bez podpisu. To niebezpieczne — używaj tylko do testów.", + "sign": "Podpisz", + "verify": "Zweryfikuj podpis", + "outputLabel": "Token (kompaktowy JWS)", + "outputPlaceholder": "Kliknij „Podpisz”…", + "copyToken": "Kopiuj token", + "copied": "Skopiowano", + "verifyKeyLabel": "Klucz weryfikacji (opcjonalnie)", + "verifyKeyPlaceholder": "Dla RS*: wklej klucz publiczny (SPKI) PEM. Dla HS*: zostaw puste, aby użyć sekretu.", + "verifyKeyHint": "Jeśli puste, weryfikacja użyje klucza podpisu (przydatne dla HS*).", + "verifyStatus": { + "valid": "Podpis poprawny", + "invalid": "Podpis niepoprawny" + }, + "securityNote": "Wszystko działa lokalnie w przeglądarce; klucze i tokeny nie są wysyłane." + }, + "signErrors": { + "invalidJson": "Header i payload muszą być poprawnym JSON.", + "unsupportedAlg": "Nieobsługiwany algorytm.", + "missingKey": "Dla tego algorytmu wymagany jest klucz podpisu.", + "cryptoFailed": "Podpisywanie nie powiodło się. Sprawdź format klucza i spróbuj ponownie." + }, + "verifyErrors": { + "empty": "Wpisz JWT do weryfikacji.", + "segments": "JWT musi mieć dokładnie 3 segmenty, aby zweryfikować podpis.", + "unsupportedAlg": "Nieobsługiwany lub brakujący alg w headerze.", + "missingKey": "Dla tego algorytmu wymagany jest klucz weryfikacji.", + "base64": "Nie udało się zdekodować nagłówka lub podpisu JWT.", + "cryptoFailed": "Weryfikacja nie powiodła się. Sprawdź format klucza i spróbuj ponownie." + } + }, + "CertificatePemDecoder": { + "title": "Dekoder certyfikatu / PEM", + "subtitle": "Wklej certyfikat X.509 (-----BEGIN CERTIFICATE-----) lub CSR (-----BEGIN CERTIFICATE REQUEST-----). Podmiot, wystawca, daty i SAN są analizowane lokalnie; nic nie jest wysyłane.", + "pemLabel": "Dane PEM", + "clear": "Wyczyść", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Przetwarzanie…", + "copyHex": "Kopiuj odcisk palca (hex bez dwukropków)", + "errors": { + "empty": "Wklej certyfikat PEM lub CSR do zdekodowania.", + "noBlock": "Nie znaleziono bloku PEM certyfikatu ani CSR.", + "parseFailed": "Nie można przetworzyć tych danych PEM." + }, + "block": { + "title": "Blok PEM {index}", + "certificate": "Certyfikat X.509", + "csr": "Żądanie podpisu certyfikatu" + }, + "fields": { + "subject": "Podmiot", + "issuer": "Wystawca", + "notBefore": "Nieważny przed", + "notAfter": "Nieważny po", + "serial": "Numer seryjny (hex)", + "sha256": "Odcisk SHA-256", + "sans": "Alternatywne nazwy podmiotu", + "sansNone": "Brak rozszerzenia SAN.", + "signatureAlgorithm": "Algorytm podpisu", + "csrIssuerNote": "CSR nie zawiera wystawcy (jest na wydanym certyfikacie).", + "csrValidityNote": "CSR nie zawiera dat ważności (są na wydanym certyfikacie)." + }, + "sanType": { + "dns": "DNS", + "dn": "Nazwa katalogowa", + "email": "E-mail", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Zarejestrowane ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Formatowanie SQL", + "subtitle": "Formatuj SQL dla MySQL, PostgreSQL lub SQLite. Działa w przeglądarce; nic nie jest wysyłane.", + "dialectLabel": "Dialekt", + "keywordsLabel": "Słowa kluczowe", + "indentLabel": "Wcięcie", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "WIELKIE", + "lower": "małe", + "preserve": "Zachowaj" + }, + "spaces": "{n} spacje", + "format": "Formatuj", + "clear": "Wyczyść", + "counter": "{current} / {max} znaków", + "inputPanel": "Wejście", + "formattedPanel": "Sformatowane", + "copy": "Kopiuj", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Kliknij „Formatuj”…", + "errors": { + "tooLong": "Wejście jest ograniczone do {max} znaków.", + "couldNotFormat": "Nie udało się sformatować tego SQL." + } + }, + "YamlFormatter": { + "title": "Formatyzator YAML", + "subtitle": "Formatuj, weryfikuj i konwertuj YAML w przeglądarce. Nic nie jest przesyłane.", + "outputLabel": "Wyjście", + "outputModes": { + "yaml": "YAML (sformatowany)", + "json": "JSON" + }, + "indentLabel": "Wcięcie", + "spaces": "{n} spacje", + "format": "Formatuj", + "clear": "Wyczyść", + "copy": "Kopiuj", + "counter": "{current} / {max} znaków", + "inputPanel": "Wejście", + "outputPanel": "Wyjście", + "errors": { + "tooLong": "Wejście jest ograniczone do {max} znaków.", + "couldNotFormat": "Nie można przetworzyć tego YAML." + } + }, + "GraphqlFormatter": { + "title": "Formatter GraphQL", + "subtitle": "Formatuj zapytania, mutacje i subskrypcje GraphQL w przeglądarce. Nic nie jest wysyłane.", + "tabs": { + "format": "Formatuj", + "build": "Edytor zapytań" + }, + "outputModeLabel": "Wynik", + "outputModes": { + "pretty": "Czytelny zapis", + "minify": "Minifikuj" + }, + "format": "Formatuj", + "clear": "Wyczyść", + "counter": "{current} / {max} znaków", + "inputPanel": "Wejście", + "outputPanel": "Wyjście", + "copy": "Kopiuj", + "builderHint": "Złóż szkielet dokumentu i wstaw go do edytora. Definicje zmiennych w składni GraphQL (np. $id: ID!) — nawiasy opcjonalne.", + "builder": { + "operation": "Operacja", + "operationName": "Nazwa operacji", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definicje zmiennych", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Zestaw selekcji", + "apply": "Zastosuj do wejścia", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Wejście jest ograniczone do {max} znaków.", + "couldNotFormat": "Nie udało się przeanalizować ani wypisać tego GraphQL.", + "emptySelection": "Dodaj zestaw selekcji przed zastosowaniem." + } + }, + "RegexTester": { + "title": "Tester regex", + "subtitle": "Regex w stylu JavaScript na żywo dla Twojego tekstu. Dopasowania są podświetlane; nic nie opuszcza przeglądarki.", + "patternLabel": "Wzorzec", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g globalnie", + "i": "i bez rozróżniania wielkości liter", + "m": "m wielowierszowe", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(puste)", + "noFlags": "—", + "matches": "{count, plural, one {dopasowanie} other {dopasowań}}", + "testTextLabel": "Tekst testowy", + "counter": "{current} / {max}", + "clear": "Wyczyść", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Tekst jest ograniczony do {max} znaków.", + "invalid": "Nieprawidłowe wyrażenie regularne.", + "runaway": "Zatrzymano po {max} dopasowaniach (możliwy niekończący się wzorzec)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Konwerter znaczników czasu", + "subtitle": "Sekundy Unix (≤10 cyfr) lub milisekundy (dłuższe liczby całkowite), ISO-8601 albo ciągi, które przeglądarka potrafi zinterpretować. Wszystko działa lokalnie.", + "inputLabel": "Wejście", + "placeholder": "1744200000 lub 2025-04-09T12:00:00Z", + "now": "Teraz", + "copy": "Kopiuj", + "hint": "Wklej wartość lub kliknij Teraz.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Względny", + "human": "Czytelny" + }, + "fields": { + "seconds": "Sekundy", + "milliseconds": "Milisekundy", + "utc": "UTC", + "local": "Lokalnie", + "distance": "Odległość" + }, + "errors": { + "empty": "Wprowadź znacznik czasu Unix, ciąg ISO lub tekst daty.", + "invalidNumber": "Nieprawidłowy liczbowy znacznik czasu.", + "outOfRange": "Poza zakresem dla JavaScript Date.", + "couldNotParse": "Nie udało się odczytać jako Unix, ISO-8601 lub data przeglądarki." + } + }, + "HttpStatusCodes": { + "title": "Kody statusu HTTP", + "subtitle": "Szukaj i przeglądaj kody statusu odpowiedzi HTTP (1xx5xx) z krótkimi opisami i odnośnikami do RFC. Działa lokalnie.", + "header": { + "title": "Kody statusu HTTP", + "description": "Wyszukiwalna ściąga kodów odpowiedzi HTTP z opisami i odnośnikami do RFC." + }, + "searchPlaceholder": "Szukaj: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtruj klasę…", + "classFilterAll": "Wszystkie klasy", + "resultsCount": "{count} z {total}", + "table": { + "code": "Code", + "phrase": "Fraza", + "description": "Opis", + "copy": "Kopiuj" + }, + "noResults": "Brak wyników dla „{query}”.", + "specPrefix": "Spec.:", + "copied": "Kod skopiowany" + }, + "CronBuilder": { + "title": "Kreator wyrażeń Cron", + "subtitle": "Standardowy cron 5‑polowy: minute hour day month weekday. Parser używa seconds=0 wewnętrznie. Opcjonalny format 6‑polowy (sec min hour dom mon dow) działa w edytorze.", + "tabs": { + "builder": "Kreator wizualny", + "expression": "Wyrażenie" + }, + "presetsLabel": "Predefiniowane", + "fieldsLabel": "Pola", + "rawExpressionLabel": "Surowe wyrażenie", + "expressionHint": "Opuść pole, aby przetworzyć i zsynchronizować kreator. {count} {count, plural, one {token} other {tokenów}}.", + "currentExpressionLabel": "Aktualne wyrażenie", + "copy": "Kopiuj", + "quickPick": "Szybki wybór", + "emptyExpression": "—", + "plainLanguageLabel": "Język naturalny", + "nextRunsLabel": "Kolejne uruchomienia (czas lokalny)", + "fields": { + "minute": "Minuta", + "hour": "Godzina", + "dayOfMonth": "Dzień miesiąca", + "month": "Miesiąc", + "dayOfWeek": "Dzień tygodnia" + }, + "presets": { + "everyMinute": "Co minutę", + "every5Minutes": "Co 5 minut", + "every15Minutes": "Co 15 minut", + "everyHour": "Co godzinę (o :00)", + "every6Hours": "Co 6 godzin", + "dailyMidnight": "Codziennie o północy", + "daily0900": "Codziennie o 09:00", + "weekdays0900": "W dni robocze o 09:00", + "weeklySunday": "Co tydzień (niedziela 00:00)", + "monthlyFirst": "Co miesiąc (1. 00:00)", + "yearlyJan1": "Co rok (1 sty 00:00)" + }, + "picks": { + "minute": { + "every": "co minutę" + }, + "hour": { + "every": "co godzinę" + }, + "dom": { + "any": "dowolny dzień", + "last": "ostatni dzień" + }, + "month": { + "every": "co miesiąc" + }, + "dow": { + "any": "dowolny dzień", + "weekdays": "dni robocze", + "weekend": "weekend" + } + }, + "months": { + "jan": "Sty", + "feb": "Lut", + "mar": "Mar", + "apr": "Kwi", + "may": "Maj", + "jun": "Cze", + "jul": "Lip", + "aug": "Sie", + "sep": "Wrz", + "oct": "Paź", + "nov": "Lis", + "dec": "Gru" + }, + "weekdays": { + "sun": "Nd", + "mon": "Pn", + "tue": "Wt", + "wed": "Śr", + "thu": "Cz", + "fri": "Pt", + "sat": "Sb" + }, + "errors": { + "fieldCount": "Oczekiwano 5 pól (minute hour day month weekday) lub 6 z sekundami.", + "invalid": "Nieprawidłowe wyrażenie cron." + } + }, + "Base64": { + "title": "Koder / dekoder Base64", + "subtitle": "Koduj tekst do Base64 lub dekoduj ciągi Base64", + "upload": "Wgraj", + "clear": "Wyczyść", + "modes": { + "encode": "Koduj", + "decode": "Dekoduj" + }, + "swapTitle": "Zamień wejście i wyjście", + "panels": { + "plainText": "Zwykły tekst", + "base64Input": "Wejście Base64", + "base64Output": "Wyjście Base64", + "decodedText": "Zdekodowany tekst" + }, + "placeholders": { + "encode": "Wpisz tekst do zakodowania w Base64…", + "decode": "Wklej Base64 do dekodowania…" + }, + "dropHere": "Upuść plik tutaj", + "charCount": "{count} znaków", + "download": { + "title": "Pobierz wynik", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Kopiuj do schowka", + "outputPlaceholder": "Wynik pojawi się tutaj…", + "errors": { + "invalidBase64": "Nieprawidłowy ciąg Base64. Sprawdź wejście.", + "encodeFailed": "Nie udało się zakodować wejścia." + } + }, + "UrlEncode": { + "title": "Kodowanie / dekodowanie URL", + "subtitle": "Koduj/dekoduj procentowo przy użyciu encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Wgraj", + "clear": "Wyczyść", + "modes": { + "encode": "Koduj", + "decode": "Dekoduj" + }, + "swapTitle": "Zamień wejście i wyjście", + "panels": { + "plainText": "Zwykły tekst", + "encodedInput": "Zakodowane wejście", + "encodedOutput": "Zakodowane wyjście", + "decodedText": "Zdekodowany tekst" + }, + "placeholders": { + "encode": "Wpisz tekst do kodowania procentowego…", + "decode": "Wklej tekst percent-encoded do dekodowania…" + }, + "dropHere": "Upuść plik tutaj", + "charCount": "{count} znaków", + "download": { + "title": "Pobierz wynik", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Kopiuj do schowka", + "outputPlaceholder": "Wynik pojawi się tutaj…", + "errors": { + "invalidEncoding": "Nieprawidłowe kodowanie procentowe. Sprawdź niekompletne pary % lub sekwencje escape.", + "encodeFailed": "Nie można zakodować (nieprawidłowy Unicode w wejściu)." + } + }, + "UrlParser": { + "title": "Parser URL", + "subtitle": "Podziel adres URL na protokół, host, ścieżkę, parametry zapytania i hash (fragment).", + "upload": "Prześlij", + "clear": "Wyczyść", + "panels": { + "input": "Wejście URL", + "output": "Wynik" + }, + "placeholders": { + "input": "Wklej adres URL (np. https://example.com/path?foo=bar#section) lub host/ścieżkę (np. example.com/path)…" + }, + "dropHere": "Upuść plik tutaj", + "charCount": "{count} znaków", + "outputPlaceholder": "Przeanalizowane składowe pojawią się tutaj…", + "fields": { + "protocol": "Protokół", + "host": "Host", + "path": "Ścieżka", + "hash": "Hash", + "queryParams": "Parametry zapytania" + }, + "paramCount": "{count} parametrów", + "noParams": "Brak parametrów zapytania.", + "paramKey": "Klucz", + "paramValue": "Wartość", + "copyTitle": "Kopiuj do schowka", + "copyParamTitle": "Kopiuj jako key=value", + "download": { + "title": "Pobierz JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Nieprawidłowy URL. Podaj nazwę hosta (example.com) i sprawdź znaki." + } + }, + "UuidGenerator": { + "title": "Generator UUID / ULID", + "subtitle": "Generuj UUID (RFC 9562) lub ULID w przeglądarce. Eksport zbiorczy do {max} na uruchomienie.", + "format": "Format", + "kindHints": { + "ulid": "Sortowalny, bezpieczny dla URL identyfikator 26‑znakowy", + "uuid4": "Losowy (RFC 9562)", + "uuid7": "Uporządkowany w czasie, ms Unix + losowość", + "uuid1": "MAC/czas (RFC 9562)", + "uuid6": "Przestawiony czas gregoriański", + "uuid3": "Hash MD5 nazwy + przestrzeni nazw", + "uuid5": "Hash SHA‑1 nazwy + przestrzeni nazw", + "nil": "Zerowy UUID (same zera, RFC 9562)" + }, + "countLabel": "Liczba (1–{max})", + "nameLabel": "Nazwa (ciąg wejściowy)", + "namePlaceholder": "np. my.app/resource", + "bulkHint": "W trybie zbiorczym dodawane jest #0, #1, … aby każdy ID był inny (v3/v5 są deterministyczne).", + "namespace": "Przestrzeń nazw", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Własny UUID" + }, + "customNamespaceLabel": "UUID przestrzeni nazw", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Generuj", + "output": "Wynik", + "lines": "{count} wierszy", + "download": { + "title": "Pobierz .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Kopiuj wszystko", + "uppercaseToggle": "Wynik wielkimi literami", + "hyphensToggle": "Pokaż myślniki", + "copyJsonTitle": "Kopiuj jako tablicę JSON", + "outputPlaceholder": "Kliknij „Generuj”…", + "errors": { + "invalidCustomNamespace": "Własna przestrzeń nazw musi być prawidłowym UUID.", + "missingName": "Nazwa jest wymagana dla UUID v3 i v5.", + "unknown": "Nie udało się wygenerować identyfikatorów." + } + }, + "SecretApiKeyGenerator": { + "title": "Generator sekretu / klucza API", + "subtitle": "Generuj kryptograficznie losowe ciągi z konfigurowalną długością i alfabetem. Eksport zbiorczy do {max} na uruchomienie. Uzupełnia generator UUID / ULID.", + "alphabetLabel": "Alfabet", + "alphabetHint": "Duplikaty znaków są usuwane. Każdy symbol jest wybierany jednostajnie za pomocą crypto.getRandomValues i próbkowania odrzuceń (bez obciążenia modulo).", + "alphabetCount": "{count} unikalnych symboli", + "presetsLabel": "Presety", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumeryczny", + "hex": "Szesnastkowy (małe litery)", + "asciiPrintable": "ASCII drukowalne" + }, + "lengthLabel": "Długość na sekret (1–{maxLen})", + "countLabel": "Liczba (1–{maxBulk})", + "generate": "Generuj", + "output": "Wynik", + "lines": "{count} wierszy", + "download": { + "title": "Pobierz .txt", + "filename": "sekrety.txt" + }, + "copyTitle": "Kopiuj wszystko", + "outputPlaceholder": "Kliknij „Generuj”…", + "entropy": "{bits} bitów entropii", + "strength": { + "weak": "Słabe", + "fair": "Średnie", + "strong": "Silne" + }, + "excludeAmbiguous": "Wyklucz niejednoznaczne znaki", + "excludeAmbiguousHint": "Usuwa {chars} — znaki łatwe do pomylenia lub szkodliwe dla powłoki.", + "prefixLabel": "Prefiks klucza (opcjonalnie)", + "errors": { + "emptyAlphabet": "Dodaj co najmniej jeden znak do alfabetu.", + "alphabetTooLong": "Po usunięciu duplikatów alfabet może mieć co najwyżej {maxAlpha} znaków.", + "lengthOutOfRange": "Długość musi być między 1 a {maxLen}.", + "countOutOfRange": "Liczba musi być między 1 a {maxBulk}.", + "unknown": "Nie udało się wygenerować sekretów." + } + }, + "LoremIpsum": { + "title": "Generator Lorem Ipsum", + "subtitle": "Generuj zastępczy tekst łaciński dla swoich układów i makiet.", + "paragraphs": "Akapity", + "sentences": "Zdania", + "words": "Słowa", + "count": "Liczba", + "generate": "Generuj", + "copy": "Kopiuj", + "startWith": "Zacznij od 'Lorem ipsum...'", + "outputPlaceholder": "Wynik pojawi się tutaj...", + "outputType": "Typ wyjścia", + "bulletList": "Lista punktowana", + "htmlMarkup": "Znaczniki HTML", + "htmlMarkupHint": "Zawiń dane wyjściowe w akapity lub użyj listy dla trybu punktowanego", + "output": "Wynik", + "download": "Pobierz" + }, + "ColorPicker": { + "title": "Selektor i konwerter kolorów", + "subtitle": "Konwertuj HEX, RGB i HSL; twórz harmonijne palety. Cała obróbka odbywa się w przeglądarce.", + "nativePicker": "Natywny selektor", + "chooseColor": "Wybierz kolor", + "eyeDropper": "Pipeta", + "invalidHex": "Nieprawidłowy kolor HEX.", + "cssValues": "Wartości CSS", + "palettes": "Palety", + "copied": "Skopiowano", + "clickToCopy": "Kliknij, aby skopiować", + "copyAllHex": "Kopiuj wszystkie HEX", + "copy": "Kopiuj", + "copyColor": "Kopiuj {color}", + "paletteShades": "Odcienie", + "paletteComplementary": "Dopełniający", + "paletteTriadic": "Triadyczny", + "paletteAnalogous": "Analogiczny", + "paletteSplit": "Dopeł. podzielony", + "paletteSquare": "Kwadratowy" + }, + "ContrastChecker": { + "title": "Sprawdzanie kontrastu", + "subtitle": "Oblicza współczynnik kontrastu WCAG 2.1 między pierwszym planem a tłem. Sprawdza AA i AAA dla zwykłego i dużego tekstu. Wszystko w przeglądarce.", + "foreground": "Tekst / pierwszy plan", + "background": "Tło", + "hexPlaceholder": "#RRGGBB", + "swap": "Zamień kolory", + "chooseForeground": "Wybierz kolor pierwszego planu", + "chooseBackground": "Wybierz kolor tła", + "eyeDropper": "Pipeta", + "ratio": "Współczynnik kontrastu", + "copyRatio": "Kopiuj współczynnik", + "copied": "Skopiowano", + "previewHeading": "Podgląd", + "previewBody": "Pchnąć w tę łódź jeża lub ośm skrzyń fig. Zażółć gęślą jaźń, Stróż pchnął kość w quiz gędźb vel fax myjń.", + "previewCaption": "Przykładowy tekst w domyślnym rozmiarze.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — zwykły tekst (4,5:1)", + "levelAALarge": "AA — duży tekst (3:1)", + "levelAAANormal": "AAA — zwykły tekst (7:1)", + "levelAAALarge": "AAA — duży tekst (4,5:1)", + "pass": "Zaliczone", + "fail": "Niezaliczone", + "invalidHex": "Nieprawidłowy kolor HEX.", + "largeTextHint": "Duży tekst: 18 pt+ zwykły lub 14 pt+ pogrubiony (definicje WCAG)." + }, + "DiffChecker": { + "title": "Sprawdzanie różnic", + "subtitle": "Porównanie wierszy dwóch tekstów obok siebie. Wszystko pozostaje w Twojej przeglądarce.", + "rowsCount": "{count} wierszy", + "truncatedWarning": "Wyświetlanie pierwszych {max} wierszy.", + "limitExceeded": "Każda strona jest ograniczona do {max} znaków.", + "clearBoth": "Wyczyść oba", + "original": "Oryginał", + "modified": "Zmodyfikowany", + "originalPlaceholder": "Wklej oryginalny tekst…", + "modifiedPlaceholder": "Wklej zmodyfikowany tekst…", + "comparisonLabel": "Porównanie obok siebie", + "emptyState": "Wprowadź tekst w obu panelach, aby porównać." + }, + "JsonSchemaGenerator": { + "title": "Generator schematu JSON", + "subtitle": "Wklej prawidłowy JSON, aby wygenerować schemat JSON lub modele typowane. Wszystko działa w przeglądarce.", + "languageLabel": "Język wyjścia", + "inputLabel": "Dane wejściowe JSON", + "inputPlaceholder": "Wklej JSON tutaj…", + "outputLabel": "Wygenerowany wynik", + "outputPlaceholder": "Schemat lub wygenerowane typy pojawią się tutaj…", + "parseError": "Nieprawidłowy JSON", + "clear": "Wyczyść", + "copy": "Kopiuj", + "copied": "Skopiowano do schowka", + "copyFailed": "Nie udało się skopiować", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Schemat JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Ładowanie edytora…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Obraz do Base64", + "subtitle": "Upuść obraz, uzyskaj identyfikator URI danych lub surowy ciąg Base64. Wszystko pozostaje w Twojej przeglądarce.", + "upload": "Prześlij obraz", + "clear": "Jasne", + "modes": { + "dataUri": "URI danych", + "rawString": "Surowa baza64" + }, + "panels": { + "imageInput": "Wejście obrazu", + "output": "Dane wyjściowe Base64" + }, + "dropHere": "Upuść obraz tutaj", + "charCount": "{count} znaków", + "placeholders": { + "uploadImage": "Przeciągnij i upuść obraz lub kliknij przycisk przesyłania, aby rozpocząć." + }, + "fileTooLarge": "Plik jest za duży. Przetwarzanie może zawiesić Twoją przeglądarkę.", + "copyTitle": "Skopiuj do schowka", + "download": { + "title": "Pobierz tekst", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Kompresor obrazów", + "subtitle": "Zmniejsz wyjście JPEG, PNG lub WebP lokalnie z kontrolą jakości — nic nie jest wysyłane.", + "upload": "Prześlij obraz", + "clear": "Wyczyść", + "dropHere": "Upuść obraz tutaj", + "placeholders": { + "uploadImage": "Przeciągnij i upuść obraz lub kliknij, aby wybrać plik." + }, + "fileTooLarge": "Plik jest za duży (maks. {maxMb} MB).", + "unsupportedFormat": "Nieobsługiwany typ pliku. Użyj JPEG, PNG, WebP, GIF, AVIF lub BMP.", + "compressFailed": "Nie udało się skompresować obrazu. Spróbuj innego pliku lub formatu.", + "panels": { + "original": "Oryginał", + "compressed": "Podgląd po kompresji" + }, + "outputFormat": "Format wyjściowy", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Jakość", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG jest bezstratny w przeglądarce; suwak jakości dotyczy tylko JPEG i WebP.", + "sizes": { + "original": "Oryginał: {size}", + "compressed": "Po kompresji: {size}", + "saved": "o {percent}% mniejszy" + }, + "largerWarning": "Wyjście jest większe niż wejście — spróbuj innego formatu lub jakości.", + "download": "Pobierz", + "downloadFilename": "obraz-skompresowany", + "processing": "Kompresowanie…", + "emptyCompressed": "Podgląd po kompresji pojawi się tutaj." + }, + "CssGradientBuilder": { + "title": "Kreator gradientów CSS", + "subtitle": "Wizualny kreator gradientów CSS z kontrolą kąta, przystankami kolorów i kopiowaniem CSS jednym kliknięciem.", + "preview": "Podgląd gradientu", + "settings": "Ustawienia", + "type": "Typ", + "linear": "Liniowy", + "radial": "Promieniowy", + "angle": "Kąt", + "stops": "Kolor zatrzymuje się", + "color": "Kolor", + "position": "Pozycja (%)", + "addStop": "Dodaj przystanek", + "deleteStop": "Usuń przystanek", + "output": "Dane wyjściowe CSS", + "copy": "Skopiuj CSS", + "copied": "Skopiowano!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Generator .gitignore", + "subtitle": "Natychmiast generuj scalone pliki .gitignore dla określonego stosu technologii lub kombinacji.", + "searchPlaceholder": "Przeszukaj stosy (np. Node, Python, macOS)...", + "noResults": "Nie znaleziono stosów.", + "selectedStacks": "Wybrane stosy", + "output": "Podgląd wyników", + "copy": "Skopiuj do schowka", + "copied": "Skopiowano!", + "download": "Pobierz .gitignore", + "downloading": "Ściąganie...", + "errorFetching": "Nie udało się pobrać listy najlepszych.", + "errorGenerating": "Nie udało się wygenerować pliku .gitignore z wybranych stosów.", + "generating": "Generowanie...", + "emptyState": "Wybierz jeden lub więcej stosów technologii, aby wygenerować plik .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Wgraj CSV lub Excel, edytuj JSON, pobierz CSV lub XLSX. Tylko dane tabelaryczne; daty z Excela stają się znacznikami ISO w JSON.", + "dropTitle": "Upuść plik CSV lub Excel", + "dropHint": "Dla .xlsx / .xls używany jest pierwszy arkusz. CSV musi mieć wiersz nagłówka.", + "chooseFile": "Wybierz plik", + "jsonLabel": "JSON (tablica obiektów)", + "jsonPlaceholder": "Wklej tablicę JSON obiektów lub zaimportuj CSV / Excel…", + "loadSample": "Załaduj przykład", + "actions": { + "format": "Formatuj JSON", + "clear": "Wyczyść", + "copy": "Kopiuj", + "copied": "Skopiowano", + "downloadCsv": "Pobierz CSV", + "downloadXlsx": "Pobierz Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Dane", + "toastLoadedCsv": "CSV wczytano do edytora", + "toastLoadedExcel": "Plik Excel wczytano do edytora", + "toastFormatted": "Sformatowano JSON", + "toastExportedCsv": "Pobrano CSV", + "toastExportedXlsx": "Pobrano Excel", + "toastCopied": "Skopiowano do schowka", + "footnote": "Wszystko w przeglądarce. JSON musi być niepustą tablicą obiektów. Zagnieżdżone wartości są serializowane do JSON w komórkach CSV.", + "errors": { + "unsupportedFile": "Użyj pliku .csv, .xlsx lub .xls.", + "parseFailed": "Nie można przetworzyć pliku.", + "invalidJson": "Nieprawidłowy JSON", + "exportFailed": "Eksport nie powiódł się", + "copyFailed": "Nie można skopiować" + } + }, + "SnippetManager": { + "title": "Menedżer fragmentów kodu", + "subtitle": "Przechowuj fragmenty kodu w przeglądarce lub zaloguj się, aby synchronizować je z kontem. Przełączaj widok i edycję, kopiuj jednym kliknięciem, wybierz język lub autowykrywanie i formatuj JSON, SQL lub kod obsługiwany przez Monaco.", + "snippets": "Fragmenty", + "newSnippet": "Nowy fragment", + "newSnippetTitle": "Nowy fragment", + "defaultSnippetTitle": "Witamy", + "defaultSnippetCode": "// Witamy we fragmentach kodu\n// Wypróbuj widok vs edycja, kopiowanie, formatowanie i auto-język\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Szukaj fragmentów…", + "searchAria": "Szukaj fragmentów", + "emptySearch": "Brak fragmentów pasujących do wyszukiwania.", + "snippetTitle": "Tytuł", + "snippetTitlePlaceholder": "Nazwa fragmentu", + "language": "Język", + "detectedHint": "Podświetlanie: {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Podgląd", + "modeEdit": "Edycja", + "copy": "Kopiuj", + "copied": "Skopiowano", + "format": "Formatuj", + "cancel": "Anuluj", + "deleteTitle": "Usunąć fragment?", + "deleteDescription": "„{title}” zostanie usunięty z tej przeglądarki.", + "deleteConfirm": "Usuń", + "editorAria": "Edytor kodu fragmentu", + "editorLoading": "Ładowanie edytora…", + "toastCopied": "Skopiowano do schowka", + "toastCopyFailed": "Nie udało się skopiować", + "toastNothingToCopy": "Nic do skopiowania", + "toastFormatted": "Sformatowano", + "toastFormatFailed": "Nie udało się sformatować", + "toastFormatJsonInvalid": "Nieprawidłowy JSON — najpierw popraw składnię", + "toastFormatUnsupported": "Brak formatu dla tego języka — spróbuj JSON lub SQL albo tryb edycji", + "toastDeleted": "Fragment usunięty", + "toastLoadFailed": "Nie udało się wczytać fragmentów z serwera.", + "toastSyncFailed": "Nie udało się zsynchronizować z serwerem.", + "languages": { + "auto": "Wykrywanie automatyczne", + "plaintext": "Zwykły tekst", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Klient SQL", + "dialogTitle": "Połączenia SQL", + "mobileSheetTitle": "Eksplorator SQL", + "newTabLabel": "Nowa karta zapytania", + "vaultLockedTitle": "Skarbiec zablokowany", + "vaultLockedDesc": "Odblokuj skarbiec, aby uzyskać dostęp do zapisanych połączeń.", + "heroTitle": "Klient SQL", + "heroDesc": "Połącz się z PostgreSQL, MySQL lub MariaDB. Dane uwierzytelniające są szyfrowane w przeglądarce.", + "heroAddConnection": "Dodaj połączenie", + "selectTableTitle": "Wybierz tabelę lub otwórz kartę zapytania", + "selectTableDesc": "Kliknij tabelę na pasku bocznym lub użyj ⌘↩.", + "toastLoadFail": "Nie udało się załadować połączeń." + }, + "connection": { + "titleNew": "Nowe połączenie SQL", + "titleEdit": "Edytuj połączenie", + "labelDbType": "Typ bazy danych", + "labelName": "Nazwa połączenia", + "placeholderName": "Moja baza produkcyjna", + "labelHostPort": "Host i port", + "labelDatabase": "Baza danych", + "placeholderDatabase": "mydb", + "labelUsername": "Nazwa użytkownika", + "labelPassword": "Hasło", + "labelSsl": "Włącz SSL / TLS", + "sslDesc": "Szyfruj połączenie z serwerem", + "securityNote": "Dane uwierzytelniające są szyfrowane AES-256 w przeglądarce przed zapisem", + "btnTest": "Testuj połączenie", + "btnSave": "Zapisz i połącz", + "btnUpdate": "Zaktualizuj", + "btnCancelEdit": "Anuluj edycję", + "savedConnections": "Zapisane połączenia", + "emptySaved": "Zapisane połączenia pojawią się tutaj", + "confirmDelete": "Usunąć to połączenie?", + "toastFillFields": "Wypełnij host i bazę danych przed testowaniem.", + "toastConnectOk": "Połączono! {version}", + "toastConnectFail": "Błąd połączenia: {message}", + "toastUnlockVault": "Najpierw odblokuj skarbiec.", + "toastFieldsRequired": "Host i baza danych są wymagane.", + "toastUpdated": "Połączenie zaktualizowane.", + "toastSaved": "Połączenie zapisane.", + "toastSaveFail": "Błąd zapisu: {message}", + "toastDeleted": "Połączenie usunięte.", + "toastDeleteFail": "Nie udało się usunąć połączenia." + }, + "sidebar": { + "title": "Eksplorator SQL", + "tooltipAddConnection": "Dodaj połączenie", + "tooltipRefreshSchema": "Odśwież schemat", + "noConnections": "Brak zapisanych połączeń", + "toastSchemaFail": "Nie udało się załadować schematu: {message}" + }, + "editor": { + "btnRun": "Uruchom", + "executing": "Wykonywanie zapytania…", + "emptyTitle": "Uruchom zapytanie, aby zobaczyć wyniki", + "emptyHint": "Naciśnij ⌘↩ lub kliknij Uruchom", + "toastNoConnection": "Brak aktywnego połączenia." + }, + "results": { + "filterPlaceholder": "Filtruj wyniki…", + "rowCount": "{count} {count, plural, one {wiersz} few {wiersze} many {wierszy} other {wierszy}}", + "rowCountFiltered": "{filtered} / {total} wierszy", + "exportCsv": "CSV", + "noRows": "Nie zwrócono wierszy" + } + }, + "QrCodeGenerator": { + "title": "Generator kodów QR", + "subtitle": "Zamień dowolny tekst lub URL na kod QR PNG. Renderowanie odbywa się lokalnie w przeglądarce.", + "contentLabel": "Treść", + "contentPlaceholder": "https://example.com, zwykły tekst, ciąg Wi‑Fi, vCard…", + "styleLabel": "Styl", + "dotTypeLabel": "Kształt punktów", + "dotTypeSquare": "Kwadrat", + "dotTypeDots": "Kropki", + "dotTypeRounded": "Zaokrąglone", + "dotTypeExtraRounded": "Bardzo zaokrąglone", + "dotTypeClassy": "Eleganckie", + "dotTypeClassyRounded": "Eleganckie zaokrąglone", + "cornerSquareLabel": "Kwadraty rogów", + "cornerSquareDot": "Kropka", + "cornerSquareSquare": "Kwadrat", + "cornerSquareExtraRounded": "Bardzo zaokrąglone", + "cornerDotLabel": "Kropki rogów", + "cornerDotDot": "Kropka", + "cornerDotSquare": "Kwadrat", + "eccLabel": "Korekcja błędów", + "eccL": "Niska (~7%)", + "eccM": "Średnia (~15%)", + "eccQ": "Kwartyl (~25%)", + "eccH": "Wysoka (~30%)", + "marginLabel": "Margines (moduły)", + "widthLabel": "Szerokość obrazu", + "widthEffective": "Renderowanie w {px} px", + "darkColor": "Ciemne moduły", + "lightColor": "Jasne moduły", + "resetColors": "Resetuj kolory", + "logoLabel": "Logo centralne", + "logoMarginLabel": "Margines", + "uploadLogo": "Prześlij obraz", + "removeLogo": "Usuń logo", + "downloadPng": "Pobierz PNG", + "downloadSvg": "Pobierz SVG", + "copyImage": "Kopiuj obraz", + "copied": "Skopiowano", + "clear": "Wyczyść", + "previewAlt": "Podgląd kodu QR", + "emptyHint": "Wpisz lub wklej treść, aby zobaczyć podgląd.", + "filename": "kod-qr.png", + "errors": { + "generationFailed": "Nie udało się wygenerować kodu QR. Spróbuj krótszego tekstu lub niższej korekcji.", + "copyUnsupported": "Przeglądarka nie obsługuje kopiowania obrazów do schowka.", + "copyFailed": "Nie udało się skopiować do schowka." + } + }, + "IpSubnetCalculator": { + "title": "Kalkulator IP / podsieci", + "subtitle": "Szczegóły CIDR IPv4 i IPv6 w przeglądarce—bez żądań sieciowych.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Sieć", + "placeholderIpv4": "10.0.0.0/24 lub 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 lub 2001:db8::1/64", + "hintIpv4": "Użyj CIDR (np. /24) lub IPv4 z maską dziesiętną.", + "hintIpv6": "Podaj długość prefiksu (np. /64). Bez tego przyjmuje się /128.", + "copyValue": "Kopiuj wartość", + "rows": { + "cidr": "CIDR", + "network": "Adres sieci", + "netmask": "Maska podsieci", + "wildcard": "Maska wildcard", + "broadcast": "Broadcast", + "firstHost": "Pierwszy host", + "lastHost": "Ostatni host", + "totalAddresses": "Łącznie adresów", + "usableHosts": "Używalne hosty", + "prefixLength": "Długość prefiksu (bity)", + "networkIpv6": "Adres sieci", + "lastIpv6": "Ostatni adres w zakresie", + "totalAddressesIpv6": "Adresów w podsieci" + }, + "usableNote": { + "pointToPoint": "Łącza /31 point-to-point używają obu adresów jako hostów (RFC 3021).", + "singleHost": "/32 to pojedynczy host." + }, + "ipv6RangeHint": { + "normal": "Pokazano pełny zakres matematyczny; reguły routera mogą się różnić.", + "single": "/128 to jeden adres.", + "full": "/0 obejmuje cały IPv6." + }, + "errors": { + "invalidIpv4": "Nieprawidłowa sieć IPv4. Spróbuj CIDR lub adres + maska.", + "invalidIpv6": "Nieprawidłowa sieć IPv6. Użyj adres/prefiks, np. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Generator skrótów", + "subtitle": "Skróty MD5, SHA-1 i SHA-2 dla tekstu lub plików; bcrypt dla haseł (wejście tekstowe). Wszystko lokalnie w przeglądarce.", + "algorithmLabel": "Algorytm", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Tekst", + "modeFile": "Plik", + "inputLabelText": "Wejście", + "placeholderText": "Wpisz lub wklej tekst…", + "chooseFile": "Wybierz plik", + "clearFile": "Wyczyść", + "fileHint": "Pliki są czytane tylko w pamięci; maks. {maxMb} MB. Brak wysyłania.", + "outputLabel": "Skrót (hex)", + "copyHash": "Kopiuj skrót", + "copied": "Skopiowano", + "byteCount": "{count, plural, one {# bajt} few {# bajty} many {# bajtów} other {# bajta}}", + "charCount": "{count, plural, one {# znak} few {# znaki} many {# znaków} other {# znaku}}", + "emptyHint": "Wpisz tekst lub wybierz plik.", + "securityNote": "MD5 i SHA-1 nie są zalecane do nowych zastosowań bezpieczeństwa; użyj SHA-256 lub SHA-512.", + "errors": { + "fileTooLarge": "Plik większy niż {maxMb} MB.", + "fileReadFailed": "Nie można odczytać pliku." + }, + "bcryptRoundsLabel": "Koszt (rundy)", + "bcryptRoundsHint": "Wyższe wartości są wolniejsze i bardziej odporne na brute force. Zwykle domyślnie 10.", + "fileTabDisabledTitle": "bcrypt haszuje tylko hasła z tekstu, nie dowolne pliki.", + "placeholderBcrypt": "Wpisz lub wklej hasło do zahashowania…", + "outputLabelBcrypt": "Skrót bcrypt", + "copyBcrypt": "Kopiuj skrót", + "emptyHintBcrypt": "Wpisz hasło, aby obliczyć skrót bcrypt.", + "securityNoteBcrypt": "Każdy wynik bcrypt zawiera losową sól; to samo hasło daje za każdym razem inny ciąg. To oczekiwane. W produkcji używaj wysokiego kosztu.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Piaskownica szyfrowania", + "subtitle": "AES-GCM w przeglądarce: szyfruj do pakietu JSON hasłem lub surowym kluczem, a następnie odszyfruj lokalnie. Edukacyjnie i czasem praktycznie — nic nie jest wysyłane.", + "tabEncrypt": "Szyfruj", + "tabDecrypt": "Odszyfruj", + "keyModeLabel": "Materiał klucza", + "modePassphrase": "Hasło (PBKDF2 + AES-GCM)", + "modeRawKey": "Surowy klucz AES (hex lub Base64)", + "aesBitsLabel": "Długość klucza AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Kodowanie surowego klucza", + "encodingAuto": "Auto (hex jeśli wygląda na hex, w przeciwnym razie Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Surowy klucz", + "rawKeyPlaceholder": "Klucz 32 bajty jako 64 znaki hex lub Base64 dla AES-256 (długość zależy od wybranego AES).", + "rawKeyPlaceholderDecrypt": "Potrzebne tylko gdy pakiet ma keyDerivation \"none\".", + "passphraseLabel": "Hasło", + "passphrasePlaceholder": "Hasło używane tylko w przeglądarce do wyprowadzenia klucza.", + "passphrasePlaceholderDecrypt": "Potrzebne tylko gdy pakiet używa PBKDF2-SHA256.", + "pbkdf2IterLabel": "Iteracje PBKDF2", + "pbkdf2Hint": "Od 10 000 do 2 000 000. Domyślnie jak w typowych sejfach haseł.", + "plaintextLabel": "Tekst jawny", + "plaintextPlaceholder": "Wpisz lub wklej wiadomość do zaszyfrowania…", + "bundleInputLabel": "Pakiet zaszyfrowany (JSON)", + "bundlePlaceholder": "Wklej JSON z Szyfruj (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Pakiet zaszyfrowany", + "plaintextOutputLabel": "Tekst jawny", + "encryptButton": "Szyfruj", + "decryptButton": "Odszyfruj", + "copyBundle": "Kopiuj JSON", + "copyPlaintext": "Kopiuj tekst jawny", + "copied": "Skopiowano", + "emptyBundleHint": "Zaszyfrowany JSON pojawi się tu po zaszyfrowaniu.", + "emptyDecryptHint": "Odszyfrowany tekst pojawi się tu.", + "decryptKeyHint": "Użyj surowego klucza, gdy keyDerivation to \"none\"; hasła przy PBKDF2-SHA256. Pakiet przechowuje sól, iteracje, IV i długość AES.", + "securityNote": "Klucze i tekst jawny nie opuszczają przeglądarki. Do sekretów produkcyjnych używaj dedykowanych narzędzi.", + "errors": { + "emptyKey": "Podaj surowy klucz zgodny z rozmiarem AES.", + "emptyPassphrase": "Podaj hasło.", + "badKeyMaterial": "Nie można odczytać klucza jako hex ani Base64.", + "wrongKeyLength": "Długość klucza nie pasuje do AES (16 / 24 / 32 bajty).", + "invalidJson": "To nie jest prawidłowy JSON.", + "invalidBundle": "Ten JSON nie jest obsługiwanym pakietem szyfrowania.", + "decryptFailed": "Odszyfrowanie nie powiodło się — zły sekret, uszkodzony pakiet lub parametry.", + "encryptFailed": "Szyfrowanie nie powiodło się. Sprawdź Web Crypto i dane wejściowe." + } + }, + "HmacGenerator": { + "title": "Generator HMAC", + "subtitle": "Do podpisywania webhooków i testowania integracji API. HMAC-SHA1/256/384/512 w hex lub Base64—obliczane lokalnie w przeglądarce.", + "secretLabel": "Tajny klucz", + "messageLabel": "Wiadomość / ładunek", + "placeholderSecret": "Wklej tajny klucz podpisu…", + "placeholderMessage": "Wklej surowe ciało lub ciąg do uwierzytelnienia (często dokładne bajty webhooka).", + "algorithmLabel": "Algorytm HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Format wyjścia", + "formatHex": "Hex (małe litery)", + "formatBase64": "Base64", + "outputLabel": "Podpis", + "copyOutput": "Kopiuj podpis", + "copied": "Skopiowano", + "byteCountHint": "{count, plural, one {# bajt} few {# bajty} many {# bajtów} other {# bajta}} (UTF-8)", + "emptyHint": "Wpisz tajny klucz, aby obliczyć HMAC.", + "securityNote": "Nigdy nie udostępniaj tajnego klucza podpisu. Ta strona nie wysyła danych—wszystko działa w przeglądarce.", + "errors": { + "cryptoFailed": "Nie można obliczyć HMAC. Sprawdź obsługę Web Crypto i spróbuj ponownie.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Generator kodu TOTP / 2FA", + "subtitle": "Wklej klucz Base32 i zobacz bieżący sześciocyfrowy kod RFC 6238 (SHA-1, 30 s). Wszystko lokalnie w przeglądarce—przydatne przy testach logowania lub MFA.", + "secretLabel": "Tajny klucz (Base32)", + "placeholderSecret": "Wklej klucz (litery A–Z i cyfry 2–7, jak przy konfiguracji aplikacji uwierzytelniającej)…", + "codeLabel": "Bieżący kod", + "copyCode": "Kopiuj kod", + "copied": "Skopiowano", + "nextIn": "Odświeżenie za {seconds} s", + "standardHint": "Krok czasu 30 s, 6 cyfr, HMAC-SHA-1 (zgodne z Google Authenticator).", + "digitsLabel": "Cyfry", + "periodLabel": "Okres", + "algorithmLabel": "Algorytm", + "periodSeconds": "{seconds} s", + "emptyHint": "Wklej klucz, aby wygenerować sześciocyfrowy kod.", + "invalidHint": "Popraw klucz, aby zobaczyć kod.", + "securityNote": "Traktuj to jak hasło. Klucz nie opuszcza przeglądarki; nic nie jest wysyłane.", + "importLabel": "Importuj z URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importuj", + "qrSectionTitle": "Kod QR aprowizacji", + "qrHint": "Zeskanuj aplikacją uwierzytelniającą (Google Authenticator, 1Password, Aegis…), aby dodać ten sekret. Kod QR koduje sekret — traktuj go jak hasło.", + "accountLabel": "Konto", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Wystawca (opcjonalnie)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Wprowadź prawidłowy sekret, aby wygenerować kod QR.", + "errors": { + "invalidSecret": "To nie jest prawidłowy klucz Base32.", + "cryptoFailed": "Nie udało się obliczyć TOTP. Sprawdź Web Crypto i spróbuj ponownie.", + "invalidUri": "To nie jest prawidłowy identyfikator URI otpauth:// TOTP.", + "unsupportedUri": "Ten URI używa wartości, których narzędzie nie obsługuje (liczba cyfr musi wynosić 6 lub 8; okres 15, 30 lub 60 s)." + } + }, + "MarkdownPreview": { + "title": "Podgląd Markdown & HTML", + "subtitle": "Podgląd Markdown na żywo z eksportem HTML i konwersją HTML → Markdown. Wszystko działa w przeglądarce.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Wejście Markdown", + "htmlInputLabel": "Wejście HTML", + "previewLabel": "Podgląd", + "htmlOutputLabel": "Źródło HTML", + "mdOutputLabel": "Wyjście Markdown", + "inputPlaceholder": "# Cześć\\n\\nWpisz swój **markdown** tutaj…", + "htmlInputPlaceholder": "Wklej swój HTML tutaj…", + "copyHtml": "Kopiuj HTML", + "copyMarkdown": "Kopiuj Markdown", + "exportHtml": "Eksportuj HTML", + "copied": "Skopiowano", + "charCount": "{count, plural, one {# znak} few {# znaki} other {# znaków}}", + "emptyHint": "Zacznij wpisywać Markdown, aby zobaczyć podgląd na żywo i wyjście HTML.", + "emptyHintHtmlToMd": "Wklej HTML, aby przekonwertować go na Markdown.", + "securityNote": "Wszystko działa lokalnie w przeglądarce. Nic nie jest przesyłane." + }, + "NumberBaseConverter": { + "title": "Konwerter podstaw liczbowych", + "subtitle": "Konwersja liczb całkowitych między podstawami 2–36. Opcjonalne prefiksy 0x / 0b / 0o. Wszystko lokalnie w przeglądarce.", + "inputBaseLabel": "Z podstawy", + "outputBaseLabel": "Na podstawę", + "inputLabel": "Liczba wejściowa", + "placeholderInput": "Wpisz liczbę całkowitą w wybranej podstawie wejściowej…", + "outputLabel": "Wartość po konwersji", + "copyOutput": "Kopiuj wynik", + "copied": "Skopiowano", + "swap": "Zamień podstawy", + "byteCountHint": "{count, plural, one {# bajt} few {# bajty} many {# bajtów} other {# bajta}} (długość UTF-8)", + "emptyHint": "Wpisz liczbę do konwersji.", + "note": "Cyfry powyżej 9 to litery A–Z (bez rozróżniania wielkości). Spacje i podkreślenia są ignorowane.", + "errors": { + "invalidNumber": "Nieprawidłowa wartość dla wybranej podstawy wejściowej." + } + }, + "FormatConverter": { + "title": "Konwerter formatów", + "subtitle": "Konwertuj między JSON, YAML, TOML i XML w dowolnym kierunku. Wszystkie 12 kombinacji. Wszystko działa w Twojej przeglądarce.", + "fromLabel": "Z", + "toLabel": "Na", + "swap": "Zamień", + "swapTitle": "Zamień formaty i użyj wyjścia jako wejścia", + "copy": "Kopiuj", + "copied": "Skopiowano", + "charCount": "{count, plural, one {# znak} few {# znaki} other {# znaków}}", + "emptyHint": "Wklej lub wpisz dane wejściowe po lewej stronie, aby zobaczyć przekonwertowane wyjście.", + "localNote": "Wszystko działa lokalnie w Twojej przeglądarce. Nic nie jest przesyłane." + }, + "MimeTypeLookup": { + "title": "Wyszukiwanie typu MIME", + "subtitle": "Sprawdź typ MIME (media type) dla rozszerzenia pliku lub pełnej nazwy pliku. Wszystko działa lokalnie w Twojej przeglądarce.", + "inputLabel": "Rozszerzenie lub nazwa pliku", + "placeholder": "np. .png, package.json, photo.jpeg", + "hint": "Wskazówka: wklej rozszerzenie (z kropką lub bez) albo pełną nazwę pliku.", + "clear": "Wyczyść", + "exampleFilename": "Użyj przykładowej nazwy pliku", + "exampleExtension": "Użyj przykładowego rozszerzenia", + "resultLabel": "Wynik", + "normalizedLabel": "Znormalizowane wejście:", + "mimeTypeLabel": "Typ MIME", + "contentTypeLabel": "Nagłówek Content-Type", + "charsetLabel": "Zestaw znaków", + "copyMimeType": "Kopiuj typ MIME", + "copy": "Kopiuj", + "copied": "Skopiowano", + "emptyHint": "Wpisz rozszerzenie lub nazwę pliku, aby sprawdzić typ MIME.", + "notFound": "Nie znaleziono typu MIME dla tego wejścia.", + "localNote": "Wszystko działa lokalnie w Twojej przeglądarce. Nic nie jest przesyłane." + }, + "UserAgentParser": { + "title": "Parser User-Agent", + "subtitle": "Wklej ciąg User-Agent (UA) i zobacz podział na przeglądarkę, system, silnik, urządzenie i CPU. Wszystko działa lokalnie w Twojej przeglądarce.", + "inputLabel": "Ciąg User-Agent", + "placeholder": "Wklej tutaj swój User-Agent…", + "hint": "Wskazówka: UA możesz skopiować z nagłówków żądania, logów lub narzędzi deweloperskich przeglądarki.", + "clear": "Wyczyść", + "exampleDesktop": "Użyj przykładu na komputer", + "exampleMobile": "Użyj przykładu na telefon", + "resultLabel": "Podział", + "browserLabel": "Przeglądarka", + "osLabel": "System operacyjny", + "engineLabel": "Silnik", + "deviceTypeLabel": "Typ urządzenia", + "deviceVendorLabel": "Producent", + "deviceModelLabel": "Model", + "cpuArchLabel": "Architektura CPU", + "copyJson": "Kopiuj JSON", + "copy": "Kopiuj", + "copied": "Skopiowano", + "emptyHint": "Wklej ciąg User-Agent, aby zobaczyć podział.", + "localNote": "Wszystko działa lokalnie w Twojej przeglądarce. Nic nie jest przesyłane." + }, + "MockDataGenerator": { + "title": "Generator danych testowych", + "subtitle": "Zdefiniuj kolumny, wybierz typy i eksportuj zestawy danych do seedów, Postmana lub testów. Do {max} wierszy, wszystko w przeglądarce — nic nie jest wysyłane.", + "mockarooLink": "Przepływ jak w Mockaroo", + "rowsLabel": "Wiersze", + "rowsHint": "Maks. {max} wierszy na generację, by karta była responsywna.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nazwa tabeli SQL", + "presetsLabel": "Szablony", + "presetPlaceholder": "Wczytaj szablon…", + "presetDefault": "Start (użytkownik)", + "presetUsers": "Użytkownicy i role", + "presetProducts": "SKU e-commerce", + "presetApiLog": "Dziennik żądań HTTP", + "generate": "Generuj", + "warningDuplicateNames": "Zduplikowane nazwy kolumn: późniejsze nadpisują wcześniejsze w obiektach JSON. Zmień nazwy.", + "schemaHeading": "Schemat", + "addField": "Dodaj kolumnę", + "removeField": "Usuń kolumnę", + "duplicateField": "Duplikuj kolumnę", + "colName": "Nazwa", + "colType": "Typ", + "colOptions": "Opcje", + "colBlank": "Puste %", + "optMin": "min", + "optMax": "max", + "optDecimals": "dec", + "optDateFrom": "od daty", + "optDateTo": "do daty", + "optValues": "a, b, c", + "optStep": "krok", + "optRowBase": "baza", + "output": "Wynik", + "outputPlaceholder": "Kliknij Generuj, aby utworzyć dane…", + "copy": "Kopiuj", + "download": "Pobierz", + "copied": "Skopiowano", + "groups": { + "personal": "Dane osobowe", + "location": "Lokalizacja", + "internetApi": "Internet i API", + "numbersIds": "Liczby i identyfikatory", + "dateTime": "Data i czas", + "textDesign": "Tekst i wygląd", + "custom": "Niestandardowe" + }, + "fieldTypes": { + "first_name": "Imię", + "last_name": "Nazwisko", + "full_name": "Imię i nazwisko", + "email": "Email", + "username": "Nazwa użytkownika", + "password": "Hasło", + "phone": "Telefon", + "company": "Firma", + "job_title": "Stanowisko", + "avatar_url": "URL awatara", + "city": "Miasto", + "country": "Kraj", + "country_code": "Kod kraju (ISO)", + "street_address": "Adres", + "zip_code": "Kod pocztowy", + "latitude": "Szerokość geogr.", + "longitude": "Długość geogr.", + "url": "URL", + "domain": "Domena", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Liczba całkowita", + "float": "Liczba zmiennoprzecinkowa", + "boolean": "Wartość logiczna", + "uuid": "UUID", + "credit_card": "Karta (test)", + "semver": "SemVer", + "row_index": "Indeks wiersza", + "sequence": "Sekwencja", + "date": "Data", + "datetime": "Data i godzina", + "unix_timestamp": "Znacznik czasu Unix", + "timezone": "Strefa czasowa", + "word": "Słowo", + "sentence": "Zdanie", + "paragraph": "Akapit", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Kod waluty", + "language_code": "Znacznik języka", + "custom_list": "Lista niestandardowa" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Optymalizator SVG", + "subtitle": "Wklej SVG, minifikuj przez SVGO w przeglądarce i porównaj rozmiar UTF-8 przed i po.", + "inputLabel": "Wejście", + "outputLabel": "Zoptymalizowano", + "placeholder": "Wklej fragment lub cały plik…", + "outputPlaceholder": "Zoptymalizowany SVG pojawi się tutaj…", + "chars": "{count} znaków • {bytes} bajtów (UTF-8)", + "preview": "Podgląd", + "localNote": "Przetwarzanie lokalnie w przeglądarce. Nic nie jest wysyłane.", + "stats": { + "title": "Rozmiar (UTF-8)", + "before": "Przed", + "after": "Po", + "savedBytes": "Zaoszczędzono {count} bajtów ({percent}%)", + "noChange": "Brak redukcji — już minimalne lub puste wyjście." + }, + "actions": { + "clear": "Wyczyść", + "sample": "Wczytaj przykład", + "copy": "Kopiuj", + "copied": "Skopiowano", + "download": "Pobierz" + }, + "errors": { + "optimizeFailed": "Nie udało się zoptymalizować tego SVG." + } + }, + "UnitConverter": { + "title": "Konwerter jednostek", + "subtitle": "Przeliczaj między 323 jednostkami w 43 kategoriach", + "category": "Kategoria", + "from": "Z", + "to": "Na", + "value": "Wartość", + "result": "Wynik", + "swapUnits": "Zamień jednostki", + "placeholder": "Wprowadź wartość", + "copyResult": "Kopiuj wynik", + "searchCategory": "Szukaj kategorii...", + "selectCategory": "Wybierz kategorię", + "noResults": "Nie znaleziono kategorii." + }, + "DockerComposeGenerator": { + "title": "Generator Docker Compose", + "subtitle": "Wybierz popularne bazy, cache, kolejki komunikatów, reverse proxy i obserwowalność — skopiuj działający docker-compose.yml na lokalny development.", + "searchPlaceholder": "Szukaj (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Usługi", + "selectedCount": "{count, plural, one {# usługa} few {# usługi} many {# usług} other {# usługi}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopiuj", + "copied": "Skopiowano!", + "download": "Pobierz", + "emptyState": "Wybierz jedną lub więcej usług, aby wygenerować YAML Compose.", + "hintPorts": "Domyślne porty hosta ograniczają konflikty; zmień je, jeśli Docker zgłosi zajęty port. Zamień hasła demonstracyjne przed środowiskami współdzielonymi.", + "categories": { + "databases": "Bazy danych i magazyny", + "cache": "Cache i pamięć operacyjna", + "queues": "Kolejki i messaging", + "search": "Wyszukiwanie i logi", + "storage": "Magazyn obiektów", + "proxy": "Reverse proxy i ingress", + "observability": "Metryki i śledzenie", + "mail": "E-mail (lokalny dev)", + "admin": "Interfejsy administracyjne i BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Wyczyść" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (zgodny z Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (zgodny z Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (zgodny z S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (przechwytywanie SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (serwer developerski)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Przesuń płytki i połącz je, aby osiągnąć 2048", + "score": "Wynik", + "best": "Z poważaniem", + "newGame": "Nowa gra", + "howToPlay": "Użyj klawiszy strzałek (lub przesuń palcem po telefonie komórkowym), aby przesunąć kafelki. Kafelki o tej samej liczbie scalają się. Osiągnij 2048, aby wygrać!", + "youWin": "Osiągnąłeś 2048 rok!", + "keepGoing": "Nie przerywaj", + "gameOver": "Koniec gry", + "tryAgain": "Ponów", + "gameOverMessage": "Nie ma więcej ruchów.", + "undo": "Cofnij" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "etapy ukończone", + "time": "Okres", + "best": "Z poważaniem", + "difficulty": { + "easy": "Łatwo", + "medium": "Średni", + "hard": "Twarda" + }, + "stage": "Etap", + "solved": "Rozwiązane", + "score": "Wynik", + "nextStage": "Następny etap", + "replay": "Powtórka", + "erase": "Wymaż", + "howToPlay": "Kliknij komórkę, a następnie naciśnij liczbę lub naciśnij 1–9. Backspace lub Delete czyści komórkę. Pogrubione liczby są wskazówkami. Wypełnij siatkę tak, aby każdy wiersz, kolumna i pole 3×3 zawierały 1–9 bez powtórzeń.", + "pencil": "Ołówek", + "undo": "Cofnij", + "hint": "Podpowiedź", + "hintsUsed": "podpowiedzi użyte", + "mistakes": "Błędy", + "failed": "Niepowodzenie!", + "mistakeMode": "Tryb 3 błędów" + }, + "Snake": { + "title": "Wąż", + "subtitle": "Jedz jedzenie, rośnij, unikaj ścian i siebie", + "score": "Wynik", + "best": "Najlepszy", + "startPrompt": "Naciśnij dowolny klawisz strzałki", + "startButton": "Rozpocznij grę", + "gameOver": "Koniec gry", + "playAgain": "Zagraj ponownie", + "howToPlay": "Używaj klawiszy strzałek lub WASD. Jedz jedzenie, aby rosnąć. Prędkość rośnie co 50 punktów.", + "diffEasy": "Łatwy", + "diffNormal": "Normalny", + "diffHard": "Trudny", + "diffInfinite": "Nieskończony", + "orTap": "lub stuknij poniżej", + "howToPlayInfinite": "Ściany się owijają — brak śmierci od ścian. Jedz jedzenie, aby rosnąć.", + "shieldActive": "Tarcza aktywna", + "speedBoost": "Przyspieszenie", + "powerSpeed": "Prędkość x2", + "powerShield": "Przeżyj jedną śmierć", + "powerShrink": "Skraca ogon" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Połączenia Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Połącz się z instancjami Redis. Przeglądaj klucze, edytuj wartości i uruchamiaj polecenia. Dane uwierzytelniające są szyfrowane w przeglądarce.", + "heroAddConnection": "Dodaj połączenie", + "vaultLockedTitle": "Skarbiec zablokowany", + "vaultLockedDesc": "Odblokuj skarbiec, aby uzyskać dostęp do zapisanych połączeń." + } + }, + "Minesweeper": { + "title": "Saper", + "subtitle": "Znajdź wszystkie bezpieczne pola bez detonacji miny", + "mines": "Miny", + "newGame": "Nowa gra", + "win": "Wygrałeś!", + "loss": "Przegrałeś!", + "playAgain": "Zagraj ponownie", + "tryAgain": "Spróbuj ponownie", + "startPrompt": "Kliknij dowolne pole, aby rozpocząć", + "difficulty": { + "beginner": "Początkujący", + "intermediate": "Średniozaawansowany", + "expert": "Ekspert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Ułóż spadające bloki, aby uzupełnić rzędy", + "next": "Następny", + "score": "Wynik", + "best": "Najlepszy", + "lines": "Linie", + "level": "Poziom", + "pause": "Pauza", + "resume": "Wznów", + "start": "Start", + "restart": "Restart", + "paused": "Wstrzymano", + "gameOver": "Koniec gry" + }, + "SshKeyGenerator": { + "title": "Generator kluczy SSH / RSA", + "subtitle": "Generuj pary kluczy Ed25519 lub RSA w przeglądarce. Pobierz klucze prywatny i publiczny w formatach OpenSSH i PEM. Nic nie jest przesyłane.", + "keyTypeLabel": "Typ klucza", + "recommended": "zalecane", + "ed25519Desc": "Szybki, mały i nowoczesny. Obsługiwany przez GitHub, GitLab i wszystkie główne serwery SSH.", + "rsa2048Desc": "Szeroka kompatybilność. Bezpieczny dla większości zastosowań.", + "rsa4096Desc": "Maksymalna kompatybilność i siła dla starszych systemów.", + "commentLabel": "Komentarz (opcjonalnie)", + "commentHint": "Dołączany do linii klucza publicznego OpenSSH.", + "generateBtn": "Generuj parę kluczy", + "generating": "Generowanie…", + "generatingLong": "Generowanie (może chwilę potrwać)…", + "copy": "Kopiuj", + "download": "Pobierz", + "privateKeyLabel": "Klucz prywatny (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Klucz publiczny (format OpenSSH)", + "publicKeySpkiLabel": "Klucz publiczny (SPKI PEM)", + "browserNote": "Wszystkie klucze są generowane w przeglądarce. Nic nie jest wysyłane na żaden serwer.", + "errors": { + "generationFailed": "Generowanie klucza nie powiodło się" + } + }, + "DnsLookup": { + "title": "Wyszukiwanie DNS", + "subtitle": "Zapytaj rekordy DNS dla dowolnej domeny — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Wpisz domenę (np. github.com)", + "lookupBtn": "Szukaj", + "looking": "Wyszukiwanie…", + "refreshBtn": "Odśwież", + "recordTypesLabel": "Typy rekordów", + "allBtn": "Wszystkie", + "tryLabel": "Spróbuj:", + "errors": { + "lookupFailed": "Wyszukiwanie DNS nie powiodło się" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Generator i weryfikator Bcrypt", + "subtitle": "Haszuj hasła za pomocą bcrypt i weryfikuj hasze — wszystko zostaje w Twojej przeglądarce", + "tabs": { + "hash": "Hasz", + "verify": "Weryfikuj" + }, + "showPassword": "Pokaż hasło", + "hidePassword": "Ukryj hasło", + "copy": "Kopiuj", + "copied": "Skopiowano", + "hash": { + "passwordLabel": "Hasło", + "passwordPlaceholder": "Wpisz hasło do zahaszowania…", + "costLabel": "Współczynnik kosztu", + "costHint": "Wyższy koszt podwaja pracę na każdym kroku. 10-12 to typowy wybór dla produkcji.", + "slowWarning": "Współczynniki kosztu 13 i wyższe mogą obliczać się kilka sekund.", + "generate": "Wygeneruj hasz", + "generating": "Haszowanie…", + "resultLabel": "Hasz bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Budowa hasza", + "algorithm": "algorytm", + "cost": "koszt", + "salt": "sól (22 znaki)", + "hash": "hasz (31 znaków)" + } + }, + "verify": { + "passwordLabel": "Hasło", + "passwordPlaceholder": "Wpisz hasło do sprawdzenia…", + "hashLabel": "Hasz bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Wpisz hasło i hasz bcrypt, aby je porównać.", + "invalidHash": "To nie wygląda na prawidłowy hasz bcrypt.", + "checking": "Porównywanie…", + "match": "Zgodne — hasło daje ten hasz.", + "noMatch": "Niezgodne — hasło nie daje tego hasza." + } + }, + "ChmodCalculator": { + "title": "Kalkulator Chmod", + "subtitle": "Konwertuj uprawnienia plików Unix między polami wyboru, postacią ósemkową i symboliczną", + "grid": { + "title": "Uprawnienia", + "read": "Odczyt", + "write": "Zapis", + "execute": "Wykonanie", + "owner": "Właściciel", + "group": "Grupa", + "others": "Pozostali" + }, + "special": { + "title": "Bity specjalne", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Ósemkowo", + "placeholder": "755 lub 4755", + "invalid": "Wpisz 3 lub 4 cyfry ósemkowe (0-7)." + }, + "symbolic": { + "label": "Symbolicznie", + "placeholder": "rwxr-xr-x", + "invalid": "Wpisz 9 znaków w rodzaju rwxr-xr-x (dozwolone s/S, t/T)." + }, + "output": { + "title": "Polecenie", + "copy": "Kopiuj", + "copied": "Skopiowano" + }, + "explain": { + "line": "{cls} może {perms}.", + "none": "{cls} nie ma uprawnień.", + "setuid": "Setuid: plik jest uruchamiany z uprawnieniami swojego właściciela.", + "setgid": "Setgid: plik jest uruchamiany z uprawnieniami swojej grupy (nowe pliki w katalogu dziedziczą jego grupę).", + "sticky": "Sticky: tylko właściciel pliku może go usunąć lub zmienić jego nazwę w tym katalogu." + }, + "presets": { + "title": "Popularne ustawienia", + "octal": "Ósemkowo", + "permissions": "Uprawnienia", + "description": "Typowe zastosowanie", + "p644": "Zwykłe pliki: właściciel zapisuje, wszyscy odczytują.", + "p755": "Pliki wykonywalne i katalogi: wszyscy mogą odczytać i wejść.", + "p600": "Pliki prywatne: tylko właściciel odczytuje i zapisuje.", + "p700": "Prywatne pliki wykonywalne i katalogi: tylko właściciel.", + "p664": "Pliki zapisywalne dla grupy.", + "p775": "Katalogi i pliki wykonywalne zapisywalne dla grupy.", + "p400": "Tylko do odczytu dla właściciela (np. klucze prywatne SSH)." + } + }, + "CodeScreenshot": { + "title": "Zrzut kodu", + "subtitle": "Zamień kod w piękne obrazy gotowe do udostępnienia", + "languageLabel": "Język", + "autoDetect": "Wykrywanie automatyczne", + "backgroundLabel": "Tło", + "paddingLabel": "Odstęp", + "filenameLabel": "Nazwa pliku", + "filenamePlaceholder": "example.ts", + "windowLabel": "Kropki okna", + "themeLabel": "Motyw okna", + "themeDark": "Ciemny", + "themeLight": "Jasny", + "scaleLabel": "Skala eksportu", + "copyImage": "Kopiuj obraz", + "copied": "Skopiowano", + "exportPng": "Eksportuj PNG", + "inputLabel": "Kod", + "detected": "Wykryto: {language}", + "clear": "Wyczyść", + "placeholder": "Wklej swój kod tutaj…", + "previewLabel": "Podgląd", + "emptyPreview": "Podgląd Twojego kodu pojawi się tutaj", + "exportFailed": "Nie udało się wyeksportować obrazu", + "copyUnsupported": "Kopiowanie obrazów nie jest obsługiwane w tej przeglądarce", + "copyFailed": "Nie udało się skopiować obrazu", + "backgrounds": { + "ocean": "Ocean", + "sunset": "Zachód słońca", + "candy": "Cukierek", + "forest": "Las", + "midnight": "Północ", + "gold": "Złoto", + "aurora": "Zorza", + "slate": "Łupek", + "transparent": "Przezroczysty" + } + }, + "CssGenerators": { + "title": "Generatory CSS", + "subtitle": "Box-shadow, border-radius i płynny clamp() — skopiuj CSS.", + "preview": "Podgląd", + "output": "CSS", + "copy": "Kopiuj", + "copied": "Skopiowano", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Płynny clamp()" + }, + "boxShadow": { + "presets": "Ustawienia", + "layer": "Warstwa", + "addLayer": "Dodaj warstwę", + "removeLayer": "Usuń warstwę", + "x": "X", + "y": "Y", + "blur": "Rozmycie", + "spread": "Rozszerzenie", + "color": "Kolor", + "alpha": "Alfa", + "inset": "Wewnętrzny", + "presetNames": { + "subtleCard": "Subtelna karta", + "elevated": "Uniesiony", + "hardOffset": "Twarde przesunięcie", + "neonGlow": "Neonowa poświata", + "inner": "Wewnętrzny", + "layeredSmooth": "Miękkie warstwy" + } + }, + "borderRadius": { + "modeLabel": "Tryb", + "modes": { + "simple": "Na róg", + "blob": "Organiczna plama" + }, + "unit": "Jednostka", + "linkCorners": "Powiąż rogi", + "randomize": "Losuj", + "horizontal": "Promienie poziome", + "vertical": "Promienie pionowe", + "corners": { + "tl": "Górny lewy", + "tr": "Górny prawy", + "br": "Dolny prawy", + "bl": "Dolny lewy" + } + }, + "clamp": { + "minFont": "Min. czcionka (px)", + "maxFont": "Maks. czcionka (px)", + "minViewport": "Min. viewport (px)", + "maxViewport": "Maks. viewport (px)", + "rootFont": "Czcionka główna (px)", + "viewportWidth": "Symulowany viewport", + "computedSize": "Obliczony rozmiar", + "slope": "Nachylenie", + "intercept": "Punkt przecięcia", + "previewText": "Szybki brązowy lis", + "invalid": "Wpisz prawidłowe i różniące się granice viewportu." + } + }, + "ExifViewer": { + "title": "Podgląd i usuwanie EXIF", + "subtitle": "Sprawdzaj metadane zdjęć i usuwaj je w swojej przeglądarce", + "dropTitle": "Upuść obraz tutaj", + "dropHint": "lub kliknij, aby przeglądać — Twój obraz nigdy nie opuszcza urządzenia", + "browse": "Wybierz obraz", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Odczytywanie metadanych…", + "parseFailed": "Nie udało się odczytać tego obrazu", + "clear": "Usuń obraz", + "gpsWarning": "Zawiera lokalizację GPS", + "openMap": "Zobacz na OpenStreetMap", + "noMetadata": "Nie znaleziono osadzonych metadanych w tym obrazie.", + "facts": { + "name": "Nazwa", + "type": "Typ", + "dimensions": "Wymiary", + "size": "Rozmiar" + }, + "groups": { + "camera": "Aparat", + "exposure": "Ekspozycja", + "timestamps": "Znaczniki czasu", + "gps": "Lokalizacja GPS", + "software": "Oprogramowanie i inne" + }, + "gpsCard": { + "latitude": "Szerokość geogr.", + "longitude": "Długość geogr.", + "altitude": "Wysokość" + }, + "raw": { + "title": "Wszystkie metadane", + "copyJson": "Kopiuj JSON", + "copied": "Skopiowano" + }, + "remove": { + "title": "Usuń metadane", + "jpegDescription": "Bezstratnie usuń EXIF, XMP, IPTC i komentarze bez ponownej kompresji obrazu.", + "reencodeDescription": "Przekoduj obraz, aby usunąć osadzone metadane. Powoduje to ponowną kompresję pikseli.", + "keepIcc": "Zachowaj profil kolorów (ICC)", + "stripButton": "Usuń metadane", + "reencodeButton": "Przekoduj i usuń", + "unsupported": "Usuwanie metadanych nie jest obsługiwane dla tego typu pliku.", + "failed": "Nie udało się usunąć metadanych", + "before": "Przed", + "after": "Po", + "download": "Pobierz oczyszczony obraz" + } + }, + "FaviconGenerator": { + "title": "Generator favicon", + "subtitle": "Twórz favicony i ikony aplikacji z jednego obrazu", + "dropTitle": "Upuść obraz tutaj", + "dropHint": "PNG, JPEG lub SVG — kliknij, aby przeglądać", + "browse": "Wybierz obraz", + "clear": "Usuń obraz", + "backgroundLabel": "Tło", + "transparent": "Przezroczysty", + "solidColor": "Jednolity", + "cornerLabel": "Kształt rogów", + "cornerNone": "Kwadrat", + "cornerRounded": "Zaokrąglony", + "cornerCircle": "Koło", + "maskPaddingLabel": "Odstęp maskable", + "maskable": "maskable", + "previewLabel": "Podgląd", + "copy": "Kopiuj", + "headSnippet": "Fragment head HTML", + "downloadIco": "Pobierz favicon.ico", + "downloadZip": "Pobierz wszystko (.zip)" + }, + "JsonDiff": { + "title": "Porównanie JSON", + "subtitle": "Semantyczne, uwzględniające strukturę porównanie dwóch dokumentów JSON — kolejność kluczy i formatowanie są ignorowane", + "options": { + "ignoreArrayOrder": "Ignoruj kolejność tablic" + }, + "loadBothSamples": "Wczytaj przykłady", + "sample": "Przykład", + "format": "Formatuj", + "clear": "Wyczyść", + "formatError": "Nie można sformatować: dane wejściowe nie są prawidłowym JSON", + "placeholder": "Wklej lub wpisz JSON tutaj…", + "panels": { + "left": "Oryginał (lewa)", + "right": "Zmodyfikowany (prawa)", + "results": "Różnice" + }, + "summary": { + "added": "{count} dodano", + "removed": "{count} usunięto", + "changed": "{count} zmieniono" + }, + "types": { + "added": "Dodano", + "removed": "Usunięto", + "changed": "Zmieniono" + }, + "copyChanges": "Kopiuj jako JSON", + "copied": "Skopiowano", + "empty": "Wklej JSON do obu paneli, aby zobaczyć ich różnice.", + "identical": "Oba dokumenty są semantycznie identyczne." + }, + "JsonpathPlayground": { + "title": "Piaskownica JSONPath", + "subtitle": "Odpytuj dokumenty JSON za pomocą JSONPath lub JMESPath i oglądaj wyniki na żywo", + "engineLabel": "Silnik", + "queryLabel": "Zapytanie", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Ściąga", + "cheats": { + "jpAllTitles": "Wszystkie tytuły książek", + "jpAllAuthors": "Wszyscy autorzy, w dowolnym miejscu dokumentu", + "jpFirstBook": "Pierwsza książka", + "jpLastBook": "Ostatnia książka", + "jpSlice": "Pierwsze dwie książki (wycinek)", + "jpFilterPrice": "Książki tańsze niż 40", + "jpFilterInStock": "Tytuły książek dostępnych w magazynie", + "jpRecursive": "Wszystkie wartości, rekurencyjnie", + "jmAllTitles": "Wszystkie tytuły książek", + "jmFirstAuthor": "Autor pierwszej książki", + "jmLastBook": "Ostatnia książka", + "jmSlice": "Pierwsze dwie książki (wycinek)", + "jmFilterPrice": "Tytuły książek tańszych niż 40", + "jmCount": "Policz książki dostępne w magazynie", + "jmProjection": "Przekształć każdą książkę w nowy obiekt", + "jmSort": "Tytuły posortowane według ceny" + }, + "panels": { + "input": "Wejście JSON", + "results": "Wyniki" + }, + "sample": "Przykład", + "format": "Formatuj", + "clear": "Wyczyść", + "formatError": "Nie można sformatować: dane wejściowe nie są prawidłowym JSON", + "placeholder": "Wklej lub wpisz JSON tutaj…", + "outputPlaceholder": "Dopasowania pojawią się tutaj…", + "matchCount": "{count, plural, one {# dopasowanie} few {# dopasowania} many {# dopasowań} other {# dopasowania}}", + "copy": "Kopiuj", + "copied": "Skopiowano" + }, + "KeycodeInspector": { + "title": "Inspektor klawiszy", + "subtitle": "Naciśnij dowolny klawisz, aby zbadać jego zdarzenie klawiatury JavaScript", + "capture": { + "prompt": "Naciśnij dowolny klawisz…", + "ready": "Nasłuchiwanie — naciśnij dowolny klawisz na klawiaturze", + "clickToFocus": "Kliknij tutaj, aby ustawić fokus, a następnie naciśnij dowolny klawisz" + }, + "hero": { + "spaceKey": "(Spacja)", + "keyLabel": "event.key", + "repeat": "powtórzenie" + }, + "copyJson": "Kopiuj JSON", + "copiedJson": "JSON zdarzenia skopiowano do schowka", + "deprecated": "przestarzałe", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standardowa", + "left": "Lewa", + "right": "Prawa", + "numpad": "Klawiatura numeryczna", + "unknown": "Nieznana" + }, + "history": { + "title": "Historia", + "clear": "Wyczyść historię", + "empty": "Naciśnięcia klawiszy pojawią się tutaj." + } + }, + "MarkdownTableGenerator": { + "title": "Generator tabel Markdown", + "subtitle": "Edytuj siatkę, wklej z arkusza i skopiuj ładnie wyrównaną tabelę Markdown lub HTML", + "addRow": "Dodaj wiersz", + "addColumn": "Dodaj kolumnę", + "importLabel": "Importuj", + "importPlaceholder": "Wklej TSV (z Excela / Arkuszy Google) lub CSV tutaj…", + "importButton": "Importuj do siatki", + "pasteClipboard": "Wklej ze schowka", + "importEmpty": "Nie ma czego importować — najpierw wklej kilka wierszy", + "clipboardError": "Nie udało się odczytać ze schowka", + "clear": "Wyczyść", + "panels": { + "editor": "Edytor tabeli", + "output": "Wynik" + }, + "align": { + "left": "Wyrównaj do lewej", + "center": "Wyśrodkuj", + "right": "Wyrównaj do prawej" + }, + "headerPlaceholder": "Nagłówek", + "cellPlaceholder": "Komórka", + "removeRow": "Usuń wiersz", + "removeColumn": "Usuń kolumnę", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopiuj", + "copied": "Skopiowano" + }, + "TimezoneConverter": { + "title": "Konwerter stref czasowych", + "subtitle": "Konwertuj jeden moment na całym świecie, aby planować spotkania.", + "dateTimeLabel": "Data i godzina", + "now": "Teraz", + "sourceZoneLabel": "Źródłowa strefa czasowa", + "selectZone": "Wybierz strefę", + "searchZone": "Szukaj stref czasowych...", + "noZone": "Nie znaleziono strefy czasowej.", + "scrubLabel": "Przesuń czas", + "reset": "Resetuj", + "targetsLabel": "Strefy docelowe", + "addZone": "Dodaj strefę", + "remove": "Usuń", + "localBadge": "Lokalna", + "invalidDate": "Wpisz prawidłową datę i godzinę.", + "bands": { + "work": "Godziny pracy (09:00–18:00)", + "edge": "Godziny brzegowe (wczesny ranek / wieczór)", + "night": "Noc" + } + }, + "TokenCounter": { + "title": "Licznik tokenów LLM", + "subtitle": "Licz tokeny, wizualizuj chunki i szacuj koszt API.", + "inputLabel": "Tekst", + "sample": "Przykład", + "clear": "Wyczyść", + "placeholder": "Wklej tekst, kod lub JSON, aby policzyć tokeny...", + "encoding": "Kodowanie", + "loading": "Ładowanie tokenizera...", + "encodings": { + "o200k": "o200k_base (GPT-4o / seria o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokeny", + "characters": "Znaki", + "words": "Słowa", + "ratio": "Tokeny/słowo", + "ratioSub": "ok." + }, + "claudeRow": "Claude (przybliżone)", + "estimate": "szacunek", + "viz": { + "title": "Wizualizacja tokenów", + "toggle": "Pokaż", + "hint": "Włącz, aby pokolorować każdy token jako chunk.", + "empty": "Wpisz tekst, aby zwizualizować tokeny.", + "truncated": "Pokazano pierwsze {limit} z {total} tokenów." + }, + "cost": { + "title": "Kalkulator kosztów", + "disclaimer": "edytowalne wartości domyślne — sprawdź aktualne ceny", + "model": "Model", + "expectedOutput": "Oczekiwane tokeny wyjściowe", + "customInput": "Wejście $/MTok", + "customOutput": "Wyjście $/MTok", + "inputCost": "Koszt wejścia", + "outputCost": "Koszt wyjścia", + "total": "Razem", + "per1000": "Na 1000 wywołań" + } + }, + "WebhookTester": { + "title": "Tester webhooków", + "subtitle": "Utwórz tymczasowy URL i sprawdzaj przychodzące żądania webhooków na żywo.", + "intro": { + "title": "Utwórz URL webhooka, aby zacząć przechwytywać żądania", + "hint": "Otrzymasz niemożliwy do odgadnięcia URL, który rejestruje każde przychodzące żądanie. Wygasa po 24 godzinach." + }, + "createBtn": "Utwórz URL webhooka", + "creating": "Tworzenie…", + "urlLabel": "Twój URL webhooka", + "expiresIn": "Wygasa za {countdown}", + "copyUrlBtn": "Kopiuj URL", + "copied": "Skopiowano", + "clearBtn": "Wyczyść", + "newBinBtn": "Nowy URL", + "requestsTitle": "Żądania", + "selectPrompt": "Wybierz żądanie, aby sprawdzić jego szczegóły.", + "expiredNotice": "Ten URL webhooka wygasł. Utwórz nowy, aby kontynuować testowanie.", + "base64Badge": "base64", + "truncatedBadge": "obcięte", + "empty": { + "title": "Oczekiwanie na żądania…", + "hint": "Wyślij żądanie na swój URL, a pojawi się tutaj natychmiast. Spróbuj tak:" + }, + "detail": { + "receivedAt": "Odebrano", + "source": "IP źródłowe", + "contentType": "Content-Type", + "query": "Parametry query", + "headers": "Nagłówki", + "body": "Treść", + "copyBody": "Kopiuj treść", + "emptyBody": "Brak treści" + }, + "errors": { + "createFailed": "Nie udało się utworzyć URL webhooka. Spróbuj ponownie.", + "clearFailed": "Nie udało się wyczyścić żądań. Spróbuj ponownie." + } + }, + "WebsocketTester": { + "title": "Tester WebSocket", + "subtitle": "Połącz się z endpointem WebSocket i wysyłaj, odbieraj oraz sprawdzaj wiadomości na żywo", + "urlLabel": "URL endpointu", + "protocolsLabel": "Podprotokoły", + "protocolsPlaceholder": "opcjonalnie, oddzielone przecinkami", + "connect": "Połącz", + "disconnect": "Rozłącz", + "status": { + "disconnected": "Rozłączono", + "connecting": "Łączenie", + "connected": "Połączono", + "error": "Błąd" + }, + "messages": { + "connecting": "Łączenie z {url}…", + "connected": "Połączenie nawiązane", + "connectedWithProtocol": "Połączenie nawiązane (protokół: {protocol})", + "invalidUrl": "Nieprawidłowy URL — musi zaczynać się od ws:// lub wss://", + "connectFailed": "Nie udało się otworzyć połączenia", + "socketError": "Wystąpił błąd połączenia", + "notConnected": "Nie połączono — najpierw się połącz", + "binaryMessage": "Odebrano wiadomość binarną" + }, + "log": { + "title": "Dziennik wiadomości", + "empty": "Brak wiadomości. Połącz się i zacznij wysyłać.", + "copyAll": "Kopiuj wszystko", + "download": "Pobierz jako .txt", + "clear": "Wyczyść dziennik", + "prettyPrint": "Formatuj JSON", + "showRaw": "Pokaż surowy tekst" + }, + "composer": { + "placeholder": "Wpisz wiadomość do wysłania…", + "send": "Wyślij", + "formatJson": "Formatuj jako JSON", + "invalidJson": "Wiadomość nie jest prawidłowym JSON", + "hint": "Cmd/Ctrl+Enter, aby wysłać" + }, + "ping": { + "label": "Auto-ping", + "seconds": "s", + "intervalTitle": "Interwał pingu w sekundach", + "payloadPlaceholder": "payload pingu" + } + }, + "WhoisLookup": { + "title": "Zapytanie Whois", + "subtitle": "Sprawdzaj dane rejestracyjne dowolnej domeny lub adresu IP przez RDAP", + "placeholder": "example.com lub 8.8.8.8", + "lookupBtn": "Sprawdź", + "looking": "Sprawdzanie…", + "refreshBtn": "Odśwież", + "tryLabel": "Spróbuj:", + "copy": "Kopiuj", + "copied": "Skopiowano", + "notAvailable": "Niedostępne", + "summary": { + "registrar": "Rejestrator", + "network": "Sieć" + }, + "dates": { + "created": "Utworzono", + "updated": "Zaktualizowano", + "expires": "Wygasa" + }, + "sections": { + "status": "Status", + "nameservers": "Serwery nazw", + "contacts": "Kontakty", + "rawRdap": "Surowy RDAP" + }, + "contacts": { + "roles": "Role", + "name": "Nazwa", + "organization": "Organizacja" + }, + "errors": { + "invalidTarget": "Wpisz prawidłową nazwę domeny lub adres IP", + "lookupFailed": "Zapytanie nie powiodło się (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/pt-BR.json b/apps/desktop-ui/messages/pt-BR.json new file mode 100644 index 00000000..06ffa8f9 --- /dev/null +++ b/apps/desktop-ui/messages/pt-BR.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Painel", + "settings": "Configurações", + "apiClient": "Cliente API", + "httpStatusCodes": "Códigos de status HTTP", + "passwordManager": "Gerenciador de Senhas", + "environmentManager": "Gerenciador de ambientes", + "bookmarks": "Favoritos", + "jsonFormatter": "Formatador JSON", + "emailValidator": "Validador de E-mail", + "nosqlExplorer": "Explorador de banco de dados", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Laboratório de criptografia", + "jwtDecoder": "Decodificador JWT", + "certificatePemDecoder": "Decodificador de certificado / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Formatador GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Analisador de URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Segredo / chave de API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Verificador de contraste", + "jsonSchemaGenerator": "Gerador de esquema JSON", + "imageToBase64": "Imagem para Base64", + "imageCompressor": "Compressor de imagens", + "cssGradientBuilder": "Construtor de gradiente CSS", + "gitignoreGenerator": "Gerador .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Trechos de código", + "qrCodeGenerator": "Gerador de QR Code", + "dnsLookup": "Pesquisa DNS", + "ipSubnetCalculator": "Calculadora IP / sub-rede", + "hashGenerator": "Gerador de hash", + "hmacGenerator": "Gerador HMAC", + "totpGenerator": "Código TOTP / 2FA", + "markdownPreview": "Pré-visualização Markdown", + "numberBaseConverter": "Conversor de base", + "formatConverter": "Conversor de formatos", + "mockDataGenerator": "Gerador de dados mock", + "svgOptimizer": "Otimizador SVG", + "unitConverter": "Conversor de Unidades", + "dockerComposeGenerator": "Gerador Docker Compose", + "breakRoom": "Sala de descanso", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Formatador YAML", + "sshKeyGenerator": "Gerador de chaves SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Calculadora Chmod", + "codeScreenshot": "Captura de Código", + "cssGenerators": "Geradores CSS", + "exifViewer": "Visualizador EXIF", + "faviconGenerator": "Gerador de Favicon", + "jsonDiff": "Diff JSON", + "jsonpathPlayground": "Playground JSONPath", + "keycodeInspector": "Inspetor de Keycode", + "markdownTableGenerator": "Tabela Markdown", + "timezoneConverter": "Conversor de Fuso Horário", + "tokenCounter": "Contador de Tokens", + "webhookTester": "Testador de Webhook", + "websocketTester": "Testador de WebSocket", + "whoisLookup": "Consulta Whois" + }, + "Help": { + "title": "Ajuda e documentação", + "subtitle": "Como o MyDevTools se encaixa, o que cada app faz e como segurança e criptografia protegem seus dados.", + "tabs": { + "overview": "Visão geral", + "apps": "Apps", + "security": "Segurança", + "tips": "Dicas" + }, + "overview": { + "whatIs": { + "title": "O que é o MyDevTools?", + "description": "Um workspace para devs com login: um painel e um conjunto de ferramentas do dia a dia.", + "p1": "Depois de fazer login, o app cria uma sessão com a API do MyDevTools para que as ferramentas possam carregar e salvar seus dados. A barra lateral lista todos os apps; você pode ocultar ferramentas que não usa em", + "settingsLink": "Configurações", + "p1After": ".", + "p2": "Alguns recursos usam uma senha mestra (ou chave global do cofre) para que segredos como senhas e strings de conexão sejam criptografados no seu dispositivo antes de chegar ao servidor." + }, + "quickLinks": { + "title": "Links rápidos", + "dashboard": "Painel", + "settings": "Configurações" + } + }, + "apps": { + "intro": "Abra qualquer ferramenta pela barra lateral ou pelo painel. Abaixo está um guia curto por app.", + "open": "Abrir", + "howItWorks": "Como funciona", + "generic": "Use esta ferramenta pela barra lateral para o fluxo de trabalho dedicado." + }, + "security": { + "session": { + "title": "Login e sessão de API", + "description": "Identidade e como o navegador conversa com o backend.", + "items": { + "authentication": "Autenticação usa Firebase para login. Depois, o app troca seu Firebase ID token por cookies da API.", + "accessTokens": "Tokens de acesso são JWT assinados no servidor (HS256) e provam qual usuário está chamando a API.", + "refreshTokens": "Tokens de refresh ficam em cookies HttpOnly; o servidor armazena um hash SHA-256 do token, não o token em si." + } + }, + "encryption": { + "title": "Criptografia que você deve conhecer", + "description": "Cripto no cliente para o cofre e dados sensíveis de conexão.", + "p1": "Para o gerenciador de senhas, o gerenciador de ambientes e as conexões salvas no NoSQL Explorer, o aplicativo web usa a Web Crypto API do navegador:", + "items": { + "kdf": "PBKDF2 com SHA-256, 100.000 iterações e um salt aleatório deriva uma chave de 256 bits a partir da senha mestra.", + "aes": "AES-256-GCM criptografa os dados; um IV de 12 bytes aleatório é usado por criptografia.", + "scope": "A chave derivada fica no navegador (não extraível). O servidor recebe apenas ciphertext e IVs, nunca sua senha mestra ou segredos em texto puro." + }, + "warning": "Se você esquecer a senha mestra, os dados criptografados não podem ser recuperados. Guarde-a com segurança." + }, + "logout": { + "title": "Sair e dados locais", + "body": "Ao sair, o app limpa segredos em memória, estado do cofre e material de chave mestra do IndexedDB quando aplicável, e encerra a sessão da API. O que estiver apenas no armazenamento local pode permanecer até você limpar no navegador." + } + }, + "tips": { + "title": "Usando o MyDevTools com segurança", + "items": { + "masterPassword": "Use uma senha mestra forte e única para recursos do cofre.", + "apiClient": "Trate o Cliente API como qualquer cliente HTTP: evite colar segredos de produção em ambientes compartilhados sem saber quem pode vê-los.", + "nosql": "O Explorador de banco de dados se conecta a bancos que você configura; use usuários com menor privilégio e regras de rede quando possível.", + "json": "O Formatador JSON é local no navegador—mesmo assim evite colar dados muito sensíveis em máquinas compartilhadas.", + "settings": "Personalize as ferramentas visíveis em Configurações para manter a barra lateral focada." + } + } + }, + "Settings": { + "title": "Configurações", + "language": "Idioma", + "spanish": "Espanhol", + "english": "English", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalão", + "chineseSimplified": "Chinês (Simplificado)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Alemão", + "danish": "Dinamarquês", + "afrikaans": "Africâner", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norueguês", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Holandês", + "polish": "Polonês", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ucraniano" + }, + "SettingsPage": { + "title": "Configurações", + "subtitle": "Gerencie as configurações e preferências da sua conta.", + "userProfile": { + "title": "Perfil do Usuário", + "description": "Suas informações pessoais e detalhes da conta.", + "avatarAlt": "Usuário", + "anonymousUser": "Usuário Anônimo", + "noEmailProvided": "E-mail não fornecido", + "notLoggedIn": "Não conectado." + }, + "tools": { + "title": "Ferramentas e Recursos", + "description": "Escolha quais ferramentas aparecem na barra lateral de navegação." + }, + "appearance": { + "title": "Aparência", + "description": "Personalize a aparência do aplicativo no seu dispositivo.", + "themePreference": "Preferência de Tema", + "themes": { + "light": "Claro", + "dark": "Escuro", + "system": "Sistema" + } + }, + "language": { + "title": "Idioma", + "description": "Defina o idioma usado em toda a plataforma.", + "selectLanguage": "Selecionar Idioma", + "selectPlaceholder": "Selecione um idioma", + "helpText": "Alterar o idioma atualizará menus e rótulos em toda a plataforma.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español", + "ar": "عربى", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά", + "de": "Deutsch", + "da": "Dansk", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italiano", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Bom dia", + "afternoon": "Boa tarde", + "evening": "Boa noite" + }, + "commaName": ", {name}", + "welcomeBack": "Bem-vindo de volta", + "welcomeBackNamed": "Bem-vindo de volta, {name}", + "tagline": "O que você gostaria de construir hoje?", + "brandName": "MyDevTools", + "stats": { + "tools": "Ferramentas", + "favorites": "Favoritos", + "pinned": "Fixados", + "recent": "Usados Recentemente" + }, + "sections": { + "recentlyUsed": "Usados Recentemente", + "favorites": "Favoritos", + "whatsNew": "Novidades", + "pinned": "Fixados" + }, + "groups": { + "apps": "Aplicativos" + }, + "toolCard": { + "defaultDescription": "Explore esta ferramenta para melhor funcionalidade.", + "launchTool": "Abrir Ferramenta" + }, + "tools": { + "toDo": { + "title": "Tarefas", + "description": "Gerencie suas listas de tarefas com eficiência." + }, + "notes": { + "title": "Notas", + "description": "Capture ideias e organize seus pensamentos." + }, + "passwordManager": { + "title": "Gerenciador de Senhas", + "description": "Armazene e gerencie suas senhas com segurança." + }, + "environmentManager": { + "title": "Gerenciador de ambientes", + "description": "Armazene variáveis estilo .env por projeto e ambiente com tags." + }, + "bookmarks": { + "title": "Favoritos", + "description": "Gerencie e organize seus favoritos." + }, + "apiClient": { + "title": "Cliente API", + "description": "Teste e depure requisições HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Explore bancos de dados MongoDB." + }, + "emailValidator": { + "title": "Validador de E-mail", + "description": "Verifique e valide endereços de e-mail." + }, + "encryptionPlayground": { + "title": "Laboratório de criptografia", + "description": "AES-GCM com chave bruta ou senha; pacotes JSON, Web Crypto só no navegador." + }, + "jwtDecoder": { + "title": "Decodificador JWT", + "description": "Decodifique cabeçalho, payload e expiração do JWT." + }, + "certificatePemDecoder": { + "title": "Decodificador de certificado / PEM", + "description": "Decodifique certificados X.509 e CSRs: assunto, emissor, validade e SAN." + }, + "sshKeyGenerator": { + "title": "Gerador de Chaves SSH / RSA", + "description": "Gere pares de chaves Ed25519 ou RSA. Faça o download nos formatos OpenSSH e PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Formatador GraphQL", + "description": "Formate GraphQL com realce de sintaxe e um editor de consultas simples." + }, + "yamlFormatter": { + "title": "Formatador YAML", + "description": "Formate, valide e converta YAML para JSON no navegador." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Analisador de URL", + "description": "Quebre uma URL em protocolo, host, caminho, parâmetros de query e hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Segredo / chave de API", + "description": "Strings aleatórias criptográficas com alfabeto, tamanho e exportação em massa configuráveis." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Verificador de contraste", + "description": "Proporção de contraste WCAG 2.1 entre texto e fundo (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Gerador de esquema JSON", + "description": "Infira um esquema JSON e modelos tipados a partir de uma amostra JSON." + }, + "imageToBase64": { + "title": "Imagem para Base64", + "description": "Converta imagens em URI de dados ou strings Base64 brutas." + }, + "imageCompressor": { + "title": "Compressor de imagens", + "description": "Comprima JPEG, PNG e WebP localmente com controle de qualidade." + }, + "cssGradientBuilder": { + "title": "Construtor de gradiente CSS", + "description": "Construtor de gradiente visual CSS com controle de ângulo e paradas de cores." + }, + "gitignoreGenerator": { + "title": "Gerador .gitignore", + "description": "Gere arquivos .gitignore mesclados instantaneamente a partir de mais de 500 pilhas de tecnologia." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Converta arquivos CSV ou Excel em JSON e exporte de volta para CSV ou XLSX." + }, + "snippetManager": { + "title": "Trechos de código", + "description": "Salve trechos com realce de sintaxe, detecção automática de linguagem e formatação." + }, + "qrCodeGenerator": { + "title": "Gerador de QR Code", + "description": "Crie QR Codes PNG a partir de texto ou URL no navegador." + }, + "ipSubnetCalculator": { + "title": "Calculadora IP / sub-rede", + "description": "IPv4 e IPv6 CIDR: máscara, intervalos e contagem de endereços." + }, + "dnsLookup": { + "title": "Pesquisa DNS", + "description": "Consulte registros DNS de qualquer domínio — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Gerador de hash", + "description": "MD5, SHA-1 e SHA-2 para texto ou arquivos." + }, + "hmacGenerator": { + "title": "Gerador HMAC", + "description": "Assine webhooks e teste integrações de API com HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Gerador de código TOTP / 2FA", + "description": "Cole um segredo Base32 do autenticador; o código de seis dígitos atualiza a cada 30 s para testar login e MFA." + }, + "markdownPreview": { + "title": "Pré-visualização Markdown", + "description": "Renderizador Markdown em tempo real com exportação HTML e conversão HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Conversor de base numérica", + "description": "Converta inteiros entre bases de 2 a 36." + }, + "formatConverter": { + "title": "Conversor de formatos", + "description": "Converta entre JSON, YAML, TOML e XML em qualquer direção. As 12 combinações são suportadas." + }, + "mockDataGenerator": { + "title": "Gerador de dados mock", + "description": "Defina colunas, escolha tipos e exporte conjuntos de dados para seeds, Postman ou testes. Até 5000 linhas, tudo no navegador — nada é enviado." + }, + "httpStatusCodes": { + "title": "Códigos de status HTTP", + "description": "Pesquise códigos de status HTTP com descrições (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Otimizador SVG", + "description": "Cole SVG, remova ruído com SVGO e veja a redução de tamanho UTF-8." + }, + "unitConverter": { + "title": "Conversor de Unidades", + "description": "Converta entre 323 unidades em 43 categorias científicas e de engenharia." + }, + "dockerComposeGenerator": { + "title": "Gerador Docker Compose", + "description": "Escolha bancos de dados, caches, filas, proxies e imagens de observabilidade; exporte um docker-compose.yml para stacks locais." + }, + "game2048": { + "title": "2048", + "description": "Deslize e mescle as peças para chegar a 2048. Clássico jogo de quebra-cabeças numéricos." + }, + "sudoku": { + "title": "Sudoku", + "description": "Puzzle numérico clássico. 3 níveis de dificuldade, 150 estágios cada." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Navegue por chaves, inspecione valores e execute comandos em qualquer instância Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Gerador e Verificador Bcrypt", + "description": "Gere hash de senhas com bcrypt e verifique hashes existentes." + }, + "chmodCalculator": { + "title": "Calculadora Chmod", + "description": "Converta permissões Unix entre octal, simbólico e caixas de seleção." + }, + "codeScreenshot": { + "title": "Captura de Código", + "description": "Transforme código-fonte em belas imagens compartilháveis." + }, + "cssGenerators": { + "title": "Geradores CSS", + "description": "Geradores de box-shadow, border-radius e clamp() fluido." + }, + "exifViewer": { + "title": "Visualizador e Removedor EXIF", + "description": "Inspecione e remova metadados EXIF de fotos localmente." + }, + "faviconGenerator": { + "title": "Gerador de Favicon", + "description": "Gere favicons, ícones de app e trechos de manifest." + }, + "jsonDiff": { + "title": "Diff JSON", + "description": "Diff semântico e ciente de estrutura de dois documentos JSON." + }, + "jsonpathPlayground": { + "title": "Playground JSONPath", + "description": "Consulte JSON com JSONPath ou JMESPath e veja resultados ao vivo." + }, + "keycodeInspector": { + "title": "Inspetor de Keycode", + "description": "Pressione uma tecla para ver seus códigos de evento JavaScript e modificadores." + }, + "markdownTableGenerator": { + "title": "Gerador de Tabela Markdown", + "description": "Crie tabelas Markdown e HTML em um editor de grade tipo planilha." + }, + "timezoneConverter": { + "title": "Conversor de Fuso Horário", + "description": "Converta um horário entre vários fusos com indicações de mudança de dia e horário comercial." + }, + "tokenCounter": { + "title": "Contador de Tokens LLM", + "description": "Conte tokens de LLM, visualize chunks e estime o custo de API." + }, + "webhookTester": { + "title": "Testador de Webhook", + "description": "Capture e inspecione requisições de webhook recebidas em tempo real." + }, + "websocketTester": { + "title": "Testador de WebSocket", + "description": "Conecte-se a endpoints WebSocket e troque mensagens em tempo real." + }, + "whoisLookup": { + "title": "Consulta Whois", + "description": "Consulte detalhes de registro de domínio e IP via RDAP." + } + }, + "tabs": { + "apps": "Aplicativos", + "analytics": "Análises", + "ui": "Interface" + }, + "analytics": { + "title": "Seus dados em resumo", + "subtitle": "Contagens sincronizadas com sua conta no servidor.", + "loadError": "Não foi possível carregar as análises.", + "retry": "Tentar de novo", + "loginHint": "Entre para ver análises de uso e preferências de interface.", + "passwordEntries": "Entradas de senhas", + "bookmarks": "Favoritos", + "bookmarkFolders": "Pastas de favoritos", + "tasksTotal": "Tarefas", + "tasksCompleted": "Concluídas", + "tasksOngoing": "Em andamento", + "tasksNotStarted": "Não iniciadas", + "projects": "Projetos", + "nosqlConnections": "Conexões de BD", + "notes": "Notas", + "apiClientCollections": "Coleções da API", + "apiClientEnvironments": "Ambientes da API", + "apiClientHistory": "Entradas do histórico da API", + "jsonFormatterDocuments": "Documentos do formatador JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Trechos de código", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Preferências de interface", + "subtitle": "Tema, idioma e ferramentas visíveis na barra lateral — nas configurações da conta.", + "loadError": "Não foi possível carregar as preferências.", + "loginHint": "Entre para ver suas preferências de interface salvas.", + "theme": "Tema", + "accent": "Cor de destaque", + "locale": "Idioma", + "enabledTools": "Ferramentas visíveis", + "favoriteTools": "Ferramentas favoritas", + "toolsWithUsage": "Ferramentas com uso rastreado" + }, + "signIn": "Entrar", + "noResults": "Nenhuma ferramenta corresponde à sua pesquisa", + "noResultsHint": "Tente uma palavra-chave diferente ou limpe o filtro.", + "filterAll": "Tudo", + "loginCta": { + "description": "Faça login para rastrear suas ferramentas usadas recentemente." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Carregando...", + "myTasksTitle": "Minhas Tarefas", + "statsLine": "{total} tarefas • {percent}% concluído", + "statsLineDesktop": "{total} total • {percent}% concluído" + }, + "filters": { + "all": "Todas", + "total": "Total", + "allProjects": "Todos os Projetos", + "export": "Exportar", + "kanban": "Kanban", + "list": "Lista", + "searchPlaceholder": "Buscar tarefas...", + "searchAria": "Buscar tarefas", + "clearSearchAria": "Limpar busca", + "foundTasks": "{count} {count, plural, one {tarefa encontrada} other {tarefas encontradas}}", + "filterByAllAria": "Filtrar por Todas", + "filterByStatusAria": "Filtrar por {status}" + }, + "status": { + "not-started": { + "label": "Não Iniciada", + "description": "Tarefas que ainda não foram iniciadas" + }, + "ongoing": { + "label": "Em Andamento", + "description": "Tarefas atualmente em progresso" + }, + "completed": { + "label": "Concluída", + "description": "Tarefas finalizadas" + } + }, + "priorities": { + "high": "Alta", + "medium": "Média", + "low": "Baixa" + }, + "form": { + "addNewTaskLabel": "Adicionar nova tarefa", + "multilinePlaceholder": "Adicione detalhes da tarefa... (Shift+Enter para nova linha)", + "singlelinePlaceholder": "O que precisa ser feito? Pressione Enter para adicionar", + "taskInputAria": "Entrada de tarefa", + "projectPlaceholder": "Projeto", + "selectProject": "Selecionar Projeto", + "errorSelectProject": "Selecione um projeto ou crie um novo para adicionar uma tarefa.", + "hintMultiline": "Shift+Enter para nova linha, Enter para adicionar", + "hintSingleline": "Enter para adicionar", + "shortcutsAria": "Atalhos do teclado", + "shortcutsLabel": "Atalhos", + "shortcutAddTask": "Adicionar tarefa", + "shortcutNewLine": "Nova linha", + "shortcutClear": "Limpar", + "addButton": "Adicionar" + }, + "list": { + "ariaLabel": "Lista de tarefas", + "loading": "Carregando suas tarefas...", + "emptyTitle": "Nenhuma tarefa encontrada", + "emptyPrimary": "Comece adicionando sua primeira tarefa acima! Pressione Enter para adicionar tarefas rapidamente.", + "emptySecondary": "Tente ajustar seus filtros ou consulta de busca." + }, + "mobileDrawer": { + "addTaskSrOnly": "Adicionar Tarefa", + "addNewTaskTitle": "Adicionar Nova Tarefa", + "addNewTaskDescription": "Crie uma nova tarefa para acompanhar seu progresso.", + "cancel": "Cancelar" + }, + "kanban": { + "ariaLabel": "Quadro Kanban", + "loading": "Carregando suas tarefas...", + "dropHere": "Solte a tarefa aqui", + "noTasksYet": "Nenhuma tarefa ainda", + "emptyColumnAria": "Coluna vazia", + "columnAria": "Coluna {title} com {count} tarefas", + "countInColumnAria": "{count} tarefas em {title}" + }, + "taskItem": { + "confirmDelete": "Tem certeza de que deseja excluir esta tarefa?", + "statusPlaceholder": "Status", + "changeStatusAria": "Alterar status da tarefa", + "taskOptionsAria": "Opções da tarefa", + "editTask": "Editar Tarefa", + "copyTask": "Copiar Tarefa", + "copied": "Copiado!", + "deleteTask": "Excluir Tarefa", + "statusHeading": "Status", + "editTaskAria": "Editar tarefa" + }, + "editDialog": { + "title": "Editar Tarefa", + "description": "Atualize detalhes da tarefa, adicione subtarefas, defina prioridades e mais.", + "taskTitleLabel": "Título da Tarefa *", + "taskTitlePlaceholder": "O que precisa ser feito?", + "descriptionLabel": "Descrição", + "descriptionPlaceholder": "Adicione mais detalhes sobre esta tarefa...", + "statusLabel": "Status", + "priorityLabel": "Prioridade", + "dueDateLabel": "Data de Vencimento", + "pickDate": "Escolha uma data", + "clearDate": "Limpar Data", + "projectLabel": "Projeto", + "selectProjectPlaceholder": "Selecione um projeto", + "noProject": "Sem Projeto", + "timeEstimateLabel": "Estimativa de Tempo (minutos)", + "timeEstimatePlaceholder": "Quanto tempo isso vai levar?", + "tagsLabel": "Tags", + "customTagNamePlaceholder": "Nome da tag personalizada", + "subtasksLabel": "Subtarefas", + "addSubtaskPlaceholder": "Adicionar uma subtarefa...", + "cancel": "Cancelar", + "saving": "Salvando...", + "saveChanges": "Salvar Alterações" + }, + "export": { + "title": "Exportar Tarefas", + "description": "Exporte suas tarefas nos formatos JSON, CSV ou Excel.", + "projectLabel": "Projeto", + "statusLabel": "Status", + "tagLabel": "Tag", + "allProjects": "Todos os Projetos", + "allStatuses": "Todos os Status", + "allTags": "Todas as Tags", + "exportAsJsonTitle": "Exportar como JSON", + "exportAsJsonDescription": "Exporte dados da tarefa incluindo subtarefas, tags e metadados.", + "exportJson": "Exportar JSON", + "exportAsCsvExcelTitle": "Exportar como CSV / Excel", + "exportAsCsvExcelDescription": "Exporte tarefas para aplicativos de planilha.", + "exportCsv": "Exportar CSV", + "exportExcel": "Exportar Excel", + "close": "Fechar", + "toastNoTasks": "Nenhuma tarefa para exportar com os filtros atuais", + "toastExportedJson": "{count} tarefas exportadas para JSON!", + "toastExportedCsv": "{count} tarefas exportadas para CSV!", + "toastExportedExcel": "{count} tarefas exportadas para Excel!", + "toastFailedJson": "Falha ao exportar para JSON", + "toastFailedCsv": "Falha ao exportar para CSV", + "toastFailedExcel": "Falha ao exportar para Excel", + "unknownProject": "Projeto Desconhecido" + }, + "ack": { + "taskAddedTitle": "Tarefa adicionada com sucesso", + "taskAddedFailedTitle": "Falha ao adicionar tarefa", + "taskUpdatedTitle": "Tarefa atualizada com sucesso", + "taskUpdatedFailedTitle": "Falha ao atualizar tarefa", + "taskStatusUpdateFailedTitle": "Falha ao atualizar status da tarefa", + "taskMovedTitle": "Tarefa movida para {status}", + "tryAgain": "Por favor, tente novamente.", + "taskDeletedTitle": "Tarefa \"{text}\" excluída", + "undo": "Desfazer", + "taskRestoredTitle": "Tarefa restaurada", + "taskRestoredSuccessTitle": "Tarefa restaurada com sucesso", + "taskRestoreFailedTitle": "Falha ao restaurar tarefa. Por favor, tente novamente.", + "taskDeleteFailedTitle": "Falha ao excluir tarefa. Por favor, tente novamente.", + "tasksImportedTitle": "{count} {count, plural, one {tarefa importada} other {tarefas importadas}} com sucesso!", + "tasksImportFailedTitle": "Falha ao importar tarefas. Por favor, tente novamente.", + "unknownProject": "Projeto Desconhecido", + "projectCreatedTitle": "Projeto criado com sucesso", + "projectCreateFailedTitle": "Falha ao criar projeto", + "projectUpdatedTitle": "Projeto atualizado com sucesso", + "projectUpdateFailedTitle": "Falha ao atualizar projeto", + "projectDeletedTitle": "Projeto excluído com sucesso", + "projectDeleteFailedTitle": "Falha ao excluir projeto" + } + }, + "Notes": { + "page": { + "loading": "Carregando..." + }, + "layout": { + "mobileNavTitle": "Navegação de Notas", + "openMenuAria": "Abrir menu de notas" + }, + "sidebar": { + "title": "Notas", + "createNoteAria": "Criar nota", + "searchPlaceholder": "Buscar notas...", + "loading": "Carregando...", + "noNotesFound": "Nenhuma nota encontrada.", + "noNotesYet": "Nenhuma nota ainda. Clique em + para criar uma.", + "inParent": "em {parentTitle}", + "untitled": "Sem Título", + "addSubPage": "Adicionar subpágina", + "delete": "Excluir", + "deleteConfirmTitle": "Excluir nota?", + "deleteConfirmDescription": "Tem certeza de que deseja excluir \"{title}\"? Esta ação não pode ser desfeita.", + "cancel": "Cancelar", + "confirmDelete": "Excluir", + "noteOptionsAria": "Opções da nota" + }, + "editor": { + "emptyState": "Selecione uma nota ou crie uma nova para começar.", + "titlePlaceholder": "Sem Título", + "saving": "Salvando…", + "ready": "Pronto", + "saved": "Salvo {time}", + "stats": "{words} palavras · {read} min de leitura", + "template": "Modelo", + "applyTemplate": "Aplicar modelo", + "exportMdLabel": ".md", + "exportMd": "Exportar como Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Exportar como HTML", + "focusMode": "Modo foco", + "exitFocusMode": "Sair do modo foco", + "save": "Salvar", + "addTag": "Adicionar tag…", + "removeTag": "Remover tag {tag}", + "loading": "Carregando…", + "searchHint": "para pesquisar", + "focusHint": "modo foco", + "notePath": "Caminho da nota" + }, + "context": { + "authRequiredError": "Usuário não autenticado", + "defaultTitle": "Sem Título", + "cloudSyncImageError": "Ative a Sincronização na Nuvem para armazenar imagens das notas. As notas de texto permanecem locais." + } + }, + "PasswordManager": { + "page": { + "loading": "Carregando...", + "title": "Gerenciador de Senhas", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Falha ao descriptografar senhas", + "toastLoadFailed": "Falha ao carregar senhas", + "toastUnlockedAuto": "Cofre desbloqueado automaticamente", + "toastAutoUnlockFailed": "Falha no desbloqueio automático. Por favor, desbloqueie manualmente.", + "toastConnectFailed": "Falha ao conectar ao servidor", + "toastAutoUnlockWarning": "Cofre desbloqueado, mas o desbloqueio automático pode não funcionar ao recarregar", + "errorIncorrectPassword": "Senha mestra incorreta", + "errorUnlockFailed": "Falha ao desbloquear cofre", + "errorPasswordsMismatch": "As senhas não coincidem", + "errorPasswordTooShort": "A senha deve ter pelo menos 8 caracteres", + "errorSetupFailed": "Falha ao configurar cofre", + "errorConnectFailed": "Falha ao conectar ao servidor.", + "mobileTitleCreate": "Crie seu cofre", + "mobileTitleWelcome": "Bem-vindo de volta", + "mobileSubtitleCreate": "Defina uma senha mestra para proteger suas credenciais", + "mobileSubtitleUnlock": "Digite sua senha mestra para desbloquear", + "placeholderMasterPassword": "Senha mestra", + "placeholderConfirmPassword": "Confirmar senha", + "labelMasterPassword": "Senha mestra", + "labelConfirmPassword": "Confirmar senha", + "placeholderEnterMaster": "Digite a senha mestra", + "placeholderConfirmMaster": "Confirme a senha mestra", + "buttonCreateVault": "Criar cofre", + "buttonUnlock": "Desbloquear", + "buttonUnlockVault": "Desbloquear cofre", + "footerCreate": "Sua senha nunca é armazenada e não pode ser recuperada", + "footerUnlock": "Protegido com criptografia de ponta a ponta", + "desktopTitleSetup": "Configurar cofre de senhas", + "desktopTitleUnlock": "Desbloquear cofre", + "desktopDescriptionSetup": "Crie uma senha mestra para proteger suas credenciais. Esta senha nunca é armazenada e não pode ser recuperada.", + "desktopDescriptionUnlock": "Digite sua senha mestra para acessar suas credenciais protegidas." + }, + "list": { + "loading": "Carregando suas senhas...", + "emptyTitle": "Cofre vazio", + "emptyHintMobile": "Toque no botão + para adicionar sua primeira senha", + "emptyHintDesktop": "Você ainda não armazenou nenhuma senha. Clique no botão \"Adicionar senha\" acima para começar.", + "addPassword": "Adicionar senha", + "searchPlaceholderMobile": "Buscar no cofre...", + "searchPlaceholderDesktop": "Buscar no cofre...", + "importExport": "Importar / exportar", + "lockVault": "Bloquear cofre", + "allPasswordsTitle": "Todas as senhas", + "passwordCount": "{count, plural, one {# senha} other {# senhas}}", + "vaultHealthDrawerTitle": "Saúde do cofre", + "noResults": "Nenhuma senha encontrada para \"{query}\"", + "tableService": "Serviço", + "tableUsername": "Usuário", + "tablePassword": "Senha", + "tableUrl": "URL", + "tableActions": "Ações", + "gridViewAria": "Visualização em grade", + "listViewAria": "Visualização em lista", + "passwordStrengthTitle": "Força da senha", + "deleteDialogTitle": "Excluir senha", + "deleteDialogDescription": "Tem certeza de que deseja excluir esta senha? Esta ação não pode ser desfeita.", + "cancel": "Cancelar", + "delete": "Excluir", + "edit": "Editar" + }, + "toasts": { + "copiedPassword": "Senha copiada para a área de transferência", + "copiedUsername": "Usuário copiado para a área de transferência", + "passwordDeleted": "Senha excluída", + "deleteFailed": "Falha ao excluir senha", + "vaultLocked": "Cofre bloqueado", + "importedCount": "{count, plural, one {# senha importada} other {# senhas importadas}} com sucesso" + }, + "form": { + "addTitle": "Adicionar nova senha", + "editTitle": "Editar senha", + "serviceName": "Nome do serviço", + "usernameEmail": "Usuário / e-mail", + "password": "Senha", + "urlOptional": "URL do site (opcional)", + "notesOptional": "Notas (opcional)", + "tags": "Tags", + "placeholderService": "ex: Netflix", + "placeholderUsername": "usuario@exemplo.com", + "placeholderPassword": "Digite ou gere uma senha", + "placeholderUrl": "https://...", + "placeholderNotes": "Detalhes adicionais...", + "placeholderTags": "Adicionar tags (pressione Enter ou vírgula)", + "strength": "Força", + "strengthWeak": "Fraca", + "strengthMedium": "Média", + "strengthStrong": "Forte", + "advancedGenerator": "Gerador avançado", + "cancel": "Cancelar", + "saving": "Salvando...", + "savePassword": "Salvar senha", + "updating": "Atualizando...", + "updatePassword": "Atualizar senha", + "addPasswordSrOnly": "Adicionar senha", + "triggerAdd": "Adicionar senha", + "toastSaved": "Senha salva com sucesso", + "toastSaveFailed": "Falha ao salvar senha", + "toastUpdated": "Senha atualizada com sucesso", + "toastUpdateFailed": "Falha ao atualizar senha" + }, + "security": { + "vaultHealth": "Saúde do cofre", + "statusLabel": "Status", + "totalPasswords": "Total de senhas", + "activeEntries": "Entradas ativas no seu cofre", + "protected": "Protegido", + "weakPasswords": "Senhas fracas", + "needingImprovement": "Senhas que precisam de melhorias", + "allGood": "Tudo certo", + "reusedPasswords": "Senhas reutilizadas", + "usedMultipleTimes": "Senhas usadas várias vezes", + "unique": "Única", + "total": "Total", + "weak": "Fraca", + "reused": "Reutilizada", + "totalCountLabel": "{count, plural, one {# senha} other {# senhas}}", + "weakCountLabel": "{count} fraca(s)", + "allSecure": "Todas seguras", + "scoreExcellent": "Excelente", + "scoreGood": "Boa", + "scoreFair": "Regular", + "scoreCritical": "Crítica" + }, + "importExport": { + "title": "Importar / exportar cofre", + "description": "Gerencie seus dados de senhas. Você pode exportar seu cofre para JSON ou importar de um arquivo JSON.", + "tabExport": "Exportar", + "tabImport": "Importar", + "warningTitle": "Aviso", + "warningBody": "Arquivos exportados contêm suas senhas em texto simples. Mantenha este arquivo seguro e exclua-o após o uso.", + "exportBlurb": "Baixe seu cofre inteiro como um arquivo JSON. Isso inclui todos os serviços, usuários, senhas e notas.", + "downloadJson": "Baixar exportação JSON", + "pasteLabel": "Colar dados JSON", + "pastePlaceholder": "[{\"service\": \"Exemplo\", \"username\": \"usuario\", \"password\": \"123\", ...}]", + "pasteHint": "Cole o conteúdo de um arquivo JSON exportado anteriormente aqui.", + "importing": "Importando...", + "importPasswords": "Importar senhas", + "trigger": "Importar / exportar", + "toastExported": "Cofre exportado com sucesso", + "toastExportFailed": "Falha ao exportar cofre", + "toastImportFailed": "Falha ao importar: formato JSON inválido" + }, + "card": { + "copyUsername": "Copiar usuário", + "openUrl": "Abrir URL", + "strength": "Força" + }, + "swipeable": { + "copy": "Copiar", + "delete": "Excluir", + "copyPassword": "Copiar senha", + "copyUsername": "Copiar usuário", + "edit": "Editar", + "deleteAction": "Excluir" + }, + "floatingMenu": { + "lockVault": "Bloquear cofre", + "importExport": "Importar / exportar", + "addPassword": "Adicionar senha" + }, + "generator": { + "tabPassword": "Senha", + "tabPassphrase": "Frase-senha", + "strength": "Força", + "lengthLabel": "Comprimento: {length} caracteres", + "lengthBadge": "{length} caracteres", + "characterSets": "Conjuntos de caracteres", + "uppercase": "Maiúsculas", + "lowercase": "Minúsculas", + "numbers": "Números", + "symbols": "Símbolos", + "wordCountLabel": "Número de palavras: {count}", + "wordCountBadge": "{count, plural, one {# palavra} other {# palavras}}", + "separator": "Separador", + "capitalization": "Capitalização", + "includeNumber": "Incluir número" + } + }, + "EnvironmentManager": { + "subtitle": "Variáveis de ambiente criptografadas por projeto e ambiente.", + "page": { + "loading": "Carregando…", + "title": "Gerenciador de ambientes", + "loadFailed": "Falha ao carregar conjuntos de ambiente" + }, + "list": { + "title": "Ambientes", + "loading": "Carregando seus conjuntos…", + "emptyTitle": "Nenhum conjunto de ambiente ainda", + "emptyHint": "Crie um conjunto para cada projeto e ambiente (ex.: chaves de API de produção). Tudo é criptografado no navegador antes do envio.", + "searchPlaceholder": "Buscar projeto, ambiente, tags, notas…", + "tagFilterPlaceholder": "Filtrar por tag", + "variableCount": "{count, plural, one {# variável} other {# variáveis}}", + "updatedAgo": "Atualizado {time}", + "copyDotEnv": "Copiar .env", + "edit": "Editar", + "delete": "Excluir", + "cancel": "Cancelar", + "confirmDelete": "Excluir", + "deleteDialogTitle": "Excluir conjunto de ambiente?", + "deleteDialogDescription": "Isso remove o blob criptografado da sua conta. Não pode ser desfeito.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env copiado para a área de transferência", + "deleted": "Conjunto de ambiente excluído", + "deleteFailed": "Falha ao excluir" + }, + "form": { + "addTitle": "Novo conjunto de ambiente", + "editTitle": "Editar conjunto de ambiente", + "triggerAdd": "Adicionar conjunto de ambiente", + "addSrOnly": "Adicionar conjunto de ambiente", + "project": "Projeto", + "environment": "Ambiente", + "placeholderProject": "ex. billing-api", + "placeholderEnvironment": "ex. production", + "pasteEnvLabel": "Colar .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Mesclar linhas coladas", + "variables": "Variáveis", + "addRow": "Adicionar linha", + "keyPlaceholder": "CHAVE", + "valuePlaceholder": "valor", + "notesOptional": "Notas (opcional)", + "placeholderNotes": "Política de rotação, responsável, links…", + "tags": "Tags", + "placeholderTags": "Adicionar tags (Enter ou vírgula)", + "cancel": "Cancelar", + "save": "Salvar", + "saving": "Salvando…", + "update": "Atualizar", + "updating": "Atualizando…", + "toastSaved": "Conjunto de ambiente salvo", + "toastSaveFailed": "Falha ao salvar", + "toastUpdated": "Conjunto de ambiente atualizado", + "toastUpdateFailed": "Falha ao atualizar", + "toastPasteEmpty": "Nenhuma linha CHAVE=valor encontrada", + "toastPasteApplied": "{count} variáveis mescladas da colagem", + "toastProjectEnvRequired": "Projeto e ambiente são obrigatórios", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Carregando..." + }, + "manager": { + "foldersHeading": "Pastas", + "tagsHeading": "Tags", + "tagsMore": "+{count} mais", + "tagsShowLess": "Show less", + "searchPlaceholder": "Buscar favoritos...", + "import": "Importar", + "export": "Exportar", + "exportAsHtml": "Exportar como HTML", + "exportAsJson": "Exportar como JSON", + "addBookmark": "Adicionar Favorito", + "matchingQuery": "correspondendo a \"{query}\"", + "bookmarkCount": "{count, plural, one {# favorito} other {# favoritos}}", + "allBookmarks": "Todos os Favoritos", + "uncategorized": "Sem Categoria", + "unknownFolder": "Desconhecido" + }, + "card": { + "urlCopied": "URL copiada para a área de transferência", + "bookmarkDeleted": "Favorito excluído", + "edit": "Editar", + "delete": "Excluir", + "copyUrl": "Copiar URL", + "deleteConfirmTitle": "Excluir favorito?", + "deleteConfirmDescription": "Isso excluirá permanentemente \"{title}\".", + "cancel": "Cancelar" + }, + "grid": { + "emptyTitle": "Nenhum favorito ainda", + "emptyDescription": "Adicione seu primeiro favorito ou importe do seu navegador para começar." + }, + "folderTree": { + "allBookmarks": "Todos os Favoritos", + "addSubfolder": "Adicionar Subpasta", + "rename": "Renomear", + "deleteFolderTitle": "Excluir pasta \"{name}\"?", + "deleteFolderDescription": "Isso excluirá a pasta e todas as suas subpastas. Favoritos nessas pastas serão movidos para Sem Categoria." + }, + "import": { + "title": "Importar Favoritos", + "description": "Importe favoritos do arquivo exportado do seu navegador.", + "parseError": "Falha ao analisar arquivo de favoritos. Certifique-se de que é uma exportação válida.", + "successToast": "{bookmarks} favoritos e {folders} pastas importados", + "errorToast": "Falha ao importar favoritos", + "chromeInstruction": "Menu → Favoritos → Gerenciador de Favoritos → ⋮ → Exportar Favoritos", + "firefoxInstruction": "Menu → Favoritos → Gerenciar Favoritos → Importar e Backup → Exportar", + "safariInstruction": "Arquivo → Exportar Favoritos...", + "edgeInstruction": "Menu → Favoritos → ⋯ → Exportar Favoritos", + "dropZoneTitle": "Solte seu arquivo de favoritos aqui", + "dropZoneHint": "ou clique para procurar (HTML ou JSON)", + "selectedFileHint": "Clique para selecionar um arquivo diferente", + "errorTitle": "Erro", + "previewTitle": "Pré-visualização da Importação", + "previewBookmarks": "{count, plural, one {# favorito} other {# favoritos}}", + "previewFolders": "{count, plural, one {# pasta} other {# pastas}}", + "importModeLabel": "Modo de Importação", + "modeMerge": "Mesclar com favoritos existentes", + "modeReplace": "Substituir todos os favoritos existentes", + "cancel": "Cancelar", + "importButton": "Importar", + "importButtonWithCount": "Importar {count, plural, one {# favorito} other {# favoritos}}" + }, + "addBookmark": { + "titleAdd": "Adicionar Favorito", + "titleEdit": "Editar Favorito", + "urlLabel": "URL", + "urlPlaceholder": "https://exemplo.com", + "urlInvalid": "Por favor, insira uma URL válida", + "titleLabel": "Título", + "titlePlaceholder": "Título do favorito", + "titleRequired": "Por favor, insira um título", + "descriptionLabel": "Descrição (opcional)", + "descriptionPlaceholder": "Adicione uma nota sobre este favorito", + "folderLabel": "Pasta", + "folderPlaceholder": "Selecionar pasta", + "noFolder": "Sem pasta", + "tagsLabel": "Tags", + "tagsPlaceholder": "Adicionar tags (pressione Enter)", + "toastUpdated": "Favorito atualizado", + "toastAdded": "Favorito adicionado", + "cancel": "Cancelar", + "submitAdd": "Adicionar Favorito", + "submitUpdate": "Atualizar Favorito" + }, + "addFolder": { + "title": "Criar Pasta", + "nameLabel": "Nome da Pasta", + "namePlaceholder": "Minha Pasta", + "nameRequired": "Por favor, insira um nome para a pasta", + "parentLabel": "Pasta Principal", + "parentPlaceholder": "Selecionar pasta principal", + "rootParent": "Raiz (Sem pasta principal)", + "toastCreated": "Pasta criada", + "cancel": "Cancelar", + "submit": "Criar Pasta" + }, + "editFolder": { + "title": "Renomear Pasta", + "nameLabel": "Nome da Pasta", + "namePlaceholder": "Nome da pasta", + "nameRequired": "Por favor, insira um nome para a pasta", + "toastRenamed": "Pasta renomeada", + "cancel": "Cancelar", + "save": "Salvar" + } + }, + "EmailValidator": { + "header": { + "title": "Validador de E-mail", + "description": "Verifique e valide endereços de e-mail quanto à sintaxe, domínio e entregabilidade." + }, + "tabs": { + "single": "Verificação Única", + "bulk": "Upload em Massa" + }, + "single": { + "cardTitle": "Verificação de E-mail Única", + "cardDescription": "Insira um endereço de e-mail para verificar sua entregabilidade e reputação.", + "placeholder": "Digite o endereço de e-mail...", + "clearAria": "Limpar e-mail", + "validate": "Validar", + "validating": "Validando...", + "formatError": "Por favor, insira um endereço de e-mail válido" + }, + "toasts": { + "errorTitle": "Erro", + "enterEmail": "Por favor, insira um endereço de e-mail", + "invalidFormatTitle": "Formato de E-mail Inválido", + "invalidFormatDescription": "Por favor, insira um endereço de e-mail válido", + "validateFailedDescription": "Ocorreu um erro ao validar o e-mail", + "noEmailsTitle": "Nenhum E-mail Encontrado", + "noEmailsDescription": "Certifique-se de que seu Excel tem uma coluna 'email'.", + "fileTooLargeTitle": "Arquivo Muito Grande", + "fileTooLargeDescription": "Encontrados {total} e-mails. Apenas os primeiros 4000 serão validados.", + "successTitle": "Sucesso", + "successValidated": "{count} e-mails validados com sucesso.", + "batchFailedTitle": "Falha na Validação em Massa", + "batchFailedDescription": "Ocorreu um erro ao processar a validação em massa.", + "noExportTitle": "Sem Dados para Exportar", + "noExportValidOnly": "Nenhum e-mail válido encontrado para exportar.", + "noExportAll": "Sem resultados para exportar.", + "exportSuccessTitle": "Exportação Bem-sucedida", + "exportSuccessExcelAll": "{count} e-mails exportados para Excel.", + "exportSuccessExcelValid": "{count} e-mails válidos exportados para Excel.", + "exportSuccessCsvAll": "{count} e-mails exportados para CSV.", + "exportSuccessCsvValid": "{count} e-mails válidos exportados para CSV." + }, + "disposableAlert": { + "title": "E-mail Descartável Detectado", + "description": "Este endereço de e-mail pertence a um provedor de e-mail descartável. Pode ser temporário ou inseguro." + }, + "score": { + "reliabilityTitle": "Pontuação de Confiabilidade", + "scoreLabel": "Pontuação" + }, + "details": { + "sectionTitle": "Detalhes da Validação" + }, + "validation": { + "syntaxLabel": "Sintaxe Válida", + "syntaxTooltip": "Verifica se o endereço de e-mail segue o formato correto (ex: usuario@dominio.com)", + "domainLabel": "Domínio Existe", + "domainTooltip": "Verifica se o nome de domínio no endereço de e-mail realmente existe", + "mxLabel": "Registros MX Encontrados", + "mxTooltip": "Verifica se o domínio tem registros de troca de e-mail (MX) configurados para receber e-mails", + "mailboxLabel": "Caixa de Correio Existe", + "mailboxTooltip": "Verifica se a caixa de correio/conta específica existe no servidor de e-mail", + "disposableLabel": "E-mail Descartável", + "disposableTooltip": "Indica se este e-mail pertence a um serviço de e-mail temporário/descartável (pode ser arriscado)", + "roleBasedLabel": "E-mail Baseado em Função", + "roleBasedTooltip": "Identifica se este é um e-mail baseado em função (ex: suporte@, info@) em vez de um endereço pessoal" + }, + "copy": { + "emailCopied": "E-mail copiado!", + "copyTooltip": "Copiar e-mail" + }, + "bulk": { + "dropHere": "Solte seu arquivo aqui", + "dragHere": "Arraste e solte seu arquivo aqui", + "uploadHint": "Envie um arquivo .xlsx, .xls ou .csv contendo uma coluna 'email' para validar até 4000 e-mails.", + "filePrefix": "Arquivo:", + "selectFile": "Selecionar Arquivo", + "downloadTemplate": "Baixar Modelo" + }, + "bulkLoading": { + "title": "Validando E-mails...", + "batchProgress": "Processando lote {current} de {total}..." + }, + "stats": { + "totalProcessed": "Total Processado", + "valid": "Válido", + "risky": "Arriscado", + "invalid": "Inválido" + }, + "results": { + "detailedTitle": "Resultados Detalhados", + "searchPlaceholder": "Buscar e-mails...", + "newVerification": "Nova Verificação", + "exportAll": "Exportar Tudo", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Exportar Válidos ({count})" + }, + "showingResults": "Mostrando {shown} de {total} resultados", + "table": { + "email": "E-mail", + "status": "Status", + "score": "Pontuação", + "checks": "Verificações" + }, + "noSearchResults": "Nenhum resultado encontrado para \"{query}\"", + "checksTooltip": { + "syntax": "Sintaxe", + "domain": "Domínio", + "mx": "MX", + "mailbox": "Caixa de Correio" + }, + "exportColumns": { + "email": "E-mail", + "status": "Status", + "score": "Pontuação", + "syntax": "Sintaxe", + "domain": "Domínio", + "mx": "MX", + "mailbox": "Caixa de Correio", + "disposable": "Descartável", + "roleBased": "Baseado em Função", + "valid": "Válido", + "invalid": "Inválido", + "yes": "Sim", + "no": "Não" + }, + "sheets": { + "validEmails": "E-mails Válidos", + "results": "Resultados", + "template": "Modelo" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "Explorador NoSQL", + "heroSubtitle": "Conecte-se aos seus bancos de dados para gerenciar coleções, executar consultas e visualizar seus dados.", + "featureMultiTitle": "Multi-Conexão", + "featureMultiDesc": "Gerencie múltiplas conexões de banco de dados simultaneamente.", + "featureMongoTitle": "Suporte MongoDB", + "featureMongoDesc": "Suporte nativo para MongoDB com filtragem avançada.", + "featureQueryTitle": "Consulta Inteligente", + "featureQueryDesc": "Construtor de consultas poderoso com destaque de sintaxe.", + "connectCta": "Conectar ao MongoDB", + "footerNote": "Atualmente suporta {db}. Mais bancos de dados em breve.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Selecione uma Coleção", + "selectCollectionDesc": "Escolha uma coleção na barra lateral para visualizar documentos e começar a explorar seus dados.", + "dialogAddConnection": "Adicionar Conexão", + "toastConnectionAdded": "Conexão adicionada", + "toastFetchFailed": "Falha ao buscar documentos para {collection}", + "toastDocDeleted": "Documento excluído", + "deleteDocTitle": "Tem certeza absoluta?", + "deleteDocDescription": "Esta ação não pode ser desfeita. Isso excluirá permanentemente o documento da sua coleção.", + "closeAllTitle": "Fechar todas as abas?", + "closeAllDescription": "Tem certeza de que deseja fechar todas as abas? Quaisquer alterações não salvas nos filtros de consulta serão perdidas.", + "closeAllConfirm": "Fechar Todas", + "cancel": "Cancelar", + "delete": "Excluir" + }, + "connection": { + "titleEdit": "Editar Conexão", + "titleConnect": "Conectar ao MongoDB", + "subtitleEdit": "Atualize os detalhes da sua conexão abaixo.", + "subtitleConnect": "Insira sua string de conexão MongoDB para começar a explorar seus dados.", + "labelName": "Nome", + "placeholderName": "ex: BD de Produção", + "labelConnectionString": "String de Conexão", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Formato: mongodb://usuario:senha@host:porta/banco", + "testConnection": "Testar Conexão", + "connecting": "Conectando...", + "updateConnect": "Atualizar e Conectar", + "connect": "Conectar", + "cancelEdit": "Cancelar Edição", + "savedConnections": "Conexões Salvas", + "emptySaved": "Nenhuma conexão salva ainda.", + "lastUsed": "Usado pela última vez {time}", + "toastTestOk": "Conexão bem-sucedida!", + "toastTestFail": "Falha na conexão: {message}", + "toastUpdated": "Conexão atualizada", + "confirmDelete": "Tem certeza de que deseja excluir esta conexão salva?", + "toastDeletedConn": "Conexão excluída", + "toastDeleteFail": "Falha ao excluir conexão", + "copyStringTitle": "Copiar string de conexão", + "toastStringCopied": "String de conexão copiada!", + "editTitle": "Editar", + "deleteTitle": "Excluir" + }, + "sidebar": { + "explorer": "Explorador", + "add": "Adicionar", + "tooltipAdd": "Adicionar nova conexão", + "tooltipRefresh": "Atualizar conexões", + "searchPlaceholder": "Buscar...", + "loadingConnections": "Carregando conexões...", + "noConnectionsFound": "Nenhuma conexão encontrada", + "toastLoadFail": "Falha ao carregar conexões", + "toastConnectFail": "Falha ao conectar a {name}", + "toastCollectionsFail": "Falha ao buscar coleções para {name}", + "toastRenamed": "Conexão renomeada", + "toastRenameFail": "Falha ao renomear conexão", + "confirmDeleteConnection": "Tem certeza de que deseja excluir a conexão \"{name}\"?", + "toastDeleted": "Conexão excluída", + "toastDeleteConnFail": "Falha ao excluir conexão", + "confirmDropDb": "Tem certeza de que deseja descartar o banco de dados \"{name}\"? Esta ação não pode ser desfeita.", + "toastDbDropped": "Banco de dados {name} descartado", + "confirmDropCollection": "Tem certeza de que deseja descartar a coleção \"{name}\"? Esta ação não pode ser desfeita.", + "toastCollectionDropped": "Coleção {name} descartada", + "toastCollectionRenamed": "Coleção renomeada para {name}", + "toastDatabaseRenamed": "Banco de dados renomeado para {name}", + "connecting": "Conectando...", + "connectionFailed": "Falha na conexão", + "noDatabases": "Sem bancos de dados", + "loading": "Carregando...", + "noCollections": "Sem coleções", + "renameCollectionTitle": "Renomear Coleção", + "renameDatabaseTitle": "Renomear Banco de Dados", + "placeholderNewCollection": "Novo nome da coleção", + "placeholderNewDatabase": "Novo nome do banco de dados", + "rename": "Renomear", + "refresh": "Atualizar", + "copyDbName": "Copiar Nome do BD", + "toastDbNameCopied": "Nome do banco de dados copiado para a área de transferência", + "dropDatabase": "Descartar Banco de Dados", + "dropCollection": "Descartar Coleção", + "menuRename": "Renomear", + "menuCopyConnectionString": "Copiar String de Conexão", + "menuDeleteConnection": "Excluir Conexão", + "toastSidebarStringCopied": "String de conexão copiada!", + "cancel": "Cancelar", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} docs", + "table": "Tabela", + "json": "JSON", + "tree": "Árvore", + "tooltipTable": "Visualização em Tabela", + "tooltipJson": "Visualização JSON", + "tooltipTree": "Visualização em Árvore", + "itemsPerPage": "Itens por página", + "perPage": "{n} / página", + "prevPage": "Página Anterior", + "nextPage": "Próxima Página", + "expandAll": "Expandir Tudo", + "collapseAll": "Recolher Tudo", + "refreshData": "Atualizar Dados", + "insert": "Inserir", + "export": "Exportar", + "actions": "Ações", + "invalidJson": "Conteúdo JSON inválido", + "copiedClipboard": "Copiado para a área de transferência", + "docUpdated": "Documento atualizado com sucesso", + "docInserted": "Documento inserido com sucesso", + "invalidJsonShort": "JSON inválido", + "docCopied": "Documento copiado para a área de transferência", + "openFullEditor": "Abrir no editor completo", + "cellArray": "Array({length})", + "cellObject": "Objeto", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Nenhum documento corresponde à sua consulta", + "emptyFilterHint": "Tente ajustar seu filtro ou limpar a consulta.", + "clearFilter": "Limpar Filtro", + "emptyCollectionTitle": "Esta coleção está vazia", + "emptyCollectionHint": "Insira um documento para começar.", + "insertDocument": "Inserir Documento", + "formatJson": "Formatar JSON", + "compactJson": "Compactar JSON", + "copyClipboard": "Copiar para Área de Transferência", + "copyJson": "Copiar JSON", + "duplicate": "Duplicar", + "editDoc": "Editar Documento", + "deleteDoc": "Excluir Documento", + "documentLabel": "Documento {n}", + "editDialogTitle": "Editar Documento", + "insertDialogTitle": "Inserir Documento", + "viewValueTitle": "Visualizar Valor", + "saveChanges": "Salvar Alterações", + "close": "Fechar", + "cancel": "Cancelar", + "insertButton": "Inserir", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Filtro ativo aplicado", + "filterActiveTooltip": "Filtro ativo", + "closeAll": "Fechar Todas" + }, + "query": { + "migrateHistory": "Histórico de consultas migrado para a nuvem", + "saveHistoryFail": "Falha ao salvar histórico de consultas", + "deleteQueryFail": "Falha ao excluir consulta", + "invalidJsonQuery": "Consulta JSON inválida", + "placeholder": "ex: { \"status\": \"ativo\" }", + "clearQuery": "Limpar consulta", + "openAdvanced": "Abrir Editor Avançado", + "visualBuilder": "Construtor Visual de Consultas", + "filterRules": "Regras de Filtro", + "addRule": "Adicionar Regra", + "noFilters": "Nenhum filtro aplicado. Adicione uma regra para filtrar documentos.", + "where": "ONDE", + "and": "E", + "fieldPlaceholder": "Campo", + "opPlaceholder": "Op", + "typePlaceholder": "Tipo", + "opRegex": "Regex", + "opIn": "Em", + "opNin": "Não Em", + "opExists": "Existe", + "typeAuto": "Auto", + "typeString": "Texto", + "typeNumber": "Número", + "typeBoolean": "Booleano", + "typeDate": "Data", + "typeObjectId": "ObjectId", + "true": "Verdadeiro", + "false": "Falso", + "valuePlaceholder": "Valor", + "valueListPlaceholder": "valor1, valor2", + "clearFilters": "Limpar Filtros", + "applyFilters": "Aplicar Filtros", + "queryHistory": "Histórico de Consultas", + "noSavedQueries": "Nenhuma consulta salva ainda", + "advancedEditorTitle": "Editor de Consultas Avançado", + "templatesPlaceholder": "Modelos", + "templateEmptyAgg": "Agregação Vazia", + "templateMatch": "$match (Filtro)", + "templateLookup": "$lookup (Junção)", + "templateGroup": "$group (Contagem/Soma)", + "format": "Formatar", + "formattedJson": "JSON Formatado", + "runQuery": "Executar Consulta" + }, + "export": { + "selectFieldsError": "Selecione pelo menos um campo para exportar", + "success": "Exportado com sucesso para {filename}", + "fail": "Falha ao exportar dados", + "title": "Exportar Dados", + "formatLabel": "Formato de Exportação", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Selecionar Campos", + "selectAll": "Selecionar Tudo", + "fieldsSelected": "{count, plural, one {# campo selecionado} other {# campos selecionados}}", + "cancel": "Cancelar", + "export": "Exportar", + "sheetName": "Dados" + }, + "jsonTree": { + "typeLabel": "Tipo: {type}", + "itemSummary": "{count, plural, one {# item} other {# itens}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Nova Requisição", + "importedRequest": "Requisição Importada", + "untitledRequest": "Requisição Sem Título" + }, + "toasts": { + "curlImported": "cURL importado com sucesso", + "curlParseFailed": "Falha ao analisar comando cURL", + "invalidJsonBody": "Corpo JSON inválido", + "requestFailed": "Requisição falhou: {message}", + "curlPasted": "cURL colado e analisado com sucesso", + "responseCopied": "Resposta copiada para a área de transferência", + "codeCopied": "Código copiado para a área de transferência", + "copyFailed": "Falha ao copiar para a área de transferência" + }, + "layout": { + "collections": "Coleções", + "closeSidebar": "Fechar barra lateral", + "openSidebar": "Abrir barra lateral", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Método", + "urlPlaceholder": "https://api.exemplo.com/v1/...", + "sending": "Enviando...", + "send": "Enviar", + "invalidJsonBodyHelp": "Não é possível enviar: o corpo JSON é inválido" + }, + "requestTabs": { + "params": "Parâmetros", + "headers": "Cabeçalhos", + "body": "Corpo", + "auth": "Autenticação", + "bodyType": "Tipo do corpo", + "selectType": "Selecionar tipo", + "bodyNone": "Nenhum", + "bodyJson": "JSON", + "bodyText": "Texto", + "noBodyHint": "Sem conteúdo de corpo para esta requisição", + "switchToJson": "Mudar para corpo JSON", + "authType": "Tipo de autenticação", + "noAuth": "Sem autenticação", + "bearerToken": "Token Bearer", + "basicAuth": "Autenticação básica", + "apiKey": "Chave API", + "labelToken": "Token", + "placeholderBearerToken": "Token Bearer", + "labelUsername": "Usuário", + "placeholderUsername": "Usuário", + "labelPassword": "Senha", + "placeholderPassword": "Senha", + "labelKey": "Chave", + "placeholderApiKeyName": "Nome da chave (ex: X-Api-Key)", + "labelValue": "Valor", + "placeholderValue": "Valor", + "addTo": "Adicionar em", + "header": "Cabeçalho", + "queryParams": "Parâmetros de consulta", + "noAuthHint": "Esta requisição não usa autenticação." + }, + "responsePanel": { + "emptyTitle": "Pronto para explorar?", + "emptyHint": "Insira uma URL e clique em Enviar para ver os dados da resposta aqui.", + "responsePreviewAlt": "Pré-visualização da resposta", + "previewUnavailable": "Pré-visualização não disponível para este tipo de conteúdo", + "status": "Status", + "time": "Tempo", + "size": "Tamanho", + "copyBody": "Copiar corpo", + "downloadResponse": "Baixar resposta", + "bodyTab": "Corpo", + "previewTab": "Pré-visualização", + "headersTab": "Cabeçalhos", + "binaryRawView": "Dados binários não podem ser exibidos na visualização bruta.", + "errorStatusLabel": "Erro", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Nova aba" + }, + "collectionsSidebar": { + "title": "Recursos API", + "newCollection": "Nova coleção", + "tabCollections": "Coleções", + "tabHistory": "Histórico", + "noCollectionsTitle": "Sem coleções", + "noCollectionsHint": "Crie uma coleção para organizar suas requisições API.", + "createCollection": "Criar coleção", + "newFolder": "Nova pasta", + "rename": "Renomear", + "delete": "Excluir", + "emptyCollection": "Coleção vazia", + "recentRequests": "Requisições recentes", + "clearAll": "Limpar tudo", + "noHistoryTitle": "Sem histórico ainda", + "noHistoryHint": "Envie uma requisição para vê-la aqui.", + "dialogNewFolderTitle": "Nova pasta", + "dialogNewFolderDescription": "Crie uma nova pasta para organizar suas requisições.", + "labelFolderName": "Nome da pasta", + "placeholderFolderName": "Minha pasta", + "createFolder": "Criar pasta", + "dialogNewCollectionTitle": "Nova coleção", + "dialogNewCollectionDescription": "Crie uma nova coleção de nível superior.", + "labelCollectionName": "Nome da coleção", + "placeholderCollectionName": "Minha coleção", + "dialogRenameCollectionTitle": "Renomear coleção", + "dialogRenameCollectionDescription": "Insira um novo nome para a coleção.", + "placeholderRenameCollection": "Nome da coleção", + "renameAction": "Renomear", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Nova pasta", + "delete": "Excluir", + "emptyFolder": "Pasta vazia" + }, + "environmentManager": { + "noEnvironment": "Sem ambiente", + "manageDialogTitle": "Gerenciar ambientes", + "placeholderNewEnv": "Novo ambiente", + "variablesHeading": "Variáveis de {name}", + "addVariable": "Adicionar variável", + "columnVariable": "Variável", + "columnValue": "Valor", + "placeholderKey": "Chave", + "placeholderValue": "Valor", + "selectEnvToEdit": "Selecione um ambiente para editar" + }, + "codeGenerator": { + "triggerTitle": "Gerar código", + "dialogTitle": "Gerar código", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Copiar", + "errorGenerating": "Erro ao gerar código" + }, + "keyValueEditor": { + "columnKey": "Chave", + "columnValue": "Valor", + "placeholderKey": "Chave", + "placeholderValue": "Valor", + "addItem": "Adicionar item" + }, + "importCurl": { + "trigger": "Importar cURL", + "dialogTitle": "Importar cURL", + "dialogDescription": "Cole seu comando cURL abaixo para importá-lo como uma nova requisição.", + "placeholder": "curl -X POST https://api.exemplo.com/dados -d '...'", + "import": "Importar" + }, + "saveRequest": { + "trigger": "Salvar", + "dialogTitle": "Salvar requisição", + "dialogDescription": "Salve esta requisição em uma pasta de coleção.", + "labelName": "Nome da requisição", + "placeholderName": "Minha requisição", + "labelFolder": "Pasta", + "placeholderFolder": "Selecione uma pasta", + "save": "Salvar" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Modo de Edição", + "viewOnlyMode": "Modo Somente Leitura", + "insertComponent": "Inserir componente", + "addUnorderedList": "Adicionar lista não ordenada", + "addOrderedList": "Adicionar lista ordenada", + "addTable": "Adicionar tabela" + }, + "addBlock": "Adicionar bloco", + "media": { + "popoverTitle": "Adicionar mídia", + "singleImage": "Imagem Única", + "singleImageDesc": "Enviar uma imagem", + "multipleImages": "Múltiplas Imagens", + "multipleImagesDesc": "Enviar grade de imagens", + "video": "Vídeo", + "videoDesc": "Enviar arquivo de vídeo", + "uploadingAlt": "Enviando...", + "uploadFailedAlt": "Falha no envio" + }, + "export": { + "fabAria": "Exportar código", + "title": "Exportar Código", + "description": "Copie a saída HTML ou JSON do conteúdo do seu editor", + "preview": "Pré-visualização", + "html": "HTML", + "json": "JSON", + "previewOptions": "Opções de Pré-visualização", + "enhanceSpaces": "Melhorar Espaços", + "livePreview": "Pré-visualização ao vivo do HTML renderizado", + "htmlTailwind": "HTML com classes Tailwind CSS", + "editorJson": "Estado do editor como JSON", + "copyHtml": "Copiar HTML", + "copyJson": "Copiar JSON", + "copied": "Copiado!" + }, + "template": { + "fabTitle": "Trocar Modelo", + "title": "Escolha um Modelo", + "description": "Selecione um modelo para começar rapidamente com layouts e conteúdo pré-construídos", + "catAll": "Todos os Modelos", + "catProductivity": "Produtividade", + "catCreative": "Criativo", + "catBusiness": "Negócios", + "catPersonal": "Pessoal", + "noTemplates": "Nenhum modelo encontrado nesta categoria", + "confirmTitle": "Substituir Conteúdo Existente?", + "confirmDescription": "Você tem conteúdo existente no editor. Aplicar este modelo substituirá todo o conteúdo atual.", + "cannotUndo": "Esta ação não pode ser desfeita.", + "cancel": "Cancelar", + "replaceContent": "Substituir Conteúdo" + }, + "command": { + "searchPlaceholder": "Buscar comandos...", + "noResults": "Nenhum comando encontrado.", + "turnInto": "Transformar em", + "h1": { + "label": "Título 1", + "description": "Título de seção grande", + "keywords": "h1,título,heading,grande" + }, + "h2": { + "label": "Título 2", + "description": "Título de seção médio", + "keywords": "h2,título,subtítulo" + }, + "h3": { + "label": "Título 3", + "description": "Título de seção pequeno", + "keywords": "h3,título,subtítulo" + }, + "h4": { + "label": "Título 4", + "description": "Título de seção mínimo", + "keywords": "h4,título" + }, + "h5": { + "label": "Título 5", + "description": "Título menor", + "keywords": "h5,título" + }, + "h6": { + "label": "Título 6", + "description": "Título mínimo", + "keywords": "h6,título" + }, + "p": { + "label": "Parágrafo", + "description": "Parágrafo de texto regular", + "keywords": "p,parágrafo,texto,normal" + }, + "code": { + "label": "Bloco de Código", + "description": "Trecho de código", + "keywords": "código,bloco,trecho,pre" + }, + "blockquote": { + "label": "Citação", + "description": "Bloco de citação", + "keywords": "citação,blockquote" + }, + "li": { + "label": "Lista com Marcadores", + "description": "Item de lista simples com marcador", + "keywords": "lista,marcador,não ordenada,ul,li" + }, + "ol": { + "label": "Lista Numerada", + "description": "Item de lista numerada", + "keywords": "lista,numerada,ordenada,ol,li" + }, + "img": { + "label": "Imagem", + "description": "Enviar ou incorporar uma imagem", + "keywords": "imagem,img,foto,upload" + }, + "video": { + "label": "Vídeo", + "description": "Enviar ou incorporar um vídeo", + "keywords": "vídeo,vid,filme,mp4,upload" + }, + "table": { + "label": "Tabela", + "description": "Criar uma tabela", + "keywords": "tabela,grade,linhas,colunas,células" + } + }, + "toasts": { + "tableImported": "Tabela Importada", + "tableImportedDesc": "Tabela markdown importada com sucesso", + "mediaPasted": "Mídia colada", + "pasteMixed": "{imageCount} imagem(ns) e {videoCount} vídeo(s) colados com sucesso.", + "pasteImages": "{count} imagem(ns) colada(s) com sucesso.", + "pasteVideos": "{count} vídeo(s) colado(s) com sucesso.", + "pasteFailed": "Falha ao colar", + "unexpectedError": "Ocorreu um erro inesperado", + "uploadFailed": "Falha no envio" + }, + "blockBackground": { + "menuItem": "Alterar Cor de Fundo", + "dialogTitle": "Cor de Fundo do Bloco", + "dialogDescription": "Escolha uma cor de fundo para este bloco", + "tabPreset": "Cores Predefinidas", + "tabCustom": "Cor Personalizada", + "sectionHeading": "Cores de Fundo", + "apply": "Aplicar", + "colors": { + "none": "Nenhuma", + "red": "Vermelho", + "orange": "Laranja", + "yellow": "Amarelo", + "green": "Verde", + "blue": "Azul", + "indigo": "Índigo", + "purple": "Roxo", + "pink": "Rosa", + "teal": "Verde-azulado", + "cyan": "Ciano", + "gray": "Cinza" + } + }, + "tableDialog": { + "title": "Criar Tabela", + "description": "Crie uma nova tabela ou importe do markdown", + "markdownCheckbox": "Tenho uma tabela markdown", + "pasteMarkdownLabel": "Colar Tabela Markdown", + "markdownPlaceholder": "| Cabeçalho 1 | Cabeçalho 2 | Cabeçalho 3 |\n|----------|----------|----------|\n| Célula 1 | Célula 2 | Célula 3 |\n| Célula 4 | Célula 5 | Célula 6 |", + "markdownHint": "Cole sua tabela markdown acima. Deve incluir linhas de cabeçalho e separador.", + "rows": "Linhas", + "columns": "Colunas", + "maxDimensions": "Máximo: 20 linhas × 10 colunas", + "cancel": "Cancelar", + "importTable": "Importar Tabela", + "createTable": "Criar Tabela" + }, + "tableParse": { + "errors": { + "minRows": "A tabela deve ter pelo menos uma linha de cabeçalho e uma linha separadora", + "pipeFormat": "As linhas da tabela devem começar e terminar com |", + "headerMinCol": "A linha de cabeçalho deve ter pelo menos uma coluna", + "separatorRow": "A segunda linha deve ser um separador (ex: |---|---|)", + "rowColumnMismatch": "A linha {row} tem {got} colunas, esperado {expected}", + "parseFailed": "Falha ao analisar tabela markdown" + } + }, + "link": { + "editLink": "Editar Link", + "addLink": "Adicionar Link", + "selectedText": "Texto selecionado: \"{text}\"", + "linkUrl": "URL do Link", + "urlPlaceholder": "https://exemplo.com", + "update": "Atualizar", + "addLinkButton": "Adicionar Link", + "toastApplied": "Link Aplicado", + "toastLinkedTo": "Vinculado a: {url}", + "toastRemoved": "Link Removido", + "toastRemovedDesc": "Link foi removido da seleção" + }, + "customClass": { + "devMode": "Modo Dev", + "toggleDevModeAria": "Alternar modo dev", + "searchPlaceholderDev": "Buscar classes... (ex: 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Buscar estilos... (ex: 'red', 'bold', 'shadow')", + "appliesTitle": "Aplica: {className}", + "noMatches": "Nenhuma classe encontrada para \"{query}\"", + "toastClassReplaced": "Classe Substituída", + "toastClassReplacedDesc": "Substituída \"{replaced}\" por \"{className}\"", + "toastCustomClassApplied": "Classe Personalizada Aplicada", + "toastAppliedClass": "Classe aplicada: {className}" + }, + "insertComponents": { + "title": "Inserir Componente", + "description": "Escolha um componente para inserir no seu documento", + "noComponents": "Nenhum componente disponível", + "categories": { + "media": "Mídia", + "layout": "Layout", + "interactive": "Interativo", + "decoration": "Decoração" + }, + "freeImage": { + "name": "Imagem com Movimento Livre", + "description": "Adicione uma imagem que pode ser posicionada em qualquer lugar e redimensionada livremente" + } + } + }, + "JsonFormatter": { + "title": "Formatador JSON", + "description": "Formate, valide e edite JSON nas visualizações de texto e árvore.", + "documentNameText": "Documento de texto {n}", + "documentNameTree": "Documento em árvore {n}", + "newDocument": "Novo", + "copy": "Copiar", + "save": "Salvar", + "load": "Carregar", + "saving": "Salvando...", + "leftDocNameLabel": "Nome do documento esquerdo", + "rightDocNameLabel": "Nome do documento direito", + "toastCopiedText": "Painel de texto copiado", + "toastCopiedTree": "Painel de árvore copiado", + "toastCopyFailed": "Falha ao copiar JSON", + "toastLoginRequired": "Faça login para salvar documentos", + "toastSaved": "Documento salvo", + "toastSaveFailed": "Falha ao salvar documento", + "format": "Formatar", + "compact": "Compactar", + "sortKeys": "Ordenar chaves", + "repair": "Reparar", + "query": "Consulta", + "toastInvalidJson": "JSON inválido", + "toastAlreadyValid": "O JSON já é válido", + "toastRepaired": "JSON reparado", + "toastRepairFailed": "Não foi possível reparar o JSON" + }, + "JwtDecoder": { + "title": "Decodificador JWT", + "subtitle": "Inspecione header e payload. A expiração usa a claim exp. Nada é enviado a um servidor; a assinatura não é verificada.", + "tokenLabel": "Token", + "clear": "Limpar", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Copiar", + "expiryLabel": "Expiração (exp)", + "signaturePresent": "Assinatura presente (não verificada)", + "noExp": "Sem claim exp no payload.", + "status": { + "expired": "Expirado", + "valid": "Válido" + }, + "fields": { + "unix": "Unix", + "local": "Local", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Emitido em (iat)", + "notBefore": "Não antes de (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Cole um JWT para decodificar.", + "segments": "Um JWT precisa de pelo menos header e payload (dois segmentos).", + "base64": "Não foi possível decodificar em base64url o header ou o payload." + }, + "relative": { + "future": "em {n} {unit}", + "past": "há {n} {unit}" + }, + "units": { + "second": "{count, plural, one {segundo} other {segundos}}", + "minute": "{count, plural, one {minuto} other {minutos}}", + "hour": "{count, plural, one {hora} other {horas}}", + "day": "{count, plural, one {dia} other {dias}}" + }, + "tabs": { + "decode": "Decodificar", + "sign": "Gerador / assinador" + }, + "signer": { + "algLabel": "Algoritmo (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "JSON do payload", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON do header", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Segredo (HS*)", + "secretPlaceholder": "Cole seu segredo HMAC…", + "privateKeyLabel": "Chave privada PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "O algoritmo \"none\" cria um token sem assinatura. Isso é inseguro e deve ser usado apenas para testes.", + "sign": "Assinar", + "verify": "Verificar assinatura", + "outputLabel": "Token (JWS compacto)", + "outputPlaceholder": "Clique em \"Assinar\"…", + "copyToken": "Copiar token", + "copied": "Copiado", + "verifyKeyLabel": "Chave de verificação (opcional)", + "verifyKeyPlaceholder": "Para RS*: cole a chave pública (SPKI) PEM. Para HS*: deixe vazio para reutilizar o segredo.", + "verifyKeyHint": "Se estiver vazio, a verificação usa a chave de assinatura (útil para HS*).", + "verifyStatus": { + "valid": "Assinatura válida", + "invalid": "Assinatura inválida" + }, + "securityNote": "Tudo roda localmente no navegador; chaves e tokens não são enviados." + }, + "signErrors": { + "invalidJson": "Header e payload devem ser JSON válido.", + "unsupportedAlg": "Algoritmo não suportado.", + "missingKey": "Uma chave de assinatura é necessária para este algoritmo.", + "cryptoFailed": "Falha ao assinar. Verifique o formato da chave e tente novamente." + }, + "verifyErrors": { + "empty": "Digite um JWT para verificar.", + "segments": "Um JWT deve ter exatamente 3 segmentos para verificar uma assinatura.", + "unsupportedAlg": "Algoritmo não suportado ou alg ausente no header.", + "missingKey": "Uma chave de verificação é necessária para este algoritmo.", + "base64": "Não foi possível decodificar o header ou a assinatura do JWT.", + "cryptoFailed": "Falha ao verificar. Verifique o formato da chave e tente novamente." + } + }, + "CertificatePemDecoder": { + "title": "Decodificador de certificado / PEM", + "subtitle": "Cole um certificado X.509 (-----BEGIN CERTIFICATE-----) ou um CSR (-----BEGIN CERTIFICATE REQUEST-----). Assunto, emissor, datas e SAN são analisados localmente; nada é enviado.", + "pemLabel": "Entrada PEM", + "clear": "Limpar", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Analisando…", + "copyHex": "Copiar impressão digital (hex sem dois pontos)", + "errors": { + "empty": "Cole um certificado PEM ou CSR para decodificar.", + "noBlock": "Nenhum bloco PEM de certificado ou CSR encontrado.", + "parseFailed": "Não foi possível analisar estes dados PEM." + }, + "block": { + "title": "Bloco PEM {index}", + "certificate": "Certificado X.509", + "csr": "Solicitação de assinatura de certificado" + }, + "fields": { + "subject": "Assunto", + "issuer": "Emissor", + "notBefore": "Inválido antes de", + "notAfter": "Inválido depois de", + "serial": "Número de série (hex)", + "sha256": "Impressão digital SHA-256", + "sans": "Nomes alternativos do assunto", + "sansNone": "Sem extensão SAN.", + "signatureAlgorithm": "Algoritmo de assinatura", + "csrIssuerNote": "CSRs não incluem emissor (aparece no certificado emitido).", + "csrValidityNote": "CSRs não incluem datas de validade (aparecem no certificado emitido)." + }, + "sanType": { + "dns": "DNS", + "dn": "Nome distinto", + "email": "E-mail", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID registrado", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Formatador SQL", + "subtitle": "Formate SQL para MySQL, PostgreSQL ou SQLite. Roda no navegador; nada é enviado.", + "dialectLabel": "Dialeto", + "keywordsLabel": "Palavras-chave", + "indentLabel": "Indentação", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "MAIÚSCULAS", + "lower": "minúsculas", + "preserve": "Manter" + }, + "spaces": "{n} espaços", + "format": "Formatar", + "clear": "Limpar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "formattedPanel": "Formatado", + "copy": "Copiar", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Clique em \"Formatar\"…", + "errors": { + "tooLong": "A entrada é limitada a {max} caracteres.", + "couldNotFormat": "Não foi possível formatar este SQL." + } + }, + "YamlFormatter": { + "title": "Formatador YAML", + "subtitle": "Formate, valide e converta YAML no navegador. Nada é enviado.", + "outputLabel": "Saída", + "outputModes": { + "yaml": "YAML (formatado)", + "json": "JSON" + }, + "indentLabel": "Recuo", + "spaces": "{n} espaços", + "format": "Formatar", + "clear": "Limpar", + "copy": "Copiar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "outputPanel": "Saída", + "errors": { + "tooLong": "A entrada é limitada a {max} caracteres.", + "couldNotFormat": "Não foi possível analisar este YAML." + } + }, + "GraphqlFormatter": { + "title": "Formatador GraphQL", + "subtitle": "Formate consultas, mutações e assinaturas GraphQL no navegador. Nada é enviado.", + "tabs": { + "format": "Formatar", + "build": "Editor de consulta" + }, + "outputModeLabel": "Saída", + "outputModes": { + "pretty": "Impressão legível", + "minify": "Minificar" + }, + "format": "Formatar", + "clear": "Limpar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "outputPanel": "Saída", + "copy": "Copiar", + "builderHint": "Monte um esqueleto e aplique-o ao editor. Definições de variáveis em sintaxe GraphQL (ex.: $id: ID!) — parênteses opcionais.", + "builder": { + "operation": "Operação", + "operationName": "Nome da operação", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definições de variáveis", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Conjunto de seleção", + "apply": "Aplicar à entrada", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "A entrada está limitada a {max} caracteres.", + "couldNotFormat": "Não foi possível analisar ou imprimir este GraphQL.", + "emptySelection": "Adicione uma seleção antes de aplicar." + } + }, + "RegexTester": { + "title": "Testador de regex", + "subtitle": "Regex estilo JavaScript ao vivo no seu texto. As correspondências são destacadas; nada sai do seu navegador.", + "patternLabel": "Padrão", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i ignorar maiúsc./minúsc.", + "m": "m multiline", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(vazio)", + "noFlags": "—", + "matches": "{count, plural, one {correspondência} other {correspondências}}", + "testTextLabel": "Texto de teste", + "counter": "{current} / {max}", + "clear": "Limpar", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "O texto é limitado a {max} caracteres.", + "invalid": "Expressão regular inválida.", + "runaway": "Interrompido após {max} correspondências (possível padrão descontrolado)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Conversor de timestamp", + "subtitle": "Segundos Unix (≤10 dígitos) ou milissegundos (inteiros maiores), ISO-8601 ou textos que o navegador consegue interpretar. Tudo roda localmente.", + "inputLabel": "Entrada", + "placeholder": "1744200000 ou 2025-04-09T12:00:00Z", + "now": "Agora", + "copy": "Copiar", + "hint": "Cole um valor ou clique em Agora.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativo", + "human": "Legível" + }, + "fields": { + "seconds": "Segundos", + "milliseconds": "Milissegundos", + "utc": "UTC", + "local": "Local", + "distance": "Distância" + }, + "errors": { + "empty": "Insira um timestamp Unix, uma string ISO ou um texto de data.", + "invalidNumber": "Timestamp numérico inválido.", + "outOfRange": "Fora do intervalo para Date do JavaScript.", + "couldNotParse": "Não foi possível interpretar como Unix, ISO-8601 ou data do navegador." + } + }, + "HttpStatusCodes": { + "title": "Códigos de status HTTP", + "subtitle": "Pesquise e navegue pelos códigos de status de respostas HTTP (1xx5xx) com descrições curtas e referências RFC. Funciona localmente.", + "header": { + "title": "Códigos de status HTTP", + "description": "Referência pesquisável de códigos de resposta HTTP com descrições e referências RFC." + }, + "searchPlaceholder": "Pesquisar: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtrar classe…", + "classFilterAll": "Todas as classes", + "resultsCount": "{count} de {total}", + "table": { + "code": "Code", + "phrase": "Frase", + "description": "Description", + "copy": "Copiar" + }, + "noResults": "Nenhum resultado para “{query}”.", + "specPrefix": "Espec.:", + "copied": "Código copiado" + }, + "CronBuilder": { + "title": "Construtor de expressão Cron", + "subtitle": "Cron padrão de 5 campos: minute hour day month weekday. O parser usa seconds=0 internamente. O formato opcional de 6 campos (sec min hour dom mon dow) funciona no editor.", + "tabs": { + "builder": "Construtor visual", + "expression": "Expressão" + }, + "presetsLabel": "Predefinições", + "fieldsLabel": "Campos", + "rawExpressionLabel": "Expressão bruta", + "expressionHint": "Tire o foco do campo para analisar e sincronizar. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Expressão atual", + "copy": "Copiar", + "quickPick": "Escolha rápida", + "emptyExpression": "—", + "plainLanguageLabel": "Linguagem natural", + "nextRunsLabel": "Próximas execuções (hora local)", + "fields": { + "minute": "Minuto", + "hour": "Hora", + "dayOfMonth": "Dia do mês", + "month": "Mês", + "dayOfWeek": "Dia da semana" + }, + "presets": { + "everyMinute": "A cada minuto", + "every5Minutes": "A cada 5 minutos", + "every15Minutes": "A cada 15 minutos", + "everyHour": "A cada hora (em :00)", + "every6Hours": "A cada 6 horas", + "dailyMidnight": "Diário à meia-noite", + "daily0900": "Diário às 09:00", + "weekdays0900": "Dias úteis às 09:00", + "weeklySunday": "Semanal (domingo 00:00)", + "monthlyFirst": "Mensal (dia 1 00:00)", + "yearlyJan1": "Anual (1 jan 00:00)" + }, + "picks": { + "minute": { + "every": "a cada minuto" + }, + "hour": { + "every": "a cada hora" + }, + "dom": { + "any": "qualquer dia", + "last": "último dia" + }, + "month": { + "every": "todo mês" + }, + "dow": { + "any": "qualquer dia", + "weekdays": "dias úteis", + "weekend": "fim de semana" + } + }, + "months": { + "jan": "Jan", + "feb": "Fev", + "mar": "Mar", + "apr": "Abr", + "may": "Mai", + "jun": "Jun", + "jul": "Jul", + "aug": "Ago", + "sep": "Set", + "oct": "Out", + "nov": "Nov", + "dec": "Dez" + }, + "weekdays": { + "sun": "Dom", + "mon": "Seg", + "tue": "Ter", + "wed": "Qua", + "thu": "Qui", + "fri": "Sex", + "sat": "Sáb" + }, + "errors": { + "fieldCount": "Esperados 5 campos (minute hour day month weekday) ou 6 incluindo segundos.", + "invalid": "Expressão cron inválida." + } + }, + "Base64": { + "title": "Codificador / decodificador Base64", + "subtitle": "Codifique texto em Base64 ou decodifique strings Base64", + "upload": "Enviar", + "clear": "Limpar", + "modes": { + "encode": "Codificar", + "decode": "Decodificar" + }, + "swapTitle": "Trocar entrada e saída", + "panels": { + "plainText": "Texto simples", + "base64Input": "Entrada Base64", + "base64Output": "Saída Base64", + "decodedText": "Texto decodificado" + }, + "placeholders": { + "encode": "Digite texto para codificar em Base64…", + "decode": "Cole Base64 para decodificar…" + }, + "dropHere": "Solte o arquivo aqui", + "charCount": "{count} caracteres", + "download": { + "title": "Baixar saída", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Copiar para a área de transferência", + "outputPlaceholder": "A saída aparecerá aqui…", + "errors": { + "invalidBase64": "String Base64 inválida. Verifique a entrada.", + "encodeFailed": "Falha ao codificar a entrada." + } + }, + "UrlEncode": { + "title": "Codificador / decodificador de URL", + "subtitle": "Codifique/decodifique por porcentagem com encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Enviar", + "clear": "Limpar", + "modes": { + "encode": "Codificar", + "decode": "Decodificar" + }, + "swapTitle": "Trocar entrada e saída", + "panels": { + "plainText": "Texto simples", + "encodedInput": "Entrada codificada", + "encodedOutput": "Saída codificada", + "decodedText": "Texto decodificado" + }, + "placeholders": { + "encode": "Digite texto para codificar (ex.: valores de query string)…", + "decode": "Cole texto percent-encoded para decodificar…" + }, + "dropHere": "Solte o arquivo aqui", + "charCount": "{count} caracteres", + "download": { + "title": "Baixar saída", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Copiar para a área de transferência", + "outputPlaceholder": "A saída aparecerá aqui…", + "errors": { + "invalidEncoding": "Codificação por porcentagem inválida. Verifique pares % incompletos ou escapes.", + "encodeFailed": "Não foi possível codificar (Unicode inválido na entrada)." + } + }, + "UrlParser": { + "title": "Analisador de URL", + "subtitle": "Quebre uma URL em protocolo, host, caminho, parâmetros de query e hash (fragmento).", + "upload": "Enviar", + "clear": "Limpar", + "panels": { + "input": "Entrada de URL", + "output": "Saída" + }, + "placeholders": { + "input": "Cole uma URL (ex.: https://example.com/path?foo=bar#section) ou um host/caminho (ex.: example.com/path)…" + }, + "dropHere": "Solte o arquivo aqui", + "charCount": "{count} caracteres", + "outputPlaceholder": "Os componentes analisados aparecerão aqui…", + "fields": { + "protocol": "Protocolo", + "host": "Host", + "path": "Caminho", + "hash": "Hash", + "queryParams": "Parâmetros de query" + }, + "paramCount": "{count} parâmetros", + "noParams": "Sem parâmetros de query.", + "paramKey": "Chave", + "paramValue": "Valor", + "copyTitle": "Copiar para a área de transferência", + "copyParamTitle": "Copiar como key=value", + "download": { + "title": "Baixar JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL inválida. Inclua um hostname (example.com) e verifique os caracteres." + } + }, + "UuidGenerator": { + "title": "Gerador de UUID / ULID", + "subtitle": "Gere UUIDs (RFC 9562) ou ULIDs no navegador. Exportação em lote de até {max} por execução.", + "format": "Formato", + "kindHints": { + "ulid": "Identificador ordenável, seguro para URL, 26 caracteres", + "uuid4": "Aleatório (RFC 9562)", + "uuid7": "Ordenado por tempo, ms Unix + aleatório", + "uuid1": "Baseado em MAC/tempo (RFC 9562)", + "uuid6": "Tempo gregoriano reordenado", + "uuid3": "Hash MD5 de nome + namespace", + "uuid5": "Hash SHA‑1 de nome + namespace", + "nil": "UUID nulo (tudo zeros, RFC 9562)" + }, + "countLabel": "Quantidade (1–{max})", + "nameLabel": "Nome (string de entrada)", + "namePlaceholder": "ex.: my.app/resource", + "bulkHint": "Em lote, adiciona #0, #1, … para que cada ID seja distinto (v3/v5 são determinísticos).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID personalizado" + }, + "customNamespaceLabel": "UUID do namespace", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Gerar", + "output": "Saída", + "lines": "{count} linhas", + "download": { + "title": "Baixar .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Copiar tudo", + "uppercaseToggle": "Saída em maiúsculas", + "hyphensToggle": "Mostrar hifens", + "copyJsonTitle": "Copiar como array JSON", + "outputPlaceholder": "Clique em \"Gerar\"…", + "errors": { + "invalidCustomNamespace": "O namespace personalizado deve ser um UUID válido.", + "missingName": "O nome é obrigatório para UUID v3 e v5.", + "unknown": "Não foi possível gerar os IDs." + } + }, + "SecretApiKeyGenerator": { + "title": "Gerador de segredo / chave de API", + "subtitle": "Gere strings aleatórias criptográficas com tamanho e alfabeto configuráveis. Exportação em massa de até {max} por execução. Complementa o gerador UUID / ULID.", + "alphabetLabel": "Alfabeto", + "alphabetHint": "Caracteres duplicados são removidos. Cada símbolo é escolhido de forma uniforme com crypto.getRandomValues e amostragem por rejeição (sem viés de módulo).", + "alphabetCount": "{count} símbolos únicos", + "presetsLabel": "Predefinições", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumérico", + "hex": "Hexadecimal (minúsculas)", + "asciiPrintable": "ASCII imprimível" + }, + "lengthLabel": "Tamanho por segredo (1–{maxLen})", + "countLabel": "Quantidade (1–{maxBulk})", + "generate": "Gerar", + "output": "Saída", + "lines": "{count} linhas", + "download": { + "title": "Baixar .txt", + "filename": "segredos.txt" + }, + "copyTitle": "Copiar tudo", + "outputPlaceholder": "Clique em \"Gerar\"…", + "entropy": "{bits} bits de entropia", + "strength": { + "weak": "Fraca", + "fair": "Razoável", + "strong": "Forte" + }, + "excludeAmbiguous": "Excluir caracteres ambíguos", + "excludeAmbiguousHint": "Remove {chars} — caracteres facilmente confundidos ou hostis ao shell.", + "prefixLabel": "Prefixo da chave (opcional)", + "errors": { + "emptyAlphabet": "Adicione pelo menos um caractere ao alfabeto.", + "alphabetTooLong": "Após remover duplicatas, o alfabeto pode ter no máximo {maxAlpha} caracteres.", + "lengthOutOfRange": "O tamanho deve estar entre 1 e {maxLen}.", + "countOutOfRange": "A quantidade deve estar entre 1 e {maxBulk}.", + "unknown": "Não foi possível gerar os segredos." + } + }, + "LoremIpsum": { + "title": "Gerador de Lorem Ipsum", + "subtitle": "Gere texto latino de espaço reservado para seus layouts e mockups.", + "paragraphs": "Parágrafos", + "sentences": "Frases", + "words": "Palavras", + "count": "Quantidade", + "generate": "Gerar", + "copy": "Copiar", + "startWith": "Começar com 'Lorem ipsum...'", + "outputPlaceholder": "A saída aparecerá aqui...", + "outputType": "Tipo de saída", + "bulletList": "Lista de marcadores", + "htmlMarkup": "Marcação HTML", + "htmlMarkupHint": "Envolva a saída em parágrafos ou use uma lista para o modo de marcadores", + "output": "Saída", + "download": "Baixar" + }, + "ColorPicker": { + "title": "Seletor e conversor de cores", + "subtitle": "Converta HEX, RGB e HSL; crie paletas harmônicas. Todo o processamento ocorre no seu navegador.", + "nativePicker": "Seletor nativo", + "chooseColor": "Escolha uma cor", + "eyeDropper": "Conta-gotas", + "invalidHex": "Cor HEX inválida.", + "cssValues": "Valores CSS", + "palettes": "Paletas", + "copied": "Copiado", + "clickToCopy": "Clique para copiar", + "copyAllHex": "Copiar todos os HEX", + "copy": "Copiar", + "copyColor": "Copiar {color}", + "paletteShades": "Tons", + "paletteComplementary": "Complementar", + "paletteTriadic": "Triádico", + "paletteAnalogous": "Análogo", + "paletteSplit": "Compl. dividido", + "paletteSquare": "Quadrado" + }, + "ContrastChecker": { + "title": "Verificador de contraste", + "subtitle": "Calcula a proporção de contraste WCAG 2.1 entre primeiro plano e fundo. Verifica AA e AAA para texto normal e grande. Tudo roda no seu navegador.", + "foreground": "Texto / primeiro plano", + "background": "Fundo", + "hexPlaceholder": "#RRGGBB", + "swap": "Trocar cores", + "chooseForeground": "Escolher cor do primeiro plano", + "chooseBackground": "Escolher cor de fundo", + "eyeDropper": "Conta-gotas", + "ratio": "Proporção de contraste", + "copyRatio": "Copiar proporção", + "copied": "Copiado", + "previewHeading": "Pré-visualização", + "previewBody": "Vejo xá gritando que fez show sem playback. À noite, vovô Kowalsky vê o ímã cair no pé do pinguim queixoso e feliz.", + "previewCaption": "Texto de exemplo no tamanho padrão.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — texto normal (4,5:1)", + "levelAALarge": "AA — texto grande (3:1)", + "levelAAANormal": "AAA — texto normal (7:1)", + "levelAAALarge": "AAA — texto grande (4,5:1)", + "pass": "Aprovado", + "fail": "Reprovado", + "invalidHex": "Cor HEX inválida.", + "largeTextHint": "Texto grande: 18 pt+ regular ou 14 pt+ em negrito (definições WCAG)." + }, + "DiffChecker": { + "title": "Verificador de diferenças", + "subtitle": "Comparação linha a linha de dois textos lado a lado. Tudo permanece no seu navegador.", + "rowsCount": "{count} linhas", + "truncatedWarning": "Exibindo as primeiras {max} linhas.", + "limitExceeded": "Cada lado é limitado a {max} caracteres.", + "clearBoth": "Limpar ambos", + "original": "Original", + "modified": "Modificado", + "originalPlaceholder": "Cole o texto original…", + "modifiedPlaceholder": "Cole o texto modificado…", + "comparisonLabel": "Comparação lado a lado", + "emptyState": "Insira texto em ambos os painéis para comparar." + }, + "JsonSchemaGenerator": { + "title": "Gerador de esquema JSON", + "subtitle": "Cole JSON válido para gerar um esquema JSON ou modelos tipados. Tudo roda no seu navegador.", + "languageLabel": "Idioma de saída", + "inputLabel": "Entrada JSON", + "inputPlaceholder": "Cole o JSON aqui…", + "outputLabel": "Saída gerada", + "outputPlaceholder": "O esquema ou os tipos gerados aparecerão aqui…", + "parseError": "JSON inválido", + "clear": "Limpar", + "copy": "Copiar", + "copied": "Copiado para a área de transferência", + "copyFailed": "Não foi possível copiar", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Esquema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Carregando o editor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Imagem para Base64", + "subtitle": "Solte uma imagem, obtenha um URI de dados ou uma string Base64 bruta. Tudo fica no seu navegador.", + "upload": "Carregar imagem", + "clear": "Claro", + "modes": { + "dataUri": "URI de dados", + "rawString": "Base64 bruta" + }, + "panels": { + "imageInput": "Entrada de imagem", + "output": "Saída Base64" + }, + "dropHere": "Solte a imagem aqui", + "charCount": "{contar} caracteres", + "placeholders": { + "uploadImage": "Arraste e solte uma imagem ou clique em carregar para começar." + }, + "fileTooLarge": "O arquivo é muito grande. O processamento pode congelar seu navegador.", + "copyTitle": "Copiar para a área de transferência", + "download": { + "title": "Baixar texto", + "filename": "imagem-base64.txt" + } + }, + "ImageCompressor": { + "title": "Compressor de imagens", + "subtitle": "Reduza a saída JPEG, PNG ou WebP localmente com controle de qualidade — nada é enviado.", + "upload": "Enviar imagem", + "clear": "Limpar", + "dropHere": "Solte a imagem aqui", + "placeholders": { + "uploadImage": "Arraste e solte uma imagem ou clique para escolher um arquivo." + }, + "fileTooLarge": "Arquivo grande demais (máx. {maxMb} MB).", + "unsupportedFormat": "Tipo de arquivo não suportado. Use JPEG, PNG, WebP, GIF, AVIF ou BMP.", + "compressFailed": "Não foi possível comprimir a imagem. Tente outro arquivo ou formato.", + "panels": { + "original": "Original", + "compressed": "Visualização comprimida" + }, + "outputFormat": "Formato de saída", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Qualidade", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG é sem perdas no navegador; o controle de qualidade vale só para JPEG e WebP.", + "sizes": { + "original": "Original: {size}", + "compressed": "Comprimido: {size}", + "saved": "{percent}% menor" + }, + "largerWarning": "A saída é maior que a entrada — tente outro formato ou qualidade.", + "download": "Baixar", + "downloadFilename": "imagem-comprimida", + "processing": "Comprimindo…", + "emptyCompressed": "A visualização comprimida aparece aqui." + }, + "CssGradientBuilder": { + "title": "Construtor de gradiente CSS", + "subtitle": "Construtor de gradiente visual CSS com controle de ângulo, limites de cores e cópia CSS com um clique.", + "preview": "Pré-visualização do gradiente", + "settings": "Configurações", + "type": "Tipo", + "linear": "Linear", + "radial": "Radial", + "angle": "Ângulo", + "stops": "Paradas de cores", + "color": "Cor", + "position": "Posição (%)", + "addStop": "Adicionar parada", + "deleteStop": "Excluir parada", + "output": "Saída CSS", + "copy": "Copiar CSS", + "copied": "Copiado!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Gerador .gitignore", + "subtitle": "Gere arquivos .gitignore mesclados instantaneamente para uma pilha ou combinação de tecnologia específica.", + "searchPlaceholder": "Pilhas de pesquisa (por exemplo, Node, Python, macOS)...", + "noResults": "Nenhuma pilha encontrada.", + "selectedStacks": "Pilhas selecionadas", + "output": "Visualizar saída", + "copy": "Copiar para a área de transferência", + "copied": "Copiado!", + "download": "Baixe .gitignore", + "downloading": "Baixando...", + "errorFetching": "Falha ao buscar a lista dos principais.", + "errorGenerating": "Falha ao gerar .gitignore das pilhas selecionadas.", + "generating": "Gerando...", + "emptyState": "Selecione uma ou mais pilhas de tecnologia para gerar seu .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Envie CSV ou Excel, edite o JSON e baixe CSV ou XLSX. Apenas dados tabulares; datas do Excel viram ISO no JSON.", + "dropTitle": "Solte um arquivo CSV ou Excel", + "dropHint": "Para .xlsx / .xls usa-se a primeira planilha. O CSV precisa de linha de cabeçalho.", + "chooseFile": "Escolher arquivo", + "jsonLabel": "JSON (array de objetos)", + "jsonPlaceholder": "Cole um array JSON de objetos ou importe CSV / Excel…", + "loadSample": "Carregar exemplo", + "actions": { + "format": "Formatar JSON", + "clear": "Limpar", + "copy": "Copiar", + "copied": "Copiado", + "downloadCsv": "Baixar CSV", + "downloadXlsx": "Baixar Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Dados", + "toastLoadedCsv": "CSV carregado no editor", + "toastLoadedExcel": "Excel carregado no editor", + "toastFormatted": "JSON formatado", + "toastExportedCsv": "CSV baixado", + "toastExportedXlsx": "Excel baixado", + "toastCopied": "Copiado para a área de transferência", + "footnote": "Tudo roda no navegador. O JSON deve ser um array não vazio de objetos. Valores aninhados viram JSON nas células CSV.", + "errors": { + "unsupportedFile": "Use um arquivo .csv, .xlsx ou .xls.", + "parseFailed": "Não foi possível analisar o arquivo.", + "invalidJson": "JSON inválido", + "exportFailed": "Falha na exportação", + "copyFailed": "Não foi possível copiar" + } + }, + "SnippetManager": { + "title": "Gerenciador de trechos de código", + "subtitle": "Armazene trechos no navegador ou entre para sincronizá-los com sua conta. Alterne entre ver e editar, copie com um clique, escolha a linguagem ou detecção automática e formate JSON, SQL ou código suportado pelo Monaco.", + "snippets": "Trechos", + "newSnippet": "Novo trecho", + "newSnippetTitle": "Novo trecho", + "defaultSnippetTitle": "Bem-vindo", + "defaultSnippetCode": "// Bem-vindo aos trechos de código\n// Experimente Ver vs Editar, Copiar, Formatar e idioma automático\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Buscar trechos…", + "searchAria": "Buscar trechos", + "emptySearch": "Nenhum trecho corresponde à busca.", + "snippetTitle": "Título", + "snippetTitlePlaceholder": "Nome do trecho", + "language": "Linguagem", + "detectedHint": "Realce como {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Ver", + "modeEdit": "Editar", + "copy": "Copiar", + "copied": "Copiado", + "format": "Formatar", + "cancel": "Cancelar", + "deleteTitle": "Excluir trecho?", + "deleteDescription": "“{title}” será removido deste navegador.", + "deleteConfirm": "Excluir", + "editorAria": "Editor de código do trecho", + "editorLoading": "Carregando editor…", + "toastCopied": "Copiado para a área de transferência", + "toastCopyFailed": "Não foi possível copiar", + "toastNothingToCopy": "Nada para copiar", + "toastFormatted": "Formatado", + "toastFormatFailed": "Não foi possível formatar", + "toastFormatJsonInvalid": "JSON inválido — corrija a sintaxe primeiro", + "toastFormatUnsupported": "Sem formatador para esta linguagem — tente JSON ou SQL, ou modo de edição", + "toastDeleted": "Trecho excluído", + "toastLoadFailed": "Não foi possível carregar os trechos do servidor.", + "toastSyncFailed": "Não foi possível sincronizar com o servidor.", + "languages": { + "auto": "Detecção automática", + "plaintext": "Texto simples", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Cliente SQL", + "dialogTitle": "Conexões SQL", + "mobileSheetTitle": "Explorador SQL", + "newTabLabel": "Nova aba", + "vaultLockedTitle": "Cofre bloqueado", + "vaultLockedDesc": "Desbloqueie seu cofre para acessar as conexões salvas.", + "heroTitle": "Cliente SQL", + "heroDesc": "Conecte-se ao PostgreSQL, MySQL ou MariaDB. As credenciais são criptografadas no navegador.", + "heroAddConnection": "Adicionar conexão", + "selectTableTitle": "Selecione uma tabela ou abra uma aba", + "selectTableDesc": "Clique em uma tabela na barra lateral ou use ⌘↩.", + "toastLoadFail": "Falha ao carregar conexões." + }, + "connection": { + "titleNew": "Nova conexão SQL", + "titleEdit": "Editar conexão", + "labelDbType": "Tipo de banco de dados", + "labelName": "Nome da conexão", + "placeholderName": "Meu banco de produção", + "labelHostPort": "Host e Porta", + "labelDatabase": "Banco de dados", + "placeholderDatabase": "meubanco", + "labelUsername": "Usuário", + "labelPassword": "Senha", + "labelSsl": "Ativar SSL / TLS", + "sslDesc": "Criptografar a conexão com o servidor", + "securityNote": "As credenciais são criptografadas AES-256 no navegador antes do armazenamento", + "btnTest": "Testar conexão", + "btnSave": "Salvar e conectar", + "btnUpdate": "Atualizar", + "btnCancelEdit": "Cancelar edição", + "savedConnections": "Conexões salvas", + "emptySaved": "As conexões salvas aparecerão aqui", + "confirmDelete": "Excluir esta conexão?", + "toastFillFields": "Preencha host e banco antes de testar.", + "toastConnectOk": "Conectado! {version}", + "toastConnectFail": "Falha na conexão: {message}", + "toastUnlockVault": "Desbloqueie seu cofre primeiro.", + "toastFieldsRequired": "Host e banco de dados são obrigatórios.", + "toastUpdated": "Conexão atualizada.", + "toastSaved": "Conexão salva.", + "toastSaveFail": "Falha ao salvar: {message}", + "toastDeleted": "Conexão excluída.", + "toastDeleteFail": "Falha ao excluir conexão." + }, + "sidebar": { + "title": "Explorador SQL", + "tooltipAddConnection": "Adicionar conexão", + "tooltipRefreshSchema": "Atualizar esquema", + "noConnections": "Nenhuma conexão salva ainda", + "toastSchemaFail": "Falha ao carregar esquema: {message}" + }, + "editor": { + "btnRun": "Executar", + "executing": "Executando consulta…", + "emptyTitle": "Execute uma consulta para ver os resultados", + "emptyHint": "Pressione ⌘↩ ou clique em Executar", + "toastNoConnection": "Sem conexão ativa." + }, + "results": { + "filterPlaceholder": "Filtrar resultados…", + "rowCount": "{count} {count, plural, one {linha} other {linhas}}", + "rowCountFiltered": "{filtered} / {total} linhas", + "exportCsv": "CSV", + "noRows": "Nenhuma linha retornada" + } + }, + "QrCodeGenerator": { + "title": "Gerador de QR Code", + "subtitle": "Transforme qualquer texto ou URL em um QR Code PNG. Tudo é renderizado localmente no navegador.", + "contentLabel": "Conteúdo", + "contentPlaceholder": "https://exemplo.com.br, texto simples, string Wi‑Fi, vCard…", + "styleLabel": "Estilo", + "dotTypeLabel": "Forma do ponto", + "dotTypeSquare": "Quadrado", + "dotTypeDots": "Pontos", + "dotTypeRounded": "Arredondado", + "dotTypeExtraRounded": "Extra arredondado", + "dotTypeClassy": "Elegante", + "dotTypeClassyRounded": "Elegante arredondado", + "cornerSquareLabel": "Quadrados de canto", + "cornerSquareDot": "Ponto", + "cornerSquareSquare": "Quadrado", + "cornerSquareExtraRounded": "Extra arredondado", + "cornerDotLabel": "Pontos de canto", + "cornerDotDot": "Ponto", + "cornerDotSquare": "Quadrado", + "eccLabel": "Correção de erro", + "eccL": "Baixa (~7%)", + "eccM": "Média (~15%)", + "eccQ": "Quartil (~25%)", + "eccH": "Alta (~30%)", + "marginLabel": "Margem (módulos)", + "widthLabel": "Largura da imagem", + "widthEffective": "Renderizado em {px} px", + "darkColor": "Módulos escuros", + "lightColor": "Módulos claros", + "resetColors": "Redefinir cores", + "logoLabel": "Logo central", + "logoMarginLabel": "Margem", + "uploadLogo": "Enviar imagem", + "removeLogo": "Remover logo", + "downloadPng": "Baixar PNG", + "downloadSvg": "Baixar SVG", + "copyImage": "Copiar imagem", + "copied": "Copiado", + "clear": "Limpar", + "previewAlt": "Prévia do QR Code", + "emptyHint": "Digite ou cole o conteúdo para ver a prévia.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "Não foi possível gerar o QR Code. Tente um texto menor ou menos correção.", + "copyUnsupported": "Seu navegador não suporta copiar imagens para a área de transferência.", + "copyFailed": "Não foi possível copiar para a área de transferência." + } + }, + "IpSubnetCalculator": { + "title": "Calculadora IP / sub-rede", + "subtitle": "Detalhes de CIDR IPv4 e IPv6 no navegador—sem requisições externas.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Rede", + "placeholderIpv4": "10.0.0.0/24 ou 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 ou 2001:db8::1/64", + "hintIpv4": "Use CIDR (ex.: /24) ou IPv4 com máscara decimal.", + "hintIpv6": "Inclua o tamanho do prefixo (ex.: /64). Se omitido, /128.", + "copyValue": "Copiar valor", + "rows": { + "cidr": "CIDR", + "network": "Endereço de rede", + "netmask": "Máscara de sub-rede", + "wildcard": "Máscara curinga", + "broadcast": "Broadcast", + "firstHost": "Primeiro host", + "lastHost": "Último host", + "totalAddresses": "Endereços totais", + "usableHosts": "Hosts utilizáveis", + "prefixLength": "Tamanho do prefixo (bits)", + "networkIpv6": "Endereço de rede", + "lastIpv6": "Último endereço no intervalo", + "totalAddressesIpv6": "Endereços na sub-rede" + }, + "usableNote": { + "pointToPoint": "Links /31 ponto a ponto usam ambos os endereços como hosts (RFC 3021).", + "singleHost": "/32 é um único host." + }, + "ipv6RangeHint": { + "normal": "Mostra o intervalo matemático completo; regras do roteador podem variar.", + "single": "/128 é um único endereço.", + "full": "/0 cobre todo o IPv6." + }, + "errors": { + "invalidIpv4": "Rede IPv4 inválida. Tente CIDR ou endereço + máscara.", + "invalidIpv6": "Rede IPv6 inválida. Use endereço/prefixo, ex.: 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Gerador de hash", + "subtitle": "Resumos MD5, SHA-1 e SHA-2 para texto ou arquivos; bcrypt para senhas (entrada de texto). Tudo é calculado localmente no navegador.", + "algorithmLabel": "Algoritmo", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Texto", + "modeFile": "Arquivo", + "inputLabelText": "Entrada", + "placeholderText": "Digite ou cole o texto…", + "chooseFile": "Escolher arquivo", + "clearFile": "Limpar", + "fileHint": "Os arquivos são lidos só na memória; máx. {maxMb} MB. Sem upload.", + "outputLabel": "Digest (hex)", + "copyHash": "Copiar digest", + "copied": "Copiado", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# caractere} other {# caracteres}}", + "emptyHint": "Digite texto ou escolha um arquivo.", + "securityNote": "MD5 e SHA-1 não são recomendados para novos usos de segurança; prefira SHA-256 ou SHA-512.", + "errors": { + "fileTooLarge": "Arquivo maior que {maxMb} MB.", + "fileReadFailed": "Não foi possível ler o arquivo." + }, + "bcryptRoundsLabel": "Custo (rodadas)", + "bcryptRoundsHint": "Valores maiores são mais lentos e mais resistentes à força bruta. O padrão comum é 10.", + "fileTabDisabledTitle": "O bcrypt só resume senhas a partir de texto, não arquivos arbitrários.", + "placeholderBcrypt": "Digite ou cole uma senha para resumir…", + "outputLabelBcrypt": "Hash bcrypt", + "copyBcrypt": "Copiar hash", + "emptyHintBcrypt": "Digite uma senha para calcular o hash bcrypt.", + "securityNoteBcrypt": "Cada saída bcrypt inclui sal aleatório; a mesma senha produz uma string diferente a cada vez. É esperado. Use custo alto em produção.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Laboratório de criptografia", + "subtitle": "AES-GCM no navegador: criptografar em um pacote JSON com senha ou chave bruta e descriptografar localmente. Educacional e às vezes prático — nada é enviado.", + "tabEncrypt": "Criptografar", + "tabDecrypt": "Descriptografar", + "keyModeLabel": "Material da chave", + "modePassphrase": "Senha (PBKDF2 + AES-GCM)", + "modeRawKey": "Chave AES bruta (hex ou Base64)", + "aesBitsLabel": "Tamanho da chave AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Codificação da chave bruta", + "encodingAuto": "Automático (hex se parecer hex, senão Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Chave bruta", + "rawKeyPlaceholder": "Chave de 32 bytes como 64 caracteres hex, ou Base64 para AES-256 (o tamanho depende do AES escolhido acima).", + "rawKeyPlaceholderDecrypt": "Só necessário se o pacote tiver keyDerivation \"none\".", + "passphraseLabel": "Senha", + "passphrasePlaceholder": "Senha usada apenas no navegador para derivar a chave.", + "passphrasePlaceholderDecrypt": "Só necessário se o pacote usar PBKDF2-SHA256.", + "pbkdf2IterLabel": "Iterações PBKDF2", + "pbkdf2Hint": "Entre 10.000 e 2.000.000. O padrão segue configurações comuns de cofres.", + "plaintextLabel": "Texto plano", + "plaintextPlaceholder": "Digite ou cole a mensagem para criptografar…", + "bundleInputLabel": "Pacote criptografado (JSON)", + "bundlePlaceholder": "Cole o JSON de Criptografar (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Pacote criptografado", + "plaintextOutputLabel": "Texto plano", + "encryptButton": "Criptografar", + "decryptButton": "Descriptografar", + "copyBundle": "Copiar JSON", + "copyPlaintext": "Copiar texto plano", + "copied": "Copiado", + "emptyBundleHint": "O JSON criptografado aparece aqui após criptografar.", + "emptyDecryptHint": "O texto descriptografado aparece aqui.", + "decryptKeyHint": "Use a chave bruta se keyDerivation for \"none\"; a senha se for PBKDF2-SHA256. O pacote armazena salt, iterações, IV e tamanho AES.", + "securityNote": "Chaves e texto plano não saem do navegador. Para segredos de produção prefira ferramentas dedicadas.", + "errors": { + "emptyKey": "Insira uma chave bruta compatível com o tamanho AES.", + "emptyPassphrase": "Insira uma senha.", + "badKeyMaterial": "Não foi possível ler a chave como hex ou Base64.", + "wrongKeyLength": "O tamanho da chave não corresponde ao AES escolhido (16 / 24 / 32 bytes).", + "invalidJson": "JSON inválido.", + "invalidBundle": "Este JSON não é um pacote de criptografia suportado.", + "decryptFailed": "Falha na descriptografia — segredo errado, pacote corrompido ou parâmetros incorretos.", + "encryptFailed": "Falha na criptografia. Verifique Web Crypto e as entradas." + } + }, + "HmacGenerator": { + "title": "Gerador HMAC", + "subtitle": "Para assinar webhooks e testar integrações de API. HMAC-SHA1/256/384/512 em hex ou Base64—calculado localmente no navegador.", + "secretLabel": "Chave secreta", + "messageLabel": "Mensagem / payload", + "placeholderSecret": "Cole seu segredo de assinatura…", + "placeholderMessage": "Cole o corpo bruto ou a string a autenticar (geralmente os mesmos bytes exatos do webhook).", + "algorithmLabel": "Algoritmo HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Formato de saída", + "formatHex": "Hex (minúsculas)", + "formatBase64": "Base64", + "outputLabel": "Assinatura", + "copyOutput": "Copiar assinatura", + "copied": "Copiado", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8)", + "emptyHint": "Insira uma chave secreta para calcular o HMAC.", + "securityNote": "Nunca compartilhe o segredo de assinatura. Esta página não envia dados; tudo roda no navegador.", + "errors": { + "cryptoFailed": "Não foi possível calcular o HMAC. Verifique o suporte a Web Crypto e tente novamente.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Gerador de código TOTP / 2FA", + "subtitle": "Cole o segredo Base32 do autenticador e veja o código de seis dígitos RFC 6238 atual (SHA-1, 30 s). Roda localmente no navegador—útil para testar login ou MFA.", + "secretLabel": "Segredo (Base32)", + "placeholderSecret": "Cole a chave secreta (letras A–Z e dígitos 2–7, como ao configurar o app autenticador)…", + "codeLabel": "Código atual", + "copyCode": "Copiar código", + "copied": "Copiado", + "nextIn": "Atualiza em {seconds} s", + "standardHint": "Intervalo de 30 s, 6 dígitos, HMAC-SHA-1 (compatível com Google Authenticator).", + "digitsLabel": "Dígitos", + "periodLabel": "Período", + "algorithmLabel": "Algoritmo", + "periodSeconds": "{seconds} s", + "emptyHint": "Cole um segredo para gerar um código de seis dígitos.", + "invalidHint": "Corrija o segredo para ver o código.", + "securityNote": "Trate como uma senha. O segredo não sai do navegador; nada é enviado.", + "importLabel": "Importar de um URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importar", + "qrSectionTitle": "Código QR de provisionamento", + "qrHint": "Escaneie com um app autenticador (Google Authenticator, 1Password, Aegis…) para adicionar este segredo. O QR codifica o segredo — trate-o como uma senha.", + "accountLabel": "Conta", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Emissor (opcional)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Insira um segredo válido para gerar um código QR.", + "errors": { + "invalidSecret": "Esse não é um segredo Base32 válido.", + "cryptoFailed": "Não foi possível calcular o TOTP. Verifique o Web Crypto e tente de novo.", + "invalidUri": "Isso não é um URI otpauth:// TOTP válido.", + "unsupportedUri": "Este URI usa valores que a ferramenta não suporta (os dígitos devem ser 6 ou 8; o período deve ser 15, 30 ou 60 s)." + } + }, + "MarkdownPreview": { + "title": "Pré-visualização Markdown & HTML", + "subtitle": "Renderizador Markdown em tempo real com exportação HTML e conversão HTML → Markdown. Tudo roda no seu navegador.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Entrada Markdown", + "htmlInputLabel": "Entrada HTML", + "previewLabel": "Pré-visualização", + "htmlOutputLabel": "Fonte HTML", + "mdOutputLabel": "Saída Markdown", + "inputPlaceholder": "# Olá\\n\\nDigite seu **markdown** aqui…", + "htmlInputPlaceholder": "Cole seu HTML aqui…", + "copyHtml": "Copiar HTML", + "copyMarkdown": "Copiar Markdown", + "exportHtml": "Exportar HTML", + "copied": "Copiado", + "charCount": "{count, plural, one {# caractere} other {# caracteres}}", + "emptyHint": "Comece a digitar Markdown para ver a pré-visualização ao vivo e a saída HTML.", + "emptyHintHtmlToMd": "Cole HTML para convertê-lo em Markdown.", + "securityNote": "Tudo é executado localmente no seu navegador. Nada é enviado." + }, + "NumberBaseConverter": { + "title": "Conversor de base numérica", + "subtitle": "Converta inteiros entre bases 2–36. Prefixos opcionais 0x / 0b / 0o. Tudo é calculado localmente no navegador.", + "inputBaseLabel": "Da base", + "outputBaseLabel": "Para a base", + "inputLabel": "Número de entrada", + "placeholderInput": "Digite um inteiro na base de entrada selecionada…", + "outputLabel": "Valor convertido", + "copyOutput": "Copiar resultado", + "copied": "Copiado", + "swap": "Trocar bases", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (comprimento UTF-8)", + "emptyHint": "Digite um número para converter.", + "note": "Dígitos acima de 9 usam letras A–Z (sem diferenciar maiúsculas). Espaços e sublinhados são ignorados.", + "errors": { + "invalidNumber": "Valor inválido para a base de entrada selecionada." + } + }, + "FormatConverter": { + "title": "Conversor de formatos", + "subtitle": "Converta entre JSON, YAML, TOML e XML em qualquer direção. Todas as 12 combinações. Tudo funciona no seu navegador.", + "fromLabel": "De", + "toLabel": "Para", + "swap": "Inverter", + "swapTitle": "Inverter formatos e usar saída como entrada", + "copy": "Copiar", + "copied": "Copiado", + "charCount": "{count, plural, one {# caractere} other {# caracteres}}", + "emptyHint": "Cole ou digite sua entrada à esquerda para ver a saída convertida.", + "localNote": "Tudo funciona localmente no seu navegador. Nada é enviado." + }, + "MimeTypeLookup": { + "title": "Pesquisa de tipo MIME", + "subtitle": "Pesquise o tipo MIME (tipo de mídia) para uma extensão ou nome de arquivo completo. Tudo funciona localmente no seu navegador.", + "inputLabel": "Extensão ou nome do arquivo", + "placeholder": "ex.: .png, package.json, photo.jpeg", + "hint": "Dica: cole uma extensão (com ou sem ponto) ou um nome de arquivo completo.", + "clear": "Limpar", + "exampleFilename": "Usar nome de arquivo de exemplo", + "exampleExtension": "Usar extensão de exemplo", + "resultLabel": "Resultado", + "normalizedLabel": "Entrada normalizada:", + "mimeTypeLabel": "Tipo MIME", + "contentTypeLabel": "Cabeçalho Content-Type", + "charsetLabel": "Conjunto de caracteres", + "copyMimeType": "Copiar tipo MIME", + "copy": "Copiar", + "copied": "Copiado", + "emptyHint": "Digite uma extensão ou nome de arquivo para pesquisar o tipo MIME.", + "notFound": "Nenhum tipo MIME encontrado para essa entrada.", + "localNote": "Tudo funciona localmente no seu navegador. Nada é enviado." + }, + "UserAgentParser": { + "title": "Parser de User-Agent", + "subtitle": "Cole uma string User-Agent (UA) e veja a decomposição de navegador, SO, engine, dispositivo e CPU. Tudo funciona localmente no seu navegador.", + "inputLabel": "String User-Agent", + "placeholder": "Cole seu User-Agent aqui…", + "hint": "Dica: você pode copiar um UA dos cabeçalhos da requisição, logs ou das devtools do navegador.", + "clear": "Limpar", + "exampleDesktop": "Usar exemplo de desktop", + "exampleMobile": "Usar exemplo de celular", + "resultLabel": "Decomposição", + "browserLabel": "Navegador", + "osLabel": "Sistema operacional", + "engineLabel": "Engine", + "deviceTypeLabel": "Tipo de dispositivo", + "deviceVendorLabel": "Fabricante do dispositivo", + "deviceModelLabel": "Modelo do dispositivo", + "cpuArchLabel": "Arquitetura da CPU", + "copyJson": "Copiar JSON", + "copy": "Copiar", + "copied": "Copiado", + "emptyHint": "Cole uma string User-Agent para ver a decomposição.", + "localNote": "Tudo funciona localmente no seu navegador. Nada é enviado." + }, + "MockDataGenerator": { + "title": "Gerador de dados mock", + "subtitle": "Defina colunas, escolha tipos e exporte conjuntos de dados para seeds, Postman ou testes. Até {max} linhas, tudo no navegador — nada é enviado.", + "mockarooLink": "Fluxo estilo Mockaroo", + "rowsLabel": "Linhas", + "rowsHint": "No máximo {max} linhas por geração para manter a aba responsiva.", + "formatLabel": "Formato", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nome da tabela SQL", + "presetsLabel": "Modelos", + "presetPlaceholder": "Carregar modelo…", + "presetDefault": "Início (usuário)", + "presetUsers": "Usuários e funções", + "presetProducts": "SKUs de e-commerce", + "presetApiLog": "Log de requisições HTTP", + "generate": "Gerar", + "warningDuplicateNames": "Nomes de coluna duplicados: colunas posteriores sobrescrevem as anteriores em objetos JSON. Renomeie.", + "schemaHeading": "Esquema", + "addField": "Adicionar coluna", + "removeField": "Remover coluna", + "duplicateField": "Duplicar coluna", + "colName": "Nome", + "colType": "Tipo", + "colOptions": "Opções", + "colBlank": "Vazio %", + "optMin": "mín", + "optMax": "máx", + "optDecimals": "dec", + "optDateFrom": "de", + "optDateTo": "até", + "optValues": "a, b, c", + "optStep": "passo", + "optRowBase": "base", + "output": "Saída", + "outputPlaceholder": "Clique em Gerar para criar os dados…", + "copy": "Copiar", + "download": "Baixar", + "copied": "Copiado", + "groups": { + "personal": "Pessoal", + "location": "Localização", + "internetApi": "Internet e API", + "numbersIds": "Números e IDs", + "dateTime": "Data e hora", + "textDesign": "Texto e design", + "custom": "Personalizado" + }, + "fieldTypes": { + "first_name": "Nome", + "last_name": "Sobrenome", + "full_name": "Nome completo", + "email": "Email", + "username": "Usuário", + "password": "Senha", + "phone": "Telefone", + "company": "Empresa", + "job_title": "Cargo", + "avatar_url": "URL do avatar", + "city": "Cidade", + "country": "País", + "country_code": "Código do país (ISO)", + "street_address": "Endereço", + "zip_code": "CEP", + "latitude": "Latitude", + "longitude": "Longitude", + "url": "URL", + "domain": "Domínio", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Inteiro", + "float": "Decimal", + "boolean": "Booleano", + "uuid": "UUID", + "credit_card": "Cartão (teste)", + "semver": "SemVer", + "row_index": "Índice da linha", + "sequence": "Sequência", + "date": "Data", + "datetime": "Data e hora", + "unix_timestamp": "Carimbo Unix", + "timezone": "Fuso horário", + "word": "Palavra", + "sentence": "Frase", + "paragraph": "Parágrafo", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Código da moeda", + "language_code": "Tag de idioma", + "custom_list": "Lista personalizada" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Otimizador SVG", + "subtitle": "Cole SVG, minifique com SVGO no navegador e compare o tamanho UTF-8 antes e depois.", + "inputLabel": "Entrada", + "outputLabel": "Otimizado", + "placeholder": "Cole um trecho ou o arquivo completo…", + "outputPlaceholder": "O SVG otimizado aparece aqui…", + "chars": "{count} caracteres • {bytes} bytes (UTF-8)", + "preview": "Pré-visualização", + "localNote": "Tudo roda localmente no navegador. Nada é enviado.", + "stats": { + "title": "Tamanho (UTF-8)", + "before": "Antes", + "after": "Depois", + "savedBytes": "{count} bytes economizados ({percent}%)", + "noChange": "Sem redução — já mínimo ou saída vazia." + }, + "actions": { + "clear": "Limpar", + "sample": "Carregar exemplo", + "copy": "Copiar", + "copied": "Copiado", + "download": "Baixar" + }, + "errors": { + "optimizeFailed": "Não foi possível otimizar este SVG." + } + }, + "UnitConverter": { + "title": "Conversor de Unidades", + "subtitle": "Converta entre 323 unidades em 43 categorias", + "category": "Categoria", + "from": "De", + "to": "Para", + "value": "Valor", + "result": "Resultado", + "swapUnits": "Inverter unidades", + "placeholder": "Digite um valor", + "copyResult": "Copiar resultado", + "searchCategory": "Buscar categoria...", + "selectCategory": "Selecione uma categoria", + "noResults": "Nenhuma categoria encontrada." + }, + "DockerComposeGenerator": { + "title": "Gerador Docker Compose", + "subtitle": "Selecione bancos de dados, caches, filas, proxies reversos e observabilidade — copie um docker-compose.yml pronto para desenvolvimento local.", + "searchPlaceholder": "Buscar (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Serviços", + "selectedCount": "{count, plural, one {# serviço} other {# serviços}}", + "outputTitle": "docker-compose.yml", + "copy": "Copiar", + "copied": "Copiado!", + "download": "Baixar", + "emptyState": "Selecione um ou mais serviços para gerar o YAML do Compose.", + "hintPorts": "As portas padrão reduzem conflitos; ajuste se o Docker acusar porta em uso. Troque senhas de demonstração antes de ambientes compartilhados.", + "categories": { + "databases": "Bancos de dados e armazenamento", + "cache": "Cache e em memória", + "queues": "Filas e mensageria", + "search": "Busca e logs", + "storage": "Armazenamento de objetos", + "proxy": "Proxies reversos e ingress", + "observability": "Métricas e tracing", + "mail": "E-mail (dev local)", + "admin": "Interfaces de administração e BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Limpar" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (compatível com Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (compatível com Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (compatível com S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (captura SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (servidor de desenvolvimento)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Deslize as peças e mescle-as para chegar a 2048", + "score": "Pontuação", + "best": "Cumprimentos", + "newGame": "Novo jogo ", + "howToPlay": "Use as teclas de seta (ou deslize o dedo no celular) para mover as peças. Blocos com o mesmo número são mesclados. Chegue a 2048 para ganhar!", + "youWin": "Atingiu 2048!", + "keepGoing": "Continua", + "gameOver": "Acabou o Jogo", + "tryAgain": "Tentar novamente", + "gameOverMessage": "Não há mais movimentos!", + "undo": "Desfazer" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "etapas concluídas", + "time": "Hora", + "best": "Cumprimentos", + "difficulty": { + "easy": "Fácil", + "medium": "Médio", + "hard": "Dura" + }, + "stage": "Etapa", + "solved": "Resolvido", + "score": "Pontuação", + "nextStage": "Próxima fase", + "replay": "Repetir", + "erase": "", + "howToPlay": "Clique numa célula e, em seguida, toque num número ou prima 1–9. Backspace ou Delete limpa a célula. Números em negrito são pistas. Preencha a grade para que cada linha, coluna e caixa 3×3 contenha 1–9 sem repetições.", + "pencil": "Pencil", + "undo": "Undo", + "hint": "Hint", + "hintsUsed": "hints used", + "mistakes": "Mistakes", + "failed": "Failed!", + "mistakeMode": "3-mistake mode" + }, + "Snake": { + "title": "Cobra", + "subtitle": "Coma comida, cresça, evite paredes e a si mesmo", + "score": "Pontuação", + "best": "Melhor", + "startPrompt": "Pressione qualquer tecla de seta", + "startButton": "Iniciar jogo", + "gameOver": "Fim de jogo", + "playAgain": "Jogar de novo", + "howToPlay": "Use as teclas de seta ou WASD. Coma para crescer. A velocidade aumenta a cada 50 pontos.", + "diffEasy": "Fácil", + "diffNormal": "Normal", + "diffHard": "Difícil", + "diffInfinite": "Infinito", + "orTap": "ou toque abaixo", + "howToPlayInfinite": "As paredes se enrolam — sem mortes por paredes. Coma para crescer.", + "shieldActive": "Shield Active", + "speedBoost": "Speed Boost", + "powerSpeed": "Speed x2 (10 ticks)", + "powerShield": "Survive one death", + "powerShrink": "Shortens tail" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Conexões Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Conecte-se a instâncias Redis. Navegue por chaves, edite valores e execute comandos. As credenciais são criptografadas no navegador.", + "heroAddConnection": "Adicionar conexão", + "vaultLockedTitle": "Cofre bloqueado", + "vaultLockedDesc": "Desbloqueie seu cofre para acessar as conexões salvas." + } + }, + "Minesweeper": { + "title": "Campo minado", + "subtitle": "Encontre todas as células seguras sem detonar uma mina", + "mines": "Minas", + "newGame": "Novo jogo", + "win": "Você venceu!", + "loss": "Você perdeu!", + "playAgain": "Jogar novamente", + "tryAgain": "Tentar novamente", + "startPrompt": "Clique em qualquer célula para começar", + "difficulty": { + "beginner": "Iniciante", + "intermediate": "Intermediário", + "expert": "Especialista" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Organize os blocos que caem para completar linhas", + "next": "Próximo", + "score": "Pontuação", + "best": "Melhor", + "lines": "Linhas", + "level": "Nível", + "pause": "Pausar", + "resume": "Retomar", + "start": "Iniciar", + "restart": "Reiniciar", + "paused": "Pausado", + "gameOver": "Fim de jogo" + }, + "SshKeyGenerator": { + "title": "Gerador de chaves SSH / RSA", + "subtitle": "Gere pares de chaves Ed25519 ou RSA no seu navegador. Baixe as chaves privada e pública nos formatos OpenSSH e PEM. Nada é enviado.", + "keyTypeLabel": "Tipo de chave", + "recommended": "recomendado", + "ed25519Desc": "Rápido, pequeno e moderno. Compatível com GitHub, GitLab e todos os principais servidores SSH.", + "rsa2048Desc": "Amplamente compatível. Seguro para a maioria dos usos.", + "rsa4096Desc": "Máxima compatibilidade e força para sistemas legados.", + "commentLabel": "Comentário (opcional)", + "commentHint": "Adicionado à linha de chave pública OpenSSH.", + "generateBtn": "Gerar par de chaves", + "generating": "Gerando…", + "generatingLong": "Gerando (pode demorar um momento)…", + "copy": "Copiar", + "download": "Baixar", + "privateKeyLabel": "Chave privada (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Chave pública (formato OpenSSH)", + "publicKeySpkiLabel": "Chave pública (SPKI PEM)", + "browserNote": "Todas as chaves são geradas no seu navegador. Nada é enviado para nenhum servidor.", + "errors": { + "generationFailed": "Falha ao gerar a chave" + } + }, + "DnsLookup": { + "title": "Pesquisa DNS", + "subtitle": "Consulte registros DNS de qualquer domínio — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Digite um domínio (ex.: github.com)", + "lookupBtn": "Consultar", + "looking": "Consultando…", + "refreshBtn": "Atualizar", + "recordTypesLabel": "Tipos de registro", + "allBtn": "Todos", + "tryLabel": "Experimente:", + "errors": { + "lookupFailed": "A consulta DNS falhou" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Gerador e Verificador Bcrypt", + "subtitle": "Gere hash de senhas com bcrypt e verifique hashes — tudo permanece no seu navegador", + "tabs": { + "hash": "Hash", + "verify": "Verificar" + }, + "showPassword": "Mostrar senha", + "hidePassword": "Ocultar senha", + "copy": "Copiar", + "copied": "Copiado", + "hash": { + "passwordLabel": "Senha", + "passwordPlaceholder": "Digite uma senha para gerar o hash…", + "costLabel": "Fator de custo", + "costHint": "Um custo maior dobra o trabalho por etapa. 10-12 é uma escolha comum para produção.", + "slowWarning": "Fatores de custo de 13 ou mais podem levar vários segundos para calcular.", + "generate": "Gerar hash", + "generating": "Gerando hash…", + "resultLabel": "Hash bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomia do hash", + "algorithm": "algoritmo", + "cost": "custo", + "salt": "salt (22 caracteres)", + "hash": "hash (31 caracteres)" + } + }, + "verify": { + "passwordLabel": "Senha", + "passwordPlaceholder": "Digite a senha para verificar…", + "hashLabel": "Hash bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Digite uma senha e um hash bcrypt para compará-los.", + "invalidHash": "Isto não parece um hash bcrypt válido.", + "checking": "Comparando…", + "match": "Corresponde — a senha produz este hash.", + "noMatch": "Não corresponde — a senha não produz este hash." + } + }, + "ChmodCalculator": { + "title": "Calculadora Chmod", + "subtitle": "Converta permissões de arquivo Unix entre caixas de seleção, octal e notação simbólica", + "grid": { + "title": "Permissões", + "read": "Leitura", + "write": "Escrita", + "execute": "Execução", + "owner": "Dono", + "group": "Grupo", + "others": "Outros" + }, + "special": { + "title": "Bits especiais", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Octal", + "placeholder": "755 ou 4755", + "invalid": "Digite 3 ou 4 dígitos octais (0-7)." + }, + "symbolic": { + "label": "Simbólico", + "placeholder": "rwxr-xr-x", + "invalid": "Digite 9 caracteres como rwxr-xr-x (s/S, t/T permitidos)." + }, + "output": { + "title": "Comando", + "copy": "Copiar", + "copied": "Copiado" + }, + "explain": { + "line": "{cls} pode {perms}.", + "none": "{cls} não tem permissões.", + "setuid": "Setuid: o arquivo é executado com os privilégios de seu dono.", + "setgid": "Setgid: o arquivo é executado com os privilégios de seu grupo (novos arquivos em um diretório herdam seu grupo).", + "sticky": "Sticky: apenas o dono de um arquivo pode excluí-lo ou renomeá-lo dentro deste diretório." + }, + "presets": { + "title": "Presets comuns", + "octal": "Octal", + "permissions": "Permissões", + "description": "Uso típico", + "p644": "Arquivos comuns: o dono escreve, todos leem.", + "p755": "Executáveis e diretórios: todos podem ler e entrar.", + "p600": "Arquivos privados: apenas o dono lê e escreve.", + "p700": "Executáveis e diretórios privados: apenas o dono.", + "p664": "Arquivos graváveis pelo grupo.", + "p775": "Diretórios e executáveis graváveis pelo grupo.", + "p400": "Somente leitura para o dono (ex.: chaves privadas SSH)." + } + }, + "CodeScreenshot": { + "title": "Captura de Código", + "subtitle": "Transforme código em belas imagens compartilháveis", + "languageLabel": "Linguagem", + "autoDetect": "Detecção automática", + "backgroundLabel": "Plano de fundo", + "paddingLabel": "Espaçamento", + "filenameLabel": "Nome do arquivo", + "filenamePlaceholder": "exemplo.ts", + "windowLabel": "Pontos da janela", + "themeLabel": "Tema da janela", + "themeDark": "Escuro", + "themeLight": "Claro", + "scaleLabel": "Escala de exportação", + "copyImage": "Copiar imagem", + "copied": "Copiado", + "exportPng": "Exportar PNG", + "inputLabel": "Código", + "detected": "Detectado: {language}", + "clear": "Limpar", + "placeholder": "Cole seu código aqui…", + "previewLabel": "Pré-visualização", + "emptyPreview": "A pré-visualização do seu código aparecerá aqui", + "exportFailed": "Falha ao exportar a imagem", + "copyUnsupported": "A cópia de imagens não é suportada neste navegador", + "copyFailed": "Falha ao copiar a imagem", + "backgrounds": { + "ocean": "Oceano", + "sunset": "Pôr do sol", + "candy": "Doce", + "forest": "Floresta", + "midnight": "Meia-noite", + "gold": "Ouro", + "aurora": "Aurora", + "slate": "Ardósia", + "transparent": "Transparente" + } + }, + "CssGenerators": { + "title": "Geradores CSS", + "subtitle": "Box-shadow, border-radius e clamp() fluido — copie o CSS.", + "preview": "Pré-visualização", + "output": "CSS", + "copy": "Copiar", + "copied": "Copiado", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() fluido" + }, + "boxShadow": { + "presets": "Presets", + "layer": "Camada", + "addLayer": "Adicionar camada", + "removeLayer": "Remover camada", + "x": "X", + "y": "Y", + "blur": "Desfoque", + "spread": "Propagação", + "color": "Cor", + "alpha": "Alfa", + "inset": "Interno", + "presetNames": { + "subtleCard": "Cartão sutil", + "elevated": "Elevado", + "hardOffset": "Deslocamento rígido", + "neonGlow": "Brilho neon", + "inner": "Interno", + "layeredSmooth": "Camadas suaves" + } + }, + "borderRadius": { + "modeLabel": "Modo", + "modes": { + "simple": "Por canto", + "blob": "Blob orgânico" + }, + "unit": "Unidade", + "linkCorners": "Vincular cantos", + "randomize": "Aleatorizar", + "horizontal": "Raios horizontais", + "vertical": "Raios verticais", + "corners": { + "tl": "Superior esquerdo", + "tr": "Superior direito", + "br": "Inferior direito", + "bl": "Inferior esquerdo" + } + }, + "clamp": { + "minFont": "Fonte mínima (px)", + "maxFont": "Fonte máxima (px)", + "minViewport": "Viewport mínima (px)", + "maxViewport": "Viewport máxima (px)", + "rootFont": "Fonte raiz (px)", + "viewportWidth": "Viewport simulada", + "computedSize": "Tamanho calculado", + "slope": "Inclinação", + "intercept": "Intercepto", + "previewText": "A rápida raposa marrom", + "invalid": "Digite limites de viewport válidos e diferentes." + } + }, + "ExifViewer": { + "title": "Visualizador e Removedor EXIF", + "subtitle": "Inspecione os metadados de fotos e remova-os no seu navegador", + "dropTitle": "Solte uma imagem aqui", + "dropHint": "ou clique para procurar — sua imagem nunca sai do seu dispositivo", + "browse": "Escolher imagem", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Lendo metadados…", + "parseFailed": "Falha ao ler esta imagem", + "clear": "Remover imagem", + "gpsWarning": "Contém localização GPS", + "openMap": "Ver no OpenStreetMap", + "noMetadata": "Nenhum metadado incorporado foi encontrado nesta imagem.", + "facts": { + "name": "Nome", + "type": "Tipo", + "dimensions": "Dimensões", + "size": "Tamanho" + }, + "groups": { + "camera": "Câmera", + "exposure": "Exposição", + "timestamps": "Carimbos de data/hora", + "gps": "Localização GPS", + "software": "Software e outros" + }, + "gpsCard": { + "latitude": "Latitude", + "longitude": "Longitude", + "altitude": "Altitude" + }, + "raw": { + "title": "Todos os metadados", + "copyJson": "Copiar JSON", + "copied": "Copiado" + }, + "remove": { + "title": "Remover metadados", + "jpegDescription": "Remova EXIF, XMP, IPTC e comentários sem perdas, sem recomprimir a imagem.", + "reencodeDescription": "Recodifique a imagem para remover metadados incorporados. Isso recomprime os pixels.", + "keepIcc": "Manter perfil de cor (ICC)", + "stripButton": "Remover metadados", + "reencodeButton": "Recodificar e remover", + "unsupported": "A remoção de metadados não é suportada para este tipo de arquivo.", + "failed": "Falha ao remover metadados", + "before": "Antes", + "after": "Depois", + "download": "Baixar imagem limpa" + } + }, + "FaviconGenerator": { + "title": "Gerador de Favicon", + "subtitle": "Crie favicons e ícones de app a partir de uma imagem", + "dropTitle": "Solte uma imagem aqui", + "dropHint": "PNG, JPEG ou SVG — clique para procurar", + "browse": "Escolher imagem", + "clear": "Remover imagem", + "backgroundLabel": "Plano de fundo", + "transparent": "Transparente", + "solidColor": "Sólido", + "cornerLabel": "Formato do canto", + "cornerNone": "Quadrado", + "cornerRounded": "Arredondado", + "cornerCircle": "Círculo", + "maskPaddingLabel": "Espaçamento maskable", + "maskable": "maskable", + "previewLabel": "Pré-visualização", + "copy": "Copiar", + "headSnippet": "Trecho do head HTML", + "downloadIco": "Baixar favicon.ico", + "downloadZip": "Baixar tudo (.zip)" + }, + "JsonDiff": { + "title": "Diff JSON", + "subtitle": "Comparação semântica e ciente de estrutura de dois documentos JSON — ordem de chaves e formatação ignoradas", + "options": { + "ignoreArrayOrder": "Ignorar ordem dos arrays" + }, + "loadBothSamples": "Carregar exemplos", + "sample": "Exemplo", + "format": "Formatar", + "clear": "Limpar", + "formatError": "Não é possível formatar: a entrada não é um JSON válido", + "placeholder": "Cole ou digite JSON aqui…", + "panels": { + "left": "Original (esquerda)", + "right": "Modificado (direita)", + "results": "Diferenças" + }, + "summary": { + "added": "{count} adicionado(s)", + "removed": "{count} removido(s)", + "changed": "{count} alterado(s)" + }, + "types": { + "added": "Adicionado", + "removed": "Removido", + "changed": "Alterado" + }, + "copyChanges": "Copiar como JSON", + "copied": "Copiado", + "empty": "Cole JSON nos dois painéis para ver suas diferenças.", + "identical": "Os dois documentos são semanticamente idênticos." + }, + "JsonpathPlayground": { + "title": "Playground JSONPath", + "subtitle": "Consulte documentos JSON com JSONPath ou JMESPath e veja resultados ao vivo", + "engineLabel": "Motor", + "queryLabel": "Consulta", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Referência rápida", + "cheats": { + "jpAllTitles": "Todos os títulos de livros", + "jpAllAuthors": "Todos os autores, em qualquer lugar do documento", + "jpFirstBook": "Primeiro livro", + "jpLastBook": "Último livro", + "jpSlice": "Primeiros dois livros (slice)", + "jpFilterPrice": "Livros mais baratos que 40", + "jpFilterInStock": "Títulos de livros em estoque", + "jpRecursive": "Todos os valores, recursivamente", + "jmAllTitles": "Todos os títulos de livros", + "jmFirstAuthor": "Autor do primeiro livro", + "jmLastBook": "Último livro", + "jmSlice": "Primeiros dois livros (slice)", + "jmFilterPrice": "Títulos de livros mais baratos que 40", + "jmCount": "Contar livros em estoque", + "jmProjection": "Remodelar cada livro em um novo objeto", + "jmSort": "Títulos ordenados por preço" + }, + "panels": { + "input": "Entrada JSON", + "results": "Resultados" + }, + "sample": "Exemplo", + "format": "Formatar", + "clear": "Limpar", + "formatError": "Não é possível formatar: a entrada não é um JSON válido", + "placeholder": "Cole ou digite JSON aqui…", + "outputPlaceholder": "As correspondências aparecerão aqui…", + "matchCount": "{count, plural, one {# correspondência} other {# correspondências}}", + "copy": "Copiar", + "copied": "Copiado" + }, + "KeycodeInspector": { + "title": "Inspetor de Keycode", + "subtitle": "Pressione qualquer tecla para inspecionar seu evento de teclado JavaScript", + "capture": { + "prompt": "Pressione qualquer tecla…", + "ready": "Ouvindo — pressione qualquer tecla no seu teclado", + "clickToFocus": "Clique aqui para focar e depois pressione qualquer tecla" + }, + "hero": { + "spaceKey": "(Espaço)", + "keyLabel": "event.key", + "repeat": "repetição" + }, + "copyJson": "Copiar JSON", + "copiedJson": "JSON do evento copiado para a área de transferência", + "deprecated": "obsoleto", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Padrão", + "left": "Esquerda", + "right": "Direita", + "numpad": "Teclado numérico", + "unknown": "Desconhecido" + }, + "history": { + "title": "Histórico", + "clear": "Limpar histórico", + "empty": "As teclas pressionadas aparecerão aqui." + } + }, + "MarkdownTableGenerator": { + "title": "Gerador de Tabela Markdown", + "subtitle": "Edite uma grade, cole de uma planilha e copie uma tabela Markdown ou HTML bem alinhada", + "addRow": "Adicionar linha", + "addColumn": "Adicionar coluna", + "importLabel": "Importar", + "importPlaceholder": "Cole TSV (do Excel / Google Sheets) ou CSV aqui…", + "importButton": "Importar para a grade", + "pasteClipboard": "Colar da área de transferência", + "importEmpty": "Nada para importar — cole algumas linhas primeiro", + "clipboardError": "Não foi possível ler da área de transferência", + "clear": "Limpar", + "panels": { + "editor": "Editor de tabela", + "output": "Saída" + }, + "align": { + "left": "Alinhar à esquerda", + "center": "Centralizar", + "right": "Alinhar à direita" + }, + "headerPlaceholder": "Cabeçalho", + "cellPlaceholder": "Célula", + "removeRow": "Remover linha", + "removeColumn": "Remover coluna", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Copiar", + "copied": "Copiado" + }, + "TimezoneConverter": { + "title": "Conversor de Fuso Horário", + "subtitle": "Converta um momento pelo mundo para planejar reuniões.", + "dateTimeLabel": "Data e hora", + "now": "Agora", + "sourceZoneLabel": "Fuso de origem", + "selectZone": "Selecionar fuso", + "searchZone": "Buscar fusos horários...", + "noZone": "Nenhum fuso horário encontrado.", + "scrubLabel": "Ajustar horário", + "reset": "Redefinir", + "targetsLabel": "Fusos de destino", + "addZone": "Adicionar fuso", + "remove": "Remover", + "localBadge": "Local", + "invalidDate": "Digite uma data e hora válidas.", + "bands": { + "work": "Horário comercial (09:00–18:00)", + "edge": "Horários limítrofes (manhã cedo / noite)", + "night": "Noite" + } + }, + "TokenCounter": { + "title": "Contador de Tokens LLM", + "subtitle": "Conte tokens, visualize chunks e estime o custo de API.", + "inputLabel": "Texto", + "sample": "Exemplo", + "clear": "Limpar", + "placeholder": "Cole texto, código ou JSON para contar tokens...", + "encoding": "Codificação", + "loading": "Carregando tokenizador...", + "encodings": { + "o200k": "o200k_base (GPT-4o / série o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Caracteres", + "words": "Palavras", + "ratio": "Tokens/palavra", + "ratioSub": "aprox." + }, + "claudeRow": "Claude (aproximado)", + "estimate": "estimativa", + "viz": { + "title": "Visualização de tokens", + "toggle": "Mostrar", + "hint": "Alterne para colorir cada token como um chunk.", + "empty": "Digite texto para visualizar os tokens.", + "truncated": "Mostrando os primeiros {limit} de {total} tokens." + }, + "cost": { + "title": "Calculadora de custo", + "disclaimer": "valores padrão editáveis — verifique os preços atuais", + "model": "Modelo", + "expectedOutput": "Tokens de saída esperados", + "customInput": "Entrada $/MTok", + "customOutput": "Saída $/MTok", + "inputCost": "Custo de entrada", + "outputCost": "Custo de saída", + "total": "Total", + "per1000": "Por 1000 chamadas" + } + }, + "WebhookTester": { + "title": "Testador de Webhook", + "subtitle": "Crie uma URL temporária e inspecione requisições de webhook recebidas ao vivo.", + "intro": { + "title": "Crie uma URL de webhook para começar a capturar requisições", + "hint": "Você receberá uma URL impossível de adivinhar que registra cada requisição recebida. Ela expira após 24 horas." + }, + "createBtn": "Criar URL de webhook", + "creating": "Criando…", + "urlLabel": "Sua URL de webhook", + "expiresIn": "Expira em {countdown}", + "copyUrlBtn": "Copiar URL", + "copied": "Copiado", + "clearBtn": "Limpar", + "newBinBtn": "Nova URL", + "requestsTitle": "Requisições", + "selectPrompt": "Selecione uma requisição para inspecionar seus detalhes.", + "expiredNotice": "Esta URL de webhook expirou. Crie uma nova para continuar testando.", + "base64Badge": "base64", + "truncatedBadge": "truncado", + "empty": { + "title": "Aguardando requisições…", + "hint": "Envie uma requisição para sua URL e ela aparecerá aqui instantaneamente. Experimente:" + }, + "detail": { + "receivedAt": "Recebido", + "source": "IP de origem", + "contentType": "Content-Type", + "query": "Parâmetros de query", + "headers": "Cabeçalhos", + "body": "Corpo", + "copyBody": "Copiar corpo", + "emptyBody": "Sem corpo" + }, + "errors": { + "createFailed": "Falha ao criar a URL de webhook. Tente novamente.", + "clearFailed": "Falha ao limpar as requisições. Tente novamente." + } + }, + "WebsocketTester": { + "title": "Testador de WebSocket", + "subtitle": "Conecte-se a um endpoint WebSocket e envie, receba e inspecione mensagens ao vivo", + "urlLabel": "URL do endpoint", + "protocolsLabel": "Subprotocolos", + "protocolsPlaceholder": "opcional, separados por vírgula", + "connect": "Conectar", + "disconnect": "Desconectar", + "status": { + "disconnected": "Desconectado", + "connecting": "Conectando", + "connected": "Conectado", + "error": "Erro" + }, + "messages": { + "connecting": "Conectando a {url}…", + "connected": "Conexão estabelecida", + "connectedWithProtocol": "Conexão estabelecida (protocolo: {protocol})", + "invalidUrl": "URL inválida — deve começar com ws:// ou wss://", + "connectFailed": "Falha ao abrir a conexão", + "socketError": "Ocorreu um erro de conexão", + "notConnected": "Não conectado — conecte-se primeiro", + "binaryMessage": "Mensagem binária recebida" + }, + "log": { + "title": "Registro de mensagens", + "empty": "Nenhuma mensagem ainda. Conecte-se e comece a enviar.", + "copyAll": "Copiar tudo", + "download": "Baixar como .txt", + "clear": "Limpar registro", + "prettyPrint": "Formatar JSON", + "showRaw": "Mostrar texto bruto" + }, + "composer": { + "placeholder": "Digite uma mensagem para enviar…", + "send": "Enviar", + "formatJson": "Formatar como JSON", + "invalidJson": "A mensagem não é um JSON válido", + "hint": "Cmd/Ctrl+Enter para enviar" + }, + "ping": { + "label": "Auto-ping", + "seconds": "seg", + "intervalTitle": "Intervalo de ping em segundos", + "payloadPlaceholder": "payload do ping" + } + }, + "WhoisLookup": { + "title": "Consulta Whois", + "subtitle": "Consulte detalhes de registro de qualquer domínio ou endereço IP via RDAP", + "placeholder": "example.com ou 8.8.8.8", + "lookupBtn": "Consultar", + "looking": "Consultando…", + "refreshBtn": "Atualizar", + "tryLabel": "Experimente:", + "copy": "Copiar", + "copied": "Copiado", + "notAvailable": "Não disponível", + "summary": { + "registrar": "Registrador", + "network": "Rede" + }, + "dates": { + "created": "Criado", + "updated": "Atualizado", + "expires": "Expira" + }, + "sections": { + "status": "Status", + "nameservers": "Servidores de nomes", + "contacts": "Contatos", + "rawRdap": "RDAP bruto" + }, + "contacts": { + "roles": "Funções", + "name": "Nome", + "organization": "Organização" + }, + "errors": { + "invalidTarget": "Digite um nome de domínio ou endereço IP válido", + "lookupFailed": "Falha na consulta (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/pt.json b/apps/desktop-ui/messages/pt.json new file mode 100644 index 00000000..7dd7eae2 --- /dev/null +++ b/apps/desktop-ui/messages/pt.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Painel", + "settings": "Definições", + "apiClient": "Cliente API", + "httpStatusCodes": "Códigos de estado HTTP", + "passwordManager": "Gestor de Palavras-passe", + "environmentManager": "Gestor de ambientes", + "bookmarks": "Marcadores", + "jsonFormatter": "Formatador JSON", + "emailValidator": "Validador de E-mail", + "nosqlExplorer": "Explorador de banco de dados", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Laboratório de encriptação", + "jwtDecoder": "Decodificador JWT", + "certificatePemDecoder": "Descodificador de certificado / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Formatador GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Analisador de URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Segredo / chave API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Verificador de contraste", + "jsonSchemaGenerator": "Gerador de esquema JSON", + "imageToBase64": "Imagem para Base64", + "imageCompressor": "Compressor de imagens", + "cssGradientBuilder": "Construtor de gradiente CSS", + "gitignoreGenerator": "Gerador .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Excertos de código", + "qrCodeGenerator": "Gerador de códigos QR", + "dnsLookup": "Pesquisa DNS", + "ipSubnetCalculator": "Calculadora IP / sub-rede", + "hashGenerator": "Gerador de hash", + "hmacGenerator": "Gerador HMAC", + "totpGenerator": "Código TOTP / 2FA", + "markdownPreview": "Pré-visualização Markdown", + "numberBaseConverter": "Conversor de base", + "formatConverter": "Conversor de formatos", + "mockDataGenerator": "Gerador de dados fictícios", + "svgOptimizer": "Otimizador SVG", + "unitConverter": "Conversor de Unidades", + "dockerComposeGenerator": "Gerador Docker Compose", + "breakRoom": "Sala de descanso", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Formatador YAML", + "sshKeyGenerator": "Gerador de chaves SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Calculadora Chmod", + "codeScreenshot": "Captura de código", + "cssGenerators": "Geradores CSS", + "exifViewer": "Visualizador EXIF", + "faviconGenerator": "Gerador de favicon", + "jsonDiff": "Diff JSON", + "jsonpathPlayground": "Playground JSONPath", + "keycodeInspector": "Inspetor de keycode", + "markdownTableGenerator": "Tabela Markdown", + "timezoneConverter": "Conversor de fusos horários", + "tokenCounter": "Contador de tokens", + "webhookTester": "Testador de webhooks", + "websocketTester": "Testador de WebSocket", + "whoisLookup": "Pesquisa Whois" + }, + "Help": { + "title": "Ajuda e documentação", + "subtitle": "Como o MyDevTools funciona em conjunto, o que faz cada app e como a segurança e a encriptação protegem os seus dados.", + "tabs": { + "overview": "Visão geral", + "apps": "Apps", + "security": "Segurança", + "tips": "Dicas" + }, + "overview": { + "whatIs": { + "title": "O que é o MyDevTools?", + "description": "Um espaço de trabalho para programadores com sessão iniciada: um painel e um conjunto de ferramentas para o dia a dia.", + "p1": "Depois de iniciar sessão, a app estabelece uma sessão com a API do MyDevTools para que as ferramentas possam carregar e guardar os seus dados. A barra lateral lista todas as apps; pode ocultar ferramentas que não utiliza em", + "settingsLink": "Definições", + "p1After": ".", + "p2": "Algumas funcionalidades usam uma palavra-passe mestre (ou chave global do cofre) para que segredos como palavras-passe e strings de ligação sejam encriptados no seu dispositivo antes de chegarem ao servidor." + }, + "quickLinks": { + "title": "Ligações rápidas", + "dashboard": "Painel", + "settings": "Definições" + } + }, + "apps": { + "intro": "Abra qualquer ferramenta a partir da barra lateral ou do painel. Abaixo está um guia curto por app.", + "open": "Abrir", + "howItWorks": "Como funciona", + "generic": "Use esta ferramenta a partir da barra lateral para o seu fluxo de trabalho dedicado." + }, + "security": { + "session": { + "title": "Sessão e API", + "description": "Identidade e como o browser comunica com o seu backend.", + "items": { + "authentication": "Autenticação via Firebase. Após o login, a app troca o seu Firebase ID token por cookies da API.", + "accessTokens": "Tokens de acesso são JWT assinados no servidor (HS256). Provam qual utilizador está a chamar a API.", + "refreshTokens": "Tokens de renovação são guardados em cookies HttpOnly; o servidor guarda um hash SHA-256 do token, não o token em bruto." + } + }, + "encryption": { + "title": "Encriptação importante", + "description": "Criptografia no cliente para o cofre e dados sensíveis de ligação.", + "p1": "Para o gestor de palavras-passe, o gestor de ambientes e as ligações guardadas no NoSQL Explorer, a aplicação web usa a Web Crypto API do navegador:", + "items": { + "kdf": "PBKDF2 com SHA-256, 100.000 iterações e um salt aleatório deriva uma chave de 256 bits a partir da palavra-passe mestre.", + "aes": "AES-256-GCM encripta os dados; é usado um IV de 12 bytes aleatório por cada encriptação.", + "scope": "A chave derivada fica no browser (não extraível). O servidor recebe apenas ciphertext e IVs, não a palavra-passe mestre nem segredos em texto simples." + }, + "warning": "Se esquecer a palavra-passe mestre, os dados encriptados não podem ser recuperados. Guarde-a em segurança." + }, + "logout": { + "title": "Terminar sessão e dados locais", + "body": "Terminar sessão limpa segredos em memória, estado do cofre e material da chave mestre do IndexedDB quando aplicável, e termina a sessão da API. Dados guardados apenas no armazenamento local podem permanecer até os limpar no browser." + } + }, + "tips": { + "title": "Usar o MyDevTools em segurança", + "items": { + "masterPassword": "Use uma palavra-passe mestre forte e única para funcionalidades do cofre.", + "apiClient": "Trate o Cliente API como qualquer cliente HTTP: evite colar segredos de produção em ambientes partilhados sem saber quem os pode ver.", + "nosql": "O Explorador de banco de dados liga-se a bases de dados que configura; use utilizadores com o mínimo privilégio e regras de rede quando possível.", + "json": "O Formatador JSON corre localmente no browser—mesmo assim evite colar dados muito sensíveis em máquinas partilhadas.", + "settings": "Personalize as ferramentas visíveis nas Definições para manter a barra lateral focada." + } + } + }, + "Settings": { + "title": "Definições", + "language": "Idioma", + "spanish": "Espanhol", + "english": "English", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalão", + "chineseSimplified": "Chinês (Simplificado)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Alemão", + "danish": "Dinamarquês", + "afrikaans": "Africâner", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norueguês", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Neerlandês", + "polish": "Polaco", + "vietnamese": "Tiếng Việt", + "ukrainian": "Українська" + }, + "SettingsPage": { + "title": "Definições", + "subtitle": "Gira as definições e preferências da tua conta.", + "userProfile": { + "title": "Perfil do Utilizador", + "description": "As tuas informações pessoais e detalhes da conta.", + "avatarAlt": "Utilizador", + "anonymousUser": "Utilizador Anónimo", + "noEmailProvided": "E-mail não fornecido", + "notLoggedIn": "Sessão não iniciada." + }, + "tools": { + "title": "Ferramentas e Funcionalidades", + "description": "Escolhe quais ferramentas aparecem na barra lateral de navegação." + }, + "appearance": { + "title": "Aparência", + "description": "Personaliza o aspeto da aplicação no teu dispositivo.", + "themePreference": "Preferência de Tema", + "themes": { + "light": "Claro", + "dark": "Escuro", + "system": "Sistema" + } + }, + "language": { + "title": "Idioma", + "description": "Define o idioma utilizado em toda a plataforma.", + "selectLanguage": "Selecionar Idioma", + "selectPlaceholder": "Seleciona um idioma", + "helpText": "Alterar o idioma irá atualizar menus e rótulos em toda a plataforma.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español", + "ar": "عربى", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά", + "de": "Deutsch", + "da": "Dansk", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italiano", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Bom dia", + "afternoon": "Boa tarde", + "evening": "Boa noite" + }, + "commaName": ", {name}", + "welcomeBack": "Bem-vindo de volta", + "welcomeBackNamed": "Bem-vindo de volta, {name}", + "tagline": "O que gostarias de criar hoje?", + "brandName": "MyDevTools", + "stats": { + "tools": "Ferramentas", + "favorites": "Favoritos", + "pinned": "Fixados", + "recent": "Utilizados Recentemente" + }, + "sections": { + "recentlyUsed": "Utilizados Recentemente", + "favorites": "Favoritos", + "whatsNew": "Novidades", + "pinned": "Fixados" + }, + "groups": { + "apps": "Aplicações" + }, + "toolCard": { + "defaultDescription": "Explora esta ferramenta para melhor funcionalidade.", + "launchTool": "Abrir Ferramenta" + }, + "tools": { + "toDo": { + "title": "Tarefas", + "description": "Gere as tuas listas de tarefas de forma eficiente." + }, + "notes": { + "title": "Notas", + "description": "Regista ideias e organiza os teus pensamentos." + }, + "passwordManager": { + "title": "Gestor de Palavras-passe", + "description": "Armazena e gere as tuas palavras-passe em segurança." + }, + "environmentManager": { + "title": "Gestor de ambientes", + "description": "Guarde variáveis tipo .env por projeto e ambiente com etiquetas." + }, + "bookmarks": { + "title": "Marcadores", + "description": "Gere e organiza os teus marcadores." + }, + "apiClient": { + "title": "Cliente API", + "description": "Testa e depura pedidos HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Explora bases de dados MongoDB." + }, + "emailValidator": { + "title": "Validador de E-mail", + "description": "Verifica e valida endereços de e-mail." + }, + "encryptionPlayground": { + "title": "Laboratório de encriptação", + "description": "AES-GCM com chave bruta ou frase-passe; pacotes JSON, Web Crypto só no navegador." + }, + "jwtDecoder": { + "title": "Decodificador JWT", + "description": "Decodifique o cabeçalho, payload e expiração do JWT." + }, + "certificatePemDecoder": { + "title": "Descodificador de certificado / PEM", + "description": "Descodifique certificados X.509 e CSRs: sujeito, emissor, validade e SAN." + }, + "sshKeyGenerator": { + "title": "Gerador de Chaves SSH / RSA", + "description": "Gere pares de chaves Ed25519 ou RSA. Faça o download nos formatos OpenSSH e PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Formatador GraphQL", + "description": "Formate GraphQL com realce de sintaxe e um editor de consultas simples." + }, + "yamlFormatter": { + "title": "Formatador YAML", + "description": "Formate, valide e converta YAML para JSON no navegador." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Analisador de URL", + "description": "Divida um URL em protocolo, host, caminho, parâmetros de query e hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Segredo / chave API", + "description": "Cadeias aleatórias criptográficas com alfabeto, comprimento e exportação em massa configuráveis." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Verificador de contraste", + "description": "Rácio de contraste WCAG 2.1 entre texto e fundo (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Gerador de esquema JSON", + "description": "Infira um esquema JSON e modelos tipados a partir de uma amostra JSON." + }, + "imageToBase64": { + "title": "Imagem para Base64", + "description": "Converta imagens em URI de dados ou strings Base64 brutas." + }, + "imageCompressor": { + "title": "Compressor de imagens", + "description": "Comprima JPEG, PNG e WebP localmente com um controlo de qualidade." + }, + "cssGradientBuilder": { + "title": "Construtor de gradiente CSS", + "description": "Construtor de gradiente visual CSS com controle de ângulo e paradas de cores." + }, + "gitignoreGenerator": { + "title": "Gerador .gitignore", + "description": "Gere arquivos .gitignore mesclados instantaneamente a partir de mais de 500 pilhas de tecnologia." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Converta ficheiros CSV ou Excel para JSON e exporte para CSV ou XLSX." + }, + "snippetManager": { + "title": "Excertos de código", + "description": "Guarde excertos com realce de sintaxe, deteção automática de linguagem e formatação." + }, + "qrCodeGenerator": { + "title": "Gerador de códigos QR", + "description": "Crie códigos QR PNG a partir de texto ou URL no navegador." + }, + "ipSubnetCalculator": { + "title": "Calculadora IP / sub-rede", + "description": "IPv4 e IPv6 CIDR: máscara, intervalos e contagem de endereços." + }, + "dnsLookup": { + "title": "Pesquisa DNS", + "description": "Consulte registros DNS de qualquer domínio — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Gerador de hash", + "description": "MD5, SHA-1 e SHA-2 para texto ou ficheiros." + }, + "hmacGenerator": { + "title": "Gerador HMAC", + "description": "Assine webhooks e teste integrações de API com HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Gerador de código TOTP / 2FA", + "description": "Cole um segredo Base32 do autenticador; o código de seis dígitos renova a cada 30 s para testar início de sessão e MFA." + }, + "markdownPreview": { + "title": "Pré-visualização Markdown", + "description": "Renderizador Markdown em tempo real com exportação HTML e conversão HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Conversor de base numérica", + "description": "Converta inteiros entre bases de 2 a 36." + }, + "formatConverter": { + "title": "Conversor de formatos", + "description": "Converta entre JSON, YAML, TOML e XML em qualquer direção. As 12 combinações são suportadas." + }, + "mockDataGenerator": { + "title": "Gerador de dados fictícios", + "description": "Defina colunas, escolha tipos e exporte conjuntos de dados para seeds, Postman ou testes. Até 5000 linhas, tudo no navegador — nada é enviado." + }, + "httpStatusCodes": { + "title": "Códigos de estado HTTP", + "description": "Pesquise códigos de estado HTTP com descrições (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Otimizador SVG", + "description": "Cole SVG, remova ruído com SVGO e veja a redução de tamanho UTF-8." + }, + "unitConverter": { + "title": "Conversor de Unidades", + "description": "Converta entre 323 unidades em 43 categorias científicas e de engenharia." + }, + "dockerComposeGenerator": { + "title": "Gerador Docker Compose", + "description": "Escolha bases de dados, caches, filas, proxies e imagens de observabilidade; exporte um docker-compose.yml para stacks locais." + }, + "game2048": { + "title": "2048", + "description": "Deslize e mescle as peças para chegar a 2048. Clássico jogo de quebra-cabeças numéricos." + }, + "sudoku": { + "title": "Sudoku", + "description": "Puzzle numérico clássico. 3 níveis de dificuldade, 150 estágios cada." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Navegue por chaves, inspecione valores e execute comandos em qualquer instância Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Gerador e verificador Bcrypt", + "description": "Gere hashes de palavras-passe com bcrypt e verifique hashes existentes." + }, + "chmodCalculator": { + "title": "Calculadora Chmod", + "description": "Converta permissões Unix entre octal, simbólico e caixas de seleção." + }, + "codeScreenshot": { + "title": "Captura de código", + "description": "Transforme código-fonte em imagens bonitas e fáceis de partilhar." + }, + "cssGenerators": { + "title": "Geradores CSS", + "description": "Geradores de box-shadow, border-radius e clamp() fluido." + }, + "exifViewer": { + "title": "Visualizador e removedor EXIF", + "description": "Inspecione e remova os metadados EXIF das fotos localmente." + }, + "faviconGenerator": { + "title": "Gerador de favicon", + "description": "Gere favicons, ícones de app e fragmentos de manifest." + }, + "jsonDiff": { + "title": "Diff JSON", + "description": "Comparação semântica e sensível à estrutura de dois documentos JSON." + }, + "jsonpathPlayground": { + "title": "Playground JSONPath", + "description": "Consulte JSON com JSONPath ou JMESPath e veja os resultados em tempo real." + }, + "keycodeInspector": { + "title": "Inspetor de keycode", + "description": "Prima uma tecla para ver os seus códigos de evento JavaScript e modificadores." + }, + "markdownTableGenerator": { + "title": "Gerador de tabelas Markdown", + "description": "Crie tabelas Markdown e HTML num editor de grelha semelhante a uma folha de cálculo." + }, + "timezoneConverter": { + "title": "Conversor de fusos horários", + "description": "Converta uma hora entre vários fusos com indicações de mudança de dia e horário de trabalho." + }, + "tokenCounter": { + "title": "Contador de tokens de LLM", + "description": "Conte tokens de LLM, visualize chunks e estime o custo da API." + }, + "webhookTester": { + "title": "Testador de webhooks", + "description": "Capture e inspecione pedidos de webhook recebidos em tempo real." + }, + "websocketTester": { + "title": "Testador de WebSocket", + "description": "Ligue-se a endpoints WebSocket e troque mensagens em tempo real." + }, + "whoisLookup": { + "title": "Pesquisa Whois", + "description": "Consulte os dados de registo de um domínio ou IP através de RDAP." + } + }, + "tabs": { + "apps": "Aplicações", + "analytics": "Análise", + "ui": "Interface" + }, + "analytics": { + "title": "Os seus dados num relance", + "subtitle": "Contagens sincronizadas com a sua conta no servidor.", + "loadError": "Não foi possível carregar as estatísticas.", + "retry": "Tentar novamente", + "loginHint": "Inicie sessão para ver análises de utilização e preferências de interface.", + "passwordEntries": "Entradas de palavras-passe", + "bookmarks": "Marcadores", + "bookmarkFolders": "Pastas de marcadores", + "tasksTotal": "Tarefas", + "tasksCompleted": "Concluídas", + "tasksOngoing": "Em curso", + "tasksNotStarted": "Não iniciadas", + "projects": "Projetos", + "nosqlConnections": "Ligações à BD", + "notes": "Notas", + "apiClientCollections": "Coleções API", + "apiClientEnvironments": "Ambientes API", + "apiClientHistory": "Entradas do histórico API", + "jsonFormatterDocuments": "Documentos do formatador JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Excertos de código", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Preferências de interface", + "subtitle": "Tema, idioma e ferramentas visíveis na barra lateral — nas definições da conta.", + "loadError": "Não foi possível carregar as preferências.", + "loginHint": "Inicie sessão para ver as preferências de interface guardadas.", + "theme": "Tema", + "accent": "Cor de destaque", + "locale": "Idioma", + "enabledTools": "Ferramentas visíveis", + "favoriteTools": "Ferramentas favoritas", + "toolsWithUsage": "Ferramentas com utilização registada" + }, + "signIn": "Iniciar sessão", + "noResults": "Nenhuma ferramenta corresponde à sua pesquisa", + "noResultsHint": "Experimente uma palavra-chave diferente ou limpe o filtro.", + "filterAll": "Tudo", + "loginCta": { + "description": "Inicie sessão para acompanhar as ferramentas utilizadas recentemente." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "A carregar...", + "myTasksTitle": "As Minhas Tarefas", + "statsLine": "{total} tarefas • {percent}% concluído", + "statsLineDesktop": "{total} total • {percent}% concluído" + }, + "filters": { + "all": "Todas", + "total": "Total", + "allProjects": "Todos os Projetos", + "export": "Exportar", + "kanban": "Kanban", + "list": "Lista", + "searchPlaceholder": "Pesquisar tarefas...", + "searchAria": "Pesquisar tarefas", + "clearSearchAria": "Limpar pesquisa", + "foundTasks": "{count} {count, plural, one {tarefa encontrada} other {tarefas encontradas}}", + "filterByAllAria": "Filtrar por Todas", + "filterByStatusAria": "Filtrar por {status}" + }, + "status": { + "not-started": { + "label": "Não Iniciada", + "description": "Tarefas que ainda não foram iniciadas" + }, + "ongoing": { + "label": "Em Curso", + "description": "Tarefas atualmente em progresso" + }, + "completed": { + "label": "Concluída", + "description": "Tarefas finalizadas" + } + }, + "priorities": { + "high": "Alta", + "medium": "Média", + "low": "Baixa" + }, + "form": { + "addNewTaskLabel": "Adicionar nova tarefa", + "multilinePlaceholder": "Adiciona detalhes da tarefa... (Shift+Enter para nova linha)", + "singlelinePlaceholder": "O que precisa de ser feito? Prime Enter para adicionar", + "taskInputAria": "Entrada de tarefa", + "projectPlaceholder": "Projeto", + "selectProject": "Selecionar Projeto", + "errorSelectProject": "Seleciona um projeto ou cria um novo para adicionar uma tarefa.", + "hintMultiline": "Shift+Enter para nova linha, Enter para adicionar", + "hintSingleline": "Enter para adicionar", + "shortcutsAria": "Atalhos de teclado", + "shortcutsLabel": "Atalhos", + "shortcutAddTask": "Adicionar tarefa", + "shortcutNewLine": "Nova linha", + "shortcutClear": "Limpar", + "addButton": "Adicionar" + }, + "list": { + "ariaLabel": "Lista de tarefas", + "loading": "A carregar as tuas tarefas...", + "emptyTitle": "Nenhuma tarefa encontrada", + "emptyPrimary": "Começa por adicionar a tua primeira tarefa acima! Prime Enter para adicionar tarefas rapidamente.", + "emptySecondary": "Tenta ajustar os teus filtros ou a consulta de pesquisa." + }, + "mobileDrawer": { + "addTaskSrOnly": "Adicionar Tarefa", + "addNewTaskTitle": "Adicionar Nova Tarefa", + "addNewTaskDescription": "Cria uma nova tarefa para acompanhar o teu progresso.", + "cancel": "Cancelar" + }, + "kanban": { + "ariaLabel": "Quadro Kanban", + "loading": "A carregar as tuas tarefas...", + "dropHere": "Larga a tarefa aqui", + "noTasksYet": "Sem tarefas ainda", + "emptyColumnAria": "Coluna vazia", + "columnAria": "Coluna {title} com {count} tarefas", + "countInColumnAria": "{count} tarefas em {title}" + }, + "taskItem": { + "confirmDelete": "Tens a certeza de que queres eliminar esta tarefa?", + "statusPlaceholder": "Estado", + "changeStatusAria": "Alterar estado da tarefa", + "taskOptionsAria": "Opções da tarefa", + "editTask": "Editar Tarefa", + "copyTask": "Copiar Tarefa", + "copied": "Copiado!", + "deleteTask": "Eliminar Tarefa", + "statusHeading": "Estado", + "editTaskAria": "Editar tarefa" + }, + "editDialog": { + "title": "Editar Tarefa", + "description": "Atualiza detalhes da tarefa, adiciona subtarefas, define prioridades e mais.", + "taskTitleLabel": "Título da Tarefa *", + "taskTitlePlaceholder": "O que precisa de ser feito?", + "descriptionLabel": "Descrição", + "descriptionPlaceholder": "Adiciona mais detalhes sobre esta tarefa...", + "statusLabel": "Estado", + "priorityLabel": "Prioridade", + "dueDateLabel": "Data de Vencimento", + "pickDate": "Escolhe uma data", + "clearDate": "Limpar Data", + "projectLabel": "Projeto", + "selectProjectPlaceholder": "Seleciona um projeto", + "noProject": "Sem Projeto", + "timeEstimateLabel": "Estimativa de Tempo (minutos)", + "timeEstimatePlaceholder": "Quanto tempo vai demorar?", + "tagsLabel": "Etiquetas", + "customTagNamePlaceholder": "Nome da etiqueta personalizada", + "subtasksLabel": "Subtarefas", + "addSubtaskPlaceholder": "Adicionar uma subtarefa...", + "cancel": "Cancelar", + "saving": "A guardar...", + "saveChanges": "Guardar Alterações" + }, + "export": { + "title": "Exportar Tarefas", + "description": "Exporta as tuas tarefas nos formatos JSON, CSV ou Excel.", + "projectLabel": "Projeto", + "statusLabel": "Estado", + "tagLabel": "Etiqueta", + "allProjects": "Todos os Projetos", + "allStatuses": "Todos os Estados", + "allTags": "Todas as Etiquetas", + "exportAsJsonTitle": "Exportar como JSON", + "exportAsJsonDescription": "Exporta dados da tarefa incluindo subtarefas, etiquetas e metadados.", + "exportJson": "Exportar JSON", + "exportAsCsvExcelTitle": "Exportar como CSV / Excel", + "exportAsCsvExcelDescription": "Exporta tarefas para aplicações de folha de cálculo.", + "exportCsv": "Exportar CSV", + "exportExcel": "Exportar Excel", + "close": "Fechar", + "toastNoTasks": "Nenhuma tarefa para exportar com os filtros atuais", + "toastExportedJson": "{count} tarefas exportadas para JSON!", + "toastExportedCsv": "{count} tarefas exportadas para CSV!", + "toastExportedExcel": "{count} tarefas exportadas para Excel!", + "toastFailedJson": "Falha ao exportar para JSON", + "toastFailedCsv": "Falha ao exportar para CSV", + "toastFailedExcel": "Falha ao exportar para Excel", + "unknownProject": "Projeto Desconhecido" + }, + "ack": { + "taskAddedTitle": "Tarefa adicionada com sucesso", + "taskAddedFailedTitle": "Falha ao adicionar tarefa", + "taskUpdatedTitle": "Tarefa atualizada com sucesso", + "taskUpdatedFailedTitle": "Falha ao atualizar tarefa", + "taskStatusUpdateFailedTitle": "Falha ao atualizar estado da tarefa", + "taskMovedTitle": "Tarefa movida para {status}", + "tryAgain": "Por favor, tenta novamente.", + "taskDeletedTitle": "Tarefa \"{text}\" eliminada", + "undo": "Anular", + "taskRestoredTitle": "Tarefa restaurada", + "taskRestoredSuccessTitle": "Tarefa restaurada com sucesso", + "taskRestoreFailedTitle": "Falha ao restaurar tarefa. Por favor, tenta novamente.", + "taskDeleteFailedTitle": "Falha ao eliminar tarefa. Por favor, tenta novamente.", + "tasksImportedTitle": "{count} {count, plural, one {tarefa importada} other {tarefas importadas}} com sucesso!", + "tasksImportFailedTitle": "Falha ao importar tarefas. Por favor, tenta novamente.", + "unknownProject": "Projeto Desconhecido", + "projectCreatedTitle": "Projeto criado com sucesso", + "projectCreateFailedTitle": "Falha ao criar projeto", + "projectUpdatedTitle": "Projeto atualizado com sucesso", + "projectUpdateFailedTitle": "Falha ao atualizar projeto", + "projectDeletedTitle": "Projeto eliminado com sucesso", + "projectDeleteFailedTitle": "Falha ao eliminar projeto" + } + }, + "Notes": { + "page": { + "loading": "A carregar..." + }, + "layout": { + "mobileNavTitle": "Navegação de Notas", + "openMenuAria": "Abrir menu de notas" + }, + "sidebar": { + "title": "Notas", + "createNoteAria": "Criar nota", + "searchPlaceholder": "Pesquisar notas...", + "loading": "A carregar...", + "noNotesFound": "Nenhuma nota encontrada.", + "noNotesYet": "Sem notas ainda. Clica em + para criar uma.", + "inParent": "em {parentTitle}", + "untitled": "Sem Título", + "addSubPage": "Adicionar subpágina", + "delete": "Eliminar", + "deleteConfirmTitle": "Eliminar nota?", + "deleteConfirmDescription": "Tens a certeza de que queres eliminar \"{title}\"? Esta ação não pode ser anulada.", + "cancel": "Cancelar", + "confirmDelete": "Eliminar", + "noteOptionsAria": "Opções da nota" + }, + "editor": { + "emptyState": "Seleciona uma nota ou cria uma nova para começar.", + "titlePlaceholder": "Sem Título", + "saving": "A guardar…", + "ready": "Pronto", + "saved": "Guardado {time}", + "stats": "{words} palavras · {read} min de leitura", + "template": "Modelo", + "applyTemplate": "Aplicar modelo", + "exportMdLabel": ".md", + "exportMd": "Exportar como Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Exportar como HTML", + "focusMode": "Modo de foco", + "exitFocusMode": "Sair do modo de foco", + "save": "Guardar", + "addTag": "Adicionar etiqueta…", + "removeTag": "Remover etiqueta {tag}", + "loading": "A carregar…", + "searchHint": "para pesquisar", + "focusHint": "modo de foco", + "notePath": "Caminho da nota" + }, + "context": { + "authRequiredError": "Utilizador não autenticado", + "defaultTitle": "Sem Título", + "cloudSyncImageError": "Ative a Sincronização na Nuvem para armazenar imagens das notas. As notas de texto permanecem locais." + } + }, + "PasswordManager": { + "page": { + "loading": "A carregar...", + "title": "Gestor de Palavras-passe", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Falha ao desencriptar palavras-passe", + "toastLoadFailed": "Falha ao carregar palavras-passe", + "toastUnlockedAuto": "Cofre desbloqueado automaticamente", + "toastAutoUnlockFailed": "Falha no desbloqueio automático. Por favor, desbloqueia manualmente.", + "toastConnectFailed": "Falha ao ligar ao servidor", + "toastAutoUnlockWarning": "Cofre desbloqueado, mas o desbloqueio automático pode não funcionar ao recarregar", + "errorIncorrectPassword": "Palavra-passe mestra incorreta", + "errorUnlockFailed": "Falha ao desbloquear cofre", + "errorPasswordsMismatch": "As palavras-passe não coincidem", + "errorPasswordTooShort": "A palavra-passe deve ter pelo menos 8 caracteres", + "errorSetupFailed": "Falha ao configurar cofre", + "errorConnectFailed": "Falha ao ligar ao servidor.", + "mobileTitleCreate": "Cria o teu cofre", + "mobileTitleWelcome": "Bem-vindo de volta", + "mobileSubtitleCreate": "Define uma palavra-passe mestra para proteger as tuas credenciais", + "mobileSubtitleUnlock": "Introduz a tua palavra-passe mestra para desbloquear", + "placeholderMasterPassword": "Palavra-passe mestra", + "placeholderConfirmPassword": "Confirmar palavra-passe", + "labelMasterPassword": "Palavra-passe mestra", + "labelConfirmPassword": "Confirmar palavra-passe", + "placeholderEnterMaster": "Introduz a palavra-passe mestra", + "placeholderConfirmMaster": "Confirma a palavra-passe mestra", + "buttonCreateVault": "Criar cofre", + "buttonUnlock": "Desbloquear", + "buttonUnlockVault": "Desbloquear cofre", + "footerCreate": "A tua palavra-passe nunca é armazenada e não pode ser recuperada", + "footerUnlock": "Protegido com encriptação ponta a ponta", + "desktopTitleSetup": "Configurar cofre de palavras-passe", + "desktopTitleUnlock": "Desbloquear cofre", + "desktopDescriptionSetup": "Cria uma palavra-passe mestra para proteger as tuas credenciais. Esta palavra-passe nunca é armazenada e não pode ser recuperada.", + "desktopDescriptionUnlock": "Introduz a tua palavra-passe mestra para aceder às tuas credenciais protegidas." + }, + "list": { + "loading": "A carregar as tuas palavras-passe...", + "emptyTitle": "Cofre vazio", + "emptyHintMobile": "Toca no botão + para adicionar a tua primeira palavra-passe", + "emptyHintDesktop": "Ainda não armazenaste nenhuma palavra-passe. Clica no botão \"Adicionar palavra-passe\" acima para começar.", + "addPassword": "Adicionar palavra-passe", + "searchPlaceholderMobile": "Pesquisar no cofre...", + "searchPlaceholderDesktop": "Pesquisar no cofre...", + "importExport": "Importar / exportar", + "lockVault": "Bloquear cofre", + "allPasswordsTitle": "Todas as palavras-passe", + "passwordCount": "{count, plural, one {# palavra-passe} other {# palavras-passe}}", + "vaultHealthDrawerTitle": "Saúde do cofre", + "noResults": "Nenhuma palavra-passe encontrada para \"{query}\"", + "tableService": "Serviço", + "tableUsername": "Utilizador", + "tablePassword": "Palavra-passe", + "tableUrl": "URL", + "tableActions": "Ações", + "gridViewAria": "Vista em grelha", + "listViewAria": "Vista em lista", + "passwordStrengthTitle": "Força da palavra-passe", + "deleteDialogTitle": "Eliminar palavra-passe", + "deleteDialogDescription": "Tens a certeza de que queres eliminar esta palavra-passe? Esta ação não pode ser anulada.", + "cancel": "Cancelar", + "delete": "Eliminar", + "edit": "Editar" + }, + "toasts": { + "copiedPassword": "Palavra-passe copiada para a área de transferência", + "copiedUsername": "Utilizador copiado para a área de transferência", + "passwordDeleted": "Palavra-passe eliminada", + "deleteFailed": "Falha ao eliminar palavra-passe", + "vaultLocked": "Cofre bloqueado", + "importedCount": "{count, plural, one {# palavra-passe importada} other {# palavras-passe importadas}} com sucesso" + }, + "form": { + "addTitle": "Adicionar nova palavra-passe", + "editTitle": "Editar palavra-passe", + "serviceName": "Nome do serviço", + "usernameEmail": "Utilizador / e-mail", + "password": "Palavra-passe", + "urlOptional": "URL do site (opcional)", + "notesOptional": "Notas (opcional)", + "tags": "Etiquetas", + "placeholderService": "ex: Netflix", + "placeholderUsername": "utilizador@exemplo.com", + "placeholderPassword": "Introduz ou gera uma palavra-passe", + "placeholderUrl": "https://...", + "placeholderNotes": "Detalhes adicionais...", + "placeholderTags": "Adicionar etiquetas (prime Enter ou vírgula)", + "strength": "Força", + "strengthWeak": "Fraca", + "strengthMedium": "Média", + "strengthStrong": "Forte", + "advancedGenerator": "Gerador avançado", + "cancel": "Cancelar", + "saving": "A guardar...", + "savePassword": "Guardar palavra-passe", + "updating": "A atualizar...", + "updatePassword": "Atualizar palavra-passe", + "addPasswordSrOnly": "Adicionar palavra-passe", + "triggerAdd": "Adicionar palavra-passe", + "toastSaved": "Palavra-passe guardada com sucesso", + "toastSaveFailed": "Falha ao guardar palavra-passe", + "toastUpdated": "Palavra-passe atualizada com sucesso", + "toastUpdateFailed": "Falha ao atualizar palavra-passe" + }, + "security": { + "vaultHealth": "Saúde do cofre", + "statusLabel": "Estado", + "totalPasswords": "Total de palavras-passe", + "activeEntries": "Entradas ativas no teu cofre", + "protected": "Protegido", + "weakPasswords": "Palavras-passe fracas", + "needingImprovement": "Palavras-passe que precisam de melhorias", + "allGood": "Tudo bem", + "reusedPasswords": "Palavras-passe reutilizadas", + "usedMultipleTimes": "Palavras-passe usadas várias vezes", + "unique": "Única", + "total": "Total", + "weak": "Fraca", + "reused": "Reutilizada", + "totalCountLabel": "{count, plural, one {# palavra-passe} other {# palavras-passe}}", + "weakCountLabel": "{count} fraca(s)", + "allSecure": "Todas seguras", + "scoreExcellent": "Excelente", + "scoreGood": "Boa", + "scoreFair": "Razoável", + "scoreCritical": "Crítica" + }, + "importExport": { + "title": "Importar / exportar cofre", + "description": "Gere os teus dados de palavras-passe. Podes exportar o teu cofre para JSON ou importar de um ficheiro JSON.", + "tabExport": "Exportar", + "tabImport": "Importar", + "warningTitle": "Aviso", + "warningBody": "Ficheiros exportados contêm as tuas palavras-passe em texto simples. Mantém este ficheiro seguro e elimina-o após utilização.", + "exportBlurb": "Transfere o teu cofre inteiro como um ficheiro JSON. Isto inclui todos os serviços, utilizadores, palavras-passe e notas.", + "downloadJson": "Transferir exportação JSON", + "pasteLabel": "Colar dados JSON", + "pastePlaceholder": "[{\"service\": \"Exemplo\", \"username\": \"utilizador\", \"password\": \"123\", ...}]", + "pasteHint": "Cola o conteúdo de um ficheiro JSON exportado anteriormente aqui.", + "importing": "A importar...", + "importPasswords": "Importar palavras-passe", + "trigger": "Importar / exportar", + "toastExported": "Cofre exportado com sucesso", + "toastExportFailed": "Falha ao exportar cofre", + "toastImportFailed": "Falha ao importar: formato JSON inválido" + }, + "card": { + "copyUsername": "Copiar utilizador", + "openUrl": "Abrir URL", + "strength": "Força" + }, + "swipeable": { + "copy": "Copiar", + "delete": "Eliminar", + "copyPassword": "Copiar palavra-passe", + "copyUsername": "Copiar utilizador", + "edit": "Editar", + "deleteAction": "Eliminar" + }, + "floatingMenu": { + "lockVault": "Bloquear cofre", + "importExport": "Importar / exportar", + "addPassword": "Adicionar palavra-passe" + }, + "generator": { + "tabPassword": "Palavra-passe", + "tabPassphrase": "Frase-passe", + "strength": "Força", + "lengthLabel": "Comprimento: {length} caracteres", + "lengthBadge": "{length} caracteres", + "characterSets": "Conjuntos de caracteres", + "uppercase": "Maiúsculas", + "lowercase": "Minúsculas", + "numbers": "Números", + "symbols": "Símbolos", + "wordCountLabel": "Número de palavras: {count}", + "wordCountBadge": "{count, plural, one {# palavra} other {# palavras}}", + "separator": "Separador", + "capitalization": "Capitalização", + "includeNumber": "Incluir número" + } + }, + "EnvironmentManager": { + "subtitle": "Variáveis de ambiente encriptadas por projeto e ambiente.", + "page": { + "loading": "A carregar…", + "title": "Gestor de ambientes", + "loadFailed": "Falha ao carregar conjuntos de ambiente" + }, + "list": { + "title": "Ambientes", + "loading": "A carregar os seus conjuntos…", + "emptyTitle": "Ainda sem conjuntos de ambiente", + "emptyHint": "Crie um conjunto por projeto e ambiente (ex.: chaves API de produção). Tudo é encriptado no navegador antes do envio.", + "searchPlaceholder": "Pesquisar projeto, ambiente, etiquetas, notas…", + "tagFilterPlaceholder": "Filtrar por etiqueta", + "variableCount": "{count, plural, one {# variável} other {# variáveis}}", + "updatedAgo": "Atualizado {time}", + "copyDotEnv": "Copiar .env", + "edit": "Editar", + "delete": "Eliminar", + "cancel": "Cancelar", + "confirmDelete": "Eliminar", + "deleteDialogTitle": "Eliminar conjunto de ambiente?", + "deleteDialogDescription": "Isto remove o blob encriptado da sua conta. Não pode ser anulado.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env copiado para a área de transferência", + "deleted": "Conjunto de ambiente eliminado", + "deleteFailed": "Falha ao eliminar" + }, + "form": { + "addTitle": "Novo conjunto de ambiente", + "editTitle": "Editar conjunto de ambiente", + "triggerAdd": "Adicionar conjunto de ambiente", + "addSrOnly": "Adicionar conjunto de ambiente", + "project": "Projeto", + "environment": "Ambiente", + "placeholderProject": "ex. billing-api", + "placeholderEnvironment": "ex. production", + "pasteEnvLabel": "Colar .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Fundir linhas coladas", + "variables": "Variáveis", + "addRow": "Adicionar linha", + "keyPlaceholder": "CHAVE", + "valuePlaceholder": "valor", + "notesOptional": "Notas (opcional)", + "placeholderNotes": "Política de rotação, responsável, ligações…", + "tags": "Etiquetas", + "placeholderTags": "Adicionar etiquetas (Enter ou vírgula)", + "cancel": "Cancelar", + "save": "Guardar", + "saving": "A guardar…", + "update": "Atualizar", + "updating": "A atualizar…", + "toastSaved": "Conjunto de ambiente guardado", + "toastSaveFailed": "Falha ao guardar", + "toastUpdated": "Conjunto de ambiente atualizado", + "toastUpdateFailed": "Falha ao atualizar", + "toastPasteEmpty": "Nenhuma linha CHAVE=valor encontrada", + "toastPasteApplied": "{count} variáveis fundidas da colagem", + "toastProjectEnvRequired": "Projeto e ambiente são obrigatórios", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "A carregar..." + }, + "manager": { + "foldersHeading": "Pastas", + "tagsHeading": "Etiquetas", + "tagsMore": "+{count} mais", + "tagsShowLess": "Show less", + "searchPlaceholder": "Pesquisar marcadores...", + "import": "Importar", + "export": "Exportar", + "exportAsHtml": "Exportar como HTML", + "exportAsJson": "Exportar como JSON", + "addBookmark": "Adicionar Marcador", + "matchingQuery": "correspondendo a \"{query}\"", + "bookmarkCount": "{count, plural, one {# marcador} other {# marcadores}}", + "allBookmarks": "Todos os Marcadores", + "uncategorized": "Sem Categoria", + "unknownFolder": "Desconhecido" + }, + "card": { + "urlCopied": "URL copiada para a área de transferência", + "bookmarkDeleted": "Marcador eliminado", + "edit": "Editar", + "delete": "Eliminar", + "copyUrl": "Copiar URL", + "deleteConfirmTitle": "Eliminar marcador?", + "deleteConfirmDescription": "Isto irá eliminar permanentemente \"{title}\".", + "cancel": "Cancelar" + }, + "grid": { + "emptyTitle": "Sem marcadores ainda", + "emptyDescription": "Adiciona o teu primeiro marcador ou importa do teu navegador para começar." + }, + "folderTree": { + "allBookmarks": "Todos os Marcadores", + "addSubfolder": "Adicionar Subpasta", + "rename": "Renomear", + "deleteFolderTitle": "Eliminar pasta \"{name}\"?", + "deleteFolderDescription": "Isto irá eliminar a pasta e todas as suas subpastas. Marcadores nestas pastas serão movidos para Sem Categoria." + }, + "import": { + "title": "Importar Marcadores", + "description": "Importa marcadores do ficheiro exportado do teu navegador.", + "parseError": "Falha ao analisar ficheiro de marcadores. Certifica-te de que é uma exportação válida.", + "successToast": "{bookmarks} marcadores e {folders} pastas importados", + "errorToast": "Falha ao importar marcadores", + "chromeInstruction": "Menu → Marcadores → Gestor de Marcadores → ⋮ → Exportar Marcadores", + "firefoxInstruction": "Menu → Marcadores → Gerir Marcadores → Importar e Cópia de Segurança → Exportar", + "safariInstruction": "Ficheiro → Exportar Marcadores...", + "edgeInstruction": "Menu → Favoritos → ⋯ → Exportar Favoritos", + "dropZoneTitle": "Larga o teu ficheiro de marcadores aqui", + "dropZoneHint": "ou clica para procurar (HTML ou JSON)", + "selectedFileHint": "Clica para selecionar um ficheiro diferente", + "errorTitle": "Erro", + "previewTitle": "Pré-visualização da Importação", + "previewBookmarks": "{count, plural, one {# marcador} other {# marcadores}}", + "previewFolders": "{count, plural, one {# pasta} other {# pastas}}", + "importModeLabel": "Modo de Importação", + "modeMerge": "Fundir com marcadores existentes", + "modeReplace": "Substituir todos os marcadores existentes", + "cancel": "Cancelar", + "importButton": "Importar", + "importButtonWithCount": "Importar {count, plural, one {# marcador} other {# marcadores}}" + }, + "addBookmark": { + "titleAdd": "Adicionar Marcador", + "titleEdit": "Editar Marcador", + "urlLabel": "URL", + "urlPlaceholder": "https://exemplo.com", + "urlInvalid": "Por favor, introduz um URL válido", + "titleLabel": "Título", + "titlePlaceholder": "Título do marcador", + "titleRequired": "Por favor, introduz um título", + "descriptionLabel": "Descrição (opcional)", + "descriptionPlaceholder": "Adiciona uma nota sobre este marcador", + "folderLabel": "Pasta", + "folderPlaceholder": "Selecionar pasta", + "noFolder": "Sem pasta", + "tagsLabel": "Etiquetas", + "tagsPlaceholder": "Adicionar etiquetas (prime Enter)", + "toastUpdated": "Marcador atualizado", + "toastAdded": "Marcador adicionado", + "cancel": "Cancelar", + "submitAdd": "Adicionar Marcador", + "submitUpdate": "Atualizar Marcador" + }, + "addFolder": { + "title": "Criar Pasta", + "nameLabel": "Nome da Pasta", + "namePlaceholder": "A Minha Pasta", + "nameRequired": "Por favor, introduz um nome para a pasta", + "parentLabel": "Pasta Principal", + "parentPlaceholder": "Selecionar pasta principal", + "rootParent": "Raiz (Sem pasta principal)", + "toastCreated": "Pasta criada", + "cancel": "Cancelar", + "submit": "Criar Pasta" + }, + "editFolder": { + "title": "Renomear Pasta", + "nameLabel": "Nome da Pasta", + "namePlaceholder": "Nome da pasta", + "nameRequired": "Por favor, introduz um nome para a pasta", + "toastRenamed": "Pasta renomeada", + "cancel": "Cancelar", + "save": "Guardar" + } + }, + "EmailValidator": { + "header": { + "title": "Validador de E-mail", + "description": "Verifica e valida endereços de e-mail quanto à sintaxe, domínio e entregabilidade." + }, + "tabs": { + "single": "Verificação Individual", + "bulk": "Carregamento em Massa" + }, + "single": { + "cardTitle": "Verificação Individual de E-mail", + "cardDescription": "Introduz um endereço de e-mail para verificar a sua entregabilidade e reputação.", + "placeholder": "Introduz o endereço de e-mail...", + "clearAria": "Limpar e-mail", + "validate": "Validar", + "validating": "A validar...", + "formatError": "Por favor, introduz um endereço de e-mail válido" + }, + "toasts": { + "errorTitle": "Erro", + "enterEmail": "Por favor, introduz um endereço de e-mail", + "invalidFormatTitle": "Formato de E-mail Inválido", + "invalidFormatDescription": "Por favor, introduz um endereço de e-mail válido", + "validateFailedDescription": "Ocorreu um erro ao validar o e-mail", + "noEmailsTitle": "Nenhum E-mail Encontrado", + "noEmailsDescription": "Certifica-te de que o teu Excel tem uma coluna 'email'.", + "fileTooLargeTitle": "Ficheiro Demasiado Grande", + "fileTooLargeDescription": "Encontrados {total} e-mails. Apenas os primeiros 4000 serão validados.", + "successTitle": "Sucesso", + "successValidated": "{count} e-mails validados com sucesso.", + "batchFailedTitle": "Falha na Validação em Massa", + "batchFailedDescription": "Ocorreu um erro ao processar a validação em massa.", + "noExportTitle": "Sem Dados para Exportar", + "noExportValidOnly": "Nenhum e-mail válido encontrado para exportar.", + "noExportAll": "Sem resultados para exportar.", + "exportSuccessTitle": "Exportação Bem-sucedida", + "exportSuccessExcelAll": "{count} e-mails exportados para Excel.", + "exportSuccessExcelValid": "{count} e-mails válidos exportados para Excel.", + "exportSuccessCsvAll": "{count} e-mails exportados para CSV.", + "exportSuccessCsvValid": "{count} e-mails válidos exportados para CSV." + }, + "disposableAlert": { + "title": "E-mail Descartável Detetado", + "description": "Este endereço de e-mail pertence a um fornecedor de e-mail descartável. Pode ser temporário ou inseguro." + }, + "score": { + "reliabilityTitle": "Pontuação de Fiabilidade", + "scoreLabel": "Pontuação" + }, + "details": { + "sectionTitle": "Detalhes da Validação" + }, + "validation": { + "syntaxLabel": "Sintaxe Válida", + "syntaxTooltip": "Verifica se o endereço de e-mail segue o formato correto (ex: utilizador@dominio.com)", + "domainLabel": "Domínio Existe", + "domainTooltip": "Verifica se o nome de domínio no endereço de e-mail realmente existe", + "mxLabel": "Registos MX Encontrados", + "mxTooltip": "Verifica se o domínio tem registos de troca de e-mail (MX) configurados para receber e-mails", + "mailboxLabel": "Caixa de Correio Existe", + "mailboxTooltip": "Verifica se a caixa de correio/conta específica existe no servidor de e-mail", + "disposableLabel": "E-mail Descartável", + "disposableTooltip": "Indica se este e-mail pertence a um serviço de e-mail temporário/descartável (pode ser arriscado)", + "roleBasedLabel": "E-mail Baseado em Função", + "roleBasedTooltip": "Identifica se este é um e-mail baseado em função (ex: suporte@, info@) em vez de um endereço pessoal" + }, + "copy": { + "emailCopied": "E-mail copiado!", + "copyTooltip": "Copiar e-mail" + }, + "bulk": { + "dropHere": "Larga o teu ficheiro aqui", + "dragHere": "Arrasta e larga o teu ficheiro aqui", + "uploadHint": "Carrega um ficheiro .xlsx, .xls ou .csv contendo uma coluna 'email' para validar até 4000 e-mails.", + "filePrefix": "Ficheiro:", + "selectFile": "Selecionar Ficheiro", + "downloadTemplate": "Transferir Modelo" + }, + "bulkLoading": { + "title": "A Validar E-mails...", + "batchProgress": "A processar lote {current} de {total}..." + }, + "stats": { + "totalProcessed": "Total Processado", + "valid": "Válido", + "risky": "Arriscado", + "invalid": "Inválido" + }, + "results": { + "detailedTitle": "Resultados Detalhados", + "searchPlaceholder": "Pesquisar e-mails...", + "newVerification": "Nova Verificação", + "exportAll": "Exportar Tudo", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Exportar Válidos ({count})" + }, + "showingResults": "A mostrar {shown} de {total} resultados", + "table": { + "email": "E-mail", + "status": "Estado", + "score": "Pontuação", + "checks": "Verificações" + }, + "noSearchResults": "Nenhum resultado encontrado para \"{query}\"", + "checksTooltip": { + "syntax": "Sintaxe", + "domain": "Domínio", + "mx": "MX", + "mailbox": "Caixa de Correio" + }, + "exportColumns": { + "email": "E-mail", + "status": "Estado", + "score": "Pontuação", + "syntax": "Sintaxe", + "domain": "Domínio", + "mx": "MX", + "mailbox": "Caixa de Correio", + "disposable": "Descartável", + "roleBased": "Baseado em Função", + "valid": "Válido", + "invalid": "Inválido", + "yes": "Sim", + "no": "Não" + }, + "sheets": { + "validEmails": "E-mails Válidos", + "results": "Resultados", + "template": "Modelo" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "Explorador NoSQL", + "heroSubtitle": "Liga-te às tuas bases de dados para gerir coleções, executar consultas e visualizar os teus dados.", + "featureMultiTitle": "Multi-Ligação", + "featureMultiDesc": "Gere múltiplas ligações de base de dados em simultâneo.", + "featureMongoTitle": "Suporte MongoDB", + "featureMongoDesc": "Suporte nativo para MongoDB com filtragem avançada.", + "featureQueryTitle": "Consulta Inteligente", + "featureQueryDesc": "Construtor de consultas poderoso com destaque de sintaxe.", + "connectCta": "Ligar ao MongoDB", + "footerNote": "Atualmente suporta {db}. Mais bases de dados em breve.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Seleciona uma Coleção", + "selectCollectionDesc": "Escolhe uma coleção na barra lateral para visualizar documentos e começar a explorar os teus dados.", + "dialogAddConnection": "Adicionar Ligação", + "toastConnectionAdded": "Ligação adicionada", + "toastFetchFailed": "Falha ao obter documentos para {collection}", + "toastDocDeleted": "Documento eliminado", + "deleteDocTitle": "Tens a certeza absoluta?", + "deleteDocDescription": "Esta ação não pode ser anulada. Isto irá eliminar permanentemente o documento da tua coleção.", + "closeAllTitle": "Fechar todos os separadores?", + "closeAllDescription": "Tens a certeza de que queres fechar todos os separadores? Quaisquer alterações não guardadas nos filtros de consulta serão perdidas.", + "closeAllConfirm": "Fechar Todos", + "cancel": "Cancelar", + "delete": "Eliminar" + }, + "connection": { + "titleEdit": "Editar Ligação", + "titleConnect": "Ligar ao MongoDB", + "subtitleEdit": "Atualiza os detalhes da tua ligação abaixo.", + "subtitleConnect": "Introduz a tua string de ligação MongoDB para começar a explorar os teus dados.", + "labelName": "Nome", + "placeholderName": "ex: BD de Produção", + "labelConnectionString": "String de Ligação", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Formato: mongodb://utilizador:palavrapasse@host:porta/basededados", + "testConnection": "Testar Ligação", + "connecting": "A ligar...", + "updateConnect": "Atualizar e Ligar", + "connect": "Ligar", + "cancelEdit": "Cancelar Edição", + "savedConnections": "Ligações Guardadas", + "emptySaved": "Nenhuma ligação guardada ainda.", + "lastUsed": "Última utilização {time}", + "toastTestOk": "Ligação bem-sucedida!", + "toastTestFail": "Falha na ligação: {message}", + "toastUpdated": "Ligação atualizada", + "confirmDelete": "Tens a certeza de que queres eliminar esta ligação guardada?", + "toastDeletedConn": "Ligação eliminada", + "toastDeleteFail": "Falha ao eliminar ligação", + "copyStringTitle": "Copiar string de ligação", + "toastStringCopied": "String de ligação copiada!", + "editTitle": "Editar", + "deleteTitle": "Eliminar" + }, + "sidebar": { + "explorer": "Explorador", + "add": "Adicionar", + "tooltipAdd": "Adicionar nova ligação", + "tooltipRefresh": "Atualizar ligações", + "searchPlaceholder": "Pesquisar...", + "loadingConnections": "A carregar ligações...", + "noConnectionsFound": "Nenhuma ligação encontrada", + "toastLoadFail": "Falha ao carregar ligações", + "toastConnectFail": "Falha ao ligar a {name}", + "toastCollectionsFail": "Falha ao obter coleções para {name}", + "toastRenamed": "Ligação renomeada", + "toastRenameFail": "Falha ao renomear ligação", + "confirmDeleteConnection": "Tens a certeza de que queres eliminar a ligação \"{name}\"?", + "toastDeleted": "Ligação eliminada", + "toastDeleteConnFail": "Falha ao eliminar ligação", + "confirmDropDb": "Tens a certeza de que queres descartar a base de dados \"{name}\"? Esta ação não pode ser anulada.", + "toastDbDropped": "Base de dados {name} descartada", + "confirmDropCollection": "Tens a certeza de que queres descartar a coleção \"{name}\"? Esta ação não pode ser anulada.", + "toastCollectionDropped": "Coleção {name} descartada", + "toastCollectionRenamed": "Coleção renomeada para {name}", + "toastDatabaseRenamed": "Base de dados renomeada para {name}", + "connecting": "A ligar...", + "connectionFailed": "Falha na ligação", + "noDatabases": "Sem bases de dados", + "loading": "A carregar...", + "noCollections": "Sem coleções", + "renameCollectionTitle": "Renomear Coleção", + "renameDatabaseTitle": "Renomear Base de Dados", + "placeholderNewCollection": "Novo nome da coleção", + "placeholderNewDatabase": "Novo nome da base de dados", + "rename": "Renomear", + "refresh": "Atualizar", + "copyDbName": "Copiar Nome da BD", + "toastDbNameCopied": "Nome da base de dados copiado para a área de transferência", + "dropDatabase": "Descartar Base de Dados", + "dropCollection": "Descartar Coleção", + "menuRename": "Renomear", + "menuCopyConnectionString": "Copiar String de Ligação", + "menuDeleteConnection": "Eliminar Ligação", + "toastSidebarStringCopied": "String de ligação copiada!", + "cancel": "Cancelar", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} docs", + "table": "Tabela", + "json": "JSON", + "tree": "Árvore", + "tooltipTable": "Vista em Tabela", + "tooltipJson": "Vista JSON", + "tooltipTree": "Vista em Árvore", + "itemsPerPage": "Itens por página", + "perPage": "{n} / página", + "prevPage": "Página Anterior", + "nextPage": "Página Seguinte", + "expandAll": "Expandir Tudo", + "collapseAll": "Recolher Tudo", + "refreshData": "Atualizar Dados", + "insert": "Inserir", + "export": "Exportar", + "actions": "Ações", + "invalidJson": "Conteúdo JSON inválido", + "copiedClipboard": "Copiado para a área de transferência", + "docUpdated": "Documento atualizado com sucesso", + "docInserted": "Documento inserido com sucesso", + "invalidJsonShort": "JSON inválido", + "docCopied": "Documento copiado para a área de transferência", + "openFullEditor": "Abrir no editor completo", + "cellArray": "Array({length})", + "cellObject": "Objeto", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Nenhum documento corresponde à tua consulta", + "emptyFilterHint": "Tenta ajustar o teu filtro ou limpar a consulta.", + "clearFilter": "Limpar Filtro", + "emptyCollectionTitle": "Esta coleção está vazia", + "emptyCollectionHint": "Insere um documento para começar.", + "insertDocument": "Inserir Documento", + "formatJson": "Formatar JSON", + "compactJson": "Compactar JSON", + "copyClipboard": "Copiar para Área de Transferência", + "copyJson": "Copiar JSON", + "duplicate": "Duplicar", + "editDoc": "Editar Documento", + "deleteDoc": "Eliminar Documento", + "documentLabel": "Documento {n}", + "editDialogTitle": "Editar Documento", + "insertDialogTitle": "Inserir Documento", + "viewValueTitle": "Ver Valor", + "saveChanges": "Guardar Alterações", + "close": "Fechar", + "cancel": "Cancelar", + "insertButton": "Inserir", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Filtro ativo aplicado", + "filterActiveTooltip": "Filtro ativo", + "closeAll": "Fechar Todos" + }, + "query": { + "migrateHistory": "Histórico de consultas migrado para a nuvem", + "saveHistoryFail": "Falha ao guardar histórico de consultas", + "deleteQueryFail": "Falha ao eliminar consulta", + "invalidJsonQuery": "Consulta JSON inválida", + "placeholder": "ex: { \"status\": \"ativo\" }", + "clearQuery": "Limpar consulta", + "openAdvanced": "Abrir Editor Avançado", + "visualBuilder": "Construtor Visual de Consultas", + "filterRules": "Regras de Filtro", + "addRule": "Adicionar Regra", + "noFilters": "Nenhum filtro aplicado. Adiciona uma regra para filtrar documentos.", + "where": "ONDE", + "and": "E", + "fieldPlaceholder": "Campo", + "opPlaceholder": "Op", + "typePlaceholder": "Tipo", + "opRegex": "Regex", + "opIn": "Em", + "opNin": "Não Em", + "opExists": "Existe", + "typeAuto": "Auto", + "typeString": "Texto", + "typeNumber": "Número", + "typeBoolean": "Booleano", + "typeDate": "Data", + "typeObjectId": "ObjectId", + "true": "Verdadeiro", + "false": "Falso", + "valuePlaceholder": "Valor", + "valueListPlaceholder": "valor1, valor2", + "clearFilters": "Limpar Filtros", + "applyFilters": "Aplicar Filtros", + "queryHistory": "Histórico de Consultas", + "noSavedQueries": "Nenhuma consulta guardada ainda", + "advancedEditorTitle": "Editor de Consultas Avançado", + "templatesPlaceholder": "Modelos", + "templateEmptyAgg": "Agregação Vazia", + "templateMatch": "$match (Filtro)", + "templateLookup": "$lookup (Junção)", + "templateGroup": "$group (Contagem/Soma)", + "format": "Formatar", + "formattedJson": "JSON Formatado", + "runQuery": "Executar Consulta" + }, + "export": { + "selectFieldsError": "Seleciona pelo menos um campo para exportar", + "success": "Exportado com sucesso para {filename}", + "fail": "Falha ao exportar dados", + "title": "Exportar Dados", + "formatLabel": "Formato de Exportação", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Selecionar Campos", + "selectAll": "Selecionar Tudo", + "fieldsSelected": "{count, plural, one {# campo selecionado} other {# campos selecionados}}", + "cancel": "Cancelar", + "export": "Exportar", + "sheetName": "Dados" + }, + "jsonTree": { + "typeLabel": "Tipo: {type}", + "itemSummary": "{count, plural, one {# item} other {# itens}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Novo Pedido", + "importedRequest": "Pedido Importado", + "untitledRequest": "Pedido Sem Título" + }, + "toasts": { + "curlImported": "cURL importado com sucesso", + "curlParseFailed": "Falha ao analisar comando cURL", + "invalidJsonBody": "Corpo JSON inválido", + "requestFailed": "Pedido falhou: {message}", + "curlPasted": "cURL colado e analisado com sucesso", + "responseCopied": "Resposta copiada para a área de transferência", + "codeCopied": "Código copiado para a área de transferência", + "copyFailed": "Falha ao copiar para a área de transferência" + }, + "layout": { + "collections": "Coleções", + "closeSidebar": "Fechar barra lateral", + "openSidebar": "Abrir barra lateral", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Método", + "urlPlaceholder": "https://api.exemplo.com/v1/...", + "sending": "A enviar...", + "send": "Enviar", + "invalidJsonBodyHelp": "Não é possível enviar: o corpo JSON é inválido" + }, + "requestTabs": { + "params": "Parâmetros", + "headers": "Cabeçalhos", + "body": "Corpo", + "auth": "Autenticação", + "bodyType": "Tipo do corpo", + "selectType": "Selecionar tipo", + "bodyNone": "Nenhum", + "bodyJson": "JSON", + "bodyText": "Texto", + "noBodyHint": "Sem conteúdo de corpo para este pedido", + "switchToJson": "Mudar para corpo JSON", + "authType": "Tipo de autenticação", + "noAuth": "Sem autenticação", + "bearerToken": "Token Bearer", + "basicAuth": "Autenticação básica", + "apiKey": "Chave API", + "labelToken": "Token", + "placeholderBearerToken": "Token Bearer", + "labelUsername": "Utilizador", + "placeholderUsername": "Utilizador", + "labelPassword": "Palavra-passe", + "placeholderPassword": "Palavra-passe", + "labelKey": "Chave", + "placeholderApiKeyName": "Nome da chave (ex: X-Api-Key)", + "labelValue": "Valor", + "placeholderValue": "Valor", + "addTo": "Adicionar em", + "header": "Cabeçalho", + "queryParams": "Parâmetros de consulta", + "noAuthHint": "Este pedido não utiliza autenticação." + }, + "responsePanel": { + "emptyTitle": "Pronto para explorar?", + "emptyHint": "Introduz um URL e clica em Enviar para ver os dados da resposta aqui.", + "responsePreviewAlt": "Pré-visualização da resposta", + "previewUnavailable": "Pré-visualização não disponível para este tipo de conteúdo", + "status": "Estado", + "time": "Tempo", + "size": "Tamanho", + "copyBody": "Copiar corpo", + "downloadResponse": "Transferir resposta", + "bodyTab": "Corpo", + "previewTab": "Pré-visualização", + "headersTab": "Cabeçalhos", + "binaryRawView": "Dados binários não podem ser apresentados na vista em bruto.", + "errorStatusLabel": "Erro", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Novo separador" + }, + "collectionsSidebar": { + "title": "Recursos API", + "newCollection": "Nova coleção", + "tabCollections": "Coleções", + "tabHistory": "Histórico", + "noCollectionsTitle": "Sem coleções", + "noCollectionsHint": "Cria uma coleção para organizar os teus pedidos API.", + "createCollection": "Criar coleção", + "newFolder": "Nova pasta", + "rename": "Renomear", + "delete": "Eliminar", + "emptyCollection": "Coleção vazia", + "recentRequests": "Pedidos recentes", + "clearAll": "Limpar tudo", + "noHistoryTitle": "Sem histórico ainda", + "noHistoryHint": "Envia um pedido para vê-lo aqui.", + "dialogNewFolderTitle": "Nova pasta", + "dialogNewFolderDescription": "Cria uma nova pasta para organizar os teus pedidos.", + "labelFolderName": "Nome da pasta", + "placeholderFolderName": "A minha pasta", + "createFolder": "Criar pasta", + "dialogNewCollectionTitle": "Nova coleção", + "dialogNewCollectionDescription": "Cria uma nova coleção de nível superior.", + "labelCollectionName": "Nome da coleção", + "placeholderCollectionName": "A minha coleção", + "dialogRenameCollectionTitle": "Renomear coleção", + "dialogRenameCollectionDescription": "Introduz um novo nome para a coleção.", + "placeholderRenameCollection": "Nome da coleção", + "renameAction": "Renomear", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Nova pasta", + "delete": "Eliminar", + "emptyFolder": "Pasta vazia" + }, + "environmentManager": { + "noEnvironment": "Sem ambiente", + "manageDialogTitle": "Gerir ambientes", + "placeholderNewEnv": "Novo ambiente", + "variablesHeading": "Variáveis de {name}", + "addVariable": "Adicionar variável", + "columnVariable": "Variável", + "columnValue": "Valor", + "placeholderKey": "Chave", + "placeholderValue": "Valor", + "selectEnvToEdit": "Seleciona um ambiente para editar" + }, + "codeGenerator": { + "triggerTitle": "Gerar código", + "dialogTitle": "Gerar código", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Copiar", + "errorGenerating": "Erro ao gerar código" + }, + "keyValueEditor": { + "columnKey": "Chave", + "columnValue": "Valor", + "placeholderKey": "Chave", + "placeholderValue": "Valor", + "addItem": "Adicionar item" + }, + "importCurl": { + "trigger": "Importar cURL", + "dialogTitle": "Importar cURL", + "dialogDescription": "Cola o teu comando cURL abaixo para importá-lo como um novo pedido.", + "placeholder": "curl -X POST https://api.exemplo.com/dados -d '...'", + "import": "Importar" + }, + "saveRequest": { + "trigger": "Guardar", + "dialogTitle": "Guardar pedido", + "dialogDescription": "Guarda este pedido numa pasta de coleção.", + "labelName": "Nome do pedido", + "placeholderName": "O meu pedido", + "labelFolder": "Pasta", + "placeholderFolder": "Seleciona uma pasta", + "save": "Guardar" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Modo de Edição", + "viewOnlyMode": "Modo Apenas Leitura", + "insertComponent": "Inserir componente", + "addUnorderedList": "Adicionar lista não ordenada", + "addOrderedList": "Adicionar lista ordenada", + "addTable": "Adicionar tabela" + }, + "addBlock": "Adicionar bloco", + "media": { + "popoverTitle": "Adicionar média", + "singleImage": "Imagem Única", + "singleImageDesc": "Carregar uma imagem", + "multipleImages": "Múltiplas Imagens", + "multipleImagesDesc": "Carregar grelha de imagens", + "video": "Vídeo", + "videoDesc": "Carregar ficheiro de vídeo", + "uploadingAlt": "A carregar...", + "uploadFailedAlt": "Falha no carregamento" + }, + "export": { + "fabAria": "Exportar código", + "title": "Exportar Código", + "description": "Copia a saída HTML ou JSON do conteúdo do teu editor", + "preview": "Pré-visualização", + "html": "HTML", + "json": "JSON", + "previewOptions": "Opções de Pré-visualização", + "enhanceSpaces": "Melhorar Espaços", + "livePreview": "Pré-visualização ao vivo do HTML renderizado", + "htmlTailwind": "HTML com classes Tailwind CSS", + "editorJson": "Estado do editor como JSON", + "copyHtml": "Copiar HTML", + "copyJson": "Copiar JSON", + "copied": "Copiado!" + }, + "template": { + "fabTitle": "Trocar Modelo", + "title": "Escolhe um Modelo", + "description": "Seleciona um modelo para começar rapidamente com layouts e conteúdo pré-construídos", + "catAll": "Todos os Modelos", + "catProductivity": "Produtividade", + "catCreative": "Criativo", + "catBusiness": "Negócios", + "catPersonal": "Pessoal", + "noTemplates": "Nenhum modelo encontrado nesta categoria", + "confirmTitle": "Substituir Conteúdo Existente?", + "confirmDescription": "Tens conteúdo existente no editor. Aplicar este modelo irá substituir todo o conteúdo atual.", + "cannotUndo": "Esta ação não pode ser anulada.", + "cancel": "Cancelar", + "replaceContent": "Substituir Conteúdo" + }, + "command": { + "searchPlaceholder": "Pesquisar comandos...", + "noResults": "Nenhum comando encontrado.", + "turnInto": "Transformar em", + "h1": { + "label": "Título 1", + "description": "Título de secção grande", + "keywords": "h1,título,heading,grande" + }, + "h2": { + "label": "Título 2", + "description": "Título de secção médio", + "keywords": "h2,título,subtítulo" + }, + "h3": { + "label": "Título 3", + "description": "Título de secção pequeno", + "keywords": "h3,título,subtítulo" + }, + "h4": { + "label": "Título 4", + "description": "Título de secção mínimo", + "keywords": "h4,título" + }, + "h5": { + "label": "Título 5", + "description": "Título menor", + "keywords": "h5,título" + }, + "h6": { + "label": "Título 6", + "description": "Título mínimo", + "keywords": "h6,título" + }, + "p": { + "label": "Parágrafo", + "description": "Parágrafo de texto regular", + "keywords": "p,parágrafo,texto,normal" + }, + "code": { + "label": "Bloco de Código", + "description": "Excerto de código", + "keywords": "código,bloco,excerto,pre" + }, + "blockquote": { + "label": "Citação", + "description": "Bloco de citação", + "keywords": "citação,blockquote" + }, + "li": { + "label": "Lista com Marcadores", + "description": "Item de lista simples com marcador", + "keywords": "lista,marcador,não ordenada,ul,li" + }, + "ol": { + "label": "Lista Numerada", + "description": "Item de lista numerada", + "keywords": "lista,numerada,ordenada,ol,li" + }, + "img": { + "label": "Imagem", + "description": "Carregar ou incorporar uma imagem", + "keywords": "imagem,img,foto,upload" + }, + "video": { + "label": "Vídeo", + "description": "Carregar ou incorporar um vídeo", + "keywords": "vídeo,vid,filme,mp4,upload" + }, + "table": { + "label": "Tabela", + "description": "Criar uma tabela", + "keywords": "tabela,grelha,linhas,colunas,células" + } + }, + "toasts": { + "tableImported": "Tabela Importada", + "tableImportedDesc": "Tabela markdown importada com sucesso", + "mediaPasted": "Média colada", + "pasteMixed": "{imageCount} imagem(ns) e {videoCount} vídeo(s) colados com sucesso.", + "pasteImages": "{count} imagem(ns) colada(s) com sucesso.", + "pasteVideos": "{count} vídeo(s) colado(s) com sucesso.", + "pasteFailed": "Falha ao colar", + "unexpectedError": "Ocorreu um erro inesperado", + "uploadFailed": "Falha no carregamento" + }, + "blockBackground": { + "menuItem": "Alterar Cor de Fundo", + "dialogTitle": "Cor de Fundo do Bloco", + "dialogDescription": "Escolhe uma cor de fundo para este bloco", + "tabPreset": "Cores Predefinidas", + "tabCustom": "Cor Personalizada", + "sectionHeading": "Cores de Fundo", + "apply": "Aplicar", + "colors": { + "none": "Nenhuma", + "red": "Vermelho", + "orange": "Laranja", + "yellow": "Amarelo", + "green": "Verde", + "blue": "Azul", + "indigo": "Índigo", + "purple": "Roxo", + "pink": "Rosa", + "teal": "Verde-azulado", + "cyan": "Ciano", + "gray": "Cinzento" + } + }, + "tableDialog": { + "title": "Criar Tabela", + "description": "Cria uma nova tabela ou importa do markdown", + "markdownCheckbox": "Tenho uma tabela markdown", + "pasteMarkdownLabel": "Colar Tabela Markdown", + "markdownPlaceholder": "| Cabeçalho 1 | Cabeçalho 2 | Cabeçalho 3 |\n|----------|----------|----------|\n| Célula 1 | Célula 2 | Célula 3 |\n| Célula 4 | Célula 5 | Célula 6 |", + "markdownHint": "Cola a tua tabela markdown acima. Deve incluir linhas de cabeçalho e separador.", + "rows": "Linhas", + "columns": "Colunas", + "maxDimensions": "Máximo: 20 linhas × 10 colunas", + "cancel": "Cancelar", + "importTable": "Importar Tabela", + "createTable": "Criar Tabela" + }, + "tableParse": { + "errors": { + "minRows": "A tabela deve ter pelo menos uma linha de cabeçalho e uma linha separadora", + "pipeFormat": "As linhas da tabela devem começar e terminar com |", + "headerMinCol": "A linha de cabeçalho deve ter pelo menos uma coluna", + "separatorRow": "A segunda linha deve ser um separador (ex: |---|---|)", + "rowColumnMismatch": "A linha {row} tem {got} colunas, esperado {expected}", + "parseFailed": "Falha ao analisar tabela markdown" + } + }, + "link": { + "editLink": "Editar Ligação", + "addLink": "Adicionar Ligação", + "selectedText": "Texto selecionado: \"{text}\"", + "linkUrl": "URL da Ligação", + "urlPlaceholder": "https://exemplo.com", + "update": "Atualizar", + "addLinkButton": "Adicionar Ligação", + "toastApplied": "Ligação Aplicada", + "toastLinkedTo": "Ligado a: {url}", + "toastRemoved": "Ligação Removida", + "toastRemovedDesc": "Ligação foi removida da seleção" + }, + "customClass": { + "devMode": "Modo Dev", + "toggleDevModeAria": "Alternar modo dev", + "searchPlaceholderDev": "Pesquisar classes... (ex: 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Pesquisar estilos... (ex: 'red', 'bold', 'shadow')", + "appliesTitle": "Aplica: {className}", + "noMatches": "Nenhuma classe encontrada para \"{query}\"", + "toastClassReplaced": "Classe Substituída", + "toastClassReplacedDesc": "Substituída \"{replaced}\" por \"{className}\"", + "toastCustomClassApplied": "Classe Personalizada Aplicada", + "toastAppliedClass": "Classe aplicada: {className}" + }, + "insertComponents": { + "title": "Inserir Componente", + "description": "Escolhe um componente para inserir no teu documento", + "noComponents": "Nenhum componente disponível", + "categories": { + "media": "Média", + "layout": "Layout", + "interactive": "Interativo", + "decoration": "Decoração" + }, + "freeImage": { + "name": "Imagem com Movimento Livre", + "description": "Adiciona uma imagem que pode ser posicionada em qualquer lugar e redimensionada livremente" + } + } + }, + "JsonFormatter": { + "title": "Formatador JSON", + "description": "Formate, valide e edite JSON nas visualizações de texto e árvore.", + "documentNameText": "Documento de texto {n}", + "documentNameTree": "Documento em árvore {n}", + "newDocument": "Novo", + "copy": "Copiar", + "save": "Guardar", + "load": "Carregar", + "saving": "A guardar...", + "leftDocNameLabel": "Nome do documento esquerdo", + "rightDocNameLabel": "Nome do documento direito", + "toastCopiedText": "Painel de texto copiado", + "toastCopiedTree": "Painel de árvore copiado", + "toastCopyFailed": "Falha ao copiar JSON", + "toastLoginRequired": "Inicie sessão para guardar documentos", + "toastSaved": "Documento guardado", + "toastSaveFailed": "Falha ao guardar documento", + "format": "Formatar", + "compact": "Compactar", + "sortKeys": "Ordenar chaves", + "repair": "Reparar", + "query": "Consulta", + "toastInvalidJson": "JSON inválido", + "toastAlreadyValid": "O JSON já é válido", + "toastRepaired": "JSON reparado", + "toastRepairFailed": "Não foi possível reparar o JSON" + }, + "JwtDecoder": { + "title": "Decodificador JWT", + "subtitle": "Inspecione cabeçalho e payload. A expiração usa a claim exp. Nada é enviado a um servidor; a assinatura não é verificada.", + "tokenLabel": "Token", + "clear": "Limpar", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Copiar", + "expiryLabel": "Expiração (exp)", + "signaturePresent": "Assinatura presente (não verificada)", + "noExp": "Sem claim exp no payload.", + "status": { + "expired": "Expirado", + "valid": "Válido" + }, + "fields": { + "unix": "Unix", + "local": "Local", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Emitido em (iat)", + "notBefore": "Não antes de (nbf)" + }, + "panels": { + "header": "Cabeçalho", + "payload": "Payload" + }, + "errors": { + "empty": "Cole um JWT para decodificar.", + "segments": "Um JWT precisa de pelo menos cabeçalho e payload (dois segmentos).", + "base64": "Não foi possível decodificar em base64url o cabeçalho ou o payload." + }, + "relative": { + "future": "em {n} {unit}", + "past": "há {n} {unit}" + }, + "units": { + "second": "{count, plural, one {segundo} other {segundos}}", + "minute": "{count, plural, one {minuto} other {minutos}}", + "hour": "{count, plural, one {hora} other {horas}}", + "day": "{count, plural, one {dia} other {dias}}" + }, + "tabs": { + "decode": "Decodificar", + "sign": "Gerador / assinador" + }, + "signer": { + "algLabel": "Algoritmo (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "JSON do payload", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "JSON do header", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Segredo (HS*)", + "secretPlaceholder": "Cole seu segredo HMAC…", + "privateKeyLabel": "Chave privada PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "O algoritmo \"none\" cria um token sem assinatura. Isso é inseguro e deve ser usado apenas para testes.", + "sign": "Assinar", + "verify": "Verificar assinatura", + "outputLabel": "Token (JWS compacto)", + "outputPlaceholder": "Clique em \"Assinar\"…", + "copyToken": "Copiar token", + "copied": "Copiado", + "verifyKeyLabel": "Chave de verificação (opcional)", + "verifyKeyPlaceholder": "Para RS*: cole a chave pública (SPKI) PEM. Para HS*: deixe vazio para reutilizar o segredo.", + "verifyKeyHint": "Se estiver vazio, a verificação usa a chave de assinatura (útil para HS*).", + "verifyStatus": { + "valid": "Assinatura válida", + "invalid": "Assinatura inválida" + }, + "securityNote": "Tudo roda localmente no navegador; chaves e tokens não são enviados." + }, + "signErrors": { + "invalidJson": "Header e payload devem ser JSON válido.", + "unsupportedAlg": "Algoritmo não suportado.", + "missingKey": "Uma chave de assinatura é necessária para este algoritmo.", + "cryptoFailed": "Falha ao assinar. Verifique o formato da chave e tente novamente." + }, + "verifyErrors": { + "empty": "Digite um JWT para verificar.", + "segments": "Um JWT deve ter exatamente 3 segmentos para verificar uma assinatura.", + "unsupportedAlg": "Algoritmo não suportado ou alg ausente no header.", + "missingKey": "Uma chave de verificação é necessária para este algoritmo.", + "base64": "Não foi possível decodificar o header ou a assinatura do JWT.", + "cryptoFailed": "Falha ao verificar. Verifique o formato da chave e tente novamente." + } + }, + "CertificatePemDecoder": { + "title": "Descodificador de certificado / PEM", + "subtitle": "Cole um certificado X.509 (-----BEGIN CERTIFICATE-----) ou um CSR (-----BEGIN CERTIFICATE REQUEST-----). Sujeito, emissor, datas e SAN são analisados localmente; nada é enviado.", + "pemLabel": "Entrada PEM", + "clear": "Limpar", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "A analisar…", + "copyHex": "Copiar impressão digital (hex sem dois pontos)", + "errors": { + "empty": "Cole um certificado PEM ou CSR para descodificar.", + "noBlock": "Nenhum bloco PEM de certificado ou CSR encontrado.", + "parseFailed": "Não foi possível analisar estes dados PEM." + }, + "block": { + "title": "Bloco PEM {index}", + "certificate": "Certificado X.509", + "csr": "Pedido de assinatura de certificado" + }, + "fields": { + "subject": "Sujeito", + "issuer": "Emissor", + "notBefore": "Inválido antes de", + "notAfter": "Inválido depois de", + "serial": "Número de série (hex)", + "sha256": "Impressão digital SHA-256", + "sans": "Nomes alternativos do sujeito", + "sansNone": "Sem extensão SAN.", + "signatureAlgorithm": "Algoritmo de assinatura", + "csrIssuerNote": "Os CSR não incluem emissor (consta no certificado emitido).", + "csrValidityNote": "Os CSR não incluem datas de validade (constam no certificado emitido)." + }, + "sanType": { + "dns": "DNS", + "dn": "Nome distinto", + "email": "E-mail", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID registado", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Formatador SQL", + "subtitle": "Formate SQL para MySQL, PostgreSQL ou SQLite. Executa no navegador; nada é enviado.", + "dialectLabel": "Dialeto", + "keywordsLabel": "Palavras-chave", + "indentLabel": "Indentação", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "MAIÚSCULAS", + "lower": "minúsculas", + "preserve": "Manter" + }, + "spaces": "{n} espaços", + "format": "Formatar", + "clear": "Limpar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "formattedPanel": "Formatado", + "copy": "Copiar", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Clique em \"Formatar\"…", + "errors": { + "tooLong": "A entrada é limitada a {max} caracteres.", + "couldNotFormat": "Não foi possível formatar este SQL." + } + }, + "YamlFormatter": { + "title": "Formatador YAML", + "subtitle": "Formate, valide e converta YAML no navegador. Nada é carregado.", + "outputLabel": "Saída", + "outputModes": { + "yaml": "YAML (formatado)", + "json": "JSON" + }, + "indentLabel": "Recuo", + "spaces": "{n} espaços", + "format": "Formatar", + "clear": "Limpar", + "copy": "Copiar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "outputPanel": "Saída", + "errors": { + "tooLong": "A entrada está limitada a {max} caracteres.", + "couldNotFormat": "Não foi possível analisar este YAML." + } + }, + "GraphqlFormatter": { + "title": "Formatador GraphQL", + "subtitle": "Formate consultas, mutações e subscrições GraphQL no navegador. Nada é enviado.", + "tabs": { + "format": "Formatar", + "build": "Editor de consulta" + }, + "outputModeLabel": "Saída", + "outputModes": { + "pretty": "Impressão legível", + "minify": "Minificar" + }, + "format": "Formatar", + "clear": "Limpar", + "counter": "{current} / {max} caracteres", + "inputPanel": "Entrada", + "outputPanel": "Saída", + "copy": "Copiar", + "builderHint": "Monte um esqueleto e aplique-o ao editor. Definições de variáveis em sintaxe GraphQL (ex.: $id: ID!) — parênteses opcionais.", + "builder": { + "operation": "Operação", + "operationName": "Nome da operação", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Definições de variáveis", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Conjunto de seleção", + "apply": "Aplicar à entrada", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "A entrada está limitada a {max} caracteres.", + "couldNotFormat": "Não foi possível analisar ou imprimir este GraphQL.", + "emptySelection": "Adicione uma seleção antes de aplicar." + } + }, + "RegexTester": { + "title": "Testador de regex", + "subtitle": "Regex no estilo JavaScript ao vivo no seu texto. As correspondências são destacadas; nada sai do seu navegador.", + "patternLabel": "Padrão", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i ignorar maiúsc./minúsc.", + "m": "m multiline", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(vazio)", + "noFlags": "—", + "matches": "{count, plural, one {correspondência} other {correspondências}}", + "testTextLabel": "Texto de teste", + "counter": "{current} / {max}", + "clear": "Limpar", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "O texto é limitado a {max} caracteres.", + "invalid": "Expressão regular inválida.", + "runaway": "Interrompido após {max} correspondências (possível padrão descontrolado)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Conversor de timestamp", + "subtitle": "Segundos Unix (≤10 dígitos) ou milissegundos (inteiros maiores), ISO-8601 ou textos que o navegador consegue interpretar. Tudo roda localmente.", + "inputLabel": "Entrada", + "placeholder": "1744200000 ou 2025-04-09T12:00:00Z", + "now": "Agora", + "copy": "Copiar", + "hint": "Cole um valor ou clique em Agora.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativo", + "human": "Legível" + }, + "fields": { + "seconds": "Segundos", + "milliseconds": "Milissegundos", + "utc": "UTC", + "local": "Local", + "distance": "Distância" + }, + "errors": { + "empty": "Insira um timestamp Unix, uma string ISO ou um texto de data.", + "invalidNumber": "Timestamp numérico inválido.", + "outOfRange": "Fora do intervalo para Date do JavaScript.", + "couldNotParse": "Não foi possível interpretar como Unix, ISO-8601 ou data do navegador." + } + }, + "HttpStatusCodes": { + "title": "Códigos de estado HTTP", + "subtitle": "Pesquise e navegue por códigos de estado de respostas HTTP (1xx5xx) com descrições curtas e referências RFC. Funciona localmente.", + "header": { + "title": "Códigos de estado HTTP", + "description": "Referência pesquisável de códigos de resposta HTTP com descrições e referências RFC." + }, + "searchPlaceholder": "Pesquisar: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtrar classe…", + "classFilterAll": "Todas as classes", + "resultsCount": "{count} de {total}", + "table": { + "code": "Code", + "phrase": "Frase", + "description": "Description", + "copy": "Copiar" + }, + "noResults": "Sem resultados para “{query}”.", + "specPrefix": "Espec.:", + "copied": "Código copiado" + }, + "CronBuilder": { + "title": "Construtor de expressão Cron", + "subtitle": "Cron padrão de 5 campos: minute hour day month weekday. O parser usa seconds=0 internamente. O formato opcional de 6 campos (sec min hour dom mon dow) funciona no editor.", + "tabs": { + "builder": "Construtor visual", + "expression": "Expressão" + }, + "presetsLabel": "Predefinições", + "fieldsLabel": "Campos", + "rawExpressionLabel": "Expressão bruta", + "expressionHint": "Tire o foco do campo para analisar e sincronizar. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Expressão atual", + "copy": "Copiar", + "quickPick": "Escolha rápida", + "emptyExpression": "—", + "plainLanguageLabel": "Linguagem natural", + "nextRunsLabel": "Próximas execuções (hora local)", + "fields": { + "minute": "Minuto", + "hour": "Hora", + "dayOfMonth": "Dia do mês", + "month": "Mês", + "dayOfWeek": "Dia da semana" + }, + "presets": { + "everyMinute": "A cada minuto", + "every5Minutes": "A cada 5 minutos", + "every15Minutes": "A cada 15 minutos", + "everyHour": "A cada hora (em :00)", + "every6Hours": "A cada 6 horas", + "dailyMidnight": "Diário à meia-noite", + "daily0900": "Diário às 09:00", + "weekdays0900": "Dias úteis às 09:00", + "weeklySunday": "Semanal (domingo 00:00)", + "monthlyFirst": "Mensal (dia 1 00:00)", + "yearlyJan1": "Anual (1 jan 00:00)" + }, + "picks": { + "minute": { + "every": "a cada minuto" + }, + "hour": { + "every": "a cada hora" + }, + "dom": { + "any": "qualquer dia", + "last": "último dia" + }, + "month": { + "every": "todo mês" + }, + "dow": { + "any": "qualquer dia", + "weekdays": "dias úteis", + "weekend": "fim de semana" + } + }, + "months": { + "jan": "Jan", + "feb": "Fev", + "mar": "Mar", + "apr": "Abr", + "may": "Mai", + "jun": "Jun", + "jul": "Jul", + "aug": "Ago", + "sep": "Set", + "oct": "Out", + "nov": "Nov", + "dec": "Dez" + }, + "weekdays": { + "sun": "Dom", + "mon": "Seg", + "tue": "Ter", + "wed": "Qua", + "thu": "Qui", + "fri": "Sex", + "sat": "Sáb" + }, + "errors": { + "fieldCount": "Esperados 5 campos (minute hour day month weekday) ou 6 incluindo segundos.", + "invalid": "Expressão cron inválida." + } + }, + "Base64": { + "title": "Codificador / decodificador Base64", + "subtitle": "Codifique texto em Base64 ou decodifique strings Base64", + "upload": "Enviar", + "clear": "Limpar", + "modes": { + "encode": "Codificar", + "decode": "Decodificar" + }, + "swapTitle": "Trocar entrada e saída", + "panels": { + "plainText": "Texto simples", + "base64Input": "Entrada Base64", + "base64Output": "Saída Base64", + "decodedText": "Texto decodificado" + }, + "placeholders": { + "encode": "Digite texto para codificar em Base64…", + "decode": "Cole Base64 para decodificar…" + }, + "dropHere": "Solte o arquivo aqui", + "charCount": "{count} caracteres", + "download": { + "title": "Baixar saída", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Copiar para a área de transferência", + "outputPlaceholder": "A saída aparecerá aqui…", + "errors": { + "invalidBase64": "String Base64 inválida. Verifique a entrada.", + "encodeFailed": "Falha ao codificar a entrada." + } + }, + "UrlEncode": { + "title": "Codificador / decodificador de URL", + "subtitle": "Codifique/decodifique por percentagem com encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Enviar", + "clear": "Limpar", + "modes": { + "encode": "Codificar", + "decode": "Decodificar" + }, + "swapTitle": "Trocar entrada e saída", + "panels": { + "plainText": "Texto simples", + "encodedInput": "Entrada codificada", + "encodedOutput": "Saída codificada", + "decodedText": "Texto decodificado" + }, + "placeholders": { + "encode": "Digite texto para codificar (ex.: valores de query string)…", + "decode": "Cole texto percent-encoded para decodificar…" + }, + "dropHere": "Solte o arquivo aqui", + "charCount": "{count} caracteres", + "download": { + "title": "Baixar saída", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Copiar para a área de transferência", + "outputPlaceholder": "A saída aparecerá aqui…", + "errors": { + "invalidEncoding": "Codificação por percentagem inválida. Verifique pares % incompletos ou escapes.", + "encodeFailed": "Não foi possível codificar (Unicode inválido na entrada)." + } + }, + "UrlParser": { + "title": "Analisador de URL", + "subtitle": "Divida um URL em protocolo, host, caminho, parâmetros de query e hash (fragmento).", + "upload": "Carregar", + "clear": "Limpar", + "panels": { + "input": "Entrada de URL", + "output": "Saída" + }, + "placeholders": { + "input": "Cole um URL (ex.: https://example.com/path?foo=bar#section) ou um host/caminho (ex.: example.com/path)…" + }, + "dropHere": "Solte o arquivo aqui", + "charCount": "{count} caracteres", + "outputPlaceholder": "Os componentes analisados aparecerão aqui…", + "fields": { + "protocol": "Protocolo", + "host": "Host", + "path": "Caminho", + "hash": "Hash", + "queryParams": "Parâmetros de query" + }, + "paramCount": "{count} parâmetros", + "noParams": "Sem parâmetros de query.", + "paramKey": "Chave", + "paramValue": "Valor", + "copyTitle": "Copiar para a área de transferência", + "copyParamTitle": "Copiar como key=value", + "download": { + "title": "Baixar JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL inválido. Inclua um hostname (example.com) e verifique os caracteres." + } + }, + "UuidGenerator": { + "title": "Gerador de UUID / ULID", + "subtitle": "Gere UUIDs (RFC 9562) ou ULIDs no navegador. Exportação em lote até {max} por execução.", + "format": "Formato", + "kindHints": { + "ulid": "Identificador ordenável, seguro para URL, 26 caracteres", + "uuid4": "Aleatório (RFC 9562)", + "uuid7": "Ordenado no tempo, ms Unix + aleatório", + "uuid1": "Baseado em MAC/tempo (RFC 9562)", + "uuid6": "Tempo gregoriano reordenado", + "uuid3": "Hash MD5 de nome + namespace", + "uuid5": "Hash SHA‑1 de nome + namespace", + "nil": "UUID nulo (tudo zeros, RFC 9562)" + }, + "countLabel": "Quantidade (1–{max})", + "nameLabel": "Nome (string de entrada)", + "namePlaceholder": "ex.: my.app/resource", + "bulkHint": "Em lote, adiciona-se #0, #1, … para que cada ID seja distinto (v3/v5 são determinísticos).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID personalizado" + }, + "customNamespaceLabel": "UUID do namespace", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Gerar", + "output": "Saída", + "lines": "{count} linhas", + "download": { + "title": "Baixar .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Copiar tudo", + "uppercaseToggle": "Saída em maiúsculas", + "hyphensToggle": "Mostrar hífenes", + "copyJsonTitle": "Copiar como array JSON", + "outputPlaceholder": "Clique em \"Gerar\"…", + "errors": { + "invalidCustomNamespace": "O namespace personalizado deve ser um UUID válido.", + "missingName": "O nome é obrigatório para UUID v3 e v5.", + "unknown": "Não foi possível gerar os IDs." + } + }, + "SecretApiKeyGenerator": { + "title": "Gerador de segredo / chave API", + "subtitle": "Gere cadeias aleatórias criptográficas com comprimento e alfabeto configuráveis. Exportação em massa até {max} por execução. Complementa o gerador UUID / ULID.", + "alphabetLabel": "Alfabeto", + "alphabetHint": "Caracteres duplicados são removidos. Cada símbolo é escolhido uniformemente com crypto.getRandomValues e amostragem por rejeição (sem viés de módulo).", + "alphabetCount": "{count} símbolos únicos", + "presetsLabel": "Predefinições", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumérico", + "hex": "Hexadecimal (minúsculas)", + "asciiPrintable": "ASCII imprimível" + }, + "lengthLabel": "Comprimento por segredo (1–{maxLen})", + "countLabel": "Quantidade (1–{maxBulk})", + "generate": "Gerar", + "output": "Saída", + "lines": "{count} linhas", + "download": { + "title": "Transferir .txt", + "filename": "segredos.txt" + }, + "copyTitle": "Copiar tudo", + "outputPlaceholder": "Clique em \"Gerar\"…", + "entropy": "{bits} bits de entropia", + "strength": { + "weak": "Fraca", + "fair": "Razoável", + "strong": "Forte" + }, + "excludeAmbiguous": "Excluir caracteres ambíguos", + "excludeAmbiguousHint": "Remove {chars} — caracteres facilmente confundidos ou hostis à shell.", + "prefixLabel": "Prefixo da chave (opcional)", + "errors": { + "emptyAlphabet": "Adicione pelo menos um carácter ao alfabeto.", + "alphabetTooLong": "Após remover duplicados, o alfabeto pode ter no máximo {maxAlpha} caracteres.", + "lengthOutOfRange": "O comprimento deve estar entre 1 e {maxLen}.", + "countOutOfRange": "A quantidade deve estar entre 1 e {maxBulk}.", + "unknown": "Não foi possível gerar os segredos." + } + }, + "LoremIpsum": { + "title": "Gerador de Lorem Ipsum", + "subtitle": "Gere texto latino de espaço reservado para os seus layouts e maquetes.", + "paragraphs": "Parágrafos", + "sentences": "Frases", + "words": "Palavras", + "count": "Quantidade", + "generate": "Gerar", + "copy": "Copiar", + "startWith": "Começar com 'Lorem ipsum...'", + "outputPlaceholder": "O resultado aparecerá aqui...", + "outputType": "Tipo de saída", + "bulletList": "Lista de marcadores", + "htmlMarkup": "Marcação HTML", + "htmlMarkupHint": "Envolva a saída em parágrafos ou use uma lista para o modo de marcadores", + "output": "Saída", + "download": "Descarregar" + }, + "ColorPicker": { + "title": "Seletor e conversor de cores", + "subtitle": "Converta HEX, RGB e HSL; crie paletas harmónicas. Todo o processamento ocorre no seu navegador.", + "nativePicker": "Seletor nativo", + "chooseColor": "Escolha uma cor", + "eyeDropper": "Conta-gotas", + "invalidHex": "Cor HEX inválida.", + "cssValues": "Valores CSS", + "palettes": "Paletas", + "copied": "Copiado", + "clickToCopy": "Clique para copiar", + "copyAllHex": "Copiar todos os HEX", + "copy": "Copiar", + "copyColor": "Copiar {color}", + "paletteShades": "Tons", + "paletteComplementary": "Complementar", + "paletteTriadic": "Triádico", + "paletteAnalogous": "Análogo", + "paletteSplit": "Compl. dividido", + "paletteSquare": "Quadrado" + }, + "ContrastChecker": { + "title": "Verificador de contraste", + "subtitle": "Calcula o rácio de contraste WCAG 2.1 entre primeiro plano e fundo. Verifica AA e AAA para texto normal e grande. Tudo corre no seu navegador.", + "foreground": "Texto / primeiro plano", + "background": "Fundo", + "hexPlaceholder": "#RRGGBB", + "swap": "Trocar cores", + "chooseForeground": "Escolher cor do primeiro plano", + "chooseBackground": "Escolher cor de fundo", + "eyeDropper": "Conta-gotas", + "ratio": "Rácio de contraste", + "copyRatio": "Copiar rácio", + "copied": "Copiado", + "previewHeading": "Pré-visualização", + "previewBody": "Vejo xá gritando que fez show sem playback. Luís argüia à Júlia que «brações, fé, chá, óxido, pôr, zâmia» caíam.", + "previewCaption": "Texto de exemplo no tamanho predefinido.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — texto normal (4,5:1)", + "levelAALarge": "AA — texto grande (3:1)", + "levelAAANormal": "AAA — texto normal (7:1)", + "levelAAALarge": "AAA — texto grande (4,5:1)", + "pass": "Conforme", + "fail": "Não conforme", + "invalidHex": "Cor HEX inválida.", + "largeTextHint": "Texto grande: 18 pt+ normal ou 14 pt+ negrito (definições WCAG)." + }, + "DiffChecker": { + "title": "Verificador de diferenças", + "subtitle": "Comparação linha a linha de dois textos lado a lado. Tudo permanece no seu navegador.", + "rowsCount": "{count} linhas", + "truncatedWarning": "A mostrar as primeiras {max} linhas.", + "limitExceeded": "Cada lado é limitado a {max} caracteres.", + "clearBoth": "Limpar ambos", + "original": "Original", + "modified": "Modificado", + "originalPlaceholder": "Cole o texto original…", + "modifiedPlaceholder": "Cole o texto modificado…", + "comparisonLabel": "Comparação lado a lado", + "emptyState": "Introduza texto em ambos os painéis para comparar." + }, + "JsonSchemaGenerator": { + "title": "Gerador de esquema JSON", + "subtitle": "Cole JSON válido para gerar um esquema JSON ou modelos tipados. Tudo corre no seu navegador.", + "languageLabel": "Idioma de saída", + "inputLabel": "Entrada JSON", + "inputPlaceholder": "Cole o JSON aqui…", + "outputLabel": "Saída gerada", + "outputPlaceholder": "O esquema ou os tipos gerados aparecerão aqui…", + "parseError": "JSON inválido", + "clear": "Limpar", + "copy": "Copiar", + "copied": "Copiado para a área de transferência", + "copyFailed": "Não foi possível copiar", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Esquema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "A carregar o editor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Imagem para Base64", + "subtitle": "Solte uma imagem, obtenha um URI de dados ou uma string Base64 bruta. Tudo fica no seu navegador.", + "upload": "Carregar imagem", + "clear": "Claro", + "modes": { + "dataUri": "URI de dados", + "rawString": "Base64 bruta" + }, + "panels": { + "imageInput": "Entrada de imagem", + "output": "Saída Base64" + }, + "dropHere": "Solte a imagem aqui", + "charCount": "{contar} caracteres", + "placeholders": { + "uploadImage": "Arraste e solte uma imagem ou clique em carregar para começar." + }, + "fileTooLarge": "O arquivo é muito grande. O processamento pode congelar seu navegador.", + "copyTitle": "Copiar para a área de transferência", + "download": { + "title": "Baixar texto", + "filename": "imagem-base64.txt" + } + }, + "ImageCompressor": { + "title": "Compressor de imagens", + "subtitle": "Reduza a saída JPEG, PNG ou WebP localmente com controlo de qualidade — nada é enviado.", + "upload": "Carregar imagem", + "clear": "Limpar", + "dropHere": "Largue a imagem aqui", + "placeholders": { + "uploadImage": "Arraste e largue uma imagem ou clique para escolher um ficheiro." + }, + "fileTooLarge": "O ficheiro é demasiado grande (máx. {maxMb} MB).", + "unsupportedFormat": "Tipo de ficheiro não suportado. Use JPEG, PNG, WebP, GIF, AVIF ou BMP.", + "compressFailed": "Não foi possível comprimir a imagem. Tente outro ficheiro ou formato.", + "panels": { + "original": "Original", + "compressed": "Pré-visualização comprimida" + }, + "outputFormat": "Formato de saída", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Qualidade", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG é sem perdas no navegador; o controlo de qualidade aplica-se só a JPEG e WebP.", + "sizes": { + "original": "Original: {size}", + "compressed": "Comprimido: {size}", + "saved": "{percent}% menor" + }, + "largerWarning": "A saída é maior que a entrada — experimente outro formato ou qualidade.", + "download": "Descarregar", + "downloadFilename": "imagem-comprimida", + "processing": "A comprimir…", + "emptyCompressed": "A pré-visualização comprimida aparece aqui." + }, + "CssGradientBuilder": { + "title": "Construtor de gradiente CSS", + "subtitle": "Construtor de gradiente visual CSS com controle de ângulo, limites de cores e cópia CSS com um clique.", + "preview": "Pré-visualização do gradiente", + "settings": "Configurações", + "type": "Tipo", + "linear": "Linear", + "radial": "Radial", + "angle": "Ângulo", + "stops": "Paradas de cores", + "color": "Cor", + "position": "Posição (%)", + "addStop": "Adicionar parada", + "deleteStop": "Excluir parada", + "output": "Saída CSS", + "copy": "Copiar CSS", + "copied": "Copiado!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Gerador .gitignore", + "subtitle": "Gere arquivos .gitignore mesclados instantaneamente para uma pilha ou combinação de tecnologia específica.", + "searchPlaceholder": "Pilhas de pesquisa (por exemplo, Node, Python, macOS)...", + "noResults": "Nenhuma pilha encontrada.", + "selectedStacks": "Pilhas selecionadas", + "output": "Visualizar saída", + "copy": "Copiar para a área de transferência", + "copied": "Copiado!", + "download": "Baixe .gitignore", + "downloading": "Baixando...", + "errorFetching": "Falha ao buscar a lista dos principais.", + "errorGenerating": "Falha ao gerar .gitignore das pilhas selecionadas.", + "generating": "Gerando...", + "emptyState": "Selecione uma ou mais pilhas de tecnologia para gerar seu .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Carregue CSV ou Excel, edite o JSON e descarregue CSV ou XLSX. Apenas dados tabulares; as datas do Excel passam a ISO no JSON.", + "dropTitle": "Largue um ficheiro CSV ou Excel", + "dropHint": "Para .xlsx / .xls usa-se a primeira folha. O CSV precisa de linha de cabeçalho.", + "chooseFile": "Escolher ficheiro", + "jsonLabel": "JSON (array de objetos)", + "jsonPlaceholder": "Cole um array JSON de objetos ou importe CSV / Excel…", + "loadSample": "Carregar exemplo", + "actions": { + "format": "Formatar JSON", + "clear": "Limpar", + "copy": "Copiar", + "copied": "Copiado", + "downloadCsv": "Descarregar CSV", + "downloadXlsx": "Descarregar Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Dados", + "toastLoadedCsv": "CSV carregado no editor", + "toastLoadedExcel": "Excel carregado no editor", + "toastFormatted": "JSON formatado", + "toastExportedCsv": "CSV descarregado", + "toastExportedXlsx": "Excel descarregado", + "toastCopied": "Copiado para a área de transferência", + "footnote": "Tudo corre no navegador. O JSON deve ser um array não vazio de objetos. Valores aninhados são serializados em JSON nas células CSV.", + "errors": { + "unsupportedFile": "Use um ficheiro .csv, .xlsx ou .xls.", + "parseFailed": "Não foi possível analisar o ficheiro.", + "invalidJson": "JSON inválido", + "exportFailed": "Falha na exportação", + "copyFailed": "Não foi possível copiar" + } + }, + "SnippetManager": { + "title": "Gestor de excertos de código", + "subtitle": "Guarde excertos no navegador ou inicie sessão para os sincronizar com a sua conta. Alterne entre ver e editar, copie com um clique, escolha a linguagem ou deteção automática e formate JSON, SQL ou código suportado pelo Monaco.", + "snippets": "Excertos", + "newSnippet": "Novo excerto", + "newSnippetTitle": "Novo excerto", + "defaultSnippetTitle": "Bem-vindo", + "defaultSnippetCode": "// Bem-vindo aos excertos de código\n// Experimente Ver vs Editar, Copiar, Formatar e idioma automático\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Pesquisar excertos…", + "searchAria": "Pesquisar excertos", + "emptySearch": "Nenhum excerto corresponde à pesquisa.", + "snippetTitle": "Título", + "snippetTitlePlaceholder": "Nome do excerto", + "language": "Linguagem", + "detectedHint": "Realce como {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Ver", + "modeEdit": "Editar", + "copy": "Copiar", + "copied": "Copiado", + "format": "Formatar", + "cancel": "Cancelar", + "deleteTitle": "Eliminar excerto?", + "deleteDescription": "«{title}» será removido deste navegador.", + "deleteConfirm": "Eliminar", + "editorAria": "Editor de código do excerto", + "editorLoading": "A carregar editor…", + "toastCopied": "Copiado para a área de transferência", + "toastCopyFailed": "Não foi possível copiar", + "toastNothingToCopy": "Nada para copiar", + "toastFormatted": "Formatado", + "toastFormatFailed": "Não foi possível formatar", + "toastFormatJsonInvalid": "JSON inválido — corrija a sintaxe primeiro", + "toastFormatUnsupported": "Sem formatador para esta linguagem — tente JSON ou SQL, ou modo de edição", + "toastDeleted": "Excerto eliminado", + "toastLoadFailed": "Não foi possível carregar os excertos do servidor.", + "toastSyncFailed": "Não foi possível sincronizar com o servidor.", + "languages": { + "auto": "Deteção automática", + "plaintext": "Texto simples", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Cliente SQL", + "dialogTitle": "Conexões SQL", + "mobileSheetTitle": "Explorador SQL", + "newTabLabel": "Nova aba", + "vaultLockedTitle": "Cofre bloqueado", + "vaultLockedDesc": "Desbloqueie seu cofre para acessar as conexões salvas.", + "heroTitle": "Cliente SQL", + "heroDesc": "Conecte-se ao PostgreSQL, MySQL ou MariaDB. As credenciais são criptografadas no navegador.", + "heroAddConnection": "Adicionar conexão", + "selectTableTitle": "Selecione uma tabela ou abra uma aba", + "selectTableDesc": "Clique em uma tabela na barra lateral ou use ⌘↩.", + "toastLoadFail": "Falha ao carregar conexões." + }, + "connection": { + "titleNew": "Nova conexão SQL", + "titleEdit": "Editar conexão", + "labelDbType": "Tipo de banco de dados", + "labelName": "Nome da conexão", + "placeholderName": "Meu banco de produção", + "labelHostPort": "Host e Porta", + "labelDatabase": "Banco de dados", + "placeholderDatabase": "meubanco", + "labelUsername": "Usuário", + "labelPassword": "Senha", + "labelSsl": "Ativar SSL / TLS", + "sslDesc": "Criptografar a conexão com o servidor", + "securityNote": "As credenciais são criptografadas AES-256 no navegador antes do armazenamento", + "btnTest": "Testar conexão", + "btnSave": "Salvar e conectar", + "btnUpdate": "Atualizar", + "btnCancelEdit": "Cancelar edição", + "savedConnections": "Conexões salvas", + "emptySaved": "As conexões salvas aparecerão aqui", + "confirmDelete": "Excluir esta conexão?", + "toastFillFields": "Preencha host e banco antes de testar.", + "toastConnectOk": "Conectado! {version}", + "toastConnectFail": "Falha na conexão: {message}", + "toastUnlockVault": "Desbloqueie seu cofre primeiro.", + "toastFieldsRequired": "Host e banco de dados são obrigatórios.", + "toastUpdated": "Conexão atualizada.", + "toastSaved": "Conexão salva.", + "toastSaveFail": "Falha ao salvar: {message}", + "toastDeleted": "Conexão excluída.", + "toastDeleteFail": "Falha ao excluir conexão." + }, + "sidebar": { + "title": "Explorador SQL", + "tooltipAddConnection": "Adicionar conexão", + "tooltipRefreshSchema": "Atualizar esquema", + "noConnections": "Nenhuma conexão salva ainda", + "toastSchemaFail": "Falha ao carregar esquema: {message}" + }, + "editor": { + "btnRun": "Executar", + "executing": "Executando consulta…", + "emptyTitle": "Execute uma consulta para ver os resultados", + "emptyHint": "Pressione ⌘↩ ou clique em Executar", + "toastNoConnection": "Sem conexão ativa." + }, + "results": { + "filterPlaceholder": "Filtrar resultados…", + "rowCount": "{count} {count, plural, one {linha} other {linhas}}", + "rowCountFiltered": "{filtered} / {total} linhas", + "exportCsv": "CSV", + "noRows": "Nenhuma linha retornada" + } + }, + "QrCodeGenerator": { + "title": "Gerador de códigos QR", + "subtitle": "Converta qualquer texto ou URL num código QR PNG. Tudo é renderizado localmente no navegador.", + "contentLabel": "Conteúdo", + "contentPlaceholder": "https://exemplo.pt, texto simples, cadeia Wi‑Fi, vCard…", + "styleLabel": "Estilo", + "dotTypeLabel": "Forma do ponto", + "dotTypeSquare": "Quadrado", + "dotTypeDots": "Pontos", + "dotTypeRounded": "Arredondado", + "dotTypeExtraRounded": "Extra arredondado", + "dotTypeClassy": "Elegante", + "dotTypeClassyRounded": "Elegante arredondado", + "cornerSquareLabel": "Quadrados de canto", + "cornerSquareDot": "Ponto", + "cornerSquareSquare": "Quadrado", + "cornerSquareExtraRounded": "Extra arredondado", + "cornerDotLabel": "Pontos de canto", + "cornerDotDot": "Ponto", + "cornerDotSquare": "Quadrado", + "eccLabel": "Correção de erros", + "eccL": "Baixa (~7%)", + "eccM": "Média (~15%)", + "eccQ": "Quartil (~25%)", + "eccH": "Alta (~30%)", + "marginLabel": "Margem (módulos)", + "widthLabel": "Largura da imagem", + "widthEffective": "Renderizado a {px} px", + "darkColor": "Módulos escuros", + "lightColor": "Módulos claros", + "resetColors": "Repor cores", + "logoLabel": "Logo central", + "logoMarginLabel": "Margem", + "uploadLogo": "Carregar imagem", + "removeLogo": "Remover logo", + "downloadPng": "Descarregar PNG", + "downloadSvg": "Transferir SVG", + "copyImage": "Copiar imagem", + "copied": "Copiado", + "clear": "Limpar", + "previewAlt": "Pré-visualização do código QR", + "emptyHint": "Escreva ou cole conteúdo para pré-visualizar.", + "filename": "codigo-qr.png", + "errors": { + "generationFailed": "Não foi possível gerar o código QR. Tente texto mais curto ou menos correção.", + "copyUnsupported": "O navegador não suporta copiar imagens para a área de transferência.", + "copyFailed": "Não foi possível copiar para a área de transferência." + } + }, + "IpSubnetCalculator": { + "title": "Calculadora IP / sub-rede", + "subtitle": "Detalhes CIDR IPv4 e IPv6 no navegador—sem pedidos externos.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Rede", + "placeholderIpv4": "10.0.0.0/24 ou 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 ou 2001:db8::1/64", + "hintIpv4": "Use CIDR (ex.: /24) ou IPv4 com máscara decimal.", + "hintIpv6": "Inclua o comprimento do prefixo (ex.: /64). Se omitido, /128.", + "copyValue": "Copiar valor", + "rows": { + "cidr": "CIDR", + "network": "Endereço de rede", + "netmask": "Máscara de sub-rede", + "wildcard": "Máscara wildcard", + "broadcast": "Broadcast", + "firstHost": "Primeiro host", + "lastHost": "Último host", + "totalAddresses": "Endereços totais", + "usableHosts": "Hosts utilizáveis", + "prefixLength": "Comprimento do prefixo (bits)", + "networkIpv6": "Endereço de rede", + "lastIpv6": "Último endereço no intervalo", + "totalAddressesIpv6": "Endereços na sub-rede" + }, + "usableNote": { + "pointToPoint": "Ligações /31 ponto a ponto usam ambos os endereços como hosts (RFC 3021).", + "singleHost": "/32 é um único host." + }, + "ipv6RangeHint": { + "normal": "Mostra o intervalo matemático completo; regras do router podem variar.", + "single": "/128 é um único endereço.", + "full": "/0 cobre todo o IPv6." + }, + "errors": { + "invalidIpv4": "Rede IPv4 inválida. Tente CIDR ou endereço + máscara.", + "invalidIpv6": "Rede IPv6 inválida. Use endereço/prefixo, ex.: 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Gerador de hash", + "subtitle": "Resumos MD5, SHA-1 e SHA-2 para texto ou ficheiros; bcrypt para palavras-passe (entrada de texto). Tudo é calculado localmente no navegador.", + "algorithmLabel": "Algoritmo", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Texto", + "modeFile": "Ficheiro", + "inputLabelText": "Entrada", + "placeholderText": "Escreva ou cole texto…", + "chooseFile": "Escolher ficheiro", + "clearFile": "Limpar", + "fileHint": "Os ficheiros são lidos só na memória; máx. {maxMb} MB. Sem envio.", + "outputLabel": "Resumo (hex)", + "copyHash": "Copiar resumo", + "copied": "Copiado", + "byteCount": "{count, plural, one {# byte} other {# bytes}}", + "charCount": "{count, plural, one {# carácter} other {# caracteres}}", + "emptyHint": "Introduza texto ou escolha um ficheiro.", + "securityNote": "MD5 e SHA-1 não são recomendados para novos usos de segurança; prefira SHA-256 ou SHA-512.", + "errors": { + "fileTooLarge": "Ficheiro maior que {maxMb} MB.", + "fileReadFailed": "Não foi possível ler o ficheiro." + }, + "bcryptRoundsLabel": "Custo (voltas)", + "bcryptRoundsHint": "Valores mais altos são mais lentos e mais resistentes à força bruta. O padrão comum é 10.", + "fileTabDisabledTitle": "O bcrypt só resume palavras-passe a partir de texto, não ficheiros arbitrários.", + "placeholderBcrypt": "Digite ou cole uma palavra-passe para resumir…", + "outputLabelBcrypt": "Hash bcrypt", + "copyBcrypt": "Copiar hash", + "emptyHintBcrypt": "Introduza uma palavra-passe para calcular o hash bcrypt.", + "securityNoteBcrypt": "Cada saída bcrypt inclui sal aleatório; a mesma palavra-passe produz uma cadeia diferente de cada vez. É esperado. Use custo elevado em produção.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Laboratório de encriptação", + "subtitle": "AES-GCM no navegador: encriptar para um pacote JSON com frase-passe ou chave bruta e desencriptar localmente. Educativo e por vezes prático — nada é enviado.", + "tabEncrypt": "Encriptar", + "tabDecrypt": "Desencriptar", + "keyModeLabel": "Material da chave", + "modePassphrase": "Frase-passe (PBKDF2 + AES-GCM)", + "modeRawKey": "Chave AES bruta (hex ou Base64)", + "aesBitsLabel": "Comprimento da chave AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Codificação da chave bruta", + "encodingAuto": "Automático (hex se parecer hex, senão Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Chave bruta", + "rawKeyPlaceholder": "Chave de 32 bytes como 64 caracteres hex, ou Base64 para AES-256 (o comprimento depende do tamanho AES acima).", + "rawKeyPlaceholderDecrypt": "Só necessário se o pacote tiver keyDerivation \"none\".", + "passphraseLabel": "Frase-passe", + "passphrasePlaceholder": "Frase-passe usada apenas no navegador para derivar a chave.", + "passphrasePlaceholderDecrypt": "Só necessário se o pacote usar PBKDF2-SHA256.", + "pbkdf2IterLabel": "Iterações PBKDF2", + "pbkdf2Hint": "Entre 10 000 e 2 000 000. O predefinido segue definições comuns de cofres.", + "plaintextLabel": "Texto simples", + "plaintextPlaceholder": "Escreva ou cole a mensagem a encriptar…", + "bundleInputLabel": "Pacote encriptado (JSON)", + "bundlePlaceholder": "Cole o JSON de Encriptar (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Pacote encriptado", + "plaintextOutputLabel": "Texto simples", + "encryptButton": "Encriptar", + "decryptButton": "Desencriptar", + "copyBundle": "Copiar JSON", + "copyPlaintext": "Copiar texto simples", + "copied": "Copiado", + "emptyBundleHint": "O JSON encriptado aparece aqui após encriptar.", + "emptyDecryptHint": "O texto desencriptado aparece aqui.", + "decryptKeyHint": "Use a chave bruta se keyDerivation for \"none\"; a frase-passe se for PBKDF2-SHA256. O pacote guarda salt, iterações, IV e tamanho AES.", + "securityNote": "Chaves e texto simples não saem do navegador. Para segredos de produção prefira ferramentas dedicadas.", + "errors": { + "emptyKey": "Introduza uma chave bruta que corresponda ao tamanho AES.", + "emptyPassphrase": "Introduza uma frase-passe.", + "badKeyMaterial": "Não foi possível ler a chave como hex ou Base64.", + "wrongKeyLength": "O comprimento da chave não corresponde ao tamanho AES (16 / 24 / 32 bytes).", + "invalidJson": "JSON inválido.", + "invalidBundle": "Este JSON não é um pacote de encriptação suportado.", + "decryptFailed": "Desencriptação falhou — segredo errado, pacote corrompido ou parâmetros incorretos.", + "encryptFailed": "Encriptação falhou. Verifique Web Crypto e as entradas." + } + }, + "HmacGenerator": { + "title": "Gerador HMAC", + "subtitle": "Para assinar webhooks e testar integrações de API. HMAC-SHA1/256/384/512 em hexadecimal ou Base64—calculado localmente no navegador.", + "secretLabel": "Chave secreta", + "messageLabel": "Mensagem / payload", + "placeholderSecret": "Cole o segredo de assinatura…", + "placeholderMessage": "Cole o corpo em bruto ou a cadeia a autenticar (normalmente os bytes exatos do webhook).", + "algorithmLabel": "Algoritmo HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Formato de saída", + "formatHex": "Hexadecimal (minúsculas)", + "formatBase64": "Base64", + "outputLabel": "Assinatura", + "copyOutput": "Copiar assinatura", + "copied": "Copiado", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (UTF-8)", + "emptyHint": "Introduza uma chave secreta para calcular o HMAC.", + "securityNote": "Nunca partilhe o segredo de assinatura. Esta página não carrega dados; tudo corre no navegador.", + "errors": { + "cryptoFailed": "Não foi possível calcular o HMAC. Verifique o suporte a Web Crypto e tente novamente.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Gerador de código TOTP / 2FA", + "subtitle": "Cole o segredo Base32 do autenticador e obtenha o código atual de seis dígitos RFC 6238 (SHA-1, 30 s). Corre localmente no browser—útil para testar login ou MFA.", + "secretLabel": "Segredo (Base32)", + "placeholderSecret": "Cole a chave secreta (letras A–Z e algarismos 2–7, como na configuração da app autenticadora)…", + "codeLabel": "Código atual", + "copyCode": "Copiar código", + "copied": "Copiado", + "nextIn": "Atualiza em {seconds} s", + "standardHint": "Passo de 30 s, 6 dígitos, HMAC-SHA-1 (compatível com Google Authenticator).", + "digitsLabel": "Dígitos", + "periodLabel": "Período", + "algorithmLabel": "Algoritmo", + "periodSeconds": "{seconds} s", + "emptyHint": "Cole um segredo para gerar um código de seis dígitos.", + "invalidHint": "Corrija o segredo para ver o código.", + "securityNote": "Trate como uma palavra-passe. O segredo não sai do browser; nada é enviado.", + "importLabel": "Importar de um URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importar", + "qrSectionTitle": "Código QR de aprovisionamento", + "qrHint": "Digitalize com uma aplicação de autenticação (Google Authenticator, 1Password, Aegis…) para adicionar este segredo. O código QR codifica o segredo — trate-o como uma palavra-passe.", + "accountLabel": "Conta", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Emissor (opcional)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Introduza um segredo válido para gerar um código QR.", + "errors": { + "invalidSecret": "Não é um segredo Base32 válido.", + "cryptoFailed": "Não foi possível calcular o TOTP. Verifique o Web Crypto e tente novamente.", + "invalidUri": "Isto não é um URI otpauth:// TOTP válido.", + "unsupportedUri": "Este URI usa valores que a ferramenta não suporta (os dígitos devem ser 6 ou 8; o período 15, 30 ou 60 s)." + } + }, + "MarkdownPreview": { + "title": "Pré-visualização Markdown & HTML", + "subtitle": "Renderizador Markdown em tempo real com exportação HTML e conversão HTML → Markdown. Tudo corre no seu navegador.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Entrada Markdown", + "htmlInputLabel": "Entrada HTML", + "previewLabel": "Pré-visualização", + "htmlOutputLabel": "Fonte HTML", + "mdOutputLabel": "Saída Markdown", + "inputPlaceholder": "# Olá\\n\\nEscreva o seu **markdown** aqui…", + "htmlInputPlaceholder": "Cole o seu HTML aqui…", + "copyHtml": "Copiar HTML", + "copyMarkdown": "Copiar Markdown", + "exportHtml": "Exportar HTML", + "copied": "Copiado", + "charCount": "{count, plural, one {# carácter} other {# caracteres}}", + "emptyHint": "Comece a escrever Markdown para ver a pré-visualização em tempo real e a saída HTML.", + "emptyHintHtmlToMd": "Cole HTML para o converter em Markdown.", + "securityNote": "Tudo é executado localmente no seu navegador. Nada é carregado." + }, + "NumberBaseConverter": { + "title": "Conversor de base numérica", + "subtitle": "Converta inteiros entre bases 2–36. Prefixos opcionais 0x / 0b / 0o. Tudo é calculado localmente no navegador.", + "inputBaseLabel": "Da base", + "outputBaseLabel": "Para a base", + "inputLabel": "Número de entrada", + "placeholderInput": "Introduza um inteiro na base de entrada selecionada…", + "outputLabel": "Valor convertido", + "copyOutput": "Copiar resultado", + "copied": "Copiado", + "swap": "Trocar bases", + "byteCountHint": "{count, plural, one {# byte} other {# bytes}} (comprimento UTF-8)", + "emptyHint": "Introduza um número para converter.", + "note": "Dígitos acima de 9 usam letras A–Z (sem distinção de maiúsculas). Espaços e sublinhados são ignorados.", + "errors": { + "invalidNumber": "Valor inválido para a base de entrada selecionada." + } + }, + "FormatConverter": { + "title": "Conversor de formatos", + "subtitle": "Converta entre JSON, YAML, TOML e XML em qualquer direção. Todas as 12 combinações. Tudo funciona no seu navegador.", + "fromLabel": "De", + "toLabel": "Para", + "swap": "Inverter", + "swapTitle": "Inverter formatos e usar saída como entrada", + "copy": "Copiar", + "copied": "Copiado", + "charCount": "{count, plural, one {# caractere} other {# caracteres}}", + "emptyHint": "Cole ou escreva a sua entrada à esquerda para ver a saída convertida.", + "localNote": "Tudo funciona localmente no seu navegador. Nada é enviado." + }, + "MimeTypeLookup": { + "title": "Pesquisa de tipo MIME", + "subtitle": "Pesquise o tipo MIME (tipo de mídia) para uma extensão ou nome de arquivo completo. Tudo funciona localmente no seu navegador.", + "inputLabel": "Extensão ou nome do arquivo", + "placeholder": "ex.: .png, package.json, photo.jpeg", + "hint": "Dica: cole uma extensão (com ou sem ponto) ou um nome de arquivo completo.", + "clear": "Limpar", + "exampleFilename": "Usar nome de arquivo de exemplo", + "exampleExtension": "Usar extensão de exemplo", + "resultLabel": "Resultado", + "normalizedLabel": "Entrada normalizada:", + "mimeTypeLabel": "Tipo MIME", + "contentTypeLabel": "Cabeçalho Content-Type", + "charsetLabel": "Conjunto de caracteres", + "copyMimeType": "Copiar tipo MIME", + "copy": "Copiar", + "copied": "Copiado", + "emptyHint": "Digite uma extensão ou nome de arquivo para pesquisar o tipo MIME.", + "notFound": "Nenhum tipo MIME encontrado para essa entrada.", + "localNote": "Tudo funciona localmente no seu navegador. Nada é enviado." + }, + "UserAgentParser": { + "title": "Parser de User-Agent", + "subtitle": "Cole uma string User-Agent (UA) e veja a decomposição de navegador, SO, engine, dispositivo e CPU. Tudo funciona localmente no seu navegador.", + "inputLabel": "String User-Agent", + "placeholder": "Cole seu User-Agent aqui…", + "hint": "Dica: você pode copiar um UA dos cabeçalhos da requisição, logs ou das devtools do navegador.", + "clear": "Limpar", + "exampleDesktop": "Usar exemplo de desktop", + "exampleMobile": "Usar exemplo de celular", + "resultLabel": "Decomposição", + "browserLabel": "Navegador", + "osLabel": "Sistema operacional", + "engineLabel": "Engine", + "deviceTypeLabel": "Tipo de dispositivo", + "deviceVendorLabel": "Fabricante do dispositivo", + "deviceModelLabel": "Modelo do dispositivo", + "cpuArchLabel": "Arquitetura da CPU", + "copyJson": "Copiar JSON", + "copy": "Copiar", + "copied": "Copiado", + "emptyHint": "Cole uma string User-Agent para ver a decomposição.", + "localNote": "Tudo funciona localmente no seu navegador. Nada é enviado." + }, + "MockDataGenerator": { + "title": "Gerador de dados fictícios", + "subtitle": "Defina colunas, escolha tipos e exporte conjuntos de dados para seeds, Postman ou testes. Até {max} linhas, tudo no navegador — nada é enviado.", + "mockarooLink": "Fluxo ao estilo Mockaroo", + "rowsLabel": "Linhas", + "rowsHint": "No máximo {max} linhas por geração para manter o separador fluido.", + "formatLabel": "Formato", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Nome da tabela SQL", + "presetsLabel": "Modelos", + "presetPlaceholder": "Carregar modelo…", + "presetDefault": "Início (utilizador)", + "presetUsers": "Utilizadores e funções", + "presetProducts": "SKU de e-commerce", + "presetApiLog": "Registo de pedidos HTTP", + "generate": "Gerar", + "warningDuplicateNames": "Nomes de coluna duplicados: colunas posteriores substituem anteriores em objetos JSON. Mude o nome.", + "schemaHeading": "Esquema", + "addField": "Adicionar coluna", + "removeField": "Remover coluna", + "duplicateField": "Duplicar coluna", + "colName": "Nome", + "colType": "Tipo", + "colOptions": "Opções", + "colBlank": "Vazio %", + "optMin": "mín", + "optMax": "máx", + "optDecimals": "dec", + "optDateFrom": "desde", + "optDateTo": "até", + "optValues": "a, b, c", + "optStep": "passo", + "optRowBase": "base", + "output": "Saída", + "outputPlaceholder": "Clique em Gerar para criar os dados…", + "copy": "Copiar", + "download": "Descarregar", + "copied": "Copiado", + "groups": { + "personal": "Pessoal", + "location": "Localização", + "internetApi": "Internet e API", + "numbersIds": "Números e IDs", + "dateTime": "Data e hora", + "textDesign": "Texto e design", + "custom": "Personalizado" + }, + "fieldTypes": { + "first_name": "Nome próprio", + "last_name": "Apelido", + "full_name": "Nome completo", + "email": "Email", + "username": "Utilizador", + "password": "Palavra-passe", + "phone": "Telefone", + "company": "Empresa", + "job_title": "Cargo", + "avatar_url": "URL do avatar", + "city": "Cidade", + "country": "País", + "country_code": "Código do país (ISO)", + "street_address": "Morada", + "zip_code": "Código postal", + "latitude": "Latitude", + "longitude": "Longitude", + "url": "URL", + "domain": "Domínio", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Inteiro", + "float": "Decimal", + "boolean": "Booleano", + "uuid": "UUID", + "credit_card": "Cartão (teste)", + "semver": "SemVer", + "row_index": "Índice da linha", + "sequence": "Sequência", + "date": "Data", + "datetime": "Data e hora", + "unix_timestamp": "Marca temporal Unix", + "timezone": "Fuso horário", + "word": "Palavra", + "sentence": "Frase", + "paragraph": "Parágrafo", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Código da moeda", + "language_code": "Etiqueta de idioma", + "custom_list": "Lista personalizada" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Otimizador SVG", + "subtitle": "Cole SVG, minifique com SVGO no browser e compare o tamanho UTF-8 antes e depois.", + "inputLabel": "Entrada", + "outputLabel": "Otimizado", + "placeholder": "Cole um trecho ou o ficheiro completo…", + "outputPlaceholder": "O SVG otimizado aparece aqui…", + "chars": "{count} caracteres • {bytes} bytes (UTF-8)", + "preview": "Pré-visualização", + "localNote": "Tudo corre localmente no browser. Nada é enviado.", + "stats": { + "title": "Tamanho (UTF-8)", + "before": "Antes", + "after": "Depois", + "savedBytes": "{count} bytes poupados ({percent}%)", + "noChange": "Sem redução — já mínimo ou saída vazia." + }, + "actions": { + "clear": "Limpar", + "sample": "Carregar exemplo", + "copy": "Copiar", + "copied": "Copiado", + "download": "Descarregar" + }, + "errors": { + "optimizeFailed": "Não foi possível otimizar este SVG." + } + }, + "UnitConverter": { + "title": "Conversor de Unidades", + "subtitle": "Converta entre 323 unidades em 43 categorias", + "category": "Categoria", + "from": "De", + "to": "Para", + "value": "Valor", + "result": "Resultado", + "swapUnits": "Inverter unidades", + "placeholder": "Introduza um valor", + "copyResult": "Copiar resultado", + "searchCategory": "Procurar categoria...", + "selectCategory": "Selecione uma categoria", + "noResults": "Nenhuma categoria encontrada." + }, + "DockerComposeGenerator": { + "title": "Gerador Docker Compose", + "subtitle": "Selecione bases de dados, caches, filas de mensagens, proxies inversos e observabilidade — copie um docker-compose.yml pronto para desenvolvimento local.", + "searchPlaceholder": "Pesquisar (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Serviços", + "selectedCount": "{count, plural, one {# serviço} other {# serviços}}", + "outputTitle": "docker-compose.yml", + "copy": "Copiar", + "copied": "Copiado!", + "download": "Transferir", + "emptyState": "Selecione um ou mais serviços para gerar o YAML do Compose.", + "hintPorts": "As portas predefinidas reduzem conflitos; ajuste se o Docker reportar porta em uso. Substitua palavras-passe de demonstração antes de ambientes partilhados.", + "categories": { + "databases": "Bases de dados e armazenamento", + "cache": "Cache e em memória", + "queues": "Filas e mensagens", + "search": "Pesquisa e registos", + "storage": "Armazenamento de objetos", + "proxy": "Proxies inversos e ingress", + "observability": "Métricas e tracing", + "mail": "E-mail (dev local)", + "admin": "Interfaces de administração e BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Limpar" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (compatível com Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (compatível com Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (compatível com S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (captura SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (servidor de desenvolvimento)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Deslize as peças e mescle-as para chegar a 2048", + "score": "Pontuação", + "best": "Cumprimentos", + "newGame": "Novo jogo ", + "howToPlay": "Use as teclas de seta (ou deslize o dedo no celular) para mover as peças. Blocos com o mesmo número são mesclados. Chegue a 2048 para ganhar!", + "youWin": "Atingiu 2048!", + "keepGoing": "Continua", + "gameOver": "Acabou o Jogo", + "tryAgain": "Tentar novamente", + "gameOverMessage": "Não há mais movimentos!", + "undo": "Desfazer" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "etapas concluídas", + "time": "Hora", + "best": "Cumprimentos", + "difficulty": { + "easy": "Fácil", + "medium": "Médio", + "hard": "Dura" + }, + "stage": "Etapa", + "solved": "Resolvido", + "score": "Pontuação", + "nextStage": "Próxima fase", + "replay": "Repetir", + "erase": "", + "howToPlay": "Clique numa célula e, em seguida, toque num número ou prima 1–9. Backspace ou Delete limpa a célula. Números em negrito são pistas. Preencha a grade para que cada linha, coluna e caixa 3×3 contenha 1–9 sem repetições.", + "pencil": "Lápis", + "undo": "Desfazer", + "hint": "Dica", + "hintsUsed": "dicas usadas", + "mistakes": "Erros", + "failed": "Falhou!", + "mistakeMode": "Modo 3 erros" + }, + "Snake": { + "title": "Cobra", + "subtitle": "Coma comida, cresça, evite paredes e a si mesmo", + "score": "Pontuação", + "best": "Melhor", + "startPrompt": "Pressione qualquer tecla de seta", + "startButton": "Iniciar jogo", + "gameOver": "Fim de jogo", + "playAgain": "Jogar de novo", + "howToPlay": "Use as teclas de seta ou WASD. Coma para crescer. A velocidade aumenta a cada 50 pontos.", + "diffEasy": "Fácil", + "diffNormal": "Normal", + "diffHard": "Difícil", + "diffInfinite": "Infinito", + "orTap": "ou toque abaixo", + "howToPlayInfinite": "As paredes se enrolam — sem mortes por paredes. Coma para crescer.", + "shieldActive": "Escudo ativo", + "speedBoost": "Impulso de velocidade", + "powerSpeed": "Velocidade x2", + "powerShield": "Sobrevive uma morte", + "powerShrink": "Encurta a cauda" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Ligações Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Ligue-se a instâncias Redis. Navegue por chaves, edite valores e execute comandos. As credenciais são encriptadas no navegador.", + "heroAddConnection": "Adicionar ligação", + "vaultLockedTitle": "Cofre bloqueado", + "vaultLockedDesc": "Desbloqueie o cofre para aceder às ligações guardadas." + } + }, + "Minesweeper": { + "title": "Campo minado", + "subtitle": "Encontre todas as células seguras sem detonar uma mina", + "mines": "Minas", + "newGame": "Novo jogo", + "win": "Venceu!", + "loss": "Perdeu!", + "playAgain": "Jogar novamente", + "tryAgain": "Tentar novamente", + "startPrompt": "Clique em qualquer célula para começar", + "difficulty": { + "beginner": "Iniciante", + "intermediate": "Intermédio", + "expert": "Especialista" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Organize os blocos que caem para completar linhas", + "next": "Próximo", + "score": "Pontuação", + "best": "Melhor", + "lines": "Linhas", + "level": "Nível", + "pause": "Pausar", + "resume": "Retomar", + "start": "Iniciar", + "restart": "Reiniciar", + "paused": "Pausado", + "gameOver": "Fim de jogo" + }, + "SshKeyGenerator": { + "title": "Gerador de chaves SSH / RSA", + "subtitle": "Gere pares de chaves Ed25519 ou RSA no seu navegador. Faça download das chaves privada e pública nos formatos OpenSSH e PEM. Nada é enviado.", + "keyTypeLabel": "Tipo de chave", + "recommended": "recomendado", + "ed25519Desc": "Rápido, pequeno e moderno. Suportado pelo GitHub, GitLab e todos os principais servidores SSH.", + "rsa2048Desc": "Amplamente compatível. Seguro para a maioria dos usos.", + "rsa4096Desc": "Máxima compatibilidade e força para sistemas legados.", + "commentLabel": "Comentário (opcional)", + "commentHint": "Adicionado à linha de chave pública OpenSSH.", + "generateBtn": "Gerar par de chaves", + "generating": "A gerar…", + "generatingLong": "A gerar (pode demorar um momento)…", + "copy": "Copiar", + "download": "Descarregar", + "privateKeyLabel": "Chave privada (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Chave pública (formato OpenSSH)", + "publicKeySpkiLabel": "Chave pública (SPKI PEM)", + "browserNote": "Todas as chaves são geradas no seu navegador. Nada é enviado para nenhum servidor.", + "errors": { + "generationFailed": "Falha ao gerar a chave" + } + }, + "DnsLookup": { + "title": "Pesquisa DNS", + "subtitle": "Consulte registros DNS de qualquer domínio — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Introduza um domínio (ex.: github.com)", + "lookupBtn": "Consultar", + "looking": "A consultar…", + "refreshBtn": "Atualizar", + "recordTypesLabel": "Tipos de registo", + "allBtn": "Todos", + "tryLabel": "Experimente:", + "errors": { + "lookupFailed": "A consulta DNS falhou" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Gerador e verificador Bcrypt", + "subtitle": "Gere hashes de palavras-passe com bcrypt e verifique hashes — tudo permanece no seu browser", + "tabs": { + "hash": "Hash", + "verify": "Verificar" + }, + "showPassword": "Mostrar palavra-passe", + "hidePassword": "Ocultar palavra-passe", + "copy": "Copiar", + "copied": "Copiado", + "hash": { + "passwordLabel": "Palavra-passe", + "passwordPlaceholder": "Introduza uma palavra-passe para gerar o hash…", + "costLabel": "Fator de custo", + "costHint": "Um custo mais alto duplica o trabalho por passo. 10-12 é uma escolha comum em produção.", + "slowWarning": "Fatores de custo de 13 ou superiores podem demorar vários segundos a calcular.", + "generate": "Gerar hash", + "generating": "A gerar hash…", + "resultLabel": "Hash bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Anatomia do hash", + "algorithm": "algoritmo", + "cost": "custo", + "salt": "salt (22 caracteres)", + "hash": "hash (31 caracteres)" + } + }, + "verify": { + "passwordLabel": "Palavra-passe", + "passwordPlaceholder": "Introduza a palavra-passe a verificar…", + "hashLabel": "Hash bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Introduza uma palavra-passe e um hash bcrypt para os comparar.", + "invalidHash": "Isto não parece um hash bcrypt válido.", + "checking": "A comparar…", + "match": "Corresponde — a palavra-passe produz este hash.", + "noMatch": "Não corresponde — a palavra-passe não produz este hash." + } + }, + "ChmodCalculator": { + "title": "Calculadora Chmod", + "subtitle": "Converta permissões de ficheiros Unix entre caixas de seleção, octal e notação simbólica", + "grid": { + "title": "Permissões", + "read": "Leitura", + "write": "Escrita", + "execute": "Execução", + "owner": "Proprietário", + "group": "Grupo", + "others": "Outros" + }, + "special": { + "title": "Bits especiais", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Octal", + "placeholder": "755 ou 4755", + "invalid": "Introduza 3 ou 4 dígitos octais (0-7)." + }, + "symbolic": { + "label": "Simbólico", + "placeholder": "rwxr-xr-x", + "invalid": "Introduza 9 caracteres como rwxr-xr-x (s/S, t/T permitidos)." + }, + "output": { + "title": "Comando", + "copy": "Copiar", + "copied": "Copiado" + }, + "explain": { + "line": "{cls} pode {perms}.", + "none": "{cls} não tem permissões.", + "setuid": "Setuid: o ficheiro é executado com os privilégios do proprietário.", + "setgid": "Setgid: o ficheiro é executado com os privilégios do seu grupo (os ficheiros novos numa diretoria herdam o seu grupo).", + "sticky": "Sticky: apenas o proprietário de um ficheiro o pode eliminar ou renomear dentro desta diretoria." + }, + "presets": { + "title": "Predefinições comuns", + "octal": "Octal", + "permissions": "Permissões", + "description": "Uso típico", + "p644": "Ficheiros normais: o proprietário escreve, todos leem.", + "p755": "Executáveis e diretorias: todos podem ler e entrar.", + "p600": "Ficheiros privados: apenas o proprietário lê e escreve.", + "p700": "Executáveis e diretorias privados: apenas o proprietário.", + "p664": "Ficheiros com escrita para o grupo.", + "p775": "Diretorias e executáveis com escrita para o grupo.", + "p400": "Apenas leitura para o proprietário (ex.: chaves privadas SSH)." + } + }, + "CodeScreenshot": { + "title": "Captura de código", + "subtitle": "Transforme código em imagens bonitas e fáceis de partilhar", + "languageLabel": "Linguagem", + "autoDetect": "Deteção automática", + "backgroundLabel": "Fundo", + "paddingLabel": "Espaçamento", + "filenameLabel": "Nome do ficheiro", + "filenamePlaceholder": "example.ts", + "windowLabel": "Pontos da janela", + "themeLabel": "Tema da janela", + "themeDark": "Escuro", + "themeLight": "Claro", + "scaleLabel": "Escala de exportação", + "copyImage": "Copiar imagem", + "copied": "Copiado", + "exportPng": "Exportar PNG", + "inputLabel": "Código", + "detected": "Detetado: {language}", + "clear": "Limpar", + "placeholder": "Cole o seu código aqui…", + "previewLabel": "Pré-visualização", + "emptyPreview": "A pré-visualização do seu código aparecerá aqui", + "exportFailed": "Falha ao exportar a imagem", + "copyUnsupported": "Copiar imagens não é suportado neste browser", + "copyFailed": "Falha ao copiar a imagem", + "backgrounds": { + "ocean": "Oceano", + "sunset": "Pôr do sol", + "candy": "Rebuçado", + "forest": "Floresta", + "midnight": "Meia-noite", + "gold": "Ouro", + "aurora": "Aurora", + "slate": "Ardósia", + "transparent": "Transparente" + } + }, + "CssGenerators": { + "title": "Geradores CSS", + "subtitle": "Box-shadow, border-radius e clamp() fluido — copie o CSS.", + "preview": "Pré-visualização", + "output": "CSS", + "copy": "Copiar", + "copied": "Copiado", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() fluido" + }, + "boxShadow": { + "presets": "Predefinições", + "layer": "Camada", + "addLayer": "Adicionar camada", + "removeLayer": "Remover camada", + "x": "X", + "y": "Y", + "blur": "Desfoque", + "spread": "Expansão", + "color": "Cor", + "alpha": "Alfa", + "inset": "Interior", + "presetNames": { + "subtleCard": "Cartão subtil", + "elevated": "Elevado", + "hardOffset": "Deslocamento nítido", + "neonGlow": "Brilho néon", + "inner": "Interior", + "layeredSmooth": "Camadas suaves" + } + }, + "borderRadius": { + "modeLabel": "Modo", + "modes": { + "simple": "Por canto", + "blob": "Blob orgânico" + }, + "unit": "Unidade", + "linkCorners": "Ligar cantos", + "randomize": "Aleatorizar", + "horizontal": "Raios horizontais", + "vertical": "Raios verticais", + "corners": { + "tl": "Superior esquerdo", + "tr": "Superior direito", + "br": "Inferior direito", + "bl": "Inferior esquerdo" + } + }, + "clamp": { + "minFont": "Fonte mín. (px)", + "maxFont": "Fonte máx. (px)", + "minViewport": "Viewport mín. (px)", + "maxViewport": "Viewport máx. (px)", + "rootFont": "Fonte raiz (px)", + "viewportWidth": "Viewport simulado", + "computedSize": "Tamanho calculado", + "slope": "Declive", + "intercept": "Interceção", + "previewText": "Um pequeno jabuti xereta", + "invalid": "Introduza limites de viewport válidos e diferentes." + } + }, + "ExifViewer": { + "title": "Visualizador e removedor EXIF", + "subtitle": "Inspecione os metadados das fotos e remova-os no seu browser", + "dropTitle": "Largue uma imagem aqui", + "dropHint": "ou clique para procurar — a sua imagem nunca sai do seu dispositivo", + "browse": "Escolher imagem", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "A ler metadados…", + "parseFailed": "Falha ao ler esta imagem", + "clear": "Remover imagem", + "gpsWarning": "Contém localização GPS", + "openMap": "Ver no OpenStreetMap", + "noMetadata": "Não foram encontrados metadados incorporados nesta imagem.", + "facts": { + "name": "Nome", + "type": "Tipo", + "dimensions": "Dimensões", + "size": "Tamanho" + }, + "groups": { + "camera": "Câmara", + "exposure": "Exposição", + "timestamps": "Datas/horas", + "gps": "Localização GPS", + "software": "Software e outros" + }, + "gpsCard": { + "latitude": "Latitude", + "longitude": "Longitude", + "altitude": "Altitude" + }, + "raw": { + "title": "Todos os metadados", + "copyJson": "Copiar JSON", + "copied": "Copiado" + }, + "remove": { + "title": "Remover metadados", + "jpegDescription": "Remova sem perdas EXIF, XMP, IPTC e comentários sem recomprimir a imagem.", + "reencodeDescription": "Recodifique a imagem para remover os metadados incorporados. Isto recomprime os pixels.", + "keepIcc": "Manter o perfil de cor (ICC)", + "stripButton": "Remover metadados", + "reencodeButton": "Recodificar e limpar", + "unsupported": "A remoção de metadados não é suportada para este tipo de ficheiro.", + "failed": "Falha ao remover os metadados", + "before": "Antes", + "after": "Depois", + "download": "Descarregar imagem limpa" + } + }, + "FaviconGenerator": { + "title": "Gerador de favicon", + "subtitle": "Crie favicons e ícones de app a partir de uma única imagem", + "dropTitle": "Largue uma imagem aqui", + "dropHint": "PNG, JPEG ou SVG — clique para procurar", + "browse": "Escolher imagem", + "clear": "Remover imagem", + "backgroundLabel": "Fundo", + "transparent": "Transparente", + "solidColor": "Sólido", + "cornerLabel": "Forma dos cantos", + "cornerNone": "Quadrado", + "cornerRounded": "Arredondado", + "cornerCircle": "Círculo", + "maskPaddingLabel": "Espaçamento maskable", + "maskable": "maskable", + "previewLabel": "Pré-visualização", + "copy": "Copiar", + "headSnippet": "Fragmento para o head HTML", + "downloadIco": "Descarregar favicon.ico", + "downloadZip": "Descarregar tudo (.zip)" + }, + "JsonDiff": { + "title": "Diff JSON", + "subtitle": "Comparação semântica e sensível à estrutura de dois documentos JSON — ordem das chaves e formatação ignoradas", + "options": { + "ignoreArrayOrder": "Ignorar a ordem dos arrays" + }, + "loadBothSamples": "Carregar exemplos", + "sample": "Carregar exemplo", + "format": "Formatar", + "clear": "Limpar", + "formatError": "Não é possível formatar: a entrada não é um JSON válido", + "placeholder": "Cole ou escreva JSON aqui…", + "panels": { + "left": "Original (esquerda)", + "right": "Modificado (direita)", + "results": "Diferenças" + }, + "summary": { + "added": "{count} adicionados", + "removed": "{count} removidos", + "changed": "{count} alterados" + }, + "types": { + "added": "Adicionado", + "removed": "Removido", + "changed": "Alterado" + }, + "copyChanges": "Copiar como JSON", + "copied": "Copiado", + "empty": "Cole JSON em ambos os painéis para ver as suas diferenças.", + "identical": "Os dois documentos são semanticamente idênticos." + }, + "JsonpathPlayground": { + "title": "Playground JSONPath", + "subtitle": "Consulte documentos JSON com JSONPath ou JMESPath e veja os resultados em tempo real", + "engineLabel": "Motor", + "queryLabel": "Consulta", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Referência rápida", + "cheats": { + "jpAllTitles": "Todos os títulos de livros", + "jpAllAuthors": "Todos os autores, em qualquer parte do documento", + "jpFirstBook": "Primeiro livro", + "jpLastBook": "Último livro", + "jpSlice": "Os dois primeiros livros (slice)", + "jpFilterPrice": "Livros abaixo de 40", + "jpFilterInStock": "Títulos dos livros em stock", + "jpRecursive": "Todos os valores, recursivamente", + "jmAllTitles": "Todos os títulos de livros", + "jmFirstAuthor": "Autor do primeiro livro", + "jmLastBook": "Último livro", + "jmSlice": "Os dois primeiros livros (slice)", + "jmFilterPrice": "Títulos dos livros abaixo de 40", + "jmCount": "Contar livros em stock", + "jmProjection": "Reformular cada livro num novo objeto", + "jmSort": "Títulos ordenados por preço" + }, + "panels": { + "input": "Entrada JSON", + "results": "Resultados" + }, + "sample": "Carregar exemplo", + "format": "Formatar", + "clear": "Limpar", + "formatError": "Não é possível formatar: a entrada não é um JSON válido", + "placeholder": "Cole ou escreva JSON aqui…", + "outputPlaceholder": "As correspondências aparecerão aqui…", + "matchCount": "{count, plural, one {# correspondência} other {# correspondências}}", + "copy": "Copiar", + "copied": "Copiado" + }, + "KeycodeInspector": { + "title": "Inspetor de keycode", + "subtitle": "Prima qualquer tecla para inspecionar o seu evento de teclado JavaScript", + "capture": { + "prompt": "Prima qualquer tecla…", + "ready": "A escutar — prima qualquer tecla no seu teclado", + "clickToFocus": "Clique aqui para focar e depois prima qualquer tecla" + }, + "hero": { + "spaceKey": "(Espaço)", + "keyLabel": "event.key", + "repeat": "repetição" + }, + "copyJson": "Copiar JSON", + "copiedJson": "JSON do evento copiado para a área de transferência", + "deprecated": "obsoleto", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Padrão", + "left": "Esquerda", + "right": "Direita", + "numpad": "Teclado numérico", + "unknown": "Desconhecido" + }, + "history": { + "title": "Histórico", + "clear": "Limpar histórico", + "empty": "As teclas premidas aparecerão aqui." + } + }, + "MarkdownTableGenerator": { + "title": "Gerador de tabelas Markdown", + "subtitle": "Edite uma grelha, cole de uma folha de cálculo e copie uma tabela Markdown ou HTML bem alinhada", + "addRow": "Adicionar linha", + "addColumn": "Adicionar coluna", + "importLabel": "Importar", + "importPlaceholder": "Cole TSV (do Excel / Google Sheets) ou CSV aqui…", + "importButton": "Importar para a grelha", + "pasteClipboard": "Colar da área de transferência", + "importEmpty": "Nada para importar — cole primeiro algumas linhas", + "clipboardError": "Não foi possível ler a área de transferência", + "clear": "Limpar", + "panels": { + "editor": "Editor de tabelas", + "output": "Saída" + }, + "align": { + "left": "Alinhar à esquerda", + "center": "Centrar", + "right": "Alinhar à direita" + }, + "headerPlaceholder": "Cabeçalho", + "cellPlaceholder": "Célula", + "removeRow": "Remover linha", + "removeColumn": "Remover coluna", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Copiar", + "copied": "Copiado" + }, + "TimezoneConverter": { + "title": "Conversor de fusos horários", + "subtitle": "Converta um momento por todo o mundo para planear reuniões.", + "dateTimeLabel": "Data e hora", + "now": "Agora", + "sourceZoneLabel": "Fuso horário de origem", + "selectZone": "Selecionar fuso", + "searchZone": "Procurar fusos horários...", + "noZone": "Nenhum fuso horário encontrado.", + "scrubLabel": "Deslocar a hora", + "reset": "Repor", + "targetsLabel": "Fusos de destino", + "addZone": "Adicionar fuso", + "remove": "Remover", + "localBadge": "Local", + "invalidDate": "Introduza uma data e hora válidas.", + "bands": { + "work": "Horário de trabalho (09:00–18:00)", + "edge": "Horas limite (cedo / noite)", + "night": "Noite" + } + }, + "TokenCounter": { + "title": "Contador de tokens de LLM", + "subtitle": "Conte tokens, visualize chunks e estime o custo da API.", + "inputLabel": "Texto", + "sample": "Carregar exemplo", + "clear": "Limpar", + "placeholder": "Cole texto, código ou JSON para contar tokens...", + "encoding": "Codificação", + "loading": "A carregar o tokenizador...", + "encodings": { + "o200k": "o200k_base (GPT-4o / série o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Caracteres", + "words": "Palavras", + "ratio": "Tokens/palavra", + "ratioSub": "aprox." + }, + "claudeRow": "Claude (aproximado)", + "estimate": "estimativa", + "viz": { + "title": "Visualização de tokens", + "toggle": "Mostrar", + "hint": "Ative para colorir cada token como um chunk.", + "empty": "Introduza texto para visualizar os tokens.", + "truncated": "A mostrar os primeiros {limit} de {total} tokens." + }, + "cost": { + "title": "Calculadora de custos", + "disclaimer": "valores predefinidos editáveis — verifique os preços atuais", + "model": "Modelo", + "expectedOutput": "Tokens de saída esperados", + "customInput": "Entrada $/MTok", + "customOutput": "Saída $/MTok", + "inputCost": "Custo de entrada", + "outputCost": "Custo de saída", + "total": "Total", + "per1000": "Por 1000 chamadas" + } + }, + "WebhookTester": { + "title": "Testador de webhooks", + "subtitle": "Crie um URL temporário e inspecione os pedidos de webhook recebidos em tempo real.", + "intro": { + "title": "Crie um URL de webhook para começar a capturar pedidos", + "hint": "Vai obter um URL indecifrável que regista cada pedido recebido. Expira ao fim de 24 horas." + }, + "createBtn": "Criar URL de webhook", + "creating": "A criar…", + "urlLabel": "O seu URL de webhook", + "expiresIn": "Expira em {countdown}", + "copyUrlBtn": "Copiar URL", + "copied": "Copiado", + "clearBtn": "Limpar", + "newBinBtn": "Novo URL", + "requestsTitle": "Pedidos", + "selectPrompt": "Selecione um pedido para inspecionar os seus detalhes.", + "expiredNotice": "Este URL de webhook expirou. Crie um novo para continuar a testar.", + "base64Badge": "base64", + "truncatedBadge": "truncado", + "empty": { + "title": "À espera de pedidos…", + "hint": "Envie um pedido para o seu URL e ele aparecerá aqui instantaneamente. Experimente isto:" + }, + "detail": { + "receivedAt": "Recebido", + "source": "IP de origem", + "contentType": "Content-Type", + "query": "Parâmetros de consulta", + "headers": "Cabeçalhos", + "body": "Corpo", + "copyBody": "Copiar corpo", + "emptyBody": "Sem corpo" + }, + "errors": { + "createFailed": "Falha ao criar o URL de webhook. Tente novamente.", + "clearFailed": "Falha ao limpar os pedidos. Tente novamente." + } + }, + "WebsocketTester": { + "title": "Testador de WebSocket", + "subtitle": "Ligue-se a um endpoint WebSocket e envie, receba e inspecione mensagens em tempo real", + "urlLabel": "URL do endpoint", + "protocolsLabel": "Subprotocolos", + "protocolsPlaceholder": "opcional, separados por vírgulas", + "connect": "Ligar", + "disconnect": "Desligar", + "status": { + "disconnected": "Desligado", + "connecting": "A ligar", + "connected": "Ligado", + "error": "Erro" + }, + "messages": { + "connecting": "A ligar a {url}…", + "connected": "Ligação estabelecida", + "connectedWithProtocol": "Ligação estabelecida (protocolo: {protocol})", + "invalidUrl": "URL inválido — deve começar por ws:// ou wss://", + "connectFailed": "Falha ao abrir a ligação", + "socketError": "Ocorreu um erro de ligação", + "notConnected": "Não ligado — ligue-se primeiro", + "binaryMessage": "Recebida uma mensagem binária" + }, + "log": { + "title": "Registo de mensagens", + "empty": "Ainda não há mensagens. Ligue-se e comece a enviar.", + "copyAll": "Copiar tudo", + "download": "Descarregar como .txt", + "clear": "Limpar registo", + "prettyPrint": "Formatar JSON", + "showRaw": "Mostrar texto simples" + }, + "composer": { + "placeholder": "Escreva uma mensagem para enviar…", + "send": "Enviar", + "formatJson": "Formatar como JSON", + "invalidJson": "A mensagem não é um JSON válido", + "hint": "Cmd/Ctrl+Enter para enviar" + }, + "ping": { + "label": "Auto-ping", + "seconds": "s", + "intervalTitle": "Intervalo de ping em segundos", + "payloadPlaceholder": "payload do ping" + } + }, + "WhoisLookup": { + "title": "Pesquisa Whois", + "subtitle": "Consulte os dados de registo de qualquer domínio ou endereço IP através de RDAP", + "placeholder": "example.com ou 8.8.8.8", + "lookupBtn": "Pesquisar", + "looking": "A pesquisar…", + "refreshBtn": "Atualizar", + "tryLabel": "Experimente:", + "copy": "Copiar", + "copied": "Copiado", + "notAvailable": "Não disponível", + "summary": { + "registrar": "Registrar", + "network": "Rede" + }, + "dates": { + "created": "Criado", + "updated": "Atualizado", + "expires": "Expira" + }, + "sections": { + "status": "Estado", + "nameservers": "Servidores de nomes", + "contacts": "Contactos", + "rawRdap": "RDAP em bruto" + }, + "contacts": { + "roles": "Funções", + "name": "Nome", + "organization": "Organização" + }, + "errors": { + "invalidTarget": "Introduza um nome de domínio ou endereço IP válido", + "lookupFailed": "Pesquisa falhou (estado {status})" + } + } +} diff --git a/apps/desktop-ui/messages/ru.json b/apps/desktop-ui/messages/ru.json new file mode 100644 index 00000000..a7c027bc --- /dev/null +++ b/apps/desktop-ui/messages/ru.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Панель управления", + "settings": "Настройки", + "apiClient": "API-клиент", + "httpStatusCodes": "Коды состояния HTTP", + "passwordManager": "Менеджер паролей", + "environmentManager": "Менеджер окружений", + "bookmarks": "Закладки", + "jsonFormatter": "Форматировщик JSON", + "emailValidator": "Валидатор email", + "nosqlExplorer": "Обозреватель баз данных", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Песочница шифрования", + "jwtDecoder": "Декодер JWT", + "certificatePemDecoder": "Декодер сертификата / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Форматтер GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL-парсер", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Секрет / ключ API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Проверка контраста", + "jsonSchemaGenerator": "Генератор JSON-схемы", + "imageToBase64": "Изображение в Base64", + "imageCompressor": "Сжатие изображений", + "cssGradientBuilder": "Конструктор CSS-градиентов", + "gitignoreGenerator": "Генератор .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Фрагменты кода", + "qrCodeGenerator": "Генератор QR-кодов", + "dnsLookup": "DNS-запрос", + "ipSubnetCalculator": "Калькулятор IP / подсети", + "hashGenerator": "Генератор хешей", + "hmacGenerator": "Генератор HMAC", + "totpGenerator": "Код TOTP / 2FA", + "markdownPreview": "Просмотр Markdown", + "numberBaseConverter": "Конвертер систем", + "formatConverter": "Конвертер форматов", + "mockDataGenerator": "Генератор тестовых данных", + "svgOptimizer": "Оптимизатор SVG", + "unitConverter": "Конвертер единиц", + "dockerComposeGenerator": "Генератор Docker Compose", + "breakRoom": "Комната отдыха", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Форматировщик YAML", + "sshKeyGenerator": "Генератор ключей SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Калькулятор Chmod", + "codeScreenshot": "Скриншот кода", + "cssGenerators": "Генераторы CSS", + "exifViewer": "Просмотр EXIF", + "faviconGenerator": "Генератор favicon", + "jsonDiff": "Сравнение JSON", + "jsonpathPlayground": "Песочница JSONPath", + "keycodeInspector": "Инспектор клавиш", + "markdownTableGenerator": "Таблица Markdown", + "timezoneConverter": "Конвертер часовых поясов", + "tokenCounter": "Счётчик токенов", + "webhookTester": "Тестер вебхуков", + "websocketTester": "Тестер WebSocket", + "whoisLookup": "Whois-запрос" + }, + "Help": { + "title": "Справка и документация", + "subtitle": "Как устроен MyDevTools, что делает каждое приложение и как безопасность и шифрование защищают ваши данные.", + "tabs": { + "overview": "Обзор", + "apps": "Приложения", + "security": "Безопасность", + "tips": "Советы" + }, + "overview": { + "whatIs": { + "title": "Что такое MyDevTools?", + "description": "Рабочее пространство разработчика с входом: панель и набор инструментов на каждый день.", + "p1": "После входа приложение устанавливает сессию с API MyDevTools, чтобы инструменты могли загружать и сохранять ваши данные. Боковая панель показывает все приложения; ненужные инструменты можно скрыть в", + "settingsLink": "Настройках", + "p1After": ".", + "p2": "Некоторые функции используют мастер‑пароль (или глобальный ключ хранилища), чтобы секреты — например, пароли и строки подключения — шифровались на вашем устройстве до отправки на сервер." + }, + "quickLinks": { + "title": "Быстрые ссылки", + "dashboard": "Панель управления", + "settings": "Настройки" + } + }, + "apps": { + "intro": "Откройте любой инструмент из боковой панели или панели управления. Ниже — краткая справка по каждому приложению.", + "open": "Открыть", + "howItWorks": "Как работает", + "generic": "Используйте этот инструмент через боковую панель — там находится его основной рабочий процесс." + }, + "security": { + "session": { + "title": "Вход и API‑сессия", + "description": "Идентификация и то, как браузер взаимодействует с бэкендом.", + "items": { + "authentication": "Аутентификация выполняется через Firebase. После входа приложение обменивает Firebase ID‑токен на cookies API.", + "accessTokens": "Access‑токены — это JWT, подписанные на сервере (HS256), которые подтверждают пользователя при вызове API.", + "refreshTokens": "Refresh‑токены хранятся в HttpOnly cookies; сервер сохраняет SHA‑256 хэш refresh‑токена, а не сам токен." + } + }, + "encryption": { + "title": "Важное о шифровании", + "description": "Криптография на стороне клиента для хранилища и чувствительных данных подключений.", + "p1": "Для менеджера паролей, менеджера окружений и сохранённых подключений в NoSQL Explorer веб-приложение использует Web Crypto API браузера:", + "items": { + "kdf": "PBKDF2 с SHA‑256, 100 000 итераций и случайной солью выводит 256‑битный ключ из мастер‑пароля.", + "aes": "AES‑256‑GCM шифрует данные; для каждого шифрования используется случайный IV 12 байт.", + "scope": "Производный ключ остаётся в браузере (не экспортируется). Сервер получает только шифртекст и IV, а не мастер‑пароль или секреты в открытом виде." + }, + "warning": "Если вы забудете мастер‑пароль, зашифрованные данные восстановить нельзя. Храните его в безопасности." + }, + "logout": { + "title": "Выход и локальные данные", + "body": "Выход очищает секреты в памяти, состояние хранилища и (если применимо) материал мастер‑ключа в IndexedDB, а также завершает API‑сессию. Данные, сохранённые только в локальном хранилище устройства, могут оставаться до очистки в браузере." + } + }, + "tips": { + "title": "Безопасное использование MyDevTools", + "items": { + "masterPassword": "Используйте сильный и уникальный мастер‑пароль для функций хранилища.", + "apiClient": "Относитесь к API‑клиенту как к любому HTTP‑клиенту: не вставляйте прод‑секреты в общие окружения, если не понимаете, кто может их увидеть.", + "nosql": "Обозреватель баз данных подключается к базам, которые вы настраиваете; используйте учётные записи с минимальными правами и сетевые ограничения по возможности.", + "json": "JSON‑форматировщик работает локально в браузере — всё равно избегайте вставки сверхчувствительных данных на общих устройствах.", + "settings": "Настройте видимые инструменты в Настройках, чтобы боковая панель оставалась компактной." + } + } + }, + "Settings": { + "title": "Настройки", + "language": "Язык", + "spanish": "Испанский", + "english": "English", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalan", + "chineseSimplified": "Китайский (упрощённый)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Немецкий", + "danish": "Датский", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Польский", + "vietnamese": "Tiếng Việt", + "norwegian": "Norsk", + "ukrainian": "Украинский" + }, + "SettingsPage": { + "title": "Настройки", + "subtitle": "Управление настройками учётной записи и предпочтениями.", + "userProfile": { + "title": "Профиль пользователя", + "description": "Ваша личная информация и данные учётной записи.", + "avatarAlt": "Пользователь", + "anonymousUser": "Анонимный пользователь", + "noEmailProvided": "Email не указан", + "notLoggedIn": "Не авторизован." + }, + "tools": { + "title": "Инструменты и функции", + "description": "Выберите, какие инструменты отображать в боковой панели навигации." + }, + "appearance": { + "title": "Внешний вид", + "description": "Настройте внешний вид приложения на вашем устройстве.", + "themePreference": "Тема оформления", + "themes": { + "light": "Светлая", + "dark": "Тёмная", + "system": "Системная" + } + }, + "language": { + "title": "Язык", + "description": "Установите язык, используемый на платформе.", + "selectLanguage": "Выберите язык", + "selectPlaceholder": "Выберите язык", + "helpText": "Смена языка обновит меню и надписи по всей платформе.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Испанский)", + "ar": "عربى (Арабский)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Греческий)", + "de": "Deutsch (Немецкий)", + "da": "Dansk (Датский)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Итальянский (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski (Польский)", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Доброе утро", + "afternoon": "Добрый день", + "evening": "Добрый вечер" + }, + "commaName": ", {name}", + "welcomeBack": "С возвращением", + "welcomeBackNamed": "С возвращением, {name}", + "tagline": "Что вы хотите создать сегодня?", + "brandName": "MyDevTools", + "stats": { + "tools": "Инструменты", + "favorites": "Избранное", + "pinned": "Закреплённые", + "recent": "Недавно использованные" + }, + "sections": { + "recentlyUsed": "Недавно использованные", + "favorites": "Избранное", + "whatsNew": "Что нового", + "pinned": "Закреплённые" + }, + "groups": { + "apps": "Приложения" + }, + "toolCard": { + "defaultDescription": "Попробуйте этот инструмент для расширения возможностей.", + "launchTool": "Запустить" + }, + "tools": { + "toDo": { + "title": "Задачи", + "description": "Эффективно управляйте списками задач." + }, + "notes": { + "title": "Заметки", + "description": "Фиксируйте идеи и систематизируйте мысли." + }, + "passwordManager": { + "title": "Менеджер паролей", + "description": "Безопасно храните и управляйте паролями." + }, + "environmentManager": { + "title": "Менеджер окружений", + "description": "Храните переменные в стиле .env по проекту и окружению с тегами." + }, + "bookmarks": { + "title": "Закладки", + "description": "Управляйте и систематизируйте закладки." + }, + "apiClient": { + "title": "API-клиент", + "description": "Тестируйте и отлаживайте HTTP-запросы." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Исследуйте базы данных MongoDB." + }, + "emailValidator": { + "title": "Валидатор email", + "description": "Проверяйте и валидируйте адреса электронной почты." + }, + "encryptionPlayground": { + "title": "Песочница шифрования", + "description": "AES-GCM с сырым ключом или паролем; JSON-пакеты, Web Crypto только в браузере." + }, + "jwtDecoder": { + "title": "Декодер JWT", + "description": "Декодируйте заголовок, полезную нагрузку и срок действия JWT." + }, + "certificatePemDecoder": { + "title": "Декодер сертификата / PEM", + "description": "Декодируйте сертификаты X.509 и CSR: субъект, издатель, срок действия, SAN." + }, + "sshKeyGenerator": { + "title": "Генератор ключей SSH / RSA", + "description": "Генерируйте пары ключей Ed25519 или RSA в браузере. Скачивайте в форматах OpenSSH и PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Форматтер GraphQL", + "description": "Форматируйте GraphQL с подсветкой синтаксиса и простым конструктором запросов." + }, + "yamlFormatter": { + "title": "Форматировщик YAML", + "description": "Форматируйте, проверяйте и конвертируйте YAML в JSON в браузере." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL-парсер", + "description": "Разберите URL на протокол, хост, путь, параметры запроса и хэш." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Секрет / ключ API", + "description": "Криптографически случайные строки с настраиваемым алфавитом, длиной и массовым экспортом." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Проверка контраста", + "description": "Коэффициент контраста WCAG 2.1 для текста и фона (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Генератор JSON-схемы", + "description": "Получите JSON-схему и типизированные модели из примера JSON." + }, + "imageToBase64": { + "title": "Изображение в Base64", + "description": "Преобразуйте изображения в URI данных или необработанные строки Base64." + }, + "imageCompressor": { + "title": "Сжатие изображений", + "description": "Сжимайте JPEG, PNG и WebP локально с ползунком качества." + }, + "cssGradientBuilder": { + "title": "Конструктор CSS-градиентов", + "description": "Визуальный конструктор градиентов CSS с контролем угла и остановками цвета." + }, + "gitignoreGenerator": { + "title": "Генератор .gitignore", + "description": "Мгновенно создавайте объединенные файлы .gitignore из более чем 500 технологических стеков." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Конвертируйте CSV или Excel в JSON и экспортируйте обратно в CSV или XLSX." + }, + "snippetManager": { + "title": "Фрагменты кода", + "description": "Сохраняйте сниппеты с подсветкой синтаксиса, автоопределением языка и форматированием." + }, + "qrCodeGenerator": { + "title": "Генератор QR-кодов", + "description": "Создавайте PNG QR-коды из текста или URL в браузере." + }, + "ipSubnetCalculator": { + "title": "Калькулятор IP / подсети", + "description": "IPv4 и IPv6 CIDR: маска, диапазоны и число адресов." + }, + "dnsLookup": { + "title": "DNS-запрос", + "description": "Запрашивайте DNS-записи для любого домена — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Генератор хешей", + "description": "MD5, SHA-1 и SHA-2 для текста или файлов." + }, + "hmacGenerator": { + "title": "Генератор HMAC", + "description": "Подписывайте вебхуки и тестируйте интеграции API с HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Генератор кода TOTP / 2FA", + "description": "Вставьте секрет Base32 из приложения-аутентификатора; шестизначный код обновляется каждые 30 с—удобно для проверки входа и MFA." + }, + "markdownPreview": { + "title": "Просмотр Markdown", + "description": "Живой рендерер Markdown с экспортом HTML и конвертацией HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Конвертер систем счисления", + "description": "Перевод целых чисел между основаниями 2–36." + }, + "formatConverter": { + "title": "Конвертер форматов", + "description": "Конвертация между JSON, YAML, TOML и XML в любом направлении. Поддерживаются все 12 комбинаций." + }, + "mockDataGenerator": { + "title": "Генератор тестовых данных", + "description": "Задайте столбцы, выберите типы и экспортируйте данные для сидов, Postman или тестов. До 5000 строк, всё в браузере — ничего не загружается на сервер." + }, + "httpStatusCodes": { + "title": "HTTP-коды состояния", + "description": "Поиск HTTP-кодов состояния с описаниями (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Оптимизатор SVG", + "description": "Вставьте SVG, удалите лишнее через SVGO и посмотрите выигрыш по размеру UTF-8." + }, + "unitConverter": { + "title": "Конвертер единиц", + "description": "Конвертируйте между 323 единицами в 43 научных и инженерных категориях." + }, + "dockerComposeGenerator": { + "title": "Генератор Docker Compose", + "description": "Выберите СУБД, кэши, очереди, прокси и образы наблюдаемости; экспортируйте docker-compose.yml для локальных стеков." + }, + "game2048": { + "title": "2048", + "description": "Сдвигайте и объединяйте плитки, чтобы достичь 2048 года. Классическая игра-головоломка с номерами." + }, + "sudoku": { + "title": "Sudoku", + "description": "Классическая числовая головоломка. 3 уровня сложности, по 150 этапов каждый." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Просматривайте ключи, проверяйте значения и выполняйте команды на любом экземпляре Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Генератор и проверка Bcrypt", + "description": "Хешируйте пароли с помощью bcrypt и проверяйте существующие хеши." + }, + "chmodCalculator": { + "title": "Калькулятор Chmod", + "description": "Преобразуйте права Unix между восьмеричной, символьной формой и флажками." + }, + "codeScreenshot": { + "title": "Скриншот кода", + "description": "Превращайте исходный код в красивые изображения для публикации." + }, + "cssGenerators": { + "title": "Генераторы CSS", + "description": "Генераторы box-shadow, border-radius и адаптивного clamp()." + }, + "exifViewer": { + "title": "Просмотр и удаление EXIF", + "description": "Просматривайте и удаляйте метаданные EXIF из фото локально." + }, + "faviconGenerator": { + "title": "Генератор favicon", + "description": "Создавайте favicon, иконки приложений и фрагменты манифеста." + }, + "jsonDiff": { + "title": "Сравнение JSON", + "description": "Семантическое, учитывающее структуру сравнение двух документов JSON." + }, + "jsonpathPlayground": { + "title": "Песочница JSONPath", + "description": "Запрашивайте JSON с помощью JSONPath или JMESPath и смотрите результаты вживую." + }, + "keycodeInspector": { + "title": "Инспектор клавиш", + "description": "Нажмите клавишу, чтобы увидеть её коды события JavaScript и модификаторы." + }, + "markdownTableGenerator": { + "title": "Генератор таблиц Markdown", + "description": "Создавайте таблицы Markdown и HTML в табличном редакторе." + }, + "timezoneConverter": { + "title": "Конвертер часовых поясов", + "description": "Преобразуйте время между поясами с подсказками о смене дня и рабочих часах." + }, + "tokenCounter": { + "title": "Счётчик токенов LLM", + "description": "Считайте токены LLM, визуализируйте чанки и оценивайте стоимость API." + }, + "webhookTester": { + "title": "Тестер вебхуков", + "description": "Захватывайте и изучайте входящие запросы вебхуков в реальном времени." + }, + "websocketTester": { + "title": "Тестер WebSocket", + "description": "Подключайтесь к эндпоинтам WebSocket и обменивайтесь сообщениями в реальном времени." + }, + "whoisLookup": { + "title": "Whois-запрос", + "description": "Узнавайте данные регистрации доменов и IP через RDAP." + } + }, + "tabs": { + "apps": "Приложения", + "analytics": "Аналитика", + "ui": "Интерфейс" + }, + "analytics": { + "title": "Ваши данные кратко", + "subtitle": "Счётчики синхронизированы с вашей учётной записью на сервере.", + "loadError": "Не удалось загрузить аналитику.", + "retry": "Повторить", + "loginHint": "Войдите, чтобы видеть аналитику использования и настройки интерфейса.", + "passwordEntries": "Записей паролей", + "bookmarks": "Закладки", + "bookmarkFolders": "Папки закладок", + "tasksTotal": "Задачи", + "tasksCompleted": "Выполнено", + "tasksOngoing": "В работе", + "tasksNotStarted": "Не начато", + "projects": "Проекты", + "nosqlConnections": "Подключения к БД", + "notes": "Заметки", + "apiClientCollections": "Коллекции API", + "apiClientEnvironments": "Окружения API", + "apiClientHistory": "Записи истории API", + "jsonFormatterDocuments": "Документы JSON-форматера", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Фрагменты кода", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Настройки интерфейса", + "subtitle": "Тема, язык и видимые инструменты на боковой панели — из настроек аккаунта.", + "loadError": "Не удалось загрузить настройки.", + "loginHint": "Войдите, чтобы видеть сохранённые настройки интерфейса.", + "theme": "Тема", + "accent": "Цвет акцента", + "locale": "Язык", + "enabledTools": "Видимые инструменты", + "favoriteTools": "Избранные инструменты", + "toolsWithUsage": "Инструменты со статистикой" + }, + "signIn": "Войти", + "noResults": "Ни один инструмент не соответствует вашему поиску", + "noResultsHint": "Попробуйте другое ключевое слово или очистите фильтр.", + "filterAll": "Все", + "loginCta": { + "description": "Войдите, чтобы отслеживать недавно используемые инструменты." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Загрузка...", + "myTasksTitle": "Мои задачи", + "statsLine": "{total} задач • {percent}% выполнено", + "statsLineDesktop": "{total} всего • {percent}% выполнено" + }, + "filters": { + "all": "Все", + "total": "Итого", + "allProjects": "Все проекты", + "export": "Экспорт", + "kanban": "Канбан", + "list": "Список", + "searchPlaceholder": "Поиск задач...", + "searchAria": "Поиск задач", + "clearSearchAria": "Очистить поиск", + "foundTasks": "Найдено {count} {count, plural, one {задача} few {задачи} other {задач}}", + "filterByAllAria": "Фильтр: Все", + "filterByStatusAria": "Фильтр по статусу: {status}" + }, + "status": { + "not-started": { + "label": "Не начато", + "description": "Задачи, которые ещё не начаты" + }, + "ongoing": { + "label": "В работе", + "description": "Задачи, которые сейчас выполняются" + }, + "completed": { + "label": "Завершено", + "description": "Задачи, которые завершены" + } + }, + "priorities": { + "high": "Высокий", + "medium": "Средний", + "low": "Низкий" + }, + "form": { + "addNewTaskLabel": "Добавить новую задачу", + "multilinePlaceholder": "Добавьте описание задачи... (Shift+Enter для новой строки)", + "singlelinePlaceholder": "Что нужно сделать? Нажмите Enter для добавления", + "taskInputAria": "Ввод задачи", + "projectPlaceholder": "Проект", + "selectProject": "Выбрать проект", + "errorSelectProject": "Пожалуйста, выберите проект или создайте новый для добавления задачи.", + "hintMultiline": "Shift+Enter — новая строка, Enter — добавить", + "hintSingleline": "Enter для добавления", + "shortcutsAria": "Горячие клавиши", + "shortcutsLabel": "Горячие клавиши", + "shortcutAddTask": "Добавить задачу", + "shortcutNewLine": "Новая строка", + "shortcutClear": "Очистить", + "addButton": "Добавить" + }, + "list": { + "ariaLabel": "Список задач", + "loading": "Загрузка задач...", + "emptyTitle": "Задачи не найдены", + "emptyPrimary": "Начните с добавления первой задачи выше! Нажмите Enter для быстрого добавления.", + "emptySecondary": "Попробуйте изменить фильтры или поисковый запрос." + }, + "mobileDrawer": { + "addTaskSrOnly": "Добавить задачу", + "addNewTaskTitle": "Добавить новую задачу", + "addNewTaskDescription": "Создайте новую задачу для отслеживания прогресса.", + "cancel": "Отмена" + }, + "kanban": { + "ariaLabel": "Канбан-доска", + "loading": "Загрузка задач...", + "dropHere": "Перетащите задачу сюда", + "noTasksYet": "Задач пока нет", + "emptyColumnAria": "Пустой столбец", + "columnAria": "Столбец {title} с {count} задачами", + "countInColumnAria": "{count} задач в {title}" + }, + "taskItem": { + "confirmDelete": "Вы уверены, что хотите удалить эту задачу?", + "statusPlaceholder": "Статус", + "changeStatusAria": "Изменить статус задачи", + "taskOptionsAria": "Параметры задачи", + "editTask": "Редактировать задачу", + "copyTask": "Копировать задачу", + "copied": "Скопировано!", + "deleteTask": "Удалить задачу", + "statusHeading": "Статус", + "editTaskAria": "Редактировать задачу" + }, + "editDialog": { + "title": "Редактировать задачу", + "description": "Обновите данные задачи, добавьте подзадачи, установите приоритеты и другое.", + "taskTitleLabel": "Название задачи *", + "taskTitlePlaceholder": "Что нужно сделать?", + "descriptionLabel": "Описание", + "descriptionPlaceholder": "Добавьте подробности о задаче...", + "statusLabel": "Статус", + "priorityLabel": "Приоритет", + "dueDateLabel": "Срок выполнения", + "pickDate": "Выберите дату", + "clearDate": "Очистить дату", + "projectLabel": "Проект", + "selectProjectPlaceholder": "Выберите проект", + "noProject": "Без проекта", + "timeEstimateLabel": "Оценка времени (минуты)", + "timeEstimatePlaceholder": "Сколько это займёт?", + "tagsLabel": "Теги", + "customTagNamePlaceholder": "Название тега", + "subtasksLabel": "Подзадачи", + "addSubtaskPlaceholder": "Добавить подзадачу...", + "cancel": "Отмена", + "saving": "Сохранение...", + "saveChanges": "Сохранить изменения" + }, + "export": { + "title": "Экспорт задач", + "description": "Экспортируйте задачи в формате JSON, CSV или Excel.", + "projectLabel": "Проект", + "statusLabel": "Статус", + "tagLabel": "Тег", + "allProjects": "Все проекты", + "allStatuses": "Все статусы", + "allTags": "Все теги", + "exportAsJsonTitle": "Экспорт в JSON", + "exportAsJsonDescription": "Экспортируйте данные задач, включая подзадачи, теги и метаданные.", + "exportJson": "Экспорт JSON", + "exportAsCsvExcelTitle": "Экспорт в CSV / Excel", + "exportAsCsvExcelDescription": "Экспортируйте задачи для табличных приложений.", + "exportCsv": "Экспорт CSV", + "exportExcel": "Экспорт Excel", + "close": "Закрыть", + "toastNoTasks": "Нет задач для экспорта с текущими фильтрами", + "toastExportedJson": "Экспортировано {count} задач в JSON!", + "toastExportedCsv": "Экспортировано {count} задач в CSV!", + "toastExportedExcel": "Экспортировано {count} задач в Excel!", + "toastFailedJson": "Не удалось экспортировать в JSON", + "toastFailedCsv": "Не удалось экспортировать в CSV", + "toastFailedExcel": "Не удалось экспортировать в Excel", + "unknownProject": "Неизвестный проект" + }, + "ack": { + "taskAddedTitle": "Задача успешно добавлена", + "taskAddedFailedTitle": "Не удалось добавить задачу", + "taskUpdatedTitle": "Задача успешно обновлена", + "taskUpdatedFailedTitle": "Не удалось обновить задачу", + "taskStatusUpdateFailedTitle": "Не удалось обновить статус задачи", + "taskMovedTitle": "Задача перемещена в {status}", + "tryAgain": "Пожалуйста, попробуйте снова.", + "taskDeletedTitle": "Задача «{text}» удалена", + "undo": "Отменить", + "taskRestoredTitle": "Задача восстановлена", + "taskRestoredSuccessTitle": "Задача успешно восстановлена", + "taskRestoreFailedTitle": "Не удалось восстановить задачу. Попробуйте снова.", + "taskDeleteFailedTitle": "Не удалось удалить задачу. Попробуйте снова.", + "tasksImportedTitle": "Успешно импортировано {count} {count, plural, one {задача} few {задачи} other {задач}}!", + "tasksImportFailedTitle": "Не удалось импортировать задачи. Попробуйте снова.", + "unknownProject": "Неизвестный проект", + "projectCreatedTitle": "Проект успешно создан", + "projectCreateFailedTitle": "Не удалось создать проект", + "projectUpdatedTitle": "Проект успешно обновлён", + "projectUpdateFailedTitle": "Не удалось обновить проект", + "projectDeletedTitle": "Проект успешно удалён", + "projectDeleteFailedTitle": "Не удалось удалить проект" + } + }, + "Notes": { + "page": { + "loading": "Загрузка..." + }, + "layout": { + "mobileNavTitle": "Навигация по заметкам", + "openMenuAria": "Открыть меню заметок" + }, + "sidebar": { + "title": "Заметки", + "createNoteAria": "Создать заметку", + "searchPlaceholder": "Поиск заметок...", + "loading": "Загрузка...", + "noNotesFound": "Заметки не найдены.", + "noNotesYet": "Заметок пока нет. Нажмите +, чтобы создать.", + "inParent": "в {parentTitle}", + "untitled": "Без названия", + "addSubPage": "Добавить подстраницу", + "delete": "Удалить", + "deleteConfirmTitle": "Удалить заметку?", + "deleteConfirmDescription": "Вы уверены, что хотите удалить «{title}»? Это действие нельзя отменить.", + "cancel": "Отмена", + "confirmDelete": "Удалить", + "noteOptionsAria": "Параметры заметки" + }, + "editor": { + "emptyState": "Выберите заметку или создайте новую, чтобы начать.", + "titlePlaceholder": "Без названия", + "saving": "Сохранение…", + "ready": "Готово", + "saved": "Сохранено {time}", + "stats": "{words} слов · {read} мин чтения", + "template": "Шаблон", + "applyTemplate": "Применить шаблон", + "exportMdLabel": ".md", + "exportMd": "Экспорт в Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Экспорт в HTML", + "focusMode": "Режим фокуса", + "exitFocusMode": "Выйти из режима фокуса", + "save": "Сохранить", + "addTag": "Добавить тег…", + "removeTag": "Удалить тег {tag}", + "loading": "Загрузка…", + "searchHint": "для поиска", + "focusHint": "режим фокуса", + "notePath": "Путь заметки" + }, + "context": { + "authRequiredError": "Пользователь не авторизован", + "defaultTitle": "Без названия", + "cloudSyncImageError": "Включите облачную синхронизацию, чтобы сохранять изображения заметок. Текстовые заметки остаются локальными." + } + }, + "PasswordManager": { + "page": { + "loading": "Загрузка...", + "title": "Менеджер паролей", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Не удалось расшифровать пароли", + "toastLoadFailed": "Не удалось загрузить пароли", + "toastUnlockedAuto": "Хранилище разблокировано автоматически", + "toastAutoUnlockFailed": "Автоматическая разблокировка не удалась. Разблокируйте вручную.", + "toastConnectFailed": "Не удалось подключиться к серверу", + "toastAutoUnlockWarning": "Хранилище разблокировано, но автоматическая разблокировка может не работать при перезагрузке", + "errorIncorrectPassword": "Неверный мастер-пароль", + "errorUnlockFailed": "Не удалось разблокировать хранилище", + "errorPasswordsMismatch": "Пароли не совпадают", + "errorPasswordTooShort": "Пароль должен содержать не менее 8 символов", + "errorSetupFailed": "Не удалось настроить хранилище", + "errorConnectFailed": "Не удалось подключиться к серверу.", + "mobileTitleCreate": "Создайте хранилище", + "mobileTitleWelcome": "С возвращением", + "mobileSubtitleCreate": "Задайте мастер-пароль для защиты ваших данных", + "mobileSubtitleUnlock": "Введите мастер-пароль для разблокировки", + "placeholderMasterPassword": "Мастер-пароль", + "placeholderConfirmPassword": "Подтвердите пароль", + "labelMasterPassword": "Мастер-пароль", + "labelConfirmPassword": "Подтвердите пароль", + "placeholderEnterMaster": "Введите мастер-пароль", + "placeholderConfirmMaster": "Подтвердите мастер-пароль", + "buttonCreateVault": "Создать хранилище", + "buttonUnlock": "Разблокировать", + "buttonUnlockVault": "Разблокировать хранилище", + "footerCreate": "Ваш пароль не хранится и не может быть восстановлен", + "footerUnlock": "Защищено сквозным шифрованием", + "desktopTitleSetup": "Настройка хранилища паролей", + "desktopTitleUnlock": "Разблокировать хранилище", + "desktopDescriptionSetup": "Создайте мастер-пароль для защиты ваших данных. Этот пароль не хранится и не может быть восстановлен.", + "desktopDescriptionUnlock": "Введите мастер-пароль для доступа к защищённым данным." + }, + "list": { + "loading": "Загрузка паролей...", + "emptyTitle": "Хранилище пусто", + "emptyHintMobile": "Нажмите кнопку +, чтобы добавить первый пароль", + "emptyHintDesktop": "У вас пока нет сохранённых паролей. Нажмите «Добавить пароль», чтобы начать.", + "addPassword": "Добавить пароль", + "searchPlaceholderMobile": "Поиск по хранилищу...", + "searchPlaceholderDesktop": "Поиск по хранилищу...", + "importExport": "Импорт / экспорт", + "lockVault": "Заблокировать хранилище", + "allPasswordsTitle": "Все пароли", + "passwordCount": "{count, plural, one {# пароль} few {# пароля} other {# паролей}}", + "vaultHealthDrawerTitle": "Состояние хранилища", + "noResults": "Пароли, соответствующие «{query}», не найдены", + "tableService": "Сервис", + "tableUsername": "Имя пользователя", + "tablePassword": "Пароль", + "tableUrl": "URL", + "tableActions": "Действия", + "gridViewAria": "Сетка", + "listViewAria": "Список", + "passwordStrengthTitle": "Надёжность пароля", + "deleteDialogTitle": "Удалить пароль", + "deleteDialogDescription": "Вы уверены, что хотите удалить этот пароль? Это действие нельзя отменить.", + "cancel": "Отмена", + "delete": "Удалить", + "edit": "Редактировать" + }, + "toasts": { + "copiedPassword": "Пароль скопирован в буфер обмена", + "copiedUsername": "Имя пользователя скопировано в буфер обмена", + "passwordDeleted": "Пароль удалён", + "deleteFailed": "Не удалось удалить пароль", + "vaultLocked": "Хранилище заблокировано", + "importedCount": "Успешно импортировано {count, plural, one {# пароль} few {# пароля} other {# паролей}}" + }, + "form": { + "addTitle": "Добавить новый пароль", + "editTitle": "Редактировать пароль", + "serviceName": "Название сервиса", + "usernameEmail": "Имя пользователя / email", + "password": "Пароль", + "urlOptional": "URL сайта (необязательно)", + "notesOptional": "Заметки (необязательно)", + "tags": "Теги", + "placeholderService": "напр. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Введите или сгенерируйте пароль", + "placeholderUrl": "https://...", + "placeholderNotes": "Дополнительные сведения...", + "placeholderTags": "Добавьте теги (Enter или запятая)", + "strength": "Надёжность", + "strengthWeak": "Слабый", + "strengthMedium": "Средний", + "strengthStrong": "Сильный", + "advancedGenerator": "Расширенный генератор", + "cancel": "Отмена", + "saving": "Сохранение...", + "savePassword": "Сохранить пароль", + "updating": "Обновление...", + "updatePassword": "Обновить пароль", + "addPasswordSrOnly": "Добавить пароль", + "triggerAdd": "Добавить пароль", + "toastSaved": "Пароль успешно сохранён", + "toastSaveFailed": "Не удалось сохранить пароль", + "toastUpdated": "Пароль успешно обновлён", + "toastUpdateFailed": "Не удалось обновить пароль" + }, + "security": { + "vaultHealth": "Состояние хранилища", + "statusLabel": "Статус", + "totalPasswords": "Всего паролей", + "activeEntries": "Активные записи в хранилище", + "protected": "Защищено", + "weakPasswords": "Слабые пароли", + "needingImprovement": "Пароли, требующие улучшения", + "allGood": "Всё в порядке", + "reusedPasswords": "Повторяющиеся пароли", + "usedMultipleTimes": "Пароли, используемые несколько раз", + "unique": "Уникальные", + "total": "Всего", + "weak": "Слабые", + "reused": "Повторяющиеся", + "totalCountLabel": "{count, plural, one {# пароль} few {# пароля} other {# паролей}}", + "weakCountLabel": "{count} слабых", + "allSecure": "Все надёжные", + "scoreExcellent": "Отлично", + "scoreGood": "Хорошо", + "scoreFair": "Удовлетворительно", + "scoreCritical": "Критично" + }, + "importExport": { + "title": "Импорт / экспорт хранилища", + "description": "Управляйте данными паролей. Вы можете экспортировать хранилище в JSON или импортировать из JSON-файла.", + "tabExport": "Экспорт", + "tabImport": "Импорт", + "warningTitle": "Предупреждение", + "warningBody": "Экспортированные файлы содержат пароли в открытом виде. Храните этот файл в безопасном месте и удалите после использования.", + "exportBlurb": "Скачайте всё хранилище как JSON-файл. Включает все сервисы, имена пользователей, пароли и заметки.", + "downloadJson": "Скачать JSON-экспорт", + "pasteLabel": "Вставьте JSON-данные", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Вставьте содержимое ранее экспортированного JSON-файла.", + "importing": "Импорт...", + "importPasswords": "Импортировать пароли", + "trigger": "Импорт / экспорт", + "toastExported": "Хранилище успешно экспортировано", + "toastExportFailed": "Не удалось экспортировать хранилище", + "toastImportFailed": "Не удалось импортировать: неверный формат JSON" + }, + "card": { + "copyUsername": "Копировать имя пользователя", + "openUrl": "Открыть URL", + "strength": "Надёжность" + }, + "swipeable": { + "copy": "Копировать", + "delete": "Удалить", + "copyPassword": "Копировать пароль", + "copyUsername": "Копировать имя пользователя", + "edit": "Редактировать", + "deleteAction": "Удалить" + }, + "floatingMenu": { + "lockVault": "Заблокировать хранилище", + "importExport": "Импорт / экспорт", + "addPassword": "Добавить пароль" + }, + "generator": { + "tabPassword": "Пароль", + "tabPassphrase": "Парольная фраза", + "strength": "Надёжность", + "lengthLabel": "Длина: {length} символов", + "lengthBadge": "{length} симв.", + "characterSets": "Наборы символов", + "uppercase": "Заглавные", + "lowercase": "Строчные", + "numbers": "Цифры", + "symbols": "Символы", + "wordCountLabel": "Количество слов: {count}", + "wordCountBadge": "{count, plural, one {# слово} few {# слова} other {# слов}}", + "separator": "Разделитель", + "capitalization": "Заглавные буквы", + "includeNumber": "Включить число" + } + }, + "EnvironmentManager": { + "subtitle": "Зашифрованные переменные окружения по проекту и среде.", + "page": { + "loading": "Загрузка…", + "title": "Менеджер окружений", + "loadFailed": "Не удалось загрузить наборы окружений" + }, + "list": { + "title": "Окружения", + "loading": "Загрузка наборов…", + "emptyTitle": "Пока нет наборов окружений", + "emptyHint": "Создайте набор для каждого проекта и среды (например, ключи API продакшена). Всё шифруется в браузере перед загрузкой.", + "searchPlaceholder": "Поиск по проекту, среде, тегам, заметкам…", + "tagFilterPlaceholder": "Фильтр по тегу", + "variableCount": "{count, plural, one {# переменная} few {# переменные} other {# переменных}}", + "updatedAgo": "Обновлено {time}", + "copyDotEnv": "Копировать .env", + "edit": "Изменить", + "delete": "Удалить", + "cancel": "Отмена", + "confirmDelete": "Удалить", + "deleteDialogTitle": "Удалить набор окружения?", + "deleteDialogDescription": "Зашифрованные данные будут удалены из аккаунта. Действие необратимо.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env скопирован в буфер обмена", + "deleted": "Набор окружения удалён", + "deleteFailed": "Не удалось удалить" + }, + "form": { + "addTitle": "Новый набор окружения", + "editTitle": "Изменить набор окружения", + "triggerAdd": "Добавить набор окружения", + "addSrOnly": "Добавить набор окружения", + "project": "Проект", + "environment": "Окружение", + "placeholderProject": "напр. billing-api", + "placeholderEnvironment": "напр. production", + "pasteEnvLabel": "Вставить .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Объединить вставленные строки", + "variables": "Переменные", + "addRow": "Добавить строку", + "keyPlaceholder": "КЛЮЧ", + "valuePlaceholder": "значение", + "notesOptional": "Заметки (необязательно)", + "placeholderNotes": "Политика ротации, владелец, ссылки…", + "tags": "Теги", + "placeholderTags": "Добавить теги (Enter или запятая)", + "cancel": "Отмена", + "save": "Сохранить", + "saving": "Сохранение…", + "update": "Обновить", + "updating": "Обновление…", + "toastSaved": "Набор окружения сохранён", + "toastSaveFailed": "Не удалось сохранить", + "toastUpdated": "Набор окружения обновлён", + "toastUpdateFailed": "Не удалось обновить", + "toastPasteEmpty": "Строки КЛЮЧ=значение не найдены", + "toastPasteApplied": "Объединено переменных из вставки: {count}", + "toastProjectEnvRequired": "Проект и окружение обязательны", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Загрузка..." + }, + "manager": { + "foldersHeading": "Папки", + "tagsHeading": "Теги", + "tagsMore": "+{count} ещё", + "tagsShowLess": "Show less", + "searchPlaceholder": "Поиск закладок...", + "import": "Импорт", + "export": "Экспорт", + "exportAsHtml": "Экспорт в HTML", + "exportAsJson": "Экспорт в JSON", + "addBookmark": "Добавить закладку", + "matchingQuery": "по запросу «{query}»", + "bookmarkCount": "{count, plural, one {# закладка} few {# закладки} other {# закладок}}", + "allBookmarks": "Все закладки", + "uncategorized": "Без категории", + "unknownFolder": "Неизвестно" + }, + "card": { + "urlCopied": "URL скопирован в буфер обмена", + "bookmarkDeleted": "Закладка удалена", + "edit": "Редактировать", + "delete": "Удалить", + "copyUrl": "Копировать URL", + "deleteConfirmTitle": "Удалить закладку?", + "deleteConfirmDescription": "Закладка «{title}» будет удалена безвозвратно.", + "cancel": "Отмена" + }, + "grid": { + "emptyTitle": "Закладок пока нет", + "emptyDescription": "Добавьте первую закладку или импортируйте из браузера, чтобы начать." + }, + "folderTree": { + "allBookmarks": "Все закладки", + "addSubfolder": "Добавить подпапку", + "rename": "Переименовать", + "deleteFolderTitle": "Удалить папку «{name}»?", + "deleteFolderDescription": "Папка и все подпапки будут удалены. Закладки из этих папок будут перемещены в «Без категории»." + }, + "import": { + "title": "Импорт закладок", + "description": "Импортируйте закладки из экспортированного файла браузера.", + "parseError": "Не удалось разобрать файл закладок. Убедитесь, что файл является корректным экспортом.", + "successToast": "Импортировано {bookmarks} закладок и {folders} папок", + "errorToast": "Не удалось импортировать закладки", + "chromeInstruction": "Меню → Закладки → Диспетчер закладок → ⋮ → Экспорт закладок", + "firefoxInstruction": "Меню → Закладки → Управление закладками → Импорт и резервные копии → Экспорт", + "safariInstruction": "Файл → Экспорт закладок...", + "edgeInstruction": "Меню → Избранное → ⋯ → Экспорт избранного", + "dropZoneTitle": "Перетащите файл закладок сюда", + "dropZoneHint": "или нажмите для выбора (HTML или JSON)", + "selectedFileHint": "Нажмите, чтобы выбрать другой файл", + "errorTitle": "Ошибка", + "previewTitle": "Предпросмотр импорта", + "previewBookmarks": "{count, plural, one {# закладка} few {# закладки} other {# закладок}}", + "previewFolders": "{count, plural, one {# папка} few {# папки} other {# папок}}", + "importModeLabel": "Режим импорта", + "modeMerge": "Объединить с существующими закладками", + "modeReplace": "Заменить все существующие закладки", + "cancel": "Отмена", + "importButton": "Импорт", + "importButtonWithCount": "Импортировать {count, plural, one {# закладку} few {# закладки} other {# закладок}}" + }, + "addBookmark": { + "titleAdd": "Добавить закладку", + "titleEdit": "Редактировать закладку", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Пожалуйста, введите корректный URL", + "titleLabel": "Название", + "titlePlaceholder": "Название закладки", + "titleRequired": "Пожалуйста, введите название", + "descriptionLabel": "Описание (необязательно)", + "descriptionPlaceholder": "Добавьте заметку об этой закладке", + "folderLabel": "Папка", + "folderPlaceholder": "Выберите папку", + "noFolder": "Без папки", + "tagsLabel": "Теги", + "tagsPlaceholder": "Добавьте теги (Enter)", + "toastUpdated": "Закладка обновлена", + "toastAdded": "Закладка добавлена", + "cancel": "Отмена", + "submitAdd": "Добавить закладку", + "submitUpdate": "Обновить закладку" + }, + "addFolder": { + "title": "Создать папку", + "nameLabel": "Название папки", + "namePlaceholder": "Моя папка", + "nameRequired": "Пожалуйста, введите название папки", + "parentLabel": "Родительская папка", + "parentPlaceholder": "Выберите родительскую папку", + "rootParent": "Корень (без родителя)", + "toastCreated": "Папка создана", + "cancel": "Отмена", + "submit": "Создать папку" + }, + "editFolder": { + "title": "Переименовать папку", + "nameLabel": "Название папки", + "namePlaceholder": "Название папки", + "nameRequired": "Пожалуйста, введите название папки", + "toastRenamed": "Папка переименована", + "cancel": "Отмена", + "save": "Сохранить" + } + }, + "EmailValidator": { + "header": { + "title": "Валидатор email", + "description": "Проверяйте и валидируйте адреса электронной почты по синтаксису, домену и доставляемости." + }, + "tabs": { + "single": "Одиночная проверка", + "bulk": "Массовая загрузка" + }, + "single": { + "cardTitle": "Проверка одного email", + "cardDescription": "Введите адрес электронной почты для проверки доставляемости и репутации.", + "placeholder": "Введите адрес электронной почты...", + "clearAria": "Очистить email", + "validate": "Проверить", + "validating": "Проверка...", + "formatError": "Пожалуйста, введите корректный адрес электронной почты" + }, + "toasts": { + "errorTitle": "Ошибка", + "enterEmail": "Пожалуйста, введите адрес электронной почты", + "invalidFormatTitle": "Неверный формат email", + "invalidFormatDescription": "Пожалуйста, введите корректный адрес электронной почты", + "validateFailedDescription": "Произошла ошибка при проверке электронной почты", + "noEmailsTitle": "Email не найдены", + "noEmailsDescription": "Убедитесь, что в вашем Excel есть столбец «email».", + "fileTooLargeTitle": "Файл слишком большой", + "fileTooLargeDescription": "Найдено {total} email. Будут проверены только первые 4000.", + "successTitle": "Успех", + "successValidated": "Успешно проверено {count} email.", + "batchFailedTitle": "Массовая проверка не удалась", + "batchFailedDescription": "Произошла ошибка при массовой проверке.", + "noExportTitle": "Нет данных для экспорта", + "noExportValidOnly": "Не найдено действительных email для экспорта.", + "noExportAll": "Нет результатов для экспорта.", + "exportSuccessTitle": "Экспорт выполнен", + "exportSuccessExcelAll": "Экспортировано {count} email в Excel.", + "exportSuccessExcelValid": "Экспортировано {count} действительных email в Excel.", + "exportSuccessCsvAll": "Экспортировано {count} email в CSV.", + "exportSuccessCsvValid": "Экспортировано {count} действительных email в CSV." + }, + "disposableAlert": { + "title": "Обнаружен одноразовый email", + "description": "Этот адрес электронной почты принадлежит одноразовому сервису. Он может быть временным или ненадёжным." + }, + "score": { + "reliabilityTitle": "Оценка надёжности", + "scoreLabel": "Оценка" + }, + "details": { + "sectionTitle": "Детали проверки" + }, + "validation": { + "syntaxLabel": "Синтаксис корректен", + "syntaxTooltip": "Проверяет, соответствует ли адрес электронной почты правильному формату (напр. user@domain.com)", + "domainLabel": "Домен существует", + "domainTooltip": "Проверяет, что доменное имя в адресе электронной почты действительно существует", + "mxLabel": "MX-записи найдены", + "mxTooltip": "Проверяет, настроены ли MX-записи домена для приёма электронной почты", + "mailboxLabel": "Почтовый ящик существует", + "mailboxTooltip": "Проверяет, что конкретный почтовый ящик/учётная запись существует на почтовом сервере", + "disposableLabel": "Одноразовый email", + "disposableTooltip": "Указывает, принадлежит ли email одноразовому сервису (может быть рискованным)", + "roleBasedLabel": "Ролевой email", + "roleBasedTooltip": "Определяет, является ли email ролевым (напр. support@, info@), а не персональным" + }, + "copy": { + "emailCopied": "Email скопирован!", + "copyTooltip": "Копировать email" + }, + "bulk": { + "dropHere": "Перетащите файл сюда", + "dragHere": "Перетащите файл сюда", + "uploadHint": "Загрузите файл .xlsx, .xls или .csv со столбцом «email» для проверки до 4000 адресов.", + "filePrefix": "Файл:", + "selectFile": "Выбрать файл", + "downloadTemplate": "Скачать шаблон" + }, + "bulkLoading": { + "title": "Проверка email...", + "batchProgress": "Обработка пакета {current} из {total}..." + }, + "stats": { + "totalProcessed": "Всего обработано", + "valid": "Действительные", + "risky": "Рискованные", + "invalid": "Недействительные" + }, + "results": { + "detailedTitle": "Подробные результаты", + "searchPlaceholder": "Поиск email...", + "newVerification": "Новая проверка", + "exportAll": "Экспортировать всё", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Экспортировать действительные ({count})" + }, + "showingResults": "Показано {shown} из {total} результатов", + "table": { + "email": "Email", + "status": "Статус", + "score": "Оценка", + "checks": "Проверки" + }, + "noSearchResults": "Результаты по запросу «{query}» не найдены", + "checksTooltip": { + "syntax": "Синтаксис", + "domain": "Домен", + "mx": "MX", + "mailbox": "Почтовый ящик" + }, + "exportColumns": { + "email": "Email", + "status": "Статус", + "score": "Оценка", + "syntax": "Синтаксис", + "domain": "Домен", + "mx": "MX", + "mailbox": "Почтовый ящик", + "disposable": "Одноразовый", + "roleBased": "Ролевой", + "valid": "Действительный", + "invalid": "Недействительный", + "yes": "Да", + "no": "Нет" + }, + "sheets": { + "validEmails": "Действительные email", + "results": "Результаты", + "template": "Шаблон" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL-обозреватель", + "heroSubtitle": "Подключайтесь к базам данных для управления коллекциями, выполнения запросов и визуализации данных.", + "featureMultiTitle": "Множественные подключения", + "featureMultiDesc": "Управляйте несколькими подключениями к базам данных одновременно.", + "featureMongoTitle": "Поддержка MongoDB", + "featureMongoDesc": "Встроенная поддержка MongoDB с расширенной фильтрацией.", + "featureQueryTitle": "Умные запросы", + "featureQueryDesc": "Мощный конструктор запросов с подсветкой синтаксиса.", + "connectCta": "Подключиться к MongoDB", + "footerNote": "В данный момент поддерживается {db}. Другие базы данных будут добавлены в будущем.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Выберите коллекцию", + "selectCollectionDesc": "Выберите коллекцию в боковой панели для просмотра документов и начала работы с данными.", + "dialogAddConnection": "Добавить подключение", + "toastConnectionAdded": "Подключение добавлено", + "toastFetchFailed": "Не удалось загрузить документы для {collection}", + "toastDocDeleted": "Документ удалён", + "deleteDocTitle": "Вы уверены?", + "deleteDocDescription": "Это действие нельзя отменить. Документ будет безвозвратно удалён из коллекции.", + "closeAllTitle": "Закрыть все вкладки?", + "closeAllDescription": "Вы уверены, что хотите закрыть все вкладки? Несохранённые изменения в фильтрах запросов будут потеряны.", + "closeAllConfirm": "Закрыть все", + "cancel": "Отмена", + "delete": "Удалить" + }, + "connection": { + "titleEdit": "Редактировать подключение", + "titleConnect": "Подключение к MongoDB", + "subtitleEdit": "Обновите параметры подключения.", + "subtitleConnect": "Введите строку подключения MongoDB для начала работы с данными.", + "labelName": "Название", + "placeholderName": "напр. Рабочая БД", + "labelConnectionString": "Строка подключения", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Формат: mongodb://username:password@host:port/database", + "testConnection": "Тест подключения", + "connecting": "Подключение...", + "updateConnect": "Обновить и подключить", + "connect": "Подключить", + "cancelEdit": "Отменить редактирование", + "savedConnections": "Сохранённые подключения", + "emptySaved": "Нет сохранённых подключений.", + "lastUsed": "Последнее использование: {time}", + "toastTestOk": "Подключение успешно!", + "toastTestFail": "Подключение не удалось: {message}", + "toastUpdated": "Подключение обновлено", + "confirmDelete": "Вы уверены, что хотите удалить это сохранённое подключение?", + "toastDeletedConn": "Подключение удалено", + "toastDeleteFail": "Не удалось удалить подключение", + "copyStringTitle": "Копировать строку подключения", + "toastStringCopied": "Строка подключения скопирована!", + "editTitle": "Редактировать", + "deleteTitle": "Удалить" + }, + "sidebar": { + "explorer": "Обозреватель", + "add": "Добавить", + "tooltipAdd": "Добавить новое подключение", + "tooltipRefresh": "Обновить подключения", + "searchPlaceholder": "Поиск...", + "loadingConnections": "Загрузка подключений...", + "noConnectionsFound": "Подключения не найдены", + "toastLoadFail": "Не удалось загрузить подключения", + "toastConnectFail": "Не удалось подключиться к {name}", + "toastCollectionsFail": "Не удалось загрузить коллекции для {name}", + "toastRenamed": "Подключение переименовано", + "toastRenameFail": "Не удалось переименовать подключение", + "confirmDeleteConnection": "Вы уверены, что хотите удалить подключение «{name}»?", + "toastDeleted": "Подключение удалено", + "toastDeleteConnFail": "Не удалось удалить подключение", + "confirmDropDb": "Вы уверены, что хотите удалить базу данных «{name}»? Это действие нельзя отменить.", + "toastDbDropped": "База данных {name} удалена", + "confirmDropCollection": "Вы уверены, что хотите удалить коллекцию «{name}»? Это действие нельзя отменить.", + "toastCollectionDropped": "Коллекция {name} удалена", + "toastCollectionRenamed": "Коллекция переименована в {name}", + "toastDatabaseRenamed": "База данных переименована в {name}", + "connecting": "Подключение...", + "connectionFailed": "Подключение не удалось", + "noDatabases": "Нет баз данных", + "loading": "Загрузка...", + "noCollections": "Нет коллекций", + "renameCollectionTitle": "Переименовать коллекцию", + "renameDatabaseTitle": "Переименовать базу данных", + "placeholderNewCollection": "Новое название коллекции", + "placeholderNewDatabase": "Новое название базы данных", + "rename": "Переименовать", + "refresh": "Обновить", + "copyDbName": "Копировать имя БД", + "toastDbNameCopied": "Имя базы данных скопировано", + "dropDatabase": "Удалить базу данных", + "dropCollection": "Удалить коллекцию", + "menuRename": "Переименовать", + "menuCopyConnectionString": "Копировать строку подключения", + "menuDeleteConnection": "Удалить подключение", + "toastSidebarStringCopied": "Строка подключения скопирована!", + "cancel": "Отмена", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} док.", + "table": "Таблица", + "json": "JSON", + "tree": "Дерево", + "tooltipTable": "Табличное представление", + "tooltipJson": "JSON-представление", + "tooltipTree": "Древовидное представление", + "itemsPerPage": "Элементов на странице", + "perPage": "{n} / стр.", + "prevPage": "Предыдущая страница", + "nextPage": "Следующая страница", + "expandAll": "Развернуть все", + "collapseAll": "Свернуть все", + "refreshData": "Обновить данные", + "insert": "Вставить", + "export": "Экспорт", + "actions": "Действия", + "invalidJson": "Некорректное содержимое JSON", + "copiedClipboard": "Скопировано в буфер обмена", + "docUpdated": "Документ успешно обновлён", + "docInserted": "Документ успешно вставлен", + "invalidJsonShort": "Некорректный JSON", + "docCopied": "Документ скопирован в буфер обмена", + "openFullEditor": "Открыть в полном редакторе", + "cellArray": "Массив({length})", + "cellObject": "Объект", + "cellArrayBracket": "Массив[{length}]", + "emptyFilterTitle": "Документы по вашему запросу не найдены", + "emptyFilterHint": "Попробуйте изменить фильтр или очистить запрос.", + "clearFilter": "Очистить фильтр", + "emptyCollectionTitle": "Эта коллекция пуста", + "emptyCollectionHint": "Вставьте документ, чтобы начать.", + "insertDocument": "Вставить документ", + "formatJson": "Форматировать JSON", + "compactJson": "Компактный JSON", + "copyClipboard": "Копировать в буфер обмена", + "copyJson": "Копировать JSON", + "duplicate": "Дублировать", + "editDoc": "Редактировать документ", + "deleteDoc": "Удалить документ", + "documentLabel": "Документ {n}", + "editDialogTitle": "Редактировать документ", + "insertDialogTitle": "Вставить документ", + "viewValueTitle": "Просмотр значения", + "saveChanges": "Сохранить изменения", + "close": "Закрыть", + "cancel": "Отмена", + "insertButton": "Вставить", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Активный фильтр применён", + "filterActiveTooltip": "Фильтр активен", + "closeAll": "Закрыть все" + }, + "query": { + "migrateHistory": "История запросов перенесена в облако", + "saveHistoryFail": "Не удалось сохранить историю запросов", + "deleteQueryFail": "Не удалось удалить запрос", + "invalidJsonQuery": "Некорректный JSON-запрос", + "placeholder": "напр. { \"status\": \"active\" }", + "clearQuery": "Очистить запрос", + "openAdvanced": "Открыть расширенный редактор", + "visualBuilder": "Визуальный конструктор запросов", + "filterRules": "Правила фильтрации", + "addRule": "Добавить правило", + "noFilters": "Фильтры не применены. Добавьте правило для фильтрации документов.", + "where": "ГДЕ", + "and": "И", + "fieldPlaceholder": "Поле", + "opPlaceholder": "Оп.", + "typePlaceholder": "Тип", + "opRegex": "Regex", + "opIn": "В списке", + "opNin": "Не в списке", + "opExists": "Существует", + "typeAuto": "Авто", + "typeString": "Строка", + "typeNumber": "Число", + "typeBoolean": "Логический", + "typeDate": "Дата", + "typeObjectId": "ObjectId", + "true": "Истина", + "false": "Ложь", + "valuePlaceholder": "Значение", + "valueListPlaceholder": "значение1, значение2", + "clearFilters": "Очистить фильтры", + "applyFilters": "Применить фильтры", + "queryHistory": "История запросов", + "noSavedQueries": "Сохранённых запросов пока нет", + "advancedEditorTitle": "Расширенный редактор запросов", + "templatesPlaceholder": "Шаблоны", + "templateEmptyAgg": "Пустая агрегация", + "templateMatch": "$match (Фильтрация)", + "templateLookup": "$lookup (Соединение)", + "templateGroup": "$group (Подсчёт/Сумма)", + "format": "Формат", + "formattedJson": "Форматированный JSON", + "runQuery": "Выполнить запрос" + }, + "export": { + "selectFieldsError": "Пожалуйста, выберите хотя бы одно поле для экспорта", + "success": "Успешно экспортировано в {filename}", + "fail": "Не удалось экспортировать данные", + "title": "Экспорт данных", + "formatLabel": "Формат экспорта", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Выберите поля", + "selectAll": "Выбрать все", + "fieldsSelected": "{count, plural, one {# поле выбрано} few {# поля выбрано} other {# полей выбрано}}", + "cancel": "Отмена", + "export": "Экспорт", + "sheetName": "Данные" + }, + "jsonTree": { + "typeLabel": "Тип: {type}", + "itemSummary": "{count, plural, one {# элемент} few {# элемента} other {# элементов}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Новый запрос", + "importedRequest": "Импортированный запрос", + "untitledRequest": "Запрос без названия" + }, + "toasts": { + "curlImported": "cURL успешно импортирован", + "curlParseFailed": "Не удалось разобрать команду cURL", + "invalidJsonBody": "Некорректное тело JSON", + "requestFailed": "Запрос не выполнен: {message}", + "curlPasted": "cURL вставлен и успешно разобран", + "responseCopied": "Ответ скопирован в буфер обмена", + "codeCopied": "Код скопирован в буфер обмена", + "copyFailed": "Не удалось скопировать в буфер обмена" + }, + "layout": { + "collections": "Коллекции", + "closeSidebar": "Закрыть боковую панель", + "openSidebar": "Открыть боковую панель", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Метод", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Отправка...", + "send": "Отправить", + "invalidJsonBodyHelp": "Невозможно отправить: тело JSON недействительно" + }, + "requestTabs": { + "params": "Параметры", + "headers": "Заголовки", + "body": "Тело", + "auth": "Авторизация", + "bodyType": "Тип тела", + "selectType": "Выберите тип", + "bodyNone": "Нет", + "bodyJson": "JSON", + "bodyText": "Текст", + "noBodyHint": "В этом запросе нет тела", + "switchToJson": "Переключить на JSON-тело", + "authType": "Тип авторизации", + "noAuth": "Без авторизации", + "bearerToken": "Bearer-токен", + "basicAuth": "Basic-авторизация", + "apiKey": "API-ключ", + "labelToken": "Токен", + "placeholderBearerToken": "Bearer-токен", + "labelUsername": "Имя пользователя", + "placeholderUsername": "Имя пользователя", + "labelPassword": "Пароль", + "placeholderPassword": "Пароль", + "labelKey": "Ключ", + "placeholderApiKeyName": "Имя ключа (напр. X-Api-Key)", + "labelValue": "Значение", + "placeholderValue": "Значение", + "addTo": "Добавить в", + "header": "Заголовок", + "queryParams": "Параметры запроса", + "noAuthHint": "Этот запрос не использует авторизацию." + }, + "responsePanel": { + "emptyTitle": "Готовы к работе?", + "emptyHint": "Введите URL и нажмите «Отправить», чтобы увидеть ответ.", + "responsePreviewAlt": "Предпросмотр ответа", + "previewUnavailable": "Предпросмотр недоступен для этого типа контента", + "status": "Статус", + "time": "Время", + "size": "Размер", + "copyBody": "Копировать тело", + "downloadResponse": "Скачать ответ", + "bodyTab": "Тело", + "previewTab": "Предпросмотр", + "headersTab": "Заголовки", + "binaryRawView": "Бинарные данные не могут быть отображены в режиме исходного текста.", + "errorStatusLabel": "Ошибка", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Новая вкладка" + }, + "collectionsSidebar": { + "title": "API-ресурсы", + "newCollection": "Новая коллекция", + "tabCollections": "Коллекции", + "tabHistory": "История", + "noCollectionsTitle": "Нет коллекций", + "noCollectionsHint": "Создайте коллекцию для организации API-запросов.", + "createCollection": "Создать коллекцию", + "newFolder": "Новая папка", + "rename": "Переименовать", + "delete": "Удалить", + "emptyCollection": "Пустая коллекция", + "recentRequests": "Недавние запросы", + "clearAll": "Очистить все", + "noHistoryTitle": "Истории пока нет", + "noHistoryHint": "Отправьте запрос, чтобы он появился здесь.", + "dialogNewFolderTitle": "Новая папка", + "dialogNewFolderDescription": "Создайте новую папку для организации запросов.", + "labelFolderName": "Название папки", + "placeholderFolderName": "Моя папка", + "createFolder": "Создать папку", + "dialogNewCollectionTitle": "Новая коллекция", + "dialogNewCollectionDescription": "Создайте новую коллекцию верхнего уровня.", + "labelCollectionName": "Название коллекции", + "placeholderCollectionName": "Моя коллекция", + "dialogRenameCollectionTitle": "Переименовать коллекцию", + "dialogRenameCollectionDescription": "Введите новое имя для коллекции.", + "placeholderRenameCollection": "Название коллекции", + "renameAction": "Переименовать", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Новая папка", + "delete": "Удалить", + "emptyFolder": "Пустая папка" + }, + "environmentManager": { + "noEnvironment": "Нет окружения", + "manageDialogTitle": "Управление окружениями", + "placeholderNewEnv": "Новое окружение", + "variablesHeading": "Переменные {name}", + "addVariable": "Добавить переменную", + "columnVariable": "Переменная", + "columnValue": "Значение", + "placeholderKey": "Ключ", + "placeholderValue": "Значение", + "selectEnvToEdit": "Выберите окружение для редактирования" + }, + "codeGenerator": { + "triggerTitle": "Сгенерировать код", + "dialogTitle": "Сгенерировать код", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Копировать", + "errorGenerating": "Ошибка генерации кода" + }, + "keyValueEditor": { + "columnKey": "Ключ", + "columnValue": "Значение", + "placeholderKey": "Ключ", + "placeholderValue": "Значение", + "addItem": "Добавить элемент" + }, + "importCurl": { + "trigger": "Импорт cURL", + "dialogTitle": "Импорт cURL", + "dialogDescription": "Вставьте команду cURL для импорта в виде нового запроса.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Импорт" + }, + "saveRequest": { + "trigger": "Сохранить", + "dialogTitle": "Сохранить запрос", + "dialogDescription": "Сохраните этот запрос в папку коллекции.", + "labelName": "Название запроса", + "placeholderName": "Мой запрос", + "labelFolder": "Папка", + "placeholderFolder": "Выберите папку", + "save": "Сохранить" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Режим редактирования", + "viewOnlyMode": "Режим просмотра", + "insertComponent": "Вставить компонент", + "addUnorderedList": "Маркированный список", + "addOrderedList": "Нумерованный список", + "addTable": "Добавить таблицу" + }, + "addBlock": "Добавить блок", + "media": { + "popoverTitle": "Добавить медиа", + "singleImage": "Одно изображение", + "singleImageDesc": "Загрузить одно изображение", + "multipleImages": "Несколько изображений", + "multipleImagesDesc": "Загрузить сетку изображений", + "video": "Видео", + "videoDesc": "Загрузить видеофайл", + "uploadingAlt": "Загрузка...", + "uploadFailedAlt": "Загрузка не удалась" + }, + "export": { + "fabAria": "Экспорт кода", + "title": "Экспорт кода", + "description": "Скопируйте HTML или JSON вашего контента", + "preview": "Предпросмотр", + "html": "HTML", + "json": "JSON", + "previewOptions": "Параметры предпросмотра", + "enhanceSpaces": "Улучшить отступы", + "livePreview": "Предпросмотр отрендеренного HTML", + "htmlTailwind": "HTML с классами Tailwind CSS", + "editorJson": "Состояние редактора в JSON", + "copyHtml": "Копировать HTML", + "copyJson": "Копировать JSON", + "copied": "Скопировано!" + }, + "template": { + "fabTitle": "Сменить шаблон", + "title": "Выберите шаблон", + "description": "Выберите шаблон для быстрого старта с готовыми макетами и контентом", + "catAll": "Все шаблоны", + "catProductivity": "Продуктивность", + "catCreative": "Творчество", + "catBusiness": "Бизнес", + "catPersonal": "Личное", + "noTemplates": "Шаблоны в этой категории не найдены", + "confirmTitle": "Заменить существующий контент?", + "confirmDescription": "В редакторе есть существующий контент. Применение этого шаблона заменит весь текущий контент.", + "cannotUndo": "Это действие нельзя отменить.", + "cancel": "Отмена", + "replaceContent": "Заменить контент" + }, + "command": { + "searchPlaceholder": "Поиск команд...", + "noResults": "Команды не найдены.", + "turnInto": "Превратить в", + "h1": { + "label": "Заголовок 1", + "description": "Большой заголовок раздела", + "keywords": "h1,заголовок,название,большой" + }, + "h2": { + "label": "Заголовок 2", + "description": "Средний заголовок раздела", + "keywords": "h2,заголовок,подзаголовок" + }, + "h3": { + "label": "Заголовок 3", + "description": "Маленький заголовок раздела", + "keywords": "h3,заголовок,подзаголовок" + }, + "h4": { + "label": "Заголовок 4", + "description": "Мелкий заголовок раздела", + "keywords": "h4,заголовок" + }, + "h5": { + "label": "Заголовок 5", + "description": "Уменьшенный заголовок", + "keywords": "h5,заголовок" + }, + "h6": { + "label": "Заголовок 6", + "description": "Наименьший заголовок", + "keywords": "h6,заголовок" + }, + "p": { + "label": "Параграф", + "description": "Обычный текстовый абзац", + "keywords": "p,параграф,текст,обычный" + }, + "code": { + "label": "Блок кода", + "description": "Фрагмент кода", + "keywords": "код,блоккода,фрагмент,pre" + }, + "blockquote": { + "label": "Цитата", + "description": "Блочная цитата", + "keywords": "цитата,блокцитата,цитирование" + }, + "li": { + "label": "Маркированный список", + "description": "Элемент списка с маркером", + "keywords": "список,маркер,ненумерованный,ul,li" + }, + "ol": { + "label": "Нумерованный список", + "description": "Элемент нумерованного списка", + "keywords": "список,нумерованный,упорядоченный,ol,li" + }, + "img": { + "label": "Изображение", + "description": "Загрузить или вставить изображение", + "keywords": "изображение,img,картинка,фото,загрузка" + }, + "video": { + "label": "Видео", + "description": "Загрузить или вставить видео", + "keywords": "видео,vid,ролик,mp4,загрузка" + }, + "table": { + "label": "Таблица", + "description": "Создать таблицу", + "keywords": "таблица,сетка,строки,столбцы,ячейки" + } + }, + "toasts": { + "tableImported": "Таблица импортирована", + "tableImportedDesc": "Таблица из Markdown успешно импортирована", + "mediaPasted": "Медиа вставлено", + "pasteMixed": "Вставлено {imageCount} изображений и {videoCount} видео.", + "pasteImages": "Вставлено {count} изображений.", + "pasteVideos": "Вставлено {count} видео.", + "pasteFailed": "Вставка не удалась", + "unexpectedError": "Произошла непредвиденная ошибка", + "uploadFailed": "Загрузка не удалась" + }, + "blockBackground": { + "menuItem": "Изменить цвет фона", + "dialogTitle": "Цвет фона блока", + "dialogDescription": "Выберите цвет фона для этого блока", + "tabPreset": "Предустановленные цвета", + "tabCustom": "Свой цвет", + "sectionHeading": "Цвета фона", + "apply": "Применить", + "colors": { + "none": "Без цвета", + "red": "Красный", + "orange": "Оранжевый", + "yellow": "Жёлтый", + "green": "Зелёный", + "blue": "Синий", + "indigo": "Индиго", + "purple": "Фиолетовый", + "pink": "Розовый", + "teal": "Бирюзовый", + "cyan": "Голубой", + "gray": "Серый" + } + }, + "tableDialog": { + "title": "Создать таблицу", + "description": "Создайте новую таблицу или импортируйте из Markdown", + "markdownCheckbox": "У меня есть Markdown-таблица", + "pasteMarkdownLabel": "Вставьте Markdown-таблицу", + "markdownPlaceholder": "| Заголовок 1 | Заголовок 2 | Заголовок 3 |\n|----------|----------|----------|\n| Ячейка 1 | Ячейка 2 | Ячейка 3 |\n| Ячейка 4 | Ячейка 5 | Ячейка 6 |", + "markdownHint": "Вставьте Markdown-таблицу выше. Должны быть строки заголовка и разделителя.", + "rows": "Строки", + "columns": "Столбцы", + "maxDimensions": "Максимум: 20 строк × 10 столбцов", + "cancel": "Отмена", + "importTable": "Импортировать таблицу", + "createTable": "Создать таблицу" + }, + "tableParse": { + "errors": { + "minRows": "Таблица должна содержать хотя бы строку заголовка и строку-разделитель", + "pipeFormat": "Строки таблицы должны начинаться и заканчиваться символом |", + "headerMinCol": "Строка заголовка должна содержать хотя бы один столбец", + "separatorRow": "Вторая строка должна быть разделителем (напр. |---|---|)", + "rowColumnMismatch": "Строка {row} содержит {got} столбцов, ожидалось {expected}", + "parseFailed": "Не удалось разобрать Markdown-таблицу" + } + }, + "link": { + "editLink": "Редактировать ссылку", + "addLink": "Добавить ссылку", + "selectedText": "Выделенный текст: «{text}»", + "linkUrl": "URL ссылки", + "urlPlaceholder": "https://example.com", + "update": "Обновить", + "addLinkButton": "Добавить ссылку", + "toastApplied": "Ссылка применена", + "toastLinkedTo": "Ссылка на: {url}", + "toastRemoved": "Ссылка удалена", + "toastRemovedDesc": "Ссылка удалена из выделения" + }, + "customClass": { + "devMode": "Режим разработчика", + "toggleDevModeAria": "Переключить режим разработчика", + "searchPlaceholderDev": "Поиск классов... (напр. «text», «bg», «flex»)", + "searchPlaceholderUser": "Поиск стилей... (напр. «red», «bold», «shadow»)", + "appliesTitle": "Применяет: {className}", + "noMatches": "Классы по запросу «{query}» не найдены", + "toastClassReplaced": "Класс заменён", + "toastClassReplacedDesc": "Заменён «{replaced}» на «{className}»", + "toastCustomClassApplied": "Пользовательский класс применён", + "toastAppliedClass": "Применён класс: {className}" + }, + "insertComponents": { + "title": "Вставить компонент", + "description": "Выберите компонент для вставки в документ", + "noComponents": "Компоненты недоступны", + "categories": { + "media": "Медиа", + "layout": "Макет", + "interactive": "Интерактив", + "decoration": "Декорация" + }, + "freeImage": { + "name": "Свободное изображение", + "description": "Добавьте изображение, которое можно свободно перемещать и масштабировать" + } + } + }, + "JsonFormatter": { + "title": "Форматировщик JSON", + "description": "Форматируйте, проверяйте и редактируйте JSON в текстовом и древовидном режимах.", + "documentNameText": "Текстовый документ {n}", + "documentNameTree": "Древовидный документ {n}", + "newDocument": "Новый", + "copy": "Копировать", + "save": "Сохранить", + "load": "Загрузить", + "saving": "Сохранение...", + "leftDocNameLabel": "Название левого документа", + "rightDocNameLabel": "Название правого документа", + "toastCopiedText": "Текстовая панель скопирована", + "toastCopiedTree": "Панель дерева скопирована", + "toastCopyFailed": "Не удалось скопировать JSON", + "toastLoginRequired": "Войдите, чтобы сохранять документы", + "toastSaved": "Документ сохранён", + "toastSaveFailed": "Не удалось сохранить документ", + "format": "Форматировать", + "compact": "Сжать", + "sortKeys": "Сортировать ключи", + "repair": "Исправить", + "query": "Запрос", + "toastInvalidJson": "Недопустимый JSON", + "toastAlreadyValid": "JSON уже корректен", + "toastRepaired": "JSON исправлен", + "toastRepairFailed": "Не удалось исправить JSON" + }, + "JwtDecoder": { + "title": "Декодер JWT", + "subtitle": "Просматривайте заголовок и payload. Срок действия берётся из claim exp. Ничего не отправляется на сервер; подпись не проверяется.", + "tokenLabel": "Токен", + "clear": "Очистить", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Копировать", + "expiryLabel": "Срок действия (exp)", + "signaturePresent": "Подпись присутствует (не проверяется)", + "noExp": "В payload нет claim exp.", + "status": { + "expired": "Истёк", + "valid": "Действителен" + }, + "fields": { + "unix": "Unix", + "local": "Локальное", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Выдано (iat)", + "notBefore": "Не ранее (nbf)" + }, + "panels": { + "header": "Заголовок", + "payload": "Payload" + }, + "errors": { + "empty": "Вставьте JWT для декодирования.", + "segments": "JWT должен содержать как минимум заголовок и payload (два сегмента).", + "base64": "Не удалось декодировать заголовок или payload (base64url)." + }, + "relative": { + "future": "через {n} {unit}", + "past": "{n} {unit} назад" + }, + "units": { + "second": "{count, plural, one {секунду} few {секунды} many {секунд} other {секунд}}", + "minute": "{count, plural, one {минуту} few {минуты} many {минут} other {минут}}", + "hour": "{count, plural, one {час} few {часа} many {часов} other {часов}}", + "day": "{count, plural, one {день} few {дня} many {дней} other {дней}}" + }, + "tabs": { + "decode": "Декодировать", + "sign": "Генератор / подпись" + }, + "signer": { + "algLabel": "Алгоритм (alg)", + "tabs": { + "payload": "Полезная нагрузка", + "header": "Заголовок" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Секрет (HS*)", + "secretPlaceholder": "Вставьте HMAC-секрет…", + "privateKeyLabel": "Закрытый ключ PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Алгоритм \"none\" создает неподписанный токен. Это небезопасно и подходит только для тестов.", + "sign": "Подписать", + "verify": "Проверить подпись", + "outputLabel": "Токен (компактный JWS)", + "outputPlaceholder": "Нажмите «Подписать»…", + "copyToken": "Скопировать токен", + "copied": "Скопировано", + "verifyKeyLabel": "Ключ проверки (необязательно)", + "verifyKeyPlaceholder": "Для RS*: вставьте публичный ключ (SPKI) PEM. Для HS*: оставьте пустым, чтобы использовать секрет.", + "verifyKeyHint": "Если пусто, проверка использует ключ подписи (удобно для HS*).", + "verifyStatus": { + "valid": "Подпись верна", + "invalid": "Подпись неверна" + }, + "securityNote": "Все выполняется локально в браузере; ключи и токены не отправляются." + }, + "signErrors": { + "invalidJson": "Заголовок и payload должны быть валидным JSON.", + "unsupportedAlg": "Неподдерживаемый алгоритм.", + "missingKey": "Для этого алгоритма требуется ключ подписи.", + "cryptoFailed": "Не удалось подписать. Проверьте формат ключа и попробуйте снова." + }, + "verifyErrors": { + "empty": "Введите JWT для проверки.", + "segments": "JWT должен содержать ровно 3 сегмента для проверки подписи.", + "unsupportedAlg": "Неподдерживаемый или отсутствующий alg в заголовке.", + "missingKey": "Для этого алгоритма требуется ключ проверки.", + "base64": "Не удалось декодировать заголовок или подпись JWT.", + "cryptoFailed": "Проверка не удалась. Проверьте формат ключа и попробуйте снова." + } + }, + "CertificatePemDecoder": { + "title": "Декодер сертификата / PEM", + "subtitle": "Вставьте сертификат X.509 (-----BEGIN CERTIFICATE-----) или CSR (-----BEGIN CERTIFICATE REQUEST-----). Субъект, издатель, даты и SAN разбираются локально; ничего не отправляется.", + "pemLabel": "Ввод PEM", + "clear": "Очистить", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Разбор…", + "copyHex": "Копировать отпечаток (hex без двоеточий)", + "errors": { + "empty": "Вставьте PEM-сертификат или CSR для декодирования.", + "noBlock": "Не найден PEM-блок сертификата или CSR.", + "parseFailed": "Не удалось разобрать эти PEM-данные." + }, + "block": { + "title": "PEM-блок {index}", + "certificate": "Сертификат X.509", + "csr": "Запрос на подпись сертификата" + }, + "fields": { + "subject": "Субъект", + "issuer": "Издатель", + "notBefore": "Не действителен до", + "notAfter": "Не действителен после", + "serial": "Серийный номер (hex)", + "sha256": "Отпечаток SHA-256", + "sans": "Альтернативные имена субъекта", + "sansNone": "Расширение SAN отсутствует.", + "signatureAlgorithm": "Алгоритм подписи", + "csrIssuerNote": "В CSR нет издателя (он указывается в выданном сертификате).", + "csrValidityNote": "В CSR нет сроков действия (они указываются в выданном сертификате)." + }, + "sanType": { + "dns": "DNS", + "dn": "Отличительное имя", + "email": "Почта", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Зарегистрированный ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Форматтер SQL", + "subtitle": "Форматируйте SQL для MySQL, PostgreSQL или SQLite. Работает в браузере; ничего не загружается.", + "dialectLabel": "Диалект", + "keywordsLabel": "Ключевые слова", + "indentLabel": "Отступ", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "ВЕРХНИЙ РЕГИСТР", + "lower": "нижний регистр", + "preserve": "Сохранить" + }, + "spaces": "{n} пробела(ов)", + "format": "Форматировать", + "clear": "Очистить", + "counter": "{current} / {max} символов", + "inputPanel": "Ввод", + "formattedPanel": "Форматировано", + "copy": "Копировать", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Нажмите «Форматировать»…", + "errors": { + "tooLong": "Ввод ограничен {max} символами.", + "couldNotFormat": "Не удалось отформатировать этот SQL." + } + }, + "YamlFormatter": { + "title": "Форматировщик YAML", + "subtitle": "Форматируйте, проверяйте и конвертируйте YAML в браузере. Ничего не загружается.", + "outputLabel": "Вывод", + "outputModes": { + "yaml": "YAML (форматированный)", + "json": "JSON" + }, + "indentLabel": "Отступ", + "spaces": "{n} пробела", + "format": "Форматировать", + "clear": "Очистить", + "copy": "Копировать", + "counter": "{current} / {max} символов", + "inputPanel": "Ввод", + "outputPanel": "Вывод", + "errors": { + "tooLong": "Ввод ограничен {max} символами.", + "couldNotFormat": "Не удалось разобрать этот YAML." + } + }, + "GraphqlFormatter": { + "title": "Форматтер GraphQL", + "subtitle": "Форматируйте запросы, мутации и подписки GraphQL в браузере. Ничего не загружается.", + "tabs": { + "format": "Форматировать", + "build": "Конструктор запроса" + }, + "outputModeLabel": "Вывод", + "outputModes": { + "pretty": "Читаемая печать", + "minify": "Минификация" + }, + "format": "Форматировать", + "clear": "Очистить", + "counter": "{current} / {max} символов", + "inputPanel": "Ввод", + "outputPanel": "Вывод", + "copy": "Копировать", + "builderHint": "Соберите каркас документа и примените к редактору. Определения переменных в синтаксисе GraphQL (напр. $id: ID!) — скобки необязательны.", + "builder": { + "operation": "Операция", + "operationName": "Имя операции", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Определения переменных", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Набор полей", + "apply": "Вставить во ввод", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Ввод ограничен {max} символами.", + "couldNotFormat": "Не удалось разобрать или вывести этот GraphQL.", + "emptySelection": "Добавьте набор полей перед применением." + } + }, + "RegexTester": { + "title": "Тестер regex", + "subtitle": "Регулярные выражения в стиле JavaScript в реальном времени. Совпадения подсвечиваются; ничего не покидает браузер.", + "patternLabel": "Шаблон", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g глобально", + "i": "i без учёта регистра", + "m": "m многострочный", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(пусто)", + "noFlags": "—", + "matches": "{count, plural, one {совпадение} few {совпадения} many {совпадений} other {совпадений}}", + "testTextLabel": "Тестовый текст", + "counter": "{current} / {max}", + "clear": "Очистить", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Текст ограничен {max} символами.", + "invalid": "Некорректное регулярное выражение.", + "runaway": "Остановлено после {max} совпадений (возможный “разбежавшийся” шаблон)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Конвертер временных меток", + "subtitle": "Unix-секунды (≤10 цифр) или миллисекунды (более длинные числа), ISO-8601 или строки, которые может разобрать браузер. Всё выполняется локально.", + "inputLabel": "Ввод", + "placeholder": "1744200000 или 2025-04-09T12:00:00Z", + "now": "Сейчас", + "copy": "Копировать", + "hint": "Вставьте значение или нажмите Сейчас.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Относительно", + "human": "Человекочитаемо" + }, + "fields": { + "seconds": "Секунды", + "milliseconds": "Миллисекунды", + "utc": "UTC", + "local": "Локально", + "distance": "Разница" + }, + "errors": { + "empty": "Введите Unix-метку времени, ISO-строку или текст даты.", + "invalidNumber": "Некорректная числовая метка времени.", + "outOfRange": "Вне диапазона для JavaScript Date.", + "couldNotParse": "Не удалось распознать как Unix, ISO-8601 или строку даты браузера." + } + }, + "HttpStatusCodes": { + "title": "Коды состояния HTTP", + "subtitle": "Поиск и справочник кодов состояния ответов HTTP (1xx5xx) с краткими описаниями и ссылками на RFC. Работает локально.", + "header": { + "title": "Коды состояния HTTP", + "description": "Справочник с поиском по кодам ответов HTTP с описаниями и ссылками на RFC." + }, + "searchPlaceholder": "Поиск: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Фильтр класса…", + "classFilterAll": "Все классы", + "resultsCount": "{count} из {total}", + "table": { + "code": "Code", + "phrase": "Фраза", + "description": "Описание", + "copy": "Копировать" + }, + "noResults": "Нет совпадений для «{query}».", + "specPrefix": "Спец.:", + "copied": "Код скопирован" + }, + "CronBuilder": { + "title": "Конструктор cron-выражений", + "subtitle": "Стандартный cron из 5 полей: minute hour day month weekday. Парсер использует seconds=0 внутри. Опциональный формат из 6 полей (sec min hour dom mon dow) работает в редакторе.", + "tabs": { + "builder": "Визуальный конструктор", + "expression": "Выражение" + }, + "presetsLabel": "Пресеты", + "fieldsLabel": "Поля", + "rawExpressionLabel": "Сырое выражение", + "expressionHint": "Уберите фокус, чтобы разобрать и синхронизировать конструктор. {count} {count, plural, one {токен} few {токена} many {токенов} other {токенов}}.", + "currentExpressionLabel": "Текущее выражение", + "copy": "Копировать", + "quickPick": "Быстрый выбор", + "emptyExpression": "—", + "plainLanguageLabel": "Естественный язык", + "nextRunsLabel": "Следующие запуски (местное время)", + "fields": { + "minute": "Минуты", + "hour": "Часы", + "dayOfMonth": "День месяца", + "month": "Месяц", + "dayOfWeek": "День недели" + }, + "presets": { + "everyMinute": "Каждую минуту", + "every5Minutes": "Каждые 5 минут", + "every15Minutes": "Каждые 15 минут", + "everyHour": "Каждый час (в :00)", + "every6Hours": "Каждые 6 часов", + "dailyMidnight": "Ежедневно в полночь", + "daily0900": "Ежедневно в 09:00", + "weekdays0900": "По будням в 09:00", + "weeklySunday": "Еженедельно (вс 00:00)", + "monthlyFirst": "Ежемесячно (1-е 00:00)", + "yearlyJan1": "Ежегодно (1 янв 00:00)" + }, + "picks": { + "minute": { + "every": "каждую минуту" + }, + "hour": { + "every": "каждый час" + }, + "dom": { + "any": "любой день", + "last": "последний день" + }, + "month": { + "every": "каждый месяц" + }, + "dow": { + "any": "любой день", + "weekdays": "будни", + "weekend": "выходные" + } + }, + "months": { + "jan": "янв", + "feb": "фев", + "mar": "мар", + "apr": "апр", + "may": "май", + "jun": "июн", + "jul": "июл", + "aug": "авг", + "sep": "сен", + "oct": "окт", + "nov": "ноя", + "dec": "дек" + }, + "weekdays": { + "sun": "вс", + "mon": "пн", + "tue": "вт", + "wed": "ср", + "thu": "чт", + "fri": "пт", + "sat": "сб" + }, + "errors": { + "fieldCount": "Ожидается 5 полей (minute hour day month weekday) или 6 с секундами.", + "invalid": "Некорректное cron-выражение." + } + }, + "Base64": { + "title": "Base64 кодирование / декодирование", + "subtitle": "Кодируйте текст в Base64 или декодируйте строки Base64", + "upload": "Загрузить", + "clear": "Очистить", + "modes": { + "encode": "Кодировать", + "decode": "Декодировать" + }, + "swapTitle": "Поменять местами ввод и вывод", + "panels": { + "plainText": "Текст", + "base64Input": "Ввод Base64", + "base64Output": "Вывод Base64", + "decodedText": "Декодированный текст" + }, + "placeholders": { + "encode": "Введите текст для кодирования в Base64…", + "decode": "Вставьте Base64 для декодирования…" + }, + "dropHere": "Перетащите файл сюда", + "charCount": "{count} символов", + "download": { + "title": "Скачать результат", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Скопировать в буфер обмена", + "outputPlaceholder": "Результат появится здесь…", + "errors": { + "invalidBase64": "Некорректная строка Base64. Проверьте ввод.", + "encodeFailed": "Не удалось закодировать ввод." + } + }, + "UrlEncode": { + "title": "URL кодирование / декодирование", + "subtitle": "Процентное кодирование/декодирование через encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Загрузить", + "clear": "Очистить", + "modes": { + "encode": "Кодировать", + "decode": "Декодировать" + }, + "swapTitle": "Поменять местами ввод и вывод", + "panels": { + "plainText": "Текст", + "encodedInput": "Кодированный ввод", + "encodedOutput": "Кодированный вывод", + "decodedText": "Декодированный текст" + }, + "placeholders": { + "encode": "Введите текст для percent-encoding…", + "decode": "Вставьте percent-encoded текст для декодирования…" + }, + "dropHere": "Перетащите файл сюда", + "charCount": "{count} символов", + "download": { + "title": "Скачать результат", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Скопировать в буфер обмена", + "outputPlaceholder": "Результат появится здесь…", + "errors": { + "invalidEncoding": "Некорректное percent-encoding. Проверьте неполные пары % или escape-последовательности.", + "encodeFailed": "Не удалось закодировать (некорректный Unicode во вводе)." + } + }, + "UrlParser": { + "title": "URL-парсер", + "subtitle": "Разберите URL на протокол, хост, путь, параметры запроса и хэш (фрагмент).", + "upload": "Загрузить", + "clear": "Очистить", + "panels": { + "input": "Ввод URL", + "output": "Результат" + }, + "placeholders": { + "input": "Вставьте URL (например, https://example.com/path?foo=bar#section) или хост/путь (например, example.com/path)…" + }, + "dropHere": "Перетащите файл сюда", + "charCount": "{count} символов", + "outputPlaceholder": "Разобранные компоненты появятся здесь…", + "fields": { + "protocol": "Протокол", + "host": "Хост", + "path": "Путь", + "hash": "Хэш", + "queryParams": "Параметры запроса" + }, + "paramCount": "{count} параметров", + "noParams": "Параметров запроса нет.", + "paramKey": "Ключ", + "paramValue": "Значение", + "copyTitle": "Скопировать в буфер обмена", + "copyParamTitle": "Скопировать как key=value", + "download": { + "title": "Скачать JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Некорректный URL. Укажите hostname (example.com) и проверьте символы." + } + }, + "UuidGenerator": { + "title": "Генератор UUID / ULID", + "subtitle": "Генерируйте UUID (RFC 9562) или ULID в браузере. Пакетный экспорт до {max} за запуск.", + "format": "Формат", + "kindHints": { + "ulid": "Сортируемый, безопасный для URL идентификатор из 26 символов", + "uuid4": "Случайный (RFC 9562)", + "uuid7": "Упорядочен по времени, Unix мс + случайность", + "uuid1": "MAC/время (RFC 9562)", + "uuid6": "Переупорядоченное григорианское время", + "uuid3": "MD5‑хеш имени + пространства имён", + "uuid5": "SHA‑1‑хеш имени + пространства имён", + "nil": "Нулевой UUID (все нули, RFC 9562)" + }, + "countLabel": "Количество (1–{max})", + "nameLabel": "Имя (входная строка)", + "namePlaceholder": "например my.app/resource", + "bulkHint": "В пакетном режиме добавляются #0, #1, … чтобы каждый ID был уникален (v3/v5 детерминированы).", + "namespace": "Пространство имён", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Свой UUID" + }, + "customNamespaceLabel": "UUID пространства имён", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Сгенерировать", + "output": "Вывод", + "lines": "{count} строк", + "download": { + "title": "Скачать .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Копировать всё", + "uppercaseToggle": "Вывод заглавными буквами", + "hyphensToggle": "Показывать дефисы", + "copyJsonTitle": "Копировать как массив JSON", + "outputPlaceholder": "Нажмите «Сгенерировать»…", + "errors": { + "invalidCustomNamespace": "Пользовательское пространство имён должно быть валидным UUID.", + "missingName": "Имя обязательно для UUID v3 и v5.", + "unknown": "Не удалось сгенерировать идентификаторы." + } + }, + "SecretApiKeyGenerator": { + "title": "Генератор секрета / ключа API", + "subtitle": "Создавайте криптографически случайные строки с настраиваемой длиной и алфавитом. Массовый экспорт до {max} за запуск. Дополняет генератор UUID / ULID.", + "alphabetLabel": "Алфавит", + "alphabetHint": "Дубликаты символов удаляются. Каждый символ выбирается равномерно через crypto.getRandomValues и отбор с отбраковкой (без модулярного смещения).", + "alphabetCount": "Уникальных символов: {count}", + "presetsLabel": "Пресеты", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Буквы и цифры", + "hex": "Шестнадцатеричный (строчные)", + "asciiPrintable": "Печатаемый ASCII" + }, + "lengthLabel": "Длина одного секрета (1–{maxLen})", + "countLabel": "Количество (1–{maxBulk})", + "generate": "Сгенерировать", + "output": "Вывод", + "lines": "{count} строк", + "download": { + "title": "Скачать .txt", + "filename": "secrets.txt" + }, + "copyTitle": "Копировать всё", + "outputPlaceholder": "Нажмите «Сгенерировать»…", + "entropy": "{bits} бит энтропии", + "strength": { + "weak": "Слабый", + "fair": "Средний", + "strong": "Сильный" + }, + "excludeAmbiguous": "Исключить неоднозначные символы", + "excludeAmbiguousHint": "Удаляет {chars} — символы, которые легко перепутать или которые опасны для shell.", + "prefixLabel": "Префикс ключа (необязательно)", + "errors": { + "emptyAlphabet": "Добавьте в алфавит хотя бы один символ.", + "alphabetTooLong": "После удаления дубликатов в алфавите может быть не больше {maxAlpha} символов.", + "lengthOutOfRange": "Длина должна быть от 1 до {maxLen}.", + "countOutOfRange": "Количество должно быть от 1 до {maxBulk}.", + "unknown": "Не удалось сгенерировать секреты." + } + }, + "LoremIpsum": { + "title": "Генератор Lorem Ipsum", + "subtitle": "Генерируйте замещающий латинский текст для ваших макетов и прототипов.", + "paragraphs": "Абзацы", + "sentences": "Предложения", + "words": "Слова", + "count": "Количество", + "generate": "Сгенерировать", + "copy": "Копировать", + "startWith": "Начать с 'Lorem ipsum...'", + "outputPlaceholder": "Результат появится здесь...", + "outputType": "Тип вывода", + "bulletList": "Маркированный список", + "htmlMarkup": "HTML-разметка", + "htmlMarkupHint": "Обернуть вывод в абзацы или использовать список для режима маркеров", + "output": "Вывод", + "download": "Скачать" + }, + "ColorPicker": { + "title": "Выбор и конвертер цветов", + "subtitle": "Конвертируйте HEX, RGB и HSL; создавайте гармоничные палитры. Вся обработка выполняется в браузере.", + "nativePicker": "Встроенный выбор цвета", + "chooseColor": "Выберите цвет", + "eyeDropper": "Пипетка", + "invalidHex": "Неверный цвет HEX.", + "cssValues": "CSS-значения", + "palettes": "Палитры", + "copied": "Скопировано", + "clickToCopy": "Нажмите для копирования", + "copyAllHex": "Копировать все HEX", + "copy": "Копировать", + "copyColor": "Копировать {color}", + "paletteShades": "Оттенки", + "paletteComplementary": "Дополнительный", + "paletteTriadic": "Триадический", + "paletteAnalogous": "Аналогичный", + "paletteSplit": "Разделённый доп.", + "paletteSquare": "Квадратный" + }, + "ContrastChecker": { + "title": "Проверка контраста", + "subtitle": "Считает коэффициент контраста WCAG 2.1 между передним планом и фоном. Показывает AA и AAA для обычного и крупного текста. Всё в браузере.", + "foreground": "Текст / передний план", + "background": "Фон", + "hexPlaceholder": "#RRGGBB", + "swap": "Поменять цвета местами", + "chooseForeground": "Выбрать цвет переднего плана", + "chooseBackground": "Выбрать цвет фона", + "eyeDropper": "Пипетка", + "ratio": "Коэффициент контраста", + "copyRatio": "Копировать значение", + "copied": "Скопировано", + "previewHeading": "Предпросмотр", + "previewBody": "В чащах юга жил бы цитрус? Да, но фальшивый экземпляр! Широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства.", + "previewCaption": "Пример текста обычного размера.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — обычный текст (4,5:1)", + "levelAALarge": "AA — крупный текст (3:1)", + "levelAAANormal": "AAA — обычный текст (7:1)", + "levelAAALarge": "AAA — крупный текст (4,5:1)", + "pass": "Соответствует", + "fail": "Не соответствует", + "invalidHex": "Некорректный цвет HEX.", + "largeTextHint": "Крупный текст: от 18 pt обычного или от 14 pt полужирного (по WCAG)." + }, + "DiffChecker": { + "title": "Сравнение различий", + "subtitle": "Построчное сравнение двух текстов бок о бок. Всё остаётся в вашем браузере.", + "rowsCount": "{count} строк", + "truncatedWarning": "Показаны первые {max} строк.", + "limitExceeded": "Каждая сторона ограничена {max} символами.", + "clearBoth": "Очистить оба", + "original": "Оригинал", + "modified": "Изменённый", + "originalPlaceholder": "Вставьте исходный текст…", + "modifiedPlaceholder": "Вставьте изменённый текст…", + "comparisonLabel": "Сравнение бок о бок", + "emptyState": "Введите текст в оба поля для сравнения." + }, + "JsonSchemaGenerator": { + "title": "Генератор JSON-схемы", + "subtitle": "Вставьте корректный JSON, чтобы сгенерировать JSON-схему или типизированные модели. Всё выполняется в браузере.", + "languageLabel": "Язык вывода", + "inputLabel": "Ввод JSON", + "inputPlaceholder": "Вставьте JSON сюда…", + "outputLabel": "Сгенерированный вывод", + "outputPlaceholder": "Схема или сгенерированные типы появятся здесь…", + "parseError": "Некорректный JSON", + "clear": "Очистить", + "copy": "Копировать", + "copied": "Скопировано в буфер обмена", + "copyFailed": "Не удалось скопировать", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-схема (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Загрузка редактора…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Изображение в Base64", + "subtitle": "Отбросьте изображение, получите URI данных или необработанную строку Base64. Все остается в вашем браузере.", + "upload": "Загрузить изображение", + "clear": "Прозрачный", + "modes": { + "dataUri": "URI данных", + "rawString": "Необработанная база64" + }, + "panels": { + "imageInput": "Ввод изображения", + "output": "Вывод Base64" + }, + "dropHere": "Перетащите изображение сюда", + "charCount": "{count} символов", + "placeholders": { + "uploadImage": "Перетащите изображение или нажмите «Загрузить», чтобы начать." + }, + "fileTooLarge": "Файл слишком большой. Обработка может привести к зависанию вашего браузера.", + "copyTitle": "Скопировать в буфер обмена", + "download": { + "title": "Скачать текст", + "filename": "изображение-base64.txt" + } + }, + "ImageCompressor": { + "title": "Сжатие изображений", + "subtitle": "Уменьшайте размер JPEG, PNG или WebP локально с настройкой качества — ничего не загружается.", + "upload": "Загрузить изображение", + "clear": "Очистить", + "dropHere": "Перетащите изображение сюда", + "placeholders": { + "uploadImage": "Перетащите файл или нажмите, чтобы выбрать изображение." + }, + "fileTooLarge": "Файл слишком большой (макс. {maxMb} МБ).", + "unsupportedFormat": "Неподдерживаемый тип файла. Используйте JPEG, PNG, WebP, GIF, AVIF или BMP.", + "compressFailed": "Не удалось сжать изображение. Попробуйте другой файл или формат.", + "panels": { + "original": "Исходное", + "compressed": "Предпросмотр" + }, + "outputFormat": "Формат вывода", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Качество", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG в браузере без потерь; ползунок качества действует только для JPEG и WebP.", + "sizes": { + "original": "Исходный: {size}", + "compressed": "После сжатия: {size}", + "saved": "на {percent}% меньше" + }, + "largerWarning": "Выходной файл больше исходного — смените формат или качество.", + "download": "Скачать", + "downloadFilename": "сжатое-изображение", + "processing": "Сжатие…", + "emptyCompressed": "Предпросмотр появится здесь." + }, + "CssGradientBuilder": { + "title": "Конструктор CSS-градиентов", + "subtitle": "Визуальный конструктор градиентов CSS с контролем угла, остановками цвета и копированием CSS одним щелчком мыши.", + "preview": "Предварительный просмотр градиента", + "settings": "Настройки", + "type": "Тип", + "linear": "Линейный", + "radial": "Радиальный", + "angle": "Угол", + "stops": "Цветовые остановки", + "color": "Цвет", + "position": "Позиция (%)", + "addStop": "Добавить остановку", + "deleteStop": "Удалить остановку", + "output": "CSS-вывод", + "copy": "Копировать CSS", + "copied": "Скопировано!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Генератор .gitignore", + "subtitle": "Мгновенно создавайте объединенные файлы .gitignore для определенного технологического стека или комбинации.", + "searchPlaceholder": "Стеки поиска (например, Node, Python, macOS)...", + "noResults": "Стеки не найдены.", + "selectedStacks": "Выбранные стеки", + "output": "Предварительный просмотр вывода", + "copy": "Копировать в буфер обмена", + "copied": "Скопировано!", + "download": "Скачать .gitignore", + "downloading": "Загрузка...", + "errorFetching": "Не удалось получить список самых популярных пользователей.", + "errorGenerating": "Не удалось создать .gitignore из выбранных стеков.", + "generating": "Создание...", + "emptyState": "Выберите один или несколько стеков технологий для создания вашего .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Загрузите CSV или Excel, отредактируйте JSON, затем скачайте CSV или XLSX. Только табличные данные; даты Excel становятся ISO-временными метками в JSON.", + "dropTitle": "Перетащите файл CSV или Excel", + "dropHint": "Для .xlsx / .xls используется первый лист. В CSV должна быть строка заголовков.", + "chooseFile": "Выбрать файл", + "jsonLabel": "JSON (массив объектов)", + "jsonPlaceholder": "Вставьте JSON-массив объектов или импортируйте CSV / Excel…", + "loadSample": "Загрузить пример", + "actions": { + "format": "Форматировать JSON", + "clear": "Очистить", + "copy": "Копировать", + "copied": "Скопировано", + "downloadCsv": "Скачать CSV", + "downloadXlsx": "Скачать Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Данные", + "toastLoadedCsv": "CSV загружен в редактор", + "toastLoadedExcel": "Excel загружен в редактор", + "toastFormatted": "JSON отформатирован", + "toastExportedCsv": "CSV скачан", + "toastExportedXlsx": "Excel скачан", + "toastCopied": "Скопировано в буфер", + "footnote": "Всё выполняется в браузере. JSON должен быть непустым массивом объектов. Вложенные значения сериализуются в JSON в ячейках CSV.", + "errors": { + "unsupportedFile": "Используйте файл .csv, .xlsx или .xls.", + "parseFailed": "Не удалось разобрать файл.", + "invalidJson": "Некорректный JSON", + "exportFailed": "Ошибка экспорта", + "copyFailed": "Не удалось скопировать" + } + }, + "SnippetManager": { + "title": "Менеджер фрагментов кода", + "subtitle": "Храните сниппеты в браузере или войдите, чтобы синхронизировать их с аккаунтом. Переключайтесь между просмотром и правкой, копируйте в один клик, выбирайте язык или автоопределение и форматируйте JSON, SQL или код, поддерживаемый Monaco.", + "snippets": "Сниппеты", + "newSnippet": "Новый сниппет", + "newSnippetTitle": "Новый сниппет", + "defaultSnippetTitle": "Добро пожаловать", + "defaultSnippetCode": "// Добро пожаловать в фрагменты кода\n// Попробуйте просмотр и правку, копирование, форматирование и автоязык\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Поиск сниппетов…", + "searchAria": "Поиск сниппетов", + "emptySearch": "Нет сниппетов по запросу.", + "snippetTitle": "Заголовок", + "snippetTitlePlaceholder": "Имя сниппета", + "language": "Язык", + "detectedHint": "Подсветка: {lang}", + "badgeAuto": "Авто · {lang}", + "modeView": "Просмотр", + "modeEdit": "Правка", + "copy": "Копировать", + "copied": "Скопировано", + "format": "Форматировать", + "cancel": "Отмена", + "deleteTitle": "Удалить сниппет?", + "deleteDescription": "«{title}» будет удалён из этого браузера.", + "deleteConfirm": "Удалить", + "editorAria": "Редактор кода сниппета", + "editorLoading": "Загрузка редактора…", + "toastCopied": "Скопировано в буфер", + "toastCopyFailed": "Не удалось скопировать", + "toastNothingToCopy": "Нечего копировать", + "toastFormatted": "Отформатировано", + "toastFormatFailed": "Не удалось отформатировать", + "toastFormatJsonInvalid": "Неверный JSON — сначала исправьте синтаксис", + "toastFormatUnsupported": "Нет форматтера для этого языка — попробуйте JSON или SQL или режим правки", + "toastDeleted": "Сниппет удалён", + "toastLoadFailed": "Не удалось загрузить сниппеты с сервера.", + "toastSyncFailed": "Не удалось синхронизировать с сервером.", + "languages": { + "auto": "Автоопределение", + "plaintext": "Простой текст", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL-клиент", + "dialogTitle": "SQL-подключения", + "mobileSheetTitle": "SQL-проводник", + "newTabLabel": "Новая вкладка запроса", + "vaultLockedTitle": "Хранилище заблокировано", + "vaultLockedDesc": "Разблокируйте хранилище для доступа к сохранённым подключениям.", + "heroTitle": "SQL-клиент", + "heroDesc": "Подключайтесь к PostgreSQL, MySQL или MariaDB. Учётные данные шифруются в браузере.", + "heroAddConnection": "Добавить подключение", + "selectTableTitle": "Выберите таблицу или откройте вкладку", + "selectTableDesc": "Нажмите на таблицу на боковой панели или используйте ⌘↩.", + "toastLoadFail": "Не удалось загрузить подключения." + }, + "connection": { + "titleNew": "Новое SQL-подключение", + "titleEdit": "Изменить подключение", + "labelDbType": "Тип базы данных", + "labelName": "Имя подключения", + "placeholderName": "Моя рабочая БД", + "labelHostPort": "Хост и порт", + "labelDatabase": "База данных", + "placeholderDatabase": "mydb", + "labelUsername": "Имя пользователя", + "labelPassword": "Пароль", + "labelSsl": "Включить SSL / TLS", + "sslDesc": "Шифровать подключение к серверу", + "securityNote": "Учётные данные шифруются AES-256 в браузере перед сохранением", + "btnTest": "Проверить подключение", + "btnSave": "Сохранить и подключиться", + "btnUpdate": "Обновить", + "btnCancelEdit": "Отменить редактирование", + "savedConnections": "Сохранённые подключения", + "emptySaved": "Сохранённые подключения появятся здесь", + "confirmDelete": "Удалить это подключение?", + "toastFillFields": "Заполните хост и базу данных перед тестом.", + "toastConnectOk": "Подключено! {version}", + "toastConnectFail": "Ошибка подключения: {message}", + "toastUnlockVault": "Сначала разблокируйте хранилище.", + "toastFieldsRequired": "Хост и база данных обязательны.", + "toastUpdated": "Подключение обновлено.", + "toastSaved": "Подключение сохранено.", + "toastSaveFail": "Ошибка сохранения: {message}", + "toastDeleted": "Подключение удалено.", + "toastDeleteFail": "Не удалось удалить подключение." + }, + "sidebar": { + "title": "SQL-проводник", + "tooltipAddConnection": "Добавить подключение", + "tooltipRefreshSchema": "Обновить схему", + "noConnections": "Нет сохранённых подключений", + "toastSchemaFail": "Не удалось загрузить схему: {message}" + }, + "editor": { + "btnRun": "Выполнить", + "executing": "Выполнение запроса…", + "emptyTitle": "Выполните запрос для просмотра результатов", + "emptyHint": "Нажмите ⌘↩ или кнопку Выполнить", + "toastNoConnection": "Нет активного подключения." + }, + "results": { + "filterPlaceholder": "Фильтр результатов…", + "rowCount": "{count} {count, plural, one {строка} few {строки} other {строк}}", + "rowCountFiltered": "{filtered} / {total} строк", + "exportCsv": "CSV", + "noRows": "Строки не возвращены" + } + }, + "QrCodeGenerator": { + "title": "Генератор QR-кодов", + "subtitle": "Превратите любой текст или URL в PNG QR-код. Всё обрабатывается локально в браузере.", + "contentLabel": "Содержимое", + "contentPlaceholder": "https://example.com, обычный текст, строка Wi‑Fi, vCard…", + "styleLabel": "Стиль", + "dotTypeLabel": "Форма точек", + "dotTypeSquare": "Квадрат", + "dotTypeDots": "Точки", + "dotTypeRounded": "Скруглённые", + "dotTypeExtraRounded": "Максимально скруглённые", + "dotTypeClassy": "Элегантные", + "dotTypeClassyRounded": "Элегантные скруглённые", + "cornerSquareLabel": "Угловые квадраты", + "cornerSquareDot": "Точка", + "cornerSquareSquare": "Квадрат", + "cornerSquareExtraRounded": "Максимально скруглённые", + "cornerDotLabel": "Угловые точки", + "cornerDotDot": "Точка", + "cornerDotSquare": "Квадрат", + "eccLabel": "Коррекция ошибок", + "eccL": "Низкая (~7%)", + "eccM": "Средняя (~15%)", + "eccQ": "Квартиль (~25%)", + "eccH": "Высокая (~30%)", + "marginLabel": "Поле (модули)", + "widthLabel": "Ширина изображения", + "widthEffective": "Рендер {px} px", + "darkColor": "Тёмные модули", + "lightColor": "Светлые модули", + "resetColors": "Сбросить цвета", + "logoLabel": "Логотип по центру", + "logoMarginLabel": "Отступ", + "uploadLogo": "Загрузить изображение", + "removeLogo": "Удалить логотип", + "downloadPng": "Скачать PNG", + "downloadSvg": "Скачать SVG", + "copyImage": "Копировать изображение", + "copied": "Скопировано", + "clear": "Очистить", + "previewAlt": "Предпросмотр QR-кода", + "emptyHint": "Введите или вставьте текст для предпросмотра.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "Не удалось создать QR-код. Попробуйте короче текст или ниже коррекцию.", + "copyUnsupported": "Браузер не поддерживает копирование изображения в буфер.", + "copyFailed": "Не удалось скопировать в буфер." + } + }, + "IpSubnetCalculator": { + "title": "Калькулятор IP / подсети", + "subtitle": "Сведения CIDR для IPv4 и IPv6 в браузере—без внешних запросов.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Сеть", + "placeholderIpv4": "10.0.0.0/24 или 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 или 2001:db8::1/64", + "hintIpv4": "Используйте CIDR (например /24) или IPv4 с маской в точечной нотации.", + "hintIpv6": "Укажите длину префикса (например /64). Без неё — /128.", + "copyValue": "Копировать значение", + "rows": { + "cidr": "CIDR", + "network": "Адрес сети", + "netmask": "Маска подсети", + "wildcard": "Инвертированная маска", + "broadcast": "Широковещание", + "firstHost": "Первый хост", + "lastHost": "Последний хост", + "totalAddresses": "Всего адресов", + "usableHosts": "Доступные хосты", + "prefixLength": "Длина префикса (бит)", + "networkIpv6": "Адрес сети", + "lastIpv6": "Последний адрес в диапазоне", + "totalAddressesIpv6": "Адресов в подсети" + }, + "usableNote": { + "pointToPoint": "/31 point-to-point использует оба адреса как хосты (RFC 3021).", + "singleHost": "/32 — один хост." + }, + "ipv6RangeHint": { + "normal": "Показан полный математический диапазон; правила маршрутизатора могут отличаться.", + "single": "/128 — один адрес.", + "full": "/0 охватывает весь IPv6." + }, + "errors": { + "invalidIpv4": "Неверная сеть IPv4. Попробуйте CIDR или адрес + маску.", + "invalidIpv6": "Неверная сеть IPv6. Используйте адрес/префикс, напр. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Генератор хешей", + "subtitle": "Контрольные суммы MD5, SHA-1 и SHA-2 для текста или файлов; bcrypt для паролей (текстовый ввод). Всё локально в браузере.", + "algorithmLabel": "Алгоритм", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Текст", + "modeFile": "Файл", + "inputLabelText": "Ввод", + "placeholderText": "Введите или вставьте текст…", + "chooseFile": "Выбрать файл", + "clearFile": "Очистить", + "fileHint": "Файлы читаются только в памяти; макс. {maxMb} МБ. Загрузки на сервер нет.", + "outputLabel": "Дайджест (hex)", + "copyHash": "Копировать дайджест", + "copied": "Скопировано", + "byteCount": "{count, plural, one {# байт} few {# байта} many {# байт} other {# байт}}", + "charCount": "{count, plural, one {# символ} few {# символа} many {# символов} other {# символа}}", + "emptyHint": "Введите текст или выберите файл.", + "securityNote": "Для новых задач безопасности MD5 и SHA-1 не рекомендуются; используйте SHA-256 или SHA-512.", + "errors": { + "fileTooLarge": "Файл больше {maxMb} МБ.", + "fileReadFailed": "Не удалось прочитать файл." + }, + "bcryptRoundsLabel": "Стоимость (раунды)", + "bcryptRoundsHint": "Больше значение — медленнее и устойчивее к перебору. Обычно по умолчанию 10.", + "fileTabDisabledTitle": "bcrypt хеширует только пароли из текста, а не произвольные файлы.", + "placeholderBcrypt": "Введите или вставьте пароль для хеширования…", + "outputLabelBcrypt": "Хеш bcrypt", + "copyBcrypt": "Копировать хеш", + "emptyHintBcrypt": "Введите пароль для вычисления bcrypt-хеша.", + "securityNoteBcrypt": "Каждый результат bcrypt содержит случайную соль; один и тот же пароль даёт разную строку каждый раз. Это нормально. В продакшене используйте высокую стоимость.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Песочница шифрования", + "subtitle": "AES-GCM в браузере: шифруйте в JSON-пакет с паролем или сырым ключом и расшифровывайте локально. Учебный и иногда практичный инструмент — ничего не отправляется.", + "tabEncrypt": "Зашифровать", + "tabDecrypt": "Расшифровать", + "keyModeLabel": "Ключевой материал", + "modePassphrase": "Парольная фраза (PBKDF2 + AES-GCM)", + "modeRawKey": "Сырой ключ AES (hex или Base64)", + "aesBitsLabel": "Длина ключа AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Кодировка сырого ключа", + "encodingAuto": "Авто (hex, если похоже на hex, иначе Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Сырой ключ", + "rawKeyPlaceholder": "Ключ 32 байта как 64 hex-символа или Base64 для AES-256 (длина зависит от выбранного AES выше).", + "rawKeyPlaceholderDecrypt": "Нужно только если в пакете keyDerivation \"none\".", + "passphraseLabel": "Парольная фраза", + "passphrasePlaceholder": "Пароль используется только в браузере для вывода ключа.", + "passphrasePlaceholderDecrypt": "Нужно только если пакет использует PBKDF2-SHA256.", + "pbkdf2IterLabel": "Итерации PBKDF2", + "pbkdf2Hint": "От 10 000 до 2 000 000. Значение по умолчанию как в типичных хранилищах паролей.", + "plaintextLabel": "Открытый текст", + "plaintextPlaceholder": "Введите или вставьте сообщение для шифрования…", + "bundleInputLabel": "Зашифрованный пакет (JSON)", + "bundlePlaceholder": "Вставьте JSON из «Зашифровать» (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Зашифрованный пакет", + "plaintextOutputLabel": "Открытый текст", + "encryptButton": "Зашифровать", + "decryptButton": "Расшифровать", + "copyBundle": "Копировать JSON", + "copyPlaintext": "Копировать открытый текст", + "copied": "Скопировано", + "emptyBundleHint": "Зашифрованный JSON появится здесь после шифрования.", + "emptyDecryptHint": "Расшифрованный текст появится здесь.", + "decryptKeyHint": "Используйте сырой ключ, если keyDerivation — \"none\"; парольную фразу при PBKDF2-SHA256. В пакете хранятся соль, итерации, IV и длина AES.", + "securityNote": "Ключи и открытый текст не покидают браузер. Для продакшн-секретов используйте специализированные средства.", + "errors": { + "emptyKey": "Введите сырой ключ, соответствующий размеру AES.", + "emptyPassphrase": "Введите парольную фразу.", + "badKeyMaterial": "Не удалось прочитать ключ как hex или Base64.", + "wrongKeyLength": "Длина ключа не соответствует выбранному AES (16 / 24 / 32 байта).", + "invalidJson": "Некорректный JSON.", + "invalidBundle": "Этот JSON не является поддерживаемым пакетом шифрования.", + "decryptFailed": "Расшифровка не удалась — неверный секрет, повреждённый пакет или параметры.", + "encryptFailed": "Шифрование не удалось. Проверьте Web Crypto и ввод." + } + }, + "HmacGenerator": { + "title": "Генератор HMAC", + "subtitle": "Для подписи вебхуков и проверки интеграций API. HMAC-SHA1/256/384/512 в hex или Base64—вычисляется локально в браузере.", + "secretLabel": "Секретный ключ", + "messageLabel": "Сообщение / полезная нагрузка", + "placeholderSecret": "Вставьте секрет подписи…", + "placeholderMessage": "Вставьте сырое тело или строку для проверки (часто точные байты тела вебхука).", + "algorithmLabel": "Алгоритм HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Формат вывода", + "formatHex": "Hex (нижний регистр)", + "formatBase64": "Base64", + "outputLabel": "Подпись", + "copyOutput": "Копировать подпись", + "copied": "Скопировано", + "byteCountHint": "{count, plural, one {# байт} few {# байта} many {# байт} other {# байта}} (UTF-8)", + "emptyHint": "Введите секретный ключ для вычисления HMAC.", + "securityNote": "Никогда не делитесь секретом подписи. Эта страница не загружает данные — всё выполняется в браузере.", + "errors": { + "cryptoFailed": "Не удалось вычислить HMAC. Проверьте поддержку Web Crypto и повторите попытку.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Генератор кода TOTP / 2FA", + "subtitle": "Вставьте секрет Base32 и получите текущий шестизначный код RFC 6238 (SHA-1, 30 с). Всё локально в браузере—удобно тестировать вход или MFA.", + "secretLabel": "Секрет (Base32)", + "placeholderSecret": "Вставьте секретный ключ (буквы A–Z и цифры 2–7, как при настройке аутентификатора)…", + "codeLabel": "Текущий код", + "copyCode": "Копировать код", + "copied": "Скопировано", + "nextIn": "Обновление через {seconds} с", + "standardHint": "Шаг времени 30 с, 6 цифр, HMAC-SHA-1 (совместимо с Google Authenticator).", + "digitsLabel": "Цифры", + "periodLabel": "Период", + "algorithmLabel": "Алгоритм", + "periodSeconds": "{seconds} с", + "emptyHint": "Вставьте секрет, чтобы получить шестизначный код.", + "invalidHint": "Исправьте секрет, чтобы увидеть код.", + "securityNote": "Относитесь к секрету как к паролю. Он не покидает браузер; ничего не отправляется.", + "importLabel": "Импорт из otpauth:// URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Импорт", + "qrSectionTitle": "QR-код подготовки", + "qrHint": "Отсканируйте приложением-аутентификатором (Google Authenticator, 1Password, Aegis…), чтобы добавить этот секрет. QR-код кодирует секрет — относитесь к нему как к паролю.", + "accountLabel": "Учётная запись", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Издатель (необязательно)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Введите корректный секрет, чтобы сгенерировать QR-код.", + "errors": { + "invalidSecret": "Недопустимый секрет Base32.", + "cryptoFailed": "Не удалось вычислить TOTP. Проверьте поддержку Web Crypto и повторите.", + "invalidUri": "Это не действительный otpauth:// TOTP URI.", + "unsupportedUri": "Этот URI использует значения, которые инструмент не поддерживает (количество цифр должно быть 6 или 8; период — 15, 30 или 60 с)." + } + }, + "MarkdownPreview": { + "title": "Просмотр Markdown & HTML", + "subtitle": "Живой рендерер Markdown с экспортом HTML и конвертацией HTML → Markdown. Всё работает в вашем браузере.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Ввод Markdown", + "htmlInputLabel": "Ввод HTML", + "previewLabel": "Предпросмотр", + "htmlOutputLabel": "Источник HTML", + "mdOutputLabel": "Вывод Markdown", + "inputPlaceholder": "# Привет\\n\\nВведите ваш **markdown** здесь…", + "htmlInputPlaceholder": "Вставьте ваш HTML сюда…", + "copyHtml": "Копировать HTML", + "copyMarkdown": "Копировать Markdown", + "exportHtml": "Экспорт HTML", + "copied": "Скопировано", + "charCount": "{count, plural, one {# символ} few {# символа} other {# символов}}", + "emptyHint": "Начните вводить Markdown, чтобы увидеть предпросмотр и HTML вывод.", + "emptyHintHtmlToMd": "Вставьте HTML для конвертации в Markdown.", + "securityNote": "Всё выполняется локально в вашем браузере. Ничего не загружается." + }, + "NumberBaseConverter": { + "title": "Конвертер систем счисления", + "subtitle": "Перевод целых чисел между основаниями 2–36. Поддержка префиксов 0x / 0b / 0o. Всё локально в браузере.", + "inputBaseLabel": "Из основания", + "outputBaseLabel": "В основание", + "inputLabel": "Входное число", + "placeholderInput": "Введите целое число в выбранной входной системе…", + "outputLabel": "Результат", + "copyOutput": "Копировать результат", + "copied": "Скопировано", + "swap": "Поменять основания", + "byteCountHint": "{count, plural, one {# байт} few {# байта} many {# байт} other {# байта}} (длина UTF-8)", + "emptyHint": "Введите число для перевода.", + "note": "Цифры выше 9 — буквы A–Z (без учёта регистра). Пробелы и подчёркивания игнорируются.", + "errors": { + "invalidNumber": "Недопустимое значение для выбранной входной системы." + } + }, + "FormatConverter": { + "title": "Конвертер форматов", + "subtitle": "Конвертируйте между JSON, YAML, TOML и XML в любом направлении. Все 12 комбинаций. Всё работает в вашем браузере.", + "fromLabel": "Из", + "toLabel": "В", + "swap": "Поменять", + "swapTitle": "Поменять форматы и использовать вывод как ввод", + "copy": "Копировать", + "copied": "Скопировано", + "charCount": "{count, plural, one {# символ} few {# символа} other {# символов}}", + "emptyHint": "Вставьте или введите данные слева, чтобы увидеть преобразованный вывод.", + "localNote": "Всё работает локально в вашем браузере. Ничего не загружается." + }, + "MimeTypeLookup": { + "title": "Поиск MIME-типа", + "subtitle": "Узнайте MIME-тип (media type) по расширению файла или полному имени файла. Всё работает локально в вашем браузере.", + "inputLabel": "Расширение или имя файла", + "placeholder": "например .png, package.json, photo.jpeg", + "hint": "Совет: вставьте расширение (с точкой или без) или полное имя файла.", + "clear": "Очистить", + "exampleFilename": "Использовать пример имени файла", + "exampleExtension": "Использовать пример расширения", + "resultLabel": "Результат", + "normalizedLabel": "Нормализованный ввод:", + "mimeTypeLabel": "MIME-тип", + "contentTypeLabel": "Заголовок Content-Type", + "charsetLabel": "Кодировка", + "copyMimeType": "Копировать MIME-тип", + "copy": "Копировать", + "copied": "Скопировано", + "emptyHint": "Введите расширение или имя файла, чтобы узнать MIME-тип.", + "notFound": "MIME-тип для этого ввода не найден.", + "localNote": "Всё работает локально в вашем браузере. Ничего не загружается." + }, + "UserAgentParser": { + "title": "Парсер User-Agent", + "subtitle": "Вставьте строку User-Agent (UA) и получите разбор по браузеру, ОС, движку, устройству и CPU. Всё работает локально в вашем браузере.", + "inputLabel": "Строка User-Agent", + "placeholder": "Вставьте ваш User-Agent сюда…", + "hint": "Совет: можно скопировать UA из заголовков запроса, логов или devtools браузера.", + "clear": "Очистить", + "exampleDesktop": "Использовать пример для компьютера", + "exampleMobile": "Использовать пример для телефона", + "resultLabel": "Разбор", + "browserLabel": "Браузер", + "osLabel": "Операционная система", + "engineLabel": "Движок", + "deviceTypeLabel": "Тип устройства", + "deviceVendorLabel": "Производитель", + "deviceModelLabel": "Модель", + "cpuArchLabel": "Архитектура CPU", + "copyJson": "Скопировать JSON", + "copy": "Копировать", + "copied": "Скопировано", + "emptyHint": "Вставьте строку User-Agent, чтобы увидеть разбор.", + "localNote": "Всё работает локально в вашем браузере. Ничего не загружается." + }, + "MockDataGenerator": { + "title": "Генератор тестовых данных", + "subtitle": "Задайте столбцы, выберите типы и экспортируйте данные для сидов, Postman или тестов. До {max} строк, всё в браузере — ничего не загружается на сервер.", + "mockarooLink": "Сценарий как в Mockaroo", + "rowsLabel": "Строки", + "rowsHint": "Не более {max} строк за генерацию, чтобы вкладка оставалась отзывчивой.", + "formatLabel": "Формат", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Имя таблицы SQL", + "presetsLabel": "Шаблоны", + "presetPlaceholder": "Загрузить шаблон…", + "presetDefault": "Старт (пользователь)", + "presetUsers": "Пользователи и роли", + "presetProducts": "SKU интернет-магазина", + "presetApiLog": "Журнал HTTP-запросов", + "generate": "Сгенерировать", + "warningDuplicateNames": "Повторяющиеся имена столбцов: поздние перезаписывают ранние в объектах JSON. Переименуйте.", + "schemaHeading": "Схема", + "addField": "Добавить столбец", + "removeField": "Удалить столбец", + "duplicateField": "Дублировать столбец", + "colName": "Имя", + "colType": "Тип", + "colOptions": "Параметры", + "colBlank": "Пусто %", + "optMin": "мин", + "optMax": "макс", + "optDecimals": "дробь", + "optDateFrom": "с даты", + "optDateTo": "по дату", + "optValues": "a, b, c", + "optStep": "шаг", + "optRowBase": "база", + "output": "Результат", + "outputPlaceholder": "Нажмите «Сгенерировать», чтобы создать данные…", + "copy": "Копировать", + "download": "Скачать", + "copied": "Скопировано", + "groups": { + "personal": "Личные данные", + "location": "Местоположение", + "internetApi": "Интернет и API", + "numbersIds": "Числа и идентификаторы", + "dateTime": "Дата и время", + "textDesign": "Текст и оформление", + "custom": "Пользовательский" + }, + "fieldTypes": { + "first_name": "Имя", + "last_name": "Фамилия", + "full_name": "Полное имя", + "email": "Email", + "username": "Имя пользователя", + "password": "Пароль", + "phone": "Телефон", + "company": "Компания", + "job_title": "Должность", + "avatar_url": "URL аватара", + "city": "Город", + "country": "Страна", + "country_code": "Код страны (ISO)", + "street_address": "Адрес", + "zip_code": "Почтовый индекс", + "latitude": "Широта", + "longitude": "Долгота", + "url": "URL", + "domain": "Домен", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Целое число", + "float": "Дробное число", + "boolean": "Логический", + "uuid": "UUID", + "credit_card": "Карта (тест)", + "semver": "SemVer", + "row_index": "Индекс строки", + "sequence": "Последовательность", + "date": "Дата", + "datetime": "Дата и время", + "unix_timestamp": "Unix-время", + "timezone": "Часовой пояс", + "word": "Слово", + "sentence": "Предложение", + "paragraph": "Абзац", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Код валюты", + "language_code": "Языковой тег", + "custom_list": "Пользовательский список" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Оптимизатор SVG", + "subtitle": "Вставьте SVG, минифицируйте через SVGO в браузере и сравните размер UTF-8 до и после.", + "inputLabel": "Ввод", + "outputLabel": "Оптимизировано", + "placeholder": "Вставьте фрагмент или весь файл…", + "outputPlaceholder": "Оптимизированный SVG появится здесь…", + "chars": "{count} симв. • {bytes} байт (UTF-8)", + "preview": "Предпросмотр", + "localNote": "Всё обрабатывается локально в браузере. Ничего не загружается.", + "stats": { + "title": "Размер (UTF-8)", + "before": "До", + "after": "После", + "savedBytes": "Сэкономлено {count} байт ({percent}%)", + "noChange": "Без уменьшения — уже минимально или пустой вывод." + }, + "actions": { + "clear": "Очистить", + "sample": "Пример", + "copy": "Копировать", + "copied": "Скопировано", + "download": "Скачать" + }, + "errors": { + "optimizeFailed": "Не удалось оптимизировать этот SVG." + } + }, + "UnitConverter": { + "title": "Конвертер единиц", + "subtitle": "Конвертируйте между 323 единицами в 43 категориях", + "category": "Категория", + "from": "Из", + "to": "В", + "value": "Значение", + "result": "Результат", + "swapUnits": "Поменять единицы", + "placeholder": "Введите значение", + "copyResult": "Скопировать результат", + "searchCategory": "Поиск категории...", + "selectCategory": "Выберите категорию", + "noResults": "Категория не найдена." + }, + "DockerComposeGenerator": { + "title": "Генератор Docker Compose", + "subtitle": "Выберите популярные базы данных, кэши, брокеры сообщений, обратные прокси и наблюдаемость — скопируйте готовый docker-compose.yml для локальной разработки.", + "searchPlaceholder": "Поиск (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Сервисы", + "selectedCount": "{count, plural, one {# сервис} few {# сервиса} many {# сервисов} other {# сервисов}}", + "outputTitle": "docker-compose.yml", + "copy": "Копировать", + "copied": "Скопировано!", + "download": "Скачать", + "emptyState": "Выберите один или несколько сервисов, чтобы сгенерировать YAML Compose.", + "hintPorts": "Порты хоста подобраны, чтобы реже конфликтовать; измените при ошибке занятого порта. Замените демо-пароли перед общими средами.", + "categories": { + "databases": "Базы данных и хранилища", + "cache": "Кэш и in-memory", + "queues": "Очереди и обмен сообщениями", + "search": "Поиск и журналы", + "storage": "Объектное хранилище", + "proxy": "Обратные прокси и ingress", + "observability": "Метрики и трассировка", + "mail": "Почта (локальная разработка)", + "admin": "Админ-интерфейсы и BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Очистить" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (совместим с Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (совместим с Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (совместим с S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (перехват SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (режим разработки)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Сдвиньте плитки и объедините их, чтобы достичь 2048 года", + "score": "Балл", + "best": "Лучше", + "newGame": "Новая игра", + "howToPlay": "Используйте клавиши со стрелками (или проведите пальцем по мобильному телефону) для перемещения плиток. Плитки с одинаковым номером сливаются. Достигните 2048 года, чтобы выиграть!", + "youWin": "Вы достигли 2048 года!", + "keepGoing": "Продолжайте", + "gameOver": "Вы проиграли", + "tryAgain": "Повторить попытку", + "gameOverMessage": "Больше ходов не осталось.", + "undo": "Отменить" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "этапы завершены", + "time": "Время", + "best": "Лучше", + "difficulty": { + "easy": "Лёгкая", + "medium": "Средний", + "hard": "Тяжелая" + }, + "stage": "Место проведения", + "solved": "Решено", + "score": "Балл", + "nextStage": "Следующая стадия", + "replay": "Повторное воспроизведение", + "erase": "Стереть", + "howToPlay": "Щелкните ячейку, затем коснитесь цифры или нажмите 1–9. Backspace или Delete очищает ячейку. Жирные цифры - это ключ к разгадке. Заполните сетку так, чтобы каждая строка, столбец и поле 3×3 содержали 1–9 без повторов.", + "pencil": "Карандаш", + "undo": "Отменить", + "hint": "Подсказка", + "hintsUsed": "подсказки использованы", + "mistakes": "Ошибки", + "failed": "Проигрыш!", + "mistakeMode": "Режим 3 ошибок" + }, + "Snake": { + "title": "Змейка", + "subtitle": "Ешь еду, расти, избегай стен и себя", + "score": "Очки", + "best": "Лучший", + "startPrompt": "Нажмите любую кнопку стрелки", + "startButton": "Начать игру", + "gameOver": "Конец игры", + "playAgain": "Играть снова", + "howToPlay": "Используй стрелки или WASD. Ешь еду, чтобы расти. Скорость растёт каждые 50 очков.", + "diffEasy": "Лёгкий", + "diffNormal": "Обычный", + "diffHard": "Сложный", + "diffInfinite": "Бесконечный", + "orTap": "или нажмите ниже", + "howToPlayInfinite": "Стены огибают — смерть от стен отключена. Ешь еду, чтобы расти.", + "shieldActive": "Щит активен", + "speedBoost": "Ускорение", + "powerSpeed": "Скорость x2", + "powerShield": "Пережить одну смерть", + "powerShrink": "Укорачивает хвост" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Подключения Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Подключайтесь к экземплярам Redis. Просматривайте ключи, редактируйте значения и выполняйте команды. Учётные данные шифруются в браузере.", + "heroAddConnection": "Добавить подключение", + "vaultLockedTitle": "Хранилище заблокировано", + "vaultLockedDesc": "Разблокируйте хранилище для доступа к сохранённым подключениям." + } + }, + "Minesweeper": { + "title": "Сапёр", + "subtitle": "Найдите все безопасные клетки, не подорвавшись на мине", + "mines": "Мины", + "newGame": "Новая игра", + "win": "Вы выиграли!", + "loss": "Вы проиграли!", + "playAgain": "Играть снова", + "tryAgain": "Попробовать снова", + "startPrompt": "Нажмите на любую клетку, чтобы начать", + "difficulty": { + "beginner": "Новичок", + "intermediate": "Средний", + "expert": "Эксперт" + } + }, + "Tetris": { + "title": "Тетрис", + "subtitle": "Расставляйте падающие блоки, чтобы заполнить ряды", + "next": "Следующий", + "score": "Очки", + "best": "Рекорд", + "lines": "Линии", + "level": "Уровень", + "pause": "Пауза", + "resume": "Продолжить", + "start": "Старт", + "restart": "Заново", + "paused": "На паузе", + "gameOver": "Игра окончена" + }, + "SshKeyGenerator": { + "title": "Генератор ключей SSH / RSA", + "subtitle": "Генерируйте пары ключей Ed25519 или RSA прямо в браузере. Скачивайте приватные и публичные ключи в форматах OpenSSH и PEM. Ничего не загружается на сервер.", + "keyTypeLabel": "Тип ключа", + "recommended": "рекомендуется", + "ed25519Desc": "Быстрый, компактный, современный. Поддерживается GitHub, GitLab и всеми основными SSH-серверами.", + "rsa2048Desc": "Широкая совместимость. Безопасен для большинства задач.", + "rsa4096Desc": "Максимальная совместимость и надёжность для устаревших систем.", + "commentLabel": "Комментарий (необязательно)", + "commentHint": "Добавляется к строке публичного ключа OpenSSH.", + "generateBtn": "Сгенерировать пару ключей", + "generating": "Генерация…", + "generatingLong": "Генерация (может занять немного времени)…", + "copy": "Копировать", + "download": "Скачать", + "privateKeyLabel": "Приватный ключ (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Публичный ключ (формат OpenSSH)", + "publicKeySpkiLabel": "Публичный ключ (SPKI PEM)", + "browserNote": "Все ключи генерируются в вашем браузере. Ничего не отправляется на сервер.", + "errors": { + "generationFailed": "Ошибка генерации ключа" + } + }, + "DnsLookup": { + "title": "DNS-запрос", + "subtitle": "Запрашивайте DNS-записи для любого домена — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Введите домен (например github.com)", + "lookupBtn": "Запрос", + "looking": "Выполняется запрос…", + "refreshBtn": "Обновить", + "recordTypesLabel": "Типы записей", + "allBtn": "Все", + "tryLabel": "Попробовать:", + "errors": { + "lookupFailed": "Ошибка DNS-запроса" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Генератор и проверка Bcrypt", + "subtitle": "Хешируйте пароли с помощью bcrypt и проверяйте хеши — всё остаётся в вашем браузере", + "tabs": { + "hash": "Хеш", + "verify": "Проверка" + }, + "showPassword": "Показать пароль", + "hidePassword": "Скрыть пароль", + "copy": "Копировать", + "copied": "Скопировано", + "hash": { + "passwordLabel": "Пароль", + "passwordPlaceholder": "Введите пароль для хеширования…", + "costLabel": "Фактор стоимости", + "costHint": "Более высокий фактор удваивает работу на каждом шаге. 10-12 — распространённый выбор для продакшена.", + "slowWarning": "Факторы стоимости 13 и выше могут вычисляться несколько секунд.", + "generate": "Создать хеш", + "generating": "Хеширование…", + "resultLabel": "Хеш bcrypt", + "timeTaken": "{ms} мс", + "anatomy": { + "title": "Строение хеша", + "algorithm": "алгоритм", + "cost": "стоимость", + "salt": "соль (22 символа)", + "hash": "хеш (31 символ)" + } + }, + "verify": { + "passwordLabel": "Пароль", + "passwordPlaceholder": "Введите пароль для проверки…", + "hashLabel": "Хеш bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Введите пароль и хеш bcrypt, чтобы сравнить их.", + "invalidHash": "Это не похоже на корректный хеш bcrypt.", + "checking": "Сравнение…", + "match": "Совпадает — пароль даёт этот хеш.", + "noMatch": "Не совпадает — пароль не даёт этот хеш." + } + }, + "ChmodCalculator": { + "title": "Калькулятор Chmod", + "subtitle": "Преобразуйте права файлов Unix между флажками, восьмеричной и символьной формой", + "grid": { + "title": "Права", + "read": "Чтение", + "write": "Запись", + "execute": "Выполнение", + "owner": "Владелец", + "group": "Группа", + "others": "Остальные" + }, + "special": { + "title": "Специальные биты", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Восьмеричная", + "placeholder": "755 или 4755", + "invalid": "Введите 3 или 4 восьмеричные цифры (0-7)." + }, + "symbolic": { + "label": "Символьная", + "placeholder": "rwxr-xr-x", + "invalid": "Введите 9 символов вида rwxr-xr-x (допустимы s/S, t/T)." + }, + "output": { + "title": "Команда", + "copy": "Копировать", + "copied": "Скопировано" + }, + "explain": { + "line": "{cls} может {perms}.", + "none": "{cls} не имеет прав.", + "setuid": "Setuid: файл выполняется с привилегиями своего владельца.", + "setgid": "Setgid: файл выполняется с привилегиями своей группы (новые файлы в каталоге наследуют его группу).", + "sticky": "Sticky: только владелец файла может удалить или переименовать его в этом каталоге." + }, + "presets": { + "title": "Частые пресеты", + "octal": "Восьмеричная", + "permissions": "Права", + "description": "Типичное применение", + "p644": "Обычные файлы: владелец пишет, все читают.", + "p755": "Исполняемые файлы и каталоги: все могут читать и входить.", + "p600": "Приватные файлы: только владелец читает и пишет.", + "p700": "Приватные исполняемые файлы и каталоги: только владелец.", + "p664": "Файлы, доступные группе для записи.", + "p775": "Каталоги и исполняемые файлы, доступные группе для записи.", + "p400": "Только чтение для владельца (например, закрытые ключи SSH)." + } + }, + "CodeScreenshot": { + "title": "Скриншот кода", + "subtitle": "Превращайте код в красивые изображения для публикации", + "languageLabel": "Язык", + "autoDetect": "Автоопределение", + "backgroundLabel": "Фон", + "paddingLabel": "Отступ", + "filenameLabel": "Имя файла", + "filenamePlaceholder": "example.ts", + "windowLabel": "Точки окна", + "themeLabel": "Тема окна", + "themeDark": "Тёмная", + "themeLight": "Светлая", + "scaleLabel": "Масштаб экспорта", + "copyImage": "Копировать изображение", + "copied": "Скопировано", + "exportPng": "Экспорт PNG", + "inputLabel": "Код", + "detected": "Определено: {language}", + "clear": "Очистить", + "placeholder": "Вставьте ваш код сюда…", + "previewLabel": "Предпросмотр", + "emptyPreview": "Здесь появится предпросмотр вашего кода", + "exportFailed": "Не удалось экспортировать изображение", + "copyUnsupported": "Копирование изображений не поддерживается в этом браузере", + "copyFailed": "Не удалось скопировать изображение", + "backgrounds": { + "ocean": "Океан", + "sunset": "Закат", + "candy": "Конфета", + "forest": "Лес", + "midnight": "Полночь", + "gold": "Золото", + "aurora": "Аврора", + "slate": "Сланец", + "transparent": "Прозрачный" + } + }, + "CssGenerators": { + "title": "Генераторы CSS", + "subtitle": "Box-shadow, border-radius и адаптивный clamp() — копируйте CSS.", + "preview": "Предпросмотр", + "output": "CSS", + "copy": "Копировать", + "copied": "Скопировано", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Адаптивный clamp()" + }, + "boxShadow": { + "presets": "Пресеты", + "layer": "Слой", + "addLayer": "Добавить слой", + "removeLayer": "Удалить слой", + "x": "X", + "y": "Y", + "blur": "Размытие", + "spread": "Растяжение", + "color": "Цвет", + "alpha": "Альфа", + "inset": "Внутренняя", + "presetNames": { + "subtleCard": "Лёгкая карточка", + "elevated": "Приподнятая", + "hardOffset": "Жёсткое смещение", + "neonGlow": "Неоновое свечение", + "inner": "Внутренняя", + "layeredSmooth": "Мягкие слои" + } + }, + "borderRadius": { + "modeLabel": "Режим", + "modes": { + "simple": "По углам", + "blob": "Органичная капля" + }, + "unit": "Единица", + "linkCorners": "Связать углы", + "randomize": "Случайно", + "horizontal": "Горизонтальные радиусы", + "vertical": "Вертикальные радиусы", + "corners": { + "tl": "Верхний левый", + "tr": "Верхний правый", + "br": "Нижний правый", + "bl": "Нижний левый" + } + }, + "clamp": { + "minFont": "Мин. шрифт (px)", + "maxFont": "Макс. шрифт (px)", + "minViewport": "Мин. viewport (px)", + "maxViewport": "Макс. viewport (px)", + "rootFont": "Корневой шрифт (px)", + "viewportWidth": "Смоделированный viewport", + "computedSize": "Вычисленный размер", + "slope": "Наклон", + "intercept": "Смещение", + "previewText": "Быстрая рыжая лиса", + "invalid": "Введите корректные и различающиеся границы viewport." + } + }, + "ExifViewer": { + "title": "Просмотр и удаление EXIF", + "subtitle": "Изучайте метаданные фото и удаляйте их в вашем браузере", + "dropTitle": "Перетащите изображение сюда", + "dropHint": "или нажмите для выбора — ваше изображение никогда не покидает устройство", + "browse": "Выбрать изображение", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Чтение метаданных…", + "parseFailed": "Не удалось прочитать это изображение", + "clear": "Удалить изображение", + "gpsWarning": "Содержит GPS-координаты", + "openMap": "Показать на OpenStreetMap", + "noMetadata": "В этом изображении не найдено встроенных метаданных.", + "facts": { + "name": "Имя", + "type": "Тип", + "dimensions": "Размеры", + "size": "Размер" + }, + "groups": { + "camera": "Камера", + "exposure": "Экспозиция", + "timestamps": "Метки времени", + "gps": "GPS-координаты", + "software": "ПО и прочее" + }, + "gpsCard": { + "latitude": "Широта", + "longitude": "Долгота", + "altitude": "Высота" + }, + "raw": { + "title": "Все метаданные", + "copyJson": "Копировать JSON", + "copied": "Скопировано" + }, + "remove": { + "title": "Удалить метаданные", + "jpegDescription": "Без потерь удалите EXIF, XMP, IPTC и комментарии без повторного сжатия изображения.", + "reencodeDescription": "Перекодируйте изображение, чтобы удалить встроенные метаданные. При этом пиксели пересжимаются.", + "keepIcc": "Сохранить цветовой профиль (ICC)", + "stripButton": "Удалить метаданные", + "reencodeButton": "Перекодировать и удалить", + "unsupported": "Удаление метаданных не поддерживается для этого типа файла.", + "failed": "Не удалось удалить метаданные", + "before": "До", + "after": "После", + "download": "Скачать очищенное изображение" + } + }, + "FaviconGenerator": { + "title": "Генератор favicon", + "subtitle": "Создавайте favicon и иконки приложений из одного изображения", + "dropTitle": "Перетащите изображение сюда", + "dropHint": "PNG, JPEG или SVG — нажмите для выбора", + "browse": "Выбрать изображение", + "clear": "Удалить изображение", + "backgroundLabel": "Фон", + "transparent": "Прозрачный", + "solidColor": "Сплошной", + "cornerLabel": "Форма углов", + "cornerNone": "Квадрат", + "cornerRounded": "Скруглённый", + "cornerCircle": "Круг", + "maskPaddingLabel": "Отступ maskable", + "maskable": "maskable", + "previewLabel": "Предпросмотр", + "copy": "Копировать", + "headSnippet": "Фрагмент для head HTML", + "downloadIco": "Скачать favicon.ico", + "downloadZip": "Скачать всё (.zip)" + }, + "JsonDiff": { + "title": "Сравнение JSON", + "subtitle": "Семантическое, учитывающее структуру сравнение двух документов JSON — порядок ключей и форматирование игнорируются", + "options": { + "ignoreArrayOrder": "Игнорировать порядок массивов" + }, + "loadBothSamples": "Загрузить примеры", + "sample": "Пример", + "format": "Формат", + "clear": "Очистить", + "formatError": "Не удаётся отформатировать: ввод не является корректным JSON", + "placeholder": "Вставьте или введите JSON здесь…", + "panels": { + "left": "Оригинал (слева)", + "right": "Изменённый (справа)", + "results": "Различия" + }, + "summary": { + "added": "{count} добавлено", + "removed": "{count} удалено", + "changed": "{count} изменено" + }, + "types": { + "added": "Добавлено", + "removed": "Удалено", + "changed": "Изменено" + }, + "copyChanges": "Копировать как JSON", + "copied": "Скопировано", + "empty": "Вставьте JSON в обе панели, чтобы увидеть их различия.", + "identical": "Оба документа семантически идентичны." + }, + "JsonpathPlayground": { + "title": "Песочница JSONPath", + "subtitle": "Запрашивайте документы JSON с помощью JSONPath или JMESPath и смотрите результаты вживую", + "engineLabel": "Движок", + "queryLabel": "Запрос", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Шпаргалка", + "cheats": { + "jpAllTitles": "Все названия книг", + "jpAllAuthors": "Все авторы в любом месте документа", + "jpFirstBook": "Первая книга", + "jpLastBook": "Последняя книга", + "jpSlice": "Первые две книги (срез)", + "jpFilterPrice": "Книги дешевле 40", + "jpFilterInStock": "Названия книг в наличии", + "jpRecursive": "Все значения рекурсивно", + "jmAllTitles": "Все названия книг", + "jmFirstAuthor": "Автор первой книги", + "jmLastBook": "Последняя книга", + "jmSlice": "Первые две книги (срез)", + "jmFilterPrice": "Названия книг дешевле 40", + "jmCount": "Подсчёт книг в наличии", + "jmProjection": "Преобразовать каждую книгу в новый объект", + "jmSort": "Названия, отсортированные по цене" + }, + "panels": { + "input": "Ввод JSON", + "results": "Результаты" + }, + "sample": "Пример", + "format": "Формат", + "clear": "Очистить", + "formatError": "Не удаётся отформатировать: ввод не является корректным JSON", + "placeholder": "Вставьте или введите JSON здесь…", + "outputPlaceholder": "Совпадения появятся здесь…", + "matchCount": "{count, plural, one {# совпадение} few {# совпадения} many {# совпадений} other {# совпадения}}", + "copy": "Копировать", + "copied": "Скопировано" + }, + "KeycodeInspector": { + "title": "Инспектор клавиш", + "subtitle": "Нажмите любую клавишу, чтобы изучить её событие клавиатуры JavaScript", + "capture": { + "prompt": "Нажмите любую клавишу…", + "ready": "Слушаю — нажмите любую клавишу на клавиатуре", + "clickToFocus": "Нажмите здесь для фокуса, затем нажмите любую клавишу" + }, + "hero": { + "spaceKey": "(Пробел)", + "keyLabel": "event.key", + "repeat": "повтор" + }, + "copyJson": "Копировать JSON", + "copiedJson": "JSON события скопирован в буфер обмена", + "deprecated": "устарело", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Стандартная", + "left": "Слева", + "right": "Справа", + "numpad": "Цифровой блок", + "unknown": "Неизвестно" + }, + "history": { + "title": "История", + "clear": "Очистить историю", + "empty": "Нажатия клавиш появятся здесь." + } + }, + "MarkdownTableGenerator": { + "title": "Генератор таблиц Markdown", + "subtitle": "Редактируйте сетку, вставляйте из таблицы и копируйте красиво выровненную таблицу Markdown или HTML", + "addRow": "Добавить строку", + "addColumn": "Добавить столбец", + "importLabel": "Импорт", + "importPlaceholder": "Вставьте TSV (из Excel / Google Sheets) или CSV сюда…", + "importButton": "Импортировать в сетку", + "pasteClipboard": "Вставить из буфера обмена", + "importEmpty": "Нечего импортировать — сначала вставьте несколько строк", + "clipboardError": "Не удалось прочитать из буфера обмена", + "clear": "Очистить", + "panels": { + "editor": "Редактор таблицы", + "output": "Результат" + }, + "align": { + "left": "Выровнять по левому краю", + "center": "Выровнять по центру", + "right": "Выровнять по правому краю" + }, + "headerPlaceholder": "Заголовок", + "cellPlaceholder": "Ячейка", + "removeRow": "Удалить строку", + "removeColumn": "Удалить столбец", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Копировать", + "copied": "Скопировано" + }, + "TimezoneConverter": { + "title": "Конвертер часовых поясов", + "subtitle": "Преобразуйте один момент по всему миру для планирования встреч.", + "dateTimeLabel": "Дата и время", + "now": "Сейчас", + "sourceZoneLabel": "Исходный часовой пояс", + "selectZone": "Выберите пояс", + "searchZone": "Поиск часовых поясов...", + "noZone": "Часовой пояс не найден.", + "scrubLabel": "Сдвинуть время", + "reset": "Сбросить", + "targetsLabel": "Целевые пояса", + "addZone": "Добавить пояс", + "remove": "Удалить", + "localBadge": "Локальный", + "invalidDate": "Введите корректные дату и время.", + "bands": { + "work": "Рабочие часы (09:00–18:00)", + "edge": "Крайние часы (раннее утро / вечер)", + "night": "Ночь" + } + }, + "TokenCounter": { + "title": "Счётчик токенов LLM", + "subtitle": "Считайте токены, визуализируйте чанки и оценивайте стоимость API.", + "inputLabel": "Текст", + "sample": "Пример", + "clear": "Очистить", + "placeholder": "Вставьте текст, код или JSON для подсчёта токенов...", + "encoding": "Кодировка", + "loading": "Загрузка токенизатора...", + "encodings": { + "o200k": "o200k_base (GPT-4o / серия o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Токены", + "characters": "Символы", + "words": "Слова", + "ratio": "Токенов/слово", + "ratioSub": "прибл." + }, + "claudeRow": "Claude (приблизительно)", + "estimate": "оценка", + "viz": { + "title": "Визуализация токенов", + "toggle": "Показать", + "hint": "Включите, чтобы раскрасить каждый токен как чанк.", + "empty": "Введите текст для визуализации токенов.", + "truncated": "Показаны первые {limit} из {total} токенов." + }, + "cost": { + "title": "Калькулятор стоимости", + "disclaimer": "редактируемые значения по умолчанию — проверьте актуальные цены", + "model": "Модель", + "expectedOutput": "Ожидаемые токены вывода", + "customInput": "Ввод $/MTok", + "customOutput": "Вывод $/MTok", + "inputCost": "Стоимость ввода", + "outputCost": "Стоимость вывода", + "total": "Итого", + "per1000": "За 1000 вызовов" + } + }, + "WebhookTester": { + "title": "Тестер вебхуков", + "subtitle": "Создайте временный URL и изучайте входящие запросы вебхуков вживую.", + "intro": { + "title": "Создайте URL вебхука, чтобы начать захват запросов", + "hint": "Вы получите непредсказуемый URL, который записывает каждый входящий запрос. Он истекает через 24 часа." + }, + "createBtn": "Создать URL вебхука", + "creating": "Создание…", + "urlLabel": "Ваш URL вебхука", + "expiresIn": "Истекает через {countdown}", + "copyUrlBtn": "Копировать URL", + "copied": "Скопировано", + "clearBtn": "Очистить", + "newBinBtn": "Новый URL", + "requestsTitle": "Запросы", + "selectPrompt": "Выберите запрос, чтобы изучить его детали.", + "expiredNotice": "Этот URL вебхука истёк. Создайте новый, чтобы продолжить тестирование.", + "base64Badge": "base64", + "truncatedBadge": "обрезано", + "empty": { + "title": "Ожидание запросов…", + "hint": "Отправьте запрос на ваш URL, и он появится здесь мгновенно. Попробуйте так:" + }, + "detail": { + "receivedAt": "Получено", + "source": "IP источника", + "contentType": "Content-Type", + "query": "Параметры query", + "headers": "Заголовки", + "body": "Тело", + "copyBody": "Копировать тело", + "emptyBody": "Нет тела" + }, + "errors": { + "createFailed": "Не удалось создать URL вебхука. Попробуйте снова.", + "clearFailed": "Не удалось очистить запросы. Попробуйте снова." + } + }, + "WebsocketTester": { + "title": "Тестер WebSocket", + "subtitle": "Подключайтесь к эндпоинту WebSocket и отправляйте, получайте и изучайте сообщения вживую", + "urlLabel": "URL эндпоинта", + "protocolsLabel": "Подпротоколы", + "protocolsPlaceholder": "необязательно, через запятую", + "connect": "Подключить", + "disconnect": "Отключить", + "status": { + "disconnected": "Отключено", + "connecting": "Подключение", + "connected": "Подключено", + "error": "Ошибка" + }, + "messages": { + "connecting": "Подключение к {url}…", + "connected": "Соединение установлено", + "connectedWithProtocol": "Соединение установлено (протокол: {protocol})", + "invalidUrl": "Некорректный URL — должен начинаться с ws:// или wss://", + "connectFailed": "Не удалось открыть соединение", + "socketError": "Произошла ошибка соединения", + "notConnected": "Не подключено — сначала подключитесь", + "binaryMessage": "Получено двоичное сообщение" + }, + "log": { + "title": "Журнал сообщений", + "empty": "Сообщений пока нет. Подключитесь и начните отправлять.", + "copyAll": "Копировать всё", + "download": "Скачать как .txt", + "clear": "Очистить журнал", + "prettyPrint": "Форматировать JSON", + "showRaw": "Показать необработанный текст" + }, + "composer": { + "placeholder": "Введите сообщение для отправки…", + "send": "Отправить", + "formatJson": "Форматировать как JSON", + "invalidJson": "Сообщение не является корректным JSON", + "hint": "Cmd/Ctrl+Enter для отправки" + }, + "ping": { + "label": "Авто-пинг", + "seconds": "с", + "intervalTitle": "Интервал пинга в секундах", + "payloadPlaceholder": "payload пинга" + } + }, + "WhoisLookup": { + "title": "Whois-запрос", + "subtitle": "Узнавайте данные регистрации любого домена или IP-адреса через RDAP", + "placeholder": "example.com или 8.8.8.8", + "lookupBtn": "Запросить", + "looking": "Запрос…", + "refreshBtn": "Обновить", + "tryLabel": "Попробуйте:", + "copy": "Копировать", + "copied": "Скопировано", + "notAvailable": "Недоступно", + "summary": { + "registrar": "Регистратор", + "network": "Сеть" + }, + "dates": { + "created": "Создан", + "updated": "Обновлён", + "expires": "Истекает" + }, + "sections": { + "status": "Статус", + "nameservers": "Серверы имён", + "contacts": "Контакты", + "rawRdap": "Необработанный RDAP" + }, + "contacts": { + "roles": "Роли", + "name": "Имя", + "organization": "Организация" + }, + "errors": { + "invalidTarget": "Введите корректное доменное имя или IP-адрес", + "lookupFailed": "Запрос не удался (статус {status})" + } + } +} diff --git a/apps/desktop-ui/messages/sv.json b/apps/desktop-ui/messages/sv.json new file mode 100644 index 00000000..001c0010 --- /dev/null +++ b/apps/desktop-ui/messages/sv.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Instrumentpanel", + "settings": "Inställningar", + "apiClient": "API-klient", + "httpStatusCodes": "HTTP-statuskoder", + "passwordManager": "Lösenordshanterare", + "environmentManager": "Miljöhanterare", + "bookmarks": "Bokmärken", + "jsonFormatter": "JSON-formaterare", + "emailValidator": "E-postvalidering", + "nosqlExplorer": "Databasutforskare", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Krypteringslekplats", + "jwtDecoder": "JWT-avkodare", + "certificatePemDecoder": "Certifikat- / PEM-avkodare", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL-formaterare", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL-tolk", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Hemlighet / API-nyckel", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Kontrastkontroll", + "jsonSchemaGenerator": "JSON-schemagenerator", + "imageToBase64": "Bild till Base64", + "imageCompressor": "Bildkomprimering", + "cssGradientBuilder": "CSS Gradient Builder", + "gitignoreGenerator": ".gitignore Generator", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Kodutdrag", + "qrCodeGenerator": "QR-kodgenerator", + "dnsLookup": "DNS-sökning", + "ipSubnetCalculator": "IP-/subnetkalkylator", + "hashGenerator": "Hash-generator", + "hmacGenerator": "HMAC-generator", + "totpGenerator": "TOTP / 2FA-kod", + "markdownPreview": "Markdown-förhandsgranskning", + "numberBaseConverter": "Basomvandlare", + "formatConverter": "Formatkonverterare", + "mockDataGenerator": "Mock-datagenerator", + "svgOptimizer": "SVG-optimering", + "unitConverter": "Enhetsomvandlare", + "dockerComposeGenerator": "Docker Compose-generator", + "breakRoom": "Pausrum", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAML-formaterare", + "sshKeyGenerator": "SSH / RSA-nyckelgenerator", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod-kalkylator", + "codeScreenshot": "Kodskärmbild", + "cssGenerators": "CSS-generatorer", + "exifViewer": "EXIF-visare", + "faviconGenerator": "Favicon-generator", + "jsonDiff": "JSON-diff", + "jsonpathPlayground": "JSONPath-lekplats", + "keycodeInspector": "Tangentkodsinspektör", + "markdownTableGenerator": "Markdown-tabell", + "timezoneConverter": "Tidszonskonverterare", + "tokenCounter": "Token-räknare", + "webhookTester": "Webhook-testare", + "websocketTester": "WebSocket-testare", + "whoisLookup": "Whois-uppslag" + }, + "Help": { + "title": "Hjälp och dokumentation", + "subtitle": "Hur MyDevTools hänger ihop, vad varje app gör och hur säkerhet och kryptering skyddar dina data.", + "tabs": { + "overview": "Översikt", + "apps": "Appar", + "security": "Säkerhet", + "tips": "Tips" + }, + "overview": { + "whatIs": { + "title": "Vad är MyDevTools?", + "description": "En utvecklar‑workspace med inloggning: ett instrumentpanel och en uppsättning verktyg för vardagen.", + "p1": "Efter att du loggat in skapar appen en session med MyDevTools API så att verktygen kan läsa in och spara dina data. Sidofältet listar alla appar; du kan dölja verktyg du inte använder i", + "settingsLink": "Inställningar", + "p1After": ".", + "p2": "Vissa funktioner använder ett huvudlösenord (eller global vault‑nyckel) så att hemligheter som lösenord och anslutningssträngar krypteras på din enhet innan de når servern." + }, + "quickLinks": { + "title": "Snabblänkar", + "dashboard": "Instrumentpanel", + "settings": "Inställningar" + } + }, + "apps": { + "intro": "Öppna valfritt verktyg från sidofältet eller instrumentpanelen. Nedan finns en kort guide per app.", + "open": "Öppna", + "howItWorks": "Så fungerar det", + "generic": "Använd det här verktyget från sidofältet för dess dedikerade arbetsflöde." + }, + "security": { + "session": { + "title": "Inloggning och API‑session", + "description": "Identitet och hur webbläsaren pratar med din backend.", + "items": { + "authentication": "Autentisering använder Firebase för inloggning. Efter inloggning byter appen din Firebase ID‑token mot API‑cookies.", + "accessTokens": "Åtkomsttoken är JWT:er signerade på servern (HS256) som bevisar vilken användare som anropar API:et.", + "refreshTokens": "Refresh‑token lagras i HttpOnly cookies; servern sparar en SHA‑256‑hash av token, inte token i klartext." + } + }, + "encryption": { + "title": "Kryptering du bör känna till", + "description": "Klientside‑krypto för vault och känslig anslutningsdata.", + "p1": "För lösenordshanteraren, miljöhanteraren och sparade anslutningar i NoSQL Explorer använder webbappen webbläsarens Web Crypto API:", + "items": { + "kdf": "PBKDF2 med SHA‑256, 100 000 iterationer och slumpmässig salt härleder en 256‑bitars nyckel från ditt huvudlösenord.", + "aes": "AES‑256‑GCM krypterar data; en slumpmässig 12‑byte IV används per kryptering.", + "scope": "Den härledda nyckeln stannar i webbläsaren (ej exporterbar). Servern tar bara emot ciphertext och IV, inte ditt huvudlösenord eller hemligheter i klartext." + }, + "warning": "Om du glömmer huvudlösenordet kan krypterade data inte återställas. Förvara det säkert." + }, + "logout": { + "title": "Logga ut och lokal data", + "body": "Utloggning rensar hemligheter i minnet, vault‑status och (om tillämpligt) huvudnyckelmaterial från IndexedDB och avslutar API‑sessionen. Data som endast finns i lokal lagring kan ligga kvar tills du rensar den i webbläsaren." + } + }, + "tips": { + "title": "Använd MyDevTools säkert", + "items": { + "masterPassword": "Använd ett starkt och unikt huvudlösenord för vault‑funktioner.", + "apiClient": "Behandla API‑klienten som vilken HTTP‑klient som helst: undvik att klistra in produktionshemligheter i delade miljöer om du inte vet vem som kan se dem.", + "nosql": "Databasutforskaren ansluter till databaser som du konfigurerar; använd minst privilegierade användare och nätverksregler där det går.", + "json": "JSON‑formateraren kör lokalt i webbläsaren—undvik ändå att klistra in mycket känsliga data på delade datorer.", + "settings": "Anpassa synliga verktyg i Inställningar för att hålla sidofältet fokuserat." + } + } + }, + "Settings": { + "title": "Inställningar", + "language": "Språk", + "spanish": "Spanska", + "english": "Engelska", + "french": "Français", + "arabic": "عربى", + "catalan": "Katalanska", + "chineseSimplified": "Kinesiska (förenklad)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Tyska", + "danish": "Danska", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polska", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukrainska", + "norwegian": "Norsk" + }, + "SettingsPage": { + "title": "Inställningar", + "subtitle": "Hantera dina kontoinställningar och preferenser.", + "userProfile": { + "title": "Användarprofil", + "description": "Din personliga information och kontouppgifter.", + "avatarAlt": "Användare", + "anonymousUser": "Anonym användare", + "noEmailProvided": "Ingen e-post angiven", + "notLoggedIn": "Inte inloggad." + }, + "tools": { + "title": "Verktyg och funktioner", + "description": "Välj vilka verktyg som visas i din navigeringspanel." + }, + "appearance": { + "title": "Utseende", + "description": "Anpassa hur applikationen ser ut på din enhet.", + "themePreference": "Temainställning", + "themes": { + "light": "Ljust", + "dark": "Mörkt", + "system": "System" + } + }, + "language": { + "title": "Språk", + "description": "Ställ in språket som används på plattformen.", + "selectLanguage": "Välj språk", + "selectPlaceholder": "Välj ett språk", + "helpText": "Att ändra språk uppdaterar menyer och etiketter på hela plattformen.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spanska)", + "ar": "عربى (Arabiska)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Grekiska)", + "de": "Deutsch (Tyska)", + "da": "Dansk (Danska)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Italienska (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski (Polska)", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "God morgon", + "afternoon": "God eftermiddag", + "evening": "God kväll" + }, + "commaName": ", {name}", + "welcomeBack": "Välkommen tillbaka", + "welcomeBackNamed": "Välkommen tillbaka, {name}", + "tagline": "Vad vill du bygga idag?", + "brandName": "MyDevTools", + "stats": { + "tools": "Verktyg", + "favorites": "Favoriter", + "pinned": "Fästa", + "recent": "Nyligen använda" + }, + "sections": { + "recentlyUsed": "Nyligen använda", + "favorites": "Favoriter", + "whatsNew": "Nyheter", + "pinned": "Fästa" + }, + "groups": { + "apps": "Appar" + }, + "toolCard": { + "defaultDescription": "Utforska detta verktyg för bättre funktionalitet.", + "launchTool": "Starta verktyg" + }, + "tools": { + "toDo": { + "title": "Uppgifter", + "description": "Hantera dina att-göra-listor effektivt." + }, + "notes": { + "title": "Anteckningar", + "description": "Fånga idéer och organisera dina tankar." + }, + "passwordManager": { + "title": "Lösenordshanterare", + "description": "Lagra och hantera dina lösenord säkert." + }, + "environmentManager": { + "title": "Miljöhanterare", + "description": "Spara .env-liknande variabler per projekt och miljö med taggar." + }, + "bookmarks": { + "title": "Bokmärken", + "description": "Hantera och organisera dina bokmärken." + }, + "apiClient": { + "title": "API-klient", + "description": "Testa och felsök HTTP-förfrågningar." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Utforska MongoDB-databaser." + }, + "emailValidator": { + "title": "E-postvalidering", + "description": "Verifiera och validera e-postadresser." + }, + "encryptionPlayground": { + "title": "Krypteringslekplats", + "description": "AES-GCM med rå nyckel eller lösenfras; JSON-paket, Web Crypto endast i webbläsaren." + }, + "jwtDecoder": { + "title": "JWT-avkodare", + "description": "Avkoda JWT-header, payload och utgångstid." + }, + "certificatePemDecoder": { + "title": "Certifikat- / PEM-avkodare", + "description": "Avkoda X.509-certifikat och CSR:er: ämne, utfärdare, giltighet och SAN." + }, + "sshKeyGenerator": { + "title": "SSH / RSA-nyckelgenerator", + "description": "Generera Ed25519- eller RSA-nyckelpar i webbläsaren. Ladda ner i OpenSSH- och PEM-format." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL-formaterare", + "description": "Formatera GraphQL med syntaxmarkering och en enkel frågebyggare." + }, + "yamlFormatter": { + "title": "YAML-formaterare", + "description": "Formatera, validera och konvertera YAML till JSON i webbläsaren." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL-tolk", + "description": "Dela upp en URL i protokoll, värd, sökväg, frågeparametrar och hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Hemlighet / API-nyckel", + "description": "Kryptografiskt slumpmässiga strängar med konfigurerbart alfabet, längd och bulkexport." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Kontrastkontroll", + "description": "WCAG 2.1-kontrast mellan text och bakgrund (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "JSON-schemagenerator", + "description": "Härled ett JSON-schema och typade modeller från exempel-JSON." + }, + "imageToBase64": { + "title": "Bild till Base64", + "description": "Konvertera bilder till Data URI eller rå Base64-strängar." + }, + "imageCompressor": { + "title": "Bildkomprimering", + "description": "Komprimera JPEG, PNG och WebP lokalt med en kvalitetsreglage." + }, + "cssGradientBuilder": { + "title": "CSS Gradient Builder", + "description": "Visuell CSS-gradientbyggare med vinkelkontroll och färgstopp." + }, + "gitignoreGenerator": { + "title": ".gitignore Generator", + "description": "Generera sammanslagna .gitignore-filer direkt från 500+ tekniska stackar." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Konvertera CSV- eller Excel-filer till JSON och exportera tillbaka till CSV eller XLSX." + }, + "snippetManager": { + "title": "Kodutdrag", + "description": "Spara utdrag med syntaxmarkering, automatisk språkupptäckt och formatering." + }, + "qrCodeGenerator": { + "title": "QR-kodgenerator", + "description": "Skapa PNG QR-koder från text eller URL i webbläsaren." + }, + "ipSubnetCalculator": { + "title": "IP-/subnetkalkylator", + "description": "IPv4 och IPv6 CIDR: nätmask, intervall och adressantal." + }, + "dnsLookup": { + "title": "DNS-sökning", + "description": "Fråga DNS-poster för valfri domän — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Hash-generator", + "description": "MD5, SHA-1 och SHA-2 för text eller filer." + }, + "hmacGenerator": { + "title": "HMAC-generator", + "description": "Signera webhooks och testa API-integrationer med HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "TOTP / 2FA-kodgenerator", + "description": "Klistra in en Base32-hemlighet från autentiseringsappen; sexsiffrig kod uppdateras var 30:e s—bra för att testa inloggning och MFA." + }, + "markdownPreview": { + "title": "Markdown-förhandsgranskning", + "description": "Live Markdown-renderer med HTML-export och HTML → Markdown-konvertering." + }, + "numberBaseConverter": { + "title": "Basomvandlare", + "description": "Konvertera heltal mellan baser 2–36." + }, + "formatConverter": { + "title": "Formatkonverterare", + "description": "Konvertera mellan JSON, YAML, TOML och XML i alla riktningar. Alla 12 kombinationer stöds." + }, + "mockDataGenerator": { + "title": "Mock-datagenerator", + "description": "Definiera kolumner, välj datatyper och exportera dataset för seeds, Postman eller tester. Upp till 5000 rader, allt i webbläsaren — inget laddas upp." + }, + "httpStatusCodes": { + "title": "HTTP-statuskoder", + "description": "Sök HTTP-statuskoder med beskrivningar (1xx–5xx)." + }, + "svgOptimizer": { + "title": "SVG-optimering", + "description": "Klistra in SVG, ta bort brus med SVGO och se UTF-8-storleksbesparing." + }, + "unitConverter": { + "title": "Enhetsomvandlare", + "description": "Konvertera mellan 323 enheter i 43 vetenskapliga och tekniska kategorier." + }, + "dockerComposeGenerator": { + "title": "Docker Compose-generator", + "description": "Välj databaser, cache, köer, proxys och observabilitetsbilder; exportera docker-compose.yml för lokala stackar." + }, + "game2048": { + "title": "2048", + "description": "Skjut och slå samman plattor för att nå 2048. Klassiskt nummerpusselspel." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klassiskt nummerpussel. 3 svårighetsgrader, 150 steg vardera." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Bläddra bland nycklar, undersök värden och kör kommandon på valfri Redis-instans." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt-generator och -kontroll", + "description": "Hasha lösenord med bcrypt och verifiera befintliga hashar." + }, + "chmodCalculator": { + "title": "Chmod-kalkylator", + "description": "Konvertera Unix-behörigheter mellan oktal, symbolisk notation och kryssrutor." + }, + "codeScreenshot": { + "title": "Kodskärmbild", + "description": "Förvandla källkod till vackra, delbara bilder." + }, + "cssGenerators": { + "title": "CSS-generatorer", + "description": "Generatorer för box-shadow, border-radius och flytande clamp()." + }, + "exifViewer": { + "title": "EXIF-visare och -borttagare", + "description": "Inspektera och ta bort EXIF-metadata från foton lokalt." + }, + "faviconGenerator": { + "title": "Favicon-generator", + "description": "Generera favicons, appikoner och manifest-utdrag." + }, + "jsonDiff": { + "title": "JSON-diff", + "description": "Semantisk, strukturmedveten jämförelse av två JSON-dokument." + }, + "jsonpathPlayground": { + "title": "JSONPath-lekplats", + "description": "Fråga JSON med JSONPath eller JMESPath och se resultat i realtid." + }, + "keycodeInspector": { + "title": "Tangentkodsinspektör", + "description": "Tryck på en tangent för att se dess JavaScript-händelsekoder och modifierare." + }, + "markdownTableGenerator": { + "title": "Markdown-tabellgenerator", + "description": "Bygg Markdown- och HTML-tabeller i en kalkylbladsliknande rutnätsredigerare." + }, + "timezoneConverter": { + "title": "Tidszonskonverterare", + "description": "Konvertera en tid över många zoner med dagsförskjutning och markeringar för arbetstid." + }, + "tokenCounter": { + "title": "LLM-token-räknare", + "description": "Räkna LLM-tokens, visualisera chunkar och uppskatta API-kostnad." + }, + "webhookTester": { + "title": "Webhook-testare", + "description": "Fånga och inspektera inkommande webhook-förfrågningar i realtid." + }, + "websocketTester": { + "title": "WebSocket-testare", + "description": "Anslut till WebSocket-slutpunkter och utbyt meddelanden i realtid." + }, + "whoisLookup": { + "title": "Whois-uppslag", + "description": "Slå upp registreringsuppgifter för domäner och IP via RDAP." + } + }, + "tabs": { + "apps": "Appar", + "analytics": "Analys", + "ui": "Gränssnitt" + }, + "analytics": { + "title": "Dina data i korthet", + "subtitle": "Antal synkroniserade med ditt konto på servern.", + "loadError": "Kunde inte ladda analys.", + "retry": "Försök igen", + "loginHint": "Logga in för att se användningsanalys och gränssnittsinställningar.", + "passwordEntries": "Lösenordsposter", + "bookmarks": "Bokmärken", + "bookmarkFolders": "Bokmärkesmappar", + "tasksTotal": "Uppgifter", + "tasksCompleted": "Klara", + "tasksOngoing": "Pågående", + "tasksNotStarted": "Ej påbörjade", + "projects": "Projekt", + "nosqlConnections": "DB-anslutningar", + "notes": "Anteckningar", + "apiClientCollections": "API-samlingar", + "apiClientEnvironments": "API-miljöer", + "apiClientHistory": "API-historikposter", + "jsonFormatterDocuments": "JSON-formateringsdokument", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Kodutdrag", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Gränssnittsinställningar", + "subtitle": "Tema, språk och synliga verktyg i sidofältet — från dina kontoinställningar.", + "loadError": "Kunde inte ladda inställningar.", + "loginHint": "Logga in för att se dina sparade gränssnittsinställningar.", + "theme": "Tema", + "accent": "Accentfärg", + "locale": "Språk", + "enabledTools": "Synliga verktyg", + "favoriteTools": "Favoritverktyg", + "toolsWithUsage": "Verktyg med användningsstatistik" + }, + "signIn": "Logga in", + "noResults": "Inga verktyg matchar din sökning", + "noResultsHint": "Prova ett annat sökord eller rensa filtret.", + "filterAll": "Alla", + "loginCta": { + "description": "Logga in för att spåra dina senast använda verktyg." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Laddar...", + "myTasksTitle": "Mina uppgifter", + "statsLine": "{total} uppgifter • {percent}% klart", + "statsLineDesktop": "{total} totalt • {percent}% klart" + }, + "filters": { + "all": "Alla", + "total": "Totalt", + "allProjects": "Alla projekt", + "export": "Exportera", + "kanban": "Kanban", + "list": "Lista", + "searchPlaceholder": "Sök uppgifter...", + "searchAria": "Sök uppgifter", + "clearSearchAria": "Rensa sökning", + "foundTasks": "Hittade {count} {count, plural, one {uppgift} other {uppgifter}}", + "filterByAllAria": "Filtrera efter alla", + "filterByStatusAria": "Filtrera efter {status}" + }, + "status": { + "not-started": { + "label": "Ej påbörjad", + "description": "Uppgifter som inte har påbörjats ännu" + }, + "ongoing": { + "label": "Pågående", + "description": "Uppgifter som pågår just nu" + }, + "completed": { + "label": "Avslutad", + "description": "Uppgifter som är klara" + } + }, + "priorities": { + "high": "Hög", + "medium": "Medel", + "low": "Låg" + }, + "form": { + "addNewTaskLabel": "Lägg till ny uppgift", + "multilinePlaceholder": "Lägg till uppgiftsdetaljer... (Shift+Enter för ny rad)", + "singlelinePlaceholder": "Vad behöver göras? Tryck Enter för att lägga till", + "taskInputAria": "Uppgiftsinmatning", + "projectPlaceholder": "Projekt", + "selectProject": "Välj projekt", + "errorSelectProject": "Välj ett projekt eller skapa ett nytt för att lägga till en uppgift.", + "hintMultiline": "Shift+Enter för ny rad, Enter för att lägga till", + "hintSingleline": "Enter för att lägga till", + "shortcutsAria": "Tangentbordsgenvägar", + "shortcutsLabel": "Genvägar", + "shortcutAddTask": "Lägg till uppgift", + "shortcutNewLine": "Ny rad", + "shortcutClear": "Rensa", + "addButton": "Lägg till" + }, + "list": { + "ariaLabel": "Uppgiftslista", + "loading": "Laddar dina uppgifter...", + "emptyTitle": "Inga uppgifter hittades", + "emptyPrimary": "Kom igång genom att lägga till din första uppgift ovan! Tryck Enter för att snabbt lägga till uppgifter.", + "emptySecondary": "Prova att justera dina filter eller sökfråga." + }, + "mobileDrawer": { + "addTaskSrOnly": "Lägg till uppgift", + "addNewTaskTitle": "Lägg till ny uppgift", + "addNewTaskDescription": "Skapa en ny uppgift för att spåra dina framsteg.", + "cancel": "Avbryt" + }, + "kanban": { + "ariaLabel": "Kanban-tavla", + "loading": "Laddar dina uppgifter...", + "dropHere": "Släpp uppgift här", + "noTasksYet": "Inga uppgifter ännu", + "emptyColumnAria": "Tom kolumn", + "columnAria": "{title}-kolumn med {count} uppgifter", + "countInColumnAria": "{count} uppgifter i {title}" + }, + "taskItem": { + "confirmDelete": "Är du säker på att du vill ta bort denna uppgift?", + "statusPlaceholder": "Status", + "changeStatusAria": "Ändra uppgiftsstatus", + "taskOptionsAria": "Uppgiftsalternativ", + "editTask": "Redigera uppgift", + "copyTask": "Kopiera uppgift", + "copied": "Kopierat!", + "deleteTask": "Ta bort uppgift", + "statusHeading": "Status", + "editTaskAria": "Redigera uppgift" + }, + "editDialog": { + "title": "Redigera uppgift", + "description": "Uppdatera uppgiftsdetaljer, lägg till deluppgifter, ställ in prioriteter och mer.", + "taskTitleLabel": "Uppgiftstitel *", + "taskTitlePlaceholder": "Vad behöver göras?", + "descriptionLabel": "Beskrivning", + "descriptionPlaceholder": "Lägg till mer detaljer om denna uppgift...", + "statusLabel": "Status", + "priorityLabel": "Prioritet", + "dueDateLabel": "Förfallodatum", + "pickDate": "Välj ett datum", + "clearDate": "Rensa datum", + "projectLabel": "Projekt", + "selectProjectPlaceholder": "Välj ett projekt", + "noProject": "Inget projekt", + "timeEstimateLabel": "Tidsuppskattning (minuter)", + "timeEstimatePlaceholder": "Hur lång tid tar det?", + "tagsLabel": "Taggar", + "customTagNamePlaceholder": "Eget taggnamn", + "subtasksLabel": "Deluppgifter", + "addSubtaskPlaceholder": "Lägg till en deluppgift...", + "cancel": "Avbryt", + "saving": "Sparar...", + "saveChanges": "Spara ändringar" + }, + "export": { + "title": "Exportera uppgifter", + "description": "Exportera dina uppgifter till JSON-, CSV- eller Excel-format.", + "projectLabel": "Projekt", + "statusLabel": "Status", + "tagLabel": "Tagg", + "allProjects": "Alla projekt", + "allStatuses": "Alla statusar", + "allTags": "Alla taggar", + "exportAsJsonTitle": "Exportera som JSON", + "exportAsJsonDescription": "Exportera uppgiftsdata inklusive deluppgifter, taggar och metadata.", + "exportJson": "Exportera JSON", + "exportAsCsvExcelTitle": "Exportera som CSV / Excel", + "exportAsCsvExcelDescription": "Exportera uppgifter för kalkylbladsprogram.", + "exportCsv": "Exportera CSV", + "exportExcel": "Exportera Excel", + "close": "Stäng", + "toastNoTasks": "Inga uppgifter att exportera med aktuella filter", + "toastExportedJson": "Exporterade {count} uppgifter till JSON!", + "toastExportedCsv": "Exporterade {count} uppgifter till CSV!", + "toastExportedExcel": "Exporterade {count} uppgifter till Excel!", + "toastFailedJson": "Misslyckades att exportera till JSON", + "toastFailedCsv": "Misslyckades att exportera till CSV", + "toastFailedExcel": "Misslyckades att exportera till Excel", + "unknownProject": "Okänt projekt" + }, + "ack": { + "taskAddedTitle": "Uppgift tillagd", + "taskAddedFailedTitle": "Misslyckades att lägga till uppgift", + "taskUpdatedTitle": "Uppgift uppdaterad", + "taskUpdatedFailedTitle": "Misslyckades att uppdatera uppgift", + "taskStatusUpdateFailedTitle": "Misslyckades att uppdatera uppgiftsstatus", + "taskMovedTitle": "Uppgift flyttad till {status}", + "tryAgain": "Försök igen.", + "taskDeletedTitle": "Uppgift \"{text}\" borttagen", + "undo": "Ångra", + "taskRestoredTitle": "Uppgift återställd", + "taskRestoredSuccessTitle": "Uppgift återställd", + "taskRestoreFailedTitle": "Misslyckades att återställa uppgift. Försök igen.", + "taskDeleteFailedTitle": "Misslyckades att ta bort uppgift. Försök igen.", + "tasksImportedTitle": "Importerade {count} {count, plural, one {uppgift} other {uppgifter}}!", + "tasksImportFailedTitle": "Misslyckades att importera uppgifter. Försök igen.", + "unknownProject": "Okänt projekt", + "projectCreatedTitle": "Projekt skapat", + "projectCreateFailedTitle": "Misslyckades att skapa projekt", + "projectUpdatedTitle": "Projekt uppdaterat", + "projectUpdateFailedTitle": "Misslyckades att uppdatera projekt", + "projectDeletedTitle": "Projekt borttaget", + "projectDeleteFailedTitle": "Misslyckades att ta bort projekt" + } + }, + "Notes": { + "page": { + "loading": "Laddar..." + }, + "layout": { + "mobileNavTitle": "Anteckningsnavigering", + "openMenuAria": "Öppna anteckningsmeny" + }, + "sidebar": { + "title": "Anteckningar", + "createNoteAria": "Skapa anteckning", + "searchPlaceholder": "Sök anteckningar...", + "loading": "Laddar...", + "noNotesFound": "Inga anteckningar hittades.", + "noNotesYet": "Inga anteckningar ännu. Klicka + för att skapa en.", + "inParent": "i {parentTitle}", + "untitled": "Namnlös", + "addSubPage": "Lägg till undersida", + "delete": "Ta bort", + "deleteConfirmTitle": "Ta bort anteckning?", + "deleteConfirmDescription": "Är du säker på att du vill ta bort \"{title}\"? Denna åtgärd kan inte ångras.", + "cancel": "Avbryt", + "confirmDelete": "Ta bort", + "noteOptionsAria": "Anteckningsalternativ" + }, + "editor": { + "emptyState": "Välj en anteckning eller skapa en ny för att komma igång.", + "titlePlaceholder": "Namnlös", + "saving": "Sparar…", + "ready": "Klar", + "saved": "Sparad {time}", + "stats": "{words} ord · {read} min läsning", + "template": "Mall", + "applyTemplate": "Använd mall", + "exportMdLabel": ".md", + "exportMd": "Exportera som Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Exportera som HTML", + "focusMode": "Fokusläge", + "exitFocusMode": "Avsluta fokusläge", + "save": "Spara", + "addTag": "Lägg till tagg…", + "removeTag": "Ta bort tagg {tag}", + "loading": "Laddar…", + "searchHint": "för att söka", + "focusHint": "fokusläge", + "notePath": "Anteckningssökväg" + }, + "context": { + "authRequiredError": "Användaren är inte autentiserad", + "defaultTitle": "Namnlös", + "cloudSyncImageError": "Aktivera molnsynkronisering för att lagra anteckningsbilder. Textanteckningar förblir lokala." + } + }, + "PasswordManager": { + "page": { + "loading": "Laddar...", + "title": "Lösenordshanterare", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Misslyckades att dekryptera lösenord", + "toastLoadFailed": "Misslyckades att ladda lösenord", + "toastUnlockedAuto": "Valvet låstes upp automatiskt", + "toastAutoUnlockFailed": "Automatisk upplåsning misslyckades. Lås upp manuellt.", + "toastConnectFailed": "Misslyckades att ansluta till servern", + "toastAutoUnlockWarning": "Valvet upplåst, men automatisk upplåsning kanske inte fungerar vid omladdning", + "errorIncorrectPassword": "Felaktigt huvudlösenord", + "errorUnlockFailed": "Misslyckades att låsa upp valvet", + "errorPasswordsMismatch": "Lösenorden matchar inte", + "errorPasswordTooShort": "Lösenordet måste vara minst 8 tecken", + "errorSetupFailed": "Misslyckades att konfigurera valvet", + "errorConnectFailed": "Misslyckades att ansluta till servern.", + "mobileTitleCreate": "Skapa ditt valv", + "mobileTitleWelcome": "Välkommen tillbaka", + "mobileSubtitleCreate": "Ange ett huvudlösenord för att skydda dina uppgifter", + "mobileSubtitleUnlock": "Ange ditt huvudlösenord för att låsa upp", + "placeholderMasterPassword": "Huvudlösenord", + "placeholderConfirmPassword": "Bekräfta lösenord", + "labelMasterPassword": "Huvudlösenord", + "labelConfirmPassword": "Bekräfta lösenord", + "placeholderEnterMaster": "Ange huvudlösenord", + "placeholderConfirmMaster": "Bekräfta huvudlösenord", + "buttonCreateVault": "Skapa valv", + "buttonUnlock": "Lås upp", + "buttonUnlockVault": "Lås upp valvet", + "footerCreate": "Ditt lösenord lagras aldrig och kan inte återställas", + "footerUnlock": "Säkrad med end-to-end-kryptering", + "desktopTitleSetup": "Konfigurera lösenordsvalv", + "desktopTitleUnlock": "Lås upp valvet", + "desktopDescriptionSetup": "Skapa ett huvudlösenord för att säkra dina uppgifter. Detta lösenord lagras aldrig och kan inte återställas.", + "desktopDescriptionUnlock": "Ange ditt huvudlösenord för att komma åt dina säkrade uppgifter." + }, + "list": { + "loading": "Laddar dina lösenord...", + "emptyTitle": "Valvet är tomt", + "emptyHintMobile": "Tryck på +-knappen för att lägga till ditt första lösenord", + "emptyHintDesktop": "Du har inte lagrat några lösenord ännu. Klicka på knappen \"Lägg till lösenord\" ovan för att komma igång.", + "addPassword": "Lägg till lösenord", + "searchPlaceholderMobile": "Sök i ditt valv...", + "searchPlaceholderDesktop": "Sök i valvet...", + "importExport": "Importera / exportera", + "lockVault": "Lås valvet", + "allPasswordsTitle": "Alla lösenord", + "passwordCount": "{count, plural, one {# lösenord} other {# lösenord}}", + "vaultHealthDrawerTitle": "Valvhälsa", + "noResults": "Inga lösenord hittades som matchar \"{query}\"", + "tableService": "Tjänst", + "tableUsername": "Användarnamn", + "tablePassword": "Lösenord", + "tableUrl": "URL", + "tableActions": "Åtgärder", + "gridViewAria": "Rutnätsvy", + "listViewAria": "Listvy", + "passwordStrengthTitle": "Lösenordsstyrka", + "deleteDialogTitle": "Ta bort lösenord", + "deleteDialogDescription": "Är du säker på att du vill ta bort detta lösenord? Denna åtgärd kan inte ångras.", + "cancel": "Avbryt", + "delete": "Ta bort", + "edit": "Redigera" + }, + "toasts": { + "copiedPassword": "Lösenord kopierat till urklipp", + "copiedUsername": "Användarnamn kopierat till urklipp", + "passwordDeleted": "Lösenord borttaget", + "deleteFailed": "Misslyckades att ta bort lösenord", + "vaultLocked": "Valvet låst", + "importedCount": "Importerade {count, plural, one {# lösenord} other {# lösenord}}" + }, + "form": { + "addTitle": "Lägg till nytt lösenord", + "editTitle": "Redigera lösenord", + "serviceName": "Tjänstnamn", + "usernameEmail": "Användarnamn / e-post", + "password": "Lösenord", + "urlOptional": "Webbadress (valfritt)", + "notesOptional": "Anteckningar (valfritt)", + "tags": "Taggar", + "placeholderService": "t.ex. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Ange eller generera lösenord", + "placeholderUrl": "https://...", + "placeholderNotes": "Ytterligare detaljer...", + "placeholderTags": "Lägg till taggar (tryck Enter eller komma)", + "strength": "Styrka", + "strengthWeak": "Svagt", + "strengthMedium": "Medel", + "strengthStrong": "Starkt", + "advancedGenerator": "Avancerad generator", + "cancel": "Avbryt", + "saving": "Sparar...", + "savePassword": "Spara lösenord", + "updating": "Uppdaterar...", + "updatePassword": "Uppdatera lösenord", + "addPasswordSrOnly": "Lägg till lösenord", + "triggerAdd": "Lägg till lösenord", + "toastSaved": "Lösenord sparat", + "toastSaveFailed": "Misslyckades att spara lösenord", + "toastUpdated": "Lösenord uppdaterat", + "toastUpdateFailed": "Misslyckades att uppdatera lösenord" + }, + "security": { + "vaultHealth": "Valvhälsa", + "statusLabel": "Status", + "totalPasswords": "Totalt antal lösenord", + "activeEntries": "Aktiva poster i ditt valv", + "protected": "Skyddat", + "weakPasswords": "Svaga lösenord", + "needingImprovement": "Lösenord som behöver förbättras", + "allGood": "Allt bra", + "reusedPasswords": "Återanvända lösenord", + "usedMultipleTimes": "Lösenord som används flera gånger", + "unique": "Unika", + "total": "Totalt", + "weak": "Svaga", + "reused": "Återanvända", + "totalCountLabel": "{count, plural, one {# lösenord} other {# lösenord}}", + "weakCountLabel": "{count} svaga", + "allSecure": "Alla säkra", + "scoreExcellent": "Utmärkt", + "scoreGood": "Bra", + "scoreFair": "Godtagbart", + "scoreCritical": "Kritiskt" + }, + "importExport": { + "title": "Importera / exportera valv", + "description": "Hantera dina lösenordsdata. Du kan exportera ditt valv till JSON eller importera från en JSON-fil.", + "tabExport": "Exportera", + "tabImport": "Importera", + "warningTitle": "Varning", + "warningBody": "Exporterade filer innehåller dina lösenord i klartext. Förvara filen säkert och ta bort den efter användning.", + "exportBlurb": "Ladda ner hela ditt valv som en JSON-fil. Detta inkluderar alla tjänster, användarnamn, lösenord och anteckningar.", + "downloadJson": "Ladda ner JSON-export", + "pasteLabel": "Klistra in JSON-data", + "pastePlaceholder": "[{\"service\": \"Exempel\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Klistra in innehållet från en tidigare exporterad JSON-fil här.", + "importing": "Importerar...", + "importPasswords": "Importera lösenord", + "trigger": "Importera / exportera", + "toastExported": "Valvet exporterat", + "toastExportFailed": "Misslyckades att exportera valvet", + "toastImportFailed": "Misslyckades att importera: ogiltigt JSON-format" + }, + "card": { + "copyUsername": "Kopiera användarnamn", + "openUrl": "Öppna URL", + "strength": "Styrka" + }, + "swipeable": { + "copy": "Kopiera", + "delete": "Ta bort", + "copyPassword": "Kopiera lösenord", + "copyUsername": "Kopiera användarnamn", + "edit": "Redigera", + "deleteAction": "Ta bort" + }, + "floatingMenu": { + "lockVault": "Lås valvet", + "importExport": "Importera / exportera", + "addPassword": "Lägg till lösenord" + }, + "generator": { + "tabPassword": "Lösenord", + "tabPassphrase": "Lösenfras", + "strength": "Styrka", + "lengthLabel": "Längd: {length} tecken", + "lengthBadge": "{length} tecken", + "characterSets": "Teckenuppsättningar", + "uppercase": "Versaler", + "lowercase": "Gemener", + "numbers": "Siffror", + "symbols": "Symboler", + "wordCountLabel": "Antal ord: {count}", + "wordCountBadge": "{count, plural, one {# ord} other {# ord}}", + "separator": "Separator", + "capitalization": "Versalisering", + "includeNumber": "Inkludera siffra" + } + }, + "EnvironmentManager": { + "subtitle": "Krypterade miljövariabler per projekt och miljö.", + "page": { + "loading": "Laddar…", + "title": "Miljöhanterare", + "loadFailed": "Kunde inte ladda miljöuppsättningar" + }, + "list": { + "title": "Miljöer", + "loading": "Laddar dina uppsättningar…", + "emptyTitle": "Inga miljöuppsättningar ännu", + "emptyHint": "Skapa en uppsättning för varje projekt och miljö (t.ex. produktions-API-nycklar). Allt krypteras i webbläsaren före uppladdning.", + "searchPlaceholder": "Sök projekt, miljö, taggar, anteckningar…", + "tagFilterPlaceholder": "Filtrera på tagg", + "variableCount": "{count, plural, one {# variabel} other {# variabler}}", + "updatedAgo": "Uppdaterad {time}", + "copyDotEnv": "Kopiera .env", + "edit": "Redigera", + "delete": "Ta bort", + "cancel": "Avbryt", + "confirmDelete": "Ta bort", + "deleteDialogTitle": "Ta bort miljöuppsättning?", + "deleteDialogDescription": "Detta tar bort den krypterade blobben från ditt konto. Kan inte ångras.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env kopierat till urklipp", + "deleted": "Miljöuppsättning borttagen", + "deleteFailed": "Kunde inte ta bort" + }, + "form": { + "addTitle": "Ny miljöuppsättning", + "editTitle": "Redigera miljöuppsättning", + "triggerAdd": "Lägg till miljöuppsättning", + "addSrOnly": "Lägg till miljöuppsättning", + "project": "Projekt", + "environment": "Miljö", + "placeholderProject": "t.ex. billing-api", + "placeholderEnvironment": "t.ex. production", + "pasteEnvLabel": "Klistra in .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Slå ihop inklistrade rader", + "variables": "Variabler", + "addRow": "Lägg till rad", + "keyPlaceholder": "NYCKEL", + "valuePlaceholder": "värde", + "notesOptional": "Anteckningar (valfritt)", + "placeholderNotes": "Rotationspolicy, ägare, länkar…", + "tags": "Taggar", + "placeholderTags": "Lägg till taggar (Enter eller komma)", + "cancel": "Avbryt", + "save": "Spara", + "saving": "Sparar…", + "update": "Uppdatera", + "updating": "Uppdaterar…", + "toastSaved": "Miljöuppsättning sparad", + "toastSaveFailed": "Kunde inte spara", + "toastUpdated": "Miljöuppsättning uppdaterad", + "toastUpdateFailed": "Kunde inte uppdatera", + "toastPasteEmpty": "Inga NYCKEL=värde-rader hittades", + "toastPasteApplied": "{count} variabler sammanslagna från inklistring", + "toastProjectEnvRequired": "Projekt och miljö krävs", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Laddar..." + }, + "manager": { + "foldersHeading": "Mappar", + "tagsHeading": "Taggar", + "tagsMore": "+{count} fler", + "tagsShowLess": "Show less", + "searchPlaceholder": "Sök bland dina bokmärken...", + "import": "Importera", + "export": "Exportera", + "exportAsHtml": "Exportera som HTML", + "exportAsJson": "Exportera som JSON", + "addBookmark": "Lägg till bokmärke", + "matchingQuery": "matchar \"{query}\"", + "bookmarkCount": "{count, plural, one {# bokmärke} other {# bokmärken}}", + "allBookmarks": "Alla bokmärken", + "uncategorized": "Okategoriserade", + "unknownFolder": "Okänd" + }, + "card": { + "urlCopied": "URL kopierad till urklipp", + "bookmarkDeleted": "Bokmärke borttaget", + "edit": "Redigera", + "delete": "Ta bort", + "copyUrl": "Kopiera URL", + "deleteConfirmTitle": "Ta bort bokmärke?", + "deleteConfirmDescription": "Detta kommer permanent ta bort \"{title}\".", + "cancel": "Avbryt" + }, + "grid": { + "emptyTitle": "Inga bokmärken ännu", + "emptyDescription": "Lägg till ditt första bokmärke eller importera från din webbläsare för att komma igång." + }, + "folderTree": { + "allBookmarks": "Alla bokmärken", + "addSubfolder": "Lägg till undermapp", + "rename": "Byt namn", + "deleteFolderTitle": "Ta bort mapp \"{name}\"?", + "deleteFolderDescription": "Detta kommer ta bort mappen och alla undermappar. Bokmärken i dessa mappar flyttas till Okategoriserade." + }, + "import": { + "title": "Importera bokmärken", + "description": "Importera bokmärken från din webbläsares exporterade fil.", + "parseError": "Misslyckades att tolka bokmärkesfil. Kontrollera att det är en giltig bokmärkesexport.", + "successToast": "Importerade {bookmarks} bokmärken och {folders} mappar", + "errorToast": "Misslyckades att importera bokmärken", + "chromeInstruction": "Meny → Bokmärken → Bokmärkeshanteraren → ⋮ → Exportera bokmärken", + "firefoxInstruction": "Meny → Bokmärken → Hantera bokmärken → Import och säkerhetskopiering → Exportera", + "safariInstruction": "Arkiv → Exportera bokmärken...", + "edgeInstruction": "Meny → Favoriter → ⋯ → Exportera favoriter", + "dropZoneTitle": "Släpp din bokmärkesfil här", + "dropZoneHint": "eller klicka för att bläddra (HTML eller JSON)", + "selectedFileHint": "Klicka för att välja en annan fil", + "errorTitle": "Fel", + "previewTitle": "Förhandsgranskning", + "previewBookmarks": "{count, plural, one {# bokmärke} other {# bokmärken}}", + "previewFolders": "{count, plural, one {# mapp} other {# mappar}}", + "importModeLabel": "Importläge", + "modeMerge": "Sammanfoga med befintliga bokmärken", + "modeReplace": "Ersätt alla befintliga bokmärken", + "cancel": "Avbryt", + "importButton": "Importera", + "importButtonWithCount": "Importera {count, plural, one {# bokmärke} other {# bokmärken}}" + }, + "addBookmark": { + "titleAdd": "Lägg till bokmärke", + "titleEdit": "Redigera bokmärke", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Ange en giltig URL", + "titleLabel": "Titel", + "titlePlaceholder": "Bokmärkestitel", + "titleRequired": "Ange en titel", + "descriptionLabel": "Beskrivning (valfritt)", + "descriptionPlaceholder": "Lägg till en anteckning om detta bokmärke", + "folderLabel": "Mapp", + "folderPlaceholder": "Välj mapp", + "noFolder": "Ingen mapp", + "tagsLabel": "Taggar", + "tagsPlaceholder": "Lägg till taggar (tryck Enter)", + "toastUpdated": "Bokmärke uppdaterat", + "toastAdded": "Bokmärke tillagt", + "cancel": "Avbryt", + "submitAdd": "Lägg till bokmärke", + "submitUpdate": "Uppdatera bokmärke" + }, + "addFolder": { + "title": "Skapa mapp", + "nameLabel": "Mappnamn", + "namePlaceholder": "Min mapp", + "nameRequired": "Ange ett mappnamn", + "parentLabel": "Överordnad mapp", + "parentPlaceholder": "Välj överordnad mapp", + "rootParent": "Rot (ingen överordnad)", + "toastCreated": "Mapp skapad", + "cancel": "Avbryt", + "submit": "Skapa mapp" + }, + "editFolder": { + "title": "Byt namn på mapp", + "nameLabel": "Mappnamn", + "namePlaceholder": "Mappnamn", + "nameRequired": "Ange ett mappnamn", + "toastRenamed": "Mapp omdöpt", + "cancel": "Avbryt", + "save": "Spara" + } + }, + "EmailValidator": { + "header": { + "title": "E-postvalidering", + "description": "Verifiera och validera e-postadresser för syntax, domän och leveransbarhet." + }, + "tabs": { + "single": "Enskild verifiering", + "bulk": "Massuppladdning" + }, + "single": { + "cardTitle": "Enskild e-postverifiering", + "cardDescription": "Ange en e-postadress för att kontrollera leveransbarhet och rykte.", + "placeholder": "Ange e-postadress...", + "clearAria": "Rensa e-post", + "validate": "Validera", + "validating": "Validerar...", + "formatError": "Ange en giltig e-postadress" + }, + "toasts": { + "errorTitle": "Fel", + "enterEmail": "Ange en e-postadress", + "invalidFormatTitle": "Ogiltigt e-postformat", + "invalidFormatDescription": "Ange en giltig e-postadress", + "validateFailedDescription": "Ett fel uppstod vid validering av e-postadressen", + "noEmailsTitle": "Inga e-postadresser hittades", + "noEmailsDescription": "Kontrollera att din Excel-fil har en 'email'-kolumn.", + "fileTooLargeTitle": "Filen är för stor", + "fileTooLargeDescription": "Hittade {total} e-postadresser. Bara de första 4000 kommer att valideras.", + "successTitle": "Lyckades", + "successValidated": "Validerade {count} e-postadresser.", + "batchFailedTitle": "Massvalidering misslyckades", + "batchFailedDescription": "Ett fel uppstod vid bearbetning av massvalidering.", + "noExportTitle": "Ingen data att exportera", + "noExportValidOnly": "Inga giltiga e-postadresser hittades att exportera.", + "noExportAll": "Inga resultat att exportera.", + "exportSuccessTitle": "Export lyckades", + "exportSuccessExcelAll": "Exporterade {count} e-postadresser till Excel.", + "exportSuccessExcelValid": "Exporterade {count} giltiga e-postadresser till Excel.", + "exportSuccessCsvAll": "Exporterade {count} e-postadresser till CSV.", + "exportSuccessCsvValid": "Exporterade {count} giltiga e-postadresser till CSV." + }, + "disposableAlert": { + "title": "Engångs-e-post upptäckt", + "description": "Denna e-postadress tillhör en engångs-e-postleverantör. Den kan vara tillfällig eller osäker." + }, + "score": { + "reliabilityTitle": "Tillförlitlighetspoäng", + "scoreLabel": "Poäng" + }, + "details": { + "sectionTitle": "Valideringsdetaljer" + }, + "validation": { + "syntaxLabel": "Syntax giltig", + "syntaxTooltip": "Kontrollerar om e-postadressen följer rätt format (t.ex. user@domain.com)", + "domainLabel": "Domän finns", + "domainTooltip": "Verifierar att domännamnet i e-postadressen faktiskt finns", + "mxLabel": "MX-poster hittade", + "mxTooltip": "Kontrollerar om domänen har MX-poster (mail exchange) konfigurerade för att ta emot e-post", + "mailboxLabel": "Brevlåda finns", + "mailboxTooltip": "Verifierar att den specifika brevlådan/kontot finns på e-postservern", + "disposableLabel": "Engångs-e-post", + "disposableTooltip": "Indikerar om denna e-post tillhör en tillfällig/engångs-e-posttjänst (kan vara riskabel)", + "roleBasedLabel": "Rollbaserad e-post", + "roleBasedTooltip": "Identifierar om detta är en rollbaserad e-post (t.ex. support@, info@) snarare än en personlig adress" + }, + "copy": { + "emailCopied": "E-post kopierad!", + "copyTooltip": "Kopiera e-post" + }, + "bulk": { + "dropHere": "Släpp din fil här", + "dragHere": "Dra och släpp din fil här", + "uploadHint": "Ladda upp en .xlsx-, .xls- eller .csv-fil med en 'email'-kolumn för att validera upp till 4000 e-postadresser.", + "filePrefix": "Fil:", + "selectFile": "Välj fil", + "downloadTemplate": "Ladda ner mall" + }, + "bulkLoading": { + "title": "Validerar e-postadresser...", + "batchProgress": "Bearbetar batch {current} av {total}..." + }, + "stats": { + "totalProcessed": "Totalt bearbetade", + "valid": "Giltiga", + "risky": "Riskfyllda", + "invalid": "Ogiltiga" + }, + "results": { + "detailedTitle": "Detaljerade resultat", + "searchPlaceholder": "Sök e-postadresser...", + "newVerification": "Ny verifiering", + "exportAll": "Exportera alla", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Exportera giltiga ({count})" + }, + "showingResults": "Visar {shown} av {total} resultat", + "table": { + "email": "E-post", + "status": "Status", + "score": "Poäng", + "checks": "Kontroller" + }, + "noSearchResults": "Inga resultat hittades som matchar \"{query}\"", + "checksTooltip": { + "syntax": "Syntax", + "domain": "Domän", + "mx": "MX", + "mailbox": "Brevlåda" + }, + "exportColumns": { + "email": "E-post", + "status": "Status", + "score": "Poäng", + "syntax": "Syntax", + "domain": "Domän", + "mx": "MX", + "mailbox": "Brevlåda", + "disposable": "Engångs", + "roleBased": "Rollbaserad", + "valid": "Giltig", + "invalid": "Ogiltig", + "yes": "Ja", + "no": "Nej" + }, + "sheets": { + "validEmails": "Giltiga e-postadresser", + "results": "Resultat", + "template": "Mall" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL-utforskare", + "heroSubtitle": "Anslut till dina databaser för att hantera samlingar, köra frågor och visualisera dina data.", + "featureMultiTitle": "Multi-anslutning", + "featureMultiDesc": "Hantera flera databasanslutningar samtidigt.", + "featureMongoTitle": "MongoDB-stöd", + "featureMongoDesc": "Inbyggt stöd för MongoDB med avancerad filtrering.", + "featureQueryTitle": "Smart fråga", + "featureQueryDesc": "Kraftfull frågebyggare med syntaxmarkering.", + "connectCta": "Anslut till MongoDB", + "footerNote": "Stöder för närvarande {db}. Fler databaser kommer snart.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Välj en samling", + "selectCollectionDesc": "Välj en samling från sidofältet för att visa dokument och börja utforska dina data.", + "dialogAddConnection": "Lägg till anslutning", + "toastConnectionAdded": "Anslutning tillagd", + "toastFetchFailed": "Misslyckades att hämta dokument för {collection}", + "toastDocDeleted": "Dokument borttaget", + "deleteDocTitle": "Är du helt säker?", + "deleteDocDescription": "Denna åtgärd kan inte ångras. Dokumentet kommer permanent tas bort från din samling.", + "closeAllTitle": "Stäng alla flikar?", + "closeAllDescription": "Är du säker på att du vill stänga alla flikar? Osparade ändringar i frågefilter kommer att gå förlorade.", + "closeAllConfirm": "Stäng alla", + "cancel": "Avbryt", + "delete": "Ta bort" + }, + "connection": { + "titleEdit": "Redigera anslutning", + "titleConnect": "Anslut till MongoDB", + "subtitleEdit": "Uppdatera dina anslutningsuppgifter nedan.", + "subtitleConnect": "Ange din MongoDB-anslutningssträng för att börja utforska dina data.", + "labelName": "Namn", + "placeholderName": "t.ex. Produktionsdatabas", + "labelConnectionString": "Anslutningssträng", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://username:password@host:port/database", + "testConnection": "Testa anslutning", + "connecting": "Ansluter...", + "updateConnect": "Uppdatera och anslut", + "connect": "Anslut", + "cancelEdit": "Avbryt redigering", + "savedConnections": "Sparade anslutningar", + "emptySaved": "Inga sparade anslutningar ännu.", + "lastUsed": "Senast använd {time}", + "toastTestOk": "Anslutning lyckades!", + "toastTestFail": "Anslutning misslyckades: {message}", + "toastUpdated": "Anslutning uppdaterad", + "confirmDelete": "Är du säker på att du vill ta bort denna sparade anslutning?", + "toastDeletedConn": "Anslutning borttagen", + "toastDeleteFail": "Misslyckades att ta bort anslutning", + "copyStringTitle": "Kopiera anslutningssträng", + "toastStringCopied": "Anslutningssträng kopierad!", + "editTitle": "Redigera", + "deleteTitle": "Ta bort" + }, + "sidebar": { + "explorer": "Utforskare", + "add": "Lägg till", + "tooltipAdd": "Lägg till ny anslutning", + "tooltipRefresh": "Uppdatera anslutningar", + "searchPlaceholder": "Sök...", + "loadingConnections": "Laddar anslutningar...", + "noConnectionsFound": "Inga anslutningar hittades", + "toastLoadFail": "Misslyckades att ladda anslutningar", + "toastConnectFail": "Misslyckades att ansluta till {name}", + "toastCollectionsFail": "Misslyckades att hämta samlingar för {name}", + "toastRenamed": "Anslutning omdöpt", + "toastRenameFail": "Misslyckades att döpa om anslutning", + "confirmDeleteConnection": "Är du säker på att du vill ta bort anslutningen \"{name}\"?", + "toastDeleted": "Anslutning borttagen", + "toastDeleteConnFail": "Misslyckades att ta bort anslutning", + "confirmDropDb": "Är du säker på att du vill ta bort databasen \"{name}\"? Denna åtgärd kan inte ångras.", + "toastDbDropped": "Databas {name} borttagen", + "confirmDropCollection": "Är du säker på att du vill ta bort samlingen \"{name}\"? Denna åtgärd kan inte ångras.", + "toastCollectionDropped": "Samling {name} borttagen", + "toastCollectionRenamed": "Samling omdöpt till {name}", + "toastDatabaseRenamed": "Databas omdöpt till {name}", + "connecting": "Ansluter...", + "connectionFailed": "Anslutning misslyckades", + "noDatabases": "Inga databaser", + "loading": "Laddar...", + "noCollections": "Inga samlingar", + "renameCollectionTitle": "Byt namn på samling", + "renameDatabaseTitle": "Byt namn på databas", + "placeholderNewCollection": "Nytt samlingsnamn", + "placeholderNewDatabase": "Nytt databasnamn", + "rename": "Byt namn", + "refresh": "Uppdatera", + "copyDbName": "Kopiera DB-namn", + "toastDbNameCopied": "Databasnamn kopierat till urklipp", + "dropDatabase": "Ta bort databas", + "dropCollection": "Ta bort samling", + "menuRename": "Byt namn", + "menuCopyConnectionString": "Kopiera anslutningssträng", + "menuDeleteConnection": "Ta bort anslutning", + "toastSidebarStringCopied": "Anslutningssträng kopierad!", + "cancel": "Avbryt", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} dok", + "table": "Tabell", + "json": "JSON", + "tree": "Träd", + "tooltipTable": "Tabellvy", + "tooltipJson": "JSON-vy", + "tooltipTree": "Trädvy", + "itemsPerPage": "Objekt per sida", + "perPage": "{n} / sida", + "prevPage": "Föregående sida", + "nextPage": "Nästa sida", + "expandAll": "Expandera alla", + "collapseAll": "Fäll ihop alla", + "refreshData": "Uppdatera data", + "insert": "Infoga", + "export": "Exportera", + "actions": "Åtgärder", + "invalidJson": "Ogiltigt JSON-innehåll", + "copiedClipboard": "Kopierat till urklipp", + "docUpdated": "Dokument uppdaterat", + "docInserted": "Dokument infogat", + "invalidJsonShort": "Ogiltig JSON", + "docCopied": "Dokument kopierat till urklipp", + "openFullEditor": "Öppna i fullständig redigerare", + "cellArray": "Array({length})", + "cellObject": "Objekt", + "cellArrayBracket": "Array[{length}]", + "emptyFilterTitle": "Inga dokument matchar din fråga", + "emptyFilterHint": "Prova att justera ditt filter eller rensa frågan.", + "clearFilter": "Rensa filter", + "emptyCollectionTitle": "Denna samling är tom", + "emptyCollectionHint": "Infoga ett dokument för att komma igång.", + "insertDocument": "Infoga dokument", + "formatJson": "Formatera JSON", + "compactJson": "Kompakt JSON", + "copyClipboard": "Kopiera till urklipp", + "copyJson": "Kopiera JSON", + "duplicate": "Duplicera", + "editDoc": "Redigera dokument", + "deleteDoc": "Ta bort dokument", + "documentLabel": "Dokument {n}", + "editDialogTitle": "Redigera dokument", + "insertDialogTitle": "Infoga dokument", + "viewValueTitle": "Visa värde", + "saveChanges": "Spara ändringar", + "close": "Stäng", + "cancel": "Avbryt", + "insertButton": "Infoga", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Aktivt filter tillämpat", + "filterActiveTooltip": "Filter aktivt", + "closeAll": "Stäng alla" + }, + "query": { + "migrateHistory": "Migrerade frågehistorik till molnet", + "saveHistoryFail": "Misslyckades att spara frågehistorik", + "deleteQueryFail": "Misslyckades att ta bort fråga", + "invalidJsonQuery": "Ogiltig JSON-fråga", + "placeholder": "t.ex. { \"status\": \"active\" }", + "clearQuery": "Rensa fråga", + "openAdvanced": "Öppna avancerad redigerare", + "visualBuilder": "Visuell frågebyggare", + "filterRules": "Filterregler", + "addRule": "Lägg till regel", + "noFilters": "Inga filter tillämpade. Lägg till en regel för att filtrera dokument.", + "where": "DÄR", + "and": "OCH", + "fieldPlaceholder": "Fält", + "opPlaceholder": "Op", + "typePlaceholder": "Typ", + "opRegex": "Regex", + "opIn": "I", + "opNin": "Inte i", + "opExists": "Finns", + "typeAuto": "Auto", + "typeString": "Sträng", + "typeNumber": "Nummer", + "typeBoolean": "Boolean", + "typeDate": "Datum", + "typeObjectId": "ObjectId", + "true": "Sant", + "false": "Falskt", + "valuePlaceholder": "Värde", + "valueListPlaceholder": "värde1, värde2", + "clearFilters": "Rensa filter", + "applyFilters": "Tillämpa filter", + "queryHistory": "Frågehistorik", + "noSavedQueries": "Inga sparade frågor ännu", + "advancedEditorTitle": "Avancerad frågeredigerare", + "templatesPlaceholder": "Mallar", + "templateEmptyAgg": "Tom aggregering", + "templateMatch": "$match (Filter)", + "templateLookup": "$lookup (Join)", + "templateGroup": "$group (Antal/Summa)", + "format": "Formatera", + "formattedJson": "Formaterad JSON", + "runQuery": "Kör fråga" + }, + "export": { + "selectFieldsError": "Välj minst ett fält att exportera", + "success": "Exporterade till {filename}", + "fail": "Misslyckades att exportera data", + "title": "Exportera data", + "formatLabel": "Exportformat", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Välj fält", + "selectAll": "Välj alla", + "fieldsSelected": "{count, plural, one {# fält valt} other {# fält valda}}", + "cancel": "Avbryt", + "export": "Exportera", + "sheetName": "Data" + }, + "jsonTree": { + "typeLabel": "Typ: {type}", + "itemSummary": "{count, plural, one {# objekt} other {# objekt}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Ny förfrågan", + "importedRequest": "Importerad förfrågan", + "untitledRequest": "Namnlös förfrågan" + }, + "toasts": { + "curlImported": "cURL importerad", + "curlParseFailed": "Misslyckades att tolka cURL-kommando", + "invalidJsonBody": "Ogiltig JSON-kropp", + "requestFailed": "Förfrågan misslyckades: {message}", + "curlPasted": "cURL inklistrad och tolkad", + "responseCopied": "Svar kopierat till urklipp", + "codeCopied": "Kod kopierad till urklipp", + "copyFailed": "Kopiering till urklipp misslyckades" + }, + "layout": { + "collections": "Samlingar", + "closeSidebar": "Stäng sidofält", + "openSidebar": "Öppna sidofält", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Metod", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Skickar...", + "send": "Skicka", + "invalidJsonBodyHelp": "Kan inte skicka: JSON-kroppen är ogiltig" + }, + "requestTabs": { + "params": "Parametrar", + "headers": "Rubriker", + "body": "Kropp", + "auth": "Auth", + "bodyType": "Kroppstyp", + "selectType": "Välj typ", + "bodyNone": "Ingen", + "bodyJson": "JSON", + "bodyText": "Text", + "noBodyHint": "Inget kroppinnehåll för denna förfrågan", + "switchToJson": "Byt till JSON-kropp", + "authType": "Auth-typ", + "noAuth": "Ingen auth", + "bearerToken": "Bearer-token", + "basicAuth": "Basic auth", + "apiKey": "API-nyckel", + "labelToken": "Token", + "placeholderBearerToken": "Bearer-token", + "labelUsername": "Användarnamn", + "placeholderUsername": "Användarnamn", + "labelPassword": "Lösenord", + "placeholderPassword": "Lösenord", + "labelKey": "Nyckel", + "placeholderApiKeyName": "Nyckelnamn (t.ex. X-Api-Key)", + "labelValue": "Värde", + "placeholderValue": "Värde", + "addTo": "Lägg till i", + "header": "Rubrik", + "queryParams": "Frågeparametrar", + "noAuthHint": "Denna förfrågan använder inte autentisering." + }, + "responsePanel": { + "emptyTitle": "Redo att utforska?", + "emptyHint": "Ange en URL och klicka Skicka för att se svarsdata här.", + "responsePreviewAlt": "Förhandsgranskning av svar", + "previewUnavailable": "Förhandsgranskning ej tillgänglig för denna innehållstyp", + "status": "Status", + "time": "Tid", + "size": "Storlek", + "copyBody": "Kopiera kropp", + "downloadResponse": "Ladda ner svar", + "bodyTab": "Kropp", + "previewTab": "Förhandsgranskning", + "headersTab": "Rubriker", + "binaryRawView": "Binärdata kan inte visas i råvy.", + "errorStatusLabel": "Fel", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Ny flik" + }, + "collectionsSidebar": { + "title": "API-resurser", + "newCollection": "Ny samling", + "tabCollections": "Samlingar", + "tabHistory": "Historik", + "noCollectionsTitle": "Inga samlingar", + "noCollectionsHint": "Skapa en samling för att organisera dina API-förfrågningar.", + "createCollection": "Skapa samling", + "newFolder": "Ny mapp", + "rename": "Byt namn", + "delete": "Ta bort", + "emptyCollection": "Tom samling", + "recentRequests": "Senaste förfrågningar", + "clearAll": "Rensa alla", + "noHistoryTitle": "Ingen historik ännu", + "noHistoryHint": "Skicka en förfrågan för att se den här.", + "dialogNewFolderTitle": "Ny mapp", + "dialogNewFolderDescription": "Skapa en ny mapp för att organisera dina förfrågningar.", + "labelFolderName": "Mappnamn", + "placeholderFolderName": "Min mapp", + "createFolder": "Skapa mapp", + "dialogNewCollectionTitle": "Ny samling", + "dialogNewCollectionDescription": "Skapa en ny toppnivåsamling.", + "labelCollectionName": "Samlingsnamn", + "placeholderCollectionName": "Min samling", + "dialogRenameCollectionTitle": "Byt namn på samling", + "dialogRenameCollectionDescription": "Ange ett nytt namn för samlingen.", + "placeholderRenameCollection": "Samlingsnamn", + "renameAction": "Byt namn", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Ny mapp", + "delete": "Ta bort", + "emptyFolder": "Tom mapp" + }, + "environmentManager": { + "noEnvironment": "Ingen miljö", + "manageDialogTitle": "Hantera miljöer", + "placeholderNewEnv": "Ny miljö", + "variablesHeading": "{name}-variabler", + "addVariable": "Lägg till variabel", + "columnVariable": "Variabel", + "columnValue": "Värde", + "placeholderKey": "Nyckel", + "placeholderValue": "Värde", + "selectEnvToEdit": "Välj en miljö att redigera" + }, + "codeGenerator": { + "triggerTitle": "Generera kod", + "dialogTitle": "Generera kod", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Kopiera", + "errorGenerating": "Fel vid kodgenerering" + }, + "keyValueEditor": { + "columnKey": "Nyckel", + "columnValue": "Värde", + "placeholderKey": "Nyckel", + "placeholderValue": "Värde", + "addItem": "Lägg till objekt" + }, + "importCurl": { + "trigger": "Importera cURL", + "dialogTitle": "Importera cURL", + "dialogDescription": "Klistra in ditt cURL-kommando nedan för att importera det som en ny förfrågan.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Importera" + }, + "saveRequest": { + "trigger": "Spara", + "dialogTitle": "Spara förfrågan", + "dialogDescription": "Spara denna förfrågan till en samlingsmapp.", + "labelName": "Förfrågningsnamn", + "placeholderName": "Min förfrågan", + "labelFolder": "Mapp", + "placeholderFolder": "Välj en mapp", + "save": "Spara" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Redigeringsläge", + "viewOnlyMode": "Skrivskyddat läge", + "insertComponent": "Infoga komponent", + "addUnorderedList": "Lägg till punktlista", + "addOrderedList": "Lägg till numrerad lista", + "addTable": "Lägg till tabell" + }, + "addBlock": "Lägg till block", + "media": { + "popoverTitle": "Lägg till media", + "singleImage": "Enskild bild", + "singleImageDesc": "Ladda upp en bild", + "multipleImages": "Flera bilder", + "multipleImagesDesc": "Ladda upp bildrutnät", + "video": "Video", + "videoDesc": "Ladda upp videofil", + "uploadingAlt": "Laddar upp...", + "uploadFailedAlt": "Uppladdning misslyckades" + }, + "export": { + "fabAria": "Exportera kod", + "title": "Exportera kod", + "description": "Kopiera HTML- eller JSON-utdata av ditt redigeringsinnehåll", + "preview": "Förhandsgranskning", + "html": "HTML", + "json": "JSON", + "previewOptions": "Förhandsgranskningsalternativ", + "enhanceSpaces": "Förbättra mellanrum", + "livePreview": "Liveförhandsgranskning av renderad HTML", + "htmlTailwind": "HTML med Tailwind CSS-klasser", + "editorJson": "Redigerarens tillstånd som JSON", + "copyHtml": "Kopiera HTML", + "copyJson": "Kopiera JSON", + "copied": "Kopierat!" + }, + "template": { + "fabTitle": "Byt mall", + "title": "Välj en mall", + "description": "Välj en mall för att snabbt komma igång med förbyggda layouter och innehåll", + "catAll": "Alla mallar", + "catProductivity": "Produktivitet", + "catCreative": "Kreativt", + "catBusiness": "Företag", + "catPersonal": "Personligt", + "noTemplates": "Inga mallar hittades i denna kategori", + "confirmTitle": "Ersätt befintligt innehåll?", + "confirmDescription": "Du har befintligt innehåll i redigeraren. Att tillämpa denna mall kommer ersätta allt nuvarande innehåll.", + "cannotUndo": "Denna åtgärd kan inte ångras.", + "cancel": "Avbryt", + "replaceContent": "Ersätt innehåll" + }, + "command": { + "searchPlaceholder": "Sök kommandon...", + "noResults": "Inga kommandon hittades.", + "turnInto": "Omvandla till", + "h1": { + "label": "Rubrik 1", + "description": "Stor sektionsrubrik", + "keywords": "h1,rubrik,titel,stor" + }, + "h2": { + "label": "Rubrik 2", + "description": "Medelstor sektionsrubrik", + "keywords": "h2,rubrik,underrubrik" + }, + "h3": { + "label": "Rubrik 3", + "description": "Liten sektionsrubrik", + "keywords": "h3,rubrik,underrubrik" + }, + "h4": { + "label": "Rubrik 4", + "description": "Minsta sektionsrubrik", + "keywords": "h4,rubrik" + }, + "h5": { + "label": "Rubrik 5", + "description": "Mindre rubrik", + "keywords": "h5,rubrik" + }, + "h6": { + "label": "Rubrik 6", + "description": "Minsta rubrik", + "keywords": "h6,rubrik" + }, + "p": { + "label": "Stycke", + "description": "Vanligt textstycke", + "keywords": "p,stycke,text,normal" + }, + "code": { + "label": "Kodblock", + "description": "Kodsnutt", + "keywords": "kod,kodblock,snutt,pre" + }, + "blockquote": { + "label": "Citat", + "description": "Blockcitat", + "keywords": "citat,blockcitat,citering" + }, + "li": { + "label": "Punktlista", + "description": "Enkel listpunkt med punkt", + "keywords": "lista,punkt,oordnad,ul,li" + }, + "ol": { + "label": "Numrerad lista", + "description": "Numrerad listpunkt", + "keywords": "lista,numrerad,ordnad,ol,li" + }, + "img": { + "label": "Bild", + "description": "Ladda upp eller bädda in en bild", + "keywords": "bild,img,foto,uppladdning" + }, + "video": { + "label": "Video", + "description": "Ladda upp eller bädda in en video", + "keywords": "video,vid,film,mp4,uppladdning" + }, + "table": { + "label": "Tabell", + "description": "Skapa en tabell", + "keywords": "tabell,rutnät,rader,kolumner,celler" + } + }, + "toasts": { + "tableImported": "Tabell importerad", + "tableImportedDesc": "Markdown-tabellen har importerats", + "mediaPasted": "Media inklistrad", + "pasteMixed": "{imageCount} bild(er) och {videoCount} video(s) inklistrade.", + "pasteImages": "{count} bild(er) inklistrade.", + "pasteVideos": "{count} video(s) inklistrade.", + "pasteFailed": "Inklistring misslyckades", + "unexpectedError": "Ett oväntat fel uppstod", + "uploadFailed": "Uppladdning misslyckades" + }, + "blockBackground": { + "menuItem": "Ändra bakgrundsfärg", + "dialogTitle": "Blockbakgrundsfärg", + "dialogDescription": "Välj en bakgrundsfärg för detta block", + "tabPreset": "Förvalda färger", + "tabCustom": "Anpassad färg", + "sectionHeading": "Bakgrundsfärger", + "apply": "Tillämpa", + "colors": { + "none": "Ingen", + "red": "Röd", + "orange": "Orange", + "yellow": "Gul", + "green": "Grön", + "blue": "Blå", + "indigo": "Indigo", + "purple": "Lila", + "pink": "Rosa", + "teal": "Blågrön", + "cyan": "Cyan", + "gray": "Grå" + } + }, + "tableDialog": { + "title": "Skapa tabell", + "description": "Skapa en ny tabell eller importera från markdown", + "markdownCheckbox": "Jag har en markdown-tabell", + "pasteMarkdownLabel": "Klistra in markdown-tabell", + "markdownPlaceholder": "| Rubrik 1 | Rubrik 2 | Rubrik 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "Klistra in din markdown-tabell ovan. Måste innehålla rubrik- och separatorrader.", + "rows": "Rader", + "columns": "Kolumner", + "maxDimensions": "Maximum: 20 rader × 10 kolumner", + "cancel": "Avbryt", + "importTable": "Importera tabell", + "createTable": "Skapa tabell" + }, + "tableParse": { + "errors": { + "minRows": "Tabellen måste ha minst en rubrikrad och separatorrad", + "pipeFormat": "Tabellrader måste börja och sluta med |", + "headerMinCol": "Rubrikraden måste ha minst en kolumn", + "separatorRow": "Andra raden måste vara en separator (t.ex. |---|---|)", + "rowColumnMismatch": "Rad {row} har {got} kolumner, förväntade {expected}", + "parseFailed": "Misslyckades att tolka markdown-tabell" + } + }, + "link": { + "editLink": "Redigera länk", + "addLink": "Lägg till länk", + "selectedText": "Markerad text: \"{text}\"", + "linkUrl": "Länk-URL", + "urlPlaceholder": "https://example.com", + "update": "Uppdatera", + "addLinkButton": "Lägg till länk", + "toastApplied": "Länk tillämpad", + "toastLinkedTo": "Länkad till: {url}", + "toastRemoved": "Länk borttagen", + "toastRemovedDesc": "Länken har tagits bort från markeringen" + }, + "customClass": { + "devMode": "Utvecklarläge", + "toggleDevModeAria": "Växla utvecklarläge", + "searchPlaceholderDev": "Sök klasser... (t.ex. 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Sök stilar... (t.ex. 'röd', 'fet', 'skugga')", + "appliesTitle": "Tillämpar: {className}", + "noMatches": "Inga klasser hittades som matchar \"{query}\"", + "toastClassReplaced": "Klass ersatt", + "toastClassReplacedDesc": "Ersatte \"{replaced}\" med \"{className}\"", + "toastCustomClassApplied": "Anpassad klass tillämpad", + "toastAppliedClass": "Tillämpad klass: {className}" + }, + "insertComponents": { + "title": "Infoga komponent", + "description": "Välj en komponent att infoga i ditt dokument", + "noComponents": "Inga komponenter tillgängliga", + "categories": { + "media": "Media", + "layout": "Layout", + "interactive": "Interaktivt", + "decoration": "Dekoration" + }, + "freeImage": { + "name": "Fritt rörlig bild", + "description": "Lägg till en bild som kan placeras var som helst och storleksändras fritt" + } + } + }, + "JsonFormatter": { + "title": "JSON-formaterare", + "description": "Formatera, validera och redigera JSON i text- och trädvy.", + "documentNameText": "Textdokument {n}", + "documentNameTree": "Träddokument {n}", + "newDocument": "Ny", + "copy": "Kopiera", + "save": "Spara", + "load": "Ladda", + "saving": "Sparar...", + "leftDocNameLabel": "Namn på vänstra dokumentet", + "rightDocNameLabel": "Namn på högra dokumentet", + "toastCopiedText": "Textpanelen kopierad", + "toastCopiedTree": "Trädpanelen kopierad", + "toastCopyFailed": "Kunde inte kopiera JSON", + "toastLoginRequired": "Logga in för att spara dokument", + "toastSaved": "Dokument sparat", + "toastSaveFailed": "Kunde inte spara dokument", + "format": "Formatera", + "compact": "Komprimera", + "sortKeys": "Sortera nycklar", + "repair": "Reparera", + "query": "Fråga", + "toastInvalidJson": "Ogiltig JSON", + "toastAlreadyValid": "JSON är redan giltig", + "toastRepaired": "JSON reparerad", + "toastRepairFailed": "Kunde inte reparera JSON" + }, + "JwtDecoder": { + "title": "JWT-avkodare", + "subtitle": "Inspektera header och payload. Utgångstid använder claim exp. Inget skickas till en server; signaturen verifieras inte.", + "tokenLabel": "Token", + "clear": "Rensa", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopiera", + "expiryLabel": "Utgångstid (exp)", + "signaturePresent": "Signatur finns (ej verifierad)", + "noExp": "Ingen exp-claim i payload.", + "status": { + "expired": "Utgången", + "valid": "Giltig" + }, + "fields": { + "unix": "Unix", + "local": "Lokal", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Utfärdad (iat)", + "notBefore": "Inte före (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Klistra in en JWT för att avkoda.", + "segments": "En JWT behöver minst header och payload (två segment).", + "base64": "Kunde inte base64url-avkoda header eller payload." + }, + "relative": { + "future": "om {n} {unit}", + "past": "för {n} {unit} sedan" + }, + "units": { + "second": "{count, plural, one {sekund} other {sekunder}}", + "minute": "{count, plural, one {minut} other {minuter}}", + "hour": "{count, plural, one {timme} other {timmar}}", + "day": "{count, plural, one {dag} other {dagar}}" + }, + "tabs": { + "decode": "Avkoda", + "sign": "Generator / signerare" + }, + "signer": { + "algLabel": "Algoritm (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Hemlighet (HS*)", + "secretPlaceholder": "Klistra in din HMAC-hemlighet…", + "privateKeyLabel": "Privat nyckel PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "\"none\"-algoritmen skapar en osignerad token. Det är osäkert och bör endast användas för test.", + "sign": "Signera", + "verify": "Verifiera signatur", + "outputLabel": "Token (kompakt JWS)", + "outputPlaceholder": "Klicka \"Signera\"…", + "copyToken": "Kopiera token", + "copied": "Kopierat", + "verifyKeyLabel": "Verifieringsnyckel (valfri)", + "verifyKeyPlaceholder": "För RS*: klistra in den publika nyckeln (SPKI) PEM. För HS*: lämna tomt för att återanvända hemligheten.", + "verifyKeyHint": "Om tomt använder verifiering signeringsnyckeln (bra för HS*).", + "verifyStatus": { + "valid": "Signaturen är giltig", + "invalid": "Signaturen är ogiltig" + }, + "securityNote": "Allt körs lokalt i din webbläsare; nycklar och tokens laddas inte upp." + }, + "signErrors": { + "invalidJson": "Header och payload måste vara giltig JSON.", + "unsupportedAlg": "Algoritmen stöds inte.", + "missingKey": "En signeringsnyckel krävs för denna algoritm.", + "cryptoFailed": "Signering misslyckades. Kontrollera nyckelformatet och försök igen." + }, + "verifyErrors": { + "empty": "Ange en JWT för att verifiera.", + "segments": "En JWT måste ha exakt 3 segment för att verifiera en signatur.", + "unsupportedAlg": "Algoritmen stöds inte eller saknar alg i headern.", + "missingKey": "En verifieringsnyckel krävs för denna algoritm.", + "base64": "Kunde inte avkoda JWT-header eller signatur.", + "cryptoFailed": "Verifiering misslyckades. Kontrollera nyckelformatet och försök igen." + } + }, + "CertificatePemDecoder": { + "title": "Certifikat- / PEM-avkodare", + "subtitle": "Klistra in ett X.509-certifikat (-----BEGIN CERTIFICATE-----) eller en CSR (-----BEGIN CERTIFICATE REQUEST-----). Ämne, utfärdare, datum och SAN analyseras lokalt; inget laddas upp.", + "pemLabel": "PEM-indata", + "clear": "Rensa", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Tolkar…", + "copyHex": "Kopiera fingeravtryck (hex utan kolon)", + "errors": { + "empty": "Klistra in ett PEM-certifikat eller en CSR för att avkoda.", + "noBlock": "Ingen PEM-block för certifikat eller CSR hittades.", + "parseFailed": "Kunde inte tolka dessa PEM-data." + }, + "block": { + "title": "PEM-block {index}", + "certificate": "X.509-certifikat", + "csr": "Begäran om certifikatsignering" + }, + "fields": { + "subject": "Ämne", + "issuer": "Utfärdare", + "notBefore": "Ogiltigt före", + "notAfter": "Ogiltigt efter", + "serial": "Serienummer (hex)", + "sha256": "SHA-256-fingeravtryck", + "sans": "Alternativa ämnesnamn", + "sansNone": "Ingen SAN-tillägg.", + "signatureAlgorithm": "Signaturalgoritm", + "csrIssuerNote": "CSR:er innehåller inte utfärdare (finns på det utfärdade certifikatet).", + "csrValidityNote": "CSR:er innehåller inte giltighetsdatum (finns på det utfärdade certifikatet)." + }, + "sanType": { + "dns": "DNS", + "dn": "Katalognamn", + "email": "E-post", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Registrerat ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL-formaterare", + "subtitle": "Formatera SQL för MySQL, PostgreSQL eller SQLite. Körs i din webbläsare; inget laddas upp.", + "dialectLabel": "Dialekt", + "keywordsLabel": "Nyckelord", + "indentLabel": "Indrag", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "VERSALER", + "lower": "gemener", + "preserve": "Behåll" + }, + "spaces": "{n} mellanslag", + "format": "Formatera", + "clear": "Rensa", + "counter": "{current} / {max} tecken", + "inputPanel": "Inmatning", + "formattedPanel": "Formaterat", + "copy": "Kopiera", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Klicka \"Formatera\"…", + "errors": { + "tooLong": "Inmatning är begränsad till {max} tecken.", + "couldNotFormat": "Kunde inte formatera denna SQL." + } + }, + "YamlFormatter": { + "title": "YAML-formaterare", + "subtitle": "Formatera, validera och konvertera YAML i webbläsaren. Ingenting laddas upp.", + "outputLabel": "Utdata", + "outputModes": { + "yaml": "YAML (formaterat)", + "json": "JSON" + }, + "indentLabel": "Indragning", + "spaces": "{n} mellanslag", + "format": "Formatera", + "clear": "Rensa", + "copy": "Kopiera", + "counter": "{current} / {max} tecken", + "inputPanel": "Indata", + "outputPanel": "Utdata", + "errors": { + "tooLong": "Indata är begränsad till {max} tecken.", + "couldNotFormat": "Kunde inte tolka denna YAML." + } + }, + "GraphqlFormatter": { + "title": "GraphQL-formaterare", + "subtitle": "Formatera GraphQL-frågor, mutationer och prenumerationer i webbläsaren. Inget laddas upp.", + "tabs": { + "format": "Formatera", + "build": "Frågebyggare" + }, + "outputModeLabel": "Utdata", + "outputModes": { + "pretty": "Läsbart format", + "minify": "Minifiera" + }, + "format": "Formatera", + "clear": "Rensa", + "counter": "{current} / {max} tecken", + "inputPanel": "Indata", + "outputPanel": "Utdata", + "copy": "Kopiera", + "builderHint": "Bygg ett skelett och lägg in det i redigeraren. Variabeldefinitioner i GraphQL-syntax (t.ex. $id: ID!) — parenteser är valfria.", + "builder": { + "operation": "Åtgärd", + "operationName": "Åtgärdsnamn", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Variabeldefinitioner", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Urval", + "apply": "Använd på indata", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Indata är begränsat till {max} tecken.", + "couldNotFormat": "Kunde inte parsa eller skriva ut denna GraphQL.", + "emptySelection": "Lägg till ett urval innan du använder." + } + }, + "RegexTester": { + "title": "Regex-tester", + "subtitle": "JavaScript-liknande regex live mot din text. Träffar markeras; inget lämnar din webbläsare.", + "patternLabel": "Mönster", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i ignorera versaler/gemener", + "m": "m flerradig", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(tom)", + "noFlags": "—", + "matches": "{count, plural, one {träff} other {träffar}}", + "testTextLabel": "Testtext", + "counter": "{current} / {max}", + "clear": "Rensa", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Texten är begränsad till {max} tecken.", + "invalid": "Ogiltigt reguljärt uttryck.", + "runaway": "Stoppade efter {max} träffar (möjligt skenande mönster)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Tidsstämpelkonverterare", + "subtitle": "Unix-sekunder (≤10 siffror) eller millisekunder (längre heltal), ISO-8601 eller strängar som din webbläsare kan tolka. Allt körs lokalt.", + "inputLabel": "Inmatning", + "placeholder": "1744200000 eller 2025-04-09T12:00:00Z", + "now": "Nu", + "copy": "Kopiera", + "hint": "Klistra in ett värde eller klicka Nu.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Relativ", + "human": "Läsbart" + }, + "fields": { + "seconds": "Sekunder", + "milliseconds": "Millisekunder", + "utc": "UTC", + "local": "Lokal", + "distance": "Avstånd" + }, + "errors": { + "empty": "Ange en Unix-tidsstämpel, ISO-sträng eller datumtext.", + "invalidNumber": "Ogiltig numerisk tidsstämpel.", + "outOfRange": "Utanför intervallet för JavaScript Date.", + "couldNotParse": "Kunde inte tolka som Unix, ISO-8601 eller webbläsardatum." + } + }, + "HttpStatusCodes": { + "title": "HTTP-statuskoder", + "subtitle": "Sök och bläddra bland HTTP-statuskoder för svar (1xx5xx) med korta beskrivningar och RFC-referenser. Körs lokalt.", + "header": { + "title": "HTTP-statuskoder", + "description": "Sökbar referens för HTTP-svarskoder med beskrivningar och RFC-referenser." + }, + "searchPlaceholder": "Sök: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Filtrera klass…", + "classFilterAll": "Alla klasser", + "resultsCount": "{count} av {total}", + "table": { + "code": "Code", + "phrase": "Fras", + "description": "Beskrivning", + "copy": "Kopiera" + }, + "noResults": "Inga träffar för “{query}”.", + "specPrefix": "Spec:", + "copied": "Kod kopierad" + }, + "CronBuilder": { + "title": "Cron-uttrycksbyggare", + "subtitle": "Standard cron med 5 fält: minute hour day month weekday. Parsern använder seconds=0 internt. Valfritt 6-fält (sec min hour dom mon dow) fungerar i uttryckseditorn.", + "tabs": { + "builder": "Visuell byggare", + "expression": "Uttryck" + }, + "presetsLabel": "Förinställningar", + "fieldsLabel": "Fält", + "rawExpressionLabel": "Rått uttryck", + "expressionHint": "Lämna fältet för att tolka och synka byggaren. {count} {count, plural, one {token} other {tokens}}.", + "currentExpressionLabel": "Aktuellt uttryck", + "copy": "Kopiera", + "quickPick": "Snabbval", + "emptyExpression": "—", + "plainLanguageLabel": "Klartext", + "nextRunsLabel": "Nästa körningar (lokal tid)", + "fields": { + "minute": "Minut", + "hour": "Timme", + "dayOfMonth": "Dag i månaden", + "month": "Månad", + "dayOfWeek": "Veckodag" + }, + "presets": { + "everyMinute": "Varje minut", + "every5Minutes": "Var 5:e minut", + "every15Minutes": "Var 15:e minut", + "everyHour": "Varje timme (vid :00)", + "every6Hours": "Var 6:e timme", + "dailyMidnight": "Dagligen vid midnatt", + "daily0900": "Dagligen kl 09:00", + "weekdays0900": "Vardagar kl 09:00", + "weeklySunday": "Veckovis (söndag 00:00)", + "monthlyFirst": "Månadsvis (1:a 00:00)", + "yearlyJan1": "Årligen (1 jan 00:00)" + }, + "picks": { + "minute": { + "every": "varje minut" + }, + "hour": { + "every": "varje timme" + }, + "dom": { + "any": "valfri dag", + "last": "sista dagen" + }, + "month": { + "every": "varje månad" + }, + "dow": { + "any": "valfri veckodag", + "weekdays": "vardagar", + "weekend": "helg" + } + }, + "months": { + "jan": "Jan", + "feb": "Feb", + "mar": "Mar", + "apr": "Apr", + "may": "Maj", + "jun": "Jun", + "jul": "Jul", + "aug": "Aug", + "sep": "Sep", + "oct": "Okt", + "nov": "Nov", + "dec": "Dec" + }, + "weekdays": { + "sun": "Sön", + "mon": "Mån", + "tue": "Tis", + "wed": "Ons", + "thu": "Tor", + "fri": "Fre", + "sat": "Lör" + }, + "errors": { + "fieldCount": "Förväntade 5 fält (minute hour day month weekday) eller 6 inklusive sekunder.", + "invalid": "Ogiltigt cron-uttryck." + } + }, + "Base64": { + "title": "Base64-kodare / avkodare", + "subtitle": "Koda text till Base64 eller avkoda Base64-strängar", + "upload": "Ladda upp", + "clear": "Rensa", + "modes": { + "encode": "Koda", + "decode": "Avkoda" + }, + "swapTitle": "Byt indata och utdata", + "panels": { + "plainText": "Vanlig text", + "base64Input": "Base64-indata", + "base64Output": "Base64-utdata", + "decodedText": "Avkodad text" + }, + "placeholders": { + "encode": "Skriv text att koda till Base64…", + "decode": "Klistra in Base64 för att avkoda…" + }, + "dropHere": "Släpp fil här", + "charCount": "{count} tecken", + "download": { + "title": "Ladda ner output", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Kopiera till urklipp", + "outputPlaceholder": "Output visas här…", + "errors": { + "invalidBase64": "Ogiltig Base64-sträng. Kontrollera indata.", + "encodeFailed": "Kunde inte koda indata." + } + }, + "UrlEncode": { + "title": "URL-kodare / avkodare", + "subtitle": "Procentkoda/avkoda med encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Ladda upp", + "clear": "Rensa", + "modes": { + "encode": "Koda", + "decode": "Avkoda" + }, + "swapTitle": "Byt indata och utdata", + "panels": { + "plainText": "Vanlig text", + "encodedInput": "Kodat indata", + "encodedOutput": "Kodat utdata", + "decodedText": "Avkodad text" + }, + "placeholders": { + "encode": "Skriv text för procentkodning…", + "decode": "Klistra in procentkodad text för avkodning…" + }, + "dropHere": "Släpp fil här", + "charCount": "{count} tecken", + "download": { + "title": "Ladda ner output", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Kopiera till urklipp", + "outputPlaceholder": "Output visas här…", + "errors": { + "invalidEncoding": "Ogiltig procentkodning. Kontrollera ofullständiga %‑par eller escapes.", + "encodeFailed": "Kunde inte koda (ogiltig Unicode i indata)." + } + }, + "UrlParser": { + "title": "URL-tolk", + "subtitle": "Dela upp en URL i protokoll, värd, sökväg, frågeparametrar och hash (fragment).", + "upload": "Ladda upp", + "clear": "Rensa", + "panels": { + "input": "URL-inmatning", + "output": "Resultat" + }, + "placeholders": { + "input": "Klistra in en URL (t.ex. https://example.com/path?foo=bar#section) eller värd/sökväg (t.ex. example.com/path)…" + }, + "dropHere": "Släpp fil här", + "charCount": "{count} tecken", + "outputPlaceholder": "De tolkade komponenterna visas här…", + "fields": { + "protocol": "Protokoll", + "host": "Värd", + "path": "Sökväg", + "hash": "Hash", + "queryParams": "Frågeparametrar" + }, + "paramCount": "{count} parametrar", + "noParams": "Inga frågeparametrar.", + "paramKey": "Nyckel", + "paramValue": "Värde", + "copyTitle": "Kopiera till urklipp", + "copyParamTitle": "Kopiera som key=value", + "download": { + "title": "Ladda ner JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Ogiltig URL. Inkludera ett värdnamn (example.com) och kontrollera tecken." + } + }, + "UuidGenerator": { + "title": "UUID-/ULID-generator", + "subtitle": "Generera RFC 9562-UUID:er eller ULID:er i webbläsaren. Bulkundexport upp till {max} per körning.", + "format": "Format", + "kindHints": { + "ulid": "Sorterbart, URL-säkert 26-teckens ID", + "uuid4": "Slumpmässig (RFC 9562)", + "uuid7": "Tidsordnad, Unix ms + slump", + "uuid1": "MAC-/tidsbaserad (RFC 9562)", + "uuid6": "Omordnad gregoriansk tid", + "uuid3": "MD5-hash av namn + namespace", + "uuid5": "SHA‑1-hash av namn + namespace", + "nil": "Noll-UUID (bara nollor, RFC 9562)" + }, + "countLabel": "Antal (1–{max})", + "nameLabel": "Namn (indatasträng)", + "namePlaceholder": "t.ex. my.app/resource", + "bulkHint": "I bulk läggs #0, #1, … till så att varje ID blir unikt (v3/v5 är deterministiska).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Anpassad UUID" + }, + "customNamespaceLabel": "Namespace-UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Generera", + "output": "Utdata", + "lines": "{count} rader", + "download": { + "title": "Ladda ner .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Kopiera allt", + "uppercaseToggle": "Utdata med versaler", + "hyphensToggle": "Visa bindestreck", + "copyJsonTitle": "Kopiera som JSON-array", + "outputPlaceholder": "Klicka på \"Generera\"…", + "errors": { + "invalidCustomNamespace": "Anpassad namespace måste vara en giltig UUID.", + "missingName": "Namn krävs för UUID v3 och v5.", + "unknown": "Kunde inte generera ID:n." + } + }, + "SecretApiKeyGenerator": { + "title": "Generator för hemlighet / API-nyckel", + "subtitle": "Generera kryptografiskt slumpmässiga strängar med konfigurerbar längd och alfabet. Bulkexport upp till {max} per körning. Kompletterar UUID-/ULID-generatorn.", + "alphabetLabel": "Alfabet", + "alphabetHint": "Dubbletter tas bort. Varje symbol väljs jämnt med crypto.getRandomValues och rejection sampling (ingen modulobias).", + "alphabetCount": "{count} unika symboler", + "presetsLabel": "Förinställningar", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfanumerisk", + "hex": "Hex (gemener)", + "asciiPrintable": "ASCII utskrivbar" + }, + "lengthLabel": "Längd per hemlighet (1–{maxLen})", + "countLabel": "Antal (1–{maxBulk})", + "generate": "Generera", + "output": "Utdata", + "lines": "{count} rader", + "download": { + "title": "Ladda ner .txt", + "filename": "hemligheter.txt" + }, + "copyTitle": "Kopiera allt", + "outputPlaceholder": "Klicka på \"Generera\"…", + "entropy": "{bits} bitars entropi", + "strength": { + "weak": "Svag", + "fair": "Godtagbar", + "strong": "Stark" + }, + "excludeAmbiguous": "Uteslut tvetydiga tecken", + "excludeAmbiguousHint": "Tar bort {chars} — tecken som lätt läses fel eller är fientliga mot skalet.", + "prefixLabel": "Nyckelprefix (valfritt)", + "errors": { + "emptyAlphabet": "Lägg till minst ett tecken i alfabetet.", + "alphabetTooLong": "Efter borttagning av dubbletter kan alfabetet ha högst {maxAlpha} tecken.", + "lengthOutOfRange": "Längden måste vara mellan 1 och {maxLen}.", + "countOutOfRange": "Antalet måste vara mellan 1 och {maxBulk}.", + "unknown": "Kunde inte generera hemligheter." + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum Generator", + "subtitle": "Generera latinsk platshållartext för dina layouter och mockups.", + "paragraphs": "Stycken", + "sentences": "Meningar", + "words": "Ord", + "count": "Antal", + "generate": "Generera", + "copy": "Kopiera", + "startWith": "Börja med 'Lorem ipsum...'", + "outputPlaceholder": "Utdata visas här...", + "outputType": "Utdatatyp", + "bulletList": "Punktlista", + "htmlMarkup": "HTML-uppmärkning", + "htmlMarkupHint": "Omslut utdata i stycken, eller använd en lista för punktlistläge", + "output": "Utdata", + "download": "Ladda ner" + }, + "ColorPicker": { + "title": "Färgväljare och konverterare", + "subtitle": "Konvertera HEX, RGB och HSL; bygg harmoniska paletter. All bearbetning sker i din webbläsare.", + "nativePicker": "Inbyggd väljare", + "chooseColor": "Välj färg", + "eyeDropper": "Pipett", + "invalidHex": "Ogiltig HEX-färg.", + "cssValues": "CSS-värden", + "palettes": "Paletter", + "copied": "Kopierat", + "clickToCopy": "Klicka för att kopiera", + "copyAllHex": "Kopiera alla HEX", + "copy": "Kopiera", + "copyColor": "Kopiera {color}", + "paletteShades": "Nyanser", + "paletteComplementary": "Komplementär", + "paletteTriadic": "Triadisk", + "paletteAnalogous": "Analog", + "paletteSplit": "Delad kompl.", + "paletteSquare": "Kvadratisk" + }, + "ContrastChecker": { + "title": "Kontrastkontroll", + "subtitle": "Beräknar WCAG 2.1-kontrasten mellan förgrund och bakgrund. Visar AA och AAA för normal och stor text. Allt körs i din webbläsare.", + "foreground": "Text / förgrund", + "background": "Bakgrund", + "hexPlaceholder": "#RRGGBB", + "swap": "Byt färger", + "chooseForeground": "Välj förgrundsfärg", + "chooseBackground": "Välj bakgrundsfärg", + "eyeDropper": "Pipett", + "ratio": "Kontrast", + "copyRatio": "Kopiera värde", + "copied": "Kopierat", + "previewHeading": "Förhandsgranskning", + "previewBody": "Yxska kåmpare med vårbyxor och tröjor följer quiz på TV. Flygande bäckasiner söka hwila på mjuka tuvor.", + "previewCaption": "Exempeltext i standardstorlek.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — normal text (4,5:1)", + "levelAALarge": "AA — stor text (3:1)", + "levelAAANormal": "AAA — normal text (7:1)", + "levelAAALarge": "AAA — stor text (4,5:1)", + "pass": "Godkänd", + "fail": "Underkänd", + "invalidHex": "Ogiltig HEX-färg.", + "largeTextHint": "Stor text: 18 pt+ normal eller 14 pt+ fet (WCAG-definitioner)." + }, + "DiffChecker": { + "title": "Diff-kontroll", + "subtitle": "Rad-för-rad-jämförelse av två texter sida vid sida. Allt stannar i din webbläsare.", + "rowsCount": "{count} rader", + "truncatedWarning": "Visar de första {max} raderna.", + "limitExceeded": "Varje sida är begränsad till {max} tecken.", + "clearBoth": "Rensa båda", + "original": "Original", + "modified": "Ändrad", + "originalPlaceholder": "Klistra in originaltext…", + "modifiedPlaceholder": "Klistra in ändrad text…", + "comparisonLabel": "Sida vid sida-jämförelse", + "emptyState": "Ange text i båda panelerna för att jämföra." + }, + "JsonSchemaGenerator": { + "title": "JSON-schemagenerator", + "subtitle": "Klistra in giltig JSON för att generera JSON-schema eller typade modeller. Allt körs i din webbläsare.", + "languageLabel": "Utdataspråk", + "inputLabel": "JSON-indata", + "inputPlaceholder": "Klistra in JSON här…", + "outputLabel": "Genererad utdata", + "outputPlaceholder": "Schemat eller de genererade typerna visas här…", + "parseError": "Ogiltig JSON", + "clear": "Rensa", + "copy": "Kopiera", + "copied": "Kopierat till urklipp", + "copyFailed": "Kunde inte kopiera", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Läser in redigeraren…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Bild till Base64", + "subtitle": "Släpp en bild, skaffa en data-URI eller rå Base64-sträng. Allt finns kvar i din webbläsare.", + "upload": "Ladda upp bild", + "clear": "Rensa", + "modes": { + "dataUri": "Data URI", + "rawString": "Raw Base64" + }, + "panels": { + "imageInput": "Bildinmatning", + "output": "Base64-utgång" + }, + "dropHere": "Släpp bilden här", + "charCount": "{count} tecken", + "placeholders": { + "uploadImage": "Dra och släpp en bild eller klicka på ladda upp för att starta." + }, + "fileTooLarge": "Filen är för stor. Bearbetning kan frysa din webbläsare.", + "copyTitle": "Kopiera till urklipp", + "download": { + "title": "Ladda ner text", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Bildkomprimering", + "subtitle": "Minska JPEG-, PNG- eller WebP-utdata lokalt med kvalitetskontroll — inget laddas upp.", + "upload": "Ladda upp bild", + "clear": "Rensa", + "dropHere": "Släpp bilden här", + "placeholders": { + "uploadImage": "Dra och släpp en bild, eller klicka för att välja fil." + }, + "fileTooLarge": "Filen är för stor (max {maxMb} MB).", + "unsupportedFormat": "Filtypen stöds inte. Använd JPEG, PNG, WebP, GIF, AVIF eller BMP.", + "compressFailed": "Det gick inte att komprimera bilden. Prova en annan fil eller ett annat format.", + "panels": { + "original": "Original", + "compressed": "Komprimerad förhandsgranskning" + }, + "outputFormat": "Utdataformat", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kvalitet", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG är förlustfritt i webbläsaren; kvalitetsreglaget gäller bara JPEG och WebP.", + "sizes": { + "original": "Original: {size}", + "compressed": "Komprimerad: {size}", + "saved": "{percent}% mindre" + }, + "largerWarning": "Utdata är större än indata — prova annat format eller kvalitet.", + "download": "Ladda ner", + "downloadFilename": "komprimerad-bild", + "processing": "Komprimerar…", + "emptyCompressed": "Komprimerad förhandsgranskning visas här." + }, + "CssGradientBuilder": { + "title": "CSS Gradient Builder", + "subtitle": "Visuell CSS-gradientbyggare med vinkelkontroll, färgstopp och CSS-kopia med ett klick.", + "preview": "Gradient Preview", + "settings": "Inställningar", + "type": "Typ", + "linear": "Linjär", + "radial": "Radiell", + "angle": "Vinkel", + "stops": "Färgen stannar", + "color": "Färg", + "position": "Position (%)", + "addStop": "Lägg till stopp", + "deleteStop": "Ta bort Stop", + "output": "CSS-utgång", + "copy": "Kopiera CSS", + "copied": "Kopierade!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Generator", + "subtitle": "Generera sammanslagna .gitignore-filer direkt för en specifik teknisk stack eller kombination.", + "searchPlaceholder": "Sök stackar (t.ex. Node, Python, macOS)...", + "noResults": "Inga stackar hittades.", + "selectedStacks": "Valda staplar", + "output": "Förhandsgranska utdata", + "copy": "Kopiera till Urklipp", + "copied": "Kopierade!", + "download": "Ladda ner .gitignore", + "downloading": "Laddar ned...", + "errorFetching": "Det gick inte att hämta topplistan.", + "errorGenerating": "Det gick inte att generera .gitignore från valda stackar.", + "generating": "Alstrande...", + "emptyState": "Välj en eller flera teknikstackar för att generera din .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Ladda upp CSV eller Excel, redigera JSON och ladda ner CSV eller XLSX. Endast tabell data; Excel-datum blir ISO-tidsstämplar i JSON.", + "dropTitle": "Släpp en CSV- eller Excel-fil", + "dropHint": "Första arbetsbladet används för .xlsx / .xls. CSV måste ha en rubrikrad.", + "chooseFile": "Välj fil", + "jsonLabel": "JSON (array av objekt)", + "jsonPlaceholder": "Klistra in JSON-array av objekt eller importera CSV / Excel…", + "loadSample": "Ladda exempel", + "actions": { + "format": "Formatera JSON", + "clear": "Rensa", + "copy": "Kopiera", + "copied": "Kopierat", + "downloadCsv": "Ladda ner CSV", + "downloadXlsx": "Ladda ner Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Data", + "toastLoadedCsv": "CSV laddad i redigeraren", + "toastLoadedExcel": "Excel laddad i redigeraren", + "toastFormatted": "JSON formaterat", + "toastExportedCsv": "CSV nedladdad", + "toastExportedXlsx": "Excel nedladdad", + "toastCopied": "Kopierat till urklipp", + "footnote": "Allt körs i webbläsaren. JSON måste vara en icke-tom array av objekt. Kapslade värden serialiseras som JSON i CSV-celler.", + "errors": { + "unsupportedFile": "Använd en .csv-, .xlsx- eller .xls-fil.", + "parseFailed": "Kunde inte tolka filen.", + "invalidJson": "Ogiltig JSON", + "exportFailed": "Export misslyckades", + "copyFailed": "Kunde inte kopiera" + } + }, + "SnippetManager": { + "title": "Hanterare för kodutdrag", + "subtitle": "Spara utdrag i webbläsaren eller logga in för att synka med ditt konto. Växla mellan visning och redigering, kopiera med ett klick, välj språk eller autoupptäckt, och formatera JSON, SQL eller Monaco-stödd kod.", + "snippets": "Utdrag", + "newSnippet": "Nytt utdrag", + "newSnippetTitle": "Nytt utdrag", + "defaultSnippetTitle": "Välkommen", + "defaultSnippetCode": "// Välkommen till kodutdrag\n// Prova visning vs redigera, kopiera, formatera och automatiskt språk\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Sök utdrag…", + "searchAria": "Sök utdrag", + "emptySearch": "Inga utdrag matchar sökningen.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Utdragsnamn", + "language": "Språk", + "detectedHint": "Syntaxmarkering som {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Visa", + "modeEdit": "Redigera", + "copy": "Kopiera", + "copied": "Kopierat", + "format": "Formatera", + "cancel": "Avbryt", + "deleteTitle": "Ta bort utdrag?", + "deleteDescription": "”{title}” tas bort från den här webbläsaren.", + "deleteConfirm": "Ta bort", + "editorAria": "Kodredigerare för utdrag", + "editorLoading": "Laddar redigerare…", + "toastCopied": "Kopierat till urklipp", + "toastCopyFailed": "Kunde inte kopiera", + "toastNothingToCopy": "Inget att kopiera", + "toastFormatted": "Formaterat", + "toastFormatFailed": "Kunde inte formatera", + "toastFormatJsonInvalid": "Ogiltig JSON — åtgärda syntaxen först", + "toastFormatUnsupported": "Ingen formaterare för detta språk — prova JSON eller SQL eller redigeringsläge", + "toastDeleted": "Utdrag borttaget", + "toastLoadFailed": "Kunde inte läsa in utdrag från servern.", + "toastSyncFailed": "Kunde inte synkronisera med servern.", + "languages": { + "auto": "Automatisk avkänning", + "plaintext": "Oformaterad text", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL-klient", + "dialogTitle": "SQL-anslutningar", + "mobileSheetTitle": "SQL-utforskaren", + "newTabLabel": "Ny flik för frågor", + "vaultLockedTitle": "Valvet är låst", + "vaultLockedDesc": "Lås upp valvet för att komma åt sparade anslutningar.", + "heroTitle": "SQL-klient", + "heroDesc": "Anslut till PostgreSQL, MySQL eller MariaDB. Uppgifter krypteras i webbläsaren.", + "heroAddConnection": "Lägg till anslutning", + "selectTableTitle": "Välj en tabell eller öppna en frågeflik", + "selectTableDesc": "Klicka på en tabell i sidofältet eller använd ⌘↩.", + "toastLoadFail": "Det gick inte att läsa in anslutningar." + }, + "connection": { + "titleNew": "Ny SQL-anslutning", + "titleEdit": "Redigera anslutning", + "labelDbType": "Databastyp", + "labelName": "Anslutningsnamn", + "placeholderName": "Min produktionsdatabas", + "labelHostPort": "Värd och port", + "labelDatabase": "Databas", + "placeholderDatabase": "mydb", + "labelUsername": "Användarnamn", + "labelPassword": "Lösenord", + "labelSsl": "Aktivera SSL / TLS", + "sslDesc": "Kryptera anslutningen till servern", + "securityNote": "Uppgifter krypteras med AES-256 i webbläsaren före lagring", + "btnTest": "Testa anslutning", + "btnSave": "Spara och anslut", + "btnUpdate": "Uppdatera", + "btnCancelEdit": "Avbryt redigering", + "savedConnections": "Sparade anslutningar", + "emptySaved": "Sparade anslutningar visas här", + "confirmDelete": "Ta bort den här anslutningen?", + "toastFillFields": "Fyll i värd och databas innan test.", + "toastConnectOk": "Ansluten! {version}", + "toastConnectFail": "Anslutning misslyckades: {message}", + "toastUnlockVault": "Lås upp valvet först.", + "toastFieldsRequired": "Värd och databas krävs.", + "toastUpdated": "Anslutning uppdaterad.", + "toastSaved": "Anslutning sparad.", + "toastSaveFail": "Spara misslyckades: {message}", + "toastDeleted": "Anslutning borttagen.", + "toastDeleteFail": "Det gick inte att ta bort anslutningen." + }, + "sidebar": { + "title": "SQL-utforskaren", + "tooltipAddConnection": "Lägg till anslutning", + "tooltipRefreshSchema": "Uppdatera schema", + "noConnections": "Inga sparade anslutningar ännu", + "toastSchemaFail": "Det gick inte att läsa in schemat: {message}" + }, + "editor": { + "btnRun": "Kör", + "executing": "Kör fråga…", + "emptyTitle": "Kör en fråga för att se resultat", + "emptyHint": "Tryck ⌘↩ eller klicka Kör", + "toastNoConnection": "Ingen aktiv anslutning." + }, + "results": { + "filterPlaceholder": "Filtrera resultat…", + "rowCount": "{count} {count, plural, one {rad} other {rader}}", + "rowCountFiltered": "{filtered} / {total} rader", + "exportCsv": "CSV", + "noRows": "Inga rader returnerades" + } + }, + "QrCodeGenerator": { + "title": "QR-kodgenerator", + "subtitle": "Gör valfri text eller URL till en PNG QR-kod. Allt renderas lokalt i webbläsaren.", + "contentLabel": "Innehåll", + "contentPlaceholder": "https://example.com, vanlig text, Wi‑Fi-sträng, vCard…", + "styleLabel": "Stil", + "dotTypeLabel": "Punktform", + "dotTypeSquare": "Kvadrat", + "dotTypeDots": "Punkter", + "dotTypeRounded": "Avrundad", + "dotTypeExtraRounded": "Extra avrundad", + "dotTypeClassy": "Elegant", + "dotTypeClassyRounded": "Elegant avrundad", + "cornerSquareLabel": "Hörn kvadrater", + "cornerSquareDot": "Punkt", + "cornerSquareSquare": "Kvadrat", + "cornerSquareExtraRounded": "Extra avrundad", + "cornerDotLabel": "Hörn punkter", + "cornerDotDot": "Punkt", + "cornerDotSquare": "Kvadrat", + "eccLabel": "Felkorrigering", + "eccL": "Låg (~7%)", + "eccM": "Medel (~15%)", + "eccQ": "Kvartil (~25%)", + "eccH": "Hög (~30%)", + "marginLabel": "Marginal (moduler)", + "widthLabel": "Bildbredd", + "widthEffective": "Renderas vid {px} px", + "darkColor": "Mörka moduler", + "lightColor": "Ljusa moduler", + "resetColors": "Återställ färger", + "logoLabel": "Centralt logo", + "logoMarginLabel": "Marginal", + "uploadLogo": "Ladda upp bild", + "removeLogo": "Ta bort logo", + "downloadPng": "Ladda ner PNG", + "downloadSvg": "Ladda ner SVG", + "copyImage": "Kopiera bild", + "copied": "Kopierat", + "clear": "Rensa", + "previewAlt": "Förhandsgranskning av QR-kod", + "emptyHint": "Skriv eller klistra in innehåll för att förhandsgranska.", + "filename": "qr-kod.png", + "errors": { + "generationFailed": "Kunde inte generera QR-koden. Prova kortare text eller lägre felkorrigering.", + "copyUnsupported": "Webbläsaren stöder inte att kopiera bilder till urklipp.", + "copyFailed": "Kunde inte kopiera till urklipp." + } + }, + "IpSubnetCalculator": { + "title": "IP-/subnetkalkylator", + "subtitle": "IPv4- och IPv6-CIDR i webbläsaren—inga externa anrop.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Nätverk", + "placeholderIpv4": "10.0.0.0/24 eller 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 eller 2001:db8::1/64", + "hintIpv4": "Använd CIDR (t.ex. /24) eller IPv4-adress + punktnätmask.", + "hintIpv6": "Ange prefixlängd (t.ex. /64). Utan anges /128.", + "copyValue": "Kopiera värde", + "rows": { + "cidr": "CIDR", + "network": "Nätverksadress", + "netmask": "Subnetmask", + "wildcard": "Wildcard-mask", + "broadcast": "Broadcast", + "firstHost": "Första värd", + "lastHost": "Sista värd", + "totalAddresses": "Adresser totalt", + "usableHosts": "Användbara värdar", + "prefixLength": "Prefixlängd (bitar)", + "networkIpv6": "Nätverksadress", + "lastIpv6": "Sista adressen i intervallet", + "totalAddressesIpv6": "Adresser i subnet" + }, + "usableNote": { + "pointToPoint": "/31 punkt-till-punkt använder båda adresserna som värdar (RFC 3021).", + "singleHost": "/32 är en enda värdadress." + }, + "ipv6RangeHint": { + "normal": "Visar hela det matematiska intervallet; routerregler kan variera.", + "single": "/128 är en enda adress.", + "full": "/0 täcker hela IPv6." + }, + "errors": { + "invalidIpv4": "Ogiltigt IPv4-nätverk. Prova CIDR eller adress + mask.", + "invalidIpv6": "Ogiltigt IPv6-nätverk. Använd adress/prefix, t.ex. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Hash-generator", + "subtitle": "MD5-, SHA-1- och SHA-2-summor för text eller filer; bcrypt för lösenord (textinmatning). Allt körs lokalt i webbläsaren.", + "algorithmLabel": "Algoritm", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Text", + "modeFile": "Fil", + "inputLabelText": "Indata", + "placeholderText": "Skriv eller klistra in text…", + "chooseFile": "Välj fil", + "clearFile": "Rensa", + "fileHint": "Filer läses bara i minnet; max {maxMb} MB. Ingen uppladdning.", + "outputLabel": "Digest (hex)", + "copyHash": "Kopiera digest", + "copied": "Kopierat", + "byteCount": "{count, plural, one {# byte} other {# byte}}", + "charCount": "{count, plural, one {# tecken} other {# tecken}}", + "emptyHint": "Ange text eller välj en fil.", + "securityNote": "MD5 och SHA-1 rekommenderas inte för nya säkerhetsanvändningar; använd SHA-256 eller SHA-512.", + "errors": { + "fileTooLarge": "Filen är större än {maxMb} MB.", + "fileReadFailed": "Kunde inte läsa filen." + }, + "bcryptRoundsLabel": "Kostnad (varv)", + "bcryptRoundsHint": "Högre värden är långsammare och mer motståndskraftiga. Vanlig standard är 10.", + "fileTabDisabledTitle": "bcrypt hashar bara lösenord från text, inte godtyckliga filer.", + "placeholderBcrypt": "Skriv eller klistra in ett lösenord…", + "outputLabelBcrypt": "bcrypt-hash", + "copyBcrypt": "Kopiera hash", + "emptyHintBcrypt": "Ange ett lösenord för att beräkna bcrypt-hash.", + "securityNoteBcrypt": "Varje bcrypt-utdata innehåller ett slumpmässigt salt; samma lösenord ger en ny sträng varje gång. Det förväntas. Använd hög kostnad i produktion.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Krypteringslekplats", + "subtitle": "AES-GCM i webbläsaren: kryptera till ett JSON-paket med lösenfras eller rå nyckel och dekryptera lokalt. Pedagogiskt och ibland praktiskt — inget laddas upp.", + "tabEncrypt": "Kryptera", + "tabDecrypt": "Dekryptera", + "keyModeLabel": "Nyckelmaterial", + "modePassphrase": "Lösenfras (PBKDF2 + AES-GCM)", + "modeRawKey": "Rå AES-nyckel (hex eller Base64)", + "aesBitsLabel": "AES-nyckellängd", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Kodning av rå nyckel", + "encodingAuto": "Auto (hex om det ser ut som hex, annars Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Rå nyckel", + "rawKeyPlaceholder": "32-byte nyckel som 64 hex-tecken eller Base64 för AES-256 (längden beror på AES-storleken ovan).", + "rawKeyPlaceholderDecrypt": "Behövs bara om paketet har keyDerivation \"none\".", + "passphraseLabel": "Lösenfras", + "passphrasePlaceholder": "Lösenfras används bara i webbläsaren för att härleda nyckeln.", + "passphrasePlaceholderDecrypt": "Behövs bara om paketet använder PBKDF2-SHA256.", + "pbkdf2IterLabel": "PBKDF2-iterationer", + "pbkdf2Hint": "Mellan 10 000 och 2 000 000. Standard liknar vanliga valvinställningar.", + "plaintextLabel": "Klartext", + "plaintextPlaceholder": "Skriv eller klistra in meddelande att kryptera…", + "bundleInputLabel": "Krypterat paket (JSON)", + "bundlePlaceholder": "Klistra in JSON från Kryptera (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Krypterat paket", + "plaintextOutputLabel": "Klartext", + "encryptButton": "Kryptera", + "decryptButton": "Dekryptera", + "copyBundle": "Kopiera JSON", + "copyPlaintext": "Kopiera klartext", + "copied": "Kopierat", + "emptyBundleHint": "Krypterad JSON visas här efter kryptering.", + "emptyDecryptHint": "Dekrypterad text visas här.", + "decryptKeyHint": "Använd rå nyckel om keyDerivation är \"none\"; lösenfras vid PBKDF2-SHA256. Paketet lagrar salt, iterationer, IV och AES-längd.", + "securityNote": "Nycklar och klartext lämnar inte din webbläsare. Använd dedikerade verktyg för produktionshemligheter.", + "errors": { + "emptyKey": "Ange en rå nyckel som matchar AES-storleken.", + "emptyPassphrase": "Ange en lösenfras.", + "badKeyMaterial": "Nyckeln kunde inte läsas som hex eller Base64.", + "wrongKeyLength": "Nyckellängden matchar inte AES (16 / 24 / 32 byte).", + "invalidJson": "Ogiltig JSON.", + "invalidBundle": "Denna JSON är inte ett paket som stöds.", + "decryptFailed": "Dekryptering misslyckades — fel hemlighet, skadat paket eller fel parametrar.", + "encryptFailed": "Kryptering misslyckades. Kontrollera Web Crypto och indata." + } + }, + "HmacGenerator": { + "title": "HMAC-generator", + "subtitle": "För att signera webhooks och testa API-integrationer. HMAC-SHA1/256/384/512 i hex eller Base64—beräknat lokalt i webbläsaren.", + "secretLabel": "Hemlig nyckel", + "messageLabel": "Meddelande / nyttolast", + "placeholderSecret": "Klistra in din signeringshemlighet…", + "placeholderMessage": "Klistra in råtext eller sträng att verifiera (ofta exakta webhook-bytes).", + "algorithmLabel": "HMAC-algoritm", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Utdataformat", + "formatHex": "Hex (gemener)", + "formatBase64": "Base64", + "outputLabel": "Signatur", + "copyOutput": "Kopiera signatur", + "copied": "Kopierat", + "byteCountHint": "{count, plural, one {# byte} other {# byte}} (UTF-8)", + "emptyHint": "Ange en hemlig nyckel för att beräkna HMAC.", + "securityNote": "Dela aldrig din signeringshemlighet. Den här sidan laddar inte upp data—allt körs i webbläsaren.", + "errors": { + "cryptoFailed": "Kunde inte beräkna HMAC. Kontrollera Web Crypto-stöd och försök igen.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA-kodgenerator", + "subtitle": "Klistra in Base32-hemligheten och se aktuell sexsiffrig RFC 6238-kod (SHA-1, 30 s). Körs lokalt i webbläsaren—användbart vid test av inloggning eller MFA.", + "secretLabel": "Hemlighet (Base32)", + "placeholderSecret": "Klistra in nyckeln (bokstäver A–Z och siffrorna 2–7, som när du ställer in autentiseraren)…", + "codeLabel": "Aktuell kod", + "copyCode": "Kopiera kod", + "copied": "Kopierat", + "nextIn": "Uppdateras om {seconds} s", + "standardHint": "30 s tidssteg, 6 siffror, HMAC-SHA-1 (kompatibel med Google Authenticator).", + "digitsLabel": "Siffror", + "periodLabel": "Period", + "algorithmLabel": "Algoritm", + "periodSeconds": "{seconds} s", + "emptyHint": "Klistra in en hemlighet för att generera en sexsiffrig kod.", + "invalidHint": "Rätta hemligheten för att se koden.", + "securityNote": "Hantera som ett lösenord. Hemligheten lämnar inte webbläsaren; inget laddas upp.", + "importLabel": "Importera från otpauth://-URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Importera", + "qrSectionTitle": "QR-kod för etablering", + "qrHint": "Skanna med en autentiseringsapp (Google Authenticator, 1Password, Aegis…) för att lägga till denna hemlighet. QR-koden kodar hemligheten — behandla den som ett lösenord.", + "accountLabel": "Konto", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Utfärdare (valfritt)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Ange en giltig hemlighet för att generera en QR-kod.", + "errors": { + "invalidSecret": "Ogiltig Base32-hemlighet.", + "cryptoFailed": "Kunde inte beräkna TOTP. Kontrollera Web Crypto och försök igen.", + "invalidUri": "Detta är inte en giltig otpauth:// TOTP-URI.", + "unsupportedUri": "Denna URI använder värden som verktyget inte stöder (siffror måste vara 6 eller 8; period 15, 30 eller 60 s)." + } + }, + "MarkdownPreview": { + "title": "Markdown-förhandsgranskning & HTML", + "subtitle": "Live Markdown-renderer med HTML-export och HTML → Markdown-konvertering. Allt körs i din webbläsare.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown-inmatning", + "htmlInputLabel": "HTML-inmatning", + "previewLabel": "Förhandsgranskning", + "htmlOutputLabel": "HTML-källa", + "mdOutputLabel": "Markdown-utmatning", + "inputPlaceholder": "# Hej\\n\\nSkriv din **markdown** här…", + "htmlInputPlaceholder": "Klistra in din HTML här…", + "copyHtml": "Kopiera HTML", + "copyMarkdown": "Kopiera Markdown", + "exportHtml": "Exportera HTML", + "copied": "Kopierat", + "charCount": "{count, plural, one {# tecken} other {# tecken}}", + "emptyHint": "Börja skriva Markdown för att se live-förhandsgranskning och HTML-utdata.", + "emptyHintHtmlToMd": "Klistra in HTML för att konvertera det till Markdown.", + "securityNote": "Allt körs lokalt i din webbläsare. Inget laddas upp." + }, + "NumberBaseConverter": { + "title": "Basomvandlare", + "subtitle": "Konvertera heltal mellan baser 2–36. Valfria prefix 0x / 0b / 0o. Allt körs lokalt i webbläsaren.", + "inputBaseLabel": "Från bas", + "outputBaseLabel": "Till bas", + "inputLabel": "Inmatat tal", + "placeholderInput": "Ange ett heltal i vald inmatningsbas…", + "outputLabel": "Konverterat värde", + "copyOutput": "Kopiera resultat", + "copied": "Kopierat", + "swap": "Byt baser", + "byteCountHint": "{count, plural, one {# byte} other {# byte}} (UTF-8-längd)", + "emptyHint": "Ange ett tal att konvertera.", + "note": "Siffror över 9 använder bokstäverna A–Z (skiftlägesokänsligt). Blanksteg och understreck ignoreras.", + "errors": { + "invalidNumber": "Ogiltigt värde för vald inmatningsbas." + } + }, + "FormatConverter": { + "title": "Formatkonverterare", + "subtitle": "Konvertera mellan JSON, YAML, TOML och XML i valfri riktning. Alla 12 kombinationer. Allt körs i din webbläsare.", + "fromLabel": "Från", + "toLabel": "Till", + "swap": "Byt", + "swapTitle": "Byt format och använd utdata som indata", + "copy": "Kopiera", + "copied": "Kopierat", + "charCount": "{count, plural, one {# tecken} other {# tecken}}", + "emptyHint": "Klistra in eller skriv din indata till vänster för att se den konverterade utdatan.", + "localNote": "Allt körs lokalt i din webbläsare. Ingenting laddas upp." + }, + "MimeTypeLookup": { + "title": "MIME-typ uppslag", + "subtitle": "Slå upp MIME-typ (medietyp) för en filändelse eller ett fullständigt filnamn. Allt körs lokalt i din webbläsare.", + "inputLabel": "Filändelse eller filnamn", + "placeholder": "t.ex. .png, package.json, photo.jpeg", + "hint": "Tips: klistra in antingen en ändelse (med eller utan punkt) eller ett fullständigt filnamn.", + "clear": "Rensa", + "exampleFilename": "Använd exempel-filnamn", + "exampleExtension": "Använd exempel-ändelse", + "resultLabel": "Resultat", + "normalizedLabel": "Normaliserad indata:", + "mimeTypeLabel": "MIME-typ", + "contentTypeLabel": "Content-Type-header", + "charsetLabel": "Teckenuppsättning", + "copyMimeType": "Kopiera MIME-typ", + "copy": "Kopiera", + "copied": "Kopierat", + "emptyHint": "Ange en filändelse eller ett filnamn för att slå upp MIME-typen.", + "notFound": "Ingen MIME-typ hittades för den indata.", + "localNote": "Allt körs lokalt i din webbläsare. Ingenting laddas upp." + }, + "UserAgentParser": { + "title": "User-Agent-tolk", + "subtitle": "Klistra in en User-Agent (UA)-sträng och få en uppdelning av webbläsare, OS, motor, enhet och CPU. Allt körs lokalt i din webbläsare.", + "inputLabel": "User-Agent-sträng", + "placeholder": "Klistra in din User-Agent här…", + "hint": "Tips: du kan kopiera en UA från request-headers, loggar eller webbläsarens devtools.", + "clear": "Rensa", + "exampleDesktop": "Använd skrivbordsexempel", + "exampleMobile": "Använd mobilexempel", + "resultLabel": "Uppdelning", + "browserLabel": "Webbläsare", + "osLabel": "Operativsystem", + "engineLabel": "Motor", + "deviceTypeLabel": "Enhetstyp", + "deviceVendorLabel": "Tillverkare", + "deviceModelLabel": "Modell", + "cpuArchLabel": "CPU-arkitektur", + "copyJson": "Kopiera JSON", + "copy": "Kopiera", + "copied": "Kopierat", + "emptyHint": "Klistra in en User-Agent-sträng för att se uppdelningen.", + "localNote": "Allt körs lokalt i din webbläsare. Ingenting laddas upp." + }, + "MockDataGenerator": { + "title": "Mock-datagenerator", + "subtitle": "Definiera kolumner, välj datatyper och exportera dataset för seeds, Postman eller tester. Upp till {max} rader, allt i webbläsaren — inget laddas upp.", + "mockarooLink": "Mockaroo-liknande arbetsflöde", + "rowsLabel": "Rader", + "rowsHint": "Högst {max} rader per körning för att hålla fliken responsiv.", + "formatLabel": "Format", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "SQL-tabellnamn", + "presetsLabel": "Mallar", + "presetPlaceholder": "Ladda mall…", + "presetDefault": "Start (användare)", + "presetUsers": "Användare och roller", + "presetProducts": "E-handels-SKU:er", + "presetApiLog": "HTTP-förfrågelogg", + "generate": "Generera", + "warningDuplicateNames": "Dubblettkolumnnamn: senare kolumner skriver över tidigare i JSON-objekt. Byt namn.", + "schemaHeading": "Schema", + "addField": "Lägg till kolumn", + "removeField": "Ta bort kolumn", + "duplicateField": "Duplicera kolumn", + "colName": "Namn", + "colType": "Typ", + "colOptions": "Alternativ", + "colBlank": "Tom %", + "optMin": "min", + "optMax": "max", + "optDecimals": "dec", + "optDateFrom": "från datum", + "optDateTo": "till datum", + "optValues": "a, b, c", + "optStep": "steg", + "optRowBase": "bas", + "output": "Utdata", + "outputPlaceholder": "Klicka på Generera för att skapa data…", + "copy": "Kopiera", + "download": "Ladda ner", + "copied": "Kopierat", + "groups": { + "personal": "Personligt", + "location": "Plats", + "internetApi": "Internet och API", + "numbersIds": "Tal och ID", + "dateTime": "Datum och tid", + "textDesign": "Text och design", + "custom": "Anpassat" + }, + "fieldTypes": { + "first_name": "Förnamn", + "last_name": "Efternamn", + "full_name": "Fullständigt namn", + "email": "Email", + "username": "Användarnamn", + "password": "Lösenord", + "phone": "Telefon", + "company": "Företag", + "job_title": "Befattning", + "avatar_url": "Avatar-URL", + "city": "Stad", + "country": "Land", + "country_code": "Landskod (ISO)", + "street_address": "Adress", + "zip_code": "Postnummer", + "latitude": "Latitud", + "longitude": "Longitud", + "url": "URL", + "domain": "Domän", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Heltal", + "float": "Decimal", + "boolean": "Boolesk", + "uuid": "UUID", + "credit_card": "Testkort", + "semver": "SemVer", + "row_index": "Radindex", + "sequence": "Sekvens", + "date": "Datum", + "datetime": "Datum och tid", + "unix_timestamp": "Unix-tidsstämpel", + "timezone": "Tidszon", + "word": "Ord", + "sentence": "Mening", + "paragraph": "Stycke", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Valutakod", + "language_code": "Språktagg", + "custom_list": "Anpassad lista" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG-optimering", + "subtitle": "Klistra in SVG, minifiera med SVGO i webbläsaren och jämför UTF-8-storlek före och efter.", + "inputLabel": "Indata", + "outputLabel": "Optimerad", + "placeholder": "Klistra in ett -utdrag eller hela filen…", + "outputPlaceholder": "Optimerad SVG visas här…", + "chars": "{count} tecken • {bytes} byte (UTF-8)", + "preview": "Förhandsvisning", + "localNote": "All bearbetning sker lokalt i webbläsaren. Inget laddas upp.", + "stats": { + "title": "Storlek (UTF-8)", + "before": "Före", + "after": "Efter", + "savedBytes": "{count} byte sparade ({percent}%)", + "noChange": "Ingen minskning — redan minimalt eller tomt resultat." + }, + "actions": { + "clear": "Rensa", + "sample": "Ladda exempel", + "copy": "Kopiera", + "copied": "Kopierat", + "download": "Ladda ner" + }, + "errors": { + "optimizeFailed": "Kunde inte optimera denna SVG." + } + }, + "UnitConverter": { + "title": "Enhetsomvandlare", + "subtitle": "Konvertera mellan 323 enheter i 43 kategorier", + "category": "Kategori", + "from": "Från", + "to": "Till", + "value": "Värde", + "result": "Resultat", + "swapUnits": "Byt enheter", + "placeholder": "Ange värde", + "copyResult": "Kopiera resultat", + "searchCategory": "Sök kategori...", + "selectCategory": "Välj en kategori", + "noResults": "Ingen kategori hittades." + }, + "DockerComposeGenerator": { + "title": "Docker Compose-generator", + "subtitle": "Välj populära databaser, cache, meddelandeköer, omvända proxys och observabilitet — kopiera en docker-compose.yml redo för lokal utveckling.", + "searchPlaceholder": "Sök (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Tjänster", + "selectedCount": "{count, plural, one {# tjänst} other {# tjänster}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopiera", + "copied": "Kopierat!", + "download": "Ladda ner", + "emptyState": "Välj en eller flera tjänster för att generera Compose-YAML.", + "hintPorts": "Standardvärdportar minskar krockar; ändra om Docker rapporterar upptagen port. Byt ut demolösenord före delade miljöer.", + "categories": { + "databases": "Databaser och lagring", + "cache": "Cache och in-memory", + "queues": "Köer och meddelanden", + "search": "Sökning och loggar", + "storage": "Objektlagring", + "proxy": "Omvända proxys och ingress", + "observability": "Mätvärden och spårning", + "mail": "E-post (lokal utveckling)", + "admin": "Admin- och BI-gränssnitt" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Rensa" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (Redis-kompatibel)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (Kafka-kompatibel)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (S3-kompatibel)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (SMTP-insamling)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (dev-server)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Skjut brickor och slå ihop dem för att nå 2048", + "score": "Poäng", + "best": "Bäst", + "newGame": "Nytt spel", + "howToPlay": "Använd piltangenterna (eller svep på mobilen) för att flytta brickor. Plattor med samma nummer slås samman. Nå 2048 för att vinna!", + "youWin": "Du har nått 2048!", + "keepGoing": "Fortsätt så!", + "gameOver": "Spelet är slut", + "tryAgain": "Försök igen", + "gameOverMessage": "Inga fler rörelser kvar.", + "undo": "Ångra" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "etapper slutförda", + "time": "Tid", + "best": "Bäst", + "difficulty": { + "easy": "Lätt", + "medium": "Medium", + "hard": "Svår" + }, + "stage": "Stage", + "solved": "Löst", + "score": "Poäng", + "nextStage": "Andra linje", + "replay": "Repris", + "erase": "Radera", + "howToPlay": "Klicka på en cell och tryck sedan på ett nummer eller tryck på 1–9. Backsteg eller Radera rensar cellen. Djärva siffror är ledtrådar. Fyll rutnätet så att varje rad, kolumn och 3× 3-ruta innehåller 1–9 utan upprepningar.", + "pencil": "Penna", + "undo": "Ångra", + "hint": "Tips", + "hintsUsed": "tips använda", + "mistakes": "Misstag", + "failed": "Misslyckades!", + "mistakeMode": "3-misstag läge" + }, + "Snake": { + "title": "Snake", + "subtitle": "Ät mat, väx, undvik väggar och dig själv", + "score": "Poäng", + "best": "Bäst", + "startPrompt": "Tryck en piltangent", + "startButton": "Starta spel", + "gameOver": "Spelet slut", + "playAgain": "Spela igen", + "howToPlay": "Använd piltangenter eller WASD. Ät mat för att växa. Hastigheten ökar var 50:e poäng.", + "diffEasy": "Lätt", + "diffNormal": "Normal", + "diffHard": "Svår", + "diffInfinite": "Oändlig", + "orTap": "eller tryck nedan", + "howToPlayInfinite": "Väggar wrappas — ingen vägdöd. Ät mat för att växa.", + "shieldActive": "Sköld aktiv", + "speedBoost": "Hastighetsnöjning", + "powerSpeed": "Hastighet x2", + "powerShield": "Överlev ett dödsfall", + "powerShrink": "Förkortar svansen" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis-anslutningar", + "heroTitle": "Redis Commander", + "heroDesc": "Anslut till Redis-instanser. Bläddra bland nycklar, redigera värden och kör kommandon. Inloggningsuppgifter krypteras i din webbläsare.", + "heroAddConnection": "Lägg till anslutning", + "vaultLockedTitle": "Valvet är låst", + "vaultLockedDesc": "Lås upp valvet för att komma åt sparade anslutningar." + } + }, + "Minesweeper": { + "title": "Minröjaren", + "subtitle": "Hitta alla säkra rutor utan att utlösa en mina", + "mines": "Minor", + "newGame": "Nytt spel", + "win": "Du vann!", + "loss": "Du förlorade!", + "playAgain": "Spela igen", + "tryAgain": "Försök igen", + "startPrompt": "Klicka på en ruta för att börja", + "difficulty": { + "beginner": "Nybörjare", + "intermediate": "Medel", + "expert": "Expert" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Ordna de fallande blocken för att slutföra rader", + "next": "Nästa", + "score": "Poäng", + "best": "Bäst", + "lines": "Rader", + "level": "Nivå", + "pause": "Pausa", + "resume": "Återuppta", + "start": "Starta", + "restart": "Starta om", + "paused": "Pausad", + "gameOver": "Spelet slut" + }, + "SshKeyGenerator": { + "title": "SSH / RSA-nyckelgenerator", + "subtitle": "Generera Ed25519- eller RSA-nyckelpar i din webbläsare. Ladda ned privata och offentliga nycklar i OpenSSH- och PEM-format. Inget laddas upp.", + "keyTypeLabel": "Nyckeltyp", + "recommended": "rekommenderas", + "ed25519Desc": "Snabb, liten och modern. Stöds av GitHub, GitLab och alla större SSH-servrar.", + "rsa2048Desc": "Brett kompatibel. Säker för de flesta användningsområden.", + "rsa4096Desc": "Maximal kompatibilitet och styrka för äldre system.", + "commentLabel": "Kommentar (valfritt)", + "commentHint": "Läggs till i OpenSSH-nyckelraden.", + "generateBtn": "Generera nyckelpar", + "generating": "Genererar…", + "generatingLong": "Genererar (kan ta en stund)…", + "copy": "Kopiera", + "download": "Ladda ned", + "privateKeyLabel": "Privat nyckel (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Offentlig nyckel (OpenSSH-format)", + "publicKeySpkiLabel": "Offentlig nyckel (SPKI PEM)", + "browserNote": "Alla nycklar genereras i din webbläsare. Inget skickas till någon server.", + "errors": { + "generationFailed": "Nyckelgenerering misslyckades" + } + }, + "DnsLookup": { + "title": "DNS-sökning", + "subtitle": "Fråga DNS-poster för valfri domän — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Ange domän (t.ex. github.com)", + "lookupBtn": "Sök", + "looking": "Söker…", + "refreshBtn": "Uppdatera", + "recordTypesLabel": "Posttyper", + "allBtn": "Alla", + "tryLabel": "Prova:", + "errors": { + "lookupFailed": "DNS-sökningen misslyckades" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt-generator och -kontroll", + "subtitle": "Hasha lösenord med bcrypt och verifiera hashar — allt stannar i din webbläsare", + "tabs": { + "hash": "Hasha", + "verify": "Verifiera" + }, + "showPassword": "Visa lösenord", + "hidePassword": "Dölj lösenord", + "copy": "Kopiera", + "copied": "Kopierat", + "hash": { + "passwordLabel": "Lösenord", + "passwordPlaceholder": "Ange ett lösenord att hasha…", + "costLabel": "Kostnadsfaktor", + "costHint": "Högre kostnad fördubblar arbetet per steg. 10–12 är ett vanligt val för produktion.", + "slowWarning": "Kostnadsfaktorer på 13 och högre kan ta flera sekunder att beräkna.", + "generate": "Generera hash", + "generating": "Hashar…", + "resultLabel": "Bcrypt-hash", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Hash-anatomi", + "algorithm": "algoritm", + "cost": "kostnad", + "salt": "salt (22 tecken)", + "hash": "hash (31 tecken)" + } + }, + "verify": { + "passwordLabel": "Lösenord", + "passwordPlaceholder": "Ange lösenordet att kontrollera…", + "hashLabel": "Bcrypt-hash", + "hashPlaceholder": "$2b$10$…", + "idle": "Ange ett lösenord och en bcrypt-hash för att jämföra dem.", + "invalidHash": "Detta ser inte ut som en giltig bcrypt-hash.", + "checking": "Jämför…", + "match": "Matchar — lösenordet ger denna hash.", + "noMatch": "Ingen matchning — lösenordet ger inte denna hash." + } + }, + "ChmodCalculator": { + "title": "Chmod-kalkylator", + "subtitle": "Konvertera Unix-filbehörigheter mellan kryssrutor, oktal och symbolisk notation", + "grid": { + "title": "Behörigheter", + "read": "Läs", + "write": "Skriv", + "execute": "Kör", + "owner": "Ägare", + "group": "Grupp", + "others": "Andra" + }, + "special": { + "title": "Specialbitar", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Oktal", + "placeholder": "755 eller 4755", + "invalid": "Ange 3 eller 4 oktala siffror (0–7)." + }, + "symbolic": { + "label": "Symbolisk", + "placeholder": "rwxr-xr-x", + "invalid": "Ange 9 tecken som rwxr-xr-x (s/S, t/T tillåtna)." + }, + "output": { + "title": "Kommando", + "copy": "Kopiera", + "copied": "Kopierat" + }, + "explain": { + "line": "{cls} kan {perms}.", + "none": "{cls} har inga behörigheter.", + "setuid": "Setuid: filen körs med ägarens behörigheter.", + "setgid": "Setgid: filen körs med gruppens behörigheter (nya filer i en katalog ärver dess grupp).", + "sticky": "Sticky: endast en fils ägare kan radera eller byta namn på den i denna katalog." + }, + "presets": { + "title": "Vanliga förinställningar", + "octal": "Oktal", + "permissions": "Behörigheter", + "description": "Typisk användning", + "p644": "Vanliga filer: ägaren skriver, alla läser.", + "p755": "Körbara filer och kataloger: alla kan läsa och gå in.", + "p600": "Privata filer: endast ägaren läser och skriver.", + "p700": "Privata körbara filer och kataloger: endast ägaren.", + "p664": "Grupp-skrivbara filer.", + "p775": "Grupp-skrivbara kataloger och körbara filer.", + "p400": "Skrivskyddad för ägaren (t.ex. privata SSH-nycklar)." + } + }, + "CodeScreenshot": { + "title": "Kodskärmbild", + "subtitle": "Förvandla kod till vackra, delbara bilder", + "languageLabel": "Språk", + "autoDetect": "Automatisk identifiering", + "backgroundLabel": "Bakgrund", + "paddingLabel": "Utfyllnad", + "filenameLabel": "Filnamn", + "filenamePlaceholder": "example.ts", + "windowLabel": "Fönsterprickar", + "themeLabel": "Fönstertema", + "themeDark": "Mörkt", + "themeLight": "Ljust", + "scaleLabel": "Exportskala", + "copyImage": "Kopiera bild", + "copied": "Kopierat", + "exportPng": "Exportera PNG", + "inputLabel": "Kod", + "detected": "Identifierat: {language}", + "clear": "Rensa", + "placeholder": "Klistra in din kod här…", + "previewLabel": "Förhandsgranskning", + "emptyPreview": "Förhandsgranskningen av din kod visas här", + "exportFailed": "Kunde inte exportera bild", + "copyUnsupported": "Kopiering av bilder stöds inte i den här webbläsaren", + "copyFailed": "Kunde inte kopiera bild", + "backgrounds": { + "ocean": "Ocean", + "sunset": "Solnedgång", + "candy": "Godis", + "forest": "Skog", + "midnight": "Midnatt", + "gold": "Guld", + "aurora": "Norrsken", + "slate": "Skiffer", + "transparent": "Transparent" + } + }, + "CssGenerators": { + "title": "CSS-generatorer", + "subtitle": "Box-shadow, border-radius och flytande clamp() — kopiera CSS-koden.", + "preview": "Förhandsgranskning", + "output": "CSS", + "copy": "Kopiera", + "copied": "Kopierat", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Flytande clamp()" + }, + "boxShadow": { + "presets": "Förinställningar", + "layer": "Lager", + "addLayer": "Lägg till lager", + "removeLayer": "Ta bort lager", + "x": "X", + "y": "Y", + "blur": "Oskärpa", + "spread": "Spridning", + "color": "Färg", + "alpha": "Alfa", + "inset": "Inre", + "presetNames": { + "subtleCard": "Diskret kort", + "elevated": "Upphöjd", + "hardOffset": "Hård förskjutning", + "neonGlow": "Neonglöd", + "inner": "Inre", + "layeredSmooth": "Lagrad mjuk" + } + }, + "borderRadius": { + "modeLabel": "Läge", + "modes": { + "simple": "Per hörn", + "blob": "Organisk klick" + }, + "unit": "Enhet", + "linkCorners": "Länka hörn", + "randomize": "Slumpa", + "horizontal": "Horisontella radier", + "vertical": "Vertikala radier", + "corners": { + "tl": "Uppe till vänster", + "tr": "Uppe till höger", + "br": "Nere till höger", + "bl": "Nere till vänster" + } + }, + "clamp": { + "minFont": "Min. typsnitt (px)", + "maxFont": "Max. typsnitt (px)", + "minViewport": "Min. viewport (px)", + "maxViewport": "Max. viewport (px)", + "rootFont": "Rot-typsnitt (px)", + "viewportWidth": "Simulerad viewport", + "computedSize": "Beräknad storlek", + "slope": "Lutning", + "intercept": "Skärningspunkt", + "previewText": "Flygande bäckasiner söka", + "invalid": "Ange giltiga, olika viewport-gränser." + } + }, + "ExifViewer": { + "title": "EXIF-visare och -borttagare", + "subtitle": "Inspektera fotons metadata och ta bort dem i din webbläsare", + "dropTitle": "Släpp en bild här", + "dropHint": "eller klicka för att bläddra — din bild lämnar aldrig din enhet", + "browse": "Välj bild", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Läser metadata…", + "parseFailed": "Kunde inte läsa den här bilden", + "clear": "Ta bort bild", + "gpsWarning": "Innehåller GPS-plats", + "openMap": "Visa på OpenStreetMap", + "noMetadata": "Inga inbäddade metadata hittades i den här bilden.", + "facts": { + "name": "Namn", + "type": "Typ", + "dimensions": "Dimensioner", + "size": "Storlek" + }, + "groups": { + "camera": "Kamera", + "exposure": "Exponering", + "timestamps": "Tidsstämplar", + "gps": "GPS-plats", + "software": "Programvara och övrigt" + }, + "gpsCard": { + "latitude": "Latitud", + "longitude": "Longitud", + "altitude": "Höjd" + }, + "raw": { + "title": "Alla metadata", + "copyJson": "Kopiera JSON", + "copied": "Kopierat" + }, + "remove": { + "title": "Ta bort metadata", + "jpegDescription": "Ta bort EXIF, XMP, IPTC och kommentarer förlustfritt utan att komprimera om bilden.", + "reencodeDescription": "Koda om bilden för att ta bort inbäddade metadata. Detta komprimerar om pixlarna.", + "keepIcc": "Behåll färgprofil (ICC)", + "stripButton": "Ta bort metadata", + "reencodeButton": "Koda om och ta bort", + "unsupported": "Borttagning av metadata stöds inte för den här filtypen.", + "failed": "Kunde inte ta bort metadata", + "before": "Före", + "after": "Efter", + "download": "Ladda ner rensad bild" + } + }, + "FaviconGenerator": { + "title": "Favicon-generator", + "subtitle": "Skapa favicons och appikoner från en enda bild", + "dropTitle": "Släpp en bild här", + "dropHint": "PNG, JPEG eller SVG — klicka för att bläddra", + "browse": "Välj bild", + "clear": "Ta bort bild", + "backgroundLabel": "Bakgrund", + "transparent": "Transparent", + "solidColor": "Enfärgad", + "cornerLabel": "Hörnform", + "cornerNone": "Fyrkantig", + "cornerRounded": "Rundad", + "cornerCircle": "Cirkel", + "maskPaddingLabel": "Maskerbar utfyllnad", + "maskable": "maskable", + "previewLabel": "Förhandsgranskning", + "copy": "Kopiera", + "headSnippet": "HTML head-utdrag", + "downloadIco": "Ladda ner favicon.ico", + "downloadZip": "Ladda ner alla (.zip)" + }, + "JsonDiff": { + "title": "JSON-diff", + "subtitle": "Semantisk, strukturmedveten jämförelse av två JSON-dokument — nyckelordning och formatering ignoreras", + "options": { + "ignoreArrayOrder": "Ignorera array-ordning" + }, + "loadBothSamples": "Ladda exempel", + "sample": "Exempel", + "format": "Formatera", + "clear": "Rensa", + "formatError": "Kan inte formatera: indata är inte giltig JSON", + "placeholder": "Klistra in eller skriv JSON här…", + "panels": { + "left": "Original (vänster)", + "right": "Ändrad (höger)", + "results": "Skillnader" + }, + "summary": { + "added": "{count} tillagda", + "removed": "{count} borttagna", + "changed": "{count} ändrade" + }, + "types": { + "added": "Tillagd", + "removed": "Borttagen", + "changed": "Ändrad" + }, + "copyChanges": "Kopiera som JSON", + "copied": "Kopierat", + "empty": "Klistra in JSON i båda panelerna för att se skillnaderna.", + "identical": "De två dokumenten är semantiskt identiska." + }, + "JsonpathPlayground": { + "title": "JSONPath-lekplats", + "subtitle": "Fråga JSON-dokument med JSONPath eller JMESPath och se resultat i realtid", + "engineLabel": "Motor", + "queryLabel": "Fråga", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Fusklapp", + "cheats": { + "jpAllTitles": "Alla boktitlar", + "jpAllAuthors": "Varje författare, var som helst i dokumentet", + "jpFirstBook": "Första boken", + "jpLastBook": "Sista boken", + "jpSlice": "De två första böckerna (utsnitt)", + "jpFilterPrice": "Böcker billigare än 40", + "jpFilterInStock": "Titlar på böcker i lager", + "jpRecursive": "Alla värden, rekursivt", + "jmAllTitles": "Alla boktitlar", + "jmFirstAuthor": "Författare till den första boken", + "jmLastBook": "Sista boken", + "jmSlice": "De två första böckerna (utsnitt)", + "jmFilterPrice": "Titlar på böcker billigare än 40", + "jmCount": "Antal böcker i lager", + "jmProjection": "Omforma varje bok till ett nytt objekt", + "jmSort": "Titlar sorterade efter pris" + }, + "panels": { + "input": "JSON-indata", + "results": "Resultat" + }, + "sample": "Exempel", + "format": "Formatera", + "clear": "Rensa", + "formatError": "Kan inte formatera: indata är inte giltig JSON", + "placeholder": "Klistra in eller skriv JSON här…", + "outputPlaceholder": "Matchningar visas här…", + "matchCount": "{count, plural, one {# matchning} other {# matchningar}}", + "copy": "Kopiera", + "copied": "Kopierat" + }, + "KeycodeInspector": { + "title": "Tangentkodsinspektör", + "subtitle": "Tryck på valfri tangent för att inspektera dess JavaScript-tangentbordshändelse", + "capture": { + "prompt": "Tryck på valfri tangent…", + "ready": "Lyssnar — tryck på valfri tangent på tangentbordet", + "clickToFocus": "Klicka här för att fokusera, tryck sedan på valfri tangent" + }, + "hero": { + "spaceKey": "(Mellanslag)", + "keyLabel": "event.key", + "repeat": "upprepning" + }, + "copyJson": "Kopiera JSON", + "copiedJson": "Händelse-JSON kopierad till urklipp", + "deprecated": "föråldrad", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standard", + "left": "Vänster", + "right": "Höger", + "numpad": "Numeriskt tangentbord", + "unknown": "Okänd" + }, + "history": { + "title": "Historik", + "clear": "Rensa historik", + "empty": "Tangenttryckningar visas här." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown-tabellgenerator", + "subtitle": "Redigera ett rutnät, klistra in från ett kalkylblad och kopiera en snyggt justerad Markdown- eller HTML-tabell", + "addRow": "Lägg till rad", + "addColumn": "Lägg till kolumn", + "importLabel": "Importera", + "importPlaceholder": "Klistra in TSV (från Excel / Google Kalkylark) eller CSV här…", + "importButton": "Importera till rutnät", + "pasteClipboard": "Klistra in från urklipp", + "importEmpty": "Inget att importera — klistra in några rader först", + "clipboardError": "Kunde inte läsa från urklipp", + "clear": "Rensa", + "panels": { + "editor": "Tabellredigerare", + "output": "Utdata" + }, + "align": { + "left": "Vänsterjustera", + "center": "Centrera", + "right": "Högerjustera" + }, + "headerPlaceholder": "Rubrik", + "cellPlaceholder": "Cell", + "removeRow": "Ta bort rad", + "removeColumn": "Ta bort kolumn", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopiera", + "copied": "Kopierat" + }, + "TimezoneConverter": { + "title": "Tidszonskonverterare", + "subtitle": "Konvertera ett ögonblick över hela världen för mötesplanering.", + "dateTimeLabel": "Datum och tid", + "now": "Nu", + "sourceZoneLabel": "Källtidszon", + "selectZone": "Välj zon", + "searchZone": "Sök tidszoner...", + "noZone": "Ingen tidszon hittades.", + "scrubLabel": "Förskjut tid", + "reset": "Återställ", + "targetsLabel": "Målzoner", + "addZone": "Lägg till zon", + "remove": "Ta bort", + "localBadge": "Lokal", + "invalidDate": "Ange ett giltigt datum och tid.", + "bands": { + "work": "Arbetstid (09:00–18:00)", + "edge": "Randtimmar (tidigt / kväll)", + "night": "Natt" + } + }, + "TokenCounter": { + "title": "LLM-token-räknare", + "subtitle": "Räkna tokens, visualisera chunkar och uppskatta API-kostnad.", + "inputLabel": "Text", + "sample": "Exempel", + "clear": "Rensa", + "placeholder": "Klistra in text, kod eller JSON för att räkna tokens...", + "encoding": "Kodning", + "loading": "Laddar tokenizer...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-serien)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Tokens", + "characters": "Tecken", + "words": "Ord", + "ratio": "Tokens/ord", + "ratioSub": "ungefär" + }, + "claudeRow": "Claude (ungefärligt)", + "estimate": "uppskattning", + "viz": { + "title": "Token-visualisering", + "toggle": "Visa", + "hint": "Växla för att färglägga varje token som en chunk.", + "empty": "Ange text för att visualisera tokens.", + "truncated": "Visar de första {limit} av {total} tokens." + }, + "cost": { + "title": "Kostnadskalkylator", + "disclaimer": "redigerbara standardvärden — verifiera aktuella priser", + "model": "Modell", + "expectedOutput": "Förväntade output-tokens", + "customInput": "Input $/MTok", + "customOutput": "Output $/MTok", + "inputCost": "Input-kostnad", + "outputCost": "Output-kostnad", + "total": "Totalt", + "per1000": "Per 1000 anrop" + } + }, + "WebhookTester": { + "title": "Webhook-testare", + "subtitle": "Skapa en tillfällig URL och inspektera inkommande webhook-förfrågningar live.", + "intro": { + "title": "Skapa en webhook-URL för att börja fånga förfrågningar", + "hint": "Du får en ogissningsbar URL som registrerar varje inkommande förfrågan. Den upphör efter 24 timmar." + }, + "createBtn": "Skapa webhook-URL", + "creating": "Skapar…", + "urlLabel": "Din webhook-URL", + "expiresIn": "Upphör om {countdown}", + "copyUrlBtn": "Kopiera URL", + "copied": "Kopierat", + "clearBtn": "Rensa", + "newBinBtn": "Ny URL", + "requestsTitle": "Förfrågningar", + "selectPrompt": "Välj en förfrågan för att inspektera dess detaljer.", + "expiredNotice": "Den här webhook-URL:en har upphört. Skapa en ny för att fortsätta testa.", + "base64Badge": "base64", + "truncatedBadge": "avkortad", + "empty": { + "title": "Väntar på förfrågningar…", + "hint": "Skicka en förfrågan till din URL så visas den här direkt. Prova detta:" + }, + "detail": { + "receivedAt": "Mottagen", + "source": "Käll-IP", + "contentType": "Content-Type", + "query": "Frågeparametrar", + "headers": "Headers", + "body": "Body", + "copyBody": "Kopiera body", + "emptyBody": "Ingen body" + }, + "errors": { + "createFailed": "Kunde inte skapa webhook-URL. Försök igen.", + "clearFailed": "Kunde inte rensa förfrågningar. Försök igen." + } + }, + "WebsocketTester": { + "title": "WebSocket-testare", + "subtitle": "Anslut till en WebSocket-slutpunkt och skicka, ta emot och inspektera meddelanden live", + "urlLabel": "Slutpunkts-URL", + "protocolsLabel": "Underprotokoll", + "protocolsPlaceholder": "valfritt, kommaseparerat", + "connect": "Anslut", + "disconnect": "Koppla från", + "status": { + "disconnected": "Frånkopplad", + "connecting": "Ansluter", + "connected": "Ansluten", + "error": "Fel" + }, + "messages": { + "connecting": "Ansluter till {url}…", + "connected": "Anslutning upprättad", + "connectedWithProtocol": "Anslutning upprättad (protokoll: {protocol})", + "invalidUrl": "Ogiltig URL — måste börja med ws:// eller wss://", + "connectFailed": "Kunde inte öppna anslutningen", + "socketError": "Ett anslutningsfel uppstod", + "notConnected": "Inte ansluten — anslut först", + "binaryMessage": "Tog emot ett binärt meddelande" + }, + "log": { + "title": "Meddelandelogg", + "empty": "Inga meddelanden ännu. Anslut och börja skicka.", + "copyAll": "Kopiera alla", + "download": "Ladda ner som .txt", + "clear": "Rensa logg", + "prettyPrint": "Formatera JSON snyggt", + "showRaw": "Visa rå text" + }, + "composer": { + "placeholder": "Skriv ett meddelande att skicka…", + "send": "Skicka", + "formatJson": "Formatera som JSON", + "invalidJson": "Meddelandet är inte giltig JSON", + "hint": "Cmd/Ctrl+Enter för att skicka" + }, + "ping": { + "label": "Auto-ping", + "seconds": "sek", + "intervalTitle": "Ping-intervall i sekunder", + "payloadPlaceholder": "ping-payload" + } + }, + "WhoisLookup": { + "title": "Whois-uppslag", + "subtitle": "Slå upp registreringsuppgifter för valfri domän eller IP-adress via RDAP", + "placeholder": "example.com eller 8.8.8.8", + "lookupBtn": "Slå upp", + "looking": "Slår upp…", + "refreshBtn": "Uppdatera", + "tryLabel": "Prova:", + "copy": "Kopiera", + "copied": "Kopierat", + "notAvailable": "Ej tillgänglig", + "summary": { + "registrar": "Registrator", + "network": "Nätverk" + }, + "dates": { + "created": "Skapad", + "updated": "Uppdaterad", + "expires": "Upphör" + }, + "sections": { + "status": "Status", + "nameservers": "Namnservrar", + "contacts": "Kontakter", + "rawRdap": "Rå RDAP" + }, + "contacts": { + "roles": "Roller", + "name": "Namn", + "organization": "Organisation" + }, + "errors": { + "invalidTarget": "Ange ett giltigt domännamn eller IP-adress", + "lookupFailed": "Uppslaget misslyckades (status {status})" + } + } +} diff --git a/apps/desktop-ui/messages/tr.json b/apps/desktop-ui/messages/tr.json new file mode 100644 index 00000000..bc59af9f --- /dev/null +++ b/apps/desktop-ui/messages/tr.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Gösterge Paneli", + "settings": "Ayarlar", + "apiClient": "API İstemcisi", + "httpStatusCodes": "HTTP durum kodları", + "passwordManager": "Şifre Yöneticisi", + "environmentManager": "Ortam yöneticisi", + "bookmarks": "Yer İmleri", + "jsonFormatter": "JSON biçimleyici", + "emailValidator": "E-posta Doğrulayıcı", + "nosqlExplorer": "Veritabanı Gezgini", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Şifreleme oyun alanı", + "jwtDecoder": "JWT Çözücü", + "certificatePemDecoder": "Sertifika / PEM çözücü", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL Biçimlendirici", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL Ayrıştırıcı", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Gizli / API anahtarı", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Kontrast denetleyici", + "jsonSchemaGenerator": "JSON Şema Oluşturucu", + "imageToBase64": "Base64'e görüntü", + "imageCompressor": "Görüntü sıkıştırıcı", + "cssGradientBuilder": "CSS Degrade Oluşturucu", + "gitignoreGenerator": ".gitignore Oluşturucu", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Kod parçacıkları", + "qrCodeGenerator": "QR kod oluşturucu", + "dnsLookup": "DNS Sorgulama", + "ipSubnetCalculator": "IP / alt ağ hesaplayıcı", + "hashGenerator": "Hash oluşturucu", + "hmacGenerator": "HMAC Oluşturucu", + "totpGenerator": "TOTP / 2FA kodu", + "markdownPreview": "Markdown Önizleme", + "numberBaseConverter": "Taban dönüştürücü", + "formatConverter": "Format Dönüştürücü", + "mockDataGenerator": "Sahte veri üreticisi", + "svgOptimizer": "SVG Optimize", + "unitConverter": "Birim Dönüştürücü", + "dockerComposeGenerator": "Docker Compose Oluşturucu", + "breakRoom": "Mola Odası", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "YAML Biçimleyici", + "sshKeyGenerator": "SSH / RSA Anahtar Oluşturucu", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod Hesaplayıcı", + "codeScreenshot": "Kod Ekran Görüntüsü", + "cssGenerators": "CSS Üreticileri", + "exifViewer": "EXIF Görüntüleyici", + "faviconGenerator": "Favicon Üreticisi", + "jsonDiff": "JSON Karşılaştırma", + "jsonpathPlayground": "JSONPath Oyun Alanı", + "keycodeInspector": "Tuş Kodu İnceleyici", + "markdownTableGenerator": "Markdown Tablosu", + "timezoneConverter": "Saat Dilimi Dönüştürücü", + "tokenCounter": "Token Sayacı", + "webhookTester": "Webhook Test Aracı", + "websocketTester": "WebSocket Test Aracı", + "whoisLookup": "Whois Sorgulama" + }, + "Help": { + "title": "Yardım ve dokümantasyon", + "subtitle": "MyDevTools’un nasıl çalıştığı, her uygulamanın ne yaptığı ve güvenlik ile şifrelemenin verilerinizi nasıl koruduğu.", + "tabs": { + "overview": "Genel bakış", + "apps": "Uygulamalar", + "security": "Güvenlik", + "tips": "İpuçları" + }, + "overview": { + "whatIs": { + "title": "MyDevTools nedir?", + "description": "Giriş yapılan bir geliştirici çalışma alanı: bir gösterge paneli ve günlük araçlar.", + "p1": "Hesabınızla giriş yaptıktan sonra uygulama, araçların verilerinizi yükleyip kaydedebilmesi için MyDevTools API ile bir oturum kurar. Yan menü tüm uygulamaları listeler; kullanmadığınız araçları", + "settingsLink": "Ayarlar", + "p1After": "sayfasından gizleyebilirsiniz.", + "p2": "Bazı özellikler ana parola (veya global kasa anahtarı) kullanır; böylece parolalar ve bağlantı dizeleri gibi sırlar sunucuya ulaşmadan önce cihazınızda şifrelenir." + }, + "quickLinks": { + "title": "Hızlı bağlantılar", + "dashboard": "Gösterge Paneli", + "settings": "Ayarlar" + } + }, + "apps": { + "intro": "Herhangi bir aracı yan menüden veya gösterge panelinden açın. Aşağıda her uygulama için kısa bir rehber var.", + "open": "Aç", + "howItWorks": "Nasıl çalışır", + "generic": "Bu aracı yan menüden, kendi iş akışı üzerinden kullanın." + }, + "security": { + "session": { + "title": "Giriş ve API oturumu", + "description": "Kimlik ve tarayıcının backend ile nasıl konuştuğu.", + "items": { + "authentication": "Kimlik doğrulama için Firebase kullanılır. Girişten sonra uygulama Firebase ID token’ınızı API çerezleriyle değiştirir.", + "accessTokens": "Erişim token’ları sunucuda imzalanan JWT’lerdir (HS256) ve API’yi çağıran kullanıcıyı kanıtlar.", + "refreshTokens": "Yenileme token’ları HttpOnly çerezlerde saklanır; sunucu ham token yerine SHA-256 hash’ini tutar." + } + }, + "encryption": { + "title": "Bilmeniz gereken şifreleme", + "description": "Kasa ve hassas bağlantı verileri için istemci tarafı kripto.", + "p1": "Şifre yöneticisi, ortam yöneticisi ve NoSQL Explorer kayıtlı bağlantıları için web uygulaması tarayıcının Web Crypto API özelliğini kullanır:", + "items": { + "kdf": "PBKDF2 (SHA-256, 100.000 iterasyon, rastgele salt) ana parolanızdan 256-bit anahtar türetir.", + "aes": "AES-256-GCM veriyi şifreler; her şifreleme için rastgele 12 bayt IV kullanılır.", + "scope": "Türetilen anahtar tarayıcıda kalır (dışa aktarılamaz). Sunucu yalnızca şifreli metin ve IV alır; ana parola veya düz metin sırlar gönderilmez." + }, + "warning": "Ana parolanızı unutursanız, şifrelenmiş veriler kurtarılamaz. Güvenle saklayın." + }, + "logout": { + "title": "Çıkış ve yerel veriler", + "body": "Çıkış yapmak bellek içindeki sırları, kasa durumunu ve (varsa) IndexedDB’deki ana anahtar materyalini temizler ve API oturumunu sonlandırır. Yalnızca yerel depoda tutulan veriler, tarayıcıdan temizleyene kadar kalabilir." + } + }, + "tips": { + "title": "MyDevTools’u güvenli kullanma", + "items": { + "masterPassword": "Kasa özellikleri için güçlü ve benzersiz bir ana parola kullanın.", + "apiClient": "API İstemcisini herhangi bir HTTP istemcisi gibi kullanın: kimlerin görebileceğini bilmiyorsanız üretim sırlarını paylaşılan ortamlara yapıştırmayın.", + "nosql": "Veritabanı Gezgini sizin yapılandırdığınız veritabanlarına bağlanır; mümkünse en az ayrıcalıklı kullanıcıları ve ağ kurallarını kullanın.", + "json": "JSON biçimleyici tarayıcıda yerel çalışır—yine de paylaşılan cihazlarda çok hassas verileri yapıştırmaktan kaçının.", + "settings": "Yan menüyü odaklı tutmak için Ayarlar’dan görünür araçları özelleştirin." + } + } + }, + "Settings": { + "title": "Ayarlar", + "language": "Dil", + "spanish": "İspanyolca", + "english": "İngilizce", + "french": "Français", + "arabic": "عربى", + "catalan": "Katalanca", + "chineseSimplified": "Çince (Basitleştirilmiş)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Almanca", + "danish": "Danca", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "Ukraynaca" + }, + "SettingsPage": { + "title": "Ayarlar", + "subtitle": "Hesap ayarlarınızı ve tercihlerinizi yönetin.", + "userProfile": { + "title": "Kullanıcı Profili", + "description": "Kişisel bilgileriniz ve hesap detaylarınız.", + "avatarAlt": "Kullanıcı", + "anonymousUser": "Anonim Kullanıcı", + "noEmailProvided": "E-posta belirtilmedi", + "notLoggedIn": "Giriş yapılmadı." + }, + "tools": { + "title": "Araçlar ve Özellikler", + "description": "Gezinme kenar çubuğunuzda hangi araçların görüneceğini seçin." + }, + "appearance": { + "title": "Görünüm", + "description": "Uygulamanın cihazınızda nasıl göründüğünü özelleştirin.", + "themePreference": "Tema Tercihi", + "themes": { + "light": "Açık", + "dark": "Koyu", + "system": "Sistem" + } + }, + "language": { + "title": "Dil", + "description": "Platform genelinde kullanılan dili ayarlayın.", + "selectLanguage": "Dil Seçin", + "selectPlaceholder": "Bir dil seçin", + "helpText": "Dilinizi değiştirmek, platform genelindeki menüleri ve etiketleri güncelleyecektir.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (İspanyolca)", + "ar": "عربى (Arapça)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Yunanca)", + "de": "Deutsch (Almanca)", + "da": "Dansk (Danca)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "İtalyanca (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski (Lehçe)", + "pt": "Português", + "pt-BR": "Português (Brezilya)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Günaydın", + "afternoon": "İyi günler", + "evening": "İyi akşamlar" + }, + "commaName": ", {name}", + "welcomeBack": "Tekrar hoş geldiniz", + "welcomeBackNamed": "Tekrar hoş geldiniz, {name}", + "tagline": "Bugün ne oluşturmak istersiniz?", + "brandName": "MyDevTools", + "stats": { + "tools": "Araçlar", + "favorites": "Favoriler", + "pinned": "Sabitlenen", + "recent": "Son Kullanılanlar" + }, + "sections": { + "recentlyUsed": "Son Kullanılanlar", + "favorites": "Favoriler", + "whatsNew": "Yenilikler", + "pinned": "Sabitlenen" + }, + "groups": { + "apps": "Uygulamalar" + }, + "toolCard": { + "defaultDescription": "Daha iyi işlevsellik için bu aracı keşfedin.", + "launchTool": "Aracı Başlat" + }, + "tools": { + "toDo": { + "title": "Görevler", + "description": "Yapılacaklar listelerinizi verimli şekilde yönetin." + }, + "notes": { + "title": "Notlar", + "description": "Fikirlerinizi kaydedin ve düşüncelerinizi düzenleyin." + }, + "passwordManager": { + "title": "Şifre Yöneticisi", + "description": "Şifrelerinizi güvenle saklayın ve yönetin." + }, + "environmentManager": { + "title": "Ortam yöneticisi", + "description": ".env tarzı değişkenleri proje ve ortama göre etiketlerle saklayın." + }, + "bookmarks": { + "title": "Yer İmleri", + "description": "Yer imlerinizi yönetin ve düzenleyin." + }, + "apiClient": { + "title": "API İstemcisi", + "description": "HTTP isteklerini test edin ve hata ayıklayın." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "MongoDB veritabanlarını keşfedin." + }, + "emailValidator": { + "title": "E-posta Doğrulayıcı", + "description": "E-posta adreslerini doğrulayın ve kontrol edin." + }, + "encryptionPlayground": { + "title": "Şifreleme oyun alanı", + "description": "Ham anahtar veya parola ile AES-GCM; JSON paketleri, Web Crypto yalnızca tarayıcıda." + }, + "jwtDecoder": { + "title": "JWT Çözücü", + "description": "JWT başlığını, yükünü ve süresini çöz." + }, + "certificatePemDecoder": { + "title": "Sertifika / PEM çözücü", + "description": "X.509 sertifikalarını ve CSR’ları çözümleyin: konu, veren, geçerlilik ve SAN." + }, + "sshKeyGenerator": { + "title": "SSH / RSA Anahtar Üreticisi", + "description": "Tarayıcınızda Ed25519 veya RSA anahtar çiftleri oluşturun. OpenSSH ve PEM formatlarında indirin." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL Biçimlendirici", + "description": "Sözdizimi vurgulama ve basit bir sorgu oluşturucuyla GraphQL biçimlendirin." + }, + "yamlFormatter": { + "title": "YAML Biçimleyici", + "description": "YAML'ı tarayıcıda biçimlendirin, doğrulayın ve JSON'a dönüştürün." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL Ayrıştırıcı", + "description": "Bir URL'yi protokol, host, yol, sorgu parametreleri ve hash olarak ayırın." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Gizli / API anahtarı", + "description": "Yapılandırılabilir alfabe, uzunluk ve toplu dışa aktarma ile kriptografik rastgele dizeler." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Kontrast denetleyici", + "description": "Metin ve arka plan için WCAG 2.1 kontrast oranı (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "JSON Şema Oluşturucu", + "description": "Örnek JSON’dan JSON Şeması ve tipli modeller türetin." + }, + "imageToBase64": { + "title": "Base64'e görüntü", + "description": "Görüntüleri Veri URI'sine veya ham Base64 dizelerine dönüştürün." + }, + "imageCompressor": { + "title": "Görüntü sıkıştırıcı", + "description": "JPEG, PNG ve WebP dosyalarını yerelde kalite kaydırıcısıyla sıkıştırın." + }, + "cssGradientBuilder": { + "title": "CSS Degrade Oluşturucu", + "description": "Açı kontrolü ve renk duraklarına sahip görsel CSS degrade oluşturucu." + }, + "gitignoreGenerator": { + "title": ".gitignore Oluşturucu", + "description": "500'den fazla teknoloji yığınından anında birleştirilmiş .gitignore dosyaları oluşturun." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "CSV veya Excel dosyalarını JSON’a dönüştürün ve tekrar CSV veya XLSX olarak dışa aktarın." + }, + "snippetManager": { + "title": "Kod parçacıkları", + "description": "Sözdizimi vurgulama, otomatik dil algılama ve biçimlendirme ile parçacıkları kaydedin." + }, + "qrCodeGenerator": { + "title": "QR kod oluşturucu", + "description": "Metin veya URL’den tarayıcıda PNG QR kodları oluşturun." + }, + "ipSubnetCalculator": { + "title": "IP / alt ağ hesaplayıcı", + "description": "IPv4 ve IPv6 CIDR: maske, aralıklar ve adres sayısı." + }, + "dnsLookup": { + "title": "DNS Sorgulama", + "description": "Herhangi bir alan adı için DNS kayıtlarını sorgulayın — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Hash oluşturucu", + "description": "Metin veya dosyalar için MD5, SHA-1 ve SHA-2." + }, + "hmacGenerator": { + "title": "HMAC Oluşturucu", + "description": "HMAC-SHA1/256/384/512 ile webhook imzalama ve API entegrasyonlarını test edin." + }, + "totpGenerator": { + "title": "TOTP / 2FA kod üreticisi", + "description": "Doğrulayıcıdan Base32 gizli anahtarı yapıştırın; altı haneli kod her 30 sn’de yenilenir—oturum açma ve MFA testleri için kullanışlıdır." + }, + "markdownPreview": { + "title": "Markdown Önizleme", + "description": "HTML dışa aktarma ve HTML → Markdown dönüşümlü canlı Markdown renderer." + }, + "numberBaseConverter": { + "title": "Sayı tabanı dönüştürücü", + "description": "2–36 tabanları arasında tam sayıları dönüştürün." + }, + "formatConverter": { + "title": "Format Dönüştürücü", + "description": "JSON, YAML, TOML ve XML arasında her yönde dönüştürün. 12 kombinasyonun tamamı desteklenir." + }, + "mockDataGenerator": { + "title": "Sahte veri üreticisi", + "description": "Sütunları tanımlayın, veri türlerini seçin ve tohum, Postman veya test için dışa aktarın. En fazla 5000 satır, hepsi tarayıcıda — yükleme yok." + }, + "httpStatusCodes": { + "title": "HTTP Durum Kodları", + "description": "Açıklamalarla birlikte HTTP durum kodlarını arayın (1xx–5xx)." + }, + "svgOptimizer": { + "title": "SVG Optimize", + "description": "SVG yapıştırın, SVGO ile gereksizleri silin ve UTF-8 boyut kazancını görün." + }, + "unitConverter": { + "title": "Birim Dönüştürücü", + "description": "43 bilimsel ve mühendislik kategorisinde 323 birim arasında dönüştürün." + }, + "dockerComposeGenerator": { + "title": "Docker Compose Oluşturucu", + "description": "Veritabanları, önbellek, kuyruklar, proxy ve gözlemlenebilirlik görüntülerini seçin; yerel yığınlar için docker-compose.yml dışa aktarın." + }, + "game2048": { + "title": "2048", + "description": "2048 'e ulaşmak için döşemeleri kaydır ve birleştir. Klasik sayı bulmaca oyunu." + }, + "sudoku": { + "title": "Sudoku", + "description": "Klasik sayı bulmacası. 3 zorluk seviyesi, her biri 150 aşama." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Herhangi bir Redis örneğinde anahtarlara göz atın, değerleri inceleyin ve komutlar çalıştırın." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt Üretici ve Doğrulayıcı", + "description": "Parolaları bcrypt ile hash'leyin ve mevcut hash'leri doğrulayın." + }, + "chmodCalculator": { + "title": "Chmod Hesaplayıcı", + "description": "Unix izinlerini sekizlik, sembolik ve onay kutuları arasında dönüştürün." + }, + "codeScreenshot": { + "title": "Kod Ekran Görüntüsü", + "description": "Kaynak kodu güzel ve paylaşılabilir görsellere dönüştürün." + }, + "cssGenerators": { + "title": "CSS Üreticileri", + "description": "box-shadow, border-radius ve akışkan clamp() üreticileri." + }, + "exifViewer": { + "title": "EXIF Görüntüleyici ve Temizleyici", + "description": "Fotoğraf EXIF meta verilerini yerel olarak inceleyin ve temizleyin." + }, + "faviconGenerator": { + "title": "Favicon Üreticisi", + "description": "Favicon, uygulama simgeleri ve manifest parçacıkları oluşturun." + }, + "jsonDiff": { + "title": "JSON Karşılaştırma", + "description": "İki JSON belgesinin anlamsal, yapı farkında karşılaştırması." + }, + "jsonpathPlayground": { + "title": "JSONPath Oyun Alanı", + "description": "JSON'ı JSONPath veya JMESPath ile sorgulayın ve sonuçları canlı görün." + }, + "keycodeInspector": { + "title": "Tuş Kodu İnceleyici", + "description": "Bir tuşa basarak JavaScript olay kodlarını ve değiştiricileri görün." + }, + "markdownTableGenerator": { + "title": "Markdown Tablosu Üreticisi", + "description": "Markdown ve HTML tablolarını elektronik tablo benzeri bir ızgara düzenleyicide oluşturun." + }, + "timezoneConverter": { + "title": "Saat Dilimi Dönüştürücü", + "description": "Bir zamanı birçok dilime; gün kayması ve çalışma saati ipuçlarıyla dönüştürün." + }, + "tokenCounter": { + "title": "LLM Token Sayacı", + "description": "LLM token'larını sayın, parçaları görselleştirin ve API maliyetini tahmin edin." + }, + "webhookTester": { + "title": "Webhook Test Aracı", + "description": "Gelen webhook isteklerini gerçek zamanlı yakalayın ve inceleyin." + }, + "websocketTester": { + "title": "WebSocket Test Aracı", + "description": "WebSocket uç noktalarına bağlanın ve mesajları gerçek zamanlı alışverişini yapın." + }, + "whoisLookup": { + "title": "Whois Sorgulama", + "description": "Alan adı ve IP kayıt bilgilerini RDAP üzerinden sorgulayın." + } + }, + "tabs": { + "apps": "Uygulamalar", + "analytics": "Analitik", + "ui": "Arayüz" + }, + "analytics": { + "title": "Verilerinize genel bakış", + "subtitle": "Sunucudaki hesabınızla senkronize sayılar.", + "loadError": "Analitik yüklenemedi.", + "retry": "Yeniden dene", + "loginHint": "Kullanım analitiği ve arayüz tercihleri için giriş yapın.", + "passwordEntries": "Parola kayıtları", + "bookmarks": "Yer imleri", + "bookmarkFolders": "Yer imi klasörleri", + "tasksTotal": "Görevler", + "tasksCompleted": "Tamamlanan", + "tasksOngoing": "Devam eden", + "tasksNotStarted": "Başlanmadı", + "projects": "Projeler", + "nosqlConnections": "Veritabanı bağlantıları", + "notes": "Notlar", + "apiClientCollections": "API koleksiyonları", + "apiClientEnvironments": "API ortamları", + "apiClientHistory": "API geçmişi kayıtları", + "jsonFormatterDocuments": "JSON biçimleyici belgeleri", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Kod parçacıkları", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Arayüz tercihleri", + "subtitle": "Tema, dil ve kenar çubuğundaki araçlar — hesap ayarlarınızdan.", + "loadError": "Tercihler yüklenemedi.", + "loginHint": "Kayıtlı arayüz tercihlerinizi görmek için giriş yapın.", + "theme": "Tema", + "accent": "Vurgu rengi", + "locale": "Dil", + "enabledTools": "Görünür araçlar", + "favoriteTools": "Favori araçlar", + "toolsWithUsage": "Kullanımı izlenen araçlar" + }, + "signIn": "Giriş yap", + "noResults": "Aramanızla eşleşen araç bulunamadı", + "noResultsHint": "Farklı bir anahtar kelime deneyin veya filtreyi temizleyin.", + "filterAll": "Tümü", + "loginCta": { + "description": "Son kullandığınız araçları takip etmek için giriş yapın." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Yükleniyor...", + "myTasksTitle": "Görevlerim", + "statsLine": "{total} görev • %{percent} tamamlandı", + "statsLineDesktop": "{total} toplam • %{percent} tamamlandı" + }, + "filters": { + "all": "Tümü", + "total": "Toplam", + "allProjects": "Tüm Projeler", + "export": "Dışa Aktar", + "kanban": "Kanban", + "list": "Liste", + "searchPlaceholder": "Görev ara...", + "searchAria": "Görev ara", + "clearSearchAria": "Aramayı temizle", + "foundTasks": "{count} {count, plural, one {görev} other {görev}} bulundu", + "filterByAllAria": "Tümüne göre filtrele", + "filterByStatusAria": "{status} durumuna göre filtrele" + }, + "status": { + "not-started": { + "label": "Başlanmadı", + "description": "Henüz başlanmamış görevler" + }, + "ongoing": { + "label": "Devam Ediyor", + "description": "Şu anda devam eden görevler" + }, + "completed": { + "label": "Tamamlandı", + "description": "Tamamlanmış görevler" + } + }, + "priorities": { + "high": "Yüksek", + "medium": "Orta", + "low": "Düşük" + }, + "form": { + "addNewTaskLabel": "Yeni görev ekle", + "multilinePlaceholder": "Görev detaylarını ekleyin... (Yeni satır için Shift+Enter)", + "singlelinePlaceholder": "Ne yapılması gerekiyor? Eklemek için Enter'a basın", + "taskInputAria": "Görev girişi", + "projectPlaceholder": "Proje", + "selectProject": "Proje Seç", + "errorSelectProject": "Görev eklemek için lütfen bir proje seçin veya yeni bir proje oluşturun.", + "hintMultiline": "Yeni satır için Shift+Enter, eklemek için Enter", + "hintSingleline": "Eklemek için Enter", + "shortcutsAria": "Klavye kısayolları", + "shortcutsLabel": "Kısayollar", + "shortcutAddTask": "Görev ekle", + "shortcutNewLine": "Yeni satır", + "shortcutClear": "Temizle", + "addButton": "Ekle" + }, + "list": { + "ariaLabel": "Görev listesi", + "loading": "Görevleriniz yükleniyor...", + "emptyTitle": "Görev bulunamadı", + "emptyPrimary": "Yukarıdaki ilk görevinizi ekleyerek başlayın! Hızlıca görev eklemek için Enter'a basın.", + "emptySecondary": "Filtrelerinizi veya arama sorgunuzu ayarlamayı deneyin." + }, + "mobileDrawer": { + "addTaskSrOnly": "Görev Ekle", + "addNewTaskTitle": "Yeni Görev Ekle", + "addNewTaskDescription": "İlerlemenizi takip etmek için yeni bir görev oluşturun.", + "cancel": "İptal" + }, + "kanban": { + "ariaLabel": "Kanban panosu", + "loading": "Görevleriniz yükleniyor...", + "dropHere": "Görevi buraya bırakın", + "noTasksYet": "Henüz görev yok", + "emptyColumnAria": "Boş sütun", + "columnAria": "{count} görevli {title} sütunu", + "countInColumnAria": "{title} içinde {count} görev" + }, + "taskItem": { + "confirmDelete": "Bu görevi silmek istediğinizden emin misiniz?", + "statusPlaceholder": "Durum", + "changeStatusAria": "Görev durumunu değiştir", + "taskOptionsAria": "Görev seçenekleri", + "editTask": "Görevi Düzenle", + "copyTask": "Görevi Kopyala", + "copied": "Kopyalandı!", + "deleteTask": "Görevi Sil", + "statusHeading": "Durum", + "editTaskAria": "Görevi düzenle" + }, + "editDialog": { + "title": "Görevi Düzenle", + "description": "Görev detaylarını güncelleyin, alt görevler ekleyin, öncelikler belirleyin ve daha fazlası.", + "taskTitleLabel": "Görev Başlığı *", + "taskTitlePlaceholder": "Ne yapılması gerekiyor?", + "descriptionLabel": "Açıklama", + "descriptionPlaceholder": "Bu görev hakkında daha fazla detay ekleyin...", + "statusLabel": "Durum", + "priorityLabel": "Öncelik", + "dueDateLabel": "Bitiş Tarihi", + "pickDate": "Tarih seçin", + "clearDate": "Tarihi Temizle", + "projectLabel": "Proje", + "selectProjectPlaceholder": "Bir proje seçin", + "noProject": "Proje Yok", + "timeEstimateLabel": "Tahmini Süre (dakika)", + "timeEstimatePlaceholder": "Bu ne kadar sürecek?", + "tagsLabel": "Etiketler", + "customTagNamePlaceholder": "Özel etiket adı", + "subtasksLabel": "Alt Görevler", + "addSubtaskPlaceholder": "Alt görev ekle...", + "cancel": "İptal", + "saving": "Kaydediliyor...", + "saveChanges": "Değişiklikleri Kaydet" + }, + "export": { + "title": "Görevleri Dışa Aktar", + "description": "Görevlerinizi JSON, CSV veya Excel formatlarında dışa aktarın.", + "projectLabel": "Proje", + "statusLabel": "Durum", + "tagLabel": "Etiket", + "allProjects": "Tüm Projeler", + "allStatuses": "Tüm Durumlar", + "allTags": "Tüm Etiketler", + "exportAsJsonTitle": "JSON olarak Dışa Aktar", + "exportAsJsonDescription": "Alt görevler, etiketler ve meta veriler dahil görev verilerini dışa aktarın.", + "exportJson": "JSON Dışa Aktar", + "exportAsCsvExcelTitle": "CSV / Excel olarak Dışa Aktar", + "exportAsCsvExcelDescription": "Elektronik tablo uygulamaları için görevleri dışa aktarın.", + "exportCsv": "CSV Dışa Aktar", + "exportExcel": "Excel Dışa Aktar", + "close": "Kapat", + "toastNoTasks": "Mevcut filtrelerle dışa aktarılacak görev yok", + "toastExportedJson": "{count} görev JSON olarak dışa aktarıldı!", + "toastExportedCsv": "{count} görev CSV olarak dışa aktarıldı!", + "toastExportedExcel": "{count} görev Excel olarak dışa aktarıldı!", + "toastFailedJson": "JSON olarak dışa aktarma başarısız", + "toastFailedCsv": "CSV olarak dışa aktarma başarısız", + "toastFailedExcel": "Excel olarak dışa aktarma başarısız", + "unknownProject": "Bilinmeyen Proje" + }, + "ack": { + "taskAddedTitle": "Görev başarıyla eklendi", + "taskAddedFailedTitle": "Görev eklenemedi", + "taskUpdatedTitle": "Görev başarıyla güncellendi", + "taskUpdatedFailedTitle": "Görev güncellenemedi", + "taskStatusUpdateFailedTitle": "Görev durumu güncellenemedi", + "taskMovedTitle": "Görev {status} durumuna taşındı", + "tryAgain": "Lütfen tekrar deneyin.", + "taskDeletedTitle": "\"{text}\" görevi silindi", + "undo": "Geri Al", + "taskRestoredTitle": "Görev geri yüklendi", + "taskRestoredSuccessTitle": "Görev başarıyla geri yüklendi", + "taskRestoreFailedTitle": "Görev geri yüklenemedi. Lütfen tekrar deneyin.", + "taskDeleteFailedTitle": "Görev silinemedi. Lütfen tekrar deneyin.", + "tasksImportedTitle": "{count} {count, plural, one {görev} other {görev}} başarıyla içe aktarıldı!", + "tasksImportFailedTitle": "Görevler içe aktarılamadı. Lütfen tekrar deneyin.", + "unknownProject": "Bilinmeyen Proje", + "projectCreatedTitle": "Proje başarıyla oluşturuldu", + "projectCreateFailedTitle": "Proje oluşturulamadı", + "projectUpdatedTitle": "Proje başarıyla güncellendi", + "projectUpdateFailedTitle": "Proje güncellenemedi", + "projectDeletedTitle": "Proje başarıyla silindi", + "projectDeleteFailedTitle": "Proje silinemedi" + } + }, + "Notes": { + "page": { + "loading": "Yükleniyor..." + }, + "layout": { + "mobileNavTitle": "Notlar Gezintisi", + "openMenuAria": "Not menüsünü aç" + }, + "sidebar": { + "title": "Notlar", + "createNoteAria": "Not oluştur", + "searchPlaceholder": "Not ara...", + "loading": "Yükleniyor...", + "noNotesFound": "Not bulunamadı.", + "noNotesYet": "Henüz not yok. Oluşturmak için + tıklayın.", + "inParent": "{parentTitle} içinde", + "untitled": "Başlıksız", + "addSubPage": "Alt sayfa ekle", + "delete": "Sil", + "deleteConfirmTitle": "Not silinsin mi?", + "deleteConfirmDescription": "\"{title}\" notunu silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "cancel": "İptal", + "confirmDelete": "Sil", + "noteOptionsAria": "Not seçenekleri" + }, + "editor": { + "emptyState": "Başlamak için bir not seçin veya yeni bir not oluşturun.", + "titlePlaceholder": "Başlıksız", + "saving": "Kaydediliyor…", + "ready": "Hazır", + "saved": "{time} kaydedildi", + "stats": "{words} kelime · {read} dk okuma", + "template": "Şablon", + "applyTemplate": "Şablonu uygula", + "exportMdLabel": ".md", + "exportMd": "Markdown olarak dışa aktar", + "exportHtmlLabel": ".html", + "exportHtml": "HTML olarak dışa aktar", + "focusMode": "Odak modu", + "exitFocusMode": "Odak modundan çık", + "save": "Kaydet", + "addTag": "Etiket ekle…", + "removeTag": "{tag} etiketini kaldır", + "loading": "Yükleniyor…", + "searchHint": "aramak için", + "focusHint": "odak modu", + "notePath": "Not yolu" + }, + "context": { + "authRequiredError": "Kullanıcı kimliği doğrulanmadı", + "defaultTitle": "Başlıksız", + "cloudSyncImageError": "Not görsellerini depolamak için Bulut Eşitlemeyi açın. Metin notları yerel kalır." + } + }, + "PasswordManager": { + "page": { + "loading": "Yükleniyor...", + "title": "Şifre Yöneticisi", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Şifrelerin çözümü başarısız", + "toastLoadFailed": "Şifreler yüklenemedi", + "toastUnlockedAuto": "Kasa otomatik olarak açıldı", + "toastAutoUnlockFailed": "Otomatik kilit açma başarısız. Lütfen manuel olarak açın.", + "toastConnectFailed": "Sunucuya bağlanılamadı", + "toastAutoUnlockWarning": "Kasa açıldı, ancak yeniden yüklemede otomatik kilit açma çalışmayabilir", + "errorIncorrectPassword": "Yanlış ana şifre", + "errorUnlockFailed": "Kasa kilidi açılamadı", + "errorPasswordsMismatch": "Şifreler eşleşmiyor", + "errorPasswordTooShort": "Şifre en az 8 karakter olmalıdır", + "errorSetupFailed": "Kasa kurulumu başarısız", + "errorConnectFailed": "Sunucuya bağlanılamadı.", + "mobileTitleCreate": "Kasanızı oluşturun", + "mobileTitleWelcome": "Tekrar hoş geldiniz", + "mobileSubtitleCreate": "Kimlik bilgilerinizi korumak için bir ana şifre belirleyin", + "mobileSubtitleUnlock": "Kilidi açmak için ana şifrenizi girin", + "placeholderMasterPassword": "Ana şifre", + "placeholderConfirmPassword": "Şifreyi onayla", + "labelMasterPassword": "Ana şifre", + "labelConfirmPassword": "Şifreyi onayla", + "placeholderEnterMaster": "Ana şifreyi girin", + "placeholderConfirmMaster": "Ana şifreyi onaylayın", + "buttonCreateVault": "Kasa oluştur", + "buttonUnlock": "Kilidi Aç", + "buttonUnlockVault": "Kasayı aç", + "footerCreate": "Şifreniz asla saklanmaz ve kurtarılamaz", + "footerUnlock": "Uçtan uca şifreleme ile güvende", + "desktopTitleSetup": "Şifre kasasını kur", + "desktopTitleUnlock": "Kasayı aç", + "desktopDescriptionSetup": "Kimlik bilgilerinizi güvence altına almak için bir ana şifre oluşturun. Bu şifre asla saklanmaz ve kurtarılamaz.", + "desktopDescriptionUnlock": "Güvenli kimlik bilgilerinize erişmek için ana şifrenizi girin." + }, + "list": { + "loading": "Şifreleriniz yükleniyor...", + "emptyTitle": "Kasa boş", + "emptyHintMobile": "İlk şifrenizi eklemek için + düğmesine dokunun", + "emptyHintDesktop": "Henüz kayıtlı şifreniz yok. Başlamak için yukarıdaki \"Şifre ekle\" düğmesine tıklayın.", + "addPassword": "Şifre ekle", + "searchPlaceholderMobile": "Kasanızda arayın...", + "searchPlaceholderDesktop": "Kasada ara...", + "importExport": "İçe / dışa aktar", + "lockVault": "Kasayı kilitle", + "allPasswordsTitle": "Tüm şifreler", + "passwordCount": "{count, plural, one {# şifre} other {# şifre}}", + "vaultHealthDrawerTitle": "Kasa sağlığı", + "noResults": "\"{query}\" ile eşleşen şifre bulunamadı", + "tableService": "Hizmet", + "tableUsername": "Kullanıcı Adı", + "tablePassword": "Şifre", + "tableUrl": "URL", + "tableActions": "İşlemler", + "gridViewAria": "Izgara görünümü", + "listViewAria": "Liste görünümü", + "passwordStrengthTitle": "Şifre gücü", + "deleteDialogTitle": "Şifreyi sil", + "deleteDialogDescription": "Bu şifreyi silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "cancel": "İptal", + "delete": "Sil", + "edit": "Düzenle" + }, + "toasts": { + "copiedPassword": "Şifre panoya kopyalandı", + "copiedUsername": "Kullanıcı adı panoya kopyalandı", + "passwordDeleted": "Şifre silindi", + "deleteFailed": "Şifre silinemedi", + "vaultLocked": "Kasa kilitlendi", + "importedCount": "{count, plural, one {# şifre} other {# şifre}} başarıyla içe aktarıldı" + }, + "form": { + "addTitle": "Yeni şifre ekle", + "editTitle": "Şifreyi düzenle", + "serviceName": "Hizmet adı", + "usernameEmail": "Kullanıcı adı / e-posta", + "password": "Şifre", + "urlOptional": "Web sitesi URL'si (isteğe bağlı)", + "notesOptional": "Notlar (isteğe bağlı)", + "tags": "Etiketler", + "placeholderService": "örn. Netflix", + "placeholderUsername": "kullanici@ornek.com", + "placeholderPassword": "Şifre girin veya oluşturun", + "placeholderUrl": "https://...", + "placeholderNotes": "Ek detaylar...", + "placeholderTags": "Etiket ekle (Enter veya virgül ile)", + "strength": "Güç", + "strengthWeak": "Zayıf", + "strengthMedium": "Orta", + "strengthStrong": "Güçlü", + "advancedGenerator": "Gelişmiş oluşturucu", + "cancel": "İptal", + "saving": "Kaydediliyor...", + "savePassword": "Şifreyi kaydet", + "updating": "Güncelleniyor...", + "updatePassword": "Şifreyi güncelle", + "addPasswordSrOnly": "Şifre ekle", + "triggerAdd": "Şifre ekle", + "toastSaved": "Şifre başarıyla kaydedildi", + "toastSaveFailed": "Şifre kaydedilemedi", + "toastUpdated": "Şifre başarıyla güncellendi", + "toastUpdateFailed": "Şifre güncellenemedi" + }, + "security": { + "vaultHealth": "Kasa sağlığı", + "statusLabel": "Durum", + "totalPasswords": "Toplam şifre", + "activeEntries": "Kasanızdaki aktif kayıtlar", + "protected": "Korumalı", + "weakPasswords": "Zayıf şifreler", + "needingImprovement": "İyileştirilmesi gereken şifreler", + "allGood": "Hepsi iyi", + "reusedPasswords": "Tekrarlanan şifreler", + "usedMultipleTimes": "Birden fazla kullanılan şifreler", + "unique": "Benzersiz", + "total": "Toplam", + "weak": "Zayıf", + "reused": "Tekrarlanan", + "totalCountLabel": "{count, plural, one {# şifre} other {# şifre}}", + "weakCountLabel": "{count} zayıf", + "allSecure": "Hepsi güvenli", + "scoreExcellent": "Mükemmel", + "scoreGood": "İyi", + "scoreFair": "Orta", + "scoreCritical": "Kritik" + }, + "importExport": { + "title": "Kasayı içe / dışa aktar", + "description": "Şifre verilerinizi yönetin. Kasanızı JSON'a dışa aktarabilir veya bir JSON dosyasından içe aktarabilirsiniz.", + "tabExport": "Dışa Aktar", + "tabImport": "İçe Aktar", + "warningTitle": "Uyarı", + "warningBody": "Dışa aktarılan dosyalar şifrelerinizi düz metin olarak içerir. Bu dosyayı güvende tutun ve kullandıktan sonra silin.", + "exportBlurb": "Tüm kasanızı JSON dosyası olarak indirin. Bu, tüm hizmetleri, kullanıcı adlarını, şifreleri ve notları içerir.", + "downloadJson": "JSON dışa aktarımını indir", + "pasteLabel": "JSON verilerini yapıştırın", + "pastePlaceholder": "[{\"service\": \"Örnek\", \"username\": \"kullanici\", \"password\": \"123\", ...}]", + "pasteHint": "Daha önce dışa aktarılmış bir JSON dosyasının içeriğini buraya yapıştırın.", + "importing": "İçe aktarılıyor...", + "importPasswords": "Şifreleri içe aktar", + "trigger": "İçe / dışa aktar", + "toastExported": "Kasa başarıyla dışa aktarıldı", + "toastExportFailed": "Kasa dışa aktarılamadı", + "toastImportFailed": "İçe aktarma başarısız: geçersiz JSON formatı" + }, + "card": { + "copyUsername": "Kullanıcı adını kopyala", + "openUrl": "URL'yi aç", + "strength": "Güç" + }, + "swipeable": { + "copy": "Kopyala", + "delete": "Sil", + "copyPassword": "Şifreyi kopyala", + "copyUsername": "Kullanıcı adını kopyala", + "edit": "Düzenle", + "deleteAction": "Sil" + }, + "floatingMenu": { + "lockVault": "Kasayı kilitle", + "importExport": "İçe / dışa aktar", + "addPassword": "Şifre ekle" + }, + "generator": { + "tabPassword": "Şifre", + "tabPassphrase": "Parola", + "strength": "Güç", + "lengthLabel": "Uzunluk: {length} karakter", + "lengthBadge": "{length} karakter", + "characterSets": "Karakter kümeleri", + "uppercase": "Büyük harf", + "lowercase": "Küçük harf", + "numbers": "Sayılar", + "symbols": "Semboller", + "wordCountLabel": "Kelime sayısı: {count}", + "wordCountBadge": "{count, plural, one {# kelime} other {# kelime}}", + "separator": "Ayırıcı", + "capitalization": "Büyük harf kullanımı", + "includeNumber": "Sayı dahil et" + } + }, + "EnvironmentManager": { + "subtitle": "Proje ve ortama göre şifrelenmiş ortam değişkenleri.", + "page": { + "loading": "Yükleniyor…", + "title": "Ortam yöneticisi", + "loadFailed": "Ortam kümeleri yüklenemedi" + }, + "list": { + "title": "Ortamlar", + "loading": "Kümeleriniz yükleniyor…", + "emptyTitle": "Henüz ortam kümesi yok", + "emptyHint": "Her proje ve ortam için bir küme oluşturun (ör. üretim API anahtarları). Yüklemeden önce her şey tarayıcınızda şifrelenir.", + "searchPlaceholder": "Proje, ortam, etiket, not ara…", + "tagFilterPlaceholder": "Etikete göre filtrele", + "variableCount": "{count, plural, one {# değişken} other {# değişken}}", + "updatedAgo": "Güncellendi {time}", + "copyDotEnv": ".env kopyala", + "edit": "Düzenle", + "delete": "Sil", + "cancel": "İptal", + "confirmDelete": "Sil", + "deleteDialogTitle": "Ortam kümesi silinsin mi?", + "deleteDialogDescription": "Şifreli veri hesabınızdan kaldırılır. Geri alınamaz.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env panoya kopyalandı", + "deleted": "Ortam kümesi silindi", + "deleteFailed": "Silinemedi" + }, + "form": { + "addTitle": "Yeni ortam kümesi", + "editTitle": "Ortam kümesini düzenle", + "triggerAdd": "Ortam kümesi ekle", + "addSrOnly": "Ortam kümesi ekle", + "project": "Proje", + "environment": "Ortam", + "placeholderProject": "ör. billing-api", + "placeholderEnvironment": "ör. production", + "pasteEnvLabel": ".env yapıştır", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Yapıştırılan satırları birleştir", + "variables": "Değişkenler", + "addRow": "Satır ekle", + "keyPlaceholder": "ANAHTAR", + "valuePlaceholder": "değer", + "notesOptional": "Notlar (isteğe bağlı)", + "placeholderNotes": "Rotasyon politikası, sahip, bağlantılar…", + "tags": "Etiketler", + "placeholderTags": "Etiket ekle (Enter veya virgül)", + "cancel": "İptal", + "save": "Kaydet", + "saving": "Kaydediliyor…", + "update": "Güncelle", + "updating": "Güncelleniyor…", + "toastSaved": "Ortam kümesi kaydedildi", + "toastSaveFailed": "Kaydedilemedi", + "toastUpdated": "Ortam kümesi güncellendi", + "toastUpdateFailed": "Güncellenemedi", + "toastPasteEmpty": "ANAHTAR=değer satırı bulunamadı", + "toastPasteApplied": "Yapıştırmadan {count} değişken birleştirildi", + "toastProjectEnvRequired": "Proje ve ortam zorunludur", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Yükleniyor..." + }, + "manager": { + "foldersHeading": "Klasörler", + "tagsHeading": "Etiketler", + "tagsMore": "+{count} daha", + "tagsShowLess": "Show less", + "searchPlaceholder": "Yer imlerinizde arayın...", + "import": "İçe Aktar", + "export": "Dışa Aktar", + "exportAsHtml": "HTML olarak dışa aktar", + "exportAsJson": "JSON olarak dışa aktar", + "addBookmark": "Yer İmi Ekle", + "matchingQuery": "\"{query}\" ile eşleşen", + "bookmarkCount": "{count, plural, one {# yer imi} other {# yer imi}}", + "allBookmarks": "Tüm Yer İmleri", + "uncategorized": "Kategorisiz", + "unknownFolder": "Bilinmeyen" + }, + "card": { + "urlCopied": "URL panoya kopyalandı", + "bookmarkDeleted": "Yer imi silindi", + "edit": "Düzenle", + "delete": "Sil", + "copyUrl": "URL Kopyala", + "deleteConfirmTitle": "Yer imi silinsin mi?", + "deleteConfirmDescription": "\"{title}\" kalıcı olarak silinecektir.", + "cancel": "İptal" + }, + "grid": { + "emptyTitle": "Henüz yer imi yok", + "emptyDescription": "Başlamak için ilk yer iminizi ekleyin veya tarayıcınızdan içe aktarın." + }, + "folderTree": { + "allBookmarks": "Tüm Yer İmleri", + "addSubfolder": "Alt Klasör Ekle", + "rename": "Yeniden Adlandır", + "deleteFolderTitle": "\"{name}\" klasörü silinsin mi?", + "deleteFolderDescription": "Bu, klasörü ve tüm alt klasörlerini silecektir. Bu klasörlerdeki yer imleri Kategorisiz'e taşınacaktır." + }, + "import": { + "title": "Yer İmlerini İçe Aktar", + "description": "Tarayıcınızın dışa aktarılmış dosyasından yer imlerini içe aktarın.", + "parseError": "Yer imi dosyası ayrıştırılamadı. Lütfen geçerli bir yer imi dışa aktarımı olduğundan emin olun.", + "successToast": "{bookmarks} yer imi ve {folders} klasör içe aktarıldı", + "errorToast": "Yer imleri içe aktarılamadı", + "chromeInstruction": "Menü → Yer İmleri → Yer İmi Yöneticisi → ⋮ → Yer İmlerini Dışa Aktar", + "firefoxInstruction": "Menü → Yer İmleri → Yer İmlerini Yönet → İçe ve Dışa Aktarma → Dışa Aktar", + "safariInstruction": "Dosya → Yer İmlerini Dışa Aktar...", + "edgeInstruction": "Menü → Sık Kullanılanlar → ⋯ → Sık Kullanılanları Dışa Aktar", + "dropZoneTitle": "Yer imi dosyanızı buraya bırakın", + "dropZoneHint": "veya göz atmak için tıklayın (HTML veya JSON)", + "selectedFileHint": "Farklı bir dosya seçmek için tıklayın", + "errorTitle": "Hata", + "previewTitle": "İçe Aktarma Önizlemesi", + "previewBookmarks": "{count, plural, one {# yer imi} other {# yer imi}}", + "previewFolders": "{count, plural, one {# klasör} other {# klasör}}", + "importModeLabel": "İçe Aktarma Modu", + "modeMerge": "Mevcut yer imleriyle birleştir", + "modeReplace": "Tüm mevcut yer imlerini değiştir", + "cancel": "İptal", + "importButton": "İçe Aktar", + "importButtonWithCount": "{count, plural, one {# yer imini} other {# yer imini}} içe aktar" + }, + "addBookmark": { + "titleAdd": "Yer İmi Ekle", + "titleEdit": "Yer İmini Düzenle", + "urlLabel": "URL", + "urlPlaceholder": "https://ornek.com", + "urlInvalid": "Lütfen geçerli bir URL girin", + "titleLabel": "Başlık", + "titlePlaceholder": "Yer imi başlığı", + "titleRequired": "Lütfen bir başlık girin", + "descriptionLabel": "Açıklama (isteğe bağlı)", + "descriptionPlaceholder": "Bu yer imi hakkında bir not ekleyin", + "folderLabel": "Klasör", + "folderPlaceholder": "Klasör seç", + "noFolder": "Klasör yok", + "tagsLabel": "Etiketler", + "tagsPlaceholder": "Etiket ekle (Enter'a basın)", + "toastUpdated": "Yer imi güncellendi", + "toastAdded": "Yer imi eklendi", + "cancel": "İptal", + "submitAdd": "Yer İmi Ekle", + "submitUpdate": "Yer İmini Güncelle" + }, + "addFolder": { + "title": "Klasör Oluştur", + "nameLabel": "Klasör Adı", + "namePlaceholder": "Klasörüm", + "nameRequired": "Lütfen bir klasör adı girin", + "parentLabel": "Üst Klasör", + "parentPlaceholder": "Üst klasör seç", + "rootParent": "Kök (Üst klasör yok)", + "toastCreated": "Klasör oluşturuldu", + "cancel": "İptal", + "submit": "Klasör Oluştur" + }, + "editFolder": { + "title": "Klasörü Yeniden Adlandır", + "nameLabel": "Klasör Adı", + "namePlaceholder": "Klasör adı", + "nameRequired": "Lütfen bir klasör adı girin", + "toastRenamed": "Klasör yeniden adlandırıldı", + "cancel": "İptal", + "save": "Kaydet" + } + }, + "EmailValidator": { + "header": { + "title": "E-posta Doğrulayıcı", + "description": "E-posta adreslerini söz dizimi, alan adı ve teslim edilebilirlik açısından doğrulayın." + }, + "tabs": { + "single": "Tekli Doğrulama", + "bulk": "Toplu Yükleme" + }, + "single": { + "cardTitle": "Tekli E-posta Doğrulama", + "cardDescription": "Teslim edilebilirliğini ve itibarını kontrol etmek için bir e-posta adresi girin.", + "placeholder": "E-posta adresi girin...", + "clearAria": "E-postayı temizle", + "validate": "Doğrula", + "validating": "Doğrulanıyor...", + "formatError": "Lütfen geçerli bir e-posta adresi girin" + }, + "toasts": { + "errorTitle": "Hata", + "enterEmail": "Lütfen bir e-posta adresi girin", + "invalidFormatTitle": "Geçersiz E-posta Formatı", + "invalidFormatDescription": "Lütfen geçerli bir e-posta adresi girin", + "validateFailedDescription": "E-posta doğrulanırken bir hata oluştu", + "noEmailsTitle": "E-posta Bulunamadı", + "noEmailsDescription": "Lütfen Excel dosyanızda bir 'email' sütunu olduğundan emin olun.", + "fileTooLargeTitle": "Dosya Çok Büyük", + "fileTooLargeDescription": "{total} e-posta bulundu. Yalnızca ilk 4000 tanesi doğrulanacaktır.", + "successTitle": "Başarılı", + "successValidated": "{count} e-posta başarıyla doğrulandı.", + "batchFailedTitle": "Toplu Doğrulama Başarısız", + "batchFailedDescription": "Toplu doğrulama işlenirken bir hata oluştu.", + "noExportTitle": "Dışa Aktarılacak Veri Yok", + "noExportValidOnly": "Dışa aktarılacak geçerli e-posta bulunamadı.", + "noExportAll": "Dışa aktarılacak sonuç yok.", + "exportSuccessTitle": "Dışa Aktarma Başarılı", + "exportSuccessExcelAll": "{count} e-posta Excel'e aktarıldı.", + "exportSuccessExcelValid": "{count} geçerli e-posta Excel'e aktarıldı.", + "exportSuccessCsvAll": "{count} e-posta CSV'ye aktarıldı.", + "exportSuccessCsvValid": "{count} geçerli e-posta CSV'ye aktarıldı." + }, + "disposableAlert": { + "title": "Tek Kullanımlık E-posta Tespit Edildi", + "description": "Bu e-posta adresi tek kullanımlık bir e-posta sağlayıcısına aittir. Geçici veya güvensiz olabilir." + }, + "score": { + "reliabilityTitle": "Güvenilirlik Puanı", + "scoreLabel": "Puan" + }, + "details": { + "sectionTitle": "Doğrulama Detayları" + }, + "validation": { + "syntaxLabel": "Söz Dizimi Geçerli", + "syntaxTooltip": "E-posta adresinin doğru formata uygun olup olmadığını kontrol eder (örn. kullanici@alanadi.com)", + "domainLabel": "Alan Adı Mevcut", + "domainTooltip": "E-posta adresindeki alan adının gerçekten var olduğunu doğrular", + "mxLabel": "MX Kayıtları Bulundu", + "mxTooltip": "Alan adının e-posta almak için yapılandırılmış posta değişim (MX) kayıtlarının olup olmadığını kontrol eder", + "mailboxLabel": "Posta Kutusu Mevcut", + "mailboxTooltip": "Belirli posta kutusunun/hesabın posta sunucusunda var olduğunu doğrular", + "disposableLabel": "Tek Kullanımlık E-posta", + "disposableTooltip": "Bu e-postanın geçici/tek kullanımlık bir e-posta hizmetine ait olup olmadığını gösterir (riskli olabilir)", + "roleBasedLabel": "Rol Tabanlı E-posta", + "roleBasedTooltip": "Bunun kişisel bir adres yerine rol tabanlı bir e-posta olup olmadığını belirler (örn. destek@, bilgi@)" + }, + "copy": { + "emailCopied": "E-posta kopyalandı!", + "copyTooltip": "E-postayı kopyala" + }, + "bulk": { + "dropHere": "Dosyanızı buraya bırakın", + "dragHere": "Dosyanızı sürükleyip bırakın", + "uploadHint": "4000'e kadar e-postayı doğrulamak için 'email' sütunu içeren bir .xlsx, .xls veya .csv dosyası yükleyin.", + "filePrefix": "Dosya:", + "selectFile": "Dosya Seç", + "downloadTemplate": "Şablonu İndir" + }, + "bulkLoading": { + "title": "E-postalar Doğrulanıyor...", + "batchProgress": "{total} partiden {current}. parti işleniyor..." + }, + "stats": { + "totalProcessed": "Toplam İşlenen", + "valid": "Geçerli", + "risky": "Riskli", + "invalid": "Geçersiz" + }, + "results": { + "detailedTitle": "Detaylı Sonuçlar", + "searchPlaceholder": "E-posta ara...", + "newVerification": "Yeni Doğrulama", + "exportAll": "Tümünü Dışa Aktar", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Geçerli Olanları Dışa Aktar ({count})" + }, + "showingResults": "{total} sonuçtan {shown} tanesi gösteriliyor", + "table": { + "email": "E-posta", + "status": "Durum", + "score": "Puan", + "checks": "Kontroller" + }, + "noSearchResults": "\"{query}\" ile eşleşen sonuç bulunamadı", + "checksTooltip": { + "syntax": "Söz Dizimi", + "domain": "Alan Adı", + "mx": "MX", + "mailbox": "Posta Kutusu" + }, + "exportColumns": { + "email": "E-posta", + "status": "Durum", + "score": "Puan", + "syntax": "Söz Dizimi", + "domain": "Alan Adı", + "mx": "MX", + "mailbox": "Posta Kutusu", + "disposable": "Tek Kullanımlık", + "roleBased": "Rol Tabanlı", + "valid": "Geçerli", + "invalid": "Geçersiz", + "yes": "Evet", + "no": "Hayır" + }, + "sheets": { + "validEmails": "Geçerli E-postalar", + "results": "Sonuçlar", + "template": "Şablon" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Gezgini", + "heroSubtitle": "Koleksiyonları yönetmek, sorguları çalıştırmak ve verilerinizi görselleştirmek için veritabanlarınıza bağlanın.", + "featureMultiTitle": "Çoklu Bağlantı", + "featureMultiDesc": "Birden fazla veritabanı bağlantısını aynı anda yönetin.", + "featureMongoTitle": "MongoDB Desteği", + "featureMongoDesc": "Gelişmiş filtreleme ile yerel MongoDB desteği.", + "featureQueryTitle": "Akıllı Sorgu", + "featureQueryDesc": "Söz dizimi vurgulama ile güçlü sorgu oluşturucu.", + "connectCta": "MongoDB'ye Bağlan", + "footerNote": "Şu anda {db} desteklenmektedir. Daha fazla veritabanı yakında.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Bir Koleksiyon Seçin", + "selectCollectionDesc": "Belgeleri görüntülemek ve verilerinizi keşfetmeye başlamak için kenar çubuğundan bir koleksiyon seçin.", + "dialogAddConnection": "Bağlantı Ekle", + "toastConnectionAdded": "Bağlantı eklendi", + "toastFetchFailed": "{collection} için belgeler getirilemedi", + "toastDocDeleted": "Belge silindi", + "deleteDocTitle": "Kesinlikle emin misiniz?", + "deleteDocDescription": "Bu işlem geri alınamaz. Bu, belgeyi koleksiyonunuzdan kalıcı olarak silecektir.", + "closeAllTitle": "Tüm sekmeler kapatılsın mı?", + "closeAllDescription": "Tüm sekmeleri kapatmak istediğinizden emin misiniz? Sorgu filtrelerindeki kaydedilmemiş değişiklikler kaybolacaktır.", + "closeAllConfirm": "Tümünü Kapat", + "cancel": "İptal", + "delete": "Sil" + }, + "connection": { + "titleEdit": "Bağlantıyı Düzenle", + "titleConnect": "MongoDB'ye Bağlan", + "subtitleEdit": "Bağlantı detaylarınızı aşağıda güncelleyin.", + "subtitleConnect": "Verilerinizi keşfetmeye başlamak için MongoDB bağlantı dizesini girin.", + "labelName": "Ad", + "placeholderName": "örn. Üretim Veritabanı", + "labelConnectionString": "Bağlantı Dizesi", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Format: mongodb://kullaniciadi:sifre@sunucu:port/veritabani", + "testConnection": "Bağlantıyı Test Et", + "connecting": "Bağlanıyor...", + "updateConnect": "Güncelle ve Bağlan", + "connect": "Bağlan", + "cancelEdit": "Düzenlemeyi İptal Et", + "savedConnections": "Kayıtlı Bağlantılar", + "emptySaved": "Henüz kayıtlı bağlantı yok.", + "lastUsed": "Son kullanım: {time}", + "toastTestOk": "Bağlantı başarılı!", + "toastTestFail": "Bağlantı başarısız: {message}", + "toastUpdated": "Bağlantı güncellendi", + "confirmDelete": "Bu kayıtlı bağlantıyı silmek istediğinizden emin misiniz?", + "toastDeletedConn": "Bağlantı silindi", + "toastDeleteFail": "Bağlantı silinemedi", + "copyStringTitle": "Bağlantı dizesini kopyala", + "toastStringCopied": "Bağlantı dizesi kopyalandı!", + "editTitle": "Düzenle", + "deleteTitle": "Sil" + }, + "sidebar": { + "explorer": "Gezgin", + "add": "Ekle", + "tooltipAdd": "Yeni bağlantı ekle", + "tooltipRefresh": "Bağlantıları yenile", + "searchPlaceholder": "Ara...", + "loadingConnections": "Bağlantılar yükleniyor...", + "noConnectionsFound": "Bağlantı bulunamadı", + "toastLoadFail": "Bağlantılar yüklenemedi", + "toastConnectFail": "{name} bağlantısı başarısız", + "toastCollectionsFail": "{name} için koleksiyonlar getirilemedi", + "toastRenamed": "Bağlantı yeniden adlandırıldı", + "toastRenameFail": "Bağlantı yeniden adlandırılamadı", + "confirmDeleteConnection": "\"{name}\" bağlantısını silmek istediğinizden emin misiniz?", + "toastDeleted": "Bağlantı silindi", + "toastDeleteConnFail": "Bağlantı silinemedi", + "confirmDropDb": "\"{name}\" veritabanını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "toastDbDropped": "{name} veritabanı silindi", + "confirmDropCollection": "\"{name}\" koleksiyonunu silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "toastCollectionDropped": "{name} koleksiyonu silindi", + "toastCollectionRenamed": "Koleksiyon {name} olarak yeniden adlandırıldı", + "toastDatabaseRenamed": "Veritabanı {name} olarak yeniden adlandırıldı", + "connecting": "Bağlanıyor...", + "connectionFailed": "Bağlantı başarısız", + "noDatabases": "Veritabanı yok", + "loading": "Yükleniyor...", + "noCollections": "Koleksiyon yok", + "renameCollectionTitle": "Koleksiyonu Yeniden Adlandır", + "renameDatabaseTitle": "Veritabanını Yeniden Adlandır", + "placeholderNewCollection": "Yeni koleksiyon adı", + "placeholderNewDatabase": "Yeni veritabanı adı", + "rename": "Yeniden Adlandır", + "refresh": "Yenile", + "copyDbName": "Veritabanı Adını Kopyala", + "toastDbNameCopied": "Veritabanı adı panoya kopyalandı", + "dropDatabase": "Veritabanını Sil", + "dropCollection": "Koleksiyonu Sil", + "menuRename": "Yeniden Adlandır", + "menuCopyConnectionString": "Bağlantı Dizesini Kopyala", + "menuDeleteConnection": "Bağlantıyı Sil", + "toastSidebarStringCopied": "Bağlantı dizesi kopyalandı!", + "cancel": "İptal", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} belge", + "table": "Tablo", + "json": "JSON", + "tree": "Ağaç", + "tooltipTable": "Tablo Görünümü", + "tooltipJson": "JSON Görünümü", + "tooltipTree": "Ağaç Görünümü", + "itemsPerPage": "Sayfa başına öğe", + "perPage": "{n} / sayfa", + "prevPage": "Önceki Sayfa", + "nextPage": "Sonraki Sayfa", + "expandAll": "Tümünü Genişlet", + "collapseAll": "Tümünü Daralt", + "refreshData": "Verileri Yenile", + "insert": "Ekle", + "export": "Dışa Aktar", + "actions": "İşlemler", + "invalidJson": "Geçersiz JSON içeriği", + "copiedClipboard": "Panoya kopyalandı", + "docUpdated": "Belge başarıyla güncellendi", + "docInserted": "Belge başarıyla eklendi", + "invalidJsonShort": "Geçersiz JSON", + "docCopied": "Belge panoya kopyalandı", + "openFullEditor": "Tam editörde aç", + "cellArray": "Dizi({length})", + "cellObject": "Nesne", + "cellArrayBracket": "Dizi[{length}]", + "emptyFilterTitle": "Sorgunuzla eşleşen belge yok", + "emptyFilterHint": "Filtrenizi ayarlamayı veya sorguyu temizlemeyi deneyin.", + "clearFilter": "Filtreyi Temizle", + "emptyCollectionTitle": "Bu koleksiyon boş", + "emptyCollectionHint": "Başlamak için bir belge ekleyin.", + "insertDocument": "Belge Ekle", + "formatJson": "JSON Biçimlendir", + "compactJson": "Sıkıştırılmış JSON", + "copyClipboard": "Panoya Kopyala", + "copyJson": "JSON Kopyala", + "duplicate": "Çoğalt", + "editDoc": "Belgeyi Düzenle", + "deleteDoc": "Belgeyi Sil", + "documentLabel": "Belge {n}", + "editDialogTitle": "Belgeyi Düzenle", + "insertDialogTitle": "Belge Ekle", + "viewValueTitle": "Değeri Görüntüle", + "saveChanges": "Değişiklikleri Kaydet", + "close": "Kapat", + "cancel": "İptal", + "insertButton": "Ekle", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Aktif filtre uygulandı", + "filterActiveTooltip": "Filtre aktif", + "closeAll": "Tümünü Kapat" + }, + "query": { + "migrateHistory": "Sorgu geçmişi buluta taşındı", + "saveHistoryFail": "Sorgu geçmişi kaydedilemedi", + "deleteQueryFail": "Sorgu silinemedi", + "invalidJsonQuery": "Geçersiz JSON sorgusu", + "placeholder": "örn. { \"status\": \"active\" }", + "clearQuery": "Sorguyu temizle", + "openAdvanced": "Gelişmiş Editörü Aç", + "visualBuilder": "Görsel Sorgu Oluşturucu", + "filterRules": "Filtre Kuralları", + "addRule": "Kural Ekle", + "noFilters": "Filtre uygulanmadı. Belgeleri filtrelemek için bir kural ekleyin.", + "where": "NEREDE", + "and": "VE", + "fieldPlaceholder": "Alan", + "opPlaceholder": "Op", + "typePlaceholder": "Tür", + "opRegex": "Regex", + "opIn": "İçinde", + "opNin": "İçinde Değil", + "opExists": "Mevcut", + "typeAuto": "Otomatik", + "typeString": "Metin", + "typeNumber": "Sayı", + "typeBoolean": "Mantıksal", + "typeDate": "Tarih", + "typeObjectId": "ObjectId", + "true": "Doğru", + "false": "Yanlış", + "valuePlaceholder": "Değer", + "valueListPlaceholder": "değer1, değer2", + "clearFilters": "Filtreleri Temizle", + "applyFilters": "Filtreleri Uygula", + "queryHistory": "Sorgu Geçmişi", + "noSavedQueries": "Henüz kayıtlı sorgu yok", + "advancedEditorTitle": "Gelişmiş Sorgu Editörü", + "templatesPlaceholder": "Şablonlar", + "templateEmptyAgg": "Boş Toplama", + "templateMatch": "$match (Filtre)", + "templateLookup": "$lookup (Birleştirme)", + "templateGroup": "$group (Sayma/Toplam)", + "format": "Biçimlendir", + "formattedJson": "Biçimlendirilmiş JSON", + "runQuery": "Sorguyu Çalıştır" + }, + "export": { + "selectFieldsError": "Lütfen dışa aktarmak için en az bir alan seçin", + "success": "{filename} dosyasına başarıyla dışa aktarıldı", + "fail": "Veri dışa aktarılamadı", + "title": "Veri Dışa Aktar", + "formatLabel": "Dışa Aktarma Formatı", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Alanları Seç", + "selectAll": "Tümünü Seç", + "fieldsSelected": "{count, plural, one {# alan seçildi} other {# alan seçildi}}", + "cancel": "İptal", + "export": "Dışa Aktar", + "sheetName": "Veri" + }, + "jsonTree": { + "typeLabel": "Tür: {type}", + "itemSummary": "{count, plural, one {# öğe} other {# öğe}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Yeni İstek", + "importedRequest": "İçe Aktarılan İstek", + "untitledRequest": "Başlıksız İstek" + }, + "toasts": { + "curlImported": "cURL başarıyla içe aktarıldı", + "curlParseFailed": "cURL komutu ayrıştırılamadı", + "invalidJsonBody": "Geçersiz JSON gövdesi", + "requestFailed": "İstek başarısız: {message}", + "curlPasted": "cURL yapıştırıldı ve başarıyla ayrıştırıldı", + "responseCopied": "Yanıt panoya kopyalandı", + "codeCopied": "Kod panoya kopyalandı", + "copyFailed": "Panoya kopyalanamadı" + }, + "layout": { + "collections": "Koleksiyonlar", + "closeSidebar": "Kenar çubuğunu kapat", + "openSidebar": "Kenar çubuğunu aç", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Yöntem", + "urlPlaceholder": "https://api.ornek.com/v1/...", + "sending": "Gönderiliyor...", + "send": "Gönder", + "invalidJsonBodyHelp": "Gönderilemedi: JSON gövdesi geçersiz" + }, + "requestTabs": { + "params": "Parametreler", + "headers": "Başlıklar", + "body": "Gövde", + "auth": "Kimlik Doğrulama", + "bodyType": "Gövde türü", + "selectType": "Tür seçin", + "bodyNone": "Yok", + "bodyJson": "JSON", + "bodyText": "Metin", + "noBodyHint": "Bu istek için gövde içeriği yok", + "switchToJson": "JSON gövdesine geç", + "authType": "Kimlik doğrulama türü", + "noAuth": "Kimlik doğrulama yok", + "bearerToken": "Bearer token", + "basicAuth": "Temel kimlik doğrulama", + "apiKey": "API anahtarı", + "labelToken": "Token", + "placeholderBearerToken": "Bearer token", + "labelUsername": "Kullanıcı Adı", + "placeholderUsername": "Kullanıcı Adı", + "labelPassword": "Şifre", + "placeholderPassword": "Şifre", + "labelKey": "Anahtar", + "placeholderApiKeyName": "Anahtar adı (örn. X-Api-Key)", + "labelValue": "Değer", + "placeholderValue": "Değer", + "addTo": "Ekle", + "header": "Başlık", + "queryParams": "Sorgu parametreleri", + "noAuthHint": "Bu istek kimlik doğrulama kullanmıyor." + }, + "responsePanel": { + "emptyTitle": "Keşfetmeye hazır mısınız?", + "emptyHint": "Yanıt verilerini burada görmek için bir URL girin ve Gönder'e tıklayın.", + "responsePreviewAlt": "Yanıt önizlemesi", + "previewUnavailable": "Bu içerik türü için önizleme kullanılamıyor", + "status": "Durum", + "time": "Süre", + "size": "Boyut", + "copyBody": "Gövdeyi kopyala", + "downloadResponse": "Yanıtı indir", + "bodyTab": "Gövde", + "previewTab": "Önizleme", + "headersTab": "Başlıklar", + "binaryRawView": "İkili veriler ham görünümde gösterilemiyor.", + "errorStatusLabel": "Hata", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Yeni sekme" + }, + "collectionsSidebar": { + "title": "API kaynakları", + "newCollection": "Yeni koleksiyon", + "tabCollections": "Koleksiyonlar", + "tabHistory": "Geçmiş", + "noCollectionsTitle": "Koleksiyon yok", + "noCollectionsHint": "API isteklerinizi düzenlemek için bir koleksiyon oluşturun.", + "createCollection": "Koleksiyon oluştur", + "newFolder": "Yeni klasör", + "rename": "Yeniden Adlandır", + "delete": "Sil", + "emptyCollection": "Boş koleksiyon", + "recentRequests": "Son istekler", + "clearAll": "Tümünü temizle", + "noHistoryTitle": "Henüz geçmiş yok", + "noHistoryHint": "Burada görmek için bir istek gönderin.", + "dialogNewFolderTitle": "Yeni klasör", + "dialogNewFolderDescription": "İsteklerinizi düzenlemek için yeni bir klasör oluşturun.", + "labelFolderName": "Klasör adı", + "placeholderFolderName": "Klasörüm", + "createFolder": "Klasör oluştur", + "dialogNewCollectionTitle": "Yeni koleksiyon", + "dialogNewCollectionDescription": "Yeni bir üst düzey koleksiyon oluşturun.", + "labelCollectionName": "Koleksiyon adı", + "placeholderCollectionName": "Koleksiyonum", + "dialogRenameCollectionTitle": "Koleksiyonu yeniden adlandır", + "dialogRenameCollectionDescription": "Koleksiyon için yeni bir ad girin.", + "placeholderRenameCollection": "Koleksiyon adı", + "renameAction": "Yeniden Adlandır", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Yeni klasör", + "delete": "Sil", + "emptyFolder": "Boş klasör" + }, + "environmentManager": { + "noEnvironment": "Ortam yok", + "manageDialogTitle": "Ortamları yönet", + "placeholderNewEnv": "Yeni ortam", + "variablesHeading": "{name} değişkenleri", + "addVariable": "Değişken ekle", + "columnVariable": "Değişken", + "columnValue": "Değer", + "placeholderKey": "Anahtar", + "placeholderValue": "Değer", + "selectEnvToEdit": "Düzenlemek için bir ortam seçin" + }, + "codeGenerator": { + "triggerTitle": "Kod oluştur", + "dialogTitle": "Kod oluştur", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Kopyala", + "errorGenerating": "Kod oluşturma hatası" + }, + "keyValueEditor": { + "columnKey": "Anahtar", + "columnValue": "Değer", + "placeholderKey": "Anahtar", + "placeholderValue": "Değer", + "addItem": "Öğe ekle" + }, + "importCurl": { + "trigger": "cURL İçe Aktar", + "dialogTitle": "cURL İçe Aktar", + "dialogDescription": "Yeni bir istek olarak içe aktarmak için cURL komutunuzu aşağıya yapıştırın.", + "placeholder": "curl -X POST https://api.ornek.com/data -d '...'", + "import": "İçe Aktar" + }, + "saveRequest": { + "trigger": "Kaydet", + "dialogTitle": "İsteği kaydet", + "dialogDescription": "Bu isteği bir koleksiyon klasörüne kaydedin.", + "labelName": "İstek adı", + "placeholderName": "İsteğim", + "labelFolder": "Klasör", + "placeholderFolder": "Bir klasör seçin", + "save": "Kaydet" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Düzenleme Modu", + "viewOnlyMode": "Yalnızca Görüntüleme Modu", + "insertComponent": "Bileşen ekle", + "addUnorderedList": "Sırasız liste ekle", + "addOrderedList": "Sıralı liste ekle", + "addTable": "Tablo ekle" + }, + "addBlock": "Blok ekle", + "media": { + "popoverTitle": "Medya ekle", + "singleImage": "Tek Görsel", + "singleImageDesc": "Bir görsel yükle", + "multipleImages": "Birden Fazla Görsel", + "multipleImagesDesc": "Görsel ızgarası yükle", + "video": "Video", + "videoDesc": "Video dosyası yükle", + "uploadingAlt": "Yükleniyor...", + "uploadFailedAlt": "Yükleme başarısız" + }, + "export": { + "fabAria": "Kodu dışa aktar", + "title": "Kodu Dışa Aktar", + "description": "Editör içeriğinizin HTML veya JSON çıktısını kopyalayın", + "preview": "Önizleme", + "html": "HTML", + "json": "JSON", + "previewOptions": "Önizleme Seçenekleri", + "enhanceSpaces": "Boşlukları İyileştir", + "livePreview": "İşlenmiş HTML'nin canlı önizlemesi", + "htmlTailwind": "Tailwind CSS sınıflarıyla HTML", + "editorJson": "Editör durumu JSON olarak", + "copyHtml": "HTML Kopyala", + "copyJson": "JSON Kopyala", + "copied": "Kopyalandı!" + }, + "template": { + "fabTitle": "Şablon Değiştir", + "title": "Bir Şablon Seçin", + "description": "Hazır düzenler ve içeriklerle hızlı başlamak için bir şablon seçin", + "catAll": "Tüm Şablonlar", + "catProductivity": "Verimlilik", + "catCreative": "Yaratıcı", + "catBusiness": "İş", + "catPersonal": "Kişisel", + "noTemplates": "Bu kategoride şablon bulunamadı", + "confirmTitle": "Mevcut İçerik Değiştirilsin mi?", + "confirmDescription": "Editörde mevcut içerik var. Bu şablonu uygulamak tüm mevcut içeriği değiştirecektir.", + "cannotUndo": "Bu işlem geri alınamaz.", + "cancel": "İptal", + "replaceContent": "İçeriği Değiştir" + }, + "command": { + "searchPlaceholder": "Komut ara...", + "noResults": "Komut bulunamadı.", + "turnInto": "Dönüştür", + "h1": { + "label": "Başlık 1", + "description": "Büyük bölüm başlığı", + "keywords": "h1,başlık,title,büyük" + }, + "h2": { + "label": "Başlık 2", + "description": "Orta bölüm başlığı", + "keywords": "h2,başlık,alt başlık" + }, + "h3": { + "label": "Başlık 3", + "description": "Küçük bölüm başlığı", + "keywords": "h3,başlık,alt başlık" + }, + "h4": { + "label": "Başlık 4", + "description": "Çok küçük bölüm başlığı", + "keywords": "h4,başlık" + }, + "h5": { + "label": "Başlık 5", + "description": "Daha küçük başlık", + "keywords": "h5,başlık" + }, + "h6": { + "label": "Başlık 6", + "description": "En küçük başlık", + "keywords": "h6,başlık" + }, + "p": { + "label": "Paragraf", + "description": "Normal metin paragrafı", + "keywords": "p,paragraf,metin,normal" + }, + "code": { + "label": "Kod Bloğu", + "description": "Kod parçacığı", + "keywords": "kod,kodblok,parçacık,pre" + }, + "blockquote": { + "label": "Alıntı", + "description": "Blok alıntı", + "keywords": "alıntı,blokalıntı,atıf" + }, + "li": { + "label": "Madde İşaretli Liste", + "description": "Madde işaretli basit liste öğesi", + "keywords": "liste,madde,sırasız,ul,li" + }, + "ol": { + "label": "Numaralı Liste", + "description": "Numaralı liste öğesi", + "keywords": "liste,numaralı,sıralı,ol,li" + }, + "img": { + "label": "Görsel", + "description": "Görsel yükle veya göm", + "keywords": "görsel,img,resim,fotoğraf,yükle" + }, + "video": { + "label": "Video", + "description": "Video yükle veya göm", + "keywords": "video,vid,film,mp4,yükle" + }, + "table": { + "label": "Tablo", + "description": "Tablo oluştur", + "keywords": "tablo,ızgara,satırlar,sütunlar,hücreler" + } + }, + "toasts": { + "tableImported": "Tablo İçe Aktarıldı", + "tableImportedDesc": "Markdown tablosu başarıyla içe aktarıldı", + "mediaPasted": "Medya yapıştırıldı", + "pasteMixed": "{imageCount} görsel ve {videoCount} video başarıyla yapıştırıldı.", + "pasteImages": "{count} görsel başarıyla yapıştırıldı.", + "pasteVideos": "{count} video başarıyla yapıştırıldı.", + "pasteFailed": "Yapıştırma başarısız", + "unexpectedError": "Beklenmeyen bir hata oluştu", + "uploadFailed": "Yükleme başarısız" + }, + "blockBackground": { + "menuItem": "Arka Plan Rengini Değiştir", + "dialogTitle": "Blok Arka Plan Rengi", + "dialogDescription": "Bu blok için bir arka plan rengi seçin", + "tabPreset": "Hazır Renkler", + "tabCustom": "Özel Renk", + "sectionHeading": "Arka Plan Renkleri", + "apply": "Uygula", + "colors": { + "none": "Yok", + "red": "Kırmızı", + "orange": "Turuncu", + "yellow": "Sarı", + "green": "Yeşil", + "blue": "Mavi", + "indigo": "Çivit", + "purple": "Mor", + "pink": "Pembe", + "teal": "Turkuaz", + "cyan": "Camgöbeği", + "gray": "Gri" + } + }, + "tableDialog": { + "title": "Tablo Oluştur", + "description": "Yeni bir tablo oluşturun veya markdown'dan içe aktarın", + "markdownCheckbox": "Markdown tablom var", + "pasteMarkdownLabel": "Markdown Tabloyu Yapıştır", + "markdownPlaceholder": "| Başlık 1 | Başlık 2 | Başlık 3 |\n|----------|----------|----------|\n| Hücre 1 | Hücre 2 | Hücre 3 |\n| Hücre 4 | Hücre 5 | Hücre 6 |", + "markdownHint": "Markdown tablonuzu yukarıya yapıştırın. Başlık ve ayırıcı satırları içermelidir.", + "rows": "Satırlar", + "columns": "Sütunlar", + "maxDimensions": "Maksimum: 20 satır × 10 sütun", + "cancel": "İptal", + "importTable": "Tabloyu İçe Aktar", + "createTable": "Tablo Oluştur" + }, + "tableParse": { + "errors": { + "minRows": "Tablo en az bir başlık satırı ve ayırıcı satırı içermelidir", + "pipeFormat": "Tablo satırları | ile başlamalı ve bitmelidir", + "headerMinCol": "Başlık satırı en az bir sütun içermelidir", + "separatorRow": "İkinci satır ayırıcı olmalıdır (örn. |---|---|)", + "rowColumnMismatch": "Satır {row}, {got} sütuna sahip, {expected} bekleniyor", + "parseFailed": "Markdown tablosu ayrıştırılamadı" + } + }, + "link": { + "editLink": "Bağlantıyı Düzenle", + "addLink": "Bağlantı Ekle", + "selectedText": "Seçili metin: \"{text}\"", + "linkUrl": "Bağlantı URL'si", + "urlPlaceholder": "https://ornek.com", + "update": "Güncelle", + "addLinkButton": "Bağlantı Ekle", + "toastApplied": "Bağlantı Uygulandı", + "toastLinkedTo": "Bağlantı: {url}", + "toastRemoved": "Bağlantı Kaldırıldı", + "toastRemovedDesc": "Bağlantı seçimden kaldırıldı" + }, + "customClass": { + "devMode": "Geliştirici Modu", + "toggleDevModeAria": "Geliştirici modunu aç/kapat", + "searchPlaceholderDev": "Sınıf ara... (örn. 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Stil ara... (örn. 'kırmızı', 'kalın', 'gölge')", + "appliesTitle": "Uygulanan: {className}", + "noMatches": "\"{query}\" ile eşleşen sınıf bulunamadı", + "toastClassReplaced": "Sınıf Değiştirildi", + "toastClassReplacedDesc": "\"{replaced}\" yerine \"{className}\" uygulandı", + "toastCustomClassApplied": "Özel Sınıf Uygulandı", + "toastAppliedClass": "Uygulanan sınıf: {className}" + }, + "insertComponents": { + "title": "Bileşen Ekle", + "description": "Belgenize eklemek için bir bileşen seçin", + "noComponents": "Kullanılabilir bileşen yok", + "categories": { + "media": "Medya", + "layout": "Düzen", + "interactive": "Etkileşimli", + "decoration": "Dekorasyon" + }, + "freeImage": { + "name": "Serbest Hareket Görseli", + "description": "Herhangi bir yere konumlandırılabilen ve serbestçe yeniden boyutlandırılabilen bir görsel ekleyin" + } + } + }, + "JsonFormatter": { + "title": "JSON biçimleyici", + "description": "JSON verilerini metin ve ağaç görünümünde biçimlendirin, doğrulayın ve düzenleyin.", + "documentNameText": "Metin belgesi {n}", + "documentNameTree": "Ağaç belgesi {n}", + "newDocument": "Yeni", + "copy": "Kopyala", + "save": "Kaydet", + "load": "Yükle", + "saving": "Kaydediliyor...", + "leftDocNameLabel": "Sol belge adı", + "rightDocNameLabel": "Sağ belge adı", + "toastCopiedText": "Metin paneli kopyalandı", + "toastCopiedTree": "Ağaç paneli kopyalandı", + "toastCopyFailed": "JSON kopyalanamadı", + "toastLoginRequired": "Belgeleri kaydetmek için giriş yapın", + "toastSaved": "Belge kaydedildi", + "toastSaveFailed": "Belge kaydedilemedi", + "format": "Biçimlendir", + "compact": "Sıkıştır", + "sortKeys": "Anahtarları sırala", + "repair": "Onar", + "query": "Sorgu", + "toastInvalidJson": "Geçersiz JSON", + "toastAlreadyValid": "JSON zaten geçerli", + "toastRepaired": "JSON onarıldı", + "toastRepairFailed": "JSON onarılamadı" + }, + "JwtDecoder": { + "title": "JWT Çözücü", + "subtitle": "Başlık ve yükü inceleyin. Süre, exp claim’ini kullanır. Sunucuya veri gönderilmez; imza doğrulanmaz.", + "tokenLabel": "Token", + "clear": "Temizle", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Kopyala", + "expiryLabel": "Süre (exp)", + "signaturePresent": "İmza var (doğrulanmadı)", + "noExp": "Payload içinde exp claim’i yok.", + "status": { + "expired": "Süresi doldu", + "valid": "Geçerli" + }, + "fields": { + "unix": "Unix", + "local": "Yerel", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Düzenlenme (iat)", + "notBefore": "Şundan önce değil (nbf)" + }, + "panels": { + "header": "Başlık", + "payload": "Yük" + }, + "errors": { + "empty": "Çözmek için bir JWT yapıştırın.", + "segments": "JWT en az başlık ve yük (iki segment) içermelidir.", + "base64": "Başlık veya yük base64url çözülemedi." + }, + "relative": { + "future": "{n} {unit} içinde", + "past": "{n} {unit} önce" + }, + "units": { + "second": "{count, plural, one {saniye} other {saniye}}", + "minute": "{count, plural, one {dakika} other {dakika}}", + "hour": "{count, plural, one {saat} other {saat}}", + "day": "{count, plural, one {gün} other {gün}}" + }, + "tabs": { + "decode": "Çöz", + "sign": "Oluşturucu / imzalayıcı" + }, + "signer": { + "algLabel": "Algoritma (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Gizli anahtar (HS*)", + "secretPlaceholder": "HMAC gizlinizi yapıştırın…", + "privateKeyLabel": "Özel anahtar PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "\"none\" algoritması imzasız bir token üretir. Güvenli değildir; yalnızca test için kullanılmalıdır.", + "sign": "İmzala", + "verify": "İmzayı doğrula", + "outputLabel": "Token (kompakt JWS)", + "outputPlaceholder": "\"İmzala\"a tıklayın…", + "copyToken": "Tokenı kopyala", + "copied": "Kopyalandı", + "verifyKeyLabel": "Doğrulama anahtarı (isteğe bağlı)", + "verifyKeyPlaceholder": "RS* için: açık anahtarı (SPKI) PEM yapıştırın. HS* için: gizliyi yeniden kullanmak üzere boş bırakın.", + "verifyKeyHint": "Boşsa doğrulama imzalama anahtarını kullanır (HS* için faydalı).", + "verifyStatus": { + "valid": "İmza geçerli", + "invalid": "İmza geçersiz" + }, + "securityNote": "Her şey tarayıcınızda yerel çalışır; anahtarlar ve tokenlar yüklenmez." + }, + "signErrors": { + "invalidJson": "Header ve payload geçerli JSON olmalıdır.", + "unsupportedAlg": "Desteklenmeyen algoritma.", + "missingKey": "Bu algoritma için bir imzalama anahtarı gerekir.", + "cryptoFailed": "İmzalama başarısız. Anahtar biçimini kontrol edip tekrar deneyin." + }, + "verifyErrors": { + "empty": "Doğrulamak için bir JWT girin.", + "segments": "İmza doğrulaması için JWT tam olarak 3 segmentten oluşmalıdır.", + "unsupportedAlg": "Header içinde desteklenmeyen veya eksik alg.", + "missingKey": "Bu algoritma için doğrulama anahtarı gerekir.", + "base64": "JWT header veya imza çözülemedi.", + "cryptoFailed": "Doğrulama başarısız. Anahtar biçimini kontrol edip tekrar deneyin." + } + }, + "CertificatePemDecoder": { + "title": "Sertifika / PEM çözücü", + "subtitle": "Bir X.509 sertifikası (-----BEGIN CERTIFICATE-----) veya CSR (-----BEGIN CERTIFICATE REQUEST-----) yapıştırın. Konu, veren, tarihler ve SAN yerel olarak ayrıştırılır; hiçbir şey yüklenmez.", + "pemLabel": "PEM girişi", + "clear": "Temizle", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Ayrıştırılıyor…", + "copyHex": "Parmak izini kopyala (iki nokta olmadan hex)", + "errors": { + "empty": "Çözmek için bir PEM sertifikası veya CSR yapıştırın.", + "noBlock": "Sertifika veya CSR PEM bloğu bulunamadı.", + "parseFailed": "Bu PEM verileri ayrıştırılamadı." + }, + "block": { + "title": "PEM bloğu {index}", + "certificate": "X.509 sertifikası", + "csr": "Sertifika imzalama isteği" + }, + "fields": { + "subject": "Konu", + "issuer": "Veren", + "notBefore": "Öncesi geçersiz", + "notAfter": "Sonrası geçersiz", + "serial": "Seri numarası (hex)", + "sha256": "SHA-256 parmak izi", + "sans": "Konu alternatif adları", + "sansNone": "SAN uzantısı yok.", + "signatureAlgorithm": "İmza algoritması", + "csrIssuerNote": "CSR’ler vereni içermez (verilen sertifikada görünür).", + "csrValidityNote": "CSR’ler geçerlilik tarihlerini içermez (verilen sertifikada görünür)." + }, + "sanType": { + "dns": "DNS", + "dn": "Dizin adı", + "email": "E-posta", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Kayıtlı ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL Biçimlendirici", + "subtitle": "MySQL, PostgreSQL veya SQLite için SQL’i biçimlendir. Tarayıcıda çalışır; hiçbir şey yüklenmez.", + "dialectLabel": "Lehçe", + "keywordsLabel": "Anahtar kelimeler", + "indentLabel": "Girinti", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "BÜYÜK", + "lower": "küçük", + "preserve": "Koru" + }, + "spaces": "{n} boşluk", + "format": "Biçimlendir", + "clear": "Temizle", + "counter": "{current} / {max} karakter", + "inputPanel": "Girdi", + "formattedPanel": "Biçimlendirilmiş", + "copy": "Kopyala", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "\"Biçimlendir\"e tıkla…", + "errors": { + "tooLong": "Girdi {max} karakter ile sınırlıdır.", + "couldNotFormat": "Bu SQL biçimlendirilemedi." + } + }, + "YamlFormatter": { + "title": "YAML Biçimleyici", + "subtitle": "YAML'ı tarayıcınızda biçimlendirin, doğrulayın ve dönüştürün. Hiçbir şey yüklenmez.", + "outputLabel": "Çıktı", + "outputModes": { + "yaml": "YAML (biçimlendirilmiş)", + "json": "JSON" + }, + "indentLabel": "Girinti", + "spaces": "{n} boşluk", + "format": "Biçimlendir", + "clear": "Temizle", + "copy": "Kopyala", + "counter": "{current} / {max} karakter", + "inputPanel": "Girdi", + "outputPanel": "Çıktı", + "errors": { + "tooLong": "Girdi {max} karakterle sınırlıdır.", + "couldNotFormat": "Bu YAML ayrıştırılamadı." + } + }, + "GraphqlFormatter": { + "title": "GraphQL biçimlendirici", + "subtitle": "GraphQL sorgularını, mutasyonlarını ve aboneliklerini tarayıcıda biçimlendirin. Hiçbir şey yüklenmez.", + "tabs": { + "format": "Biçimlendir", + "build": "Sorgu oluşturucu" + }, + "outputModeLabel": "Çıktı", + "outputModes": { + "pretty": "Okunabilir yazdır", + "minify": "Küçült" + }, + "format": "Biçimlendir", + "clear": "Temizle", + "counter": "{current} / {max} karakter", + "inputPanel": "Girdi", + "outputPanel": "Çıktı", + "copy": "Kopyala", + "builderHint": "Bir iskelet oluşturup düzenleyiciye uygulayın. Değişken tanımları GraphQL sözdizimindedir (ör. $id: ID!) — parantezler isteğe bağlıdır.", + "builder": { + "operation": "İşlem", + "operationName": "İşlem adı", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Değişken tanımları", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Seçim kümesi", + "apply": "Girdiye uygula", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Girdi {max} karakterle sınırlıdır.", + "couldNotFormat": "Bu GraphQL ayrıştırılamadı veya yazdırılamadı.", + "emptySelection": "Uygulamadan önce bir seçim kümesi ekleyin." + } + }, + "RegexTester": { + "title": "Regex Testi", + "subtitle": "Metninize karşı canlı JavaScript tarzı regex. Eşleşmeler vurgulanır; hiçbir şey tarayıcınızdan çıkmaz.", + "patternLabel": "Desen", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g global", + "i": "i büyük/küçük harf duyarsız", + "m": "m çok satırlı", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(boş)", + "noFlags": "—", + "matches": "{count, plural, one {eşleşme} other {eşleşme}}", + "testTextLabel": "Test metni", + "counter": "{current} / {max}", + "clear": "Temizle", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Metin {max} karakter ile sınırlıdır.", + "invalid": "Geçersiz düzenli ifade.", + "runaway": "{max} eşleşmeden sonra durduruldu (olası kontrolden çıkan desen)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Zaman damgası dönüştürücü", + "subtitle": "Unix saniyeleri (≤10 basamak) veya milisaniyeler (daha uzun tam sayılar), ISO-8601 ya da tarayıcınızın ayrıştırabildiği dizeler. Her şey yerelde çalışır.", + "inputLabel": "Girdi", + "placeholder": "1744200000 veya 2025-04-09T12:00:00Z", + "now": "Şimdi", + "copy": "Kopyala", + "hint": "Bir değer yapıştırın veya Şimdiye tıklayın.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Göreli", + "human": "Okunabilir" + }, + "fields": { + "seconds": "Saniye", + "milliseconds": "Milisaniye", + "utc": "UTC", + "local": "Yerel", + "distance": "Mesafe" + }, + "errors": { + "empty": "Bir Unix zaman damgası, ISO dizesi veya tarih metni girin.", + "invalidNumber": "Geçersiz sayısal zaman damgası.", + "outOfRange": "JavaScript Date için aralık dışında.", + "couldNotParse": "Unix, ISO-8601 veya tarayıcı tarih dizesi olarak ayrıştırılamadı." + } + }, + "HttpStatusCodes": { + "title": "HTTP durum kodları", + "subtitle": "HTTP yanıt durum kodlarını (1xx5xx) kısa açıklamalar ve RFC referanslarıyla arayın ve göz atın. Yerel olarak çalışır.", + "header": { + "title": "HTTP durum kodları", + "description": "Açıklamalar ve RFC referanslarıyla HTTP yanıt kodları için aranabilir başvuru." + }, + "searchPlaceholder": "Ara: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Sınıfa göre filtrele…", + "classFilterAll": "Tüm sınıflar", + "resultsCount": "{total} içinde {count}", + "table": { + "code": "Code", + "phrase": "İfade", + "description": "Açıklama", + "copy": "Kopyala" + }, + "noResults": "“{query}” için sonuç yok.", + "specPrefix": "Spes.:", + "copied": "Kod kopyalandı" + }, + "CronBuilder": { + "title": "Cron ifade oluşturucu", + "subtitle": "Standart 5 alanlı cron: minute hour day month weekday. Ayrıştırıcı dahili olarak seconds=0 kullanır. İfade düzenleyicide isteğe bağlı 6 alan (sec min hour dom mon dow) da çalışır.", + "tabs": { + "builder": "Görsel oluşturucu", + "expression": "İfade" + }, + "presetsLabel": "Hazır ayarlar", + "fieldsLabel": "Alanlar", + "rawExpressionLabel": "Ham ifade", + "expressionHint": "Ayrıştırmak ve senkronize etmek için alandan çıkın. {count} {count, plural, one {token} other {token}}.", + "currentExpressionLabel": "Mevcut ifade", + "copy": "Kopyala", + "quickPick": "Hızlı seçim", + "emptyExpression": "—", + "plainLanguageLabel": "Düz metin", + "nextRunsLabel": "Sonraki çalıştırmalar (yerel saat)", + "fields": { + "minute": "Dakika", + "hour": "Saat", + "dayOfMonth": "Ayın günü", + "month": "Ay", + "dayOfWeek": "Haftanın günü" + }, + "presets": { + "everyMinute": "Her dakika", + "every5Minutes": "Her 5 dakika", + "every15Minutes": "Her 15 dakika", + "everyHour": "Her saat (:00)", + "every6Hours": "Her 6 saat", + "dailyMidnight": "Günlük (gece yarısı)", + "daily0900": "Günlük 09:00", + "weekdays0900": "Hafta içi 09:00", + "weeklySunday": "Haftalık (Pazar 00:00)", + "monthlyFirst": "Aylık (1. gün 00:00)", + "yearlyJan1": "Yıllık (1 Oca 00:00)" + }, + "picks": { + "minute": { + "every": "her dakika" + }, + "hour": { + "every": "her saat" + }, + "dom": { + "any": "herhangi bir gün", + "last": "son gün" + }, + "month": { + "every": "her ay" + }, + "dow": { + "any": "herhangi bir gün", + "weekdays": "hafta içi", + "weekend": "hafta sonu" + } + }, + "months": { + "jan": "Oca", + "feb": "Şub", + "mar": "Mar", + "apr": "Nis", + "may": "May", + "jun": "Haz", + "jul": "Tem", + "aug": "Ağu", + "sep": "Eyl", + "oct": "Eki", + "nov": "Kas", + "dec": "Ara" + }, + "weekdays": { + "sun": "Paz", + "mon": "Pzt", + "tue": "Sal", + "wed": "Çar", + "thu": "Per", + "fri": "Cum", + "sat": "Cmt" + }, + "errors": { + "fieldCount": "5 alan (minute hour day month weekday) veya saniyeli 6 alan bekleniyor.", + "invalid": "Geçersiz cron ifadesi." + } + }, + "Base64": { + "title": "Base64 Kodlayıcı / Çözücü", + "subtitle": "Metni Base64’e kodlayın veya Base64 dizelerini çözün", + "upload": "Yükle", + "clear": "Temizle", + "modes": { + "encode": "Kodla", + "decode": "Çöz" + }, + "swapTitle": "Girdi ve çıktıyı değiştir", + "panels": { + "plainText": "Düz metin", + "base64Input": "Base64 girdi", + "base64Output": "Base64 çıktı", + "decodedText": "Çözülmüş metin" + }, + "placeholders": { + "encode": "Base64’e kodlamak için metin girin…", + "decode": "Çözmek için Base64 yapıştırın…" + }, + "dropHere": "Dosyayı buraya bırakın", + "charCount": "{count} karakter", + "download": { + "title": "Çıktıyı indir", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Panoya kopyala", + "outputPlaceholder": "Çıktı burada görünecek…", + "errors": { + "invalidBase64": "Geçersiz Base64 dizesi. Girdiyi kontrol edin.", + "encodeFailed": "Girdi kodlanamadı." + } + }, + "UrlEncode": { + "title": "URL Kodlayıcı / Çözücü", + "subtitle": "encodeURIComponent / decodeURIComponent (UTF‑8) ile yüzde kodla/çöz", + "upload": "Yükle", + "clear": "Temizle", + "modes": { + "encode": "Kodla", + "decode": "Çöz" + }, + "swapTitle": "Girdi ve çıktıyı değiştir", + "panels": { + "plainText": "Düz metin", + "encodedInput": "Kodlanmış girdi", + "encodedOutput": "Kodlanmış çıktı", + "decodedText": "Çözülmüş metin" + }, + "placeholders": { + "encode": "Yüzde kodlamak için metin girin…", + "decode": "Çözmek için percent-encoded metin yapıştırın…" + }, + "dropHere": "Dosyayı buraya bırakın", + "charCount": "{count} karakter", + "download": { + "title": "Çıktıyı indir", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Panoya kopyala", + "outputPlaceholder": "Çıktı burada görünecek…", + "errors": { + "invalidEncoding": "Geçersiz yüzde kodlama. Eksik % çiftlerini veya hatalı kaçış dizilerini kontrol edin.", + "encodeFailed": "Kodlanamadı (girdide geçersiz Unicode)." + } + }, + "UrlParser": { + "title": "URL Ayrıştırıcı", + "subtitle": "Bir URL'yi protokol, host, yol, sorgu parametreleri ve hash (parça) olarak ayırın.", + "upload": "Yükle", + "clear": "Temizle", + "panels": { + "input": "URL girişi", + "output": "Sonuç" + }, + "placeholders": { + "input": "Bir URL yapıştırın (örn. https://example.com/path?foo=bar#section) veya host/yol (örn. example.com/path)…" + }, + "dropHere": "Dosyayı buraya bırakın", + "charCount": "{count} karakter", + "outputPlaceholder": "Ayrıştırılan bileşenler burada görünecek…", + "fields": { + "protocol": "Protokol", + "host": "Host", + "path": "Yol", + "hash": "Hash", + "queryParams": "Sorgu parametreleri" + }, + "paramCount": "{count} parametre", + "noParams": "Sorgu parametresi yok.", + "paramKey": "Anahtar", + "paramValue": "Değer", + "copyTitle": "Panoya kopyala", + "copyParamTitle": "key=value olarak kopyala", + "download": { + "title": "JSON indir", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Geçersiz URL. Bir hostname (example.com) ekleyin ve karakterleri kontrol edin." + } + }, + "UuidGenerator": { + "title": "UUID / ULID Oluşturucu", + "subtitle": "Tarayıcıda RFC 9562 UUID’leri veya ULID’ler oluşturun. Toplu dışa aktarma: çalıştırma başına {max} adede kadar.", + "format": "Biçim", + "kindHints": { + "ulid": "Sıralanabilir, URL için güvenli 26 karakterlik tanımlayıcı", + "uuid4": "Rastgele (RFC 9562)", + "uuid7": "Zamana göre sıralı, Unix ms + rastgele", + "uuid1": "MAC/zaman tabanlı (RFC 9562)", + "uuid6": "Yeniden sıralanmış Gregoryen zaman", + "uuid3": "Ad + ad alanı için MD5 hash", + "uuid5": "Ad + ad alanı için SHA‑1 hash", + "nil": "Sıfır UUID (tümü sıfır, RFC 9562)" + }, + "countLabel": "Adet (1–{max})", + "nameLabel": "Ad (girdi dizesi)", + "namePlaceholder": "örn. my.app/resource", + "bulkHint": "Toplu çalıştırmada #0, #1, … eklenir; böylece her ID farklı olur (v3/v5 deterministiktir).", + "namespace": "Ad alanı", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Özel UUID" + }, + "customNamespaceLabel": "Ad alanı UUID’si", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Oluştur", + "output": "Çıktı", + "lines": "{count} satır", + "download": { + "title": ".txt indir", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Tümünü kopyala", + "uppercaseToggle": "Büyük harfli çıktı", + "hyphensToggle": "Tireleri göster", + "copyJsonTitle": "JSON dizisi olarak kopyala", + "outputPlaceholder": "\"Oluştur\"a tıklayın…", + "errors": { + "invalidCustomNamespace": "Özel ad alanı geçerli bir UUID olmalıdır.", + "missingName": "UUID v3 ve v5 için ad gereklidir.", + "unknown": "ID’ler oluşturulamadı." + } + }, + "SecretApiKeyGenerator": { + "title": "Gizli / API anahtarı oluşturucu", + "subtitle": "Yapılandırılabilir uzunluk ve alfabe ile kriptografik rastgele dizeler oluşturun. Çalıştırma başına {max} adede kadar toplu dışa aktarma. UUID / ULID oluşturucu ile birlikte kullanıma uygundur.", + "alphabetLabel": "Alfabe", + "alphabetHint": "Yinelenen karakterler kaldırılır. Her simge crypto.getRandomValues ve reddetme örneklemesiyle tekdüze seçilir (modül yanlılığı yok).", + "alphabetCount": "{count} benzersiz simge", + "presetsLabel": "Ön ayarlar", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Alfasayısal", + "hex": "Onaltılık (küçük harf)", + "asciiPrintable": "ASCII yazdırılabilir" + }, + "lengthLabel": "Gizli başına uzunluk (1–{maxLen})", + "countLabel": "Adet (1–{maxBulk})", + "generate": "Oluştur", + "output": "Çıktı", + "lines": "{count} satır", + "download": { + "title": ".txt indir", + "filename": "gizli-anahtarlar.txt" + }, + "copyTitle": "Tümünü kopyala", + "outputPlaceholder": "\"Oluştur\"a tıklayın…", + "entropy": "{bits} bit entropi", + "strength": { + "weak": "Zayıf", + "fair": "Orta", + "strong": "Güçlü" + }, + "excludeAmbiguous": "Belirsiz karakterleri hariç tut", + "excludeAmbiguousHint": "{chars} kaldırılır — kolayca yanlış okunan veya kabuk için tehlikeli karakterler.", + "prefixLabel": "Anahtar öneki (isteğe bağlı)", + "errors": { + "emptyAlphabet": "Alfabeye en az bir karakter ekleyin.", + "alphabetTooLong": "Yinelenenler kaldırıldıktan sonra alfabe en fazla {maxAlpha} karakter olabilir.", + "lengthOutOfRange": "Uzunluk 1 ile {maxLen} arasında olmalıdır.", + "countOutOfRange": "Adet 1 ile {maxBulk} arasında olmalıdır.", + "unknown": "Gizli dizeler oluşturulamadı." + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum Oluşturucu", + "subtitle": "Düzenleriniz ve maketleriniz için Latince yer tutucu metin oluşturun.", + "paragraphs": "Paragraflar", + "sentences": "Cümleler", + "words": "Kelimeler", + "count": "Sayı", + "generate": "Oluştur", + "copy": "Kopyala", + "startWith": "'Lorem ipsum...' ile başla", + "outputPlaceholder": "Çıktı burada görünecek...", + "outputType": "Çıktı türü", + "bulletList": "Madde işaretli liste", + "htmlMarkup": "HTML işaretlemesi", + "htmlMarkupHint": "Çıktıyı paragraflara sarın veya madde işareti modu için liste kullanın", + "output": "Çıktı", + "download": "İndir" + }, + "ColorPicker": { + "title": "Renk seçici ve dönüştürücü", + "subtitle": "HEX, RGB ve HSL'yi dönüştürün; uyumlu paletler oluşturun. Tüm işlemler tarayıcınızda gerçekleşir.", + "nativePicker": "Yerleşik seçici", + "chooseColor": "Renk seçin", + "eyeDropper": "Göz damlası", + "invalidHex": "Geçersiz HEX rengi.", + "cssValues": "CSS değerleri", + "palettes": "Paletler", + "copied": "Kopyalandı", + "clickToCopy": "Kopyalamak için tıklayın", + "copyAllHex": "Tüm HEX'leri kopyala", + "copy": "Kopyala", + "copyColor": "{color} kopyala", + "paletteShades": "Tonlar", + "paletteComplementary": "Tamamlayıcı", + "paletteTriadic": "Üçlü", + "paletteAnalogous": "Benzer", + "paletteSplit": "Bölünmüş tamaml.", + "paletteSquare": "Kare" + }, + "ContrastChecker": { + "title": "Kontrast denetleyici", + "subtitle": "Ön plan ve arka plan arasında WCAG 2.1 kontrast oranını hesaplar. Normal ve büyük metin için AA ve AAA durumunu gösterir. Tümü tarayıcınızda çalışır.", + "foreground": "Metin / ön plan", + "background": "Arka plan", + "hexPlaceholder": "#RRGGBB", + "swap": "Renkleri değiştir", + "chooseForeground": "Ön plan rengini seç", + "chooseBackground": "Arka plan rengini seç", + "eyeDropper": "Damlalık", + "ratio": "Kontrast oranı", + "copyRatio": "Oranı kopyala", + "copied": "Kopyalandı", + "previewHeading": "Ön izleme", + "previewBody": "Pijamalı hasta, yağız şoföre çabucak güvendi. Fâni idrak ömrünü, câhil zâhid inkâr etmiş, göçtü.", + "previewCaption": "Varsayılan boyutta örnek metin.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — normal metin (4,5:1)", + "levelAALarge": "AA — büyük metin (3:1)", + "levelAAANormal": "AAA — normal metin (7:1)", + "levelAAALarge": "AAA — büyük metin (4,5:1)", + "pass": "Uygun", + "fail": "Uygun değil", + "invalidHex": "Geçersiz HEX rengi.", + "largeTextHint": "Büyük metin: 18 pt+ düz veya 14 pt+ kalın (WCAG tanımları)." + }, + "DiffChecker": { + "title": "Fark denetleyici", + "subtitle": "İki metnin satır satır yan yana karşılaştırması. Her şey tarayıcınızda kalır.", + "rowsCount": "{count} satır", + "truncatedWarning": "İlk {max} satır gösteriliyor.", + "limitExceeded": "Her taraf {max} karakterle sınırlıdır.", + "clearBoth": "İkisini de temizle", + "original": "Orijinal", + "modified": "Değiştirilmiş", + "originalPlaceholder": "Orijinal metni yapıştırın…", + "modifiedPlaceholder": "Değiştirilmiş metni yapıştırın…", + "comparisonLabel": "Yan yana karşılaştırma", + "emptyState": "Karşılaştırmak için her iki panele de metin girin." + }, + "JsonSchemaGenerator": { + "title": "JSON Şema Oluşturucu", + "subtitle": "JSON Şeması veya tipli modeller üretmek için geçerli JSON yapıştırın. Her şey tarayıcınızda çalışır.", + "languageLabel": "Çıktı dili", + "inputLabel": "JSON girdisi", + "inputPlaceholder": "JSON’u buraya yapıştırın…", + "outputLabel": "Üretilen çıktı", + "outputPlaceholder": "Üretilen şema veya türler burada görünecek…", + "parseError": "Geçersiz JSON", + "clear": "Temizle", + "copy": "Kopyala", + "copied": "Panoya kopyalandı", + "copyFailed": "Kopyalanamadı", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON Şeması (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Editör yükleniyor…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Base64'e görüntü", + "subtitle": "Bir görüntüyü bırakın, bir veri URI'si veya ham Base64 dizesi alın. Her şey tarayıcınızda kalır.", + "upload": "Resim Yükle", + "clear": "Temizlemek", + "modes": { + "dataUri": "Veri URI'si", + "rawString": "Ham Base64" + }, + "panels": { + "imageInput": "Görüntü Girişi", + "output": "Base64 Çıkışı" + }, + "dropHere": "Resmi buraya bırakın", + "charCount": "{count} karakter", + "placeholders": { + "uploadImage": "Başlamak için bir resmi sürükleyip bırakın veya yükle'ye tıklayın." + }, + "fileTooLarge": "Dosya çok büyük. İşleme, tarayıcınızı dondurabilir.", + "copyTitle": "Panoya kopyala", + "download": { + "title": "Metni indir", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Görüntü sıkıştırıcı", + "subtitle": "JPEG, PNG veya WebP çıktısını yerelde kaliteyle küçültün — hiçbir şey yüklenmez.", + "upload": "Görüntü yükle", + "clear": "Temizle", + "dropHere": "Görüntüyü buraya bırakın", + "placeholders": { + "uploadImage": "Sürükleyip bırakın veya dosya seçmek için tıklayın." + }, + "fileTooLarge": "Dosya çok büyük (en fazla {maxMb} MB).", + "unsupportedFormat": "Desteklenmeyen dosya türü. JPEG, PNG, WebP, GIF, AVIF veya BMP kullanın.", + "compressFailed": "Bu görüntü sıkıştırılamadı. Başka bir dosya veya biçim deneyin.", + "panels": { + "original": "Orijinal", + "compressed": "Sıkıştırılmış önizleme" + }, + "outputFormat": "Çıktı biçimi", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Kalite", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG tarayıcıda kayıpsızdır; kalite kaydırıcısı yalnızca JPEG ve WebP için geçerlidir.", + "sizes": { + "original": "Orijinal: {size}", + "compressed": "Sıkıştırılmış: {size}", + "saved": "{percent}% daha küçük" + }, + "largerWarning": "Çıktı girişten büyük — farklı biçim veya kalite deneyin.", + "download": "İndir", + "downloadFilename": "sikistirilmis-goruntu", + "processing": "Sıkıştırılıyor…", + "emptyCompressed": "Sıkıştırılmış önizleme burada görünür." + }, + "CssGradientBuilder": { + "title": "CSS Degrade Oluşturucu", + "subtitle": "Açı kontrolü, renk durakları ve tek tıklamayla CSS kopyalama özelliklerine sahip görsel CSS degrade oluşturucu.", + "preview": "Degrade Önizleme", + "settings": "Ayarlar", + "type": "Tip", + "linear": "Doğrusal", + "radial": "Radyal", + "angle": "Açı", + "stops": "Renk Durakları", + "color": "Renk", + "position": "Konum (%)", + "addStop": "Durak Ekle", + "deleteStop": "Durdurmayı Sil", + "output": "CSS Çıkışı", + "copy": "CSS'yi kopyala", + "copied": "Kopyalandı!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore Oluşturucu", + "subtitle": "Belirli bir teknoloji yığını veya birleşimi için birleştirilmiş .gitignore dosyalarını anında oluşturun.", + "searchPlaceholder": "Yığınlarda arama yapın (ör. Node, Python, macOS)...", + "noResults": "Yığın bulunamadı.", + "selectedStacks": "Seçilen Yığınlar", + "output": "Çıktıyı Önizle", + "copy": "Panoya kopyala", + "copied": "Kopyalandı!", + "download": ".gitignore'u indirin", + "downloading": "İndiriliyor...", + "errorFetching": "Toplam liste getirilemedi.", + "errorGenerating": "Seçilen yığınlardan .gitignore oluşturulamadı.", + "generating": "Oluşturuluyor...", + "emptyState": ".gitignore'unuzu oluşturmak için bir veya daha fazla teknoloji yığını seçin." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV veya Excel yükleyin, JSON’u düzenleyin, ardından CSV veya XLSX indirin. Yalnızca tablo verisi; Excel tarih hücreleri JSON’da ISO zaman damgası olur.", + "dropTitle": "CSV veya Excel dosyasını bırakın", + "dropHint": ".xlsx / .xls için ilk çalışma sayfası kullanılır. CSV’de başlık satırı olmalıdır.", + "chooseFile": "Dosya seç", + "jsonLabel": "JSON (nesne dizisi)", + "jsonPlaceholder": "Nesnelerden oluşan JSON dizisini yapıştırın veya CSV / Excel içe aktarın…", + "loadSample": "Örnek yükle", + "actions": { + "format": "JSON’u biçimlendir", + "clear": "Temizle", + "copy": "Kopyala", + "copied": "Kopyalandı", + "downloadCsv": "CSV indir", + "downloadXlsx": "Excel indir" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Veri", + "toastLoadedCsv": "CSV düzenleyiciye yüklendi", + "toastLoadedExcel": "Excel düzenleyiciye yüklendi", + "toastFormatted": "JSON biçimlendirildi", + "toastExportedCsv": "CSV indirildi", + "toastExportedXlsx": "Excel indirildi", + "toastCopied": "Panoya kopyalandı", + "footnote": "Her şey tarayıcıda çalışır. JSON boş olmayan bir nesne dizisi olmalıdır. İç içe değerler CSV hücrelerinde JSON olarak serileştirilir.", + "errors": { + "unsupportedFile": ".csv, .xlsx veya .xls dosyası kullanın.", + "parseFailed": "Bu dosya ayrıştırılamadı.", + "invalidJson": "Geçersiz JSON", + "exportFailed": "Dışa aktarma başarısız", + "copyFailed": "Kopyalanamadı" + } + }, + "SnippetManager": { + "title": "Kod parçacığı yöneticisi", + "subtitle": "Parçacıkları tarayıcıda saklayın veya hesabınızla eşitlemek için oturum açın. Görüntüleme ve düzenleme arasında geçiş yapın, tek tıkla kopyalayın, dil seçin veya otomatik algılayın ve JSON, SQL veya Monaco destekli kodu biçimlendirin.", + "snippets": "Parçacıklar", + "newSnippet": "Yeni parçacık", + "newSnippetTitle": "Yeni parçacık", + "defaultSnippetTitle": "Hoş geldiniz", + "defaultSnippetCode": "// Kod parçacıklarına hoş geldiniz\n// Görüntüle vs düzenle, kopyala, biçimlendir ve otomatik dil deneyin\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Parçacık ara…", + "searchAria": "Parçacık ara", + "emptySearch": "Aramanızla eşleşen parçacık yok.", + "snippetTitle": "Başlık", + "snippetTitlePlaceholder": "Parçacık adı", + "language": "Dil", + "detectedHint": "Vurgulama: {lang}", + "badgeAuto": "Otomatik · {lang}", + "modeView": "Görüntüle", + "modeEdit": "Düzenle", + "copy": "Kopyala", + "copied": "Kopyalandı", + "format": "Biçimlendir", + "cancel": "İptal", + "deleteTitle": "Parçacık silinsin mi?", + "deleteDescription": "“{title}” bu tarayıcıdan kaldırılacak.", + "deleteConfirm": "Sil", + "editorAria": "Parçacık kod düzenleyici", + "editorLoading": "Düzenleyici yükleniyor…", + "toastCopied": "Panoya kopyalandı", + "toastCopyFailed": "Kopyalanamadı", + "toastNothingToCopy": "Kopyalanacak bir şey yok", + "toastFormatted": "Biçimlendirildi", + "toastFormatFailed": "Biçimlendirilemedi", + "toastFormatJsonInvalid": "Geçersiz JSON — önce sözdizimini düzeltin", + "toastFormatUnsupported": "Bu dil için biçimlendirici yok — JSON veya SQL deneyin ya da düzenleme modunu kullanın", + "toastDeleted": "Parçacık silindi", + "toastLoadFailed": "Parçacıklar sunucudan yüklenemedi.", + "toastSyncFailed": "Sunucu ile eşitleme başarısız oldu.", + "languages": { + "auto": "Otomatik algıla", + "plaintext": "Düz metin", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL İstemcisi", + "dialogTitle": "SQL Bağlantıları", + "mobileSheetTitle": "SQL Gezgini", + "newTabLabel": "Yeni sorgu sekmesi", + "vaultLockedTitle": "Kasa kilitli", + "vaultLockedDesc": "Kayıtlı bağlantılara erişmek için kasanızı açın.", + "heroTitle": "SQL İstemcisi", + "heroDesc": "PostgreSQL, MySQL veya MariaDB'ye bağlanın. Kimlik bilgileri tarayıcınızda şifrelenir.", + "heroAddConnection": "Bağlantı ekle", + "selectTableTitle": "Bir tablo seçin veya sorgu sekmesi açın", + "selectTableDesc": "Kenar çubuğundan bir tabloya tıklayın veya ⌘↩ kullanın.", + "toastLoadFail": "Bağlantılar yüklenemedi." + }, + "connection": { + "titleNew": "Yeni SQL Bağlantısı", + "titleEdit": "Bağlantıyı düzenle", + "labelDbType": "Veritabanı türü", + "labelName": "Bağlantı adı", + "placeholderName": "Üretim veritabanım", + "labelHostPort": "Host ve Port", + "labelDatabase": "Veritabanı", + "placeholderDatabase": "mydb", + "labelUsername": "Kullanıcı adı", + "labelPassword": "Parola", + "labelSsl": "SSL / TLS etkinleştir", + "sslDesc": "Sunucu bağlantısını şifrele", + "securityNote": "Kimlik bilgileri depolanmadan önce tarayıcıda AES-256 ile şifrelenir", + "btnTest": "Bağlantıyı test et", + "btnSave": "Kaydet ve bağlan", + "btnUpdate": "Güncelle", + "btnCancelEdit": "Düzenlemeyi iptal et", + "savedConnections": "Kayıtlı bağlantılar", + "emptySaved": "Kayıtlı bağlantılar burada görünecek", + "confirmDelete": "Bu bağlantı silinsin mi?", + "toastFillFields": "Test etmeden önce host ve veritabanını doldurun.", + "toastConnectOk": "Bağlandı! {version}", + "toastConnectFail": "Bağlantı başarısız: {message}", + "toastUnlockVault": "Önce kasanızı açın.", + "toastFieldsRequired": "Host ve veritabanı zorunludur.", + "toastUpdated": "Bağlantı güncellendi.", + "toastSaved": "Bağlantı kaydedildi.", + "toastSaveFail": "Kaydetme başarısız: {message}", + "toastDeleted": "Bağlantı silindi.", + "toastDeleteFail": "Bağlantı silinemedi." + }, + "sidebar": { + "title": "SQL Gezgini", + "tooltipAddConnection": "Bağlantı ekle", + "tooltipRefreshSchema": "Şemayı yenile", + "noConnections": "Henüz kayıtlı bağlantı yok", + "toastSchemaFail": "Şema yüklenemedi: {message}" + }, + "editor": { + "btnRun": "Çalıştır", + "executing": "Sorgu çalıştırılıyor…", + "emptyTitle": "Sonuçları görmek için sorgu çalıştırın", + "emptyHint": "⌘↩ basın veya Çalıştır'a tıklayın", + "toastNoConnection": "Aktif bağlantı yok." + }, + "results": { + "filterPlaceholder": "Sonuçları filtrele…", + "rowCount": "{count} satır", + "rowCountFiltered": "{filtered} / {total} satır", + "exportCsv": "CSV", + "noRows": "Satır döndürülmedi" + } + }, + "QrCodeGenerator": { + "title": "QR kod oluşturucu", + "subtitle": "Herhangi bir metni veya URL’yi PNG QR koda dönüştürün. İşlem tamamen tarayıcıda yerel olarak yapılır.", + "contentLabel": "İçerik", + "contentPlaceholder": "https://ornek.com, düz metin, Wi‑Fi dizesi, vCard…", + "styleLabel": "Stil", + "dotTypeLabel": "Nokta şekli", + "dotTypeSquare": "Kare", + "dotTypeDots": "Noktalar", + "dotTypeRounded": "Yuvarlatılmış", + "dotTypeExtraRounded": "Ekstra yuvarlatılmış", + "dotTypeClassy": "Zarif", + "dotTypeClassyRounded": "Zarif yuvarlatılmış", + "cornerSquareLabel": "Köşe kareleri", + "cornerSquareDot": "Nokta", + "cornerSquareSquare": "Kare", + "cornerSquareExtraRounded": "Ekstra yuvarlatılmış", + "cornerDotLabel": "Köşe noktaları", + "cornerDotDot": "Nokta", + "cornerDotSquare": "Kare", + "eccLabel": "Hata düzeltme", + "eccL": "Düşük (~%7)", + "eccM": "Orta (~%15)", + "eccQ": "Çeyrek (~%25)", + "eccH": "Yüksek (~%30)", + "marginLabel": "Kenar boşluğu (modül)", + "widthLabel": "Görüntü genişliği", + "widthEffective": "{px} px olarak oluşturulur", + "darkColor": "Koyu modüller", + "lightColor": "Açık modüller", + "resetColors": "Renkleri sıfırla", + "logoLabel": "Orta logo", + "logoMarginLabel": "Boşluk", + "uploadLogo": "Resim yükle", + "removeLogo": "Logoyu kaldır", + "downloadPng": "PNG indir", + "downloadSvg": "SVG indir", + "copyImage": "Görüntüyü kopyala", + "copied": "Kopyalandı", + "clear": "Temizle", + "previewAlt": "QR kod önizlemesi", + "emptyHint": "Önizleme için içerik yazın veya yapıştırın.", + "filename": "qr-kod.png", + "errors": { + "generationFailed": "QR kod oluşturulamadı. Daha kısa metin veya daha düşük düzeltme deneyin.", + "copyUnsupported": "Tarayıcı görüntüyü panoya kopyalamayı desteklemiyor.", + "copyFailed": "Panoya kopyalanamadı." + } + }, + "IpSubnetCalculator": { + "title": "IP / alt ağ hesaplayıcı", + "subtitle": "IPv4 ve IPv6 CIDR ayrıntıları tarayıcıda—dış istek yok.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Ağ", + "placeholderIpv4": "10.0.0.0/24 veya 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 veya 2001:db8::1/64", + "hintIpv4": "CIDR (ör. /24) veya noktalı alt ağ maskesi ile IPv4 kullanın.", + "hintIpv6": "Önek uzunluğunu ekleyin (ör. /64). Yoksa /128 varsayılır.", + "copyValue": "Değeri kopyala", + "rows": { + "cidr": "CIDR", + "network": "Ağ adresi", + "netmask": "Alt ağ maskesi", + "wildcard": "Wildcard maskesi", + "broadcast": "Yayın", + "firstHost": "İlk ana bilgisayar", + "lastHost": "Son ana bilgisayar", + "totalAddresses": "Toplam adres", + "usableHosts": "Kullanılabilir ana bilgisayar", + "prefixLength": "Önek uzunluğu (bit)", + "networkIpv6": "Ağ adresi", + "lastIpv6": "Aralıktaki son adres", + "totalAddressesIpv6": "Alt ağdaki adresler" + }, + "usableNote": { + "pointToPoint": "/31 noktadan noktaya bağlantılar her iki adresi de ana bilgisayar olarak kullanır (RFC 3021).", + "singleHost": "/32 tek ana bilgisayardır." + }, + "ipv6RangeHint": { + "normal": "Tam matematiksel aralık gösterilir; yönlendirici kuralları farklı olabilir.", + "single": "/128 tek adrestir.", + "full": "/0 tüm IPv6 kapsar." + }, + "errors": { + "invalidIpv4": "Geçersiz IPv4 ağı. CIDR veya adres + maske deneyin.", + "invalidIpv6": "Geçersiz IPv6 ağı. Örn. 2001:db8::/48 biçimini kullanın." + } + }, + "HashGenerator": { + "title": "Hash oluşturucu", + "subtitle": "Metin veya dosyalar için MD5, SHA-1 ve SHA-2 özetleri; parolalar için bcrypt (metin girişi). Tümü tarayıcıda yerel olarak hesaplanır.", + "algorithmLabel": "Algoritma", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Metin", + "modeFile": "Dosya", + "inputLabelText": "Girdi", + "placeholderText": "Metin yazın veya yapıştırın…", + "chooseFile": "Dosya seç", + "clearFile": "Temizle", + "fileHint": "Dosyalar yalnızca bellekte okunur; en fazla {maxMb} MB. Yükleme yok.", + "outputLabel": "Özet (hex)", + "copyHash": "Özeti kopyala", + "copied": "Kopyalandı", + "byteCount": "{count, plural, one {# bayt} other {# bayt}}", + "charCount": "{count, plural, one {# karakter} other {# karakter}}", + "emptyHint": "Metin girin veya dosya seçin.", + "securityNote": "Yeni güvenlik kullanımları için MD5 ve SHA-1 önerilmez; SHA-256 veya SHA-512 kullanın.", + "errors": { + "fileTooLarge": "Dosya {maxMb} MB’den büyük.", + "fileReadFailed": "Dosya okunamadı." + }, + "bcryptRoundsLabel": "Maliyet (tur)", + "bcryptRoundsHint": "Daha yüksek değerler daha yavaş ve kaba kuvvete daha dayanıklıdır. Yaygın varsayılan 10’dur.", + "fileTabDisabledTitle": "bcrypt yalnızca metinden parolaları özetler; rastgele dosyaları değil.", + "placeholderBcrypt": "Özetlemek için parola yazın veya yapıştırın…", + "outputLabelBcrypt": "bcrypt özeti", + "copyBcrypt": "Özeti kopyala", + "emptyHintBcrypt": "bcrypt özeti hesaplamak için parola girin.", + "securityNoteBcrypt": "Her bcrypt çıktısı rastgele tuz içerir; aynı parola her seferinde farklı bir dize üretir. Bu beklenendir. Üretimde yüksek maliyet kullanın.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Şifreleme oyun alanı", + "subtitle": "Tarayıcıda AES-GCM: parola veya ham anahtarla JSON paketine şifreleyin, yerel olarak çözün. Öğretici ve bazen pratik — hiçbir şey yüklenmez.", + "tabEncrypt": "Şifrele", + "tabDecrypt": "Şifreyi çöz", + "keyModeLabel": "Anahtar malzemesi", + "modePassphrase": "Parola (PBKDF2 + AES-GCM)", + "modeRawKey": "Ham AES anahtarı (hex veya Base64)", + "aesBitsLabel": "AES anahtar uzunluğu", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Ham anahtar kodlaması", + "encodingAuto": "Otomatik (hex gibi görünüyorsa hex, değilse Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Ham anahtar", + "rawKeyPlaceholder": "AES-256 için 32 bayt anahtar: 64 hex karakter veya Base64 (uzunluk yukarıdaki AES boyutuna bağlı).", + "rawKeyPlaceholderDecrypt": "Yalnızca pakette keyDerivation \"none\" ise gerekli.", + "passphraseLabel": "Parola", + "passphrasePlaceholder": "Parola yalnızca tarayıcıda anahtar türetmek için kullanılır.", + "passphrasePlaceholderDecrypt": "Yalnızca paket PBKDF2-SHA256 kullanıyorsa gerekli.", + "pbkdf2IterLabel": "PBKDF2 yinelemeleri", + "pbkdf2Hint": "10.000 ile 2.000.000 arasında. Varsayılan, yaygın kasa ayarlarıyla uyumludur.", + "plaintextLabel": "Düz metin", + "plaintextPlaceholder": "Şifrelenecek iletiyi yazın veya yapıştırın…", + "bundleInputLabel": "Şifreli paket (JSON)", + "bundlePlaceholder": "Şifrele’den JSON yapıştırın (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Şifreli paket", + "plaintextOutputLabel": "Düz metin", + "encryptButton": "Şifrele", + "decryptButton": "Şifreyi çöz", + "copyBundle": "JSON kopyala", + "copyPlaintext": "Düz metni kopyala", + "copied": "Kopyalandı", + "emptyBundleHint": "Şifrelemeden sonra şifreli JSON burada görünür.", + "emptyDecryptHint": "Çözülmüş metin burada görünür.", + "decryptKeyHint": "keyDerivation \"none\" ise ham anahtarı; PBKDF2-SHA256 ise parolayı kullanın. Paket tuz, yineleme, IV ve AES uzunluğunu saklar.", + "securityNote": "Anahtarlar ve düz metin tarayıcınızdan çıkmaz. Üretim sırları için özel araçlar tercih edin.", + "errors": { + "emptyKey": "AES boyutuna uyan bir ham anahtar girin.", + "emptyPassphrase": "Bir parola girin.", + "badKeyMaterial": "Anahtar hex veya Base64 olarak okunamadı.", + "wrongKeyLength": "Anahtar uzunluğu AES ile eşleşmiyor (16 / 24 / 32 bayt).", + "invalidJson": "Geçerli bir JSON değil.", + "invalidBundle": "Bu JSON desteklenen bir şifre paketi değil.", + "decryptFailed": "Şifre çözme başarısız — yanlış gizli, bozuk paket veya uyumsuz parametreler.", + "encryptFailed": "Şifreleme başarısız. Web Crypto ve girdileri kontrol edin." + } + }, + "HmacGenerator": { + "title": "HMAC oluşturucu", + "subtitle": "Webhook imzalamak ve API entegrasyonlarını test etmek için. HMAC-SHA1/256/384/512 hex veya Base64—tarayıcıda yerel hesaplanır.", + "secretLabel": "Gizli anahtar", + "messageLabel": "İleti / yük", + "placeholderSecret": "İmzalama sırrını yapıştırın…", + "placeholderMessage": "Doğrulanacak ham gövde veya dizeyi yapıştırın (genelde webhook gövdesinin tam baytları).", + "algorithmLabel": "HMAC algoritması", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Çıktı biçimi", + "formatHex": "Hex (küçük harf)", + "formatBase64": "Base64", + "outputLabel": "İmza", + "copyOutput": "İmzayı kopyala", + "copied": "Kopyalandı", + "byteCountHint": "{count, plural, one {# bayt} other {# bayt}} (UTF-8)", + "emptyHint": "HMAC hesaplamak için gizli anahtar girin.", + "securityNote": "İmzalama sırrını asla paylaşmayın. Bu sayfa veri yüklemez—her şey tarayıcıda çalışır.", + "errors": { + "cryptoFailed": "HMAC hesaplanamadı. Web Crypto desteğini kontrol edip yeniden deneyin.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA kod üreticisi", + "subtitle": "Base32 gizli anahtarı yapıştırın ve güncel altı haneli RFC 6238 kodunu (SHA-1, 30 sn) alın. Tamamen tarayıcıda çalışır—oturum açma veya MFA testleri için uygundur.", + "secretLabel": "Gizli anahtar (Base32)", + "placeholderSecret": "Gizli anahtarı yapıştırın (A–Z harfleri ve 2–7 rakamları, doğrulayıcı kurulumunda gösterildiği gibi)…", + "codeLabel": "Güncel kod", + "copyCode": "Kodu kopyala", + "copied": "Kopyalandı", + "nextIn": "{seconds} sn içinde yenilenir", + "standardHint": "30 sn zaman adımı, 6 hane, HMAC-SHA-1 (Google Authenticator ile uyumlu).", + "digitsLabel": "Hane", + "periodLabel": "Süre", + "algorithmLabel": "Algoritma", + "periodSeconds": "{seconds} sn", + "emptyHint": "Altı haneli kod için gizli anahtarı yapıştırın.", + "invalidHint": "Kodu görmek için gizli anahtarı düzeltin.", + "securityNote": "Bunu parola gibi koruyun. Gizli anahtar tarayıcıdan çıkmaz; hiçbir şey yüklenmez.", + "importLabel": "otpauth:// URI'sinden içe aktar", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "İçe aktar", + "qrSectionTitle": "Sağlama QR kodu", + "qrHint": "Bu gizli anahtarı eklemek için bir kimlik doğrulayıcı uygulamasıyla (Google Authenticator, 1Password, Aegis…) tarayın. QR kodu gizli anahtarı kodlar — onu bir parola gibi ele alın.", + "accountLabel": "Hesap", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Veren (isteğe bağlı)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "QR kodu oluşturmak için geçerli bir gizli anahtar girin.", + "errors": { + "invalidSecret": "Geçerli bir Base32 gizli anahtar değil.", + "cryptoFailed": "TOTP hesaplanamadı. Web Crypto desteğini kontrol edip tekrar deneyin.", + "invalidUri": "Bu geçerli bir otpauth:// TOTP URI'si değil.", + "unsupportedUri": "Bu URI, aracın desteklemediği değerler kullanıyor (basamak sayısı 6 veya 8 olmalı; periyot 15, 30 veya 60 sn)." + } + }, + "MarkdownPreview": { + "title": "Markdown Önizleme & HTML", + "subtitle": "HTML dışa aktarma ve HTML → Markdown dönüşümlü canlı Markdown renderer. Her şey tarayıcınızda çalışır.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown girişi", + "htmlInputLabel": "HTML girişi", + "previewLabel": "Önizleme", + "htmlOutputLabel": "HTML kaynağı", + "mdOutputLabel": "Markdown çıktısı", + "inputPlaceholder": "# Merhaba\\n\\n**markdown**'ınızı buraya yazın…", + "htmlInputPlaceholder": "HTML'inizi buraya yapıştırın…", + "copyHtml": "HTML'yi Kopyala", + "copyMarkdown": "Markdown'ı Kopyala", + "exportHtml": "HTML'yi Dışa Aktar", + "copied": "Kopyalandı", + "charCount": "{count, plural, one {# karakter} other {# karakter}}", + "emptyHint": "Canlı önizleme ve HTML çıktısını görmek için Markdown yazmaya başlayın.", + "emptyHintHtmlToMd": "HTML'yi Markdown'a dönüştürmek için yapıştırın.", + "securityNote": "Her şey tarayıcınızda yerel olarak çalışır. Hiçbir şey yüklenmez." + }, + "NumberBaseConverter": { + "title": "Sayı tabanı dönüştürücü", + "subtitle": "2–36 tabanları arasında tam sayı dönüşümü. İsteğe bağlı 0x / 0b / 0o önekleri. Tümü tarayıcıda yerel.", + "inputBaseLabel": "Kaynak taban", + "outputBaseLabel": "Hedef taban", + "inputLabel": "Girdi sayısı", + "placeholderInput": "Seçilen giriş tabanında bir tam sayı girin…", + "outputLabel": "Dönüştürülen değer", + "copyOutput": "Sonucu kopyala", + "copied": "Kopyalandı", + "swap": "Tabanları değiştir", + "byteCountHint": "{count, plural, one {# bayt} other {# bayt}} (UTF-8 uzunluğu)", + "emptyHint": "Dönüştürmek için bir sayı girin.", + "note": "9’dan büyük basamaklar A–Z harflerini kullanır (büyük/küçük harf duyarsız). Boşluk ve alt çizgi yok sayılır.", + "errors": { + "invalidNumber": "Seçilen giriş tabanı için geçersiz değer." + } + }, + "FormatConverter": { + "title": "Format Dönüştürücü", + "subtitle": "JSON, YAML, TOML ve XML arasında her yönde dönüştürün. 12 kombinasyonun tamamı. Her şey tarayıcınızda çalışır.", + "fromLabel": "Kaynak", + "toLabel": "Hedef", + "swap": "Değiştir", + "swapTitle": "Formatları değiştirin ve çıktıyı girdi olarak kullanın", + "copy": "Kopyala", + "copied": "Kopyalandı", + "charCount": "{count, plural, one {# karakter} other {# karakter}}", + "emptyHint": "Dönüştürülmüş çıktıyı görmek için solda girdinizi yapıştırın veya yazın.", + "localNote": "Her şey tarayıcınızda yerel olarak çalışır. Hiçbir şey yüklenmez." + }, + "MimeTypeLookup": { + "title": "MIME türü arama", + "subtitle": "Dosya uzantısına veya tam dosya adına göre MIME türünü (media type) bulun. Her şey tarayıcınızda yerel olarak çalışır.", + "inputLabel": "Uzantı veya dosya adı", + "placeholder": "örn. .png, package.json, photo.jpeg", + "hint": "İpucu: bir uzantı (noktalı/noktasıız) veya tam dosya adı yapıştırın.", + "clear": "Temizle", + "exampleFilename": "Örnek dosya adı kullan", + "exampleExtension": "Örnek uzantı kullan", + "resultLabel": "Sonuç", + "normalizedLabel": "Normalize edilmiş giriş:", + "mimeTypeLabel": "MIME türü", + "contentTypeLabel": "Content-Type başlığı", + "charsetLabel": "Karakter kümesi", + "copyMimeType": "MIME türünü kopyala", + "copy": "Kopyala", + "copied": "Kopyalandı", + "emptyHint": "MIME türünü bulmak için bir uzantı veya dosya adı girin.", + "notFound": "Bu giriş için MIME türü bulunamadı.", + "localNote": "Her şey tarayıcınızda yerel olarak çalışır. Hiçbir şey yüklenmez." + }, + "UserAgentParser": { + "title": "User-Agent ayrıştırıcı", + "subtitle": "Bir User-Agent (UA) dizesi yapıştırın ve tarayıcı, işletim sistemi, motor, cihaz ve CPU detaylarını görün. Her şey tarayıcınızda yerel olarak çalışır.", + "inputLabel": "User-Agent dizesi", + "placeholder": "User-Agent'i buraya yapıştırın…", + "hint": "İpucu: UA’yı istek başlıklarından, loglardan veya tarayıcı devtools’tan kopyalayabilirsiniz.", + "clear": "Temizle", + "exampleDesktop": "Masaüstü örneği kullan", + "exampleMobile": "Mobil örnek kullan", + "resultLabel": "Detaylar", + "browserLabel": "Tarayıcı", + "osLabel": "İşletim sistemi", + "engineLabel": "Motor", + "deviceTypeLabel": "Cihaz türü", + "deviceVendorLabel": "Üretici", + "deviceModelLabel": "Model", + "cpuArchLabel": "CPU mimarisi", + "copyJson": "JSON'u kopyala", + "copy": "Kopyala", + "copied": "Kopyalandı", + "emptyHint": "Detayları görmek için bir User-Agent dizesi yapıştırın.", + "localNote": "Her şey tarayıcınızda yerel olarak çalışır. Hiçbir şey yüklenmez." + }, + "MockDataGenerator": { + "title": "Sahte veri üreticisi", + "subtitle": "Sütunları tanımlayın, veri türlerini seçin ve tohum, Postman veya test için dışa aktarın. En fazla {max} satır, hepsi tarayıcıda — yükleme yok.", + "mockarooLink": "Mockaroo tarzı akış", + "rowsLabel": "Satırlar", + "rowsHint": "Sekmeyi akıcı tutmak için oluşturma başına en fazla {max} satır.", + "formatLabel": "Biçim", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "SQL tablo adı", + "presetsLabel": "Ön ayarlar", + "presetPlaceholder": "Ön ayar yükle…", + "presetDefault": "Başlangıç (kullanıcı)", + "presetUsers": "Kullanıcılar ve roller", + "presetProducts": "E-ticaret SKU’ları", + "presetApiLog": "HTTP istek günlüğü", + "generate": "Oluştur", + "warningDuplicateNames": "Yinelenen sütun adları: JSON nesnelerinde sonraki sütunlar öncekilerin üzerine yazar. Yeniden adlandırın.", + "schemaHeading": "Şema", + "addField": "Sütun ekle", + "removeField": "Sütunu kaldır", + "duplicateField": "Sütunu çoğalt", + "colName": "Ad", + "colType": "Tür", + "colOptions": "Seçenekler", + "colBlank": "Boş %", + "optMin": "min", + "optMax": "maks", + "optDecimals": "ond", + "optDateFrom": "başlangıç", + "optDateTo": "bitiş", + "optValues": "a, b, c", + "optStep": "adım", + "optRowBase": "taban", + "output": "Çıktı", + "outputPlaceholder": "Veri oluşturmak için Oluştur’a tıklayın…", + "copy": "Kopyala", + "download": "İndir", + "copied": "Kopyalandı", + "groups": { + "personal": "Kişisel", + "location": "Konum", + "internetApi": "İnternet ve API", + "numbersIds": "Sayılar ve kimlikler", + "dateTime": "Tarih ve saat", + "textDesign": "Metin ve tasarım", + "custom": "Özel" + }, + "fieldTypes": { + "first_name": "Ad", + "last_name": "Soyad", + "full_name": "Ad soyad", + "email": "Email", + "username": "Kullanıcı adı", + "password": "Parola", + "phone": "Telefon", + "company": "Şirket", + "job_title": "Ünvan", + "avatar_url": "Avatar URL’si", + "city": "Şehir", + "country": "Ülke", + "country_code": "Ülke kodu (ISO)", + "street_address": "Adres", + "zip_code": "Posta kodu", + "latitude": "Enlem", + "longitude": "Boylam", + "url": "URL", + "domain": "Alan adı", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Tam sayı", + "float": "Ondalık", + "boolean": "Mantıksal", + "uuid": "UUID", + "credit_card": "Test kartı", + "semver": "SemVer", + "row_index": "Satır dizini", + "sequence": "Dizi", + "date": "Tarih", + "datetime": "Tarih ve saat", + "unix_timestamp": "Unix zaman damgası", + "timezone": "Saat dilimi", + "word": "Kelime", + "sentence": "Cümle", + "paragraph": "Paragraf", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Para birimi kodu", + "language_code": "Dil etiketi", + "custom_list": "Özel liste" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG Optimize", + "subtitle": "SVG yapıştırın, tarayıcıda SVGO ile küçültün ve UTF-8 boyutunu önce/sonra karşılaştırın.", + "inputLabel": "Girdi", + "outputLabel": "Optimize", + "placeholder": " parçası veya tüm dosyayı yapıştırın…", + "outputPlaceholder": "Optimize SVG burada görünür…", + "chars": "{count} karakter • {bytes} bayt (UTF-8)", + "preview": "Önizleme", + "localNote": "İşlem tamamen tarayıcıda yerel. Hiçbir şey yüklenmez.", + "stats": { + "title": "Boyut (UTF-8)", + "before": "Önce", + "after": "Sonra", + "savedBytes": "{count} bayt tasarruf ({percent}%)", + "noChange": "Azalma yok — zaten minimal veya boş çıktı." + }, + "actions": { + "clear": "Temizle", + "sample": "Örnek yükle", + "copy": "Kopyala", + "copied": "Kopyalandı", + "download": "İndir" + }, + "errors": { + "optimizeFailed": "Bu SVG optimize edilemedi." + } + }, + "UnitConverter": { + "title": "Birim Dönüştürücü", + "subtitle": "43 kategoride 323 birim arasında dönüştürün", + "category": "Kategori", + "from": "Kaynak", + "to": "Hedef", + "value": "Değer", + "result": "Sonuç", + "swapUnits": "Birimleri değiştir", + "placeholder": "Değer girin", + "copyResult": "Sonucu kopyala", + "searchCategory": "Kategori ara...", + "selectCategory": "Bir kategori seçin", + "noResults": "Kategori bulunamadı." + }, + "DockerComposeGenerator": { + "title": "Docker Compose Oluşturucu", + "subtitle": "Popüler veritabanları, önbellek, mesaj kuyrukları, ters proxy ve gözlemlenebilirlik seçin — yerel geliştirme için hazır docker-compose.yml kopyalayın.", + "searchPlaceholder": "Ara (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Hizmetler", + "selectedCount": "{count, plural, one {# hizmet} other {# hizmet}}", + "outputTitle": "docker-compose.yml", + "copy": "Kopyala", + "copied": "Kopyalandı!", + "download": "İndir", + "emptyState": "Compose YAML üretmek için bir veya daha fazla hizmet seçin.", + "hintPorts": "Varsayılan ana bilgisayar bağlantı noktaları çakışmaları azaltır; Docker bağlama hatası verirse değiştirin. Paylaşılan ortamlardan önce demo parolalarını değiştirin.", + "categories": { + "databases": "Veritabanları ve depolar", + "cache": "Önbellek ve bellek içi", + "queues": "Kuyruklar ve mesajlaşma", + "search": "Arama ve günlükler", + "storage": "Nesne depolama", + "proxy": "Ters proxy ve ingress", + "observability": "Metrikler ve izleme", + "mail": "E-posta (yerel geliştirme)", + "admin": "Yönetim ve BI arayüzleri" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Temizle" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (Redis uyumlu)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (Kafka uyumlu)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (S3 uyumlu)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (SMTP yakalama)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (geliştirme sunucusu)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "2048 'e ulaşmak için döşemeleri kaydır ve birleştir", + "score": "Not", + "best": "En iyi", + "newGame": "Yeni oyun", + "howToPlay": "Taşları taşımak için ok tuşlarını kullanın (veya mobil cihazlarda kaydırın). Aynı sayıdaki döşemeler birleşir. Kazanmak için 2048 'e ulaş!", + "youWin": "2048 'e ulaştınız!", + "keepGoing": "Devam Et", + "gameOver": "Oyun bitti", + "tryAgain": "Tekrar Deneyin", + "gameOverMessage": "Başka hamle kalmadı.", + "undo": "Geri al" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "tamamlanan aşamalar", + "time": "Zaman", + "best": "En iyi", + "difficulty": { + "easy": "Basit ", + "medium": "Orta", + "hard": "Zor" + }, + "stage": "Aşama", + "solved": "Çözüldü", + "score": "Not", + "nextStage": "Bir Sonraki Aşama", + "replay": "Tekrar Oynayın", + "erase": "Sil", + "howToPlay": "Bir hücreye tıklayın, ardından bir numaraya dokunun veya 1 -9 'a basın. Backspace veya Delete hücreyi temizler. Cesur sayılar ipucudur. Gridi doldurun, böylece her satır, sütun ve 3×3 kutu tekrarsız 1 -9 içerir.", + "pencil": "Kalem", + "undo": "Geri Al", + "hint": "İpucu", + "hintsUsed": "ipucu kullanıldı", + "mistakes": "Hatalar", + "failed": "Başarısız!", + "mistakeMode": "3-hata modu" + }, + "Snake": { + "title": "Yılan", + "subtitle": "Yemek ye, büyü, duvarlardan ve kendinden kaçın", + "score": "Puan", + "best": "En İyi", + "startPrompt": "Herhangi bir ok tuşuna basın", + "startButton": "Oyunu Başlat", + "gameOver": "Oyun Bitti", + "playAgain": "Tekrar Oyna", + "howToPlay": "Ok tuşlarını veya WASD'ı kullan. Büyümek için ye. Hız her 50 puanda artar.", + "diffEasy": "Kolay", + "diffNormal": "Normal", + "diffHard": "Zor", + "diffInfinite": "Sonsuz", + "orTap": "veya aşağıya dokun", + "howToPlayInfinite": "Duvarlar sarılır — duvar ölümü yok. Büyümek için ye.", + "shieldActive": "Kalkan aktif", + "speedBoost": "Hız artışı", + "powerSpeed": "Hız x2", + "powerShield": "Bir ölümden kurtar", + "powerShrink": "Kuyruğu kısalt" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Redis Bağlantıları", + "heroTitle": "Redis Commander", + "heroDesc": "Redis örneklerine bağlanın. Anahtarlara göz atın, değerleri düzenleyin ve komutlar çalıştırın. Kimlik bilgileri tarayıcınızda şifrelenir.", + "heroAddConnection": "Bağlantı Ekle", + "vaultLockedTitle": "Kasa kilitli", + "vaultLockedDesc": "Kayıtlı bağlantılara erişmek için kasanızı açın." + } + }, + "Minesweeper": { + "title": "Mayın Tarlası", + "subtitle": "Bir mayına basmadan tüm güvenli hücreleri bulun", + "mines": "Mayınlar", + "newGame": "Yeni oyun", + "win": "Kazandınız!", + "loss": "Kaybettiniz!", + "playAgain": "Tekrar oyna", + "tryAgain": "Tekrar dene", + "startPrompt": "Başlamak için herhangi bir hücreye tıklayın", + "difficulty": { + "beginner": "Başlangıç", + "intermediate": "Orta", + "expert": "Uzman" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Düşen blokları sıralara yerleştirerek tamamlayın", + "next": "Sıradaki", + "score": "Puan", + "best": "En iyi", + "lines": "Satırlar", + "level": "Seviye", + "pause": "Duraklat", + "resume": "Devam et", + "start": "Başla", + "restart": "Yeniden başla", + "paused": "Duraklatıldı", + "gameOver": "Oyun bitti" + }, + "SshKeyGenerator": { + "title": "SSH / RSA Anahtar Oluşturucu", + "subtitle": "Tarayıcınızda Ed25519 veya RSA anahtar çiftleri oluşturun. Özel ve genel anahtarları OpenSSH ve PEM formatlarında indirin. Hiçbir şey yüklenmez.", + "keyTypeLabel": "Anahtar Türü", + "recommended": "önerilen", + "ed25519Desc": "Hızlı, küçük ve modern. GitHub, GitLab ve tüm büyük SSH sunucuları tarafından desteklenir.", + "rsa2048Desc": "Geniş uyumluluk. Çoğu kullanım için güvenli.", + "rsa4096Desc": "Eski sistemler için maksimum uyumluluk ve güç.", + "commentLabel": "Yorum (isteğe bağlı)", + "commentHint": "OpenSSH genel anahtar satırına eklenir.", + "generateBtn": "Anahtar Çifti Oluştur", + "generating": "Oluşturuluyor…", + "generatingLong": "Oluşturuluyor (biraz zaman alabilir)…", + "copy": "Kopyala", + "download": "İndir", + "privateKeyLabel": "Özel Anahtar (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Genel Anahtar (OpenSSH formatı)", + "publicKeySpkiLabel": "Genel Anahtar (SPKI PEM)", + "browserNote": "Tüm anahtarlar tarayıcınızda oluşturulur. Hiçbir şey herhangi bir sunucuya gönderilmez.", + "errors": { + "generationFailed": "Anahtar oluşturma başarısız" + } + }, + "DnsLookup": { + "title": "DNS Sorgulama", + "subtitle": "Herhangi bir alan adı için DNS kayıtlarını sorgulayın — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Alan adını girin (ör. github.com)", + "lookupBtn": "Sorgula", + "looking": "Sorgulanıyor…", + "refreshBtn": "Yenile", + "recordTypesLabel": "Kayıt türleri", + "allBtn": "Tümü", + "tryLabel": "Dene:", + "errors": { + "lookupFailed": "DNS sorgusu başarısız" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt Üretici ve Doğrulayıcı", + "subtitle": "Parolaları bcrypt ile hash'leyin ve hash'leri doğrulayın — her şey tarayıcınızda kalır", + "tabs": { + "hash": "Hash'le", + "verify": "Doğrula" + }, + "showPassword": "Parolayı göster", + "hidePassword": "Parolayı gizle", + "copy": "Kopyala", + "copied": "Kopyalandı", + "hash": { + "passwordLabel": "Parola", + "passwordPlaceholder": "Hash'lenecek bir parola girin…", + "costLabel": "Maliyet faktörü", + "costHint": "Daha yüksek maliyet, her adımdaki işi ikiye katlar. Üretim için genellikle 10-12 tercih edilir.", + "slowWarning": "13 ve üzeri maliyet faktörlerinin hesaplanması birkaç saniye sürebilir.", + "generate": "Hash oluştur", + "generating": "Hash'leniyor…", + "resultLabel": "Bcrypt hash'i", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Hash anatomisi", + "algorithm": "algoritma", + "cost": "maliyet", + "salt": "salt (22 karakter)", + "hash": "hash (31 karakter)" + } + }, + "verify": { + "passwordLabel": "Parola", + "passwordPlaceholder": "Kontrol edilecek parolayı girin…", + "hashLabel": "Bcrypt hash'i", + "hashPlaceholder": "$2b$10$…", + "idle": "Karşılaştırmak için bir parola ve bir bcrypt hash'i girin.", + "invalidHash": "Bu, geçerli bir bcrypt hash'i gibi görünmüyor.", + "checking": "Karşılaştırılıyor…", + "match": "Eşleşti — parola bu hash'i üretiyor.", + "noMatch": "Eşleşmedi — parola bu hash'i üretmiyor." + } + }, + "ChmodCalculator": { + "title": "Chmod Hesaplayıcı", + "subtitle": "Unix dosya izinlerini onay kutuları, sekizlik ve sembolik gösterim arasında dönüştürün", + "grid": { + "title": "İzinler", + "read": "Okuma", + "write": "Yazma", + "execute": "Çalıştırma", + "owner": "Sahip", + "group": "Grup", + "others": "Diğerleri" + }, + "special": { + "title": "Özel bitler", + "setuid": "setuid", + "setgid": "setgid", + "sticky": "sticky" + }, + "octal": { + "label": "Sekizlik", + "placeholder": "755 veya 4755", + "invalid": "3 veya 4 sekizlik basamak girin (0-7)." + }, + "symbolic": { + "label": "Sembolik", + "placeholder": "rwxr-xr-x", + "invalid": "rwxr-xr-x gibi 9 karakter girin (s/S, t/T kabul edilir)." + }, + "output": { + "title": "Komut", + "copy": "Kopyala", + "copied": "Kopyalandı" + }, + "explain": { + "line": "{cls} şunları yapabilir: {perms}.", + "none": "{cls} hiçbir izne sahip değil.", + "setuid": "setuid: dosya, sahibinin ayrıcalıklarıyla çalışır.", + "setgid": "setgid: dosya, grubunun ayrıcalıklarıyla çalışır (bir dizindeki yeni dosyalar onun grubunu devralır).", + "sticky": "sticky: bu dizin içindeki bir dosyayı yalnızca sahibi silebilir veya yeniden adlandırabilir." + }, + "presets": { + "title": "Yaygın ön ayarlar", + "octal": "Sekizlik", + "permissions": "İzinler", + "description": "Tipik kullanım", + "p644": "Normal dosyalar: sahibi yazar, herkes okur.", + "p755": "Çalıştırılabilirler ve dizinler: herkes okuyabilir ve girebilir.", + "p600": "Özel dosyalar: yalnızca sahibi okur ve yazar.", + "p700": "Özel çalıştırılabilirler ve dizinler: yalnızca sahibi.", + "p664": "Grup tarafından yazılabilen dosyalar.", + "p775": "Grup tarafından yazılabilen dizinler ve çalıştırılabilirler.", + "p400": "Sahibi için salt okunur (ör. SSH özel anahtarları)." + } + }, + "CodeScreenshot": { + "title": "Kod Ekran Görüntüsü", + "subtitle": "Kodu güzel, paylaşılabilir görsellere dönüştürün", + "languageLabel": "Dil", + "autoDetect": "Otomatik algıla", + "backgroundLabel": "Arka plan", + "paddingLabel": "İç boşluk", + "filenameLabel": "Dosya adı", + "filenamePlaceholder": "example.ts", + "windowLabel": "Pencere noktaları", + "themeLabel": "Pencere teması", + "themeDark": "Koyu", + "themeLight": "Açık", + "scaleLabel": "Dışa aktarma ölçeği", + "copyImage": "Görseli kopyala", + "copied": "Kopyalandı", + "exportPng": "PNG dışa aktar", + "inputLabel": "Kod", + "detected": "Algılanan: {language}", + "clear": "Temizle", + "placeholder": "Kodunuzu buraya yapıştırın…", + "previewLabel": "Önizleme", + "emptyPreview": "Kod önizlemeniz burada görünecek", + "exportFailed": "Görsel dışa aktarılamadı", + "copyUnsupported": "Görsel kopyalama bu tarayıcıda desteklenmiyor", + "copyFailed": "Görsel kopyalanamadı", + "backgrounds": { + "ocean": "Okyanus", + "sunset": "Gün batımı", + "candy": "Şeker", + "forest": "Orman", + "midnight": "Gece yarısı", + "gold": "Altın", + "aurora": "Kutup ışığı", + "slate": "Arduvaz", + "transparent": "Şeffaf" + } + }, + "CssGenerators": { + "title": "CSS Üreticileri", + "subtitle": "box-shadow, border-radius ve akışkan clamp() — CSS'i kopyalayın.", + "preview": "Önizleme", + "output": "CSS", + "copy": "Kopyala", + "copied": "Kopyalandı", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Akışkan clamp()" + }, + "boxShadow": { + "presets": "Ön ayarlar", + "layer": "Katman", + "addLayer": "Katman ekle", + "removeLayer": "Katmanı kaldır", + "x": "X", + "y": "Y", + "blur": "Bulanıklık", + "spread": "Yayılım", + "color": "Renk", + "alpha": "Alfa", + "inset": "İç gölge", + "presetNames": { + "subtleCard": "İnce kart", + "elevated": "Yükseltilmiş", + "hardOffset": "Sert kaydırma", + "neonGlow": "Neon parıltı", + "inner": "İç", + "layeredSmooth": "Katmanlı yumuşak" + } + }, + "borderRadius": { + "modeLabel": "Mod", + "modes": { + "simple": "Köşe başına", + "blob": "Organik blob" + }, + "unit": "Birim", + "linkCorners": "Köşeleri bağla", + "randomize": "Rastgele", + "horizontal": "Yatay yarıçaplar", + "vertical": "Dikey yarıçaplar", + "corners": { + "tl": "Sol üst", + "tr": "Sağ üst", + "br": "Sağ alt", + "bl": "Sol alt" + } + }, + "clamp": { + "minFont": "Min yazı tipi (px)", + "maxFont": "Maks yazı tipi (px)", + "minViewport": "Min viewport (px)", + "maxViewport": "Maks viewport (px)", + "rootFont": "Kök yazı tipi (px)", + "viewportWidth": "Simüle edilen viewport", + "computedSize": "Hesaplanan boyut", + "slope": "Eğim", + "intercept": "Kesişim", + "previewText": "Önizleme için örnek metin", + "invalid": "Geçerli ve birbirinden farklı viewport sınırları girin." + } + }, + "ExifViewer": { + "title": "EXIF Görüntüleyici ve Temizleyici", + "subtitle": "Fotoğraf meta verilerini inceleyin ve tarayıcınızda temizleyin", + "dropTitle": "Bir görseli buraya bırakın", + "dropHint": "veya göz atmak için tıklayın — görseliniz asla cihazınızdan çıkmaz", + "browse": "Görsel seç", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Meta veri okunuyor…", + "parseFailed": "Bu görsel okunamadı", + "clear": "Görseli kaldır", + "gpsWarning": "GPS konumu içeriyor", + "openMap": "OpenStreetMap'te görüntüle", + "noMetadata": "Bu görselde gömülü meta veri bulunamadı.", + "facts": { + "name": "Ad", + "type": "Tür", + "dimensions": "Boyutlar", + "size": "Boyut" + }, + "groups": { + "camera": "Kamera", + "exposure": "Pozlama", + "timestamps": "Zaman damgaları", + "gps": "GPS Konumu", + "software": "Yazılım ve Diğer" + }, + "gpsCard": { + "latitude": "Enlem", + "longitude": "Boylam", + "altitude": "Rakım" + }, + "raw": { + "title": "Tüm meta veriler", + "copyJson": "JSON kopyala", + "copied": "Kopyalandı" + }, + "remove": { + "title": "Meta veriyi kaldır", + "jpegDescription": "Görseli yeniden sıkıştırmadan EXIF, XMP, IPTC ve yorumları kayıpsız kaldırın.", + "reencodeDescription": "Gömülü meta veriyi temizlemek için görseli yeniden kodlayın. Bu, pikselleri yeniden sıkıştırır.", + "keepIcc": "Renk profilini koru (ICC)", + "stripButton": "Meta veriyi temizle", + "reencodeButton": "Yeniden kodla ve temizle", + "unsupported": "Bu dosya türü için meta veri kaldırma desteklenmiyor.", + "failed": "Meta veri kaldırılamadı", + "before": "Önce", + "after": "Sonra", + "download": "Temizlenmiş görseli indir" + } + }, + "FaviconGenerator": { + "title": "Favicon Üreticisi", + "subtitle": "Tek bir görselden favicon ve uygulama simgeleri oluşturun", + "dropTitle": "Bir görseli buraya bırakın", + "dropHint": "PNG, JPEG veya SVG — göz atmak için tıklayın", + "browse": "Görsel seç", + "clear": "Görseli kaldır", + "backgroundLabel": "Arka plan", + "transparent": "Şeffaf", + "solidColor": "Düz renk", + "cornerLabel": "Köşe şekli", + "cornerNone": "Kare", + "cornerRounded": "Yuvarlatılmış", + "cornerCircle": "Daire", + "maskPaddingLabel": "Maskelenebilir iç boşluk", + "maskable": "maskable", + "previewLabel": "Önizleme", + "copy": "Kopyala", + "headSnippet": "HTML head parçacığı", + "downloadIco": "favicon.ico indir", + "downloadZip": "Tümünü indir (.zip)" + }, + "JsonDiff": { + "title": "JSON Karşılaştırma", + "subtitle": "İki JSON belgesinin anlamsal, yapı farkında karşılaştırması — anahtar sırası ve biçimlendirme yok sayılır", + "options": { + "ignoreArrayOrder": "Dizi sırasını yok say" + }, + "loadBothSamples": "Örnekleri yükle", + "sample": "Örnek", + "format": "Biçimlendir", + "clear": "Temizle", + "formatError": "Biçimlendirilemiyor: giriş geçerli JSON değil", + "placeholder": "JSON'ı buraya yapıştırın veya yazın…", + "panels": { + "left": "Orijinal (sol)", + "right": "Değiştirilmiş (sağ)", + "results": "Farklar" + }, + "summary": { + "added": "{count} eklendi", + "removed": "{count} kaldırıldı", + "changed": "{count} değişti" + }, + "types": { + "added": "Eklendi", + "removed": "Kaldırıldı", + "changed": "Değişti" + }, + "copyChanges": "JSON olarak kopyala", + "copied": "Kopyalandı", + "empty": "Farklarını görmek için her iki panele de JSON yapıştırın.", + "identical": "İki belge anlamsal olarak aynı." + }, + "JsonpathPlayground": { + "title": "JSONPath Oyun Alanı", + "subtitle": "JSON belgelerini JSONPath veya JMESPath ile sorgulayın ve sonuçları canlı görün", + "engineLabel": "Motor", + "queryLabel": "Sorgu", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Kopya kâğıdı", + "cheats": { + "jpAllTitles": "Tüm kitap başlıkları", + "jpAllAuthors": "Belgenin herhangi bir yerindeki her yazar", + "jpFirstBook": "İlk kitap", + "jpLastBook": "Son kitap", + "jpSlice": "İlk iki kitap (dilim)", + "jpFilterPrice": "40'tan ucuz kitaplar", + "jpFilterInStock": "Stoktaki kitapların başlıkları", + "jpRecursive": "Tüm değerler, özyinelemeli", + "jmAllTitles": "Tüm kitap başlıkları", + "jmFirstAuthor": "İlk kitabın yazarı", + "jmLastBook": "Son kitap", + "jmSlice": "İlk iki kitap (dilim)", + "jmFilterPrice": "40'tan ucuz kitapların başlıkları", + "jmCount": "Stoktaki kitapları say", + "jmProjection": "Her kitabı yeni bir nesneye dönüştür", + "jmSort": "Fiyata göre sıralanmış başlıklar" + }, + "panels": { + "input": "JSON girişi", + "results": "Sonuçlar" + }, + "sample": "Örnek", + "format": "Biçimlendir", + "clear": "Temizle", + "formatError": "Biçimlendirilemiyor: giriş geçerli JSON değil", + "placeholder": "JSON'ı buraya yapıştırın veya yazın…", + "outputPlaceholder": "Eşleşmeler burada görünecek…", + "matchCount": "{count, plural, one {# eşleşme} other {# eşleşme}}", + "copy": "Kopyala", + "copied": "Kopyalandı" + }, + "KeycodeInspector": { + "title": "Tuş Kodu İnceleyici", + "subtitle": "JavaScript klavye olayını incelemek için herhangi bir tuşa basın", + "capture": { + "prompt": "Herhangi bir tuşa basın…", + "ready": "Dinleniyor — klavyenizde herhangi bir tuşa basın", + "clickToFocus": "Odaklanmak için buraya tıklayın, sonra herhangi bir tuşa basın" + }, + "hero": { + "spaceKey": "(Boşluk)", + "keyLabel": "event.key", + "repeat": "yineleme" + }, + "copyJson": "JSON kopyala", + "copiedJson": "Olay JSON'ı panoya kopyalandı", + "deprecated": "kullanımdan kaldırıldı", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Standart", + "left": "Sol", + "right": "Sağ", + "numpad": "Sayı tuş takımı", + "unknown": "Bilinmiyor" + }, + "history": { + "title": "Geçmiş", + "clear": "Geçmişi temizle", + "empty": "Tuş basmaları burada görünecek." + } + }, + "MarkdownTableGenerator": { + "title": "Markdown Tablosu Üreticisi", + "subtitle": "Bir ızgarayı düzenleyin, elektronik tablodan yapıştırın ve düzgün hizalanmış bir Markdown veya HTML tablosu kopyalayın", + "addRow": "Satır ekle", + "addColumn": "Sütun ekle", + "importLabel": "İçe aktar", + "importPlaceholder": "TSV (Excel / Google Sheets'ten) veya CSV'yi buraya yapıştırın…", + "importButton": "Izgaraya içe aktar", + "pasteClipboard": "Panodan yapıştır", + "importEmpty": "İçe aktarılacak bir şey yok — önce birkaç satır yapıştırın", + "clipboardError": "Pano okunamadı", + "clear": "Temizle", + "panels": { + "editor": "Tablo düzenleyici", + "output": "Çıktı" + }, + "align": { + "left": "Sola hizala", + "center": "Ortala", + "right": "Sağa hizala" + }, + "headerPlaceholder": "Başlık", + "cellPlaceholder": "Hücre", + "removeRow": "Satırı kaldır", + "removeColumn": "Sütunu kaldır", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Kopyala", + "copied": "Kopyalandı" + }, + "TimezoneConverter": { + "title": "Saat Dilimi Dönüştürücü", + "subtitle": "Toplantı planlaması için bir anı dünya genelinde dönüştürün.", + "dateTimeLabel": "Tarih ve saat", + "now": "Şimdi", + "sourceZoneLabel": "Kaynak saat dilimi", + "selectZone": "Dilim seç", + "searchZone": "Saat dilimlerinde ara...", + "noZone": "Saat dilimi bulunamadı.", + "scrubLabel": "Zamanı kaydır", + "reset": "Sıfırla", + "targetsLabel": "Hedef dilimler", + "addZone": "Dilim ekle", + "remove": "Kaldır", + "localBadge": "Yerel", + "invalidDate": "Geçerli bir tarih ve saat girin.", + "bands": { + "work": "Çalışma saatleri (09:00–18:00)", + "edge": "Sınır saatleri (erken / akşam)", + "night": "Gece" + } + }, + "TokenCounter": { + "title": "LLM Token Sayacı", + "subtitle": "Token'ları sayın, parçaları görselleştirin ve API maliyetini tahmin edin.", + "inputLabel": "Metin", + "sample": "Örnek", + "clear": "Temizle", + "placeholder": "Token saymak için metin, kod veya JSON yapıştırın...", + "encoding": "Kodlama", + "loading": "Tokenizer yükleniyor...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o-series)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Token", + "characters": "Karakter", + "words": "Kelime", + "ratio": "Token/kelime", + "ratioSub": "yaklaşık" + }, + "claudeRow": "Claude (yaklaşık)", + "estimate": "tahmin", + "viz": { + "title": "Token görselleştirme", + "toggle": "Göster", + "hint": "Her token'ı bir parça olarak renklendirmek için değiştirin.", + "empty": "Token'ları görselleştirmek için metin girin.", + "truncated": "{total} token'ın ilk {limit} tanesi gösteriliyor." + }, + "cost": { + "title": "Maliyet hesaplayıcı", + "disclaimer": "düzenlenebilir varsayılanlar — güncel fiyatlandırmayı doğrulayın", + "model": "Model", + "expectedOutput": "Beklenen çıktı token'ları", + "customInput": "Giriş $/MTok", + "customOutput": "Çıkış $/MTok", + "inputCost": "Giriş maliyeti", + "outputCost": "Çıkış maliyeti", + "total": "Toplam", + "per1000": "1000 çağrı başına" + } + }, + "WebhookTester": { + "title": "Webhook Test Aracı", + "subtitle": "Geçici bir URL oluşturun ve gelen webhook isteklerini canlı inceleyin.", + "intro": { + "title": "İstekleri yakalamaya başlamak için bir webhook URL'si oluşturun", + "hint": "Her gelen isteği kaydeden, tahmin edilemez bir URL alacaksınız. 24 saat sonra süresi dolar." + }, + "createBtn": "Webhook URL'si oluştur", + "creating": "Oluşturuluyor…", + "urlLabel": "Webhook URL'niz", + "expiresIn": "Süre bitişi: {countdown}", + "copyUrlBtn": "URL kopyala", + "copied": "Kopyalandı", + "clearBtn": "Temizle", + "newBinBtn": "Yeni URL", + "requestsTitle": "İstekler", + "selectPrompt": "Ayrıntılarını incelemek için bir istek seçin.", + "expiredNotice": "Bu webhook URL'sinin süresi doldu. Test etmeye devam etmek için yeni bir tane oluşturun.", + "base64Badge": "base64", + "truncatedBadge": "kırpıldı", + "empty": { + "title": "İstekler bekleniyor…", + "hint": "URL'nize bir istek gönderin ve anında burada görünsün. Bunu deneyin:" + }, + "detail": { + "receivedAt": "Alındı", + "source": "Kaynak IP", + "contentType": "Content-Type", + "query": "Sorgu parametreleri", + "headers": "Başlıklar", + "body": "Gövde", + "copyBody": "Gövdeyi kopyala", + "emptyBody": "Gövde yok" + }, + "errors": { + "createFailed": "Webhook URL'si oluşturulamadı. Lütfen tekrar deneyin.", + "clearFailed": "İstekler temizlenemedi. Lütfen tekrar deneyin." + } + }, + "WebsocketTester": { + "title": "WebSocket Test Aracı", + "subtitle": "Bir WebSocket uç noktasına bağlanın ve mesajları canlı gönderin, alın ve inceleyin", + "urlLabel": "Uç nokta URL'si", + "protocolsLabel": "Alt protokoller", + "protocolsPlaceholder": "isteğe bağlı, virgülle ayrılmış", + "connect": "Bağlan", + "disconnect": "Bağlantıyı kes", + "status": { + "disconnected": "Bağlı değil", + "connecting": "Bağlanıyor", + "connected": "Bağlandı", + "error": "Hata" + }, + "messages": { + "connecting": "{url} adresine bağlanılıyor…", + "connected": "Bağlantı kuruldu", + "connectedWithProtocol": "Bağlantı kuruldu (protokol: {protocol})", + "invalidUrl": "Geçersiz URL — ws:// veya wss:// ile başlamalı", + "connectFailed": "Bağlantı açılamadı", + "socketError": "Bir bağlantı hatası oluştu", + "notConnected": "Bağlı değil — önce bağlanın", + "binaryMessage": "İkili bir mesaj alındı" + }, + "log": { + "title": "Mesaj Günlüğü", + "empty": "Henüz mesaj yok. Bağlanın ve göndermeye başlayın.", + "copyAll": "Tümünü kopyala", + "download": ".txt olarak indir", + "clear": "Günlüğü temizle", + "prettyPrint": "JSON'ı biçimlendir", + "showRaw": "Ham metni göster" + }, + "composer": { + "placeholder": "Gönderilecek bir mesaj yazın…", + "send": "Gönder", + "formatJson": "JSON olarak biçimlendir", + "invalidJson": "Mesaj geçerli JSON değil", + "hint": "Göndermek için Cmd/Ctrl+Enter" + }, + "ping": { + "label": "Otomatik ping", + "seconds": "sn", + "intervalTitle": "Saniye cinsinden ping aralığı", + "payloadPlaceholder": "ping yükü" + } + }, + "WhoisLookup": { + "title": "Whois Sorgulama", + "subtitle": "Herhangi bir alan adı veya IP adresinin kayıt bilgilerini RDAP üzerinden sorgulayın", + "placeholder": "example.com veya 8.8.8.8", + "lookupBtn": "Sorgula", + "looking": "Sorgulanıyor…", + "refreshBtn": "Yenile", + "tryLabel": "Deneyin:", + "copy": "Kopyala", + "copied": "Kopyalandı", + "notAvailable": "Mevcut değil", + "summary": { + "registrar": "Kayıt kuruluşu", + "network": "Ağ" + }, + "dates": { + "created": "Oluşturuldu", + "updated": "Güncellendi", + "expires": "Süre bitişi" + }, + "sections": { + "status": "Durum", + "nameservers": "Ad sunucuları", + "contacts": "Kişiler", + "rawRdap": "Ham RDAP" + }, + "contacts": { + "roles": "Roller", + "name": "Ad", + "organization": "Kuruluş" + }, + "errors": { + "invalidTarget": "Geçerli bir alan adı veya IP adresi girin", + "lookupFailed": "Sorgulama başarısız (durum {status})" + } + } +} diff --git a/apps/desktop-ui/messages/uk.json b/apps/desktop-ui/messages/uk.json new file mode 100644 index 00000000..13ebe4a1 --- /dev/null +++ b/apps/desktop-ui/messages/uk.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Панель", + "settings": "Налаштування", + "apiClient": "API Клієнт", + "httpStatusCodes": "Коди стану HTTP", + "passwordManager": "Менеджер паролів", + "environmentManager": "Менеджер середовищ", + "bookmarks": "Закладки", + "jsonFormatter": "Форматувальник JSON", + "emailValidator": "Перевірка email", + "nosqlExplorer": "Провідник баз даних", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Пісочниця шифрування", + "jwtDecoder": "Декодер JWT", + "certificatePemDecoder": "Декодер сертифіката / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Форматувач GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Парсер URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Секрет / ключ API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Перевірка контрасту", + "jsonSchemaGenerator": "Генератор JSON-схеми", + "imageToBase64": "Зображення до Base64", + "imageCompressor": "Стиснення зображень", + "cssGradientBuilder": "Конструктор градієнтів CSS", + "gitignoreGenerator": "Генератор .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Фрагменти коду", + "qrCodeGenerator": "Генератор QR-кодів", + "dnsLookup": "DNS-запит", + "ipSubnetCalculator": "Калькулятор IP / підмережі", + "hashGenerator": "Генератор хешів", + "hmacGenerator": "Генератор HMAC", + "totpGenerator": "Код TOTP / 2FA", + "markdownPreview": "Перегляд Markdown", + "numberBaseConverter": "Конвертер систем", + "formatConverter": "Конвертер форматів", + "mockDataGenerator": "Генератор тестових даних", + "svgOptimizer": "Оптимізатор SVG", + "unitConverter": "Конвертер одиниць", + "dockerComposeGenerator": "Генератор Docker Compose", + "breakRoom": "Кімната відпочинку", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Форматувальник YAML", + "sshKeyGenerator": "Генератор ключів SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Калькулятор Chmod", + "codeScreenshot": "Скриншот коду", + "cssGenerators": "Генератори CSS", + "exifViewer": "Перегляд EXIF", + "faviconGenerator": "Генератор favicon", + "jsonDiff": "Порівняння JSON", + "jsonpathPlayground": "Пісочниця JSONPath", + "keycodeInspector": "Інспектор клавіш", + "markdownTableGenerator": "Таблиця Markdown", + "timezoneConverter": "Конвертер часових поясів", + "tokenCounter": "Лічильник токенів", + "webhookTester": "Тестер вебхуків", + "websocketTester": "Тестер WebSocket", + "whoisLookup": "Whois-запит" + }, + "Help": { + "title": "Довідка та документація", + "subtitle": "Як MyDevTools працює, що робить кожен застосунок і як безпека та шифрування захищають ваші дані.", + "tabs": { + "overview": "Огляд", + "apps": "Застосунки", + "security": "Безпека", + "tips": "Поради" + }, + "overview": { + "whatIs": { + "title": "Що таке MyDevTools?", + "description": "Робочий простір розробника з входом: панель та набір інструментів для щоденних задач.", + "p1": "Після входу застосунок встановлює сесію з MyDevTools API, щоб інструменти могли завантажувати та зберігати ваші дані. Бічна панель показує всі застосунки; непотрібні інструменти можна приховати в", + "settingsLink": "Налаштуваннях", + "p1After": ".", + "p2": "Деякі функції використовують майстер‑пароль (або глобальний ключ сховища), щоб секрети на кшталт паролів і рядків підключення шифрувалися на вашому пристрої до того, як потраплять на сервер." + }, + "quickLinks": { + "title": "Швидкі посилання", + "dashboard": "Панель", + "settings": "Налаштування" + } + }, + "apps": { + "intro": "Відкрийте будь-який інструмент з бічної панелі або панелі. Нижче — коротка довідка для кожного застосунку.", + "open": "Відкрити", + "howItWorks": "Як працює", + "generic": "Користуйтеся цим інструментом з бічної панелі — там знаходиться його основний робочий процес." + }, + "security": { + "session": { + "title": "Вхід і API‑сесія", + "description": "Ідентичність і те, як браузер взаємодіє з бекендом.", + "items": { + "authentication": "Автентифікація використовує Firebase для входу. Після входу застосунок обмінює Firebase ID‑токен на cookies API.", + "accessTokens": "Токени доступу — це JWT, підписані на сервері (HS256), які підтверджують користувача при виклику API.", + "refreshTokens": "Токени оновлення зберігаються в HttpOnly cookies; сервер зберігає SHA-256 хеш токена, а не сам токен." + } + }, + "encryption": { + "title": "Шифрування, яке варто знати", + "description": "Криптографія на стороні клієнта для сховища та чутливих даних підключень.", + "p1": "Для менеджера паролів, менеджера середовищ і збережених підключень у NoSQL Explorer вебзастосунок використовує Web Crypto API браузера:", + "items": { + "kdf": "PBKDF2 з SHA-256, 100 000 ітерацій і випадковою сіллю виводить 256‑бітний ключ з майстер‑пароля.", + "aes": "AES‑256‑GCM шифрує дані; для кожного шифрування використовується випадковий IV 12 байт.", + "scope": "Похідний ключ залишається в браузері (не експортується). Сервер отримує лише шифртекст та IV, а не майстер‑пароль чи секрети у відкритому вигляді." + }, + "warning": "Якщо ви забудете майстер‑пароль, зашифровані дані неможливо відновити. Зберігайте його безпечно." + }, + "logout": { + "title": "Вихід і локальні дані", + "body": "Вихід очищає секрети в пам’яті, стан сховища та (за потреби) матеріал майстер‑ключа з IndexedDB, а також завершує API‑сесію. Дані, що зберігаються лише в локальному сховищі, можуть залишатися, доки ви не очистите їх у браузері." + } + }, + "tips": { + "title": "Безпечне використання MyDevTools", + "items": { + "masterPassword": "Використовуйте сильний і унікальний майстер‑пароль для функцій сховища.", + "apiClient": "Ставтеся до API Client як до будь‑якого HTTP‑клієнта: не вставляйте продакшн‑секрети в спільні середовища, якщо не розумієте, хто може їх бачити.", + "nosql": "Провідник баз даних підключається до баз, які ви налаштовуєте; використовуйте облікові записи з мінімальними правами та мережеві правила за можливості.", + "json": "JSON‑форматувальник працює локально в браузері — все одно уникайте вставки дуже чутливих даних на спільних пристроях.", + "settings": "Налаштуйте видимі інструменти в Налаштуваннях, щоб бічна панель залишалася охайною." + } + } + }, + "Settings": { + "title": "Налаштування", + "language": "Мова", + "spanish": "Spanish", + "english": "English", + "french": "Français", + "arabic": "عربى", + "catalan": "Catalan", + "chineseSimplified": "Chinese (Simplified)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "German", + "danish": "Danish", + "afrikaans": "Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polish", + "vietnamese": "Tiếng Việt", + "ukrainian": "Українська", + "norwegian": "Норвезька" + }, + "SettingsPage": { + "title": "Налаштування", + "subtitle": "Керуйте налаштуваннями облікового запису та вподобаннями.", + "userProfile": { + "title": "Профіль користувача", + "description": "Ваша особиста інформація та дані облікового запису.", + "avatarAlt": "Користувач", + "anonymousUser": "Анонімний користувач", + "noEmailProvided": "Email не вказано", + "notLoggedIn": "Не авторизовано." + }, + "tools": { + "title": "Інструменти та функції", + "description": "Оберіть, які інструменти відображатимуться на бічній панелі навігації." + }, + "appearance": { + "title": "Зовнішній вигляд", + "description": "Налаштуйте вигляд програми на вашому пристрої.", + "themePreference": "Вибір теми", + "themes": { + "light": "Світла", + "dark": "Темна", + "system": "Системна" + } + }, + "language": { + "title": "Мова", + "description": "Встановіть мову, яка використовується на платформі.", + "selectLanguage": "Обрати мову", + "selectPlaceholder": "Оберіть мову", + "helpText": "Зміна мови оновить меню та мітки на всій платформі.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Spanish)", + "ar": "عربى (Arabic)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Greek)", + "de": "Deutsch (German)", + "da": "Dansk (Danish)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Італійська (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski (Polish)", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Доброго ранку", + "afternoon": "Доброго дня", + "evening": "Доброго вечора" + }, + "commaName": ", {name}", + "welcomeBack": "З поверненням", + "welcomeBackNamed": "З поверненням, {name}", + "tagline": "Що ви хочете створити сьогодні?", + "brandName": "MyDevTools", + "stats": { + "tools": "Інструменти", + "favorites": "Обране", + "pinned": "Закріплені", + "recent": "Нещодавно використані" + }, + "sections": { + "recentlyUsed": "Нещодавно використані", + "favorites": "Обране", + "whatsNew": "Що нового", + "pinned": "Закріплені" + }, + "groups": { + "apps": "Програми" + }, + "toolCard": { + "defaultDescription": "Дослідіть цей інструмент для кращої функціональності.", + "launchTool": "Запустити" + }, + "tools": { + "toDo": { + "title": "Завдання", + "description": "Ефективно керуйте списками справ." + }, + "notes": { + "title": "Нотатки", + "description": "Записуйте ідеї та організовуйте думки." + }, + "passwordManager": { + "title": "Менеджер паролів", + "description": "Безпечно зберігайте та керуйте паролями." + }, + "environmentManager": { + "title": "Менеджер середовищ", + "description": "Зберігайте змінні у стилі .env за проєктом і середовищем з тегами." + }, + "bookmarks": { + "title": "Закладки", + "description": "Керуйте та організовуйте закладки." + }, + "apiClient": { + "title": "API Клієнт", + "description": "Тестуйте та налагоджуйте HTTP-запити." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Досліджуйте бази даних MongoDB." + }, + "emailValidator": { + "title": "Перевірка email", + "description": "Перевіряйте та валідуйте email-адреси." + }, + "encryptionPlayground": { + "title": "Пісочниця шифрування", + "description": "AES-GCM із сирим ключем або паролем; JSON-пакети, Web Crypto лише в браузері." + }, + "jwtDecoder": { + "title": "Декодер JWT", + "description": "Декодуйте заголовок, корисне навантаження та строк дії JWT." + }, + "certificatePemDecoder": { + "title": "Декодер сертифіката / PEM", + "description": "Декодуйте сертифікати X.509 і CSR: суб’єкт, видавник, строк дії, SAN." + }, + "sshKeyGenerator": { + "title": "Генератор ключів SSH / RSA", + "description": "Генеруйте пари ключів Ed25519 або RSA у браузері. Завантажуйте у форматах OpenSSH та PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Форматувач GraphQL", + "description": "Форматуйте GraphQL із підсвітленням синтаксису й простим конструктором запитів." + }, + "yamlFormatter": { + "title": "Форматувальник YAML", + "description": "Форматуйте, перевіряйте та конвертуйте YAML у JSON у браузері." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Парсер URL", + "description": "Розбийте URL на протокол, хост, шлях, параметри запиту та хеш." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Секрет / ключ API", + "description": "Криптографічно випадкові рядки з налаштовуваним алфавітом, довжиною та масовим експортом." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Перевірка контрасту", + "description": "Коефіцієнт контрасту WCAG 2.1 для тексту й тла (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Генератор JSON-схеми", + "description": "Виведіть JSON-схему та типізовані моделі з прикладу JSON." + }, + "imageToBase64": { + "title": "Зображення до Base64", + "description": "Перетворюйте зображення на URI даних або необроблені рядки Base64." + }, + "imageCompressor": { + "title": "Стиснення зображень", + "description": "Стисніть JPEG, PNG і WebP локально повзунком якості." + }, + "cssGradientBuilder": { + "title": "Конструктор градієнтів CSS", + "description": "Візуальний конструктор градієнтів CSS із керуванням кутом і кольорами." + }, + "gitignoreGenerator": { + "title": "Генератор .gitignore", + "description": "Миттєво створюйте об’єднані файли .gitignore із понад 500 стеків технологій." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Перетворюйте CSV або Excel на JSON і експортуйте назад у CSV чи XLSX." + }, + "snippetManager": { + "title": "Фрагменти коду", + "description": "Зберігайте фрагменти з підсвічуванням синтаксису, авто-визначенням мови та форматуванням." + }, + "qrCodeGenerator": { + "title": "Генератор QR-кодів", + "description": "Створюйте PNG QR-коди з тексту або URL у браузері." + }, + "ipSubnetCalculator": { + "title": "Калькулятор IP / підмережі", + "description": "IPv4 та IPv6 CIDR: маска, діапазони й кількість адрес." + }, + "dnsLookup": { + "title": "DNS-запит", + "description": "Запитуйте DNS-записи для будь-якого домену — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Генератор хешів", + "description": "MD5, SHA-1 і SHA-2 для тексту або файлів." + }, + "hmacGenerator": { + "title": "Генератор HMAC", + "description": "Підписуйте вебхуки та тестуйте інтеграції API з HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Генератор коду TOTP / 2FA", + "description": "Вставте секрет Base32 з автентифікатора; шестизначний код оновлюється кожні 30 с—зручно для перевірки входу та MFA." + }, + "markdownPreview": { + "title": "Перегляд Markdown", + "description": "Живий рендерер Markdown з експортом HTML і конвертацією HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Конвертер систем числення", + "description": "Перетворення цілих чисел між основами 2–36." + }, + "formatConverter": { + "title": "Конвертер форматів", + "description": "Конвертуйте між JSON, YAML, TOML та XML у будь-якому напрямку. Підтримуються усі 12 комбінацій." + }, + "mockDataGenerator": { + "title": "Генератор тестових даних", + "description": "Задайте стовпці, оберіть типи й експортуйте набори для сидів, Postman чи тестів. До 5000 рядків, усе в браузері — без завантаження на сервер." + }, + "httpStatusCodes": { + "title": "Коди стану HTTP", + "description": "Шукайте коди стану HTTP з описами (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Оптимізатор SVG", + "description": "Вставте SVG, приберіть зайве через SVGO та подивіться економію розміру UTF-8." + }, + "unitConverter": { + "title": "Конвертер одиниць", + "description": "Конвертуйте між 323 одиницями у 43 наукових і технічних категоріях." + }, + "dockerComposeGenerator": { + "title": "Генератор Docker Compose", + "description": "Оберіть бази даних, кеші, черги, проксі та образи спостережуваності; експортуйте docker-compose.yml для локальних стеків." + }, + "game2048": { + "title": "2048", + "description": "Просуньте та об 'єднайте плитки, щоб досягти 2048 року. Класична гра-головоломка з числами." + }, + "sudoku": { + "title": "Судоку", + "description": "Класична числова головоломка. 3 рівні складності, по 150 етапів." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Переглядайте ключі, перевіряйте значення та виконуйте команди на будь-якому екземплярі Redis." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Генератор і перевірка Bcrypt", + "description": "Хешуйте паролі за допомогою bcrypt і перевіряйте наявні хеші." + }, + "chmodCalculator": { + "title": "Калькулятор Chmod", + "description": "Перетворюйте права Unix між вісімковою, символьною формою та прапорцями." + }, + "codeScreenshot": { + "title": "Скриншот коду", + "description": "Перетворюйте вихідний код на красиві зображення для публікації." + }, + "cssGenerators": { + "title": "Генератори CSS", + "description": "Генератори box-shadow, border-radius та адаптивного clamp()." + }, + "exifViewer": { + "title": "Перегляд і видалення EXIF", + "description": "Переглядайте та видаляйте метадані EXIF з фото локально." + }, + "faviconGenerator": { + "title": "Генератор favicon", + "description": "Створюйте favicon, іконки застосунків і фрагменти маніфесту." + }, + "jsonDiff": { + "title": "Порівняння JSON", + "description": "Семантичне порівняння двох документів JSON з урахуванням структури." + }, + "jsonpathPlayground": { + "title": "Пісочниця JSONPath", + "description": "Запитуйте JSON за допомогою JSONPath або JMESPath і дивіться результати наживо." + }, + "keycodeInspector": { + "title": "Інспектор клавіш", + "description": "Натисніть клавішу, щоб побачити її коди події JavaScript і модифікатори." + }, + "markdownTableGenerator": { + "title": "Генератор таблиць Markdown", + "description": "Створюйте таблиці Markdown та HTML у табличному редакторі." + }, + "timezoneConverter": { + "title": "Конвертер часових поясів", + "description": "Перетворюйте час між поясами з підказками про зміну дня та робочі години." + }, + "tokenCounter": { + "title": "Лічильник токенів LLM", + "description": "Рахуйте токени LLM, візуалізуйте чанки та оцінюйте вартість API." + }, + "webhookTester": { + "title": "Тестер вебхуків", + "description": "Захоплюйте та досліджуйте вхідні запити вебхуків у реальному часі." + }, + "websocketTester": { + "title": "Тестер WebSocket", + "description": "Підключайтеся до ендпоінтів WebSocket і обмінюйтеся повідомленнями в реальному часі." + }, + "whoisLookup": { + "title": "Whois-запит", + "description": "Дізнавайтеся дані реєстрації доменів та IP через RDAP." + } + }, + "tabs": { + "apps": "Застосунки", + "analytics": "Аналітика", + "ui": "Інтерфейс" + }, + "analytics": { + "title": "Ваші дані коротко", + "subtitle": "Лічильники синхронізовано з обліковим записом на сервері.", + "loadError": "Не вдалося завантажити аналітику.", + "retry": "Повторити", + "loginHint": "Увійдіть, щоб бачити аналітику та налаштування інтерфейсу.", + "passwordEntries": "Записів паролів", + "bookmarks": "Закладки", + "bookmarkFolders": "Папки закладок", + "tasksTotal": "Завдання", + "tasksCompleted": "Виконано", + "tasksOngoing": "У процесі", + "tasksNotStarted": "Не розпочато", + "projects": "Проєкти", + "nosqlConnections": "Підключення до БД", + "notes": "Нотатки", + "apiClientCollections": "Колекції API", + "apiClientEnvironments": "Оточення API", + "apiClientHistory": "Записи історії API", + "jsonFormatterDocuments": "Документи JSON-форматера", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Фрагменти коду", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Налаштування інтерфейсу", + "subtitle": "Тема, мова та видимі інструменти на бічній панелі — з налаштувань облікового запису.", + "loadError": "Не вдалося завантажити налаштування.", + "loginHint": "Увійдіть, щоб бачити збережені налаштування інтерфейсу.", + "theme": "Тема", + "accent": "Колір акценту", + "locale": "Мова", + "enabledTools": "Видимі інструменти", + "favoriteTools": "Обрані інструменти", + "toolsWithUsage": "Інструменти зі статистикою" + }, + "signIn": "Увійти", + "noResults": "Жоден інструмент не відповідає вашому пошуку", + "noResultsHint": "Спробуйте інше ключове слово або очистіть фільтр.", + "filterAll": "Усі", + "loginCta": { + "description": "Увійдіть, щоб відстежувати нещодавно використані інструменти." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Завантаження...", + "myTasksTitle": "Мої завдання", + "statsLine": "{total} завдань • {percent}% виконано", + "statsLineDesktop": "{total} всього • {percent}% виконано" + }, + "filters": { + "all": "Усі", + "total": "Всього", + "allProjects": "Усі проєкти", + "export": "Експорт", + "kanban": "Канбан", + "list": "Список", + "searchPlaceholder": "Пошук завдань...", + "searchAria": "Пошук завдань", + "clearSearchAria": "Очистити пошук", + "foundTasks": "Знайдено {count} {count, plural, one {завдання} few {завдання} other {завдань}}", + "filterByAllAria": "Фільтр: Усі", + "filterByStatusAria": "Фільтр за статусом: {status}" + }, + "status": { + "not-started": { + "label": "Не розпочато", + "description": "Завдання, які ще не розпочаті" + }, + "ongoing": { + "label": "В процесі", + "description": "Завдання, які зараз виконуються" + }, + "completed": { + "label": "Завершено", + "description": "Завдання, які виконані" + } + }, + "priorities": { + "high": "Високий", + "medium": "Середній", + "low": "Низький" + }, + "form": { + "addNewTaskLabel": "Додати нове завдання", + "multilinePlaceholder": "Додайте деталі завдання... (Shift+Enter для нового рядка)", + "singlelinePlaceholder": "Що потрібно зробити? Натисніть Enter щоб додати", + "taskInputAria": "Поле введення завдання", + "projectPlaceholder": "Проєкт", + "selectProject": "Обрати проєкт", + "errorSelectProject": "Будь ласка, оберіть або створіть новий проєкт для додавання завдання.", + "hintMultiline": "Shift+Enter — новий рядок, Enter — додати", + "hintSingleline": "Enter щоб додати", + "shortcutsAria": "Комбінації клавіш", + "shortcutsLabel": "Комбінації", + "shortcutAddTask": "Додати завдання", + "shortcutNewLine": "Новий рядок", + "shortcutClear": "Очистити", + "addButton": "Додати" + }, + "list": { + "ariaLabel": "Список завдань", + "loading": "Завантаження завдань...", + "emptyTitle": "Завдань не знайдено", + "emptyPrimary": "Почніть, додавши перше завдання вище! Натисніть Enter для швидкого додавання.", + "emptySecondary": "Спробуйте змінити фільтри або пошуковий запит." + }, + "mobileDrawer": { + "addTaskSrOnly": "Додати завдання", + "addNewTaskTitle": "Додати нове завдання", + "addNewTaskDescription": "Створіть нове завдання для відстеження прогресу.", + "cancel": "Скасувати" + }, + "kanban": { + "ariaLabel": "Канбан-дошка", + "loading": "Завантаження завдань...", + "dropHere": "Перетягніть завдання сюди", + "noTasksYet": "Завдань поки немає", + "emptyColumnAria": "Порожня колонка", + "columnAria": "Колонка {title} з {count} завданнями", + "countInColumnAria": "{count} завдань у {title}" + }, + "taskItem": { + "confirmDelete": "Ви впевнені, що хочете видалити це завдання?", + "statusPlaceholder": "Статус", + "changeStatusAria": "Змінити статус завдання", + "taskOptionsAria": "Опції завдання", + "editTask": "Редагувати завдання", + "copyTask": "Копіювати завдання", + "copied": "Скопійовано!", + "deleteTask": "Видалити завдання", + "statusHeading": "Статус", + "editTaskAria": "Редагувати завдання" + }, + "editDialog": { + "title": "Редагувати завдання", + "description": "Оновіть деталі завдання, додайте підзавдання, встановіть пріоритети тощо.", + "taskTitleLabel": "Назва завдання *", + "taskTitlePlaceholder": "Що потрібно зробити?", + "descriptionLabel": "Опис", + "descriptionPlaceholder": "Додайте більше деталей про це завдання...", + "statusLabel": "Статус", + "priorityLabel": "Пріоритет", + "dueDateLabel": "Термін виконання", + "pickDate": "Обрати дату", + "clearDate": "Очистити дату", + "projectLabel": "Проєкт", + "selectProjectPlaceholder": "Обрати проєкт", + "noProject": "Без проєкту", + "timeEstimateLabel": "Оцінка часу (хвилини)", + "timeEstimatePlaceholder": "Скільки часу це займе?", + "tagsLabel": "Теги", + "customTagNamePlaceholder": "Назва тегу", + "subtasksLabel": "Підзавдання", + "addSubtaskPlaceholder": "Додати підзавдання...", + "cancel": "Скасувати", + "saving": "Збереження...", + "saveChanges": "Зберегти зміни" + }, + "export": { + "title": "Експорт завдань", + "description": "Експортуйте завдання у форматах JSON, CSV або Excel.", + "projectLabel": "Проєкт", + "statusLabel": "Статус", + "tagLabel": "Тег", + "allProjects": "Усі проєкти", + "allStatuses": "Усі статуси", + "allTags": "Усі теги", + "exportAsJsonTitle": "Експорт у JSON", + "exportAsJsonDescription": "Експортуйте дані завдань включно з підзавданнями, тегами та метаданими.", + "exportJson": "Експорт JSON", + "exportAsCsvExcelTitle": "Експорт у CSV / Excel", + "exportAsCsvExcelDescription": "Експортуйте завдання для табличних програм.", + "exportCsv": "Експорт CSV", + "exportExcel": "Експорт Excel", + "close": "Закрити", + "toastNoTasks": "Немає завдань для експорту з поточними фільтрами", + "toastExportedJson": "Експортовано {count} завдань у JSON!", + "toastExportedCsv": "Експортовано {count} завдань у CSV!", + "toastExportedExcel": "Експортовано {count} завдань у Excel!", + "toastFailedJson": "Не вдалося експортувати у JSON", + "toastFailedCsv": "Не вдалося експортувати у CSV", + "toastFailedExcel": "Не вдалося експортувати у Excel", + "unknownProject": "Невідомий проєкт" + }, + "ack": { + "taskAddedTitle": "Завдання додано успішно", + "taskAddedFailedTitle": "Не вдалося додати завдання", + "taskUpdatedTitle": "Завдання оновлено успішно", + "taskUpdatedFailedTitle": "Не вдалося оновити завдання", + "taskStatusUpdateFailedTitle": "Не вдалося оновити статус завдання", + "taskMovedTitle": "Завдання переміщено до {status}", + "tryAgain": "Будь ласка, спробуйте ще раз.", + "taskDeletedTitle": "Завдання \"{text}\" видалено", + "undo": "Скасувати", + "taskRestoredTitle": "Завдання відновлено", + "taskRestoredSuccessTitle": "Завдання відновлено успішно", + "taskRestoreFailedTitle": "Не вдалося відновити завдання. Спробуйте ще раз.", + "taskDeleteFailedTitle": "Не вдалося видалити завдання. Спробуйте ще раз.", + "tasksImportedTitle": "Успішно імпортовано {count} {count, plural, one {завдання} few {завдання} other {завдань}}!", + "tasksImportFailedTitle": "Не вдалося імпортувати завдання. Спробуйте ще раз.", + "unknownProject": "Невідомий проєкт", + "projectCreatedTitle": "Проєкт створено успішно", + "projectCreateFailedTitle": "Не вдалося створити проєкт", + "projectUpdatedTitle": "Проєкт оновлено успішно", + "projectUpdateFailedTitle": "Не вдалося оновити проєкт", + "projectDeletedTitle": "Проєкт видалено успішно", + "projectDeleteFailedTitle": "Не вдалося видалити проєкт" + } + }, + "Notes": { + "page": { + "loading": "Завантаження..." + }, + "layout": { + "mobileNavTitle": "Навігація нотатками", + "openMenuAria": "Відкрити меню нотаток" + }, + "sidebar": { + "title": "Нотатки", + "createNoteAria": "Створити нотатку", + "searchPlaceholder": "Пошук нотаток...", + "loading": "Завантаження...", + "noNotesFound": "Нотаток не знайдено.", + "noNotesYet": "Нотаток поки немає. Натисніть +, щоб створити.", + "inParent": "у {parentTitle}", + "untitled": "Без назви", + "addSubPage": "Додати підсторінку", + "delete": "Видалити", + "deleteConfirmTitle": "Видалити нотатку?", + "deleteConfirmDescription": "Ви впевнені, що хочете видалити \"{title}\"? Цю дію не можна скасувати.", + "cancel": "Скасувати", + "confirmDelete": "Видалити", + "noteOptionsAria": "Опції нотатки" + }, + "editor": { + "emptyState": "Оберіть нотатку або створіть нову, щоб почати.", + "titlePlaceholder": "Без назви", + "saving": "Збереження…", + "ready": "Готово", + "saved": "Збережено {time}", + "stats": "{words} слів · {read} хв читання", + "template": "Шаблон", + "applyTemplate": "Застосувати шаблон", + "exportMdLabel": ".md", + "exportMd": "Експортувати як Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Експортувати як HTML", + "focusMode": "Режим фокусування", + "exitFocusMode": "Вийти з режиму фокусування", + "save": "Зберегти", + "addTag": "Додати тег…", + "removeTag": "Видалити тег {tag}", + "loading": "Завантаження…", + "searchHint": "для пошуку", + "focusHint": "режим фокусування", + "notePath": "Шлях нотатки" + }, + "context": { + "authRequiredError": "Користувач не автентифікований", + "defaultTitle": "Без назви", + "cloudSyncImageError": "Увімкніть хмарну синхронізацію, щоб зберігати зображення нотаток. Текстові нотатки залишаються локальними." + } + }, + "PasswordManager": { + "page": { + "loading": "Завантаження...", + "title": "Менеджер паролів", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Не вдалося розшифрувати паролі", + "toastLoadFailed": "Не вдалося завантажити паролі", + "toastUnlockedAuto": "Сховище розблоковано автоматично", + "toastAutoUnlockFailed": "Автоматичне розблокування не вдалося. Розблокуйте вручну.", + "toastConnectFailed": "Не вдалося підʼєднатися до сервера", + "toastAutoUnlockWarning": "Сховище розблоковано, але автоматичне розблокування може не працювати після перезавантаження", + "errorIncorrectPassword": "Неправильний майстер-пароль", + "errorUnlockFailed": "Не вдалося розблокувати сховище", + "errorPasswordsMismatch": "Паролі не збігаються", + "errorPasswordTooShort": "Пароль має містити щонайменше 8 символів", + "errorSetupFailed": "Не вдалося налаштувати сховище", + "errorConnectFailed": "Не вдалося підʼєднатися до сервера.", + "mobileTitleCreate": "Створіть сховище", + "mobileTitleWelcome": "З поверненням", + "mobileSubtitleCreate": "Встановіть майстер-пароль для захисту облікових даних", + "mobileSubtitleUnlock": "Введіть майстер-пароль для розблокування", + "placeholderMasterPassword": "Майстер-пароль", + "placeholderConfirmPassword": "Підтвердіть пароль", + "labelMasterPassword": "Майстер-пароль", + "labelConfirmPassword": "Підтвердіть пароль", + "placeholderEnterMaster": "Введіть майстер-пароль", + "placeholderConfirmMaster": "Підтвердіть майстер-пароль", + "buttonCreateVault": "Створити сховище", + "buttonUnlock": "Розблокувати", + "buttonUnlockVault": "Розблокувати сховище", + "footerCreate": "Ваш пароль ніколи не зберігається і не може бути відновлений", + "footerUnlock": "Захищено наскрізним шифруванням", + "desktopTitleSetup": "Налаштування сховища паролів", + "desktopTitleUnlock": "Розблокувати сховище", + "desktopDescriptionSetup": "Створіть майстер-пароль для захисту облікових даних. Цей пароль ніколи не зберігається і не може бути відновлений.", + "desktopDescriptionUnlock": "Введіть майстер-пароль для доступу до захищених облікових даних." + }, + "list": { + "loading": "Завантаження паролів...", + "emptyTitle": "Сховище порожнє", + "emptyHintMobile": "Натисніть кнопку +, щоб додати перший пароль", + "emptyHintDesktop": "Ви ще не зберегли жодного паролю. Натисніть кнопку «Додати пароль» вище, щоб почати.", + "addPassword": "Додати пароль", + "searchPlaceholderMobile": "Пошук у сховищі...", + "searchPlaceholderDesktop": "Пошук у сховищі...", + "importExport": "Імпорт / експорт", + "lockVault": "Заблокувати сховище", + "allPasswordsTitle": "Усі паролі", + "passwordCount": "{count, plural, one {# пароль} few {# паролі} other {# паролів}}", + "vaultHealthDrawerTitle": "Стан сховища", + "noResults": "Не знайдено паролів за запитом \"{query}\"", + "tableService": "Сервіс", + "tableUsername": "Імʼя користувача", + "tablePassword": "Пароль", + "tableUrl": "URL", + "tableActions": "Дії", + "gridViewAria": "Сітка", + "listViewAria": "Список", + "passwordStrengthTitle": "Надійність пароля", + "deleteDialogTitle": "Видалити пароль", + "deleteDialogDescription": "Ви впевнені, що хочете видалити цей пароль? Цю дію не можна скасувати.", + "cancel": "Скасувати", + "delete": "Видалити", + "edit": "Редагувати" + }, + "toasts": { + "copiedPassword": "Пароль скопійовано до буфера обміну", + "copiedUsername": "Імʼя користувача скопійовано до буфера обміну", + "passwordDeleted": "Пароль видалено", + "deleteFailed": "Не вдалося видалити пароль", + "vaultLocked": "Сховище заблоковано", + "importedCount": "Успішно імпортовано {count, plural, one {# пароль} few {# паролі} other {# паролів}}" + }, + "form": { + "addTitle": "Додати новий пароль", + "editTitle": "Редагувати пароль", + "serviceName": "Назва сервісу", + "usernameEmail": "Імʼя користувача / email", + "password": "Пароль", + "urlOptional": "URL вебсайту (необовʼязково)", + "notesOptional": "Примітки (необовʼязково)", + "tags": "Теги", + "placeholderService": "напр. Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Введіть або згенеруйте пароль", + "placeholderUrl": "https://...", + "placeholderNotes": "Додаткові деталі...", + "placeholderTags": "Додайте теги (натисніть Enter або кому)", + "strength": "Надійність", + "strengthWeak": "Слабкий", + "strengthMedium": "Середній", + "strengthStrong": "Надійний", + "advancedGenerator": "Розширений генератор", + "cancel": "Скасувати", + "saving": "Збереження...", + "savePassword": "Зберегти пароль", + "updating": "Оновлення...", + "updatePassword": "Оновити пароль", + "addPasswordSrOnly": "Додати пароль", + "triggerAdd": "Додати пароль", + "toastSaved": "Пароль збережено успішно", + "toastSaveFailed": "Не вдалося зберегти пароль", + "toastUpdated": "Пароль оновлено успішно", + "toastUpdateFailed": "Не вдалося оновити пароль" + }, + "security": { + "vaultHealth": "Стан сховища", + "statusLabel": "Статус", + "totalPasswords": "Всього паролів", + "activeEntries": "Активні записи у сховищі", + "protected": "Захищено", + "weakPasswords": "Слабкі паролі", + "needingImprovement": "Паролі, що потребують покращення", + "allGood": "Все добре", + "reusedPasswords": "Повторно використані паролі", + "usedMultipleTimes": "Паролі, використані кілька разів", + "unique": "Унікальні", + "total": "Всього", + "weak": "Слабкі", + "reused": "Повторені", + "totalCountLabel": "{count, plural, one {# пароль} few {# паролі} other {# паролів}}", + "weakCountLabel": "{count} слабких", + "allSecure": "Усі безпечні", + "scoreExcellent": "Відмінно", + "scoreGood": "Добре", + "scoreFair": "Задовільно", + "scoreCritical": "Критично" + }, + "importExport": { + "title": "Імпорт / експорт сховища", + "description": "Керуйте даними паролів. Ви можете експортувати сховище у JSON або імпортувати з JSON-файлу.", + "tabExport": "Експорт", + "tabImport": "Імпорт", + "warningTitle": "Увага", + "warningBody": "Експортовані файли містять паролі у відкритому вигляді. Зберігайте цей файл у безпечному місці та видаліть після використання.", + "exportBlurb": "Завантажте все сховище як JSON-файл. Включає всі сервіси, імена користувачів, паролі та примітки.", + "downloadJson": "Завантажити JSON-експорт", + "pasteLabel": "Вставте дані JSON", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Вставте вміст раніше експортованого JSON-файлу сюди.", + "importing": "Імпорт...", + "importPasswords": "Імпортувати паролі", + "trigger": "Імпорт / експорт", + "toastExported": "Сховище успішно експортовано", + "toastExportFailed": "Не вдалося експортувати сховище", + "toastImportFailed": "Не вдалося імпортувати: невірний формат JSON" + }, + "card": { + "copyUsername": "Копіювати імʼя користувача", + "openUrl": "Відкрити URL", + "strength": "Надійність" + }, + "swipeable": { + "copy": "Копіювати", + "delete": "Видалити", + "copyPassword": "Копіювати пароль", + "copyUsername": "Копіювати імʼя користувача", + "edit": "Редагувати", + "deleteAction": "Видалити" + }, + "floatingMenu": { + "lockVault": "Заблокувати сховище", + "importExport": "Імпорт / експорт", + "addPassword": "Додати пароль" + }, + "generator": { + "tabPassword": "Пароль", + "tabPassphrase": "Парольна фраза", + "strength": "Надійність", + "lengthLabel": "Довжина: {length} символів", + "lengthBadge": "{length} симв.", + "characterSets": "Набори символів", + "uppercase": "Великі літери", + "lowercase": "Малі літери", + "numbers": "Цифри", + "symbols": "Символи", + "wordCountLabel": "Кількість слів: {count}", + "wordCountBadge": "{count, plural, one {# слово} few {# слова} other {# слів}}", + "separator": "Розділювач", + "capitalization": "Капіталізація", + "includeNumber": "Додати цифру" + } + }, + "EnvironmentManager": { + "subtitle": "Зашифровані змінні середовища за проєктом і середовищем.", + "page": { + "loading": "Завантаження…", + "title": "Менеджер середовищ", + "loadFailed": "Не вдалося завантажити набори середовищ" + }, + "list": { + "title": "Середовища", + "loading": "Завантаження наборів…", + "emptyTitle": "Поки немає наборів середовищ", + "emptyHint": "Створіть набір для кожного проєкту й середовища (наприклад, ключі API продакшену). Усе шифрується в браузері перед завантаженням.", + "searchPlaceholder": "Пошук за проєктом, середовищем, тегами, нотатками…", + "tagFilterPlaceholder": "Фільтр за тегом", + "variableCount": "{count, plural, one {# змінна} few {# змінні} other {# змінних}}", + "updatedAgo": "Оновлено {time}", + "copyDotEnv": "Копіювати .env", + "edit": "Редагувати", + "delete": "Видалити", + "cancel": "Скасувати", + "confirmDelete": "Видалити", + "deleteDialogTitle": "Видалити набір середовища?", + "deleteDialogDescription": "Зашифровані дані буде видалено з облікового запису. Це незворотно.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": ".env скопійовано в буфер обміну", + "deleted": "Набір середовища видалено", + "deleteFailed": "Не вдалося видалити" + }, + "form": { + "addTitle": "Новий набір середовища", + "editTitle": "Редагувати набір середовища", + "triggerAdd": "Додати набір середовища", + "addSrOnly": "Додати набір середовища", + "project": "Проєкт", + "environment": "Середовище", + "placeholderProject": "напр. billing-api", + "placeholderEnvironment": "напр. production", + "pasteEnvLabel": "Вставити .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Об'єднати вставлені рядки", + "variables": "Змінні", + "addRow": "Додати рядок", + "keyPlaceholder": "КЛЮЧ", + "valuePlaceholder": "значення", + "notesOptional": "Нотатки (необов'язково)", + "placeholderNotes": "Політика ротації, власник, посилання…", + "tags": "Теги", + "placeholderTags": "Додати теги (Enter або кома)", + "cancel": "Скасувати", + "save": "Зберегти", + "saving": "Збереження…", + "update": "Оновити", + "updating": "Оновлення…", + "toastSaved": "Набір середовища збережено", + "toastSaveFailed": "Не вдалося зберегти", + "toastUpdated": "Набір середовища оновлено", + "toastUpdateFailed": "Не вдалося оновити", + "toastPasteEmpty": "Рядків КЛЮЧ=значення не знайдено", + "toastPasteApplied": "Об'єднано {count} змінних з вставки", + "toastProjectEnvRequired": "Проєкт і середовище обов'язкові", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Завантаження..." + }, + "manager": { + "foldersHeading": "Теки", + "tagsHeading": "Теги", + "tagsMore": "+{count} ще", + "tagsShowLess": "Show less", + "searchPlaceholder": "Пошук закладок...", + "import": "Імпорт", + "export": "Експорт", + "exportAsHtml": "Експорт у HTML", + "exportAsJson": "Експорт у JSON", + "addBookmark": "Додати закладку", + "matchingQuery": "за запитом \"{query}\"", + "bookmarkCount": "{count, plural, one {# закладка} few {# закладки} other {# закладок}}", + "allBookmarks": "Усі закладки", + "uncategorized": "Без категорії", + "unknownFolder": "Невідомо" + }, + "card": { + "urlCopied": "URL скопійовано до буфера обміну", + "bookmarkDeleted": "Закладку видалено", + "edit": "Редагувати", + "delete": "Видалити", + "copyUrl": "Копіювати URL", + "deleteConfirmTitle": "Видалити закладку?", + "deleteConfirmDescription": "Це назавжди видалить \"{title}\".", + "cancel": "Скасувати" + }, + "grid": { + "emptyTitle": "Закладок поки немає", + "emptyDescription": "Додайте першу закладку або імпортуйте з браузера, щоб почати." + }, + "folderTree": { + "allBookmarks": "Усі закладки", + "addSubfolder": "Додати підтеку", + "rename": "Перейменувати", + "deleteFolderTitle": "Видалити теку \"{name}\"?", + "deleteFolderDescription": "Це видалить теку та всі підтеки. Закладки в цих теках буде переміщено до \"Без категорії\"." + }, + "import": { + "title": "Імпорт закладок", + "description": "Імпортуйте закладки з експортованого файлу браузера.", + "parseError": "Не вдалося розібрати файл закладок. Переконайтеся, що це дійсний експорт закладок.", + "successToast": "Імпортовано {bookmarks} закладок та {folders} тек", + "errorToast": "Не вдалося імпортувати закладки", + "chromeInstruction": "Меню → Закладки → Менеджер закладок → ⋮ → Експорт закладок", + "firefoxInstruction": "Меню → Закладки → Керування закладками → Імпорт і резервне копіювання → Експорт", + "safariInstruction": "Файл → Експорт закладок...", + "edgeInstruction": "Меню → Обране → ⋯ → Експорт обраного", + "dropZoneTitle": "Перетягніть файл закладок сюди", + "dropZoneHint": "або натисніть, щоб обрати (HTML або JSON)", + "selectedFileHint": "Натисніть, щоб обрати інший файл", + "errorTitle": "Помилка", + "previewTitle": "Попередній перегляд імпорту", + "previewBookmarks": "{count, plural, one {# закладка} few {# закладки} other {# закладок}}", + "previewFolders": "{count, plural, one {# тека} few {# теки} other {# тек}}", + "importModeLabel": "Режим імпорту", + "modeMerge": "Обʼєднати з існуючими закладками", + "modeReplace": "Замінити всі існуючі закладки", + "cancel": "Скасувати", + "importButton": "Імпорт", + "importButtonWithCount": "Імпортувати {count, plural, one {# закладку} few {# закладки} other {# закладок}}" + }, + "addBookmark": { + "titleAdd": "Додати закладку", + "titleEdit": "Редагувати закладку", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Введіть дійсний URL", + "titleLabel": "Назва", + "titlePlaceholder": "Назва закладки", + "titleRequired": "Введіть назву", + "descriptionLabel": "Опис (необовʼязково)", + "descriptionPlaceholder": "Додайте примітку про цю закладку", + "folderLabel": "Тека", + "folderPlaceholder": "Обрати теку", + "noFolder": "Без теки", + "tagsLabel": "Теги", + "tagsPlaceholder": "Додайте теги (натисніть Enter)", + "toastUpdated": "Закладку оновлено", + "toastAdded": "Закладку додано", + "cancel": "Скасувати", + "submitAdd": "Додати закладку", + "submitUpdate": "Оновити закладку" + }, + "addFolder": { + "title": "Створити теку", + "nameLabel": "Назва теки", + "namePlaceholder": "Моя тека", + "nameRequired": "Введіть назву теки", + "parentLabel": "Батьківська тека", + "parentPlaceholder": "Обрати батьківську теку", + "rootParent": "Корінь (без батьківської)", + "toastCreated": "Теку створено", + "cancel": "Скасувати", + "submit": "Створити теку" + }, + "editFolder": { + "title": "Перейменувати теку", + "nameLabel": "Назва теки", + "namePlaceholder": "Назва теки", + "nameRequired": "Введіть назву теки", + "toastRenamed": "Теку перейменовано", + "cancel": "Скасувати", + "save": "Зберегти" + } + }, + "EmailValidator": { + "header": { + "title": "Перевірка email", + "description": "Перевіряйте та валідуйте email-адреси на синтаксис, домен та доставляність." + }, + "tabs": { + "single": "Одинична перевірка", + "bulk": "Масове завантаження" + }, + "single": { + "cardTitle": "Одинична перевірка email", + "cardDescription": "Введіть email-адресу для перевірки доставляності та репутації.", + "placeholder": "Введіть email-адресу...", + "clearAria": "Очистити email", + "validate": "Перевірити", + "validating": "Перевірка...", + "formatError": "Введіть дійсну email-адресу" + }, + "toasts": { + "errorTitle": "Помилка", + "enterEmail": "Введіть email-адресу", + "invalidFormatTitle": "Невірний формат email", + "invalidFormatDescription": "Введіть дійсну email-адресу", + "validateFailedDescription": "Виникла помилка під час перевірки email", + "noEmailsTitle": "Emails не знайдено", + "noEmailsDescription": "Переконайтеся, що у вашому Excel є стовпець 'email'.", + "fileTooLargeTitle": "Файл занадто великий", + "fileTooLargeDescription": "Знайдено {total} emails. Тільки перші 4000 будуть перевірені.", + "successTitle": "Успішно", + "successValidated": "Перевірено {count} emails успішно.", + "batchFailedTitle": "Масова перевірка не вдалася", + "batchFailedDescription": "Виникла помилка під час масової перевірки.", + "noExportTitle": "Немає даних для експорту", + "noExportValidOnly": "Не знайдено дійсних emails для експорту.", + "noExportAll": "Немає результатів для експорту.", + "exportSuccessTitle": "Експорт успішний", + "exportSuccessExcelAll": "Експортовано {count} emails у Excel.", + "exportSuccessExcelValid": "Експортовано {count} дійсних emails у Excel.", + "exportSuccessCsvAll": "Експортовано {count} emails у CSV.", + "exportSuccessCsvValid": "Експортовано {count} дійсних emails у CSV." + }, + "disposableAlert": { + "title": "Виявлено одноразовий email", + "description": "Ця email-адреса належить до одноразового email-провайдера. Вона може бути тимчасовою або небезпечною." + }, + "score": { + "reliabilityTitle": "Оцінка надійності", + "scoreLabel": "Оцінка" + }, + "details": { + "sectionTitle": "Деталі перевірки" + }, + "validation": { + "syntaxLabel": "Синтаксис дійсний", + "syntaxTooltip": "Перевіряє, чи email-адреса має правильний формат (напр. user@domain.com)", + "domainLabel": "Домен існує", + "domainTooltip": "Перевіряє, чи існує доменне імʼя в email-адресі", + "mxLabel": "MX-записи знайдено", + "mxTooltip": "Перевіряє, чи домен має налаштовані MX-записи для прийому електронної пошти", + "mailboxLabel": "Поштова скринька існує", + "mailboxTooltip": "Перевіряє, чи існує конкретна поштова скринька/обліковий запис на поштовому сервері", + "disposableLabel": "Одноразовий email", + "disposableTooltip": "Вказує, чи email належить до тимчасового/одноразового email-сервісу (може бути ризикованим)", + "roleBasedLabel": "Рольовий email", + "roleBasedTooltip": "Визначає, чи це рольова email-адреса (напр. support@, info@), а не особиста" + }, + "copy": { + "emailCopied": "Email скопійовано!", + "copyTooltip": "Копіювати email" + }, + "bulk": { + "dropHere": "Перетягніть файл сюди", + "dragHere": "Перетягніть файл сюди", + "uploadHint": "Завантажте файл .xlsx, .xls або .csv зі стовпцем 'email' для перевірки до 4000 адрес.", + "filePrefix": "Файл:", + "selectFile": "Обрати файл", + "downloadTemplate": "Завантажити шаблон" + }, + "bulkLoading": { + "title": "Перевірка emails...", + "batchProgress": "Обробка пакету {current} з {total}..." + }, + "stats": { + "totalProcessed": "Всього оброблено", + "valid": "Дійсні", + "risky": "Ризикові", + "invalid": "Недійсні" + }, + "results": { + "detailedTitle": "Детальні результати", + "searchPlaceholder": "Пошук emails...", + "newVerification": "Нова перевірка", + "exportAll": "Експортувати все", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Експортувати дійсні ({count})" + }, + "showingResults": "Показано {shown} з {total} результатів", + "table": { + "email": "Email", + "status": "Статус", + "score": "Оцінка", + "checks": "Перевірки" + }, + "noSearchResults": "Не знайдено результатів за запитом \"{query}\"", + "checksTooltip": { + "syntax": "Синтаксис", + "domain": "Домен", + "mx": "MX", + "mailbox": "Поштова скринька" + }, + "exportColumns": { + "email": "Email", + "status": "Статус", + "score": "Оцінка", + "syntax": "Синтаксис", + "domain": "Домен", + "mx": "MX", + "mailbox": "Поштова скринька", + "disposable": "Одноразовий", + "roleBased": "Рольовий", + "valid": "Дійсний", + "invalid": "Недійсний", + "yes": "Так", + "no": "Ні" + }, + "sheets": { + "validEmails": "Дійсні emails", + "results": "Результати", + "template": "Шаблон" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Провідник", + "heroSubtitle": "Підключайтеся до баз даних для керування колекціями, виконання запитів та візуалізації даних.", + "featureMultiTitle": "Мульти-підключення", + "featureMultiDesc": "Керуйте кількома підключеннями до баз даних одночасно.", + "featureMongoTitle": "Підтримка MongoDB", + "featureMongoDesc": "Вбудована підтримка MongoDB з розширеною фільтрацією.", + "featureQueryTitle": "Розумні запити", + "featureQueryDesc": "Потужний конструктор запитів з підсвічуванням синтаксису.", + "connectCta": "Підключитися до MongoDB", + "footerNote": "Наразі підтримується {db}. Інші бази даних незабаром.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Оберіть колекцію", + "selectCollectionDesc": "Виберіть колекцію на бічній панелі, щоб переглянути документи та почати дослідження даних.", + "dialogAddConnection": "Додати підключення", + "toastConnectionAdded": "Підключення додано", + "toastFetchFailed": "Не вдалося отримати документи для {collection}", + "toastDocDeleted": "Документ видалено", + "deleteDocTitle": "Ви абсолютно впевнені?", + "deleteDocDescription": "Цю дію не можна скасувати. Документ буде назавжди видалено з колекції.", + "closeAllTitle": "Закрити всі вкладки?", + "closeAllDescription": "Ви впевнені, що хочете закрити всі вкладки? Незбережені зміни у фільтрах запитів будуть втрачені.", + "closeAllConfirm": "Закрити всі", + "cancel": "Скасувати", + "delete": "Видалити" + }, + "connection": { + "titleEdit": "Редагувати підключення", + "titleConnect": "Підключитися до MongoDB", + "subtitleEdit": "Оновіть деталі підключення нижче.", + "subtitleConnect": "Введіть рядок підключення MongoDB, щоб почати дослідження даних.", + "labelName": "Назва", + "placeholderName": "напр. Production DB", + "labelConnectionString": "Рядок підключення", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Формат: mongodb://username:password@host:port/database", + "testConnection": "Тестувати підключення", + "connecting": "Підключення...", + "updateConnect": "Оновити та підключити", + "connect": "Підключити", + "cancelEdit": "Скасувати редагування", + "savedConnections": "Збережені підключення", + "emptySaved": "Збережених підключень поки немає.", + "lastUsed": "Останнє використання {time}", + "toastTestOk": "Підключення успішне!", + "toastTestFail": "Підключення не вдалося: {message}", + "toastUpdated": "Підключення оновлено", + "confirmDelete": "Ви впевнені, що хочете видалити це збережене підключення?", + "toastDeletedConn": "Підключення видалено", + "toastDeleteFail": "Не вдалося видалити підключення", + "copyStringTitle": "Копіювати рядок підключення", + "toastStringCopied": "Рядок підключення скопійовано!", + "editTitle": "Редагувати", + "deleteTitle": "Видалити" + }, + "sidebar": { + "explorer": "Провідник", + "add": "Додати", + "tooltipAdd": "Додати нове підключення", + "tooltipRefresh": "Оновити підключення", + "searchPlaceholder": "Пошук...", + "loadingConnections": "Завантаження підключень...", + "noConnectionsFound": "Підключень не знайдено", + "toastLoadFail": "Не вдалося завантажити підключення", + "toastConnectFail": "Не вдалося підключитися до {name}", + "toastCollectionsFail": "Не вдалося отримати колекції для {name}", + "toastRenamed": "Підключення перейменовано", + "toastRenameFail": "Не вдалося перейменувати підключення", + "confirmDeleteConnection": "Ви впевнені, що хочете видалити підключення \"{name}\"?", + "toastDeleted": "Підключення видалено", + "toastDeleteConnFail": "Не вдалося видалити підключення", + "confirmDropDb": "Ви впевнені, що хочете видалити базу даних \"{name}\"? Цю дію не можна скасувати.", + "toastDbDropped": "Базу даних {name} видалено", + "confirmDropCollection": "Ви впевнені, що хочете видалити колекцію \"{name}\"? Цю дію не можна скасувати.", + "toastCollectionDropped": "Колекцію {name} видалено", + "toastCollectionRenamed": "Колекцію перейменовано на {name}", + "toastDatabaseRenamed": "Базу даних перейменовано на {name}", + "connecting": "Підключення...", + "connectionFailed": "Підключення не вдалося", + "noDatabases": "Немає баз даних", + "loading": "Завантаження...", + "noCollections": "Немає колекцій", + "renameCollectionTitle": "Перейменувати колекцію", + "renameDatabaseTitle": "Перейменувати базу даних", + "placeholderNewCollection": "Нова назва колекції", + "placeholderNewDatabase": "Нова назва бази даних", + "rename": "Перейменувати", + "refresh": "Оновити", + "copyDbName": "Копіювати назву БД", + "toastDbNameCopied": "Назву бази даних скопійовано до буфера обміну", + "dropDatabase": "Видалити базу даних", + "dropCollection": "Видалити колекцію", + "menuRename": "Перейменувати", + "menuCopyConnectionString": "Копіювати рядок підключення", + "menuDeleteConnection": "Видалити підключення", + "toastSidebarStringCopied": "Рядок підключення скопійовано!", + "cancel": "Скасувати", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} документів", + "table": "Таблиця", + "json": "JSON", + "tree": "Дерево", + "tooltipTable": "Табличний вигляд", + "tooltipJson": "JSON вигляд", + "tooltipTree": "Деревовидний вигляд", + "itemsPerPage": "Елементів на сторінку", + "perPage": "{n} / стор.", + "prevPage": "Попередня сторінка", + "nextPage": "Наступна сторінка", + "expandAll": "Розгорнути все", + "collapseAll": "Згорнути все", + "refreshData": "Оновити дані", + "insert": "Вставити", + "export": "Експорт", + "actions": "Дії", + "invalidJson": "Невірний вміст JSON", + "copiedClipboard": "Скопійовано до буфера обміну", + "docUpdated": "Документ оновлено успішно", + "docInserted": "Документ вставлено успішно", + "invalidJsonShort": "Невірний JSON", + "docCopied": "Документ скопійовано до буфера обміну", + "openFullEditor": "Відкрити у повному редакторі", + "cellArray": "Масив({length})", + "cellObject": "Обʼєкт", + "cellArrayBracket": "Масив[{length}]", + "emptyFilterTitle": "Документи за вашим запитом не знайдено", + "emptyFilterHint": "Спробуйте змінити фільтр або очистити запит.", + "clearFilter": "Очистити фільтр", + "emptyCollectionTitle": "Ця колекція порожня", + "emptyCollectionHint": "Вставте документ, щоб почати.", + "insertDocument": "Вставити документ", + "formatJson": "Форматувати JSON", + "compactJson": "Компактний JSON", + "copyClipboard": "Копіювати до буфера обміну", + "copyJson": "Копіювати JSON", + "duplicate": "Дублювати", + "editDoc": "Редагувати документ", + "deleteDoc": "Видалити документ", + "documentLabel": "Документ {n}", + "editDialogTitle": "Редагувати документ", + "insertDialogTitle": "Вставити документ", + "viewValueTitle": "Переглянути значення", + "saveChanges": "Зберегти зміни", + "close": "Закрити", + "cancel": "Скасувати", + "insertButton": "Вставити", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Активний фільтр застосовано", + "filterActiveTooltip": "Фільтр активний", + "closeAll": "Закрити всі" + }, + "query": { + "migrateHistory": "Історію запитів перенесено до хмари", + "saveHistoryFail": "Не вдалося зберегти історію запитів", + "deleteQueryFail": "Не вдалося видалити запит", + "invalidJsonQuery": "Невірний JSON-запит", + "placeholder": "напр. { \"status\": \"active\" }", + "clearQuery": "Очистити запит", + "openAdvanced": "Відкрити розширений редактор", + "visualBuilder": "Візуальний конструктор запитів", + "filterRules": "Правила фільтрації", + "addRule": "Додати правило", + "noFilters": "Фільтри не застосовані. Додайте правило для фільтрації документів.", + "where": "ДЕ", + "and": "ТА", + "fieldPlaceholder": "Поле", + "opPlaceholder": "Оп.", + "typePlaceholder": "Тип", + "opRegex": "Regex", + "opIn": "В", + "opNin": "Не в", + "opExists": "Існує", + "typeAuto": "Авто", + "typeString": "Рядок", + "typeNumber": "Число", + "typeBoolean": "Логічний", + "typeDate": "Дата", + "typeObjectId": "ObjectId", + "true": "Так", + "false": "Ні", + "valuePlaceholder": "Значення", + "valueListPlaceholder": "значення1, значення2", + "clearFilters": "Очистити фільтри", + "applyFilters": "Застосувати фільтри", + "queryHistory": "Історія запитів", + "noSavedQueries": "Збережених запитів поки немає", + "advancedEditorTitle": "Розширений редактор запитів", + "templatesPlaceholder": "Шаблони", + "templateEmptyAgg": "Порожня агрегація", + "templateMatch": "$match (Фільтр)", + "templateLookup": "$lookup (Обʼєднання)", + "templateGroup": "$group (Підрахунок/Сума)", + "format": "Формат", + "formattedJson": "Форматований JSON", + "runQuery": "Виконати запит" + }, + "export": { + "selectFieldsError": "Оберіть щонайменше одне поле для експорту", + "success": "Успішно експортовано до {filename}", + "fail": "Не вдалося експортувати дані", + "title": "Експорт даних", + "formatLabel": "Формат експорту", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Обрати поля", + "selectAll": "Обрати все", + "fieldsSelected": "{count, plural, one {# поле обрано} few {# поля обрано} other {# полів обрано}}", + "cancel": "Скасувати", + "export": "Експорт", + "sheetName": "Дані" + }, + "jsonTree": { + "typeLabel": "Тип: {type}", + "itemSummary": "{count, plural, one {# елемент} few {# елементи} other {# елементів}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Новий запит", + "importedRequest": "Імпортований запит", + "untitledRequest": "Запит без назви" + }, + "toasts": { + "curlImported": "cURL імпортовано успішно", + "curlParseFailed": "Не вдалося розібрати команду cURL", + "invalidJsonBody": "Невірне тіло JSON", + "requestFailed": "Запит не вдався: {message}", + "curlPasted": "cURL вставлено та розібрано успішно", + "responseCopied": "Відповідь скопійовано до буфера обміну", + "codeCopied": "Код скопійовано до буфера обміну", + "copyFailed": "Не вдалося скопіювати до буфера обміну" + }, + "layout": { + "collections": "Колекції", + "closeSidebar": "Закрити бічну панель", + "openSidebar": "Відкрити бічну панель", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Метод", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Надсилання...", + "send": "Надіслати", + "invalidJsonBodyHelp": "Неможливо надіслати: тіло JSON недійсне" + }, + "requestTabs": { + "params": "Параметри", + "headers": "Заголовки", + "body": "Тіло", + "auth": "Авторизація", + "bodyType": "Тип тіла", + "selectType": "Обрати тип", + "bodyNone": "Немає", + "bodyJson": "JSON", + "bodyText": "Текст", + "noBodyHint": "Немає вмісту тіла для цього запиту", + "switchToJson": "Перейти до тіла JSON", + "authType": "Тип авторизації", + "noAuth": "Без авторизації", + "bearerToken": "Bearer токен", + "basicAuth": "Базова автентифікація", + "apiKey": "API ключ", + "labelToken": "Токен", + "placeholderBearerToken": "Bearer токен", + "labelUsername": "Імʼя користувача", + "placeholderUsername": "Імʼя користувача", + "labelPassword": "Пароль", + "placeholderPassword": "Пароль", + "labelKey": "Ключ", + "placeholderApiKeyName": "Назва ключа (напр. X-Api-Key)", + "labelValue": "Значення", + "placeholderValue": "Значення", + "addTo": "Додати до", + "header": "Заголовок", + "queryParams": "Параметри запиту", + "noAuthHint": "Цей запит не використовує автентифікацію." + }, + "responsePanel": { + "emptyTitle": "Готові дослідити?", + "emptyHint": "Введіть URL та натисніть «Надіслати», щоб побачити дані відповіді тут.", + "responsePreviewAlt": "Попередній перегляд відповіді", + "previewUnavailable": "Попередній перегляд недоступний для цього типу вмісту", + "status": "Статус", + "time": "Час", + "size": "Розмір", + "copyBody": "Копіювати тіло", + "downloadResponse": "Завантажити відповідь", + "bodyTab": "Тіло", + "previewTab": "Перегляд", + "headersTab": "Заголовки", + "binaryRawView": "Двійкові дані не можна відобразити у вигляді тексту.", + "errorStatusLabel": "Помилка", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Нова вкладка" + }, + "collectionsSidebar": { + "title": "API ресурси", + "newCollection": "Нова колекція", + "tabCollections": "Колекції", + "tabHistory": "Історія", + "noCollectionsTitle": "Немає колекцій", + "noCollectionsHint": "Створіть колекцію для організації ваших API-запитів.", + "createCollection": "Створити колекцію", + "newFolder": "Нова тека", + "rename": "Перейменувати", + "delete": "Видалити", + "emptyCollection": "Порожня колекція", + "recentRequests": "Нещодавні запити", + "clearAll": "Очистити все", + "noHistoryTitle": "Історії поки немає", + "noHistoryHint": "Надішліть запит, щоб побачити його тут.", + "dialogNewFolderTitle": "Нова тека", + "dialogNewFolderDescription": "Створіть нову теку для організації запитів.", + "labelFolderName": "Назва теки", + "placeholderFolderName": "Моя тека", + "createFolder": "Створити теку", + "dialogNewCollectionTitle": "Нова колекція", + "dialogNewCollectionDescription": "Створіть нову колекцію верхнього рівня.", + "labelCollectionName": "Назва колекції", + "placeholderCollectionName": "Моя колекція", + "dialogRenameCollectionTitle": "Перейменувати колекцію", + "dialogRenameCollectionDescription": "Введіть нову назву для колекції.", + "placeholderRenameCollection": "Назва колекції", + "renameAction": "Перейменувати", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Нова тека", + "delete": "Видалити", + "emptyFolder": "Порожня тека" + }, + "environmentManager": { + "noEnvironment": "Без середовища", + "manageDialogTitle": "Керування середовищами", + "placeholderNewEnv": "Нове середовище", + "variablesHeading": "Змінні {name}", + "addVariable": "Додати змінну", + "columnVariable": "Змінна", + "columnValue": "Значення", + "placeholderKey": "Ключ", + "placeholderValue": "Значення", + "selectEnvToEdit": "Оберіть середовище для редагування" + }, + "codeGenerator": { + "triggerTitle": "Згенерувати код", + "dialogTitle": "Згенерувати код", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Копіювати", + "errorGenerating": "Помилка при генерації коду" + }, + "keyValueEditor": { + "columnKey": "Ключ", + "columnValue": "Значення", + "placeholderKey": "Ключ", + "placeholderValue": "Значення", + "addItem": "Додати елемент" + }, + "importCurl": { + "trigger": "Імпорт cURL", + "dialogTitle": "Імпорт cURL", + "dialogDescription": "Вставте команду cURL нижче для імпорту як нового запиту.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Імпорт" + }, + "saveRequest": { + "trigger": "Зберегти", + "dialogTitle": "Зберегти запит", + "dialogDescription": "Збережіть цей запит до теки колекції.", + "labelName": "Назва запиту", + "placeholderName": "Мій запит", + "labelFolder": "Тека", + "placeholderFolder": "Обрати теку", + "save": "Зберегти" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Режим редагування", + "viewOnlyMode": "Режим перегляду", + "insertComponent": "Вставити компонент", + "addUnorderedList": "Додати маркований список", + "addOrderedList": "Додати нумерований список", + "addTable": "Додати таблицю" + }, + "addBlock": "Додати блок", + "media": { + "popoverTitle": "Додати медіа", + "singleImage": "Одне зображення", + "singleImageDesc": "Завантажити одне зображення", + "multipleImages": "Кілька зображень", + "multipleImagesDesc": "Завантажити сітку зображень", + "video": "Відео", + "videoDesc": "Завантажити відеофайл", + "uploadingAlt": "Завантаження...", + "uploadFailedAlt": "Завантаження не вдалося" + }, + "export": { + "fabAria": "Експорт коду", + "title": "Експорт коду", + "description": "Скопіюйте HTML або JSON-вивід вмісту редактора", + "preview": "Перегляд", + "html": "HTML", + "json": "JSON", + "previewOptions": "Параметри перегляду", + "enhanceSpaces": "Покращити відступи", + "livePreview": "Живий перегляд відрендереного HTML", + "htmlTailwind": "HTML з класами Tailwind CSS", + "editorJson": "Стан редактора у JSON", + "copyHtml": "Копіювати HTML", + "copyJson": "Копіювати JSON", + "copied": "Скопійовано!" + }, + "template": { + "fabTitle": "Змінити шаблон", + "title": "Оберіть шаблон", + "description": "Оберіть шаблон для швидкого старту з готовими макетами та вмістом", + "catAll": "Усі шаблони", + "catProductivity": "Продуктивність", + "catCreative": "Творчість", + "catBusiness": "Бізнес", + "catPersonal": "Особисте", + "noTemplates": "У цій категорії шаблонів не знайдено", + "confirmTitle": "Замінити існуючий вміст?", + "confirmDescription": "У редакторі є вміст. Застосування цього шаблону замінить весь поточний вміст.", + "cannotUndo": "Цю дію не можна скасувати.", + "cancel": "Скасувати", + "replaceContent": "Замінити вміст" + }, + "command": { + "searchPlaceholder": "Пошук команд...", + "noResults": "Команд не знайдено.", + "turnInto": "Перетворити на", + "h1": { + "label": "Заголовок 1", + "description": "Великий заголовок розділу", + "keywords": "h1,heading,title,large" + }, + "h2": { + "label": "Заголовок 2", + "description": "Середній заголовок розділу", + "keywords": "h2,heading,subtitle" + }, + "h3": { + "label": "Заголовок 3", + "description": "Малий заголовок розділу", + "keywords": "h3,heading,subheading" + }, + "h4": { + "label": "Заголовок 4", + "description": "Дрібний заголовок розділу", + "keywords": "h4,heading" + }, + "h5": { + "label": "Заголовок 5", + "description": "Менший заголовок", + "keywords": "h5,heading" + }, + "h6": { + "label": "Заголовок 6", + "description": "Найменший заголовок", + "keywords": "h6,heading" + }, + "p": { + "label": "Параграф", + "description": "Звичайний текстовий параграф", + "keywords": "p,paragraph,text,normal" + }, + "code": { + "label": "Блок коду", + "description": "Фрагмент коду", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "Цитата", + "description": "Блокова цитата", + "keywords": "quote,blockquote,citation" + }, + "li": { + "label": "Маркований список", + "description": "Елемент списку з маркером", + "keywords": "list,bullet,unordered,ul,li" + }, + "ol": { + "label": "Нумерований список", + "description": "Нумерований елемент списку", + "keywords": "list,numbered,ordered,ol,li" + }, + "img": { + "label": "Зображення", + "description": "Завантажити або вбудувати зображення", + "keywords": "image,img,picture,photo,upload" + }, + "video": { + "label": "Відео", + "description": "Завантажити або вбудувати відео", + "keywords": "video,vid,movie,mp4,upload" + }, + "table": { + "label": "Таблиця", + "description": "Створити таблицю", + "keywords": "table,grid,rows,columns,cells" + } + }, + "toasts": { + "tableImported": "Таблицю імпортовано", + "tableImportedDesc": "Таблицю Markdown успішно імпортовано", + "mediaPasted": "Медіа вставлено", + "pasteMixed": "{imageCount} зображень та {videoCount} відео вставлено успішно.", + "pasteImages": "{count} зображень вставлено успішно.", + "pasteVideos": "{count} відео вставлено успішно.", + "pasteFailed": "Вставка не вдалася", + "unexpectedError": "Сталася неочікувана помилка", + "uploadFailed": "Завантаження не вдалося" + }, + "blockBackground": { + "menuItem": "Змінити колір фону", + "dialogTitle": "Колір фону блоку", + "dialogDescription": "Оберіть колір фону для цього блоку", + "tabPreset": "Готові кольори", + "tabCustom": "Власний колір", + "sectionHeading": "Кольори фону", + "apply": "Застосувати", + "colors": { + "none": "Немає", + "red": "Червоний", + "orange": "Помаранчевий", + "yellow": "Жовтий", + "green": "Зелений", + "blue": "Синій", + "indigo": "Індиго", + "purple": "Фіолетовий", + "pink": "Рожевий", + "teal": "Бірюзовий", + "cyan": "Блакитний", + "gray": "Сірий" + } + }, + "tableDialog": { + "title": "Створити таблицю", + "description": "Створіть нову таблицю або імпортуйте з markdown", + "markdownCheckbox": "У мене є таблиця markdown", + "pasteMarkdownLabel": "Вставте таблицю Markdown", + "markdownPlaceholder": "| Заголовок 1 | Заголовок 2 | Заголовок 3 |\n|----------|----------|----------|\n| Комірка 1 | Комірка 2 | Комірка 3 |\n| Комірка 4 | Комірка 5 | Комірка 6 |", + "markdownHint": "Вставте таблицю markdown вище. Повинна містити рядок заголовка та рядок-розділювач.", + "rows": "Рядки", + "columns": "Стовпці", + "maxDimensions": "Максимум: 20 рядків × 10 стовпців", + "cancel": "Скасувати", + "importTable": "Імпортувати таблицю", + "createTable": "Створити таблицю" + }, + "tableParse": { + "errors": { + "minRows": "Таблиця повинна мати щонайменше рядок заголовка та рядок-розділювач", + "pipeFormat": "Рядки таблиці повинні починатися та закінчуватися символом |", + "headerMinCol": "Рядок заголовка повинен мати щонайменше один стовпець", + "separatorRow": "Другий рядок повинен бути розділювачем (напр., |---|---|)", + "rowColumnMismatch": "Рядок {row} має {got} стовпців, очікувалося {expected}", + "parseFailed": "Не вдалося розібрати таблицю markdown" + } + }, + "link": { + "editLink": "Редагувати посилання", + "addLink": "Додати посилання", + "selectedText": "Обраний текст: \"{text}\"", + "linkUrl": "URL посилання", + "urlPlaceholder": "https://example.com", + "update": "Оновити", + "addLinkButton": "Додати посилання", + "toastApplied": "Посилання застосовано", + "toastLinkedTo": "Посилання на: {url}", + "toastRemoved": "Посилання видалено", + "toastRemovedDesc": "Посилання видалено з виділення" + }, + "customClass": { + "devMode": "Режим розробника", + "toggleDevModeAria": "Перемкнути режим розробника", + "searchPlaceholderDev": "Пошук класів... (напр., 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Пошук стилів... (напр., 'red', 'bold', 'shadow')", + "appliesTitle": "Застосовує: {className}", + "noMatches": "Класів за запитом \"{query}\" не знайдено", + "toastClassReplaced": "Клас замінено", + "toastClassReplacedDesc": "Замінено \"{replaced}\" на \"{className}\"", + "toastCustomClassApplied": "Користувацький клас застосовано", + "toastAppliedClass": "Застосовано клас: {className}" + }, + "insertComponents": { + "title": "Вставити компонент", + "description": "Оберіть компонент для вставки у документ", + "noComponents": "Компоненти недоступні", + "categories": { + "media": "Медіа", + "layout": "Макет", + "interactive": "Інтерактивне", + "decoration": "Декорація" + }, + "freeImage": { + "name": "Зображення з вільним розташуванням", + "description": "Додайте зображення, яке можна розмістити будь-де та вільно змінювати розмір" + } + } + }, + "JsonFormatter": { + "title": "Форматувальник JSON", + "description": "Форматуйте, перевіряйте та редагуйте JSON у текстовому й деревоподібному режимах.", + "documentNameText": "Текстовий документ {n}", + "documentNameTree": "Деревоподібний документ {n}", + "newDocument": "Новий", + "copy": "Копіювати", + "save": "Зберегти", + "load": "Завантажити", + "saving": "Збереження...", + "leftDocNameLabel": "Назва лівого документа", + "rightDocNameLabel": "Назва правого документа", + "toastCopiedText": "Текстову панель скопійовано", + "toastCopiedTree": "Панель дерева скопійовано", + "toastCopyFailed": "Не вдалося скопіювати JSON", + "toastLoginRequired": "Увійдіть, щоб зберігати документи", + "toastSaved": "Документ збережено", + "toastSaveFailed": "Не вдалося зберегти документ", + "format": "Форматувати", + "compact": "Стиснути", + "sortKeys": "Сортувати ключі", + "repair": "Виправити", + "query": "Запит", + "toastInvalidJson": "Недійсний JSON", + "toastAlreadyValid": "JSON вже коректний", + "toastRepaired": "JSON виправлено", + "toastRepairFailed": "Не вдалося виправити JSON" + }, + "JwtDecoder": { + "title": "Декодер JWT", + "subtitle": "Переглядайте заголовок і payload. Строк дії береться з claim exp. Нічого не надсилається на сервер; підпис не перевіряється.", + "tokenLabel": "Токен", + "clear": "Очистити", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Копіювати", + "expiryLabel": "Строк дії (exp)", + "signaturePresent": "Підпис присутній (не перевіряється)", + "noExp": "У payload немає claim exp.", + "status": { + "expired": "Прострочено", + "valid": "Дійсний" + }, + "fields": { + "unix": "Unix", + "local": "Локальний", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Видано (iat)", + "notBefore": "Не раніше (nbf)" + }, + "panels": { + "header": "Заголовок", + "payload": "Payload" + }, + "errors": { + "empty": "Вставте JWT для декодування.", + "segments": "JWT має містити щонайменше заголовок і payload (два сегменти).", + "base64": "Не вдалося декодувати заголовок або payload (base64url)." + }, + "relative": { + "future": "через {n} {unit}", + "past": "{n} {unit} тому" + }, + "units": { + "second": "{count, plural, one {секунду} few {секунди} many {секунд} other {секунд}}", + "minute": "{count, plural, one {хвилину} few {хвилини} many {хвилин} other {хвилин}}", + "hour": "{count, plural, one {годину} few {години} many {годин} other {годин}}", + "day": "{count, plural, one {день} few {дні} many {днів} other {днів}}" + }, + "tabs": { + "decode": "Декодувати", + "sign": "Генератор / підпис" + }, + "signer": { + "algLabel": "Алгоритм (alg)", + "tabs": { + "payload": "Навантаження", + "header": "Заголовок" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Секрет (HS*)", + "secretPlaceholder": "Вставте HMAC‑секрет…", + "privateKeyLabel": "Приватний ключ PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Алгоритм \"none\" створює токен без підпису. Це небезпечно — лише для тестування.", + "sign": "Підписати", + "verify": "Перевірити підпис", + "outputLabel": "Токен (компактний JWS)", + "outputPlaceholder": "Натисніть «Підписати»…", + "copyToken": "Копіювати токен", + "copied": "Скопійовано", + "verifyKeyLabel": "Ключ перевірки (необов’язково)", + "verifyKeyPlaceholder": "Для RS*: вставте публічний ключ (SPKI) PEM. Для HS*: залиште порожнім, щоб використати секрет.", + "verifyKeyHint": "Якщо порожньо, перевірка використовує ключ підпису (зручно для HS*).", + "verifyStatus": { + "valid": "Підпис дійсний", + "invalid": "Підпис недійсний" + }, + "securityNote": "Усе виконується локально в браузері; ключі й токени не завантажуються." + }, + "signErrors": { + "invalidJson": "Заголовок і payload мають бути валідним JSON.", + "unsupportedAlg": "Непідтримуваний алгоритм.", + "missingKey": "Для цього алгоритму потрібен ключ підпису.", + "cryptoFailed": "Підписання не вдалося. Перевірте формат ключа та спробуйте знову." + }, + "verifyErrors": { + "empty": "Введіть JWT для перевірки.", + "segments": "JWT має містити рівно 3 сегменти для перевірки підпису.", + "unsupportedAlg": "Непідтримуваний або відсутній alg у заголовку.", + "missingKey": "Для цього алгоритму потрібен ключ перевірки.", + "base64": "Не вдалося декодувати заголовок або підпис JWT.", + "cryptoFailed": "Перевірка не вдалася. Перевірте формат ключа та спробуйте знову." + } + }, + "CertificatePemDecoder": { + "title": "Декодер сертифіката / PEM", + "subtitle": "Вставте сертифікат X.509 (-----BEGIN CERTIFICATE-----) або CSR (-----BEGIN CERTIFICATE REQUEST-----). Суб’єкт, видавник, дати та SAN обробляються локально; нічого не надсилається.", + "pemLabel": "Ввід PEM", + "clear": "Очистити", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Обробка…", + "copyHex": "Копіювати відбиток (hex без двокрапок)", + "errors": { + "empty": "Вставте PEM-сертифікат або CSR для декодування.", + "noBlock": "Не знайдено PEM-блоку сертифіката або CSR.", + "parseFailed": "Не вдалося обробити ці PEM-дані." + }, + "block": { + "title": "PEM-блок {index}", + "certificate": "Сертифікат X.509", + "csr": "Запит на підпис сертифіката" + }, + "fields": { + "subject": "Суб’єкт", + "issuer": "Видавник", + "notBefore": "Недійсний до", + "notAfter": "Недійсний після", + "serial": "Серійний номер (hex)", + "sha256": "Відбиток SHA-256", + "sans": "Альтернативні імена суб’єкта", + "sansNone": "Розширення SAN відсутнє.", + "signatureAlgorithm": "Алгоритм підпису", + "csrIssuerNote": "CSR не містить видавника (він у виданому сертифікаті).", + "csrValidityNote": "CSR не містить строк дії (вони у виданому сертифікаті)." + }, + "sanType": { + "dns": "DNS", + "dn": "Ім’я каталогу", + "email": "Електронна пошта", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "Зареєстрований ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Форматувач SQL", + "subtitle": "Форматуйте SQL для MySQL, PostgreSQL або SQLite. Працює у браузері; нічого не завантажується.", + "dialectLabel": "Діалект", + "keywordsLabel": "Ключові слова", + "indentLabel": "Відступ", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "ВЕРХНІЙ РЕГІСТР", + "lower": "нижній регістр", + "preserve": "Зберегти" + }, + "spaces": "{n} пробілів", + "format": "Форматувати", + "clear": "Очистити", + "counter": "{current} / {max} символів", + "inputPanel": "Ввід", + "formattedPanel": "Відформатовано", + "copy": "Копіювати", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Натисніть \"Форматувати\"…", + "errors": { + "tooLong": "Ввід обмежено {max} символами.", + "couldNotFormat": "Не вдалося відформатувати цей SQL." + } + }, + "YamlFormatter": { + "title": "Форматувальник YAML", + "subtitle": "Форматуйте, перевіряйте та конвертуйте YAML у браузері. Нічого не завантажується.", + "outputLabel": "Вивід", + "outputModes": { + "yaml": "YAML (відформатований)", + "json": "JSON" + }, + "indentLabel": "Відступ", + "spaces": "{n} пробіли", + "format": "Форматувати", + "clear": "Очистити", + "copy": "Копіювати", + "counter": "{current} / {max} символів", + "inputPanel": "Введення", + "outputPanel": "Вивід", + "errors": { + "tooLong": "Введення обмежено {max} символами.", + "couldNotFormat": "Не вдалося розібрати цей YAML." + } + }, + "GraphqlFormatter": { + "title": "Форматувач GraphQL", + "subtitle": "Форматуйте запити, мутації й підписки GraphQL у браузері. Нічого не завантажується.", + "tabs": { + "format": "Форматувати", + "build": "Конструктор запиту" + }, + "outputModeLabel": "Вивід", + "outputModes": { + "pretty": "Читабельний вивід", + "minify": "Мініфікувати" + }, + "format": "Форматувати", + "clear": "Очистити", + "counter": "{current} / {max} символів", + "inputPanel": "Вхід", + "outputPanel": "Вихід", + "copy": "Копіювати", + "builderHint": "Зберіть каркас документа й застосуйте до редактора. Визначення змінних у синтаксисі GraphQL (напр. $id: ID!) — дужки не обов’язкові.", + "builder": { + "operation": "Операція", + "operationName": "Назва операції", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Визначення змінних", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Набір вибору", + "apply": "Застосувати до вводу", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Ввід обмежено {max} символами.", + "couldNotFormat": "Не вдалося розібрати чи надрукувати цей GraphQL.", + "emptySelection": "Додайте набір вибору перед застосуванням." + } + }, + "RegexTester": { + "title": "Тестер regex", + "subtitle": "Регулярні вирази у стилі JavaScript у реальному часі. Збіги підсвічуються; нічого не залишає ваш браузер.", + "patternLabel": "Шаблон", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g глобально", + "i": "i без урахування регістру", + "m": "m багаторядковий", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(порожньо)", + "noFlags": "—", + "matches": "{count, plural, one {збіг} few {збіги} many {збігів} other {збігів}}", + "testTextLabel": "Тестовий текст", + "counter": "{current} / {max}", + "clear": "Очистити", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Текст обмежено {max} символами.", + "invalid": "Некоректний регулярний вираз.", + "runaway": "Зупинено після {max} збігів (можливо, шаблон “розбігся”)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Конвертер часових міток", + "subtitle": "Unix-секунди (≤10 цифр) або мілісекунди (довші цілі числа), ISO-8601 або рядки, які може розібрати браузер. Усе працює локально.", + "inputLabel": "Ввід", + "placeholder": "1744200000 або 2025-04-09T12:00:00Z", + "now": "Зараз", + "copy": "Копіювати", + "hint": "Вставте значення або натисніть Зараз.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Відносно", + "human": "Зрозуміло" + }, + "fields": { + "seconds": "Секунди", + "milliseconds": "Мілісекунди", + "utc": "UTC", + "local": "Локально", + "distance": "Відстань" + }, + "errors": { + "empty": "Введіть Unix-мітку, ISO-рядок або текст дати.", + "invalidNumber": "Некоректна числова мітка часу.", + "outOfRange": "Поза діапазоном для JavaScript Date.", + "couldNotParse": "Не вдалося розібрати як Unix, ISO-8601 або дату браузера." + } + }, + "HttpStatusCodes": { + "title": "Коди стану HTTP", + "subtitle": "Пошук і довідник кодів стану HTTP-відповідей (1xx5xx) з короткими описами та посиланнями на RFC. Працює локально.", + "header": { + "title": "Коди стану HTTP", + "description": "Довідник з пошуком по HTTP-кодах відповіді з описами та посиланнями на RFC." + }, + "searchPlaceholder": "Пошук: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Фільтр класу…", + "classFilterAll": "Усі класи", + "resultsCount": "{count} із {total}", + "table": { + "code": "Code", + "phrase": "Фраза", + "description": "Опис", + "copy": "Копіювати" + }, + "noResults": "Немає збігів для «{query}».", + "specPrefix": "Спец.:", + "copied": "Код скопійовано" + }, + "CronBuilder": { + "title": "Конструктор cron-виразів", + "subtitle": "Стандартний 5-польовий cron: minute hour day month weekday. Парсер внутрішньо використовує seconds=0. Опційний 6-польовий формат (sec min hour dom mon dow) працює в редакторі.", + "tabs": { + "builder": "Візуальний конструктор", + "expression": "Вираз" + }, + "presetsLabel": "Пресети", + "fieldsLabel": "Поля", + "rawExpressionLabel": "Сирий вираз", + "expressionHint": "Зніміть фокус, щоб розібрати та синхронізувати конструктор. {count} {count, plural, one {токен} few {токени} many {токенів} other {токенів}}.", + "currentExpressionLabel": "Поточний вираз", + "copy": "Копіювати", + "quickPick": "Швидкий вибір", + "emptyExpression": "—", + "plainLanguageLabel": "Простою мовою", + "nextRunsLabel": "Наступні запуски (локальний час)", + "fields": { + "minute": "Хвилина", + "hour": "Година", + "dayOfMonth": "День місяця", + "month": "Місяць", + "dayOfWeek": "День тижня" + }, + "presets": { + "everyMinute": "Щохвилини", + "every5Minutes": "Кожні 5 хвилин", + "every15Minutes": "Кожні 15 хвилин", + "everyHour": "Щогодини (о :00)", + "every6Hours": "Кожні 6 годин", + "dailyMidnight": "Щодня опівночі", + "daily0900": "Щодня о 09:00", + "weekdays0900": "Будні о 09:00", + "weeklySunday": "Щотижня (неділя 00:00)", + "monthlyFirst": "Щомісяця (1-ше 00:00)", + "yearlyJan1": "Щороку (1 січ 00:00)" + }, + "picks": { + "minute": { + "every": "щохвилини" + }, + "hour": { + "every": "щогодини" + }, + "dom": { + "any": "будь-який день", + "last": "останній день" + }, + "month": { + "every": "щомісяця" + }, + "dow": { + "any": "будь-який день", + "weekdays": "будні", + "weekend": "вихідні" + } + }, + "months": { + "jan": "Січ", + "feb": "Лют", + "mar": "Бер", + "apr": "Кві", + "may": "Тра", + "jun": "Чер", + "jul": "Лип", + "aug": "Сер", + "sep": "Вер", + "oct": "Жов", + "nov": "Лис", + "dec": "Гру" + }, + "weekdays": { + "sun": "Нд", + "mon": "Пн", + "tue": "Вт", + "wed": "Ср", + "thu": "Чт", + "fri": "Пт", + "sat": "Сб" + }, + "errors": { + "fieldCount": "Очікується 5 полів (minute hour day month weekday) або 6 із секундами.", + "invalid": "Некоректний cron-вираз." + } + }, + "Base64": { + "title": "Кодування / декодування Base64", + "subtitle": "Кодуйте текст у Base64 або декодуйте Base64-рядки", + "upload": "Завантажити", + "clear": "Очистити", + "modes": { + "encode": "Кодувати", + "decode": "Декодувати" + }, + "swapTitle": "Поміняти місцями ввід і вивід", + "panels": { + "plainText": "Звичайний текст", + "base64Input": "Ввід Base64", + "base64Output": "Вивід Base64", + "decodedText": "Декодований текст" + }, + "placeholders": { + "encode": "Введіть текст для кодування у Base64…", + "decode": "Вставте Base64 для декодування…" + }, + "dropHere": "Перетягніть файл сюди", + "charCount": "{count} символів", + "download": { + "title": "Завантажити результат", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Копіювати в буфер обміну", + "outputPlaceholder": "Результат з’явиться тут…", + "errors": { + "invalidBase64": "Некоректний Base64-рядок. Перевірте ввід.", + "encodeFailed": "Не вдалося закодувати ввід." + } + }, + "UrlEncode": { + "title": "Кодування / декодування URL", + "subtitle": "Відсоткове кодування/декодування через encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Завантажити", + "clear": "Очистити", + "modes": { + "encode": "Кодувати", + "decode": "Декодувати" + }, + "swapTitle": "Поміняти місцями ввід і вивід", + "panels": { + "plainText": "Звичайний текст", + "encodedInput": "Закодований ввід", + "encodedOutput": "Закодований вивід", + "decodedText": "Декодований текст" + }, + "placeholders": { + "encode": "Введіть текст для відсоткового кодування…", + "decode": "Вставте percent-encoded текст для декодування…" + }, + "dropHere": "Перетягніть файл сюди", + "charCount": "{count} символів", + "download": { + "title": "Завантажити результат", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Копіювати в буфер обміну", + "outputPlaceholder": "Результат з’явиться тут…", + "errors": { + "invalidEncoding": "Некоректне percent-encoding. Перевірте неповні пари % або escape-послідовності.", + "encodeFailed": "Не вдалося закодувати (некоректний Unicode у вводі)." + } + }, + "UrlParser": { + "title": "Парсер URL", + "subtitle": "Розбийте URL на протокол, хост, шлях, параметри запиту та хеш (фрагмент).", + "upload": "Завантажити", + "clear": "Очистити", + "panels": { + "input": "Ввід URL", + "output": "Результат" + }, + "placeholders": { + "input": "Вставте URL (наприклад, https://example.com/path?foo=bar#section) або хост/шлях (наприклад, example.com/path)…" + }, + "dropHere": "Перетягніть файл сюди", + "charCount": "{count} символів", + "outputPlaceholder": "Розібрані компоненти з’являться тут…", + "fields": { + "protocol": "Протокол", + "host": "Хост", + "path": "Шлях", + "hash": "Хеш", + "queryParams": "Параметри запиту" + }, + "paramCount": "{count} параметрів", + "noParams": "Параметрів запиту немає.", + "paramKey": "Ключ", + "paramValue": "Значення", + "copyTitle": "Копіювати в буфер обміну", + "copyParamTitle": "Копіювати як key=value", + "download": { + "title": "Завантажити JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "Некоректний URL. Додайте hostname (example.com) і перевірте символи." + } + }, + "UuidGenerator": { + "title": "Генератор UUID / ULID", + "subtitle": "Генеруйте UUID (RFC 9562) або ULID у браузері. Пакетний експорт до {max} за запуск.", + "format": "Формат", + "kindHints": { + "ulid": "Сортований, URL-безпечний ідентифікатор на 26 символів", + "uuid4": "Випадковий (RFC 9562)", + "uuid7": "Упорядкований за часом, Unix мс + випадковість", + "uuid1": "MAC/час (RFC 9562)", + "uuid6": "Перевпорядкований григоріанський час", + "uuid3": "MD5‑хеш імені + простору назв", + "uuid5": "SHA‑1‑хеш імені + простору назв", + "nil": "Нульовий UUID (усі нулі, RFC 9562)" + }, + "countLabel": "Кількість (1–{max})", + "nameLabel": "Ім’я (вхідний рядок)", + "namePlaceholder": "наприклад my.app/resource", + "bulkHint": "У пакетному режимі додаються #0, #1, … щоб кожен ID був унікальним (v3/v5 детерміновані).", + "namespace": "Простір назв", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "Власний UUID" + }, + "customNamespaceLabel": "UUID простору назв", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Згенерувати", + "output": "Вивід", + "lines": "{count} рядків", + "download": { + "title": "Завантажити .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Копіювати все", + "uppercaseToggle": "Вивід великими літерами", + "hyphensToggle": "Показувати дефіси", + "copyJsonTitle": "Копіювати як масив JSON", + "outputPlaceholder": "Натисніть \"Згенерувати\"…", + "errors": { + "invalidCustomNamespace": "Власний простір назв має бути валідним UUID.", + "missingName": "Ім’я потрібне для UUID v3 та v5.", + "unknown": "Не вдалося згенерувати ідентифікатори." + } + }, + "SecretApiKeyGenerator": { + "title": "Генератор секрету / ключа API", + "subtitle": "Створюйте криптографічно випадкові рядки з налаштовуваною довжиною та алфавітом. Масовий експорт до {max} за запуск. Доповнює генератор UUID / ULID.", + "alphabetLabel": "Алфавіт", + "alphabetHint": "Дублікати символів прибираються. Кожен символ обирається рівномірно через crypto.getRandomValues і відбір із відсівом (без модульного зміщення).", + "alphabetCount": "Унікальних символів: {count}", + "presetsLabel": "Пресети", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Літери й цифри", + "hex": "Шістнадцятковий (малі літери)", + "asciiPrintable": "Друкований ASCII" + }, + "lengthLabel": "Довжина на секрет (1–{maxLen})", + "countLabel": "Кількість (1–{maxBulk})", + "generate": "Згенерувати", + "output": "Вивід", + "lines": "{count} рядків", + "download": { + "title": "Завантажити .txt", + "filename": "secrets.txt" + }, + "copyTitle": "Копіювати все", + "outputPlaceholder": "Натисніть «Згенерувати»…", + "entropy": "{bits} біт ентропії", + "strength": { + "weak": "Слабкий", + "fair": "Середній", + "strong": "Сильний" + }, + "excludeAmbiguous": "Виключити неоднозначні символи", + "excludeAmbiguousHint": "Видаляє {chars} — символи, які легко сплутати або які небезпечні для оболонки.", + "prefixLabel": "Префікс ключа (необов'язково)", + "errors": { + "emptyAlphabet": "Додайте до алфавіту хоча б один символ.", + "alphabetTooLong": "Після видалення дублікатів алфавіт може містити щонайбільше {maxAlpha} символів.", + "lengthOutOfRange": "Довжина має бути від 1 до {maxLen}.", + "countOutOfRange": "Кількість має бути від 1 до {maxBulk}.", + "unknown": "Не вдалося згенерувати секрети." + } + }, + "LoremIpsum": { + "title": "Генератор Lorem Ipsum", + "subtitle": "Генеруйте замінний латинський текст для ваших макетів та прототипів.", + "paragraphs": "Абзаци", + "sentences": "Речення", + "words": "Слова", + "count": "Кількість", + "generate": "Генерувати", + "copy": "Копіювати", + "startWith": "Почати з 'Lorem ipsum...'", + "outputPlaceholder": "Результат з'явиться тут...", + "outputType": "Тип виведення", + "bulletList": "Маркований список", + "htmlMarkup": "HTML-розмітка", + "htmlMarkupHint": "Обгорніть виведення в абзаци або використовуйте список для режиму маркерів", + "output": "Виведення", + "download": "Завантажити" + }, + "ColorPicker": { + "title": "Вибір і конвертер кольорів", + "subtitle": "Конвертуйте HEX, RGB та HSL; створюйте гармонійні палітри. Вся обробка відбувається у вашому браузері.", + "nativePicker": "Вбудований вибір кольору", + "chooseColor": "Виберіть колір", + "eyeDropper": "Піпетка", + "invalidHex": "Невірний колір HEX.", + "cssValues": "CSS-значення", + "palettes": "Палітри", + "copied": "Скопійовано", + "clickToCopy": "Натисніть для копіювання", + "copyAllHex": "Копіювати всі HEX", + "copy": "Копіювати", + "copyColor": "Копіювати {color}", + "paletteShades": "Відтінки", + "paletteComplementary": "Додатковий", + "paletteTriadic": "Тріадний", + "paletteAnalogous": "Аналогічний", + "paletteSplit": "Розділений доп.", + "paletteSquare": "Квадратний" + }, + "ContrastChecker": { + "title": "Перевірка контрасту", + "subtitle": "Обчислює коефіцієнт контрасту WCAG 2.1 між переднім планом і тлом. Перевіряє AA та AAA для звичайного й великого тексту. Усе в браузері.", + "foreground": "Текст / передній план", + "background": "Тло", + "hexPlaceholder": "#RRGGBB", + "swap": "Поміняти кольори місцями", + "chooseForeground": "Обрати колір переднього плану", + "chooseBackground": "Обрати колір тла", + "eyeDropper": "Піпетка", + "ratio": "Коефіцієнт контрасту", + "copyRatio": "Копіювати значення", + "copied": "Скопійовано", + "previewHeading": "Попередній перегляд", + "previewBody": "Єхидна, ґава, їжак ще й білка на цьому шляху гризли фундук. Чуєш їх: гам, ґвалт, лай — не хочу я так бути з ними!", + "previewCaption": "Зразок тексту стандартного розміру.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — звичайний текст (4,5:1)", + "levelAALarge": "AA — великий текст (3:1)", + "levelAAANormal": "AAA — звичайний текст (7:1)", + "levelAAALarge": "AAA — великий текст (4,5:1)", + "pass": "Відповідає", + "fail": "Не відповідає", + "invalidHex": "Некоректний колір HEX.", + "largeTextHint": "Великий текст: 18 pt+ звичайний або 14 pt+ напівжирний (визначення WCAG)." + }, + "DiffChecker": { + "title": "Перевірка різниць", + "subtitle": "Порядкове порівняння двох текстів поруч. Все залишається у вашому браузері.", + "rowsCount": "{count} рядків", + "truncatedWarning": "Показано перші {max} рядків.", + "limitExceeded": "Кожна сторона обмежена {max} символами.", + "clearBoth": "Очистити обидва", + "original": "Оригінал", + "modified": "Змінено", + "originalPlaceholder": "Вставте оригінальний текст…", + "modifiedPlaceholder": "Вставте змінений текст…", + "comparisonLabel": "Порівняння поруч", + "emptyState": "Введіть текст в обидві панелі для порівняння." + }, + "JsonSchemaGenerator": { + "title": "Генератор JSON-схеми", + "subtitle": "Вставте коректний JSON, щоб згенерувати JSON-схему або типізовані моделі. Усе працює у вашому браузері.", + "languageLabel": "Мова виводу", + "inputLabel": "Вхід JSON", + "inputPlaceholder": "Вставте JSON сюди…", + "outputLabel": "Згенерований вивід", + "outputPlaceholder": "Схема або згенеровані типи з’являться тут…", + "parseError": "Некоректний JSON", + "clear": "Очистити", + "copy": "Копіювати", + "copied": "Скопійовано в буфер обміну", + "copyFailed": "Не вдалося скопіювати", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-схема (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Завантаження редактора…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Зображення до Base64", + "subtitle": "Перетягніть зображення, отримайте URI даних або необроблений рядок Base64. Усе залишається у вашому браузері.", + "upload": "Завантажити зображення", + "clear": "ясно", + "modes": { + "dataUri": "URI даних", + "rawString": "Необроблена база 64" + }, + "panels": { + "imageInput": "Вхід зображення", + "output": "Вихід Base64" + }, + "dropHere": "Перетягніть зображення сюди", + "charCount": "{count} символів", + "placeholders": { + "uploadImage": "Перетягніть зображення або натисніть «Завантажити», щоб почати." + }, + "fileTooLarge": "Файл завеликий. Обробка може призвести до зависання вашого браузера.", + "copyTitle": "Копіювати в буфер обміну", + "download": { + "title": "Завантажити текст", + "filename": "image-base64.txt" + } + }, + "ImageCompressor": { + "title": "Стиснення зображень", + "subtitle": "Зменшуйте JPEG, PNG або WebP локально з керуванням якістю — нічого не надсилається.", + "upload": "Завантажити зображення", + "clear": "Очистити", + "dropHere": "Перетягніть зображення сюди", + "placeholders": { + "uploadImage": "Перетягніть файл або натисніть, щоб обрати зображення." + }, + "fileTooLarge": "Файл завеликий (макс. {maxMb} МБ).", + "unsupportedFormat": "Непідтримуваний тип файлу. Використовуйте JPEG, PNG, WebP, GIF, AVIF або BMP.", + "compressFailed": "Не вдалося стиснути зображення. Спробуйте інший файл або формат.", + "panels": { + "original": "Оригінал", + "compressed": "Попередній перегляд" + }, + "outputFormat": "Формат виводу", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Якість", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG у браузері без втрат; повзунок якості лише для JPEG і WebP.", + "sizes": { + "original": "Оригінал: {size}", + "compressed": "Після стиснення: {size}", + "saved": "на {percent}% менше" + }, + "largerWarning": "Вихід більший за вхід — змініть формат або якість.", + "download": "Завантажити", + "downloadFilename": "стиснуте-зображення", + "processing": "Стиснення…", + "emptyCompressed": "Попередній перегляд з’явиться тут." + }, + "CssGradientBuilder": { + "title": "Конструктор градієнтів CSS", + "subtitle": "Візуальний конструктор градієнтів CSS із керуванням кутом, кольорами та копіюванням CSS одним клацанням миші.", + "preview": "Попередній перегляд градієнта", + "settings": "Налаштування", + "type": "Тип", + "linear": "Лінійний", + "radial": "Радіальний", + "angle": "Кут", + "stops": "Кольорові зупинки", + "color": "колір", + "position": "Позиція (%)", + "addStop": "Додайте зупинку", + "deleteStop": "Видалити зупинку", + "output": "Вихід CSS", + "copy": "Копіювати CSS", + "copied": "Скопійовано!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Генератор .gitignore", + "subtitle": "Миттєво створюйте об’єднані файли .gitignore для певного технологічного стеку чи комбінації.", + "searchPlaceholder": "Стеки пошуку (наприклад, Node, Python, macOS)...", + "noResults": "Стоси не знайдено.", + "selectedStacks": "Вибрані стеки", + "output": "Попередній перегляд результату", + "copy": "Копіювати в буфер обміну", + "copied": "Скопійовано!", + "download": "Завантажте .gitignore", + "downloading": "Завантаження...", + "errorFetching": "Не вдалося отримати перший список.", + "errorGenerating": "Не вдалося створити .gitignore із вибраних стеків.", + "generating": "Створення...", + "emptyState": "Виберіть один або кілька технічних стеків, щоб створити свій .gitignore." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Завантажте CSV або Excel, відредагуйте JSON, потім завантажте CSV або XLSX. Лише табличні дані; дати Excel стають ISO-мітками часу в JSON.", + "dropTitle": "Перетягніть файл CSV або Excel", + "dropHint": "Для .xlsx / .xls використовується перший аркуш. CSV має містити рядок заголовків.", + "chooseFile": "Вибрати файл", + "jsonLabel": "JSON (масив об’єктів)", + "jsonPlaceholder": "Вставте JSON-масив об’єктів або імпортуйте CSV / Excel…", + "loadSample": "Завантажити приклад", + "actions": { + "format": "Форматувати JSON", + "clear": "Очистити", + "copy": "Копіювати", + "copied": "Скопійовано", + "downloadCsv": "Завантажити CSV", + "downloadXlsx": "Завантажити Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Дані", + "toastLoadedCsv": "CSV завантажено в редактор", + "toastLoadedExcel": "Excel завантажено в редактор", + "toastFormatted": "JSON відформатовано", + "toastExportedCsv": "CSV завантажено", + "toastExportedXlsx": "Excel завантажено", + "toastCopied": "Скопійовано в буфер", + "footnote": "Усе працює у браузері. JSON має бути непустим масивом об’єктів. Вкладені значення серіалізуються в JSON у комірках CSV.", + "errors": { + "unsupportedFile": "Використайте файл .csv, .xlsx або .xls.", + "parseFailed": "Не вдалося розібрати файл.", + "invalidJson": "Некоректний JSON", + "exportFailed": "Помилка експорту", + "copyFailed": "Не вдалося скопіювати" + } + }, + "SnippetManager": { + "title": "Менеджер фрагментів коду", + "subtitle": "Зберігайте фрагменти коду в браузері або увійдіть, щоб синхронізувати їх з обліковим записом. Перемикайте перегляд і редагування, копіюйте одним кліком, обирайте мову або авто-визначення та форматуйте JSON, SQL або код, який підтримує Monaco.", + "snippets": "Фрагменти", + "newSnippet": "Новий фрагмент", + "newSnippetTitle": "Новий фрагмент", + "defaultSnippetTitle": "Ласкаво просимо", + "defaultSnippetCode": "// Ласкаво просимо до фрагментів коду\n// Спробуйте перегляд і редагування, копіювання, форматування та авто-мову\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Пошук фрагментів…", + "searchAria": "Пошук фрагментів", + "emptySearch": "Немає фрагментів за запитом.", + "snippetTitle": "Заголовок", + "snippetTitlePlaceholder": "Назва фрагмента", + "language": "Мова", + "detectedHint": "Підсвічування: {lang}", + "badgeAuto": "Авто · {lang}", + "modeView": "Перегляд", + "modeEdit": "Редагування", + "copy": "Копіювати", + "copied": "Скопійовано", + "format": "Форматувати", + "cancel": "Скасувати", + "deleteTitle": "Видалити фрагмент?", + "deleteDescription": "«{title}» буде вилучено з цього браузера.", + "deleteConfirm": "Видалити", + "editorAria": "Редактор коду фрагмента", + "editorLoading": "Завантаження редактора…", + "toastCopied": "Скопійовано в буфер", + "toastCopyFailed": "Не вдалося скопіювати", + "toastNothingToCopy": "Нічого копіювати", + "toastFormatted": "Відформатовано", + "toastFormatFailed": "Не вдалося відформатувати", + "toastFormatJsonInvalid": "Некоректний JSON — спочатку виправте синтаксис", + "toastFormatUnsupported": "Немає форматера для цієї мови — спробуйте JSON або SQL або режим редагування", + "toastDeleted": "Фрагмент видалено", + "toastLoadFailed": "Не вдалося завантажити фрагменти з сервера.", + "toastSyncFailed": "Не вдалося синхронізувати з сервером.", + "languages": { + "auto": "Автовизначення", + "plaintext": "Звичайний текст", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL-клієнт", + "dialogTitle": "SQL-підключення", + "mobileSheetTitle": "SQL-провідник", + "newTabLabel": "Нова вкладка запиту", + "vaultLockedTitle": "Сховище заблоковано", + "vaultLockedDesc": "Розблокуйте сховище для доступу до збережених підключень.", + "heroTitle": "SQL-клієнт", + "heroDesc": "Підключайтеся до PostgreSQL, MySQL або MariaDB. Облікові дані шифруються у браузері.", + "heroAddConnection": "Додати підключення", + "selectTableTitle": "Оберіть таблицю або відкрийте вкладку", + "selectTableDesc": "Натисніть на таблицю у бічній панелі або використайте ⌘↩.", + "toastLoadFail": "Не вдалося завантажити підключення." + }, + "connection": { + "titleNew": "Нове SQL-підключення", + "titleEdit": "Редагувати підключення", + "labelDbType": "Тип бази даних", + "labelName": "Назва підключення", + "placeholderName": "Моя виробнича БД", + "labelHostPort": "Хост і порт", + "labelDatabase": "База даних", + "placeholderDatabase": "mydb", + "labelUsername": "Ім'я користувача", + "labelPassword": "Пароль", + "labelSsl": "Увімкнути SSL / TLS", + "sslDesc": "Шифрувати підключення до сервера", + "securityNote": "Облікові дані шифруються AES-256 у браузері перед збереженням", + "btnTest": "Перевірити підключення", + "btnSave": "Зберегти і підключитися", + "btnUpdate": "Оновити", + "btnCancelEdit": "Скасувати редагування", + "savedConnections": "Збережені підключення", + "emptySaved": "Збережені підключення з'являться тут", + "confirmDelete": "Видалити це підключення?", + "toastFillFields": "Заповніть хост і базу даних перед тестуванням.", + "toastConnectOk": "Підключено! {version}", + "toastConnectFail": "Помилка підключення: {message}", + "toastUnlockVault": "Спочатку розблокуйте сховище.", + "toastFieldsRequired": "Хост і база даних обов'язкові.", + "toastUpdated": "Підключення оновлено.", + "toastSaved": "Підключення збережено.", + "toastSaveFail": "Помилка збереження: {message}", + "toastDeleted": "Підключення видалено.", + "toastDeleteFail": "Не вдалося видалити підключення." + }, + "sidebar": { + "title": "SQL-провідник", + "tooltipAddConnection": "Додати підключення", + "tooltipRefreshSchema": "Оновити схему", + "noConnections": "Немає збережених підключень", + "toastSchemaFail": "Не вдалося завантажити схему: {message}" + }, + "editor": { + "btnRun": "Виконати", + "executing": "Виконання запиту…", + "emptyTitle": "Виконайте запит для перегляду результатів", + "emptyHint": "Натисніть ⌘↩ або кнопку Виконати", + "toastNoConnection": "Немає активного підключення." + }, + "results": { + "filterPlaceholder": "Фільтр результатів…", + "rowCount": "{count} {count, plural, one {рядок} few {рядки} many {рядків} other {рядків}}", + "rowCountFiltered": "{filtered} / {total} рядків", + "exportCsv": "CSV", + "noRows": "Рядки не повернуто" + } + }, + "QrCodeGenerator": { + "title": "Генератор QR-кодів", + "subtitle": "Перетворіть будь-який текст або URL на PNG QR-код. Усе обробляється локально в браузері.", + "contentLabel": "Вміст", + "contentPlaceholder": "https://example.com, звичайний текст, рядок Wi‑Fi, vCard…", + "styleLabel": "Стиль", + "dotTypeLabel": "Форма точок", + "dotTypeSquare": "Квадрат", + "dotTypeDots": "Точки", + "dotTypeRounded": "Округлені", + "dotTypeExtraRounded": "Максимально округлені", + "dotTypeClassy": "Елегантні", + "dotTypeClassyRounded": "Елегантні округлені", + "cornerSquareLabel": "Кутові квадрати", + "cornerSquareDot": "Точка", + "cornerSquareSquare": "Квадрат", + "cornerSquareExtraRounded": "Максимально округлені", + "cornerDotLabel": "Кутові точки", + "cornerDotDot": "Точка", + "cornerDotSquare": "Квадрат", + "eccLabel": "Корекція помилок", + "eccL": "Низька (~7%)", + "eccM": "Середня (~15%)", + "eccQ": "Квартиль (~25%)", + "eccH": "Висока (~30%)", + "marginLabel": "Поле (модулі)", + "widthLabel": "Ширина зображення", + "widthEffective": "Рендер {px} px", + "darkColor": "Темні модулі", + "lightColor": "Світлі модулі", + "resetColors": "Скинути кольори", + "logoLabel": "Логотип по центру", + "logoMarginLabel": "Відступ", + "uploadLogo": "Завантажити зображення", + "removeLogo": "Видалити логотип", + "downloadPng": "Завантажити PNG", + "downloadSvg": "Завантажити SVG", + "copyImage": "Копіювати зображення", + "copied": "Скопійовано", + "clear": "Очистити", + "previewAlt": "Попередній перегляд QR", + "emptyHint": "Введіть або вставте вміст для перегляду.", + "filename": "qr-code.png", + "errors": { + "generationFailed": "Не вдалося згенерувати QR-код. Спробуйте коротший текст або нижчу корекцію.", + "copyUnsupported": "Браузер не підтримує копіювання зображення в буфер.", + "copyFailed": "Не вдалося скопіювати в буфер." + } + }, + "IpSubnetCalculator": { + "title": "Калькулятор IP / підмережі", + "subtitle": "Деталі CIDR IPv4 та IPv6 у браузері—без зовнішніх запитів.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Мережа", + "placeholderIpv4": "10.0.0.0/24 або 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 або 2001:db8::1/64", + "hintIpv4": "Використовуйте CIDR (напр. /24) або IPv4 з крапковою маскою.", + "hintIpv6": "Вкажіть довжину префікса (напр. /64). Якщо немає — /128.", + "copyValue": "Копіювати значення", + "rows": { + "cidr": "CIDR", + "network": "Адреса мережі", + "netmask": "Маска підмережі", + "wildcard": "Wildcard-маска", + "broadcast": "Широкомовлення", + "firstHost": "Перший вузол", + "lastHost": "Останній вузол", + "totalAddresses": "Усього адрес", + "usableHosts": "Доступні вузли", + "prefixLength": "Довжина префікса (біти)", + "networkIpv6": "Адреса мережі", + "lastIpv6": "Остання адреса в діапазоні", + "totalAddressesIpv6": "Адрес у підмережі" + }, + "usableNote": { + "pointToPoint": "/31 point-to-point використовує обидві адреси як вузли (RFC 3021).", + "singleHost": "/32 — один вузол." + }, + "ipv6RangeHint": { + "normal": "Показано повний математичний діапазон; правила маршрутизатора можуть відрізнятися.", + "single": "/128 — одна адреса.", + "full": "/0 охоплює весь IPv6." + }, + "errors": { + "invalidIpv4": "Некоректна мережа IPv4. Спробуйте CIDR або адресу + маску.", + "invalidIpv6": "Некоректна мережа IPv6. Використайте адресу/префікс, напр. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Генератор хешів", + "subtitle": "Контрольні суми MD5, SHA-1 і SHA-2 для тексту або файлів; bcrypt для паролів (текстовий ввід). Усе локально в браузері.", + "algorithmLabel": "Алгоритм", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Текст", + "modeFile": "Файл", + "inputLabelText": "Ввод", + "placeholderText": "Введіть або вставте текст…", + "chooseFile": "Вибрати файл", + "clearFile": "Очистити", + "fileHint": "Файли читаються лише в пам’яті; макс. {maxMb} МБ. Без завантаження на сервер.", + "outputLabel": "Дайджест (hex)", + "copyHash": "Копіювати дайджест", + "copied": "Скопійовано", + "byteCount": "{count, plural, one {# байт} few {# байти} many {# байтів} other {# байта}}", + "charCount": "{count, plural, one {# символ} few {# символи} many {# символів} other {# символа}}", + "emptyHint": "Введіть текст або виберіть файл.", + "securityNote": "Для нових задач безпеки MD5 і SHA-1 не рекомендуються; використовуйте SHA-256 або SHA-512.", + "errors": { + "fileTooLarge": "Файл більший за {maxMb} МБ.", + "fileReadFailed": "Не вдалося прочитати файл." + }, + "bcryptRoundsLabel": "Вартість (раунди)", + "bcryptRoundsHint": "Більше значення — повільніше й стійкіше до перебору. Зазвичай за замовчуванням 10.", + "fileTabDisabledTitle": "bcrypt хешує лише паролі з тексту, а не довільні файли.", + "placeholderBcrypt": "Введіть або вставте пароль для хешування…", + "outputLabelBcrypt": "Хеш bcrypt", + "copyBcrypt": "Копіювати хеш", + "emptyHintBcrypt": "Введіть пароль для обчислення bcrypt-хеша.", + "securityNoteBcrypt": "Кожен вихід bcrypt містить випадкову сіль; той самий пароль дає різний рядок щоразу. Це очікувано. У продакшені використовуйте високу вартість.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Пісочниця шифрування", + "subtitle": "AES-GCM у браузері: шифруйте в JSON-пакет паролем або сирим ключем і розшифровуйте локально. Навчальний і іноді практичний — нічого не надсилається.", + "tabEncrypt": "Зашифрувати", + "tabDecrypt": "Розшифрувати", + "keyModeLabel": "Ключовий матеріал", + "modePassphrase": "Парольна фраза (PBKDF2 + AES-GCM)", + "modeRawKey": "Сирий ключ AES (hex або Base64)", + "aesBitsLabel": "Довжина ключа AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Кодування сирого ключа", + "encodingAuto": "Авто (hex, якщо схоже на hex, інакше Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Сирий ключ", + "rawKeyPlaceholder": "Ключ 32 байти як 64 hex-символи або Base64 для AES-256 (довжина залежить від обраного AES вище).", + "rawKeyPlaceholderDecrypt": "Потрібно лише якщо в пакеті keyDerivation \"none\".", + "passphraseLabel": "Парольна фраза", + "passphrasePlaceholder": "Пароль використовується лише в браузері для виведення ключа.", + "passphrasePlaceholderDecrypt": "Потрібно лише якщо пакет використовує PBKDF2-SHA256.", + "pbkdf2IterLabel": "Ітерації PBKDF2", + "pbkdf2Hint": "Від 10 000 до 2 000 000. Типові налаштування сховищ паролів.", + "plaintextLabel": "Відкритий текст", + "plaintextPlaceholder": "Введіть або вставте повідомлення для шифрування…", + "bundleInputLabel": "Зашифрований пакет (JSON)", + "bundlePlaceholder": "Вставте JSON з «Зашифрувати» (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Зашифрований пакет", + "plaintextOutputLabel": "Відкритий текст", + "encryptButton": "Зашифрувати", + "decryptButton": "Розшифрувати", + "copyBundle": "Копіювати JSON", + "copyPlaintext": "Копіювати відкритий текст", + "copied": "Скопійовано", + "emptyBundleHint": "Зашифрований JSON з’явиться тут після шифрування.", + "emptyDecryptHint": "Розшифрований текст з’явиться тут.", + "decryptKeyHint": "Використовуйте сирий ключ, якщо keyDerivation — \"none\"; парольну фразу для PBKDF2-SHA256. У пакеті: сіль, ітерації, IV і довжина AES.", + "securityNote": "Ключі й відкритий текст не залишають браузер. Для продакшн-секретів використовуйте спеціалізовані інструменти.", + "errors": { + "emptyKey": "Введіть сирий ключ відповідно до розміру AES.", + "emptyPassphrase": "Введіть парольну фразу.", + "badKeyMaterial": "Не вдалося прочитати ключ як hex або Base64.", + "wrongKeyLength": "Довжина ключа не відповідає AES (16 / 24 / 32 байти).", + "invalidJson": "Некоректний JSON.", + "invalidBundle": "Цей JSON не є підтримуваним пакетом шифрування.", + "decryptFailed": "Розшифрування не вдалося — невірний секрет, пошкоджений пакет або параметри.", + "encryptFailed": "Шифрування не вдалося. Перевірте Web Crypto та введення." + } + }, + "HmacGenerator": { + "title": "Генератор HMAC", + "subtitle": "Для підпису вебхуків і тестування інтеграцій API. HMAC-SHA1/256/384/512 у hex або Base64—локально в браузері.", + "secretLabel": "Секретний ключ", + "messageLabel": "Повідомлення / корисне навантаження", + "placeholderSecret": "Вставте секрет підпису…", + "placeholderMessage": "Вставте сире тіло або рядок для перевірки (часто точні байти тіла вебхука).", + "algorithmLabel": "Алгоритм HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Формат виводу", + "formatHex": "Hex (нижній регістр)", + "formatBase64": "Base64", + "outputLabel": "Підпис", + "copyOutput": "Копіювати підпис", + "copied": "Скопійовано", + "byteCountHint": "{count, plural, one {# байт} few {# байти} many {# байтів} other {# байта}} (UTF-8)", + "emptyHint": "Введіть секретний ключ для обчислення HMAC.", + "securityNote": "Ніколи не діліться секретом підпису. Ця сторінка не завантажує дані — усе виконується в браузері.", + "errors": { + "cryptoFailed": "Не вдалося обчислити HMAC. Перевірте підтримку Web Crypto і спробуйте ще раз.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Генератор коду TOTP / 2FA", + "subtitle": "Вставте секрет Base32 і отримайте поточний шестизначний код RFC 6238 (SHA-1, 30 с). Усе локально в браузері—зручно тестувати вхід або MFA.", + "secretLabel": "Секрет (Base32)", + "placeholderSecret": "Вставте секретний ключ (літери A–Z і цифри 2–7, як під час налаштування автентифікатора)…", + "codeLabel": "Поточний код", + "copyCode": "Копіювати код", + "copied": "Скопійовано", + "nextIn": "Оновлення за {seconds} с", + "standardHint": "Крок часу 30 с, 6 цифр, HMAC-SHA-1 (сумісно з Google Authenticator).", + "digitsLabel": "Цифри", + "periodLabel": "Період", + "algorithmLabel": "Алгоритм", + "periodSeconds": "{seconds} с", + "emptyHint": "Вставте секрет, щоб згенерувати шестизначний код.", + "invalidHint": "Виправте секрет, щоб побачити код.", + "securityNote": "Ставтеся до секрету як до пароля. Він не залишає браузер; нічого не надсилається.", + "importLabel": "Імпорт з otpauth:// URI", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Імпортувати", + "qrSectionTitle": "QR-код підготовки", + "qrHint": "Скануйте застосунком-автентифікатором (Google Authenticator, 1Password, Aegis…), щоб додати цей секрет. QR-код кодує секрет — ставтеся до нього як до пароля.", + "accountLabel": "Обліковий запис", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Видавець (необов'язково)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Введіть дійсний секрет, щоб згенерувати QR-код.", + "errors": { + "invalidSecret": "Це недійсний секрет Base32.", + "cryptoFailed": "Не вдалося обчислити TOTP. Перевірте Web Crypto і спробуйте ще раз.", + "invalidUri": "Це не дійсний otpauth:// TOTP URI.", + "unsupportedUri": "Цей URI використовує значення, які інструмент не підтримує (кількість цифр має бути 6 або 8; період 15, 30 або 60 с)." + } + }, + "MarkdownPreview": { + "title": "Перегляд Markdown & HTML", + "subtitle": "Живий рендерер Markdown з експортом HTML і конвертацією HTML → Markdown. Все працює у вашому браузері.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Введення Markdown", + "htmlInputLabel": "Введення HTML", + "previewLabel": "Перегляд", + "htmlOutputLabel": "Джерело HTML", + "mdOutputLabel": "Виведення Markdown", + "inputPlaceholder": "# Привіт\\n\\nВведіть ваш **markdown** тут…", + "htmlInputPlaceholder": "Вставте ваш HTML тут…", + "copyHtml": "Копіювати HTML", + "copyMarkdown": "Копіювати Markdown", + "exportHtml": "Експортувати HTML", + "copied": "Скопійовано", + "charCount": "{count, plural, one {# символ} few {# символи} other {# символів}}", + "emptyHint": "Почніть вводити Markdown, щоб побачити попередній перегляд і HTML вивід.", + "emptyHintHtmlToMd": "Вставте HTML для конвертації в Markdown.", + "securityNote": "Все виконується локально у вашому браузері. Нічого не завантажується." + }, + "NumberBaseConverter": { + "title": "Конвертер систем числення", + "subtitle": "Перетворення цілих чисел між основами 2–36. Підтримка префіксів 0x / 0b / 0o. Усе локально в браузері.", + "inputBaseLabel": "З основи", + "outputBaseLabel": "В основу", + "inputLabel": "Вхідне число", + "placeholderInput": "Введіть ціле число у вибраній вхідній основі…", + "outputLabel": "Результат", + "copyOutput": "Копіювати результат", + "copied": "Скопійовано", + "swap": "Поміняти основи", + "byteCountHint": "{count, plural, one {# байт} few {# байти} many {# байтів} other {# байта}} (довжина UTF-8)", + "emptyHint": "Введіть число для перетворення.", + "note": "Цифри понад 9 — літери A–Z (без урахування регістру). Пробіли та підкреслення ігноруються.", + "errors": { + "invalidNumber": "Неприпустиме значення для вибраної вхідної основи." + } + }, + "FormatConverter": { + "title": "Конвертер форматів", + "subtitle": "Конвертуйте між JSON, YAML, TOML і XML у будь-якому напрямку. Всі 12 комбінацій. Усе працює у вашому браузері.", + "fromLabel": "З", + "toLabel": "В", + "swap": "Поміняти", + "swapTitle": "Поміняти формати та використати вивід як введення", + "copy": "Копіювати", + "copied": "Скопійовано", + "charCount": "{count, plural, one {# символ} few {# символи} other {# символів}}", + "emptyHint": "Вставте або введіть дані ліворуч, щоб побачити конвертований вивід.", + "localNote": "Усе працює локально у вашому браузері. Нічого не завантажується." + }, + "MimeTypeLookup": { + "title": "Пошук MIME-типу", + "subtitle": "Знайдіть MIME-тип (media type) за розширенням або повною назвою файлу. Усе працює локально у вашому браузері.", + "inputLabel": "Розширення або назва файлу", + "placeholder": "наприклад .png, package.json, photo.jpeg", + "hint": "Порада: вставте розширення (з крапкою або без) або повну назву файлу.", + "clear": "Очистити", + "exampleFilename": "Використати приклад назви файлу", + "exampleExtension": "Використати приклад розширення", + "resultLabel": "Результат", + "normalizedLabel": "Нормалізоване введення:", + "mimeTypeLabel": "MIME-тип", + "contentTypeLabel": "Заголовок Content-Type", + "charsetLabel": "Кодування", + "copyMimeType": "Копіювати MIME-тип", + "copy": "Копіювати", + "copied": "Скопійовано", + "emptyHint": "Введіть розширення або назву файлу, щоб знайти MIME-тип.", + "notFound": "Для цього введення MIME-тип не знайдено.", + "localNote": "Усе працює локально у вашому браузері. Нічого не завантажується." + }, + "UserAgentParser": { + "title": "Парсер User-Agent", + "subtitle": "Вставте рядок User-Agent (UA) і отримайте розбір браузера, ОС, engine, пристрою та CPU. Усе працює локально у вашому браузері.", + "inputLabel": "Рядок User-Agent", + "placeholder": "Вставте ваш User-Agent сюди…", + "hint": "Порада: ви можете скопіювати UA з заголовків запиту, логів або devtools браузера.", + "clear": "Очистити", + "exampleDesktop": "Використати приклад для ПК", + "exampleMobile": "Використати приклад для мобільного", + "resultLabel": "Розбір", + "browserLabel": "Браузер", + "osLabel": "Операційна система", + "engineLabel": "Engine", + "deviceTypeLabel": "Тип пристрою", + "deviceVendorLabel": "Виробник", + "deviceModelLabel": "Модель", + "cpuArchLabel": "Архітектура CPU", + "copyJson": "Копіювати JSON", + "copy": "Копіювати", + "copied": "Скопійовано", + "emptyHint": "Вставте рядок User-Agent, щоб побачити розбір.", + "localNote": "Усе працює локально у вашому браузері. Нічого не завантажується." + }, + "MockDataGenerator": { + "title": "Генератор тестових даних", + "subtitle": "Задайте стовпці, оберіть типи й експортуйте набори для сидів, Postman чи тестів. До {max} рядків, усе в браузері — без завантаження на сервер.", + "mockarooLink": "Сценарій як у Mockaroo", + "rowsLabel": "Рядки", + "rowsHint": "Щонайбільше {max} рядків за генерацію, щоб вкладка лишалась швидкою.", + "formatLabel": "Формат", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Назва таблиці SQL", + "presetsLabel": "Шаблони", + "presetPlaceholder": "Завантажити шаблон…", + "presetDefault": "Старт (користувач)", + "presetUsers": "Користувачі та ролі", + "presetProducts": "SKU електронної комерції", + "presetApiLog": "Журнал HTTP-запитів", + "generate": "Згенерувати", + "warningDuplicateNames": "Дублікати назв стовпців: пізніші перезаписують попередні в JSON. Перейменуйте.", + "schemaHeading": "Схема", + "addField": "Додати стовпець", + "removeField": "Видалити стовпець", + "duplicateField": "Дублювати стовпець", + "colName": "Назва", + "colType": "Тип", + "colOptions": "Параметри", + "colBlank": "Порожньо %", + "optMin": "мін", + "optMax": "макс", + "optDecimals": "дроб", + "optDateFrom": "від дати", + "optDateTo": "до дати", + "optValues": "a, b, c", + "optStep": "крок", + "optRowBase": "база", + "output": "Результат", + "outputPlaceholder": "Натисніть «Згенерувати», щоб створити дані…", + "copy": "Копіювати", + "download": "Завантажити", + "copied": "Скопійовано", + "groups": { + "personal": "Особисте", + "location": "Локація", + "internetApi": "Інтернет і API", + "numbersIds": "Числа та ідентифікатори", + "dateTime": "Дата й час", + "textDesign": "Текст і оформлення", + "custom": "Власний" + }, + "fieldTypes": { + "first_name": "Ім'я", + "last_name": "Прізвище", + "full_name": "Повне ім'я", + "email": "Email", + "username": "Ім'я користувача", + "password": "Пароль", + "phone": "Телефон", + "company": "Компанія", + "job_title": "Посада", + "avatar_url": "URL аватара", + "city": "Місто", + "country": "Країна", + "country_code": "Код країни (ISO)", + "street_address": "Адреса", + "zip_code": "Поштовий індекс", + "latitude": "Широта", + "longitude": "Довгота", + "url": "URL", + "domain": "Домен", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Ціле число", + "float": "Дробове число", + "boolean": "Логічне", + "uuid": "UUID", + "credit_card": "Картка (тест)", + "semver": "SemVer", + "row_index": "Індекс рядка", + "sequence": "Послідовність", + "date": "Дата", + "datetime": "Дата й час", + "unix_timestamp": "Мітка часу Unix", + "timezone": "Часовий пояс", + "word": "Слово", + "sentence": "Речення", + "paragraph": "Абзац", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Код валюти", + "language_code": "Мовна мітка", + "custom_list": "Власний список" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Оптимізатор SVG", + "subtitle": "Вставте SVG, мініфікуйте через SVGO в браузері та порівняйте розмір UTF-8 до й після.", + "inputLabel": "Вхід", + "outputLabel": "Оптимізовано", + "placeholder": "Вставте фрагмент або весь файл…", + "outputPlaceholder": "Оптимізований SVG з’явиться тут…", + "chars": "{count} симв. • {bytes} байт (UTF-8)", + "preview": "Перегляд", + "localNote": "Усе локально в браузері. Нічого не надсилається.", + "stats": { + "title": "Розмір (UTF-8)", + "before": "До", + "after": "Після", + "savedBytes": "Заощаджено {count} байт ({percent}%)", + "noChange": "Без зменшення — уже мінімально або порожній вивід." + }, + "actions": { + "clear": "Очистити", + "sample": "Приклад", + "copy": "Копіювати", + "copied": "Скопійовано", + "download": "Завантажити" + }, + "errors": { + "optimizeFailed": "Не вдалося оптимізувати цей SVG." + } + }, + "UnitConverter": { + "title": "Конвертер одиниць", + "subtitle": "Конвертуйте між 323 одиницями у 43 категоріях", + "category": "Категорія", + "from": "З", + "to": "В", + "value": "Значення", + "result": "Результат", + "swapUnits": "Поміняти одиниці", + "placeholder": "Введіть значення", + "copyResult": "Скопіювати результат", + "searchCategory": "Пошук категорії...", + "selectCategory": "Виберіть категорію", + "noResults": "Категорію не знайдено." + }, + "DockerComposeGenerator": { + "title": "Генератор Docker Compose", + "subtitle": "Оберіть популярні бази даних, кеші, брокери повідомлень, зворотні проксі та спостережуваність — скопіюйте готовий docker-compose.yml для локальної розробки.", + "searchPlaceholder": "Пошук (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Сервіси", + "selectedCount": "{count, plural, one {# сервіс} few {# сервіси} many {# сервісів} other {# сервісів}}", + "outputTitle": "docker-compose.yml", + "copy": "Копіювати", + "copied": "Скопійовано!", + "download": "Завантажити", + "emptyState": "Оберіть один або кілька сервісів, щоб згенерувати YAML Compose.", + "hintPorts": "Порти хоста підібрані, щоб рідше конфліктували; змініть за потреби. Замініть демо-паролі перед спільними середовищами.", + "categories": { + "databases": "Бази даних і сховища", + "cache": "Кеш і in-memory", + "queues": "Черги та обмін повідомленнями", + "search": "Пошук і журнали", + "storage": "Об’єктне сховище", + "proxy": "Зворотні проксі та ingress", + "observability": "Метрики та трасування", + "mail": "Пошта (локальна розробка)", + "admin": "Адмін-інтерфейси та BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Очистити" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (сумісний з Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (сумісна з Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (сумісний з S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (перехоплення SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (режим розробки)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Просуньте плитки та об 'єднайте їх, щоб досягти 2048 року", + "score": "Рахунок", + "best": "Best", + "newGame": "Нова гра", + "howToPlay": "Використовуйте клавіші зі стрілками (або проведіть пальцем на мобільному пристрої) для переміщення плиток. Плитки з однаковим номером об 'єднуються. Досягніть 2048 року, щоб виграти!", + "youWin": "Ви досягли 2048 року!", + "keepGoing": "Продовжуй.", + "gameOver": "Гру завершено", + "tryAgain": "Спробуйте ще раз", + "gameOverMessage": "Залишилося більше рухів.", + "undo": "Скасувати" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "етапи завершено", + "time": "Позначка часу", + "best": "Best", + "difficulty": { + "easy": "Легко", + "medium": "Середня", + "hard": "Складно" + }, + "stage": "Сцена", + "solved": "Вирішено", + "score": "Рахунок", + "nextStage": "Наступний етап", + "replay": "Повторити", + "erase": "Витерти", + "howToPlay": "Клацніть клітинку, а потім торкніться числа або натисніть 1–9. Backspace або Delete очищує комірку. Жирні цифри - це ключі. Заповніть сітку так, щоб кожен рядок, стовпець і поле 3×3 містили 1–9 без повторів.", + "pencil": "Олівець", + "undo": "Скасувати", + "hint": "Підказка", + "hintsUsed": "підказки використані", + "mistakes": "Помилки", + "failed": "Провал!", + "mistakeMode": "Режим 3 помилок" + }, + "Snake": { + "title": "Змійка", + "subtitle": "Їж їжу, ростай, уникай стін і себе", + "score": "Очки", + "best": "Найкращий", + "startPrompt": "Натисніть будь-яку клавішу зі стрілкою", + "startButton": "Почати гру", + "gameOver": "Гра закінчена", + "playAgain": "Грати знову", + "howToPlay": "Використовуй стрілки або WASD. Їж, щоб рости. Швидкість зростає кожні 50 очок.", + "diffEasy": "Легкий", + "diffNormal": "Звичайний", + "diffHard": "Важкий", + "diffInfinite": "Нескінченний", + "orTap": "або торкніться нижче", + "howToPlayInfinite": "Стіни огинають — смерть від стін вимкнена. Їж, щоб рости.", + "shieldActive": "Щит активний", + "speedBoost": "Прискорення", + "powerSpeed": "Швидкість x2", + "powerShield": "Пережити одну смерть", + "powerShrink": "Вкорочує хвіст" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Підключення Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Підключайтеся до екземплярів Redis. Переглядайте ключі, редагуйте значення та виконуйте команди. Облікові дані шифруються у браузері.", + "heroAddConnection": "Додати підключення", + "vaultLockedTitle": "Сховище заблоковано", + "vaultLockedDesc": "Розблокуйте сховище для доступу до збережених підключень." + } + }, + "Minesweeper": { + "title": "Сапер", + "subtitle": "Знайдіть усі безпечні клітинки, не підірвавшись на міні", + "mines": "Міни", + "newGame": "Нова гра", + "win": "Ви виграли!", + "loss": "Ви програли!", + "playAgain": "Грати знову", + "tryAgain": "Спробувати знову", + "startPrompt": "Натисніть будь-яку клітинку, щоб почати", + "difficulty": { + "beginner": "Початківець", + "intermediate": "Середній", + "expert": "Експерт" + } + }, + "Tetris": { + "title": "Тетріс", + "subtitle": "Розташовуйте блоки, що падають, щоб заповнити ряди", + "next": "Наступний", + "score": "Очки", + "best": "Рекорд", + "lines": "Лінії", + "level": "Рівень", + "pause": "Пауза", + "resume": "Продовжити", + "start": "Старт", + "restart": "Заново", + "paused": "На паузі", + "gameOver": "Гру завершено" + }, + "SshKeyGenerator": { + "title": "Генератор ключів SSH / RSA", + "subtitle": "Генеруйте пари ключів Ed25519 або RSA у браузері. Завантажуйте приватні та публічні ключі у форматах OpenSSH і PEM. Нічого не завантажується на сервер.", + "keyTypeLabel": "Тип ключа", + "recommended": "рекомендовано", + "ed25519Desc": "Швидкий, компактний, сучасний. Підтримується GitHub, GitLab та всіма основними SSH-серверами.", + "rsa2048Desc": "Широка сумісність. Безпечний для більшості завдань.", + "rsa4096Desc": "Максимальна сумісність і надійність для застарілих систем.", + "commentLabel": "Коментар (необов'язково)", + "commentHint": "Додається до рядка публічного ключа OpenSSH.", + "generateBtn": "Згенерувати пару ключів", + "generating": "Генерація…", + "generatingLong": "Генерація (може зайняти трохи часу)…", + "copy": "Копіювати", + "download": "Завантажити", + "privateKeyLabel": "Приватний ключ (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Публічний ключ (формат OpenSSH)", + "publicKeySpkiLabel": "Публічний ключ (SPKI PEM)", + "browserNote": "Всі ключі генеруються у вашому браузері. Нічого не надсилається на жоден сервер.", + "errors": { + "generationFailed": "Помилка генерації ключа" + } + }, + "DnsLookup": { + "title": "DNS-запит", + "subtitle": "Запитуйте DNS-записи для будь-якого домену — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Введіть домен (наприклад github.com)", + "lookupBtn": "Запит", + "looking": "Виконується запит…", + "refreshBtn": "Оновити", + "recordTypesLabel": "Типи записів", + "allBtn": "Усі", + "tryLabel": "Спробувати:", + "errors": { + "lookupFailed": "Помилка DNS-запиту" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Генератор і перевірка Bcrypt", + "subtitle": "Хешуйте паролі за допомогою bcrypt і перевіряйте хеші — усе залишається у вашому браузері", + "tabs": { + "hash": "Хеш", + "verify": "Перевірка" + }, + "showPassword": "Показати пароль", + "hidePassword": "Приховати пароль", + "copy": "Копіювати", + "copied": "Скопійовано", + "hash": { + "passwordLabel": "Пароль", + "passwordPlaceholder": "Введіть пароль для хешування…", + "costLabel": "Фактор вартості", + "costHint": "Вищий фактор подвоює роботу на кожному кроці. 10-12 — поширений вибір для продакшену.", + "slowWarning": "Фактори вартості 13 і вище можуть обчислюватися кілька секунд.", + "generate": "Створити хеш", + "generating": "Хешування…", + "resultLabel": "Хеш bcrypt", + "timeTaken": "{ms} мс", + "anatomy": { + "title": "Будова хешу", + "algorithm": "алгоритм", + "cost": "вартість", + "salt": "сіль (22 символи)", + "hash": "хеш (31 символ)" + } + }, + "verify": { + "passwordLabel": "Пароль", + "passwordPlaceholder": "Введіть пароль для перевірки…", + "hashLabel": "Хеш bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Введіть пароль і хеш bcrypt, щоб порівняти їх.", + "invalidHash": "Це не схоже на коректний хеш bcrypt.", + "checking": "Порівняння…", + "match": "Збігається — пароль дає цей хеш.", + "noMatch": "Не збігається — пароль не дає цей хеш." + } + }, + "ChmodCalculator": { + "title": "Калькулятор Chmod", + "subtitle": "Перетворюйте права файлів Unix між прапорцями, вісімковою та символьною формою", + "grid": { + "title": "Права", + "read": "Читання", + "write": "Запис", + "execute": "Виконання", + "owner": "Власник", + "group": "Група", + "others": "Інші" + }, + "special": { + "title": "Спеціальні біти", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Вісімкова", + "placeholder": "755 або 4755", + "invalid": "Введіть 3 або 4 вісімкові цифри (0-7)." + }, + "symbolic": { + "label": "Символьна", + "placeholder": "rwxr-xr-x", + "invalid": "Введіть 9 символів на кшталт rwxr-xr-x (дозволені s/S, t/T)." + }, + "output": { + "title": "Команда", + "copy": "Копіювати", + "copied": "Скопійовано" + }, + "explain": { + "line": "{cls} може {perms}.", + "none": "{cls} не має прав.", + "setuid": "Setuid: файл виконується з привілеями свого власника.", + "setgid": "Setgid: файл виконується з привілеями своєї групи (нові файли в каталозі успадковують його групу).", + "sticky": "Sticky: лише власник файлу може видалити або перейменувати його в цьому каталозі." + }, + "presets": { + "title": "Поширені пресети", + "octal": "Вісімкова", + "permissions": "Права", + "description": "Типове застосування", + "p644": "Звичайні файли: власник пише, усі читають.", + "p755": "Виконувані файли та каталоги: усі можуть читати й заходити.", + "p600": "Приватні файли: лише власник читає й пише.", + "p700": "Приватні виконувані файли та каталоги: лише власник.", + "p664": "Файли, доступні групі для запису.", + "p775": "Каталоги та виконувані файли, доступні групі для запису.", + "p400": "Лише читання для власника (наприклад, приватні ключі SSH)." + } + }, + "CodeScreenshot": { + "title": "Скриншот коду", + "subtitle": "Перетворюйте код на красиві зображення для публікації", + "languageLabel": "Мова", + "autoDetect": "Автовизначення", + "backgroundLabel": "Фон", + "paddingLabel": "Відступ", + "filenameLabel": "Ім'я файлу", + "filenamePlaceholder": "example.ts", + "windowLabel": "Крапки вікна", + "themeLabel": "Тема вікна", + "themeDark": "Темна", + "themeLight": "Світла", + "scaleLabel": "Масштаб експорту", + "copyImage": "Копіювати зображення", + "copied": "Скопійовано", + "exportPng": "Експорт PNG", + "inputLabel": "Код", + "detected": "Визначено: {language}", + "clear": "Очистити", + "placeholder": "Вставте ваш код сюди…", + "previewLabel": "Перегляд", + "emptyPreview": "Тут з'явиться перегляд вашого коду", + "exportFailed": "Не вдалося експортувати зображення", + "copyUnsupported": "Копіювання зображень не підтримується в цьому браузері", + "copyFailed": "Не вдалося скопіювати зображення", + "backgrounds": { + "ocean": "Океан", + "sunset": "Захід сонця", + "candy": "Цукерка", + "forest": "Ліс", + "midnight": "Північ", + "gold": "Золото", + "aurora": "Аврора", + "slate": "Сланець", + "transparent": "Прозорий" + } + }, + "CssGenerators": { + "title": "Генератори CSS", + "subtitle": "Box-shadow, border-radius та адаптивний clamp() — копіюйте CSS.", + "preview": "Перегляд", + "output": "CSS", + "copy": "Копіювати", + "copied": "Скопійовано", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "Адаптивний clamp()" + }, + "boxShadow": { + "presets": "Пресети", + "layer": "Шар", + "addLayer": "Додати шар", + "removeLayer": "Видалити шар", + "x": "X", + "y": "Y", + "blur": "Розмиття", + "spread": "Розтягнення", + "color": "Колір", + "alpha": "Альфа", + "inset": "Внутрішня", + "presetNames": { + "subtleCard": "Легка картка", + "elevated": "Піднята", + "hardOffset": "Жорстке зміщення", + "neonGlow": "Неонове сяйво", + "inner": "Внутрішня", + "layeredSmooth": "М'які шари" + } + }, + "borderRadius": { + "modeLabel": "Режим", + "modes": { + "simple": "По кутах", + "blob": "Органічна крапля" + }, + "unit": "Одиниця", + "linkCorners": "Зв'язати кути", + "randomize": "Випадково", + "horizontal": "Горизонтальні радіуси", + "vertical": "Вертикальні радіуси", + "corners": { + "tl": "Верхній лівий", + "tr": "Верхній правий", + "br": "Нижній правий", + "bl": "Нижній лівий" + } + }, + "clamp": { + "minFont": "Мін. шрифт (px)", + "maxFont": "Макс. шрифт (px)", + "minViewport": "Мін. viewport (px)", + "maxViewport": "Макс. viewport (px)", + "rootFont": "Кореневий шрифт (px)", + "viewportWidth": "Змодельований viewport", + "computedSize": "Обчислений розмір", + "slope": "Нахил", + "intercept": "Зміщення", + "previewText": "Швидка руда лисиця", + "invalid": "Введіть коректні та різні межі viewport." + } + }, + "ExifViewer": { + "title": "Перегляд і видалення EXIF", + "subtitle": "Досліджуйте метадані фото та видаляйте їх у вашому браузері", + "dropTitle": "Перетягніть зображення сюди", + "dropHint": "або натисніть для вибору — ваше зображення ніколи не залишає ваш пристрій", + "browse": "Вибрати зображення", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Читання метаданих…", + "parseFailed": "Не вдалося прочитати це зображення", + "clear": "Видалити зображення", + "gpsWarning": "Містить GPS-координати", + "openMap": "Показати на OpenStreetMap", + "noMetadata": "У цьому зображенні не знайдено вбудованих метаданих.", + "facts": { + "name": "Ім'я", + "type": "Тип", + "dimensions": "Розміри", + "size": "Розмір" + }, + "groups": { + "camera": "Камера", + "exposure": "Експозиція", + "timestamps": "Мітки часу", + "gps": "GPS-координати", + "software": "ПЗ та інше" + }, + "gpsCard": { + "latitude": "Широта", + "longitude": "Довгота", + "altitude": "Висота" + }, + "raw": { + "title": "Усі метадані", + "copyJson": "Копіювати JSON", + "copied": "Скопійовано" + }, + "remove": { + "title": "Видалити метадані", + "jpegDescription": "Без втрат видаліть EXIF, XMP, IPTC і коментарі без повторного стиснення зображення.", + "reencodeDescription": "Перекодуйте зображення, щоб видалити вбудовані метадані. При цьому пікселі стискаються повторно.", + "keepIcc": "Зберегти колірний профіль (ICC)", + "stripButton": "Видалити метадані", + "reencodeButton": "Перекодувати й видалити", + "unsupported": "Видалення метаданих не підтримується для цього типу файлу.", + "failed": "Не вдалося видалити метадані", + "before": "До", + "after": "Після", + "download": "Завантажити очищене зображення" + } + }, + "FaviconGenerator": { + "title": "Генератор favicon", + "subtitle": "Створюйте favicon та іконки застосунків з одного зображення", + "dropTitle": "Перетягніть зображення сюди", + "dropHint": "PNG, JPEG або SVG — натисніть для вибору", + "browse": "Вибрати зображення", + "clear": "Видалити зображення", + "backgroundLabel": "Фон", + "transparent": "Прозорий", + "solidColor": "Суцільний", + "cornerLabel": "Форма кутів", + "cornerNone": "Квадрат", + "cornerRounded": "Заокруглений", + "cornerCircle": "Коло", + "maskPaddingLabel": "Відступ maskable", + "maskable": "maskable", + "previewLabel": "Перегляд", + "copy": "Копіювати", + "headSnippet": "Фрагмент для head HTML", + "downloadIco": "Завантажити favicon.ico", + "downloadZip": "Завантажити все (.zip)" + }, + "JsonDiff": { + "title": "Порівняння JSON", + "subtitle": "Семантичне порівняння двох документів JSON з урахуванням структури — порядок ключів і форматування ігноруються", + "options": { + "ignoreArrayOrder": "Ігнорувати порядок масивів" + }, + "loadBothSamples": "Завантажити приклади", + "sample": "Приклад", + "format": "Формат", + "clear": "Очистити", + "formatError": "Не вдається відформатувати: введення не є коректним JSON", + "placeholder": "Вставте або введіть JSON тут…", + "panels": { + "left": "Оригінал (ліворуч)", + "right": "Змінений (праворуч)", + "results": "Відмінності" + }, + "summary": { + "added": "{count} додано", + "removed": "{count} вилучено", + "changed": "{count} змінено" + }, + "types": { + "added": "Додано", + "removed": "Вилучено", + "changed": "Змінено" + }, + "copyChanges": "Копіювати як JSON", + "copied": "Скопійовано", + "empty": "Вставте JSON в обидві панелі, щоб побачити їхні відмінності.", + "identical": "Обидва документи семантично ідентичні." + }, + "JsonpathPlayground": { + "title": "Пісочниця JSONPath", + "subtitle": "Запитуйте документи JSON за допомогою JSONPath або JMESPath і дивіться результати наживо", + "engineLabel": "Рушій", + "queryLabel": "Запит", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Шпаргалка", + "cheats": { + "jpAllTitles": "Усі назви книг", + "jpAllAuthors": "Усі автори будь-де в документі", + "jpFirstBook": "Перша книга", + "jpLastBook": "Остання книга", + "jpSlice": "Перші дві книги (зріз)", + "jpFilterPrice": "Книги дешевші за 40", + "jpFilterInStock": "Назви книг у наявності", + "jpRecursive": "Усі значення рекурсивно", + "jmAllTitles": "Усі назви книг", + "jmFirstAuthor": "Автор першої книги", + "jmLastBook": "Остання книга", + "jmSlice": "Перші дві книги (зріз)", + "jmFilterPrice": "Назви книг дешевших за 40", + "jmCount": "Підрахунок книг у наявності", + "jmProjection": "Перетворити кожну книгу на новий об'єкт", + "jmSort": "Назви, відсортовані за ціною" + }, + "panels": { + "input": "Введення JSON", + "results": "Результати" + }, + "sample": "Приклад", + "format": "Формат", + "clear": "Очистити", + "formatError": "Не вдається відформатувати: введення не є коректним JSON", + "placeholder": "Вставте або введіть JSON тут…", + "outputPlaceholder": "Збіги з'являться тут…", + "matchCount": "{count, plural, one {# збіг} few {# збіги} many {# збігів} other {# збігу}}", + "copy": "Копіювати", + "copied": "Скопійовано" + }, + "KeycodeInspector": { + "title": "Інспектор клавіш", + "subtitle": "Натисніть будь-яку клавішу, щоб дослідити її подію клавіатури JavaScript", + "capture": { + "prompt": "Натисніть будь-яку клавішу…", + "ready": "Слухаю — натисніть будь-яку клавішу на клавіатурі", + "clickToFocus": "Натисніть тут для фокуса, потім натисніть будь-яку клавішу" + }, + "hero": { + "spaceKey": "(Пробіл)", + "keyLabel": "event.key", + "repeat": "повтор" + }, + "copyJson": "Копіювати JSON", + "copiedJson": "JSON події скопійовано до буфера обміну", + "deprecated": "застаріле", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Стандартне", + "left": "Ліворуч", + "right": "Праворуч", + "numpad": "Цифровий блок", + "unknown": "Невідоме" + }, + "history": { + "title": "Історія", + "clear": "Очистити історію", + "empty": "Натискання клавіш з'являться тут." + } + }, + "MarkdownTableGenerator": { + "title": "Генератор таблиць Markdown", + "subtitle": "Редагуйте сітку, вставляйте з таблиці та копіюйте гарно вирівняну таблицю Markdown або HTML", + "addRow": "Додати рядок", + "addColumn": "Додати стовпець", + "importLabel": "Імпорт", + "importPlaceholder": "Вставте TSV (з Excel / Google Sheets) або CSV тут…", + "importButton": "Імпортувати в сітку", + "pasteClipboard": "Вставити з буфера обміну", + "importEmpty": "Немає що імпортувати — спершу вставте кілька рядків", + "clipboardError": "Не вдалося прочитати з буфера обміну", + "clear": "Очистити", + "panels": { + "editor": "Редактор таблиці", + "output": "Результат" + }, + "align": { + "left": "Вирівняти ліворуч", + "center": "Вирівняти по центру", + "right": "Вирівняти праворуч" + }, + "headerPlaceholder": "Заголовок", + "cellPlaceholder": "Комірка", + "removeRow": "Видалити рядок", + "removeColumn": "Видалити стовпець", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Копіювати", + "copied": "Скопійовано" + }, + "TimezoneConverter": { + "title": "Конвертер часових поясів", + "subtitle": "Перетворюйте один момент по всьому світу для планування зустрічей.", + "dateTimeLabel": "Дата й час", + "now": "Зараз", + "sourceZoneLabel": "Вихідний часовий пояс", + "selectZone": "Виберіть пояс", + "searchZone": "Пошук часових поясів...", + "noZone": "Часовий пояс не знайдено.", + "scrubLabel": "Зсунути час", + "reset": "Скинути", + "targetsLabel": "Цільові пояси", + "addZone": "Додати пояс", + "remove": "Видалити", + "localBadge": "Локальний", + "invalidDate": "Введіть коректні дату й час.", + "bands": { + "work": "Робочі години (09:00–18:00)", + "edge": "Крайні години (ранній ранок / вечір)", + "night": "Ніч" + } + }, + "TokenCounter": { + "title": "Лічильник токенів LLM", + "subtitle": "Рахуйте токени, візуалізуйте чанки та оцінюйте вартість API.", + "inputLabel": "Текст", + "sample": "Приклад", + "clear": "Очистити", + "placeholder": "Вставте текст, код або JSON для підрахунку токенів...", + "encoding": "Кодування", + "loading": "Завантаження токенізатора...", + "encodings": { + "o200k": "o200k_base (GPT-4o / серія o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Токени", + "characters": "Символи", + "words": "Слова", + "ratio": "Токенів/слово", + "ratioSub": "прибл." + }, + "claudeRow": "Claude (приблизно)", + "estimate": "оцінка", + "viz": { + "title": "Візуалізація токенів", + "toggle": "Показати", + "hint": "Увімкніть, щоб розфарбувати кожен токен як чанк.", + "empty": "Введіть текст для візуалізації токенів.", + "truncated": "Показано перші {limit} з {total} токенів." + }, + "cost": { + "title": "Калькулятор вартості", + "disclaimer": "редаговані значення за замовчуванням — перевірте актуальні ціни", + "model": "Модель", + "expectedOutput": "Очікувані токени виводу", + "customInput": "Введення $/MTok", + "customOutput": "Вивід $/MTok", + "inputCost": "Вартість введення", + "outputCost": "Вартість виводу", + "total": "Разом", + "per1000": "За 1000 викликів" + } + }, + "WebhookTester": { + "title": "Тестер вебхуків", + "subtitle": "Створіть тимчасовий URL і досліджуйте вхідні запити вебхуків наживо.", + "intro": { + "title": "Створіть URL вебхука, щоб почати захоплення запитів", + "hint": "Ви отримаєте непередбачуваний URL, який записує кожен вхідний запит. Він спливає через 24 години." + }, + "createBtn": "Створити URL вебхука", + "creating": "Створення…", + "urlLabel": "Ваш URL вебхука", + "expiresIn": "Спливає через {countdown}", + "copyUrlBtn": "Копіювати URL", + "copied": "Скопійовано", + "clearBtn": "Очистити", + "newBinBtn": "Новий URL", + "requestsTitle": "Запити", + "selectPrompt": "Виберіть запит, щоб дослідити його деталі.", + "expiredNotice": "Цей URL вебхука сплив. Створіть новий, щоб продовжити тестування.", + "base64Badge": "base64", + "truncatedBadge": "обрізано", + "empty": { + "title": "Очікування запитів…", + "hint": "Надішліть запит на ваш URL, і він з'явиться тут миттєво. Спробуйте так:" + }, + "detail": { + "receivedAt": "Отримано", + "source": "IP джерела", + "contentType": "Content-Type", + "query": "Параметри query", + "headers": "Заголовки", + "body": "Тіло", + "copyBody": "Копіювати тіло", + "emptyBody": "Немає тіла" + }, + "errors": { + "createFailed": "Не вдалося створити URL вебхука. Спробуйте ще раз.", + "clearFailed": "Не вдалося очистити запити. Спробуйте ще раз." + } + }, + "WebsocketTester": { + "title": "Тестер WebSocket", + "subtitle": "Підключайтеся до ендпоінта WebSocket і надсилайте, отримуйте та досліджуйте повідомлення наживо", + "urlLabel": "URL ендпоінта", + "protocolsLabel": "Підпротоколи", + "protocolsPlaceholder": "необов'язково, через кому", + "connect": "Підключити", + "disconnect": "Відключити", + "status": { + "disconnected": "Відключено", + "connecting": "Підключення", + "connected": "Підключено", + "error": "Помилка" + }, + "messages": { + "connecting": "Підключення до {url}…", + "connected": "З'єднання встановлено", + "connectedWithProtocol": "З'єднання встановлено (протокол: {protocol})", + "invalidUrl": "Некоректний URL — має починатися з ws:// або wss://", + "connectFailed": "Не вдалося відкрити з'єднання", + "socketError": "Сталася помилка з'єднання", + "notConnected": "Не підключено — спершу підключіться", + "binaryMessage": "Отримано двійкове повідомлення" + }, + "log": { + "title": "Журнал повідомлень", + "empty": "Повідомлень поки немає. Підключіться та почніть надсилати.", + "copyAll": "Копіювати все", + "download": "Завантажити як .txt", + "clear": "Очистити журнал", + "prettyPrint": "Форматувати JSON", + "showRaw": "Показати необроблений текст" + }, + "composer": { + "placeholder": "Введіть повідомлення для надсилання…", + "send": "Надіслати", + "formatJson": "Форматувати як JSON", + "invalidJson": "Повідомлення не є коректним JSON", + "hint": "Cmd/Ctrl+Enter для надсилання" + }, + "ping": { + "label": "Авто-пінг", + "seconds": "с", + "intervalTitle": "Інтервал пінгу в секундах", + "payloadPlaceholder": "payload пінгу" + } + }, + "WhoisLookup": { + "title": "Whois-запит", + "subtitle": "Дізнавайтеся дані реєстрації будь-якого домену або IP-адреси через RDAP", + "placeholder": "example.com або 8.8.8.8", + "lookupBtn": "Запитати", + "looking": "Запит…", + "refreshBtn": "Оновити", + "tryLabel": "Спробуйте:", + "copy": "Копіювати", + "copied": "Скопійовано", + "notAvailable": "Недоступно", + "summary": { + "registrar": "Реєстратор", + "network": "Мережа" + }, + "dates": { + "created": "Створено", + "updated": "Оновлено", + "expires": "Спливає" + }, + "sections": { + "status": "Статус", + "nameservers": "Сервери імен", + "contacts": "Контакти", + "rawRdap": "Необроблений RDAP" + }, + "contacts": { + "roles": "Ролі", + "name": "Ім'я", + "organization": "Організація" + }, + "errors": { + "invalidTarget": "Введіть коректне доменне ім'я або IP-адресу", + "lookupFailed": "Запит не вдався (статус {status})" + } + } +} diff --git a/apps/desktop-ui/messages/vi.json b/apps/desktop-ui/messages/vi.json new file mode 100644 index 00000000..dff80a21 --- /dev/null +++ b/apps/desktop-ui/messages/vi.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "Bảng điều khiển", + "settings": "Cài đặt", + "apiClient": "API Client", + "httpStatusCodes": "Mã trạng thái HTTP", + "passwordManager": "Quản lý mật khẩu", + "environmentManager": "Trình quản lý môi trường", + "bookmarks": "Dấu trang", + "jsonFormatter": "Trình định dạng JSON", + "emailValidator": "Xác thực email", + "nosqlExplorer": "Trình khám phá cơ sở dữ liệu", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "Sân chơi mã hóa", + "jwtDecoder": "Trình giải mã JWT", + "certificatePemDecoder": "Bộ giải mã chứng chỉ / PEM", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "Trình định dạng GraphQL", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "Trình phân tích URL", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "Bí mật / khóa API", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "Kiểm tra độ tương phản", + "jsonSchemaGenerator": "Trình tạo lược đồ JSON", + "imageToBase64": "Hình ảnh tới Base64", + "imageCompressor": "Nén ảnh", + "cssGradientBuilder": "Trình tạo độ dốc CSS", + "gitignoreGenerator": "Trình tạo .gitignore", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "Đoạn mã", + "qrCodeGenerator": "Trình tạo mã QR", + "dnsLookup": "Tra cứu DNS", + "ipSubnetCalculator": "Máy tính IP / mạng con", + "hashGenerator": "Trình tạo hash", + "hmacGenerator": "Trình tạo HMAC", + "totpGenerator": "Mã TOTP / 2FA", + "markdownPreview": "Xem trước Markdown", + "numberBaseConverter": "Chuyển đổi cơ số", + "formatConverter": "Chuyển đổi định dạng", + "mockDataGenerator": "Trình tạo dữ liệu giả", + "svgOptimizer": "Tối ưu SVG", + "unitConverter": "Chuyển đổi đơn vị", + "dockerComposeGenerator": "Trình tạo Docker Compose", + "breakRoom": "Phòng Nghỉ", + "game2048": "2048", + "sudoku": "Sudoku", + "redisCommander": "Redis Commander", + "yamlFormatter": "Định dạng YAML", + "sshKeyGenerator": "Trình tạo khóa SSH / RSA", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Máy tính Chmod", + "codeScreenshot": "Ảnh chụp mã", + "cssGenerators": "Trình tạo CSS", + "exifViewer": "Trình xem EXIF", + "faviconGenerator": "Trình tạo Favicon", + "jsonDiff": "So sánh JSON", + "jsonpathPlayground": "Sân chơi JSONPath", + "keycodeInspector": "Trình kiểm tra mã phím", + "markdownTableGenerator": "Bảng Markdown", + "timezoneConverter": "Chuyển đổi múi giờ", + "tokenCounter": "Bộ đếm token", + "webhookTester": "Trình kiểm tra Webhook", + "websocketTester": "Trình kiểm tra WebSocket", + "whoisLookup": "Tra cứu Whois" + }, + "Help": { + "title": "Trợ giúp & tài liệu", + "subtitle": "MyDevTools hoạt động như thế nào, mỗi ứng dụng làm gì, và bảo mật/mã hóa bảo vệ dữ liệu của bạn ra sao.", + "tabs": { + "overview": "Tổng quan", + "apps": "Ứng dụng", + "security": "Bảo mật", + "tips": "Mẹo" + }, + "overview": { + "whatIs": { + "title": "MyDevTools là gì?", + "description": "Một không gian làm việc dành cho dev có đăng nhập: bảng điều khiển và bộ công cụ hằng ngày.", + "p1": "Sau khi đăng nhập, ứng dụng thiết lập phiên với MyDevTools API để các công cụ có thể tải và lưu dữ liệu của bạn. Thanh bên liệt kê mọi ứng dụng; bạn có thể ẩn các công cụ không dùng trong", + "settingsLink": "Cài đặt", + "p1After": ".", + "p2": "Một số tính năng dùng mật khẩu chính (hoặc khóa vault toàn cục) để các bí mật như mật khẩu và chuỗi kết nối được mã hóa trên thiết bị của bạn trước khi đến máy chủ." + }, + "quickLinks": { + "title": "Liên kết nhanh", + "dashboard": "Bảng điều khiển", + "settings": "Cài đặt" + } + }, + "apps": { + "intro": "Mở bất kỳ công cụ nào từ thanh bên hoặc bảng điều khiển. Dưới đây là hướng dẫn ngắn cho từng ứng dụng.", + "open": "Mở", + "howItWorks": "Cách hoạt động", + "generic": "Hãy dùng công cụ này từ thanh bên theo workflow riêng của nó." + }, + "security": { + "session": { + "title": "Đăng nhập và phiên API", + "description": "Danh tính và cách trình duyệt giao tiếp với backend.", + "items": { + "authentication": "Xác thực dùng Firebase để đăng nhập. Sau đó ứng dụng đổi Firebase ID token sang cookie API.", + "accessTokens": "Access token là JWT được ký trên máy chủ (HS256) và xác định người dùng gọi API.", + "refreshTokens": "Refresh token được lưu trong HttpOnly cookie; máy chủ lưu hash SHA-256 của refresh token chứ không lưu token thô." + } + }, + "encryption": { + "title": "Mã hóa cần biết", + "description": "Crypto phía client cho vault và dữ liệu kết nối nhạy cảm.", + "p1": "Đối với trình quản lý mật khẩu, trình quản lý môi trường và kết nối đã lưu trong NoSQL Explorer, ứng dụng web dùng Web Crypto API của trình duyệt:", + "items": { + "kdf": "PBKDF2 với SHA-256, 100.000 vòng lặp và salt ngẫu nhiên để suy ra khóa 256-bit từ mật khẩu chính.", + "aes": "AES-256-GCM mã hóa dữ liệu; mỗi lần mã hóa dùng IV 12 byte ngẫu nhiên.", + "scope": "Khóa suy ra ở lại trong trình duyệt (không trích xuất được). Máy chủ chỉ nhận bản mã và IV, không nhận mật khẩu chính hay bí mật dạng rõ." + }, + "warning": "Nếu bạn quên mật khẩu chính, dữ liệu đã mã hóa sẽ không thể khôi phục. Hãy lưu giữ cẩn thận." + }, + "logout": { + "title": "Đăng xuất và dữ liệu cục bộ", + "body": "Đăng xuất sẽ xóa bí mật trong bộ nhớ, trạng thái vault, và (khi áp dụng) vật liệu khóa chính khỏi IndexedDB, đồng thời kết thúc phiên API. Dữ liệu chỉ nằm trong local storage có thể vẫn còn cho đến khi bạn tự xóa trong trình duyệt." + } + }, + "tips": { + "title": "Dùng MyDevTools an toàn", + "items": { + "masterPassword": "Dùng mật khẩu chính mạnh và duy nhất cho các tính năng vault.", + "apiClient": "Hãy coi API Client như mọi HTTP client: tránh dán bí mật production vào môi trường dùng chung nếu bạn không biết ai có thể xem.", + "nosql": "Trình khám phá cơ sở dữ liệu kết nối tới DB do bạn cấu hình; hãy dùng tài khoản tối thiểu quyền và quy tắc mạng khi có thể.", + "json": "Trình định dạng JSON chạy cục bộ trong trình duyệt—dù vậy vẫn nên tránh dán dữ liệu cực kỳ nhạy cảm trên máy dùng chung.", + "settings": "Tùy chỉnh công cụ hiển thị trong Cài đặt để thanh bên gọn và tập trung hơn." + } + } + }, + "Settings": { + "title": "Cài đặt", + "language": "Ngôn ngữ", + "spanish": "Tiếng Tây Ban Nha", + "english": "Tiếng Anh", + "french": "Français", + "arabic": "عربى", + "catalan": "Tiếng Catalan", + "chineseSimplified": "Tiếng Trung (Giản thể)", + "czech": "Čeština", + "greek": "Ελληνικά", + "german": "Tiếng Đức", + "danish": "Tiếng Đan Mạch", + "afrikaans": "Tiếng Afrikaans", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Tiếng Ba Lan", + "vietnamese": "Tiếng Việt", + "norwegian": "Tiếng Na Uy", + "ukrainian": "Tiếng Ukraina" + }, + "SettingsPage": { + "title": "Cài đặt", + "subtitle": "Quản lý cài đặt tài khoản và tùy chọn của bạn.", + "userProfile": { + "title": "Hồ sơ người dùng", + "description": "Thông tin cá nhân và chi tiết tài khoản của bạn.", + "avatarAlt": "Người dùng", + "anonymousUser": "Người dùng ẩn danh", + "noEmailProvided": "Chưa cung cấp email", + "notLoggedIn": "Chưa đăng nhập." + }, + "tools": { + "title": "Công cụ & Tính năng", + "description": "Chọn công cụ hiển thị trên thanh điều hướng." + }, + "appearance": { + "title": "Giao diện", + "description": "Tùy chỉnh giao diện ứng dụng trên thiết bị của bạn.", + "themePreference": "Tùy chọn giao diện", + "themes": { + "light": "Sáng", + "dark": "Tối", + "system": "Hệ thống" + } + }, + "language": { + "title": "Ngôn ngữ", + "description": "Đặt ngôn ngữ sử dụng trên toàn nền tảng.", + "selectLanguage": "Chọn ngôn ngữ", + "selectPlaceholder": "Chọn một ngôn ngữ", + "helpText": "Thay đổi ngôn ngữ sẽ cập nhật menu và nhãn trên toàn nền tảng.", + "languages": { + "en": "English", + "fr": "Français", + "es": "Español (Tiếng Tây Ban Nha)", + "ar": "عربى (Tiếng Ả Rập)", + "ca": "Català", + "zh": "简体中文", + "cs": "Čeština", + "el": "Ελληνικά (Tiếng Hy Lạp)", + "de": "Deutsch (Tiếng Đức)", + "da": "Dansk (Tiếng Đan Mạch)", + "af": "Afrikaans", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "Tiếng Ý (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski (Tiếng Ba Lan)", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "sv": "Svenska", + "tr": "Türkçe", + "uk": "Українська" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "Chào buổi sáng", + "afternoon": "Chào buổi chiều", + "evening": "Chào buổi tối" + }, + "commaName": ", {name}", + "welcomeBack": "Chào mừng trở lại", + "welcomeBackNamed": "Chào mừng trở lại, {name}", + "tagline": "Hôm nay bạn muốn xây dựng gì?", + "brandName": "MyDevTools", + "stats": { + "tools": "Công cụ", + "favorites": "Yêu thích", + "pinned": "Đã ghim", + "recent": "Dùng gần đây" + }, + "sections": { + "recentlyUsed": "Dùng gần đây", + "favorites": "Yêu thích", + "whatsNew": "Có gì mới", + "pinned": "Đã ghim" + }, + "groups": { + "apps": "Ứng dụng" + }, + "toolCard": { + "defaultDescription": "Khám phá công cụ này để có chức năng tốt hơn.", + "launchTool": "Mở công cụ" + }, + "tools": { + "toDo": { + "title": "Công việc", + "description": "Quản lý danh sách công việc hiệu quả." + }, + "notes": { + "title": "Ghi chú", + "description": "Ghi lại ý tưởng và sắp xếp suy nghĩ." + }, + "passwordManager": { + "title": "Quản lý mật khẩu", + "description": "Lưu trữ và quản lý mật khẩu an toàn." + }, + "environmentManager": { + "title": "Trình quản lý môi trường", + "description": "Lưu biến kiểu .env theo dự án và môi trường kèm thẻ." + }, + "bookmarks": { + "title": "Dấu trang", + "description": "Quản lý và sắp xếp dấu trang." + }, + "apiClient": { + "title": "API Client", + "description": "Kiểm tra và gỡ lỗi các yêu cầu HTTP." + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "Khám phá cơ sở dữ liệu MongoDB." + }, + "emailValidator": { + "title": "Xác thực Email", + "description": "Xác minh và kiểm tra địa chỉ email." + }, + "encryptionPlayground": { + "title": "Sân chơi mã hóa", + "description": "AES-GCM với khóa thô hoặc cụm mật khẩu; gói JSON, Web Crypto chỉ trong trình duyệt." + }, + "jwtDecoder": { + "title": "Trình giải mã JWT", + "description": "Giải mã header, payload và thời gian hết hạn của JWT." + }, + "certificatePemDecoder": { + "title": "Bộ giải mã chứng chỉ / PEM", + "description": "Giải mã chứng chỉ X.509 và CSR: chủ thể, nhà phát hành, hiệu lực và SAN." + }, + "sshKeyGenerator": { + "title": "Trình tạo khóa SSH / RSA", + "description": "Tạo cặp khóa Ed25519 hoặc RSA trong trình duyệt. Tải xuống ở định dạng OpenSSH và PEM." + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "Trình định dạng GraphQL", + "description": "Định dạng GraphQL với tô sáng cú pháp và trình xây dựng truy vấn đơn giản." + }, + "yamlFormatter": { + "title": "Trình định dạng YAML", + "description": "Định dạng, xác thực và chuyển đổi YAML sang JSON trong trình duyệt." + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "Trình phân tích URL", + "description": "Tách URL thành giao thức, host, đường dẫn, tham số truy vấn và hash." + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "Bí mật / khóa API", + "description": "Chuỗi ngẫu nhiên mật mã với bảng chữ cái, độ dài và xuất hàng loạt có thể cấu hình." + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "Kiểm tra độ tương phản", + "description": "Tỷ lệ tương phản WCAG 2.1 giữa chữ và nền (AA / AAA)." + }, + "jsonSchemaGenerator": { + "title": "Trình tạo lược đồ JSON", + "description": "Suy ra lược đồ JSON và mô hình có kiểu từ mẫu JSON." + }, + "imageToBase64": { + "title": "Hình ảnh tới Base64", + "description": "Chuyển đổi hình ảnh thành URI dữ liệu hoặc chuỗi Base64 thô." + }, + "imageCompressor": { + "title": "Nén ảnh", + "description": "Nén JPEG, PNG và WebP cục bộ với thanh trượt chất lượng." + }, + "cssGradientBuilder": { + "title": "Trình tạo độ dốc CSS", + "description": "Trình tạo gradient CSS trực quan với khả năng kiểm soát góc và điểm dừng màu." + }, + "gitignoreGenerator": { + "title": "Trình tạo .gitignore", + "description": "Tạo các tệp .gitignore đã hợp nhất ngay lập tức từ hơn 500 nhóm công nghệ." + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "Chuyển file CSV hoặc Excel sang JSON và xuất lại CSV hoặc XLSX." + }, + "snippetManager": { + "title": "Đoạn mã", + "description": "Lưu đoạn mã với tô sáng cú pháp, tự động nhận diện ngôn ngữ và định dạng." + }, + "qrCodeGenerator": { + "title": "Trình tạo mã QR", + "description": "Tạo mã QR PNG từ văn bản hoặc URL ngay trên trình duyệt." + }, + "ipSubnetCalculator": { + "title": "Máy tính IP / mạng con", + "description": "IPv4 và IPv6 CIDR: mặt nạ, dải và số địa chỉ." + }, + "dnsLookup": { + "title": "Tra cứu DNS", + "description": "Truy vấn bản ghi DNS cho bất kỳ tên miền nào — A, AAAA, MX, TXT, NS, CNAME." + }, + "hashGenerator": { + "title": "Trình tạo hash", + "description": "MD5, SHA-1 và SHA-2 cho văn bản hoặc tệp." + }, + "hmacGenerator": { + "title": "Trình tạo HMAC", + "description": "Ký webhook và kiểm thử tích hợp API với HMAC-SHA1/256/384/512." + }, + "totpGenerator": { + "title": "Trình tạo mã TOTP / 2FA", + "description": "Dán bí mật Base32 từ ứng dụng xác thực; mã sáu chữ số làm mới mỗi 30 giây—tiện để thử đăng nhập và MFA." + }, + "markdownPreview": { + "title": "Xem trước Markdown", + "description": "Trình kết xuất Markdown trực tiếp với xuất HTML và chuyển đổi HTML → Markdown." + }, + "numberBaseConverter": { + "title": "Chuyển đổi cơ số", + "description": "Chuyển đổi số nguyên giữa các cơ số 2–36." + }, + "formatConverter": { + "title": "Chuyển đổi định dạng", + "description": "Chuyển đổi giữa JSON, YAML, TOML và XML theo mọi hướng. Hỗ trợ đủ 12 tổ hợp." + }, + "mockDataGenerator": { + "title": "Trình tạo dữ liệu giả", + "description": "Định nghĩa cột, chọn kiểu dữ liệu và xuất bộ dữ liệu cho seed, Postman hoặc kiểm thử. Tối đa 5000 dòng, tất cả trong trình duyệt — không tải lên." + }, + "httpStatusCodes": { + "title": "Mã trạng thái HTTP", + "description": "Tìm kiếm mã trạng thái HTTP với mô tả (1xx–5xx)." + }, + "svgOptimizer": { + "title": "Tối ưu SVG", + "description": "Dán SVG, loại bỏ thừa bằng SVGO và xem tiết kiệm kích thước UTF-8." + }, + "unitConverter": { + "title": "Chuyển đổi đơn vị", + "description": "Chuyển đổi giữa 323 đơn vị trong 43 danh mục khoa học và kỹ thuật." + }, + "dockerComposeGenerator": { + "title": "Trình tạo Docker Compose", + "description": "Chọn cơ sở dữ liệu, bộ nhớ đệm, hàng đợi, proxy và image quan sát; xuất docker-compose.yml cho stack cục bộ." + }, + "game2048": { + "title": "2048", + "description": "Trượt và hợp nhất các ô để đạt đến năm 2048. Trò chơi giải đố số cổ điển." + }, + "sudoku": { + "title": "Sudoku", + "description": "Câu đố số cổ điển. 3 cấp độ khó, mỗi cấp độ 150 cấp độ." + }, + "redisCommander": { + "title": "Redis Commander", + "description": "Duyệt khóa, kiểm tra giá trị và chạy lệnh trên bất kỳ phiên bản Redis nào." + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Trình tạo & kiểm tra Bcrypt", + "description": "Băm mật khẩu bằng bcrypt và xác minh các băm hiện có." + }, + "chmodCalculator": { + "title": "Máy tính Chmod", + "description": "Chuyển đổi quyền Unix giữa bát phân, ký hiệu và hộp kiểm." + }, + "codeScreenshot": { + "title": "Ảnh chụp mã", + "description": "Biến mã nguồn thành hình ảnh đẹp, dễ chia sẻ." + }, + "cssGenerators": { + "title": "Trình tạo CSS", + "description": "Trình tạo box-shadow, border-radius và clamp() linh hoạt." + }, + "exifViewer": { + "title": "Trình xem & xóa EXIF", + "description": "Kiểm tra và xóa metadata EXIF của ảnh ngay trên máy." + }, + "faviconGenerator": { + "title": "Trình tạo Favicon", + "description": "Tạo favicon, biểu tượng ứng dụng và đoạn mã manifest." + }, + "jsonDiff": { + "title": "So sánh JSON", + "description": "So sánh hai tài liệu JSON theo ngữ nghĩa và cấu trúc." + }, + "jsonpathPlayground": { + "title": "Sân chơi JSONPath", + "description": "Truy vấn JSON bằng JSONPath hoặc JMESPath và xem kết quả trực tiếp." + }, + "keycodeInspector": { + "title": "Trình kiểm tra mã phím", + "description": "Nhấn một phím để xem mã sự kiện JavaScript và các phím bổ trợ." + }, + "markdownTableGenerator": { + "title": "Trình tạo bảng Markdown", + "description": "Tạo bảng Markdown và HTML trong trình chỉnh sửa dạng lưới như bảng tính." + }, + "timezoneConverter": { + "title": "Chuyển đổi múi giờ", + "description": "Chuyển đổi một thời điểm qua nhiều múi giờ kèm gợi ý chênh lệch ngày và giờ làm việc." + }, + "tokenCounter": { + "title": "Bộ đếm token LLM", + "description": "Đếm token LLM, trực quan hóa các khối và ước tính chi phí API." + }, + "webhookTester": { + "title": "Trình kiểm tra Webhook", + "description": "Ghi lại và kiểm tra các yêu cầu webhook đến theo thời gian thực." + }, + "websocketTester": { + "title": "Trình kiểm tra WebSocket", + "description": "Kết nối tới các điểm cuối WebSocket và trao đổi tin nhắn theo thời gian thực." + }, + "whoisLookup": { + "title": "Tra cứu Whois", + "description": "Tra cứu thông tin đăng ký tên miền và IP qua RDAP." + } + }, + "tabs": { + "apps": "Ứng dụng", + "analytics": "Phân tích", + "ui": "Giao diện" + }, + "analytics": { + "title": "Dữ liệu của bạn trong nháy mắt", + "subtitle": "Số liệu đồng bộ với tài khoản trên máy chủ.", + "loadError": "Không tải được phân tích.", + "retry": "Thử lại", + "loginHint": "Đăng nhập để xem phân tích sử dụng và tùy chọn giao diện.", + "passwordEntries": "Mục mật khẩu", + "bookmarks": "Dấu trang", + "bookmarkFolders": "Thư mục dấu trang", + "tasksTotal": "Nhiệm vụ", + "tasksCompleted": "Hoàn thành", + "tasksOngoing": "Đang làm", + "tasksNotStarted": "Chưa bắt đầu", + "projects": "Dự án", + "nosqlConnections": "Kết nối CSDL", + "notes": "Ghi chú", + "apiClientCollections": "Bộ sưu tập API", + "apiClientEnvironments": "Môi trường API", + "apiClientHistory": "Mục lịch sử API", + "jsonFormatterDocuments": "Tài liệu định dạng JSON", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "Đoạn mã", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "Tùy chọn giao diện", + "subtitle": "Chủ đề, ngôn ngữ và công cụ hiển thị trên thanh bên — từ cài đặt tài khoản.", + "loadError": "Không tải được tùy chọn.", + "loginHint": "Đăng nhập để xem tùy chọn giao diện đã lưu.", + "theme": "Chủ đề", + "accent": "Màu nhấn", + "locale": "Ngôn ngữ", + "enabledTools": "Công cụ hiển thị", + "favoriteTools": "Công cụ yêu thích", + "toolsWithUsage": "Công cụ có theo dõi sử dụng" + }, + "signIn": "Đăng nhập", + "noResults": "Không có công cụ nào phù hợp với tìm kiếm của bạn", + "noResultsHint": "Thử từ khóa khác hoặc xóa bộ lọc.", + "filterAll": "Tất cả", + "loginCta": { + "description": "Đăng nhập để theo dõi các công cụ bạn sử dụng gần đây." + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "Đang tải...", + "myTasksTitle": "Công việc của tôi", + "statsLine": "{total} công việc • Hoàn thành {percent}%", + "statsLineDesktop": "{total} tổng cộng • Hoàn thành {percent}%" + }, + "filters": { + "all": "Tất cả", + "total": "Tổng", + "allProjects": "Tất cả dự án", + "export": "Xuất", + "kanban": "Kanban", + "list": "Danh sách", + "searchPlaceholder": "Tìm kiếm công việc...", + "searchAria": "Tìm kiếm công việc", + "clearSearchAria": "Xóa tìm kiếm", + "foundTasks": "Tìm thấy {count} {count, plural, one {công việc} other {công việc}}", + "filterByAllAria": "Lọc theo Tất cả", + "filterByStatusAria": "Lọc theo {status}" + }, + "status": { + "not-started": { + "label": "Chưa bắt đầu", + "description": "Công việc chưa được bắt đầu" + }, + "ongoing": { + "label": "Đang thực hiện", + "description": "Công việc đang được thực hiện" + }, + "completed": { + "label": "Hoàn thành", + "description": "Công việc đã hoàn thành" + } + }, + "priorities": { + "high": "Cao", + "medium": "Trung bình", + "low": "Thấp" + }, + "form": { + "addNewTaskLabel": "Thêm công việc mới", + "multilinePlaceholder": "Thêm chi tiết công việc... (Shift+Enter để xuống dòng)", + "singlelinePlaceholder": "Cần làm gì? Nhấn Enter để thêm", + "taskInputAria": "Nhập công việc", + "projectPlaceholder": "Dự án", + "selectProject": "Chọn dự án", + "errorSelectProject": "Vui lòng chọn dự án hoặc tạo dự án mới để thêm công việc.", + "hintMultiline": "Shift+Enter để xuống dòng, Enter để thêm", + "hintSingleline": "Enter để thêm", + "shortcutsAria": "Phím tắt", + "shortcutsLabel": "Phím tắt", + "shortcutAddTask": "Thêm công việc", + "shortcutNewLine": "Dòng mới", + "shortcutClear": "Xóa", + "addButton": "Thêm" + }, + "list": { + "ariaLabel": "Danh sách công việc", + "loading": "Đang tải công việc...", + "emptyTitle": "Không tìm thấy công việc", + "emptyPrimary": "Bắt đầu bằng cách thêm công việc đầu tiên ở trên! Nhấn Enter để thêm nhanh.", + "emptySecondary": "Thử điều chỉnh bộ lọc hoặc từ khóa tìm kiếm." + }, + "mobileDrawer": { + "addTaskSrOnly": "Thêm công việc", + "addNewTaskTitle": "Thêm công việc mới", + "addNewTaskDescription": "Tạo công việc mới để theo dõi tiến độ.", + "cancel": "Hủy" + }, + "kanban": { + "ariaLabel": "Bảng Kanban", + "loading": "Đang tải công việc...", + "dropHere": "Thả công việc vào đây", + "noTasksYet": "Chưa có công việc", + "emptyColumnAria": "Cột trống", + "columnAria": "Cột {title} với {count} công việc", + "countInColumnAria": "{count} công việc trong {title}" + }, + "taskItem": { + "confirmDelete": "Bạn có chắc muốn xóa công việc này không?", + "statusPlaceholder": "Trạng thái", + "changeStatusAria": "Thay đổi trạng thái công việc", + "taskOptionsAria": "Tùy chọn công việc", + "editTask": "Sửa công việc", + "copyTask": "Sao chép công việc", + "copied": "Đã sao chép!", + "deleteTask": "Xóa công việc", + "statusHeading": "Trạng thái", + "editTaskAria": "Sửa công việc" + }, + "editDialog": { + "title": "Sửa công việc", + "description": "Cập nhật chi tiết công việc, thêm công việc con, đặt mức ưu tiên và nhiều hơn nữa.", + "taskTitleLabel": "Tiêu đề công việc *", + "taskTitlePlaceholder": "Cần làm gì?", + "descriptionLabel": "Mô tả", + "descriptionPlaceholder": "Thêm chi tiết về công việc này...", + "statusLabel": "Trạng thái", + "priorityLabel": "Mức ưu tiên", + "dueDateLabel": "Hạn chót", + "pickDate": "Chọn ngày", + "clearDate": "Xóa ngày", + "projectLabel": "Dự án", + "selectProjectPlaceholder": "Chọn dự án", + "noProject": "Không có dự án", + "timeEstimateLabel": "Ước tính thời gian (phút)", + "timeEstimatePlaceholder": "Mất bao lâu?", + "tagsLabel": "Nhãn", + "customTagNamePlaceholder": "Tên nhãn tùy chỉnh", + "subtasksLabel": "Công việc con", + "addSubtaskPlaceholder": "Thêm công việc con...", + "cancel": "Hủy", + "saving": "Đang lưu...", + "saveChanges": "Lưu thay đổi" + }, + "export": { + "title": "Xuất công việc", + "description": "Xuất công việc sang định dạng JSON, CSV hoặc Excel.", + "projectLabel": "Dự án", + "statusLabel": "Trạng thái", + "tagLabel": "Nhãn", + "allProjects": "Tất cả dự án", + "allStatuses": "Tất cả trạng thái", + "allTags": "Tất cả nhãn", + "exportAsJsonTitle": "Xuất dạng JSON", + "exportAsJsonDescription": "Xuất dữ liệu công việc bao gồm công việc con, nhãn và siêu dữ liệu.", + "exportJson": "Xuất JSON", + "exportAsCsvExcelTitle": "Xuất dạng CSV / Excel", + "exportAsCsvExcelDescription": "Xuất công việc cho ứng dụng bảng tính.", + "exportCsv": "Xuất CSV", + "exportExcel": "Xuất Excel", + "close": "Đóng", + "toastNoTasks": "Không có công việc để xuất với bộ lọc hiện tại", + "toastExportedJson": "Đã xuất {count} công việc sang JSON!", + "toastExportedCsv": "Đã xuất {count} công việc sang CSV!", + "toastExportedExcel": "Đã xuất {count} công việc sang Excel!", + "toastFailedJson": "Không thể xuất sang JSON", + "toastFailedCsv": "Không thể xuất sang CSV", + "toastFailedExcel": "Không thể xuất sang Excel", + "unknownProject": "Dự án không xác định" + }, + "ack": { + "taskAddedTitle": "Đã thêm công việc thành công", + "taskAddedFailedTitle": "Không thể thêm công việc", + "taskUpdatedTitle": "Đã cập nhật công việc thành công", + "taskUpdatedFailedTitle": "Không thể cập nhật công việc", + "taskStatusUpdateFailedTitle": "Không thể cập nhật trạng thái công việc", + "taskMovedTitle": "Đã chuyển công việc sang {status}", + "tryAgain": "Vui lòng thử lại.", + "taskDeletedTitle": "Đã xóa công việc \"{text}\"", + "undo": "Hoàn tác", + "taskRestoredTitle": "Đã khôi phục công việc", + "taskRestoredSuccessTitle": "Đã khôi phục công việc thành công", + "taskRestoreFailedTitle": "Không thể khôi phục công việc. Vui lòng thử lại.", + "taskDeleteFailedTitle": "Không thể xóa công việc. Vui lòng thử lại.", + "tasksImportedTitle": "Đã nhập thành công {count} {count, plural, one {công việc} other {công việc}}!", + "tasksImportFailedTitle": "Không thể nhập công việc. Vui lòng thử lại.", + "unknownProject": "Dự án không xác định", + "projectCreatedTitle": "Đã tạo dự án thành công", + "projectCreateFailedTitle": "Không thể tạo dự án", + "projectUpdatedTitle": "Đã cập nhật dự án thành công", + "projectUpdateFailedTitle": "Không thể cập nhật dự án", + "projectDeletedTitle": "Đã xóa dự án thành công", + "projectDeleteFailedTitle": "Không thể xóa dự án" + } + }, + "Notes": { + "page": { + "loading": "Đang tải..." + }, + "layout": { + "mobileNavTitle": "Điều hướng ghi chú", + "openMenuAria": "Mở menu ghi chú" + }, + "sidebar": { + "title": "Ghi chú", + "createNoteAria": "Tạo ghi chú", + "searchPlaceholder": "Tìm kiếm ghi chú...", + "loading": "Đang tải...", + "noNotesFound": "Không tìm thấy ghi chú.", + "noNotesYet": "Chưa có ghi chú. Nhấn + để tạo mới.", + "inParent": "trong {parentTitle}", + "untitled": "Không tiêu đề", + "addSubPage": "Thêm trang con", + "delete": "Xóa", + "deleteConfirmTitle": "Xóa ghi chú?", + "deleteConfirmDescription": "Bạn có chắc muốn xóa \"{title}\"? Hành động này không thể hoàn tác.", + "cancel": "Hủy", + "confirmDelete": "Xóa", + "noteOptionsAria": "Tùy chọn ghi chú" + }, + "editor": { + "emptyState": "Chọn ghi chú hoặc tạo mới để bắt đầu.", + "titlePlaceholder": "Không tiêu đề", + "saving": "Đang lưu…", + "ready": "Sẵn sàng", + "saved": "Đã lưu {time}", + "stats": "{words} từ · {read} phút đọc", + "template": "Mẫu", + "applyTemplate": "Áp dụng mẫu", + "exportMdLabel": ".md", + "exportMd": "Xuất dưới dạng Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "Xuất dưới dạng HTML", + "focusMode": "Chế độ tập trung", + "exitFocusMode": "Thoát chế độ tập trung", + "save": "Lưu", + "addTag": "Thêm thẻ…", + "removeTag": "Xóa thẻ {tag}", + "loading": "Đang tải…", + "searchHint": "để tìm kiếm", + "focusHint": "chế độ tập trung", + "notePath": "Đường dẫn ghi chú" + }, + "context": { + "authRequiredError": "Người dùng chưa xác thực", + "defaultTitle": "Không tiêu đề", + "cloudSyncImageError": "Bật Đồng bộ Đám mây để lưu trữ hình ảnh ghi chú. Ghi chú văn bản vẫn ở cục bộ." + } + }, + "PasswordManager": { + "page": { + "loading": "Đang tải...", + "title": "Quản lý mật khẩu", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "Không thể giải mã mật khẩu", + "toastLoadFailed": "Không thể tải mật khẩu", + "toastUnlockedAuto": "Kho đã được mở khóa tự động", + "toastAutoUnlockFailed": "Mở khóa tự động thất bại. Vui lòng mở khóa thủ công.", + "toastConnectFailed": "Không thể kết nối đến máy chủ", + "toastAutoUnlockWarning": "Kho đã mở khóa, nhưng mở khóa tự động có thể không hoạt động khi tải lại", + "errorIncorrectPassword": "Mật khẩu chính không đúng", + "errorUnlockFailed": "Không thể mở khóa kho", + "errorPasswordsMismatch": "Mật khẩu không khớp", + "errorPasswordTooShort": "Mật khẩu phải có ít nhất 8 ký tự", + "errorSetupFailed": "Không thể thiết lập kho", + "errorConnectFailed": "Không thể kết nối đến máy chủ.", + "mobileTitleCreate": "Tạo kho của bạn", + "mobileTitleWelcome": "Chào mừng trở lại", + "mobileSubtitleCreate": "Đặt mật khẩu chính để bảo vệ thông tin đăng nhập", + "mobileSubtitleUnlock": "Nhập mật khẩu chính để mở khóa", + "placeholderMasterPassword": "Mật khẩu chính", + "placeholderConfirmPassword": "Xác nhận mật khẩu", + "labelMasterPassword": "Mật khẩu chính", + "labelConfirmPassword": "Xác nhận mật khẩu", + "placeholderEnterMaster": "Nhập mật khẩu chính", + "placeholderConfirmMaster": "Xác nhận mật khẩu chính", + "buttonCreateVault": "Tạo kho", + "buttonUnlock": "Mở khóa", + "buttonUnlockVault": "Mở khóa kho", + "footerCreate": "Mật khẩu của bạn không bao giờ được lưu trữ và không thể khôi phục", + "footerUnlock": "Được bảo mật bằng mã hóa đầu cuối", + "desktopTitleSetup": "Thiết lập kho mật khẩu", + "desktopTitleUnlock": "Mở khóa kho", + "desktopDescriptionSetup": "Tạo mật khẩu chính để bảo vệ thông tin đăng nhập. Mật khẩu này không bao giờ được lưu trữ và không thể khôi phục.", + "desktopDescriptionUnlock": "Nhập mật khẩu chính để truy cập thông tin đăng nhập được bảo mật." + }, + "list": { + "loading": "Đang tải mật khẩu...", + "emptyTitle": "Kho trống", + "emptyHintMobile": "Nhấn nút + để thêm mật khẩu đầu tiên", + "emptyHintDesktop": "Bạn chưa lưu mật khẩu nào. Nhấn nút \"Thêm mật khẩu\" ở trên để bắt đầu.", + "addPassword": "Thêm mật khẩu", + "searchPlaceholderMobile": "Tìm trong kho...", + "searchPlaceholderDesktop": "Tìm trong kho...", + "importExport": "Nhập / xuất", + "lockVault": "Khóa kho", + "allPasswordsTitle": "Tất cả mật khẩu", + "passwordCount": "{count, plural, one {# mật khẩu} other {# mật khẩu}}", + "vaultHealthDrawerTitle": "Sức khỏe kho", + "noResults": "Không tìm thấy mật khẩu phù hợp \"{query}\"", + "tableService": "Dịch vụ", + "tableUsername": "Tên đăng nhập", + "tablePassword": "Mật khẩu", + "tableUrl": "URL", + "tableActions": "Hành động", + "gridViewAria": "Xem dạng lưới", + "listViewAria": "Xem dạng danh sách", + "passwordStrengthTitle": "Độ mạnh mật khẩu", + "deleteDialogTitle": "Xóa mật khẩu", + "deleteDialogDescription": "Bạn có chắc muốn xóa mật khẩu này? Hành động này không thể hoàn tác.", + "cancel": "Hủy", + "delete": "Xóa", + "edit": "Sửa" + }, + "toasts": { + "copiedPassword": "Đã sao chép mật khẩu vào bộ nhớ tạm", + "copiedUsername": "Đã sao chép tên đăng nhập vào bộ nhớ tạm", + "passwordDeleted": "Đã xóa mật khẩu", + "deleteFailed": "Không thể xóa mật khẩu", + "vaultLocked": "Kho đã khóa", + "importedCount": "Đã nhập thành công {count, plural, one {# mật khẩu} other {# mật khẩu}}" + }, + "form": { + "addTitle": "Thêm mật khẩu mới", + "editTitle": "Sửa mật khẩu", + "serviceName": "Tên dịch vụ", + "usernameEmail": "Tên đăng nhập / email", + "password": "Mật khẩu", + "urlOptional": "URL trang web (tùy chọn)", + "notesOptional": "Ghi chú (tùy chọn)", + "tags": "Nhãn", + "placeholderService": "vd: Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "Nhập hoặc tạo mật khẩu", + "placeholderUrl": "https://...", + "placeholderNotes": "Chi tiết bổ sung...", + "placeholderTags": "Thêm nhãn (nhấn Enter hoặc dấu phẩy)", + "strength": "Độ mạnh", + "strengthWeak": "Yếu", + "strengthMedium": "Trung bình", + "strengthStrong": "Mạnh", + "advancedGenerator": "Trình tạo nâng cao", + "cancel": "Hủy", + "saving": "Đang lưu...", + "savePassword": "Lưu mật khẩu", + "updating": "Đang cập nhật...", + "updatePassword": "Cập nhật mật khẩu", + "addPasswordSrOnly": "Thêm mật khẩu", + "triggerAdd": "Thêm mật khẩu", + "toastSaved": "Đã lưu mật khẩu thành công", + "toastSaveFailed": "Không thể lưu mật khẩu", + "toastUpdated": "Đã cập nhật mật khẩu thành công", + "toastUpdateFailed": "Không thể cập nhật mật khẩu" + }, + "security": { + "vaultHealth": "Sức khỏe kho", + "statusLabel": "Trạng thái", + "totalPasswords": "Tổng mật khẩu", + "activeEntries": "Mục đang hoạt động trong kho", + "protected": "Được bảo vệ", + "weakPasswords": "Mật khẩu yếu", + "needingImprovement": "Mật khẩu cần cải thiện", + "allGood": "Tốt", + "reusedPasswords": "Mật khẩu trùng lặp", + "usedMultipleTimes": "Mật khẩu dùng nhiều lần", + "unique": "Duy nhất", + "total": "Tổng", + "weak": "Yếu", + "reused": "Trùng lặp", + "totalCountLabel": "{count, plural, one {# mật khẩu} other {# mật khẩu}}", + "weakCountLabel": "{count} yếu", + "allSecure": "Tất cả an toàn", + "scoreExcellent": "Xuất sắc", + "scoreGood": "Tốt", + "scoreFair": "Khá", + "scoreCritical": "Nguy hiểm" + }, + "importExport": { + "title": "Nhập / xuất kho", + "description": "Quản lý dữ liệu mật khẩu. Bạn có thể xuất kho sang JSON hoặc nhập từ tệp JSON.", + "tabExport": "Xuất", + "tabImport": "Nhập", + "warningTitle": "Cảnh báo", + "warningBody": "Tệp xuất chứa mật khẩu ở dạng văn bản thuần. Hãy giữ tệp an toàn và xóa sau khi sử dụng.", + "exportBlurb": "Tải xuống toàn bộ kho dưới dạng tệp JSON. Bao gồm tất cả dịch vụ, tên đăng nhập, mật khẩu và ghi chú.", + "downloadJson": "Tải xuống JSON", + "pasteLabel": "Dán dữ liệu JSON", + "pastePlaceholder": "[{\"service\": \"Ví dụ\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "Dán nội dung tệp JSON đã xuất trước đó vào đây.", + "importing": "Đang nhập...", + "importPasswords": "Nhập mật khẩu", + "trigger": "Nhập / xuất", + "toastExported": "Đã xuất kho thành công", + "toastExportFailed": "Không thể xuất kho", + "toastImportFailed": "Không thể nhập: định dạng JSON không hợp lệ" + }, + "card": { + "copyUsername": "Sao chép tên đăng nhập", + "openUrl": "Mở URL", + "strength": "Độ mạnh" + }, + "swipeable": { + "copy": "Sao chép", + "delete": "Xóa", + "copyPassword": "Sao chép mật khẩu", + "copyUsername": "Sao chép tên đăng nhập", + "edit": "Sửa", + "deleteAction": "Xóa" + }, + "floatingMenu": { + "lockVault": "Khóa kho", + "importExport": "Nhập / xuất", + "addPassword": "Thêm mật khẩu" + }, + "generator": { + "tabPassword": "Mật khẩu", + "tabPassphrase": "Cụm mật khẩu", + "strength": "Độ mạnh", + "lengthLabel": "Độ dài: {length} ký tự", + "lengthBadge": "{length} ký tự", + "characterSets": "Bộ ký tự", + "uppercase": "Chữ hoa", + "lowercase": "Chữ thường", + "numbers": "Số", + "symbols": "Ký hiệu", + "wordCountLabel": "Số từ: {count}", + "wordCountBadge": "{count, plural, one {# từ} other {# từ}}", + "separator": "Dấu phân cách", + "capitalization": "Viết hoa", + "includeNumber": "Bao gồm số" + } + }, + "EnvironmentManager": { + "subtitle": "Biến môi trường được mã hóa theo dự án và môi trường.", + "page": { + "loading": "Đang tải…", + "title": "Trình quản lý môi trường", + "loadFailed": "Không tải được bộ môi trường" + }, + "list": { + "title": "Môi trường", + "loading": "Đang tải bộ của bạn…", + "emptyTitle": "Chưa có bộ môi trường", + "emptyHint": "Tạo một bộ cho mỗi dự án và môi trường (ví dụ khóa API production). Mọi thứ được mã hóa trong trình duyệt trước khi tải lên.", + "searchPlaceholder": "Tìm dự án, môi trường, thẻ, ghi chú…", + "tagFilterPlaceholder": "Lọc theo thẻ", + "variableCount": "{count, plural, other {# biến}}", + "updatedAgo": "Cập nhật {time}", + "copyDotEnv": "Sao chép .env", + "edit": "Sửa", + "delete": "Xóa", + "cancel": "Hủy", + "confirmDelete": "Xóa", + "deleteDialogTitle": "Xóa bộ môi trường?", + "deleteDialogDescription": "Dữ liệu mã hóa sẽ bị xóa khỏi tài khoản. Không hoàn tác được.", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": "Đã sao chép .env vào clipboard", + "deleted": "Đã xóa bộ môi trường", + "deleteFailed": "Xóa thất bại" + }, + "form": { + "addTitle": "Bộ môi trường mới", + "editTitle": "Sửa bộ môi trường", + "triggerAdd": "Thêm bộ môi trường", + "addSrOnly": "Thêm bộ môi trường", + "project": "Dự án", + "environment": "Môi trường", + "placeholderProject": "vd. billing-api", + "placeholderEnvironment": "vd. production", + "pasteEnvLabel": "Dán .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "Gộp các dòng đã dán", + "variables": "Biến", + "addRow": "Thêm dòng", + "keyPlaceholder": "KHÓA", + "valuePlaceholder": "giá trị", + "notesOptional": "Ghi chú (tuỳ chọn)", + "placeholderNotes": "Chính sách luân chuyển, chủ sở hữu, liên kết…", + "tags": "Thẻ", + "placeholderTags": "Thêm thẻ (Enter hoặc dấu phẩy)", + "cancel": "Hủy", + "save": "Lưu", + "saving": "Đang lưu…", + "update": "Cập nhật", + "updating": "Đang cập nhật…", + "toastSaved": "Đã lưu bộ môi trường", + "toastSaveFailed": "Lưu thất bại", + "toastUpdated": "Đã cập nhật bộ môi trường", + "toastUpdateFailed": "Cập nhật thất bại", + "toastPasteEmpty": "Không tìm thấy dòng KHÓA=giá trị", + "toastPasteApplied": "Đã gộp {count} biến từ nội dung dán", + "toastProjectEnvRequired": "Dự án và môi trường là bắt buộc", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "Đang tải..." + }, + "manager": { + "foldersHeading": "Thư mục", + "tagsHeading": "Nhãn", + "tagsMore": "+{count} thêm", + "tagsShowLess": "Show less", + "searchPlaceholder": "Tìm kiếm dấu trang...", + "import": "Nhập", + "export": "Xuất", + "exportAsHtml": "Xuất dạng HTML", + "exportAsJson": "Xuất dạng JSON", + "addBookmark": "Thêm dấu trang", + "matchingQuery": "phù hợp \"{query}\"", + "bookmarkCount": "{count, plural, one {# dấu trang} other {# dấu trang}}", + "allBookmarks": "Tất cả dấu trang", + "uncategorized": "Chưa phân loại", + "unknownFolder": "Không xác định" + }, + "card": { + "urlCopied": "Đã sao chép URL vào bộ nhớ tạm", + "bookmarkDeleted": "Đã xóa dấu trang", + "edit": "Sửa", + "delete": "Xóa", + "copyUrl": "Sao chép URL", + "deleteConfirmTitle": "Xóa dấu trang?", + "deleteConfirmDescription": "Hành động này sẽ xóa vĩnh viễn \"{title}\".", + "cancel": "Hủy" + }, + "grid": { + "emptyTitle": "Chưa có dấu trang", + "emptyDescription": "Thêm dấu trang đầu tiên hoặc nhập từ trình duyệt để bắt đầu." + }, + "folderTree": { + "allBookmarks": "Tất cả dấu trang", + "addSubfolder": "Thêm thư mục con", + "rename": "Đổi tên", + "deleteFolderTitle": "Xóa thư mục \"{name}\"?", + "deleteFolderDescription": "Hành động này sẽ xóa thư mục và tất cả thư mục con. Dấu trang trong các thư mục này sẽ được chuyển sang Chưa phân loại." + }, + "import": { + "title": "Nhập dấu trang", + "description": "Nhập dấu trang từ tệp xuất của trình duyệt.", + "parseError": "Không thể phân tích tệp dấu trang. Vui lòng đảm bảo đó là tệp xuất dấu trang hợp lệ.", + "successToast": "Đã nhập {bookmarks} dấu trang và {folders} thư mục", + "errorToast": "Không thể nhập dấu trang", + "chromeInstruction": "Menu → Dấu trang → Trình quản lý dấu trang → ⋮ → Xuất dấu trang", + "firefoxInstruction": "Menu → Dấu trang → Quản lý dấu trang → Nhập và Sao lưu → Xuất", + "safariInstruction": "Tệp → Xuất dấu trang...", + "edgeInstruction": "Menu → Yêu thích → ⋯ → Xuất yêu thích", + "dropZoneTitle": "Thả tệp dấu trang vào đây", + "dropZoneHint": "hoặc nhấn để duyệt (HTML hoặc JSON)", + "selectedFileHint": "Nhấn để chọn tệp khác", + "errorTitle": "Lỗi", + "previewTitle": "Xem trước nhập", + "previewBookmarks": "{count, plural, one {# dấu trang} other {# dấu trang}}", + "previewFolders": "{count, plural, one {# thư mục} other {# thư mục}}", + "importModeLabel": "Chế độ nhập", + "modeMerge": "Gộp với dấu trang hiện có", + "modeReplace": "Thay thế tất cả dấu trang hiện có", + "cancel": "Hủy", + "importButton": "Nhập", + "importButtonWithCount": "Nhập {count, plural, one {# dấu trang} other {# dấu trang}}" + }, + "addBookmark": { + "titleAdd": "Thêm dấu trang", + "titleEdit": "Sửa dấu trang", + "urlLabel": "URL", + "urlPlaceholder": "https://example.com", + "urlInvalid": "Vui lòng nhập URL hợp lệ", + "titleLabel": "Tiêu đề", + "titlePlaceholder": "Tiêu đề dấu trang", + "titleRequired": "Vui lòng nhập tiêu đề", + "descriptionLabel": "Mô tả (tùy chọn)", + "descriptionPlaceholder": "Thêm ghi chú về dấu trang này", + "folderLabel": "Thư mục", + "folderPlaceholder": "Chọn thư mục", + "noFolder": "Không có thư mục", + "tagsLabel": "Nhãn", + "tagsPlaceholder": "Thêm nhãn (nhấn Enter)", + "toastUpdated": "Đã cập nhật dấu trang", + "toastAdded": "Đã thêm dấu trang", + "cancel": "Hủy", + "submitAdd": "Thêm dấu trang", + "submitUpdate": "Cập nhật dấu trang" + }, + "addFolder": { + "title": "Tạo thư mục", + "nameLabel": "Tên thư mục", + "namePlaceholder": "Thư mục của tôi", + "nameRequired": "Vui lòng nhập tên thư mục", + "parentLabel": "Thư mục cha", + "parentPlaceholder": "Chọn thư mục cha", + "rootParent": "Gốc (Không có thư mục cha)", + "toastCreated": "Đã tạo thư mục", + "cancel": "Hủy", + "submit": "Tạo thư mục" + }, + "editFolder": { + "title": "Đổi tên thư mục", + "nameLabel": "Tên thư mục", + "namePlaceholder": "Tên thư mục", + "nameRequired": "Vui lòng nhập tên thư mục", + "toastRenamed": "Đã đổi tên thư mục", + "cancel": "Hủy", + "save": "Lưu" + } + }, + "EmailValidator": { + "header": { + "title": "Xác thực Email", + "description": "Xác minh và kiểm tra địa chỉ email về cú pháp, tên miền và khả năng gửi." + }, + "tabs": { + "single": "Xác minh đơn lẻ", + "bulk": "Tải lên hàng loạt" + }, + "single": { + "cardTitle": "Xác minh Email đơn lẻ", + "cardDescription": "Nhập địa chỉ email để kiểm tra khả năng gửi và uy tín.", + "placeholder": "Nhập địa chỉ email...", + "clearAria": "Xóa email", + "validate": "Xác thực", + "validating": "Đang xác thực...", + "formatError": "Vui lòng nhập địa chỉ email hợp lệ" + }, + "toasts": { + "errorTitle": "Lỗi", + "enterEmail": "Vui lòng nhập địa chỉ email", + "invalidFormatTitle": "Định dạng email không hợp lệ", + "invalidFormatDescription": "Vui lòng nhập địa chỉ email hợp lệ", + "validateFailedDescription": "Đã xảy ra lỗi khi xác thực email", + "noEmailsTitle": "Không tìm thấy email", + "noEmailsDescription": "Vui lòng đảm bảo tệp Excel có cột 'email'.", + "fileTooLargeTitle": "Tệp quá lớn", + "fileTooLargeDescription": "Tìm thấy {total} email. Chỉ 4000 email đầu tiên sẽ được xác thực.", + "successTitle": "Thành công", + "successValidated": "Đã xác thực {count} email thành công.", + "batchFailedTitle": "Xác thực hàng loạt thất bại", + "batchFailedDescription": "Đã xảy ra lỗi khi xử lý xác thực hàng loạt.", + "noExportTitle": "Không có dữ liệu để xuất", + "noExportValidOnly": "Không tìm thấy email hợp lệ để xuất.", + "noExportAll": "Không có kết quả để xuất.", + "exportSuccessTitle": "Xuất thành công", + "exportSuccessExcelAll": "Đã xuất {count} email sang Excel.", + "exportSuccessExcelValid": "Đã xuất {count} email hợp lệ sang Excel.", + "exportSuccessCsvAll": "Đã xuất {count} email sang CSV.", + "exportSuccessCsvValid": "Đã xuất {count} email hợp lệ sang CSV." + }, + "disposableAlert": { + "title": "Phát hiện email dùng một lần", + "description": "Địa chỉ email này thuộc nhà cung cấp email dùng một lần. Có thể là tạm thời hoặc không an toàn." + }, + "score": { + "reliabilityTitle": "Điểm tin cậy", + "scoreLabel": "Điểm" + }, + "details": { + "sectionTitle": "Chi tiết xác thực" + }, + "validation": { + "syntaxLabel": "Cú pháp hợp lệ", + "syntaxTooltip": "Kiểm tra xem địa chỉ email có đúng định dạng không (vd: user@domain.com)", + "domainLabel": "Tên miền tồn tại", + "domainTooltip": "Xác minh rằng tên miền trong địa chỉ email thực sự tồn tại", + "mxLabel": "Tìm thấy bản ghi MX", + "mxTooltip": "Kiểm tra xem tên miền có bản ghi trao đổi thư (MX) được cấu hình để nhận email không", + "mailboxLabel": "Hộp thư tồn tại", + "mailboxTooltip": "Xác minh rằng hộp thư/tài khoản cụ thể tồn tại trên máy chủ thư", + "disposableLabel": "Email dùng một lần", + "disposableTooltip": "Cho biết email này có thuộc dịch vụ email tạm thời/dùng một lần không (có thể rủi ro)", + "roleBasedLabel": "Email theo vai trò", + "roleBasedTooltip": "Xác định đây có phải email theo vai trò (vd: support@, info@) thay vì địa chỉ cá nhân" + }, + "copy": { + "emailCopied": "Đã sao chép email!", + "copyTooltip": "Sao chép email" + }, + "bulk": { + "dropHere": "Thả tệp vào đây", + "dragHere": "Kéo và thả tệp vào đây", + "uploadHint": "Tải lên tệp .xlsx, .xls hoặc .csv chứa cột 'email' để xác thực tối đa 4000 email.", + "filePrefix": "Tệp:", + "selectFile": "Chọn tệp", + "downloadTemplate": "Tải mẫu" + }, + "bulkLoading": { + "title": "Đang xác thực email...", + "batchProgress": "Đang xử lý lô {current} / {total}..." + }, + "stats": { + "totalProcessed": "Tổng đã xử lý", + "valid": "Hợp lệ", + "risky": "Rủi ro", + "invalid": "Không hợp lệ" + }, + "results": { + "detailedTitle": "Kết quả chi tiết", + "searchPlaceholder": "Tìm kiếm email...", + "newVerification": "Xác minh mới", + "exportAll": "Xuất tất cả", + "exportExcel": "Excel (.xlsx)", + "exportCsv": "CSV (.csv)", + "exportValid": "Xuất hợp lệ ({count})" + }, + "showingResults": "Hiển thị {shown} / {total} kết quả", + "table": { + "email": "Email", + "status": "Trạng thái", + "score": "Điểm", + "checks": "Kiểm tra" + }, + "noSearchResults": "Không tìm thấy kết quả phù hợp \"{query}\"", + "checksTooltip": { + "syntax": "Cú pháp", + "domain": "Tên miền", + "mx": "MX", + "mailbox": "Hộp thư" + }, + "exportColumns": { + "email": "Email", + "status": "Trạng thái", + "score": "Điểm", + "syntax": "Cú pháp", + "domain": "Tên miền", + "mx": "MX", + "mailbox": "Hộp thư", + "disposable": "Dùng một lần", + "roleBased": "Theo vai trò", + "valid": "Hợp lệ", + "invalid": "Không hợp lệ", + "yes": "Có", + "no": "Không" + }, + "sheets": { + "validEmails": "Email hợp lệ", + "results": "Kết quả", + "template": "Mẫu" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL Explorer", + "heroSubtitle": "Kết nối cơ sở dữ liệu để quản lý bộ sưu tập, thực thi truy vấn và trực quan hóa dữ liệu.", + "featureMultiTitle": "Đa kết nối", + "featureMultiDesc": "Quản lý nhiều kết nối cơ sở dữ liệu đồng thời.", + "featureMongoTitle": "Hỗ trợ MongoDB", + "featureMongoDesc": "Hỗ trợ MongoDB gốc với bộ lọc nâng cao.", + "featureQueryTitle": "Truy vấn thông minh", + "featureQueryDesc": "Trình xây dựng truy vấn mạnh mẽ với đánh dấu cú pháp.", + "connectCta": "Kết nối MongoDB", + "footerNote": "Hiện hỗ trợ {db}. Thêm cơ sở dữ liệu sẽ sớm có.", + "footerDb": "MongoDB", + "selectCollectionTitle": "Chọn bộ sưu tập", + "selectCollectionDesc": "Chọn bộ sưu tập từ thanh bên để xem tài liệu và bắt đầu khám phá dữ liệu.", + "dialogAddConnection": "Thêm kết nối", + "toastConnectionAdded": "Đã thêm kết nối", + "toastFetchFailed": "Không thể lấy tài liệu cho {collection}", + "toastDocDeleted": "Đã xóa tài liệu", + "deleteDocTitle": "Bạn có chắc chắn không?", + "deleteDocDescription": "Hành động này không thể hoàn tác. Tài liệu sẽ bị xóa vĩnh viễn khỏi bộ sưu tập.", + "closeAllTitle": "Đóng tất cả tab?", + "closeAllDescription": "Bạn có chắc muốn đóng tất cả tab? Mọi thay đổi chưa lưu trong bộ lọc truy vấn sẽ bị mất.", + "closeAllConfirm": "Đóng tất cả", + "cancel": "Hủy", + "delete": "Xóa" + }, + "connection": { + "titleEdit": "Sửa kết nối", + "titleConnect": "Kết nối MongoDB", + "subtitleEdit": "Cập nhật chi tiết kết nối bên dưới.", + "subtitleConnect": "Nhập chuỗi kết nối MongoDB để bắt đầu khám phá dữ liệu.", + "labelName": "Tên", + "placeholderName": "vd: Production DB", + "labelConnectionString": "Chuỗi kết nối", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "Định dạng: mongodb://username:password@host:port/database", + "testConnection": "Kiểm tra kết nối", + "connecting": "Đang kết nối...", + "updateConnect": "Cập nhật & Kết nối", + "connect": "Kết nối", + "cancelEdit": "Hủy sửa", + "savedConnections": "Kết nối đã lưu", + "emptySaved": "Chưa có kết nối nào được lưu.", + "lastUsed": "Sử dụng lần cuối {time}", + "toastTestOk": "Kết nối thành công!", + "toastTestFail": "Kết nối thất bại: {message}", + "toastUpdated": "Đã cập nhật kết nối", + "confirmDelete": "Bạn có chắc muốn xóa kết nối đã lưu này?", + "toastDeletedConn": "Đã xóa kết nối", + "toastDeleteFail": "Không thể xóa kết nối", + "copyStringTitle": "Sao chép chuỗi kết nối", + "toastStringCopied": "Đã sao chép chuỗi kết nối!", + "editTitle": "Sửa", + "deleteTitle": "Xóa" + }, + "sidebar": { + "explorer": "Explorer", + "add": "Thêm", + "tooltipAdd": "Thêm kết nối mới", + "tooltipRefresh": "Làm mới kết nối", + "searchPlaceholder": "Tìm kiếm...", + "loadingConnections": "Đang tải kết nối...", + "noConnectionsFound": "Không tìm thấy kết nối", + "toastLoadFail": "Không thể tải kết nối", + "toastConnectFail": "Không thể kết nối đến {name}", + "toastCollectionsFail": "Không thể lấy bộ sưu tập cho {name}", + "toastRenamed": "Đã đổi tên kết nối", + "toastRenameFail": "Không thể đổi tên kết nối", + "confirmDeleteConnection": "Bạn có chắc muốn xóa kết nối \"{name}\"?", + "toastDeleted": "Đã xóa kết nối", + "toastDeleteConnFail": "Không thể xóa kết nối", + "confirmDropDb": "Bạn có chắc muốn xóa cơ sở dữ liệu \"{name}\"? Hành động này không thể hoàn tác.", + "toastDbDropped": "Đã xóa cơ sở dữ liệu {name}", + "confirmDropCollection": "Bạn có chắc muốn xóa bộ sưu tập \"{name}\"? Hành động này không thể hoàn tác.", + "toastCollectionDropped": "Đã xóa bộ sưu tập {name}", + "toastCollectionRenamed": "Đã đổi tên bộ sưu tập thành {name}", + "toastDatabaseRenamed": "Đã đổi tên cơ sở dữ liệu thành {name}", + "connecting": "Đang kết nối...", + "connectionFailed": "Kết nối thất bại", + "noDatabases": "Không có cơ sở dữ liệu", + "loading": "Đang tải...", + "noCollections": "Không có bộ sưu tập", + "renameCollectionTitle": "Đổi tên bộ sưu tập", + "renameDatabaseTitle": "Đổi tên cơ sở dữ liệu", + "placeholderNewCollection": "Tên bộ sưu tập mới", + "placeholderNewDatabase": "Tên cơ sở dữ liệu mới", + "rename": "Đổi tên", + "refresh": "Làm mới", + "copyDbName": "Sao chép tên DB", + "toastDbNameCopied": "Đã sao chép tên cơ sở dữ liệu", + "dropDatabase": "Xóa cơ sở dữ liệu", + "dropCollection": "Xóa bộ sưu tập", + "menuRename": "Đổi tên", + "menuCopyConnectionString": "Sao chép chuỗi kết nối", + "menuDeleteConnection": "Xóa kết nối", + "toastSidebarStringCopied": "Đã sao chép chuỗi kết nối!", + "cancel": "Hủy", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} tài liệu", + "table": "Bảng", + "json": "JSON", + "tree": "Cây", + "tooltipTable": "Xem dạng bảng", + "tooltipJson": "Xem dạng JSON", + "tooltipTree": "Xem dạng cây", + "itemsPerPage": "Mục mỗi trang", + "perPage": "{n} / trang", + "prevPage": "Trang trước", + "nextPage": "Trang sau", + "expandAll": "Mở rộng tất cả", + "collapseAll": "Thu gọn tất cả", + "refreshData": "Làm mới dữ liệu", + "insert": "Chèn", + "export": "Xuất", + "actions": "Hành động", + "invalidJson": "Nội dung JSON không hợp lệ", + "copiedClipboard": "Đã sao chép vào bộ nhớ tạm", + "docUpdated": "Đã cập nhật tài liệu thành công", + "docInserted": "Đã chèn tài liệu thành công", + "invalidJsonShort": "JSON không hợp lệ", + "docCopied": "Đã sao chép tài liệu vào bộ nhớ tạm", + "openFullEditor": "Mở trong trình soạn thảo đầy đủ", + "cellArray": "Mảng({length})", + "cellObject": "Đối tượng", + "cellArrayBracket": "Mảng[{length}]", + "emptyFilterTitle": "Không có tài liệu phù hợp truy vấn", + "emptyFilterHint": "Thử điều chỉnh bộ lọc hoặc xóa truy vấn.", + "clearFilter": "Xóa bộ lọc", + "emptyCollectionTitle": "Bộ sưu tập trống", + "emptyCollectionHint": "Chèn tài liệu để bắt đầu.", + "insertDocument": "Chèn tài liệu", + "formatJson": "Định dạng JSON", + "compactJson": "Nén JSON", + "copyClipboard": "Sao chép vào bộ nhớ tạm", + "copyJson": "Sao chép JSON", + "duplicate": "Nhân đôi", + "editDoc": "Sửa tài liệu", + "deleteDoc": "Xóa tài liệu", + "documentLabel": "Tài liệu {n}", + "editDialogTitle": "Sửa tài liệu", + "insertDialogTitle": "Chèn tài liệu", + "viewValueTitle": "Xem giá trị", + "saveChanges": "Lưu thay đổi", + "close": "Đóng", + "cancel": "Hủy", + "insertButton": "Chèn", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "Bộ lọc đang áp dụng", + "filterActiveTooltip": "Bộ lọc đang hoạt động", + "closeAll": "Đóng tất cả" + }, + "query": { + "migrateHistory": "Đã chuyển lịch sử truy vấn lên đám mây", + "saveHistoryFail": "Không thể lưu lịch sử truy vấn", + "deleteQueryFail": "Không thể xóa truy vấn", + "invalidJsonQuery": "Truy vấn JSON không hợp lệ", + "placeholder": "vd: { \"status\": \"active\" }", + "clearQuery": "Xóa truy vấn", + "openAdvanced": "Mở trình soạn thảo nâng cao", + "visualBuilder": "Trình xây dựng truy vấn trực quan", + "filterRules": "Quy tắc lọc", + "addRule": "Thêm quy tắc", + "noFilters": "Chưa áp dụng bộ lọc. Thêm quy tắc để lọc tài liệu.", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "Trường", + "opPlaceholder": "Toán tử", + "typePlaceholder": "Kiểu", + "opRegex": "Regex", + "opIn": "Trong", + "opNin": "Không trong", + "opExists": "Tồn tại", + "typeAuto": "Tự động", + "typeString": "Chuỗi", + "typeNumber": "Số", + "typeBoolean": "Boolean", + "typeDate": "Ngày", + "typeObjectId": "ObjectId", + "true": "Đúng", + "false": "Sai", + "valuePlaceholder": "Giá trị", + "valueListPlaceholder": "giá trị1, giá trị2", + "clearFilters": "Xóa bộ lọc", + "applyFilters": "Áp dụng bộ lọc", + "queryHistory": "Lịch sử truy vấn", + "noSavedQueries": "Chưa có truy vấn đã lưu", + "advancedEditorTitle": "Trình soạn thảo truy vấn nâng cao", + "templatesPlaceholder": "Mẫu", + "templateEmptyAgg": "Aggregation trống", + "templateMatch": "$match (Lọc)", + "templateLookup": "$lookup (Nối)", + "templateGroup": "$group (Đếm/Tổng)", + "format": "Định dạng", + "formattedJson": "JSON đã định dạng", + "runQuery": "Chạy truy vấn" + }, + "export": { + "selectFieldsError": "Vui lòng chọn ít nhất một trường để xuất", + "success": "Đã xuất thành công sang {filename}", + "fail": "Không thể xuất dữ liệu", + "title": "Xuất dữ liệu", + "formatLabel": "Định dạng xuất", + "formatXlsx": "Excel (XLSX)", + "formatCsv": "CSV", + "selectFields": "Chọn trường", + "selectAll": "Chọn tất cả", + "fieldsSelected": "{count, plural, one {# trường đã chọn} other {# trường đã chọn}}", + "cancel": "Hủy", + "export": "Xuất", + "sheetName": "Dữ liệu" + }, + "jsonTree": { + "typeLabel": "Kiểu: {type}", + "itemSummary": "{count, plural, one {# mục} other {# mục}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "Yêu cầu mới", + "importedRequest": "Yêu cầu đã nhập", + "untitledRequest": "Yêu cầu chưa đặt tên" + }, + "toasts": { + "curlImported": "Đã nhập cURL thành công", + "curlParseFailed": "Không thể phân tích lệnh cURL", + "invalidJsonBody": "Body JSON không hợp lệ", + "requestFailed": "Yêu cầu thất bại: {message}", + "curlPasted": "Đã dán và phân tích cURL thành công", + "responseCopied": "Đã sao chép phản hồi vào bộ nhớ tạm", + "codeCopied": "Đã sao chép mã vào bộ nhớ tạm", + "copyFailed": "Sao chép vào clipboard không thành công" + }, + "layout": { + "collections": "Bộ sưu tập", + "closeSidebar": "Đóng thanh bên", + "openSidebar": "Mở thanh bên", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "Phương thức", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "Đang gửi...", + "send": "Gửi", + "invalidJsonBodyHelp": "Không thể gửi: nội dung JSON không hợp lệ" + }, + "requestTabs": { + "params": "Tham số", + "headers": "Headers", + "body": "Body", + "auth": "Xác thực", + "bodyType": "Loại body", + "selectType": "Chọn loại", + "bodyNone": "Không", + "bodyJson": "JSON", + "bodyText": "Text", + "noBodyHint": "Không có nội dung body cho yêu cầu này", + "switchToJson": "Chuyển sang body JSON", + "authType": "Loại xác thực", + "noAuth": "Không xác thực", + "bearerToken": "Bearer token", + "basicAuth": "Basic auth", + "apiKey": "API key", + "labelToken": "Token", + "placeholderBearerToken": "Bearer token", + "labelUsername": "Tên đăng nhập", + "placeholderUsername": "Tên đăng nhập", + "labelPassword": "Mật khẩu", + "placeholderPassword": "Mật khẩu", + "labelKey": "Khóa", + "placeholderApiKeyName": "Tên khóa (vd: X-Api-Key)", + "labelValue": "Giá trị", + "placeholderValue": "Giá trị", + "addTo": "Thêm vào", + "header": "Header", + "queryParams": "Tham số truy vấn", + "noAuthHint": "Yêu cầu này không sử dụng xác thực." + }, + "responsePanel": { + "emptyTitle": "Sẵn sàng khám phá?", + "emptyHint": "Nhập URL và nhấn Gửi để xem dữ liệu phản hồi tại đây.", + "responsePreviewAlt": "Xem trước phản hồi", + "previewUnavailable": "Không thể xem trước loại nội dung này", + "status": "Trạng thái", + "time": "Thời gian", + "size": "Kích thước", + "copyBody": "Sao chép body", + "downloadResponse": "Tải phản hồi", + "bodyTab": "Body", + "previewTab": "Xem trước", + "headersTab": "Headers", + "binaryRawView": "Dữ liệu nhị phân không thể hiển thị ở chế độ raw.", + "errorStatusLabel": "Lỗi", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "Tab mới" + }, + "collectionsSidebar": { + "title": "Tài nguyên API", + "newCollection": "Bộ sưu tập mới", + "tabCollections": "Bộ sưu tập", + "tabHistory": "Lịch sử", + "noCollectionsTitle": "Chưa có bộ sưu tập", + "noCollectionsHint": "Tạo bộ sưu tập để sắp xếp các yêu cầu API.", + "createCollection": "Tạo bộ sưu tập", + "newFolder": "Thư mục mới", + "rename": "Đổi tên", + "delete": "Xóa", + "emptyCollection": "Bộ sưu tập trống", + "recentRequests": "Yêu cầu gần đây", + "clearAll": "Xóa tất cả", + "noHistoryTitle": "Chưa có lịch sử", + "noHistoryHint": "Gửi yêu cầu để xem tại đây.", + "dialogNewFolderTitle": "Thư mục mới", + "dialogNewFolderDescription": "Tạo thư mục mới để sắp xếp yêu cầu.", + "labelFolderName": "Tên thư mục", + "placeholderFolderName": "Thư mục của tôi", + "createFolder": "Tạo thư mục", + "dialogNewCollectionTitle": "Bộ sưu tập mới", + "dialogNewCollectionDescription": "Tạo bộ sưu tập cấp cao nhất mới.", + "labelCollectionName": "Tên bộ sưu tập", + "placeholderCollectionName": "Bộ sưu tập của tôi", + "dialogRenameCollectionTitle": "Đổi tên bộ sưu tập", + "dialogRenameCollectionDescription": "Nhập tên mới cho bộ sưu tập.", + "placeholderRenameCollection": "Tên bộ sưu tập", + "renameAction": "Đổi tên", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "Thư mục mới", + "delete": "Xóa", + "emptyFolder": "Thư mục trống" + }, + "environmentManager": { + "noEnvironment": "Không có môi trường", + "manageDialogTitle": "Quản lý môi trường", + "placeholderNewEnv": "Môi trường mới", + "variablesHeading": "Biến {name}", + "addVariable": "Thêm biến", + "columnVariable": "Biến", + "columnValue": "Giá trị", + "placeholderKey": "Khóa", + "placeholderValue": "Giá trị", + "selectEnvToEdit": "Chọn môi trường để chỉnh sửa" + }, + "codeGenerator": { + "triggerTitle": "Tạo mã", + "dialogTitle": "Tạo mã", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript (fetch)", + "languagePython": "Python (requests)", + "languageGo": "Go", + "copy": "Sao chép", + "errorGenerating": "Lỗi khi tạo mã" + }, + "keyValueEditor": { + "columnKey": "Khóa", + "columnValue": "Giá trị", + "placeholderKey": "Khóa", + "placeholderValue": "Giá trị", + "addItem": "Thêm mục" + }, + "importCurl": { + "trigger": "Nhập cURL", + "dialogTitle": "Nhập cURL", + "dialogDescription": "Dán lệnh cURL bên dưới để nhập như yêu cầu mới.", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "Nhập" + }, + "saveRequest": { + "trigger": "Lưu", + "dialogTitle": "Lưu yêu cầu", + "dialogDescription": "Lưu yêu cầu này vào thư mục bộ sưu tập.", + "labelName": "Tên yêu cầu", + "placeholderName": "Yêu cầu của tôi", + "labelFolder": "Thư mục", + "placeholderFolder": "Chọn thư mục", + "save": "Lưu" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "Chế độ chỉnh sửa", + "viewOnlyMode": "Chế độ chỉ xem", + "insertComponent": "Chèn thành phần", + "addUnorderedList": "Thêm danh sách không thứ tự", + "addOrderedList": "Thêm danh sách có thứ tự", + "addTable": "Thêm bảng" + }, + "addBlock": "Thêm khối", + "media": { + "popoverTitle": "Thêm phương tiện", + "singleImage": "Ảnh đơn", + "singleImageDesc": "Tải lên một ảnh", + "multipleImages": "Nhiều ảnh", + "multipleImagesDesc": "Tải lên lưới ảnh", + "video": "Video", + "videoDesc": "Tải lên tệp video", + "uploadingAlt": "Đang tải lên...", + "uploadFailedAlt": "Tải lên thất bại" + }, + "export": { + "fabAria": "Xuất mã", + "title": "Xuất mã", + "description": "Sao chép đầu ra HTML hoặc JSON của nội dung trình soạn thảo", + "preview": "Xem trước", + "html": "HTML", + "json": "JSON", + "previewOptions": "Tùy chọn xem trước", + "enhanceSpaces": "Cải thiện khoảng cách", + "livePreview": "Xem trước trực tiếp HTML đã render", + "htmlTailwind": "HTML với lớp Tailwind CSS", + "editorJson": "Trạng thái trình soạn thảo dạng JSON", + "copyHtml": "Sao chép HTML", + "copyJson": "Sao chép JSON", + "copied": "Đã sao chép!" + }, + "template": { + "fabTitle": "Đổi mẫu", + "title": "Chọn mẫu", + "description": "Chọn mẫu để bắt đầu nhanh với bố cục và nội dung sẵn có", + "catAll": "Tất cả mẫu", + "catProductivity": "Năng suất", + "catCreative": "Sáng tạo", + "catBusiness": "Kinh doanh", + "catPersonal": "Cá nhân", + "noTemplates": "Không tìm thấy mẫu trong danh mục này", + "confirmTitle": "Thay thế nội dung hiện tại?", + "confirmDescription": "Bạn có nội dung hiện tại trong trình soạn thảo. Áp dụng mẫu này sẽ thay thế toàn bộ nội dung hiện tại.", + "cannotUndo": "Hành động này không thể hoàn tác.", + "cancel": "Hủy", + "replaceContent": "Thay thế nội dung" + }, + "command": { + "searchPlaceholder": "Tìm lệnh...", + "noResults": "Không tìm thấy lệnh.", + "turnInto": "Chuyển thành", + "h1": { + "label": "Tiêu đề 1", + "description": "Tiêu đề phần lớn", + "keywords": "h1,heading,title,large" + }, + "h2": { + "label": "Tiêu đề 2", + "description": "Tiêu đề phần vừa", + "keywords": "h2,heading,subtitle" + }, + "h3": { + "label": "Tiêu đề 3", + "description": "Tiêu đề phần nhỏ", + "keywords": "h3,heading,subheading" + }, + "h4": { + "label": "Tiêu đề 4", + "description": "Tiêu đề phần rất nhỏ", + "keywords": "h4,heading" + }, + "h5": { + "label": "Tiêu đề 5", + "description": "Tiêu đề nhỏ hơn", + "keywords": "h5,heading" + }, + "h6": { + "label": "Tiêu đề 6", + "description": "Tiêu đề nhỏ nhất", + "keywords": "h6,heading" + }, + "p": { + "label": "Đoạn văn", + "description": "Đoạn văn bản thường", + "keywords": "p,paragraph,text,normal" + }, + "code": { + "label": "Khối mã", + "description": "Đoạn mã", + "keywords": "code,codeblock,snippet,pre" + }, + "blockquote": { + "label": "Trích dẫn", + "description": "Khối trích dẫn", + "keywords": "quote,blockquote,citation" + }, + "li": { + "label": "Danh sách có dấu đầu dòng", + "description": "Mục danh sách với dấu đầu dòng", + "keywords": "list,bullet,unordered,ul,li" + }, + "ol": { + "label": "Danh sách đánh số", + "description": "Mục danh sách đánh số", + "keywords": "list,numbered,ordered,ol,li" + }, + "img": { + "label": "Hình ảnh", + "description": "Tải lên hoặc nhúng ảnh", + "keywords": "image,img,picture,photo,upload" + }, + "video": { + "label": "Video", + "description": "Tải lên hoặc nhúng video", + "keywords": "video,vid,movie,mp4,upload" + }, + "table": { + "label": "Bảng", + "description": "Tạo bảng", + "keywords": "table,grid,rows,columns,cells" + } + }, + "toasts": { + "tableImported": "Đã nhập bảng", + "tableImportedDesc": "Bảng markdown đã được nhập thành công", + "mediaPasted": "Đã dán phương tiện", + "pasteMixed": "Đã dán {imageCount} ảnh và {videoCount} video thành công.", + "pasteImages": "Đã dán {count} ảnh thành công.", + "pasteVideos": "Đã dán {count} video thành công.", + "pasteFailed": "Dán thất bại", + "unexpectedError": "Đã xảy ra lỗi không mong muốn", + "uploadFailed": "Tải lên thất bại" + }, + "blockBackground": { + "menuItem": "Đổi màu nền", + "dialogTitle": "Màu nền khối", + "dialogDescription": "Chọn màu nền cho khối này", + "tabPreset": "Màu có sẵn", + "tabCustom": "Màu tùy chỉnh", + "sectionHeading": "Màu nền", + "apply": "Áp dụng", + "colors": { + "none": "Không", + "red": "Đỏ", + "orange": "Cam", + "yellow": "Vàng", + "green": "Xanh lá", + "blue": "Xanh dương", + "indigo": "Chàm", + "purple": "Tím", + "pink": "Hồng", + "teal": "Xanh mòng két", + "cyan": "Lục lam", + "gray": "Xám" + } + }, + "tableDialog": { + "title": "Tạo bảng", + "description": "Tạo bảng mới hoặc nhập từ markdown", + "markdownCheckbox": "Tôi có bảng markdown", + "pasteMarkdownLabel": "Dán bảng Markdown", + "markdownPlaceholder": "| Cột 1 | Cột 2 | Cột 3 |\n|----------|----------|----------|\n| Ô 1 | Ô 2 | Ô 3 |\n| Ô 4 | Ô 5 | Ô 6 |", + "markdownHint": "Dán bảng markdown ở trên. Phải bao gồm hàng tiêu đề và hàng phân cách.", + "rows": "Hàng", + "columns": "Cột", + "maxDimensions": "Tối đa: 20 hàng × 10 cột", + "cancel": "Hủy", + "importTable": "Nhập bảng", + "createTable": "Tạo bảng" + }, + "tableParse": { + "errors": { + "minRows": "Bảng phải có ít nhất hàng tiêu đề và hàng phân cách", + "pipeFormat": "Các hàng phải bắt đầu và kết thúc bằng |", + "headerMinCol": "Hàng tiêu đề phải có ít nhất một cột", + "separatorRow": "Hàng thứ hai phải là phân cách (vd: |---|---|)", + "rowColumnMismatch": "Hàng {row} có {got} cột, dự kiến {expected}", + "parseFailed": "Không thể phân tích bảng markdown" + } + }, + "link": { + "editLink": "Sửa liên kết", + "addLink": "Thêm liên kết", + "selectedText": "Văn bản đã chọn: \"{text}\"", + "linkUrl": "URL liên kết", + "urlPlaceholder": "https://example.com", + "update": "Cập nhật", + "addLinkButton": "Thêm liên kết", + "toastApplied": "Đã áp dụng liên kết", + "toastLinkedTo": "Đã liên kết đến: {url}", + "toastRemoved": "Đã xóa liên kết", + "toastRemovedDesc": "Đã xóa liên kết khỏi vùng chọn" + }, + "customClass": { + "devMode": "Chế độ nhà phát triển", + "toggleDevModeAria": "Bật/tắt chế độ nhà phát triển", + "searchPlaceholderDev": "Tìm lớp... (vd: 'text', 'bg', 'flex')", + "searchPlaceholderUser": "Tìm kiểu... (vd: 'red', 'bold', 'shadow')", + "appliesTitle": "Áp dụng: {className}", + "noMatches": "Không tìm thấy lớp phù hợp \"{query}\"", + "toastClassReplaced": "Đã thay thế lớp", + "toastClassReplacedDesc": "Đã thay thế \"{replaced}\" bằng \"{className}\"", + "toastCustomClassApplied": "Đã áp dụng lớp tùy chỉnh", + "toastAppliedClass": "Đã áp dụng lớp: {className}" + }, + "insertComponents": { + "title": "Chèn thành phần", + "description": "Chọn thành phần để chèn vào tài liệu", + "noComponents": "Không có thành phần khả dụng", + "categories": { + "media": "Phương tiện", + "layout": "Bố cục", + "interactive": "Tương tác", + "decoration": "Trang trí" + }, + "freeImage": { + "name": "Ảnh tự do", + "description": "Thêm ảnh có thể đặt vị trí bất kỳ và thay đổi kích thước tự do" + } + } + }, + "JsonFormatter": { + "title": "Trình định dạng JSON", + "description": "Định dạng, kiểm tra và chỉnh sửa JSON ở chế độ văn bản và cây.", + "documentNameText": "Tài liệu văn bản {n}", + "documentNameTree": "Tài liệu dạng cây {n}", + "newDocument": "Mới", + "copy": "Sao chép", + "save": "Lưu", + "load": "Tải", + "saving": "Đang lưu...", + "leftDocNameLabel": "Tên tài liệu bên trái", + "rightDocNameLabel": "Tên tài liệu bên phải", + "toastCopiedText": "Đã sao chép bảng văn bản", + "toastCopiedTree": "Đã sao chép bảng cây", + "toastCopyFailed": "Không thể sao chép JSON", + "toastLoginRequired": "Vui lòng đăng nhập để lưu tài liệu", + "toastSaved": "Đã lưu tài liệu", + "toastSaveFailed": "Không thể lưu tài liệu", + "format": "Định dạng", + "compact": "Nén gọn", + "sortKeys": "Sắp xếp khóa", + "repair": "Sửa chữa", + "query": "Truy vấn", + "toastInvalidJson": "JSON không hợp lệ", + "toastAlreadyValid": "JSON đã hợp lệ", + "toastRepaired": "Đã sửa JSON", + "toastRepairFailed": "Không thể sửa JSON" + }, + "JwtDecoder": { + "title": "Trình giải mã JWT", + "subtitle": "Xem header và payload. Hết hạn dùng claim exp. Không gửi lên máy chủ; chữ ký không được xác minh.", + "tokenLabel": "Token", + "clear": "Xóa", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "Sao chép", + "expiryLabel": "Hết hạn (exp)", + "signaturePresent": "Có chữ ký (không xác minh)", + "noExp": "Không có claim exp trong payload.", + "status": { + "expired": "Đã hết hạn", + "valid": "Hợp lệ" + }, + "fields": { + "unix": "Unix", + "local": "Cục bộ", + "utc": "UTC" + }, + "claims": { + "issuedAt": "Phát hành (iat)", + "notBefore": "Không trước (nbf)" + }, + "panels": { + "header": "Header", + "payload": "Payload" + }, + "errors": { + "empty": "Dán JWT để giải mã.", + "segments": "JWT cần ít nhất header và payload (hai đoạn).", + "base64": "Không thể giải mã base64url cho header hoặc payload." + }, + "relative": { + "future": "trong {n} {unit}", + "past": "{n} {unit} trước" + }, + "units": { + "second": "{count, plural, one {giây} other {giây}}", + "minute": "{count, plural, one {phút} other {phút}}", + "hour": "{count, plural, one {giờ} other {giờ}}", + "day": "{count, plural, one {ngày} other {ngày}}" + }, + "tabs": { + "decode": "Giải mã", + "sign": "Tạo / ký" + }, + "signer": { + "algLabel": "Thuật toán (alg)", + "tabs": { + "payload": "Payload", + "header": "Header" + }, + "payloadLabel": "Payload JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "Header JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "Bí mật (HS*)", + "secretPlaceholder": "Dán bí mật HMAC của bạn…", + "privateKeyLabel": "Khóa riêng PEM (RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "Thuật toán \"none\" tạo token không có chữ ký. Không an toàn và chỉ nên dùng để kiểm thử.", + "sign": "Ký", + "verify": "Xác minh chữ ký", + "outputLabel": "Token (JWS gọn)", + "outputPlaceholder": "Nhấn \"Ký\"…", + "copyToken": "Sao chép token", + "copied": "Đã sao chép", + "verifyKeyLabel": "Khóa xác minh (tùy chọn)", + "verifyKeyPlaceholder": "Với RS*: dán khóa công khai (SPKI) PEM. Với HS*: để trống để dùng lại bí mật.", + "verifyKeyHint": "Nếu để trống, xác minh sẽ dùng khóa ký (hữu ích với HS*).", + "verifyStatus": { + "valid": "Chữ ký hợp lệ", + "invalid": "Chữ ký không hợp lệ" + }, + "securityNote": "Mọi thứ chạy cục bộ trong trình duyệt; không tải lên khóa hay token." + }, + "signErrors": { + "invalidJson": "Header và payload phải là JSON hợp lệ.", + "unsupportedAlg": "Thuật toán không được hỗ trợ.", + "missingKey": "Cần khóa ký cho thuật toán này.", + "cryptoFailed": "Ký thất bại. Kiểm tra định dạng khóa và thử lại." + }, + "verifyErrors": { + "empty": "Nhập JWT để xác minh.", + "segments": "JWT phải có đúng 3 phần để xác minh chữ ký.", + "unsupportedAlg": "Thuật toán không được hỗ trợ hoặc thiếu alg trong header.", + "missingKey": "Cần khóa xác minh cho thuật toán này.", + "base64": "Không thể giải mã header hoặc chữ ký JWT.", + "cryptoFailed": "Xác minh thất bại. Kiểm tra định dạng khóa và thử lại." + } + }, + "CertificatePemDecoder": { + "title": "Bộ giải mã chứng chỉ / PEM", + "subtitle": "Dán chứng chỉ X.509 (-----BEGIN CERTIFICATE-----) hoặc CSR (-----BEGIN CERTIFICATE REQUEST-----). Chủ thể, nhà phát hành, ngày và SAN được phân tích cục bộ; không tải lên.", + "pemLabel": "Đầu vào PEM", + "clear": "Xóa", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "Đang phân tích…", + "copyHex": "Sao chép vân tay (hex không có dấu hai chấm)", + "errors": { + "empty": "Dán chứng chỉ PEM hoặc CSR để giải mã.", + "noBlock": "Không tìm thấy khối PEM chứng chỉ hoặc CSR.", + "parseFailed": "Không thể phân tích dữ liệu PEM này." + }, + "block": { + "title": "Khối PEM {index}", + "certificate": "Chứng chỉ X.509", + "csr": "Yêu cầu ký chứng chỉ" + }, + "fields": { + "subject": "Chủ thể", + "issuer": "Nhà phát hành", + "notBefore": "Không hợp lệ trước", + "notAfter": "Không hợp lệ sau", + "serial": "Số sê-ri (hex)", + "sha256": "Vân tay SHA-256", + "sans": "Tên thay thế cho chủ thể", + "sansNone": "Không có phần mở rộng SAN.", + "signatureAlgorithm": "Thuật toán ký", + "csrIssuerNote": "CSR không có nhà phát hành (có trên chứng chỉ đã cấp).", + "csrValidityNote": "CSR không có ngày hiệu lực (có trên chứng chỉ đã cấp)." + }, + "sanType": { + "dns": "DNS", + "dn": "Tên thư mục", + "email": "Email", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "ID đã đăng ký", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "Trình định dạng SQL", + "subtitle": "Định dạng SQL cho MySQL, PostgreSQL hoặc SQLite. Chạy trong trình duyệt; không tải lên gì cả.", + "dialectLabel": "Phương ngữ", + "keywordsLabel": "Từ khóa", + "indentLabel": "Thụt lề", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "CHỮ HOA", + "lower": "chữ thường", + "preserve": "Giữ nguyên" + }, + "spaces": "{n} khoảng trắng", + "format": "Định dạng", + "clear": "Xóa", + "counter": "{current} / {max} ký tự", + "inputPanel": "Đầu vào", + "formattedPanel": "Đã định dạng", + "copy": "Sao chép", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "Nhấn \"Định dạng\"…", + "errors": { + "tooLong": "Đầu vào bị giới hạn {max} ký tự.", + "couldNotFormat": "Không thể định dạng SQL này." + } + }, + "YamlFormatter": { + "title": "Trình định dạng YAML", + "subtitle": "Định dạng, xác thực và chuyển đổi YAML trong trình duyệt. Không có gì được tải lên.", + "outputLabel": "Đầu ra", + "outputModes": { + "yaml": "YAML (đã định dạng)", + "json": "JSON" + }, + "indentLabel": "Thụt lề", + "spaces": "{n} khoảng trắng", + "format": "Định dạng", + "clear": "Xóa", + "copy": "Sao chép", + "counter": "{current} / {max} ký tự", + "inputPanel": "Đầu vào", + "outputPanel": "Đầu ra", + "errors": { + "tooLong": "Đầu vào giới hạn {max} ký tự.", + "couldNotFormat": "Không thể phân tích cú pháp YAML này." + } + }, + "GraphqlFormatter": { + "title": "Trình định dạng GraphQL", + "subtitle": "Định dạng truy vấn, mutation và subscription GraphQL trong trình duyệt. Không tải dữ liệu lên.", + "tabs": { + "format": "Định dạng", + "build": "Trình tạo truy vấn" + }, + "outputModeLabel": "Đầu ra", + "outputModes": { + "pretty": "In đẹp", + "minify": "Thu nhỏ" + }, + "format": "Định dạng", + "clear": "Xóa", + "counter": "{current} / {max} ký tự", + "inputPanel": "Đầu vào", + "outputPanel": "Đầu ra", + "copy": "Sao chép", + "builderHint": "Soạn khung tài liệu rồi áp vào trình soạn. Định nghĩa biến theo cú pháp GraphQL (ví dụ $id: ID!) — dấu ngoặc là tùy chọn.", + "builder": { + "operation": "Thao tác", + "operationName": "Tên thao tác", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "Định nghĩa biến", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "Tập chọn", + "apply": "Áp vào đầu vào", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "Đầu vào giới hạn {max} ký tự.", + "couldNotFormat": "Không thể phân tích hoặc in GraphQL này.", + "emptySelection": "Thêm tập chọn trước khi áp dụng." + } + }, + "RegexTester": { + "title": "Trình thử Regex", + "subtitle": "Regex kiểu JavaScript chạy trực tiếp trên văn bản. Kết quả khớp được tô sáng; không có gì rời khỏi trình duyệt.", + "patternLabel": "Mẫu", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g toàn cục", + "i": "i không phân biệt hoa/thường", + "m": "m nhiều dòng", + "s": "s dotAll", + "u": "u unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(trống)", + "noFlags": "—", + "matches": "{count, plural, one {kết quả} other {kết quả}}", + "testTextLabel": "Văn bản thử", + "counter": "{current} / {max}", + "clear": "Xóa", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "Văn bản bị giới hạn {max} ký tự.", + "invalid": "Biểu thức chính quy không hợp lệ.", + "runaway": "Dừng sau {max} lần khớp (có thể mẫu bị runaway)." + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "Trình chuyển đổi dấu thời gian", + "subtitle": "Giây Unix (≤10 chữ số) hoặc mili giây (số nguyên dài hơn), ISO-8601 hoặc chuỗi mà trình duyệt có thể phân tích. Mọi thứ chạy cục bộ.", + "inputLabel": "Đầu vào", + "placeholder": "1744200000 hoặc 2025-04-09T12:00:00Z", + "now": "Bây giờ", + "copy": "Sao chép", + "hint": "Dán một giá trị hoặc bấm Bây giờ.", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "Tương đối", + "human": "Dễ đọc" + }, + "fields": { + "seconds": "Giây", + "milliseconds": "Mili giây", + "utc": "UTC", + "local": "Cục bộ", + "distance": "Khoảng cách" + }, + "errors": { + "empty": "Nhập dấu thời gian Unix, chuỗi ISO hoặc văn bản ngày.", + "invalidNumber": "Dấu thời gian số không hợp lệ.", + "outOfRange": "Ngoài phạm vi JavaScript Date.", + "couldNotParse": "Không thể phân tích dưới dạng Unix, ISO-8601 hoặc chuỗi ngày của trình duyệt." + } + }, + "HttpStatusCodes": { + "title": "Mã trạng thái HTTP", + "subtitle": "Tìm kiếm và duyệt mã trạng thái phản hồi HTTP (1xx5xx) với mô tả ngắn và tham chiếu RFC. Chạy cục bộ trên trình duyệt.", + "header": { + "title": "Mã trạng thái HTTP", + "description": "Danh sách mã phản hồi HTTP có thể tìm kiếm, kèm mô tả và tham chiếu RFC." + }, + "searchPlaceholder": "Tìm: 404, not found, rate limit, gateway…", + "classFilterPlaceholder": "Lọc theo nhóm…", + "classFilterAll": "Tất cả nhóm", + "resultsCount": "{count}/{total}", + "table": { + "code": "Code", + "phrase": "Cụm từ", + "description": "Mô tả", + "copy": "Sao chép" + }, + "noResults": "Không có kết quả cho “{query}”.", + "specPrefix": "Chuẩn:", + "copied": "Đã sao chép mã" + }, + "CronBuilder": { + "title": "Trình tạo biểu thức Cron", + "subtitle": "Cron tiêu chuẩn 5 trường: minute hour day month weekday. Bộ phân tích dùng seconds=0 nội bộ. Định dạng 6 trường tùy chọn (sec min hour dom mon dow) hoạt động trong trình soạn thảo.", + "tabs": { + "builder": "Trình tạo trực quan", + "expression": "Biểu thức" + }, + "presetsLabel": "Mẫu sẵn", + "fieldsLabel": "Trường", + "rawExpressionLabel": "Biểu thức thô", + "expressionHint": "Rời khỏi ô để phân tích và đồng bộ trình tạo. {count} {count, plural, one {token} other {token}}.", + "currentExpressionLabel": "Biểu thức hiện tại", + "copy": "Sao chép", + "quickPick": "Chọn nhanh", + "emptyExpression": "—", + "plainLanguageLabel": "Ngôn ngữ tự nhiên", + "nextRunsLabel": "Lần chạy tiếp theo (giờ địa phương)", + "fields": { + "minute": "Phút", + "hour": "Giờ", + "dayOfMonth": "Ngày trong tháng", + "month": "Tháng", + "dayOfWeek": "Thứ" + }, + "presets": { + "everyMinute": "Mỗi phút", + "every5Minutes": "Mỗi 5 phút", + "every15Minutes": "Mỗi 15 phút", + "everyHour": "Mỗi giờ (lúc :00)", + "every6Hours": "Mỗi 6 giờ", + "dailyMidnight": "Hằng ngày lúc nửa đêm", + "daily0900": "Hằng ngày lúc 09:00", + "weekdays0900": "Ngày thường lúc 09:00", + "weeklySunday": "Hàng tuần (CN 00:00)", + "monthlyFirst": "Hàng tháng (ngày 1 00:00)", + "yearlyJan1": "Hàng năm (1/1 00:00)" + }, + "picks": { + "minute": { + "every": "mỗi phút" + }, + "hour": { + "every": "mỗi giờ" + }, + "dom": { + "any": "bất kỳ ngày", + "last": "ngày cuối" + }, + "month": { + "every": "mỗi tháng" + }, + "dow": { + "any": "bất kỳ ngày", + "weekdays": "ngày thường", + "weekend": "cuối tuần" + } + }, + "months": { + "jan": "Th1", + "feb": "Th2", + "mar": "Th3", + "apr": "Th4", + "may": "Th5", + "jun": "Th6", + "jul": "Th7", + "aug": "Th8", + "sep": "Th9", + "oct": "Th10", + "nov": "Th11", + "dec": "Th12" + }, + "weekdays": { + "sun": "CN", + "mon": "T2", + "tue": "T3", + "wed": "T4", + "thu": "T5", + "fri": "T6", + "sat": "T7" + }, + "errors": { + "fieldCount": "Cần 5 trường (minute hour day month weekday) hoặc 6 trường gồm giây.", + "invalid": "Biểu thức cron không hợp lệ." + } + }, + "Base64": { + "title": "Mã hóa / giải mã Base64", + "subtitle": "Mã hóa văn bản sang Base64 hoặc giải mã chuỗi Base64", + "upload": "Tải lên", + "clear": "Xóa", + "modes": { + "encode": "Mã hóa", + "decode": "Giải mã" + }, + "swapTitle": "Hoán đổi đầu vào/đầu ra", + "panels": { + "plainText": "Văn bản", + "base64Input": "Đầu vào Base64", + "base64Output": "Đầu ra Base64", + "decodedText": "Văn bản đã giải mã" + }, + "placeholders": { + "encode": "Nhập văn bản để mã hóa Base64…", + "decode": "Dán Base64 để giải mã…" + }, + "dropHere": "Thả tệp vào đây", + "charCount": "{count} ký tự", + "download": { + "title": "Tải xuống kết quả", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "Sao chép vào clipboard", + "outputPlaceholder": "Kết quả sẽ hiển thị ở đây…", + "errors": { + "invalidBase64": "Chuỗi Base64 không hợp lệ. Hãy kiểm tra đầu vào.", + "encodeFailed": "Không thể mã hóa đầu vào." + } + }, + "UrlEncode": { + "title": "Mã hóa / giải mã URL", + "subtitle": "Mã hóa/giải mã theo phần trăm bằng encodeURIComponent / decodeURIComponent (UTF‑8)", + "upload": "Tải lên", + "clear": "Xóa", + "modes": { + "encode": "Mã hóa", + "decode": "Giải mã" + }, + "swapTitle": "Hoán đổi đầu vào/đầu ra", + "panels": { + "plainText": "Văn bản", + "encodedInput": "Đầu vào đã mã hóa", + "encodedOutput": "Đầu ra đã mã hóa", + "decodedText": "Văn bản đã giải mã" + }, + "placeholders": { + "encode": "Nhập văn bản để mã hóa phần trăm…", + "decode": "Dán văn bản percent-encoded để giải mã…" + }, + "dropHere": "Thả tệp vào đây", + "charCount": "{count} ký tự", + "download": { + "title": "Tải xuống kết quả", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "Sao chép vào clipboard", + "outputPlaceholder": "Kết quả sẽ hiển thị ở đây…", + "errors": { + "invalidEncoding": "Mã hóa phần trăm không hợp lệ. Hãy kiểm tra cặp % chưa đầy đủ hoặc chuỗi escape sai.", + "encodeFailed": "Không thể mã hóa (Unicode không hợp lệ trong đầu vào)." + } + }, + "UrlParser": { + "title": "Trình phân tích URL", + "subtitle": "Tách URL thành giao thức, host, đường dẫn, tham số truy vấn và hash (fragment).", + "upload": "Tải lên", + "clear": "Xóa", + "panels": { + "input": "Đầu vào URL", + "output": "Kết quả" + }, + "placeholders": { + "input": "Dán URL (ví dụ https://example.com/path?foo=bar#section) hoặc host/đường dẫn (ví dụ example.com/path)…" + }, + "dropHere": "Thả tệp vào đây", + "charCount": "{count} ký tự", + "outputPlaceholder": "Các thành phần đã phân tích sẽ hiển thị ở đây…", + "fields": { + "protocol": "Giao thức", + "host": "Host", + "path": "Đường dẫn", + "hash": "Hash", + "queryParams": "Tham số truy vấn" + }, + "paramCount": "{count} tham số", + "noParams": "Không có tham số truy vấn.", + "paramKey": "Khóa", + "paramValue": "Giá trị", + "copyTitle": "Sao chép vào clipboard", + "copyParamTitle": "Sao chép dưới dạng key=value", + "download": { + "title": "Tải xuống JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "URL không hợp lệ. Hãy bao gồm hostname (example.com) và kiểm tra ký tự." + } + }, + "UuidGenerator": { + "title": "Trình tạo UUID / ULID", + "subtitle": "Tạo UUID (RFC 9562) hoặc ULID trong trình duyệt. Xuất hàng loạt tối đa {max} mỗi lần chạy.", + "format": "Định dạng", + "kindHints": { + "ulid": "Định danh 26 ký tự có thể sắp xếp, an toàn cho URL", + "uuid4": "Ngẫu nhiên (RFC 9562)", + "uuid7": "Theo thời gian, ms Unix + ngẫu nhiên", + "uuid1": "Dựa trên MAC/thời gian (RFC 9562)", + "uuid6": "Thời gian Gregory được sắp xếp lại", + "uuid3": "Băm MD5 của tên + namespace", + "uuid5": "Băm SHA‑1 của tên + namespace", + "nil": "UUID rỗng (toàn số 0, RFC 9562)" + }, + "countLabel": "Số lượng (1–{max})", + "nameLabel": "Tên (chuỗi đầu vào)", + "namePlaceholder": "vd. my.app/resource", + "bulkHint": "Chạy hàng loạt sẽ thêm #0, #1, … để mỗi ID khác nhau (v3/v5 là tất định).", + "namespace": "Namespace", + "namespaceOptions": { + "dns": "DNS (RFC 4122)", + "url": "URL (RFC 4122)", + "custom": "UUID tùy chỉnh" + }, + "customNamespaceLabel": "UUID namespace", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "Tạo", + "output": "Kết quả", + "lines": "{count} dòng", + "download": { + "title": "Tải .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "Sao chép tất cả", + "uppercaseToggle": "Xuất chữ hoa", + "hyphensToggle": "Hiển thị dấu gạch nối", + "copyJsonTitle": "Sao chép dạng mảng JSON", + "outputPlaceholder": "Nhấn \"Tạo\"…", + "errors": { + "invalidCustomNamespace": "Namespace tùy chỉnh phải là UUID hợp lệ.", + "missingName": "Cần có tên cho UUID v3 và v5.", + "unknown": "Không thể tạo ID." + } + }, + "SecretApiKeyGenerator": { + "title": "Trình tạo bí mật / khóa API", + "subtitle": "Tạo chuỗi ngẫu nhiên mật mã với độ dài và bảng chữ cái có thể cấu hình. Xuất hàng loạt tối đa {max} mỗi lần chạy. Đi cùng trình tạo UUID / ULID.", + "alphabetLabel": "Bảng chữ cái", + "alphabetHint": "Các ký tự trùng lặp bị loại bỏ. Mỗi ký hiệu được chọn đều bằng crypto.getRandomValues và lấy mẫu loại trừ (không lệch modulo).", + "alphabetCount": "{count} ký hiệu duy nhất", + "presetsLabel": "Cài sẵn", + "preset": { + "base64url": "Base64url", + "alphanumeric": "Chữ và số", + "hex": "Thập lục phân (chữ thường)", + "asciiPrintable": "ASCII có thể in" + }, + "lengthLabel": "Độ dài mỗi bí mật (1–{maxLen})", + "countLabel": "Số lượng (1–{maxBulk})", + "generate": "Tạo", + "output": "Đầu ra", + "lines": "{count} dòng", + "download": { + "title": "Tải .txt", + "filename": "bi-mat.txt" + }, + "copyTitle": "Sao chép tất cả", + "outputPlaceholder": "Nhấn \"Tạo\"…", + "entropy": "{bits} bit entropy", + "strength": { + "weak": "Yếu", + "fair": "Khá", + "strong": "Mạnh" + }, + "excludeAmbiguous": "Loại trừ ký tự dễ nhầm lẫn", + "excludeAmbiguousHint": "Xóa {chars} — các ký tự dễ đọc nhầm hoặc gây hại cho shell.", + "prefixLabel": "Tiền tố khóa (tùy chọn)", + "errors": { + "emptyAlphabet": "Thêm ít nhất một ký tự vào bảng chữ cái.", + "alphabetTooLong": "Sau khi loại trùng, bảng chữ cái tối đa {maxAlpha} ký tự.", + "lengthOutOfRange": "Độ dài phải từ 1 đến {maxLen}.", + "countOutOfRange": "Số lượng phải từ 1 đến {maxBulk}.", + "unknown": "Không thể tạo bí mật." + } + }, + "LoremIpsum": { + "title": "Bộ Tạo Lorem Ipsum", + "subtitle": "Tạo văn bản Latin giữ chỗ cho bố cục và mockup của bạn.", + "paragraphs": "Đoạn văn", + "sentences": "Câu", + "words": "Từ", + "count": "Số lượng", + "generate": "Tạo", + "copy": "Sao chép", + "startWith": "Bắt đầu với 'Lorem ipsum...'", + "outputPlaceholder": "Kết quả sẽ xuất hiện ở đây...", + "outputType": "Loại đầu ra", + "bulletList": "Danh sách gạch đầu dòng", + "htmlMarkup": "Đánh dấu HTML", + "htmlMarkupHint": "Bọc đầu ra trong đoạn văn, hoặc sử dụng danh sách cho chế độ gạch đầu dòng", + "output": "Đầu ra", + "download": "Tải xuống" + }, + "ColorPicker": { + "title": "Bộ chọn và chuyển đổi màu sắc", + "subtitle": "Chuyển đổi HEX, RGB và HSL; tạo bảng màu hài hòa. Tất cả xử lý diễn ra trong trình duyệt của bạn.", + "nativePicker": "Bộ chọn gốc", + "chooseColor": "Chọn màu", + "eyeDropper": "Ống nhỏ giọt", + "invalidHex": "Màu HEX không hợp lệ.", + "cssValues": "Giá trị CSS", + "palettes": "Bảng màu", + "copied": "Đã sao chép", + "clickToCopy": "Nhấp để sao chép", + "copyAllHex": "Sao chép tất cả HEX", + "copy": "Sao chép", + "copyColor": "Sao chép {color}", + "paletteShades": "Sắc độ", + "paletteComplementary": "Bổ sung", + "paletteTriadic": "Bộ ba", + "paletteAnalogous": "Tương tự", + "paletteSplit": "Bổ sung phân chia", + "paletteSquare": "Hình vuông" + }, + "ContrastChecker": { + "title": "Kiểm tra độ tương phản", + "subtitle": "Tính tỷ lệ tương phản WCAG 2.1 giữa chữ và nền. Kiểm tra AA và AAA cho chữ thường và chữ lớn. Chạy hoàn toàn trên trình duyệt.", + "foreground": "Chữ / tiền cảnh", + "background": "Nền", + "hexPlaceholder": "#RRGGBB", + "swap": "Hoán đổi màu", + "chooseForeground": "Chọn màu chữ / tiền cảnh", + "chooseBackground": "Chọn màu nền", + "eyeDropper": "Ống hút màu", + "ratio": "Tỷ lệ tương phản", + "copyRatio": "Sao chép tỷ lệ", + "copied": "Đã sao chép", + "previewHeading": "Xem trước", + "previewBody": "Do bách phục vị yêu cầu giáo dục thể chất và tinh thần minh bạch. Không có quý tộc, chỉ có bình đẳng — ai cũng xứng đáng tự do.", + "previewCaption": "Văn bản mẫu cỡ mặc định.", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — chữ thường (4,5:1)", + "levelAALarge": "AA — chữ lớn (3:1)", + "levelAAANormal": "AAA — chữ thường (7:1)", + "levelAAALarge": "AAA — chữ lớn (4,5:1)", + "pass": "Đạt", + "fail": "Không đạt", + "invalidHex": "Màu HEX không hợp lệ.", + "largeTextHint": "Chữ lớn: từ 18 pt thường hoặc từ 14 pt đậm (theo WCAG)." + }, + "DiffChecker": { + "title": "Kiểm tra sự khác biệt", + "subtitle": "So sánh từng dòng giữa hai văn bản cạnh nhau. Mọi thứ đều ở lại trong trình duyệt của bạn.", + "rowsCount": "{count} hàng", + "truncatedWarning": "Hiển thị {max} hàng đầu tiên.", + "limitExceeded": "Mỗi bên bị giới hạn {max} ký tự.", + "clearBoth": "Xóa cả hai", + "original": "Gốc", + "modified": "Đã sửa đổi", + "originalPlaceholder": "Dán văn bản gốc…", + "modifiedPlaceholder": "Dán văn bản đã sửa đổi…", + "comparisonLabel": "So sánh cạnh nhau", + "emptyState": "Nhập văn bản vào cả hai bảng để so sánh." + }, + "JsonSchemaGenerator": { + "title": "Trình tạo lược đồ JSON", + "subtitle": "Dán JSON hợp lệ để tạo lược đồ JSON hoặc mô hình có kiểu. Mọi thứ chạy trong trình duyệt.", + "languageLabel": "Ngôn ngữ đầu ra", + "inputLabel": "Đầu vào JSON", + "inputPlaceholder": "Dán JSON vào đây…", + "outputLabel": "Đầu ra đã tạo", + "outputPlaceholder": "Lược đồ hoặc kiểu đã tạo sẽ hiển thị ở đây…", + "parseError": "JSON không hợp lệ", + "clear": "Xóa", + "copy": "Sao chép", + "copied": "Đã sao chép vào bảng tạm", + "copyFailed": "Không thể sao chép", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Lược đồ JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Đang tải trình soạn thảo…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "Hình ảnh tới Base64", + "subtitle": "Thả hình ảnh, lấy URI dữ liệu hoặc chuỗi Base64 thô. Mọi thứ vẫn ở trong trình duyệt của bạn.", + "upload": "Tải hình ảnh lên", + "clear": "Thông thoáng", + "modes": { + "dataUri": "URI dữ liệu", + "rawString": "Cơ sở thô64" + }, + "panels": { + "imageInput": "Nhập hình ảnh", + "output": "Đầu ra Base64" + }, + "dropHere": "Thả hình ảnh ở đây", + "charCount": "{đếm} ký tự", + "placeholders": { + "uploadImage": "Kéo và thả hình ảnh hoặc nhấp vào tải lên để bắt đầu." + }, + "fileTooLarge": "Tệp quá lớn. Quá trình xử lý có thể đóng băng trình duyệt của bạn.", + "copyTitle": "Sao chép vào khay nhớ tạm", + "download": { + "title": "Tải văn bản xuống", + "filename": "hình ảnh-base64.txt" + } + }, + "ImageCompressor": { + "title": "Nén ảnh", + "subtitle": "Giảm kích thước đầu ra JPEG, PNG hoặc WebP cục bộ với chất lượng — không tải lên máy chủ.", + "upload": "Tải ảnh lên", + "clear": "Xóa", + "dropHere": "Thả ảnh vào đây", + "placeholders": { + "uploadImage": "Kéo thả ảnh hoặc bấm để chọn tệp." + }, + "fileTooLarge": "Tệp quá lớn (tối đa {maxMb} MB).", + "unsupportedFormat": "Định dạng không hỗ trợ. Dùng JPEG, PNG, WebP, GIF, AVIF hoặc BMP.", + "compressFailed": "Không thể nén ảnh này. Thử tệp hoặc định dạng khác.", + "panels": { + "original": "Gốc", + "compressed": "Xem trước đã nén" + }, + "outputFormat": "Định dạng đầu ra", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "Chất lượng", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG không mất dữ liệu trên trình duyệt; thanh chất lượng chỉ áp dụng cho JPEG và WebP.", + "sizes": { + "original": "Gốc: {size}", + "compressed": "Đã nén: {size}", + "saved": "nhỏ hơn {percent}%" + }, + "largerWarning": "Đầu ra lớn hơn đầu vào — thử định dạng hoặc chất lượng khác.", + "download": "Tải xuống", + "downloadFilename": "anh-da-nen", + "processing": "Đang nén…", + "emptyCompressed": "Xem trước đã nén hiển thị tại đây." + }, + "CssGradientBuilder": { + "title": "Trình tạo độ dốc CSS", + "subtitle": "Trình tạo gradient CSS trực quan với khả năng kiểm soát góc, điểm dừng màu và sao chép CSS chỉ bằng một cú nhấp chuột.", + "preview": "Xem trước độ dốc", + "settings": "Cài đặt", + "type": "Kiểu", + "linear": "tuyến tính", + "radial": "Xuyên tâm", + "angle": "Góc", + "stops": "Điểm dừng màu", + "color": "Màu sắc", + "position": "Chức vụ (%)", + "addStop": "Thêm điểm dừng", + "deleteStop": "Xóa điểm dừng", + "output": "Đầu ra CSS", + "copy": "Sao chép CSS", + "copied": "Đã sao chép!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": "Trình tạo .gitignore", + "subtitle": "Tạo các tệp .gitignore đã hợp nhất ngay lập tức cho một tổ hợp hoặc ngăn xếp công nghệ cụ thể.", + "searchPlaceholder": "Ngăn xếp tìm kiếm (ví dụ: Node, Python, macOS)...", + "noResults": "Không tìm thấy ngăn xếp nào.", + "selectedStacks": "Ngăn xếp đã chọn", + "output": "Xem trước đầu ra", + "copy": "Sao chép vào Clipboard", + "copied": "Đã sao chép!", + "download": "Tải xuống .gitignore", + "downloading": "Đang tải xuống...", + "errorFetching": "Không tìm nạp được danh sách hàng đầu.", + "errorGenerating": "Không tạo được .gitignore từ các ngăn xếp đã chọn.", + "generating": "Đang tạo...", + "emptyState": "Chọn một hoặc nhiều nhóm công nghệ để tạo .gitignore của bạn." + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Tải CSV hoặc Excel, chỉnh JSON, rồi tải CSV hoặc XLSX. Chỉ dữ liệu dạng bảng; ô ngày trong Excel thành dấu thời gian ISO trong JSON.", + "dropTitle": "Thả file CSV hoặc Excel", + "dropHint": "Với .xlsx / .xls dùng sheet đầu tiên. CSV cần có dòng tiêu đề.", + "chooseFile": "Chọn file", + "jsonLabel": "JSON (mảng đối tượng)", + "jsonPlaceholder": "Dán mảng JSON đối tượng hoặc nhập CSV / Excel…", + "loadSample": "Tải mẫu", + "actions": { + "format": "Định dạng JSON", + "clear": "Xóa", + "copy": "Sao chép", + "copied": "Đã sao chép", + "downloadCsv": "Tải CSV", + "downloadXlsx": "Tải Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "Dữ liệu", + "toastLoadedCsv": "Đã tải CSV vào trình sửa", + "toastLoadedExcel": "Đã tải Excel vào trình sửa", + "toastFormatted": "Đã định dạng JSON", + "toastExportedCsv": "Đã tải CSV", + "toastExportedXlsx": "Đã tải Excel", + "toastCopied": "Đã sao chép vào clipboard", + "footnote": "Mọi thứ chạy trong trình duyệt. JSON phải là mảng đối tượng không rỗng. Giá trị lồng nhau được chuỗi hóa JSON trong ô CSV.", + "errors": { + "unsupportedFile": "Dùng file .csv, .xlsx hoặc .xls.", + "parseFailed": "Không thể phân tích file này.", + "invalidJson": "JSON không hợp lệ", + "exportFailed": "Xuất thất bại", + "copyFailed": "Không thể sao chép" + } + }, + "SnippetManager": { + "title": "Trình quản lý đoạn mã", + "subtitle": "Lưu đoạn mã trong trình duyệt hoặc đăng nhập để đồng bộ với tài khoản. Chuyển giữa xem và sửa, sao chép một cú nhấp, chọn ngôn ngữ hoặc tự động nhận diện, và định dạng JSON, SQL hoặc mã được Monaco hỗ trợ.", + "snippets": "Đoạn mã", + "newSnippet": "Đoạn mới", + "newSnippetTitle": "Đoạn mới", + "defaultSnippetTitle": "Chào mừng", + "defaultSnippetCode": "// Chào mừng đến với đoạn mã\n// Thử xem so với sửa, sao chép, định dạng và ngôn ngữ tự động\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Tìm đoạn mã…", + "searchAria": "Tìm đoạn mã", + "emptySearch": "Không có đoạn mã khớp tìm kiếm.", + "snippetTitle": "Tiêu đề", + "snippetTitlePlaceholder": "Tên đoạn", + "language": "Ngôn ngữ", + "detectedHint": "Tô sáng dạng {lang}", + "badgeAuto": "Tự động · {lang}", + "modeView": "Xem", + "modeEdit": "Sửa", + "copy": "Sao chép", + "copied": "Đã sao chép", + "format": "Định dạng", + "cancel": "Hủy", + "deleteTitle": "Xóa đoạn mã?", + "deleteDescription": "“{title}” sẽ bị xóa khỏi trình duyệt này.", + "deleteConfirm": "Xóa", + "editorAria": "Trình sửa mã đoạn", + "editorLoading": "Đang tải trình sửa…", + "toastCopied": "Đã sao chép vào bảng tạm", + "toastCopyFailed": "Không thể sao chép", + "toastNothingToCopy": "Không có gì để sao chép", + "toastFormatted": "Đã định dạng", + "toastFormatFailed": "Không thể định dạng", + "toastFormatJsonInvalid": "JSON không hợp lệ — sửa cú pháp trước", + "toastFormatUnsupported": "Không có bộ định dạng cho ngôn ngữ này — thử JSON hoặc SQL hoặc chế độ sửa", + "toastDeleted": "Đã xóa đoạn mã", + "toastLoadFailed": "Không thể tải đoạn mã từ máy chủ.", + "toastSyncFailed": "Không thể đồng bộ với máy chủ.", + "languages": { + "auto": "Tự động nhận diện", + "plaintext": "Văn bản thuần", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "Máy khách SQL", + "dialogTitle": "Kết nối SQL", + "mobileSheetTitle": "Trình duyệt SQL", + "newTabLabel": "Tab truy vấn mới", + "vaultLockedTitle": "Kho bị khóa", + "vaultLockedDesc": "Mở khóa kho để truy cập các kết nối đã lưu.", + "heroTitle": "Máy khách SQL", + "heroDesc": "Kết nối với PostgreSQL, MySQL hoặc MariaDB. Thông tin đăng nhập được mã hóa trong trình duyệt.", + "heroAddConnection": "Thêm kết nối", + "selectTableTitle": "Chọn bảng hoặc mở tab truy vấn", + "selectTableDesc": "Nhấp vào bảng trong thanh bên hoặc dùng ⌘↩.", + "toastLoadFail": "Không thể tải kết nối." + }, + "connection": { + "titleNew": "Kết nối SQL mới", + "titleEdit": "Chỉnh sửa kết nối", + "labelDbType": "Loại cơ sở dữ liệu", + "labelName": "Tên kết nối", + "placeholderName": "CSDL sản xuất của tôi", + "labelHostPort": "Host và Cổng", + "labelDatabase": "Cơ sở dữ liệu", + "placeholderDatabase": "mydb", + "labelUsername": "Tên người dùng", + "labelPassword": "Mật khẩu", + "labelSsl": "Bật SSL / TLS", + "sslDesc": "Mã hóa kết nối đến máy chủ", + "securityNote": "Thông tin đăng nhập được mã hóa AES-256 trong trình duyệt trước khi lưu", + "btnTest": "Kiểm tra kết nối", + "btnSave": "Lưu và kết nối", + "btnUpdate": "Cập nhật", + "btnCancelEdit": "Hủy chỉnh sửa", + "savedConnections": "Kết nối đã lưu", + "emptySaved": "Các kết nối đã lưu sẽ hiện ở đây", + "confirmDelete": "Xóa kết nối này?", + "toastFillFields": "Điền host và cơ sở dữ liệu trước khi kiểm tra.", + "toastConnectOk": "Đã kết nối! {version}", + "toastConnectFail": "Kết nối thất bại: {message}", + "toastUnlockVault": "Mở khóa kho trước.", + "toastFieldsRequired": "Host và cơ sở dữ liệu là bắt buộc.", + "toastUpdated": "Đã cập nhật kết nối.", + "toastSaved": "Đã lưu kết nối.", + "toastSaveFail": "Lưu thất bại: {message}", + "toastDeleted": "Đã xóa kết nối.", + "toastDeleteFail": "Xóa kết nối thất bại." + }, + "sidebar": { + "title": "Trình duyệt SQL", + "tooltipAddConnection": "Thêm kết nối", + "tooltipRefreshSchema": "Làm mới lược đồ", + "noConnections": "Chưa có kết nối nào được lưu", + "toastSchemaFail": "Không thể tải lược đồ: {message}" + }, + "editor": { + "btnRun": "Chạy", + "executing": "Đang thực thi truy vấn…", + "emptyTitle": "Chạy truy vấn để xem kết quả", + "emptyHint": "Nhấn ⌘↩ hoặc nhấp Chạy", + "toastNoConnection": "Không có kết nối hoạt động." + }, + "results": { + "filterPlaceholder": "Lọc kết quả…", + "rowCount": "{count} hàng", + "rowCountFiltered": "{filtered} / {total} hàng", + "exportCsv": "CSV", + "noRows": "Không có hàng nào được trả về" + } + }, + "QrCodeGenerator": { + "title": "Trình tạo mã QR", + "subtitle": "Chuyển mọi văn bản hoặc URL thành mã QR PNG. Mọi thứ được kết xuất cục bộ trong trình duyệt.", + "contentLabel": "Nội dung", + "contentPlaceholder": "https://example.com, văn bản thuần, chuỗi Wi‑Fi, vCard…", + "styleLabel": "Kiểu", + "dotTypeLabel": "Hình dạng chấm", + "dotTypeSquare": "Vuông", + "dotTypeDots": "Chấm tròn", + "dotTypeRounded": "Bo tròn", + "dotTypeExtraRounded": "Bo tròn nhiều", + "dotTypeClassy": "Thanh lịch", + "dotTypeClassyRounded": "Thanh lịch bo tròn", + "cornerSquareLabel": "Ô vuông góc", + "cornerSquareDot": "Chấm", + "cornerSquareSquare": "Vuông", + "cornerSquareExtraRounded": "Bo tròn nhiều", + "cornerDotLabel": "Chấm góc", + "cornerDotDot": "Chấm", + "cornerDotSquare": "Vuông", + "eccLabel": "Sửa lỗi", + "eccL": "Thấp (~7%)", + "eccM": "Trung bình (~15%)", + "eccQ": "Tứ phân vị (~25%)", + "eccH": "Cao (~30%)", + "marginLabel": "Lề (module)", + "widthLabel": "Chiều rộng ảnh", + "widthEffective": "Kết xuất ở {px} px", + "darkColor": "Module tối", + "lightColor": "Module sáng", + "resetColors": "Đặt lại màu", + "logoLabel": "Logo trung tâm", + "logoMarginLabel": "Lề", + "uploadLogo": "Tải ảnh lên", + "removeLogo": "Xóa logo", + "downloadPng": "Tải PNG", + "downloadSvg": "Tải SVG", + "copyImage": "Sao chép ảnh", + "copied": "Đã sao chép", + "clear": "Xóa", + "previewAlt": "Xem trước mã QR", + "emptyHint": "Nhập hoặc dán nội dung để xem trước.", + "filename": "ma-qr.png", + "errors": { + "generationFailed": "Không tạo được mã QR. Thử văn bản ngắn hơn hoặc mức sửa lỗi thấp hơn.", + "copyUnsupported": "Trình duyệt không hỗ trợ sao chép ảnh vào clipboard.", + "copyFailed": "Không sao chép được vào clipboard." + } + }, + "IpSubnetCalculator": { + "title": "Máy tính IP / mạng con", + "subtitle": "Chi tiết CIDR IPv4 và IPv6 trong trình duyệt—không gửi yêu cầu ra ngoài.", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "Mạng", + "placeholderIpv4": "10.0.0.0/24 hoặc 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 hoặc 2001:db8::1/64", + "hintIpv4": "Dùng CIDR (vd. /24) hoặc IPv4 kèm mạng con dạng chấm.", + "hintIpv6": "Gồm độ dài tiền tố (vd. /64). Bỏ qua thì mặc định /128.", + "copyValue": "Sao chép giá trị", + "rows": { + "cidr": "CIDR", + "network": "Địa chỉ mạng", + "netmask": "Mặt nạ mạng con", + "wildcard": "Mặt nạ wildcard", + "broadcast": "Broadcast", + "firstHost": "Máy chủ đầu", + "lastHost": "Máy chủ cuối", + "totalAddresses": "Tổng địa chỉ", + "usableHosts": "Máy chủ khả dụng", + "prefixLength": "Độ dài tiền tố (bit)", + "networkIpv6": "Địa chỉ mạng", + "lastIpv6": "Địa chỉ cuối trong dải", + "totalAddressesIpv6": "Địa chỉ trong mạng con" + }, + "usableNote": { + "pointToPoint": "Liên kết /31 điểm-điểm dùng cả hai địa chỉ làm máy chủ (RFC 3021).", + "singleHost": "/32 là một máy chủ." + }, + "ipv6RangeHint": { + "normal": "Hiển thị toàn bộ dải phạm vi toán học; quy tắc bộ định tuyến có thể khác.", + "single": "/128 là một địa chỉ.", + "full": "/0 bao phủ toàn IPv6." + }, + "errors": { + "invalidIpv4": "Mạng IPv4 không hợp lệ. Thử CIDR hoặc địa chỉ + mặt nạ.", + "invalidIpv6": "Mạng IPv6 không hợp lệ. Dùng địa chỉ/tiền tố, vd. 2001:db8::/48." + } + }, + "HashGenerator": { + "title": "Trình tạo hash", + "subtitle": "Bản tóm lược MD5, SHA-1 và SHA-2 cho văn bản hoặc tệp; bcrypt cho mật khẩu (nhập văn bản). Tất cả chạy cục bộ trong trình duyệt.", + "algorithmLabel": "Thuật toán", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "Văn bản", + "modeFile": "Tệp", + "inputLabelText": "Đầu vào", + "placeholderText": "Nhập hoặc dán văn bản…", + "chooseFile": "Chọn tệp", + "clearFile": "Xóa", + "fileHint": "Chỉ đọc tệp trong bộ nhớ; tối đa {maxMb} MB. Không tải lên.", + "outputLabel": "Bản tóm tắt (hex)", + "copyHash": "Sao chép bản tóm tắt", + "copied": "Đã sao chép", + "byteCount": "{count, plural, other {# byte}}", + "charCount": "{count, plural, other {# ký tự}}", + "emptyHint": "Nhập văn bản hoặc chọn tệp.", + "securityNote": "MD5 và SHA-1 không khuyến nghị cho mục đích bảo mật mới; dùng SHA-256 hoặc SHA-512.", + "errors": { + "fileTooLarge": "Tệp lớn hơn {maxMb} MB.", + "fileReadFailed": "Không đọc được tệp." + }, + "bcryptRoundsLabel": "Chi phí (vòng)", + "bcryptRoundsHint": "Giá trị cao hơn chậm hơn và chống brute-force tốt hơn. Mặc định thường là 10.", + "fileTabDisabledTitle": "bcrypt chỉ băm mật khẩu từ văn bản, không phải tệp tùy ý.", + "placeholderBcrypt": "Nhập hoặc dán mật khẩu để băm…", + "outputLabelBcrypt": "Băm bcrypt", + "copyBcrypt": "Sao chép băm", + "emptyHintBcrypt": "Nhập mật khẩu để tính băm bcrypt.", + "securityNoteBcrypt": "Mỗi đầu ra bcrypt có muối ngẫu nhiên; cùng mật khẩu cho chuỗi khác nhau mỗi lần. Điều này là bình thường. Dùng chi phí cao trong môi trường production.", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "Sân chơi mã hóa", + "subtitle": "AES-GCM trong trình duyệt: mã hóa thành gói JSON bằng cụm mật khẩu hoặc khóa thô, rồi giải mã cục bộ. Mang tính học tập và đôi khi thực dụng — không tải dữ liệu lên.", + "tabEncrypt": "Mã hóa", + "tabDecrypt": "Giải mã", + "keyModeLabel": "Nguyên liệu khóa", + "modePassphrase": "Cụm mật khẩu (PBKDF2 + AES-GCM)", + "modeRawKey": "Khóa AES thô (hex hoặc Base64)", + "aesBitsLabel": "Độ dài khóa AES", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "Mã hóa khóa thô", + "encodingAuto": "Tự động (hex nếu trông giống hex, không thì Base64)", + "encodingHex": "Hex", + "encodingBase64": "Base64", + "rawKeyLabel": "Khóa thô", + "rawKeyPlaceholder": "Khóa 32 byte dưới dạng 64 ký tự hex hoặc Base64 cho AES-256 (độ dài phụ thuộc kích thước AES ở trên).", + "rawKeyPlaceholderDecrypt": "Chỉ cần khi gói có keyDerivation \"none\".", + "passphraseLabel": "Cụm mật khẩu", + "passphrasePlaceholder": "Cụm mật khẩu chỉ dùng trong trình duyệt để dẫn xuất khóa.", + "passphrasePlaceholderDecrypt": "Chỉ cần khi gói dùng PBKDF2-SHA256.", + "pbkdf2IterLabel": "Số vòng PBKDF2", + "pbkdf2Hint": "Giới hạn từ 10.000 đến 2.000.000. Mặc định giống cài đặt kho mật thông dụng.", + "plaintextLabel": "Văn bản thuần", + "plaintextPlaceholder": "Nhập hoặc dán nội dung cần mã hóa…", + "bundleInputLabel": "Gói đã mã hóa (JSON)", + "bundlePlaceholder": "Dán JSON từ Mã hóa (v, alg, ivB64, cipherB64, …).", + "bundleOutputLabel": "Gói đã mã hóa", + "plaintextOutputLabel": "Văn bản thuần", + "encryptButton": "Mã hóa", + "decryptButton": "Giải mã", + "copyBundle": "Sao chép JSON", + "copyPlaintext": "Sao chép văn bản thuần", + "copied": "Đã sao chép", + "emptyBundleHint": "JSON đã mã hóa hiển thị ở đây sau khi mã hóa.", + "emptyDecryptHint": "Văn bản đã giải mã hiển thị ở đây.", + "decryptKeyHint": "Dùng khóa thô nếu keyDerivation là \"none\"; dùng cụm mật khẩu nếu là PBKDF2-SHA256. Gói lưu muối, số vòng, IV và độ dài AES.", + "securityNote": "Khóa và văn bản thuần không rời khỏi trình duyệt. Bí mật production nên dùng công cụ chuyên dụng.", + "errors": { + "emptyKey": "Nhập khóa thô khớp kích thước AES.", + "emptyPassphrase": "Nhập cụm mật khẩu.", + "badKeyMaterial": "Không đọc được khóa dưới dạng hex hoặc Base64.", + "wrongKeyLength": "Độ dài khóa không khớp AES (16 / 24 / 32 byte).", + "invalidJson": "JSON không hợp lệ.", + "invalidBundle": "JSON này không phải gói mã hóa được hỗ trợ.", + "decryptFailed": "Giải mã thất bại — sai bí mật, gói hỏng hoặc tham số không khớp.", + "encryptFailed": "Mã hóa thất bại. Kiểm tra Web Crypto và đầu vào." + } + }, + "HmacGenerator": { + "title": "Trình tạo HMAC", + "subtitle": "Để ký webhook và kiểm thử tích hợp API. HMAC-SHA1/256/384/512 dạng hex hoặc Base64—tính cục bộ trong trình duyệt.", + "secretLabel": "Khóa bí mật", + "messageLabel": "Thông điệp / payload", + "placeholderSecret": "Dán bí mật ký…", + "placeholderMessage": "Dán nội dung thô hoặc chuỗi cần xác thực (thường đúng byte nội dung webhook).", + "algorithmLabel": "Thuật toán HMAC", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "Định dạng đầu ra", + "formatHex": "Hex (chữ thường)", + "formatBase64": "Base64", + "outputLabel": "Chữ ký", + "copyOutput": "Sao chép chữ ký", + "copied": "Đã sao chép", + "byteCountHint": "{count, plural, other {# byte}} (UTF-8)", + "emptyHint": "Nhập khóa bí mật để tính HMAC.", + "securityNote": "Không bao giờ chia sẻ bí mật ký. Trang này không tải dữ liệu lên—mọi thứ chạy trong trình duyệt.", + "errors": { + "cryptoFailed": "Không thể tính HMAC. Kiểm tra hỗ trợ Web Crypto và thử lại.", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "Trình tạo mã TOTP / 2FA", + "subtitle": "Dán bí mật Base32 và lấy mã sáu chữ số RFC 6238 hiện tại (SHA-1, 30 giây). Chạy cục bộ trong trình duyệt—hữu ích khi thử đăng nhập hoặc MFA.", + "secretLabel": "Bí mật (Base32)", + "placeholderSecret": "Dán khóa bí mật (chữ A–Z và số 2–7, như khi thiết lập ứng dụng xác thực)…", + "codeLabel": "Mã hiện tại", + "copyCode": "Sao chép mã", + "copied": "Đã sao chép", + "nextIn": "Làm mới sau {seconds} giây", + "standardHint": "Bước thời gian 30 giây, 6 chữ số, HMAC-SHA-1 (tương thích Google Authenticator).", + "digitsLabel": "Số chữ số", + "periodLabel": "Chu kỳ", + "algorithmLabel": "Thuật toán", + "periodSeconds": "{seconds} giây", + "emptyHint": "Dán bí mật để tạo mã sáu chữ số.", + "invalidHint": "Sửa bí mật để xem mã.", + "securityNote": "Coi như mật khẩu. Bí mật không rời khỏi trình duyệt; không tải dữ liệu lên.", + "importLabel": "Nhập từ URI otpauth://", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "Nhập", + "qrSectionTitle": "Mã QR cấp phát", + "qrHint": "Quét bằng ứng dụng xác thực (Google Authenticator, 1Password, Aegis…) để thêm khóa bí mật này. Mã QR mã hóa khóa bí mật — hãy đối xử với nó như mật khẩu.", + "accountLabel": "Tài khoản", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "Nhà phát hành (tùy chọn)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "Nhập khóa bí mật hợp lệ để tạo mã QR.", + "errors": { + "invalidSecret": "Đây không phải bí mật Base32 hợp lệ.", + "cryptoFailed": "Không thể tính TOTP. Kiểm tra Web Crypto và thử lại.", + "invalidUri": "Đây không phải là URI otpauth:// TOTP hợp lệ.", + "unsupportedUri": "URI này sử dụng các giá trị mà công cụ không hỗ trợ (số chữ số phải là 6 hoặc 8; chu kỳ 15, 30 hoặc 60 giây)." + } + }, + "MarkdownPreview": { + "title": "Xem trước Markdown & HTML", + "subtitle": "Trình kết xuất Markdown trực tiếp với xuất HTML và chuyển đổi HTML → Markdown. Tất cả chạy trong trình duyệt của bạn.", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Đầu vào Markdown", + "htmlInputLabel": "Đầu vào HTML", + "previewLabel": "Xem trước", + "htmlOutputLabel": "Nguồn HTML", + "mdOutputLabel": "Đầu ra Markdown", + "inputPlaceholder": "# Xin chào\\n\\nNhập **markdown** của bạn tại đây…", + "htmlInputPlaceholder": "Dán HTML của bạn tại đây…", + "copyHtml": "Sao chép HTML", + "copyMarkdown": "Sao chép Markdown", + "exportHtml": "Xuất HTML", + "copied": "Đã sao chép", + "charCount": "{count, plural, other {# ký tự}}", + "emptyHint": "Bắt đầu nhập Markdown để xem bản xem trước trực tiếp và đầu ra HTML.", + "emptyHintHtmlToMd": "Dán HTML để chuyển đổi sang Markdown.", + "securityNote": "Tất cả chạy cục bộ trong trình duyệt của bạn. Không có gì được tải lên." + }, + "NumberBaseConverter": { + "title": "Chuyển đổi cơ số", + "subtitle": "Chuyển đổi số nguyên giữa cơ số 2–36. Hỗ trợ tiền tố tùy chọn 0x / 0b / 0o. Tất cả chạy cục bộ trong trình duyệt.", + "inputBaseLabel": "Từ cơ số", + "outputBaseLabel": "Sang cơ số", + "inputLabel": "Số nhập", + "placeholderInput": "Nhập số nguyên theo cơ số đầu vào đã chọn…", + "outputLabel": "Giá trị đã chuyển", + "copyOutput": "Sao chép kết quả", + "copied": "Đã sao chép", + "swap": "Hoán đổi cơ số", + "byteCountHint": "{count, plural, other {# byte}} (độ dài UTF-8)", + "emptyHint": "Nhập số để chuyển đổi.", + "note": "Chữ số trên 9 dùng A–Z (không phân biệt hoa thường). Khoảng trắng và gạch dưới bị bỏ qua.", + "errors": { + "invalidNumber": "Giá trị không hợp lệ với cơ số đầu vào đã chọn." + } + }, + "FormatConverter": { + "title": "Chuyển đổi định dạng", + "subtitle": "Chuyển đổi giữa JSON, YAML, TOML và XML theo bất kỳ hướng nào. Tất cả 12 tổ hợp. Mọi thứ chạy trên trình duyệt của bạn.", + "fromLabel": "Từ", + "toLabel": "Đến", + "swap": "Hoán đổi", + "swapTitle": "Hoán đổi định dạng và sử dụng đầu ra làm đầu vào", + "copy": "Sao chép", + "copied": "Đã sao chép", + "charCount": "{count, plural, other {# ký tự}}", + "emptyHint": "Dán hoặc nhập dữ liệu đầu vào ở bên trái để xem đầu ra đã chuyển đổi.", + "localNote": "Mọi thứ chạy cục bộ trên trình duyệt của bạn. Không có gì được tải lên." + }, + "MimeTypeLookup": { + "title": "Tra cứu loại MIME", + "subtitle": "Tra cứu loại MIME (media type) theo phần mở rộng hoặc tên tệp đầy đủ. Mọi thứ chạy cục bộ trên trình duyệt của bạn.", + "inputLabel": "Phần mở rộng hoặc tên tệp", + "placeholder": "ví dụ .png, package.json, photo.jpeg", + "hint": "Mẹo: dán phần mở rộng (có hoặc không có dấu chấm) hoặc tên tệp đầy đủ.", + "clear": "Xóa", + "exampleFilename": "Dùng tên tệp ví dụ", + "exampleExtension": "Dùng phần mở rộng ví dụ", + "resultLabel": "Kết quả", + "normalizedLabel": "Đầu vào đã chuẩn hóa:", + "mimeTypeLabel": "Loại MIME", + "contentTypeLabel": "Header Content-Type", + "charsetLabel": "Bảng mã", + "copyMimeType": "Sao chép loại MIME", + "copy": "Sao chép", + "copied": "Đã sao chép", + "emptyHint": "Nhập phần mở rộng hoặc tên tệp để tra cứu loại MIME.", + "notFound": "Không tìm thấy loại MIME cho đầu vào này.", + "localNote": "Mọi thứ chạy cục bộ trên trình duyệt của bạn. Không có gì được tải lên." + }, + "UserAgentParser": { + "title": "Trình phân tích User-Agent", + "subtitle": "Dán chuỗi User-Agent (UA) để xem phân tích trình duyệt, hệ điều hành, engine, thiết bị và CPU. Mọi thứ chạy cục bộ trên trình duyệt của bạn.", + "inputLabel": "Chuỗi User-Agent", + "placeholder": "Dán User-Agent của bạn vào đây…", + "hint": "Mẹo: bạn có thể sao chép UA từ header yêu cầu, log hoặc devtools của trình duyệt.", + "clear": "Xóa", + "exampleDesktop": "Dùng ví dụ máy tính", + "exampleMobile": "Dùng ví dụ di động", + "resultLabel": "Phân tích", + "browserLabel": "Trình duyệt", + "osLabel": "Hệ điều hành", + "engineLabel": "Engine", + "deviceTypeLabel": "Loại thiết bị", + "deviceVendorLabel": "Hãng thiết bị", + "deviceModelLabel": "Mẫu thiết bị", + "cpuArchLabel": "Kiến trúc CPU", + "copyJson": "Sao chép JSON", + "copy": "Sao chép", + "copied": "Đã sao chép", + "emptyHint": "Dán chuỗi User-Agent để xem phân tích.", + "localNote": "Mọi thứ chạy cục bộ trên trình duyệt của bạn. Không có gì được tải lên." + }, + "MockDataGenerator": { + "title": "Trình tạo dữ liệu giả", + "subtitle": "Định nghĩa cột, chọn kiểu dữ liệu và xuất bộ dữ liệu cho seed, Postman hoặc kiểm thử. Tối đa {max} dòng, tất cả trong trình duyệt — không tải lên.", + "mockarooLink": "Quy trình kiểu Mockaroo", + "rowsLabel": "Số dòng", + "rowsHint": "Tối đa {max} dòng mỗi lần tạo để tab mượt.", + "formatLabel": "Định dạng", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "INSERT SQL", + "xml": "XML" + }, + "tableNameLabel": "Tên bảng SQL", + "presetsLabel": "Bản mẫu", + "presetPlaceholder": "Tải bản mẫu…", + "presetDefault": "Khởi đầu (người dùng)", + "presetUsers": "Người dùng & vai trò", + "presetProducts": "SKU thương mại điện tử", + "presetApiLog": "Nhật ký yêu cầu HTTP", + "generate": "Tạo", + "warningDuplicateNames": "Trùng tên cột: cột sau ghi đè cột trước trong đối tượng JSON. Hãy đổi tên.", + "schemaHeading": "Lược đồ", + "addField": "Thêm cột", + "removeField": "Xóa cột", + "duplicateField": "Nhân đôi cột", + "colName": "Tên", + "colType": "Kiểu", + "colOptions": "Tùy chọn", + "colBlank": "Rỗng %", + "optMin": "tối thiểu", + "optMax": "tối đa", + "optDecimals": "thập phân", + "optDateFrom": "từ ngày", + "optDateTo": "đến ngày", + "optValues": "a, b, c", + "optStep": "bước", + "optRowBase": "cơ sở", + "output": "Đầu ra", + "outputPlaceholder": "Nhấn Tạo để sinh dữ liệu…", + "copy": "Sao chép", + "download": "Tải xuống", + "copied": "Đã sao chép", + "groups": { + "personal": "Cá nhân", + "location": "Địa điểm", + "internetApi": "Internet & API", + "numbersIds": "Số & ID", + "dateTime": "Ngày & giờ", + "textDesign": "Văn bản & thiết kế", + "custom": "Tùy chỉnh" + }, + "fieldTypes": { + "first_name": "Tên", + "last_name": "Họ", + "full_name": "Họ và tên", + "email": "Email", + "username": "Tên người dùng", + "password": "Mật khẩu", + "phone": "Điện thoại", + "company": "Công ty", + "job_title": "Chức danh", + "avatar_url": "URL ảnh đại diện", + "city": "Thành phố", + "country": "Quốc gia", + "country_code": "Mã quốc gia (ISO)", + "street_address": "Địa chỉ", + "zip_code": "Mã bưu chính", + "latitude": "Vĩ độ", + "longitude": "Kinh độ", + "url": "URL", + "domain": "Tên miền", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "Số nguyên", + "float": "Số thập phân", + "boolean": "Luận lý", + "uuid": "UUID", + "credit_card": "Thẻ (thử nghiệm)", + "semver": "SemVer", + "row_index": "Chỉ số dòng", + "sequence": "Dãy số", + "date": "Ngày", + "datetime": "Ngày giờ", + "unix_timestamp": "Dấu thời gian Unix", + "timezone": "Múi giờ", + "word": "Từ", + "sentence": "Câu", + "paragraph": "Đoạn văn", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "Mã tiền tệ", + "language_code": "Thẻ ngôn ngữ", + "custom_list": "Danh sách tùy chỉnh" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "Tối ưu SVG", + "subtitle": "Dán SVG, thu gọn bằng SVGO ngay trên trình duyệt và so sánh kích thước UTF-8 trước/sau.", + "inputLabel": "Đầu vào", + "outputLabel": "Đã tối ưu", + "placeholder": "Dán đoạn hoặc toàn bộ tệp…", + "outputPlaceholder": "SVG đã tối ưu hiển thị tại đây…", + "chars": "{count} ký tự • {bytes} byte (UTF-8)", + "preview": "Xem trước", + "localNote": "Xử lý hoàn toàn cục bộ trên trình duyệt. Không tải lên máy chủ.", + "stats": { + "title": "Kích thước (UTF-8)", + "before": "Trước", + "after": "Sau", + "savedBytes": "Tiết kiệm {count} byte ({percent}%)", + "noChange": "Không giảm — đã tối thiểu hoặc đầu ra trống." + }, + "actions": { + "clear": "Xóa", + "sample": "Tải ví dụ", + "copy": "Sao chép", + "copied": "Đã sao chép", + "download": "Tải xuống" + }, + "errors": { + "optimizeFailed": "Không thể tối ưu SVG này." + } + }, + "UnitConverter": { + "title": "Chuyển đổi đơn vị", + "subtitle": "Chuyển đổi giữa 323 đơn vị trong 43 danh mục", + "category": "Danh mục", + "from": "Từ", + "to": "Sang", + "value": "Giá trị", + "result": "Kết quả", + "swapUnits": "Hoán đổi đơn vị", + "placeholder": "Nhập giá trị", + "copyResult": "Sao chép kết quả", + "searchCategory": "Tìm danh mục...", + "selectCategory": "Chọn danh mục", + "noResults": "Không tìm thấy danh mục." + }, + "DockerComposeGenerator": { + "title": "Trình tạo Docker Compose", + "subtitle": "Chọn cơ sở dữ liệu, cache, hàng đợi tin nhắn, reverse proxy và quan sát — sao chép docker-compose.yml sẵn sàng cho phát triển cục bộ.", + "searchPlaceholder": "Tìm (postgres, redis, kafka, nginx, grafana…)", + "selectedLabel": "Dịch vụ", + "selectedCount": "{count, plural, other {# dịch vụ}}", + "outputTitle": "docker-compose.yml", + "copy": "Sao chép", + "copied": "Đã sao chép!", + "download": "Tải xuống", + "emptyState": "Chọn một hoặc nhiều dịch vụ để tạo YAML Compose.", + "hintPorts": "Cổng máy chủ mặc định giảm xung đột; chỉnh nếu Docker báo cổng đã dùng. Đổi mật khẩu demo trước môi trường dùng chung.", + "categories": { + "databases": "Cơ sở dữ liệu & lưu trữ", + "cache": "Cache & trong bộ nhớ", + "queues": "Hàng đợi & tin nhắn", + "search": "Tìm kiếm & nhật ký", + "storage": "Lưu trữ đối tượng", + "proxy": "Reverse proxy & ingress", + "observability": "Số liệu & tracing", + "mail": "Email (phát triển cục bộ)", + "admin": "Giao diện quản trị & BI" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "Xóa" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey (tương thích Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda (tương thích Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO (tương thích S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit (bắt SMTP)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault (máy chủ dev)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg (Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "Trượt các ô và hợp nhất chúng để đạt đến năm 2048", + "score": "Điểm", + "best": "Trân trọng", + "newGame": "Trò chơi Same", + "howToPlay": "Sử dụng các phím mũi tên (hoặc vuốt trên thiết bị di động) để di chuyển các ô. Các ô có cùng số hợp nhất. Đạt đến 2048 để giành chiến thắng!", + "youWin": "Bạn đã đạt đến năm 2048!", + "keepGoing": "Hãy phát huy", + "gameOver": "Kết thúc Trò chơi", + "tryAgain": "Thử lại", + "gameOverMessage": "Không còn động thái nào nữa.", + "undo": "Hoàn tác" + }, + "Sudoku": { + "title": "Sudoku", + "stagesCompleted": "đã hoàn thành các giai đoạn", + "time": "Thời gian", + "best": "Trân trọng", + "difficulty": { + "easy": "Dễ", + "medium": "Trung bình", + "hard": "Khó" + }, + "stage": "Giai đoạn", + "solved": "Đã được chấp nhận", + "score": "Điểm", + "nextStage": "GIAI ĐOẠN TIẾP THEO", + "replay": "Phát lại", + "erase": "Xóa", + "howToPlay": "Nhấp vào một ô, sau đó nhấn vào một số hoặc nhấn 1–9. Backspace hoặc Delete sẽ xóa ô. Những con số đậm là manh mối. Điền vào lưới để mỗi hàng, cột và hộp 3×3 chứa 1–9 mà không lặp lại.", + "pencil": "Pencil", + "undo": "Undo", + "hint": "Hint", + "hintsUsed": "hints used", + "mistakes": "Mistakes", + "failed": "Failed!", + "mistakeMode": "3-mistake mode" + }, + "Snake": { + "title": "Rắn", + "subtitle": "Ăn thức ăn, lớn lên, tránh tường và chính mình", + "score": "Điểm", + "best": "Tốt nhất", + "startPrompt": "Nhấn phím mũi tên bất kỳ", + "startButton": "Bắt đầu chơi", + "gameOver": "Trò chơi kết thúc", + "playAgain": "Chơi lại", + "howToPlay": "Dùng phím mũi tên hoặc WASD. Ăn để lớn hơn. Tốc độ tăng mỗi 50 điểm.", + "diffEasy": "Dễ", + "diffNormal": "Bình thường", + "diffHard": "Khó", + "diffInfinite": "Vô hạn", + "orTap": "hoặc nhấn bên dưới", + "howToPlayInfinite": "Tường nối liền — không chết vì tường. Ăn để lớn hơn.", + "shieldActive": "Shield Active", + "speedBoost": "Speed Boost", + "powerSpeed": "Speed x2 (10 ticks)", + "powerShield": "Survive one death", + "powerShrink": "Shortens tail" + }, + "RedisCommander": { + "page": { + "title": "Redis Commander", + "dialogTitle": "Kết nối Redis", + "heroTitle": "Redis Commander", + "heroDesc": "Kết nối với các phiên bản Redis. Duyệt khóa, chỉnh sửa giá trị và chạy lệnh. Thông tin xác thực được mã hóa trong trình duyệt của bạn.", + "heroAddConnection": "Thêm kết nối", + "vaultLockedTitle": "Kho bị khóa", + "vaultLockedDesc": "Mở khóa kho của bạn để truy cập các kết nối đã lưu." + } + }, + "Minesweeper": { + "title": "Dò mìn", + "subtitle": "Tìm tất cả ô an toàn mà không kích nổ mìn", + "mines": "Mìn", + "newGame": "Ván mới", + "win": "Bạn thắng rồi!", + "loss": "Bạn thua rồi!", + "playAgain": "Chơi lại", + "tryAgain": "Thử lại", + "startPrompt": "Nhấp vào ô bất kỳ để bắt đầu", + "difficulty": { + "beginner": "Dễ", + "intermediate": "Trung bình", + "expert": "Chuyên gia" + } + }, + "Tetris": { + "title": "Tetris", + "subtitle": "Sắp xếp các khối rơi để hoàn thành hàng", + "next": "Tiếp theo", + "score": "Điểm", + "best": "Kỷ lục", + "lines": "Hàng", + "level": "Cấp độ", + "pause": "Tạm dừng", + "resume": "Tiếp tục", + "start": "Bắt đầu", + "restart": "Chơi lại", + "paused": "Đã tạm dừng", + "gameOver": "Kết thúc" + }, + "SshKeyGenerator": { + "title": "Trình tạo khóa SSH / RSA", + "subtitle": "Tạo cặp khóa Ed25519 hoặc RSA trong trình duyệt của bạn. Tải xuống khóa riêng tư và công khai ở định dạng OpenSSH và PEM. Không có gì được tải lên.", + "keyTypeLabel": "Loại khóa", + "recommended": "khuyến nghị", + "ed25519Desc": "Nhanh, nhỏ và hiện đại. Được hỗ trợ bởi GitHub, GitLab và tất cả các máy chủ SSH lớn.", + "rsa2048Desc": "Tương thích rộng rãi. An toàn cho hầu hết các mục đích sử dụng.", + "rsa4096Desc": "Tương thích tối đa và mạnh mẽ cho các hệ thống cũ.", + "commentLabel": "Bình luận (tùy chọn)", + "commentHint": "Được thêm vào dòng khóa công khai OpenSSH.", + "generateBtn": "Tạo cặp khóa", + "generating": "Đang tạo…", + "generatingLong": "Đang tạo (có thể mất một lúc)…", + "copy": "Sao chép", + "download": "Tải xuống", + "privateKeyLabel": "Khóa riêng tư (PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "Khóa công khai (định dạng OpenSSH)", + "publicKeySpkiLabel": "Khóa công khai (SPKI PEM)", + "browserNote": "Tất cả các khóa được tạo trong trình duyệt của bạn. Không có gì được gửi đến bất kỳ máy chủ nào.", + "errors": { + "generationFailed": "Tạo khóa thất bại" + } + }, + "DnsLookup": { + "title": "Tra cứu DNS", + "subtitle": "Truy vấn bản ghi DNS cho bất kỳ tên miền nào — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Nhập tên miền (vd: github.com)", + "lookupBtn": "Tra cứu", + "looking": "Đang tra cứu…", + "refreshBtn": "Làm mới", + "recordTypesLabel": "Loại bản ghi", + "allBtn": "Tất cả", + "tryLabel": "Thử:", + "errors": { + "lookupFailed": "Tra cứu DNS thất bại" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Trình tạo & kiểm tra Bcrypt", + "subtitle": "Băm mật khẩu bằng bcrypt và xác minh băm — mọi thứ diễn ra trong trình duyệt của bạn", + "tabs": { + "hash": "Băm", + "verify": "Xác minh" + }, + "showPassword": "Hiện mật khẩu", + "hidePassword": "Ẩn mật khẩu", + "copy": "Sao chép", + "copied": "Đã sao chép", + "hash": { + "passwordLabel": "Mật khẩu", + "passwordPlaceholder": "Nhập mật khẩu cần băm…", + "costLabel": "Hệ số chi phí", + "costHint": "Chi phí càng cao thì khối lượng tính toán mỗi bước càng tăng gấp đôi. 10-12 là lựa chọn phổ biến cho môi trường sản xuất.", + "slowWarning": "Hệ số chi phí từ 13 trở lên có thể mất vài giây để tính toán.", + "generate": "Tạo băm", + "generating": "Đang băm…", + "resultLabel": "Băm Bcrypt", + "timeTaken": "{ms} ms", + "anatomy": { + "title": "Cấu trúc băm", + "algorithm": "thuật toán", + "cost": "chi phí", + "salt": "salt (22 ký tự)", + "hash": "băm (31 ký tự)" + } + }, + "verify": { + "passwordLabel": "Mật khẩu", + "passwordPlaceholder": "Nhập mật khẩu cần kiểm tra…", + "hashLabel": "Băm Bcrypt", + "hashPlaceholder": "$2b$10$…", + "idle": "Nhập mật khẩu và một băm bcrypt để so sánh chúng.", + "invalidHash": "Đây có vẻ không phải là một băm bcrypt hợp lệ.", + "checking": "Đang so sánh…", + "match": "Khớp — mật khẩu này tạo ra băm này.", + "noMatch": "Không khớp — mật khẩu này không tạo ra băm này." + } + }, + "ChmodCalculator": { + "title": "Máy tính Chmod", + "subtitle": "Chuyển đổi quyền tệp Unix giữa hộp kiểm, bát phân và ký hiệu tượng trưng", + "grid": { + "title": "Quyền", + "read": "Đọc", + "write": "Ghi", + "execute": "Thực thi", + "owner": "Chủ sở hữu", + "group": "Nhóm", + "others": "Người khác" + }, + "special": { + "title": "Bit đặc biệt", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "Bát phân", + "placeholder": "755 hoặc 4755", + "invalid": "Nhập 3 hoặc 4 chữ số bát phân (0-7)." + }, + "symbolic": { + "label": "Ký hiệu", + "placeholder": "rwxr-xr-x", + "invalid": "Nhập 9 ký tự như rwxr-xr-x (cho phép s/S, t/T)." + }, + "output": { + "title": "Lệnh", + "copy": "Sao chép", + "copied": "Đã sao chép" + }, + "explain": { + "line": "{cls} có thể {perms}.", + "none": "{cls} không có quyền nào.", + "setuid": "Setuid: tệp chạy với đặc quyền của chủ sở hữu.", + "setgid": "Setgid: tệp chạy với đặc quyền của nhóm (các tệp mới trong thư mục kế thừa nhóm của nó).", + "sticky": "Sticky: chỉ chủ sở hữu tệp mới có thể xóa hoặc đổi tên tệp trong thư mục này." + }, + "presets": { + "title": "Cài đặt sẵn phổ biến", + "octal": "Bát phân", + "permissions": "Quyền", + "description": "Công dụng điển hình", + "p644": "Tệp thông thường: chủ sở hữu ghi, mọi người đọc.", + "p755": "Tệp thực thi và thư mục: mọi người có thể đọc và truy cập.", + "p600": "Tệp riêng tư: chỉ chủ sở hữu đọc và ghi.", + "p700": "Tệp thực thi và thư mục riêng tư: chỉ chủ sở hữu.", + "p664": "Tệp cho phép nhóm ghi.", + "p775": "Thư mục và tệp thực thi cho phép nhóm ghi.", + "p400": "Chỉ đọc cho chủ sở hữu (ví dụ khóa riêng SSH)." + } + }, + "CodeScreenshot": { + "title": "Ảnh chụp mã", + "subtitle": "Biến mã thành hình ảnh đẹp, dễ chia sẻ", + "languageLabel": "Ngôn ngữ", + "autoDetect": "Tự động nhận diện", + "backgroundLabel": "Nền", + "paddingLabel": "Khoảng đệm", + "filenameLabel": "Tên tệp", + "filenamePlaceholder": "example.ts", + "windowLabel": "Chấm cửa sổ", + "themeLabel": "Chủ đề cửa sổ", + "themeDark": "Tối", + "themeLight": "Sáng", + "scaleLabel": "Tỷ lệ xuất", + "copyImage": "Sao chép hình ảnh", + "copied": "Đã sao chép", + "exportPng": "Xuất PNG", + "inputLabel": "Mã", + "detected": "Đã nhận diện: {language}", + "clear": "Xóa", + "placeholder": "Dán mã của bạn vào đây…", + "previewLabel": "Xem trước", + "emptyPreview": "Bản xem trước mã của bạn sẽ hiển thị ở đây", + "exportFailed": "Không xuất được hình ảnh", + "copyUnsupported": "Trình duyệt này không hỗ trợ sao chép hình ảnh", + "copyFailed": "Không sao chép được hình ảnh", + "backgrounds": { + "ocean": "Đại dương", + "sunset": "Hoàng hôn", + "candy": "Kẹo ngọt", + "forest": "Rừng cây", + "midnight": "Nửa đêm", + "gold": "Vàng kim", + "aurora": "Cực quang", + "slate": "Xám đá", + "transparent": "Trong suốt" + } + }, + "CssGenerators": { + "title": "Trình tạo CSS", + "subtitle": "Box-shadow, border-radius và clamp() linh hoạt — sao chép CSS.", + "preview": "Xem trước", + "output": "CSS", + "copy": "Sao chép", + "copied": "Đã sao chép", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "clamp() linh hoạt" + }, + "boxShadow": { + "presets": "Cài đặt sẵn", + "layer": "Lớp", + "addLayer": "Thêm lớp", + "removeLayer": "Xóa lớp", + "x": "X", + "y": "Y", + "blur": "Độ mờ", + "spread": "Độ lan", + "color": "Màu", + "alpha": "Độ trong", + "inset": "Đổ bóng trong", + "presetNames": { + "subtleCard": "Thẻ nhẹ nhàng", + "elevated": "Nổi lên", + "hardOffset": "Lệch cứng", + "neonGlow": "Ánh neon", + "inner": "Bóng trong", + "layeredSmooth": "Nhiều lớp mượt" + } + }, + "borderRadius": { + "modeLabel": "Chế độ", + "modes": { + "simple": "Theo từng góc", + "blob": "Khối hữu cơ" + }, + "unit": "Đơn vị", + "linkCorners": "Liên kết các góc", + "randomize": "Ngẫu nhiên", + "horizontal": "Bán kính ngang", + "vertical": "Bán kính dọc", + "corners": { + "tl": "Trên trái", + "tr": "Trên phải", + "br": "Dưới phải", + "bl": "Dưới trái" + } + }, + "clamp": { + "minFont": "Cỡ chữ tối thiểu (px)", + "maxFont": "Cỡ chữ tối đa (px)", + "minViewport": "Viewport tối thiểu (px)", + "maxViewport": "Viewport tối đa (px)", + "rootFont": "Cỡ chữ gốc (px)", + "viewportWidth": "Viewport mô phỏng", + "computedSize": "Kích thước tính toán", + "slope": "Độ dốc", + "intercept": "Giao điểm", + "previewText": "The quick brown fox", + "invalid": "Nhập các giới hạn viewport hợp lệ và khác nhau." + } + }, + "ExifViewer": { + "title": "Trình xem & xóa EXIF", + "subtitle": "Kiểm tra metadata của ảnh và xóa chúng ngay trong trình duyệt", + "dropTitle": "Thả một ảnh vào đây", + "dropHint": "hoặc nhấn để duyệt — ảnh của bạn không bao giờ rời khỏi thiết bị", + "browse": "Chọn ảnh", + "formats": "JPEG, PNG, WebP, HEIC, TIFF", + "parsing": "Đang đọc metadata…", + "parseFailed": "Không đọc được ảnh này", + "clear": "Xóa ảnh", + "gpsWarning": "Chứa vị trí GPS", + "openMap": "Xem trên OpenStreetMap", + "noMetadata": "Không tìm thấy metadata nhúng nào trong ảnh này.", + "facts": { + "name": "Tên", + "type": "Loại", + "dimensions": "Kích thước", + "size": "Dung lượng" + }, + "groups": { + "camera": "Máy ảnh", + "exposure": "Phơi sáng", + "timestamps": "Dấu thời gian", + "gps": "Vị trí GPS", + "software": "Phần mềm & khác" + }, + "gpsCard": { + "latitude": "Vĩ độ", + "longitude": "Kinh độ", + "altitude": "Độ cao" + }, + "raw": { + "title": "Toàn bộ metadata", + "copyJson": "Sao chép JSON", + "copied": "Đã sao chép" + }, + "remove": { + "title": "Xóa metadata", + "jpegDescription": "Xóa EXIF, XMP, IPTC và chú thích không mất dữ liệu mà không nén lại ảnh.", + "reencodeDescription": "Mã hóa lại ảnh để loại bỏ metadata nhúng. Thao tác này nén lại các điểm ảnh.", + "keepIcc": "Giữ hồ sơ màu (ICC)", + "stripButton": "Xóa metadata", + "reencodeButton": "Mã hóa lại & xóa", + "unsupported": "Không hỗ trợ xóa metadata cho loại tệp này.", + "failed": "Không xóa được metadata", + "before": "Trước", + "after": "Sau", + "download": "Tải ảnh đã làm sạch" + } + }, + "FaviconGenerator": { + "title": "Trình tạo Favicon", + "subtitle": "Tạo favicon và biểu tượng ứng dụng từ một ảnh", + "dropTitle": "Thả một ảnh vào đây", + "dropHint": "PNG, JPEG hoặc SVG — nhấn để duyệt", + "browse": "Chọn ảnh", + "clear": "Xóa ảnh", + "backgroundLabel": "Nền", + "transparent": "Trong suốt", + "solidColor": "Màu đặc", + "cornerLabel": "Hình dạng góc", + "cornerNone": "Vuông", + "cornerRounded": "Bo tròn", + "cornerCircle": "Hình tròn", + "maskPaddingLabel": "Khoảng đệm maskable", + "maskable": "maskable", + "previewLabel": "Xem trước", + "copy": "Sao chép", + "headSnippet": "Đoạn mã HTML head", + "downloadIco": "Tải favicon.ico", + "downloadZip": "Tải tất cả (.zip)" + }, + "JsonDiff": { + "title": "So sánh JSON", + "subtitle": "So sánh hai tài liệu JSON theo ngữ nghĩa và cấu trúc — bỏ qua thứ tự khóa và định dạng", + "options": { + "ignoreArrayOrder": "Bỏ qua thứ tự mảng" + }, + "loadBothSamples": "Tải ví dụ", + "sample": "Ví dụ", + "format": "Định dạng", + "clear": "Xóa", + "formatError": "Không thể định dạng: đầu vào không phải JSON hợp lệ", + "placeholder": "Dán hoặc nhập JSON vào đây…", + "panels": { + "left": "Bản gốc (trái)", + "right": "Bản sửa (phải)", + "results": "Khác biệt" + }, + "summary": { + "added": "{count} đã thêm", + "removed": "{count} đã xóa", + "changed": "{count} đã thay đổi" + }, + "types": { + "added": "Đã thêm", + "removed": "Đã xóa", + "changed": "Đã thay đổi" + }, + "copyChanges": "Sao chép dưới dạng JSON", + "copied": "Đã sao chép", + "empty": "Dán JSON vào cả hai ô để xem khác biệt của chúng.", + "identical": "Hai tài liệu giống nhau về mặt ngữ nghĩa." + }, + "JsonpathPlayground": { + "title": "Sân chơi JSONPath", + "subtitle": "Truy vấn các tài liệu JSON bằng JSONPath hoặc JMESPath và xem kết quả trực tiếp", + "engineLabel": "Bộ máy", + "queryLabel": "Truy vấn", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "Bảng tra cứu nhanh", + "cheats": { + "jpAllTitles": "Tất cả tựa sách", + "jpAllAuthors": "Mọi tác giả, ở bất kỳ đâu trong tài liệu", + "jpFirstBook": "Cuốn sách đầu tiên", + "jpLastBook": "Cuốn sách cuối cùng", + "jpSlice": "Hai cuốn đầu (slice)", + "jpFilterPrice": "Sách rẻ hơn 40", + "jpFilterInStock": "Tựa của các cuốn sách còn hàng", + "jpRecursive": "Tất cả giá trị, theo đệ quy", + "jmAllTitles": "Tất cả tựa sách", + "jmFirstAuthor": "Tác giả của cuốn sách đầu tiên", + "jmLastBook": "Cuốn sách cuối cùng", + "jmSlice": "Hai cuốn đầu (slice)", + "jmFilterPrice": "Tựa của các cuốn sách rẻ hơn 40", + "jmCount": "Đếm số sách còn hàng", + "jmProjection": "Định hình lại mỗi cuốn sách thành đối tượng mới", + "jmSort": "Tựa sách sắp xếp theo giá" + }, + "panels": { + "input": "Đầu vào JSON", + "results": "Kết quả" + }, + "sample": "Ví dụ", + "format": "Định dạng", + "clear": "Xóa", + "formatError": "Không thể định dạng: đầu vào không phải JSON hợp lệ", + "placeholder": "Dán hoặc nhập JSON vào đây…", + "outputPlaceholder": "Các kết quả khớp sẽ hiển thị ở đây…", + "matchCount": "{count, plural, one {# kết quả khớp} other {# kết quả khớp}}", + "copy": "Sao chép", + "copied": "Đã sao chép" + }, + "KeycodeInspector": { + "title": "Trình kiểm tra mã phím", + "subtitle": "Nhấn phím bất kỳ để kiểm tra sự kiện bàn phím JavaScript của nó", + "capture": { + "prompt": "Nhấn phím bất kỳ…", + "ready": "Đang lắng nghe — nhấn phím bất kỳ trên bàn phím", + "clickToFocus": "Nhấn vào đây để lấy tiêu điểm, rồi nhấn phím bất kỳ" + }, + "hero": { + "spaceKey": "(Dấu cách)", + "keyLabel": "event.key", + "repeat": "lặp lại" + }, + "copyJson": "Sao chép JSON", + "copiedJson": "Đã sao chép JSON sự kiện vào bộ nhớ tạm", + "deprecated": "không dùng nữa", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "Chuẩn", + "left": "Trái", + "right": "Phải", + "numpad": "Bàn phím số", + "unknown": "Không rõ" + }, + "history": { + "title": "Lịch sử", + "clear": "Xóa lịch sử", + "empty": "Các lần nhấn phím sẽ hiển thị ở đây." + } + }, + "MarkdownTableGenerator": { + "title": "Trình tạo bảng Markdown", + "subtitle": "Chỉnh sửa lưới, dán từ bảng tính và sao chép bảng Markdown hoặc HTML được căn chỉnh đẹp mắt", + "addRow": "Thêm hàng", + "addColumn": "Thêm cột", + "importLabel": "Nhập", + "importPlaceholder": "Dán TSV (từ Excel / Google Sheets) hoặc CSV vào đây…", + "importButton": "Nhập vào lưới", + "pasteClipboard": "Dán từ bộ nhớ tạm", + "importEmpty": "Không có gì để nhập — hãy dán một số hàng trước", + "clipboardError": "Không thể đọc từ bộ nhớ tạm", + "clear": "Xóa", + "panels": { + "editor": "Trình chỉnh sửa bảng", + "output": "Kết quả" + }, + "align": { + "left": "Căn trái", + "center": "Căn giữa", + "right": "Căn phải" + }, + "headerPlaceholder": "Tiêu đề", + "cellPlaceholder": "Ô", + "removeRow": "Xóa hàng", + "removeColumn": "Xóa cột", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "Sao chép", + "copied": "Đã sao chép" + }, + "TimezoneConverter": { + "title": "Chuyển đổi múi giờ", + "subtitle": "Chuyển đổi một thời điểm trên khắp thế giới để lên kế hoạch họp.", + "dateTimeLabel": "Ngày & giờ", + "now": "Bây giờ", + "sourceZoneLabel": "Múi giờ nguồn", + "selectZone": "Chọn múi giờ", + "searchZone": "Tìm múi giờ...", + "noZone": "Không tìm thấy múi giờ.", + "scrubLabel": "Dịch chuyển thời gian", + "reset": "Đặt lại", + "targetsLabel": "Múi giờ đích", + "addZone": "Thêm múi giờ", + "remove": "Xóa", + "localBadge": "Địa phương", + "invalidDate": "Nhập ngày và giờ hợp lệ.", + "bands": { + "work": "Giờ làm việc (09:00–18:00)", + "edge": "Giờ giáp ranh (sáng sớm / tối)", + "night": "Ban đêm" + } + }, + "TokenCounter": { + "title": "Bộ đếm token LLM", + "subtitle": "Đếm token, trực quan hóa các khối và ước tính chi phí API.", + "inputLabel": "Văn bản", + "sample": "Ví dụ", + "clear": "Xóa", + "placeholder": "Dán văn bản, mã hoặc JSON để đếm token...", + "encoding": "Mã hóa", + "loading": "Đang tải bộ tách token...", + "encodings": { + "o200k": "o200k_base (GPT-4o / dòng o)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Token", + "characters": "Ký tự", + "words": "Từ", + "ratio": "Token/từ", + "ratioSub": "xấp xỉ" + }, + "claudeRow": "Claude (xấp xỉ)", + "estimate": "ước tính", + "viz": { + "title": "Trực quan hóa token", + "toggle": "Hiển thị", + "hint": "Bật để tô màu mỗi token thành một khối.", + "empty": "Nhập văn bản để trực quan hóa token.", + "truncated": "Đang hiển thị {limit} token đầu tiên trong tổng số {total}." + }, + "cost": { + "title": "Máy tính chi phí", + "disclaimer": "giá trị mặc định có thể sửa — hãy kiểm tra giá hiện tại", + "model": "Mô hình", + "expectedOutput": "Số token đầu ra dự kiến", + "customInput": "Đầu vào $/MTok", + "customOutput": "Đầu ra $/MTok", + "inputCost": "Chi phí đầu vào", + "outputCost": "Chi phí đầu ra", + "total": "Tổng", + "per1000": "Mỗi 1000 lần gọi" + } + }, + "WebhookTester": { + "title": "Trình kiểm tra Webhook", + "subtitle": "Tạo một URL tạm thời và kiểm tra các yêu cầu webhook đến trực tiếp.", + "intro": { + "title": "Tạo một URL webhook để bắt đầu ghi lại các yêu cầu", + "hint": "Bạn sẽ nhận được một URL không thể đoán, ghi lại mọi yêu cầu đến. Nó hết hạn sau 24 giờ." + }, + "createBtn": "Tạo URL webhook", + "creating": "Đang tạo…", + "urlLabel": "URL webhook của bạn", + "expiresIn": "Hết hạn sau {countdown}", + "copyUrlBtn": "Sao chép URL", + "copied": "Đã sao chép", + "clearBtn": "Xóa", + "newBinBtn": "URL mới", + "requestsTitle": "Yêu cầu", + "selectPrompt": "Chọn một yêu cầu để xem chi tiết.", + "expiredNotice": "URL webhook này đã hết hạn. Tạo một URL mới để tiếp tục kiểm tra.", + "base64Badge": "base64", + "truncatedBadge": "đã cắt bớt", + "empty": { + "title": "Đang chờ yêu cầu…", + "hint": "Gửi một yêu cầu đến URL của bạn và nó sẽ hiển thị ngay tại đây. Hãy thử:" + }, + "detail": { + "receivedAt": "Nhận lúc", + "source": "IP nguồn", + "contentType": "Content-Type", + "query": "Tham số truy vấn", + "headers": "Header", + "body": "Nội dung", + "copyBody": "Sao chép nội dung", + "emptyBody": "Không có nội dung" + }, + "errors": { + "createFailed": "Không tạo được URL webhook. Vui lòng thử lại.", + "clearFailed": "Không xóa được các yêu cầu. Vui lòng thử lại." + } + }, + "WebsocketTester": { + "title": "Trình kiểm tra WebSocket", + "subtitle": "Kết nối tới một điểm cuối WebSocket và gửi, nhận, kiểm tra tin nhắn trực tiếp", + "urlLabel": "URL điểm cuối", + "protocolsLabel": "Giao thức phụ", + "protocolsPlaceholder": "tùy chọn, phân tách bằng dấu phẩy", + "connect": "Kết nối", + "disconnect": "Ngắt kết nối", + "status": { + "disconnected": "Đã ngắt kết nối", + "connecting": "Đang kết nối", + "connected": "Đã kết nối", + "error": "Lỗi" + }, + "messages": { + "connecting": "Đang kết nối tới {url}…", + "connected": "Đã thiết lập kết nối", + "connectedWithProtocol": "Đã thiết lập kết nối (giao thức: {protocol})", + "invalidUrl": "URL không hợp lệ — phải bắt đầu bằng ws:// hoặc wss://", + "connectFailed": "Không mở được kết nối", + "socketError": "Đã xảy ra lỗi kết nối", + "notConnected": "Chưa kết nối — hãy kết nối trước", + "binaryMessage": "Đã nhận một tin nhắn nhị phân" + }, + "log": { + "title": "Nhật ký tin nhắn", + "empty": "Chưa có tin nhắn. Hãy kết nối và bắt đầu gửi.", + "copyAll": "Sao chép tất cả", + "download": "Tải xuống dạng .txt", + "clear": "Xóa nhật ký", + "prettyPrint": "Định dạng đẹp JSON", + "showRaw": "Hiện văn bản thô" + }, + "composer": { + "placeholder": "Nhập tin nhắn để gửi…", + "send": "Gửi", + "formatJson": "Định dạng thành JSON", + "invalidJson": "Tin nhắn không phải JSON hợp lệ", + "hint": "Cmd/Ctrl+Enter để gửi" + }, + "ping": { + "label": "Tự động ping", + "seconds": "giây", + "intervalTitle": "Khoảng ping tính bằng giây", + "payloadPlaceholder": "payload ping" + } + }, + "WhoisLookup": { + "title": "Tra cứu Whois", + "subtitle": "Tra cứu thông tin đăng ký cho bất kỳ tên miền hoặc địa chỉ IP nào qua RDAP", + "placeholder": "example.com hoặc 8.8.8.8", + "lookupBtn": "Tra cứu", + "looking": "Đang tra cứu…", + "refreshBtn": "Làm mới", + "tryLabel": "Thử:", + "copy": "Sao chép", + "copied": "Đã sao chép", + "notAvailable": "Không có sẵn", + "summary": { + "registrar": "Nhà đăng ký", + "network": "Mạng" + }, + "dates": { + "created": "Ngày tạo", + "updated": "Ngày cập nhật", + "expires": "Ngày hết hạn" + }, + "sections": { + "status": "Trạng thái", + "nameservers": "Máy chủ tên", + "contacts": "Liên hệ", + "rawRdap": "RDAP thô" + }, + "contacts": { + "roles": "Vai trò", + "name": "Tên", + "organization": "Tổ chức" + }, + "errors": { + "invalidTarget": "Nhập một tên miền hoặc địa chỉ IP hợp lệ", + "lookupFailed": "Tra cứu thất bại (trạng thái {status})" + } + } +} diff --git a/apps/desktop-ui/messages/zh.json b/apps/desktop-ui/messages/zh.json new file mode 100644 index 00000000..006c3780 --- /dev/null +++ b/apps/desktop-ui/messages/zh.json @@ -0,0 +1,4573 @@ +{ + "Navigation": { + "dashboard": "控制台", + "settings": "设置", + "apiClient": "API 客户端", + "httpStatusCodes": "HTTP 状态码", + "passwordManager": "密码管理器", + "environmentManager": "环境管理", + "bookmarks": "书签", + "jsonFormatter": "JSON 格式化器", + "emailValidator": "邮箱验证", + "nosqlExplorer": "数据库浏览器", + "toDo": "Tasks", + "notes": "Notes", + "encryptionPlayground": "加密演练场", + "jwtDecoder": "JWT 解码器", + "certificatePemDecoder": "证书 / PEM 解码器", + "sqlFormatter": "SQL Formatter", + "graphqlFormatter": "GraphQL 格式化", + "diffChecker": "Diff checker", + "regexTester": "Regex Tester", + "timestampConverter": "Timestamp Converter", + "cronBuilder": "Cron Builder", + "base64Encoder": "Base64 Encoder", + "urlEncoder": "URL Encoder", + "urlParser": "URL 解析器", + "uuidGenerator": "UUID / ULID", + "secretApiKeyGenerator": "密钥 / API 密钥", + "loremIpsum": "Lorem Ipsum", + "colorPicker": "Color Picker", + "contrastChecker": "对比度检查", + "jsonSchemaGenerator": "JSON 模式生成器", + "imageToBase64": "图像转 Base64", + "imageCompressor": "图片压缩", + "cssGradientBuilder": "CSS 渐变生成器", + "gitignoreGenerator": ".gitignore 生成器", + "csvExcelJson": "CSV / Excel ↔ JSON", + "snippetManager": "代码片段", + "qrCodeGenerator": "二维码生成器", + "dnsLookup": "DNS 查询", + "ipSubnetCalculator": "IP / 子网计算器", + "hashGenerator": "哈希生成器", + "hmacGenerator": "HMAC 生成器", + "totpGenerator": "TOTP / 2FA 验证码", + "markdownPreview": "Markdown 预览", + "numberBaseConverter": "进制转换", + "formatConverter": "格式转换器", + "mockDataGenerator": "模拟数据生成器", + "svgOptimizer": "SVG 优化", + "unitConverter": "单位换算器", + "dockerComposeGenerator": "Docker Compose 生成器", + "breakRoom": "休闲角", + "game2048": "2048", + "sudoku": "数独", + "redisCommander": "Redis 指挥官", + "yamlFormatter": "YAML格式化工具", + "sshKeyGenerator": "SSH / RSA 密钥生成器", + "curlToCode": "cURL to Code", + "jsonToCode": "JSON to Code", + "beautifyMinify": "Beautify & Minify", + "stringCaseConverter": "String Case Converter", + "lineSortDedupe": "Line Sort & Dedupe", + "stringInspector": "String Inspector", + "escapeEncode": "Escape / Encode", + "bcryptGenerator": "Bcrypt", + "chmodCalculator": "Chmod 计算器", + "codeScreenshot": "代码截图", + "cssGenerators": "CSS 生成器", + "exifViewer": "EXIF 查看器", + "faviconGenerator": "Favicon 生成器", + "jsonDiff": "JSON 对比", + "jsonpathPlayground": "JSONPath 沙盒", + "keycodeInspector": "键码检查器", + "markdownTableGenerator": "Markdown 表格", + "timezoneConverter": "时区转换器", + "tokenCounter": "Token 计数器", + "webhookTester": "Webhook 测试器", + "websocketTester": "WebSocket 测试器", + "whoisLookup": "Whois 查询" + }, + "Help": { + "title": "帮助与文档", + "subtitle": "了解 MyDevTools 的整体结构、各应用的用途,以及安全与加密如何保护你的数据。", + "tabs": { + "overview": "概览", + "apps": "应用", + "security": "安全", + "tips": "提示" + }, + "overview": { + "whatIs": { + "title": "什么是 MyDevTools?", + "description": "一个可登录的开发者工作区:包含控制台与日常工具集合。", + "p1": "登录后,应用会与 MyDevTools API 建立会话,让工具能够加载与保存你的数据。侧边栏列出所有应用;你可以在", + "settingsLink": "设置", + "p1After": "中隐藏不常用的工具。", + "p2": "部分功能使用 主密码(或全局保险库密钥),使密码、数据库连接串等敏感信息在到达服务器前先在你的设备上完成加密。" + }, + "quickLinks": { + "title": "快捷链接", + "dashboard": "控制台", + "settings": "设置" + } + }, + "apps": { + "intro": "你可以从侧边栏或控制台打开任何工具。下面是每个应用的简要说明。", + "open": "打开", + "howItWorks": "工作方式", + "generic": "请从侧边栏使用该工具的专用工作流程。" + }, + "security": { + "session": { + "title": "登录与 API 会话", + "description": "身份与浏览器如何与后端通信。", + "items": { + "authentication": "认证 使用 Firebase 登录。登录后,应用会将 Firebase ID Token 交换为 API Cookie。", + "accessTokens": "访问令牌 为服务器签名的 JWT(HS256),用于证明调用 API 的用户身份。", + "refreshTokens": "刷新令牌 存放在 HttpOnly Cookie 中;服务器保存的是刷新令牌的 SHA-256 哈希值,而不是原始令牌。" + } + }, + "encryption": { + "title": "你需要了解的加密", + "description": "保险库与敏感连接数据的客户端加密。", + "p1": "对于密码管理器环境管理器以及 NoSQL Explorer 中保存的连接,Web 应用使用浏览器的 Web Crypto API:", + "items": { + "kdf": "PBKDF2SHA-256100,000 次迭代与随机盐)从主密码派生 256 位密钥。", + "aes": "AES-256-GCM 加密数据;每次加密都会使用随机的 12 字节 IV。", + "scope": "派生密钥保留在浏览器中(不可导出)。服务器只接收 密文与 IV,不会接收主密码或明文秘密。" + }, + "warning": "如果你忘记主密码,加密数据将无法恢复。请妥善保管。" + }, + "logout": { + "title": "退出登录与本地数据", + "body": "退出登录会清除内存中的敏感信息、保险库相关状态,并在适用时从 IndexedDB 清除主密钥材料,同时结束 API 会话。仅保存在设备本地存储中的数据可能会保留,直到你在浏览器中清理。" + } + }, + "tips": { + "title": "安全使用 MyDevTools", + "items": { + "masterPassword": "为保险库功能设置强且唯一的主密码。", + "apiClient": "将 API 客户端视为普通 HTTP 客户端:不了解可见范围时,避免在共享环境中粘贴生产密钥。", + "nosql": "数据库浏览器连接到你配置的数据库;尽量使用最小权限账号与网络规则。", + "json": "JSON 格式化在浏览器本地运行——在共享设备上仍应避免粘贴高度敏感数据。", + "settings": "在设置中自定义可见工具,让侧边栏更聚焦。" + } + } + }, + "Settings": { + "title": "设置", + "language": "语言", + "spanish": "西班牙语", + "english": "英语", + "french": "法语", + "arabic": "阿拉伯语", + "catalan": "加泰罗尼亚语", + "chineseSimplified": "简体中文", + "czech": "捷克语", + "greek": "Ελληνικά", + "german": "德语", + "danish": "丹麦语", + "afrikaans": "南非荷兰语", + "persian": "فارسی", + "indonesian": "Bahasa Indonesia", + "norwegian": "Norsk", + "japanese": "日本語", + "korean": "한국어", + "malay": "Bahasa Melayu", + "dutch": "Nederlands", + "polish": "Polski", + "vietnamese": "Tiếng Việt", + "ukrainian": "乌克兰语" + }, + "SettingsPage": { + "title": "设置", + "subtitle": "管理你的账户设置与偏好。", + "userProfile": { + "title": "用户资料", + "description": "你的个人信息与账户详情。", + "avatarAlt": "用户", + "anonymousUser": "匿名用户", + "noEmailProvided": "未提供邮箱", + "notLoggedIn": "未登录。" + }, + "tools": { + "title": "工具与功能", + "description": "选择要在侧边导航中显示的工具。" + }, + "appearance": { + "title": "外观", + "description": "自定义应用在你设备上的显示方式。", + "themePreference": "主题偏好", + "themes": { + "light": "浅色", + "dark": "深色", + "system": "跟随系统" + } + }, + "language": { + "title": "语言", + "description": "设置全站使用的语言。", + "selectLanguage": "选择语言", + "selectPlaceholder": "请选择语言", + "helpText": "更改语言后,菜单与标签将在全站更新。", + "languages": { + "en": "English(英语)", + "fr": "Français(法语)", + "es": "Español(西班牙语)", + "ca": "Català(加泰罗尼亚语)", + "ar": "عربى(阿拉伯语)", + "zh": "简体中文", + "cs": "Čeština(捷克语)", + "el": "Ελληνικά(希腊语)", + "de": "Deutsch(德语)", + "da": "Dansk(丹麦语)", + "af": "Afrikaans(南非荷兰语)", + "fa": "فارسی", + "id": "Bahasa Indonesia", + "it": "意大利语 (Italiano)", + "nb": "Norsk", + "ja": "日本語", + "ko": "한국어", + "ms": "Bahasa Melayu", + "nl": "Nederlands", + "pl": "Polski", + "pt": "Português", + "pt-BR": "Português (Brasil)", + "vi": "Tiếng Việt", + "ru": "Русский", + "uk": "Українська", + "sv": "Svenska", + "tr": "Türkçe" + } + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", + "addButton": "Add passkey", + "loading": "Loading…", + "none": "No passkeys yet.", + "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", + "addedOn": "Added {date}", + "lastUsedOn": "Last used {date}", + "synced": "Synced", + "rename": "Rename", + "renamePrompt": "Rename passkey", + "addedToast": "Passkey added.", + "renamedToast": "Passkey renamed.", + "removedToast": "Passkey removed.", + "loadError": "Could not load passkeys.", + "addError": "Could not add passkey.", + "renameError": "Could not rename passkey.", + "deleteError": "Could not delete passkey.", + "dialog": { + "title": "Add a passkey", + "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", + "nameLabel": "Device name (optional)", + "namePlaceholder": "e.g. MacBook Touch ID", + "cancel": "Cancel", + "continue": "Continue", + "waiting": "Waiting for device…" + }, + "delete": { + "title": "Remove passkey?", + "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", + "cancel": "Cancel", + "confirm": "Remove" + }, + "prompt": { + "title": "Sign in faster next time", + "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", + "skip": "Maybe later", + "setup": "Set up passkey", + "successToast": "Passkey added — try signing out and back in to test it.", + "errorToast": "Could not add passkey. You can try again from Settings." + } + } + }, + "Dashboard": { + "greeting": { + "morning": "早上好", + "afternoon": "下午好", + "evening": "晚上好" + }, + "commaName": ",{name}", + "welcomeBack": "欢迎回来", + "welcomeBackNamed": "欢迎回来,{name}", + "tagline": "今天想做什么?", + "brandName": "MyDevTools", + "stats": { + "tools": "工具", + "favorites": "收藏", + "pinned": "已固定", + "recent": "最近使用" + }, + "sections": { + "recentlyUsed": "最近使用", + "favorites": "收藏", + "whatsNew": "最新动态", + "pinned": "已固定" + }, + "groups": { + "apps": "应用" + }, + "toolCard": { + "defaultDescription": "使用此工具获得更强大的功能。", + "launchTool": "打开工具" + }, + "tools": { + "toDo": { + "title": "任务", + "description": "高效管理待办列表。" + }, + "notes": { + "title": "笔记", + "description": "记录想法并整理思路。" + }, + "passwordManager": { + "title": "密码管理器", + "description": "安全保存和管理密码。" + }, + "environmentManager": { + "title": "环境管理", + "description": "按项目与环境保存 .env 风格变量,并支持标签。" + }, + "bookmarks": { + "title": "书签", + "description": "管理与整理书签。" + }, + "apiClient": { + "title": "API 客户端", + "description": "测试与调试 HTTP 请求。" + }, + "nosqlExplorer": { + "title": "Database Explorer", + "description": "浏览 MongoDB 数据库。" + }, + "emailValidator": { + "title": "邮箱验证", + "description": "验证电子邮件地址。" + }, + "encryptionPlayground": { + "title": "加密演练场", + "description": "使用原始密钥或口令的 AES-GCM;JSON 数据包,Web Crypto 仅在浏览器中运行。" + }, + "jwtDecoder": { + "title": "JWT 解码器", + "description": "解码 JWT 的头部、载荷和过期时间。" + }, + "certificatePemDecoder": { + "title": "证书 / PEM 解码器", + "description": "解码 X.509 证书与 CSR:主题、颁发者、有效期与 SAN。" + }, + "sshKeyGenerator": { + "title": "SSH / RSA 密钥生成器", + "description": "在浏览器中生成 Ed25519 或 RSA 密钥对,以 OpenSSH 和 PEM 格式下载。" + }, + "jsonFormatter": { + "title": "JSON Formatter", + "description": "Format, validate, and edit JSON data." + }, + "sqlFormatter": { + "title": "SQL Formatter", + "description": "Format SQL for MySQL, PostgreSQL, and SQLite." + }, + "graphqlFormatter": { + "title": "GraphQL 格式化", + "description": "格式化 GraphQL,提供语法高亮与简易查询构建器。" + }, + "yamlFormatter": { + "title": "YAML 格式化", + "description": "在浏览器中格式化、验证并将 YAML 转换为 JSON。" + }, + "diffChecker": { + "title": "Diff checker", + "description": "Side-by-side line diff of two texts." + }, + "regexTester": { + "title": "Regex Tester", + "description": "Test regex with live match highlighting." + }, + "timestampConverter": { + "title": "Timestamp Converter", + "description": "Unix, ISO, and relative time conversion." + }, + "cronBuilder": { + "title": "Cron Builder", + "description": "Visual cron builder, presets, and parser." + }, + "base64Encoder": { + "title": "Base64 Encoder", + "description": "Encode and decode Base64 strings." + }, + "urlEncoder": { + "title": "URL Encoder", + "description": "Percent-encode and decode URL components." + }, + "urlParser": { + "title": "URL 解析器", + "description": "将 URL 拆分为协议、主机、路径、查询参数和哈希。" + }, + "uuidGenerator": { + "title": "UUID / ULID", + "description": "Generate UUIDs and ULIDs with bulk export." + }, + "secretApiKeyGenerator": { + "title": "密钥 / API 密钥", + "description": "可配置字符集、长度并支持批量导出的密码学随机字符串。" + }, + "loremIpsum": { + "title": "Lorem Ipsum", + "description": "Generate placeholder Latin text for layouts and mockups." + }, + "colorPicker": { + "title": "Color Picker", + "description": "Convert HEX, RGB, HSL and explore color palettes." + }, + "contrastChecker": { + "title": "对比度检查", + "description": "文本与背景的 WCAG 2.1 对比度(AA / AAA)。" + }, + "jsonSchemaGenerator": { + "title": "JSON 模式生成器", + "description": "从示例 JSON 推断 JSON 模式与类型化模型。" + }, + "imageToBase64": { + "title": "图像转 Base64", + "description": "将图像转换为数据 URI 或原始 Base64 字符串。" + }, + "imageCompressor": { + "title": "图片压缩", + "description": "在本地用质量滑块压缩 JPEG、PNG 和 WebP。" + }, + "cssGradientBuilder": { + "title": "CSS 渐变生成器", + "description": "具有角度控制和色标的视觉 CSS 渐变生成器。" + }, + "gitignoreGenerator": { + "title": ".gitignore 生成器", + "description": "立即从 500 多个技术堆栈生成合并的 .gitignore 文件。" + }, + "csvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "description": "将 CSV 或 Excel 转为 JSON,并可导出为 CSV 或 XLSX。" + }, + "snippetManager": { + "title": "代码片段", + "description": "保存代码片段,支持语法高亮、自动检测语言和格式化。" + }, + "qrCodeGenerator": { + "title": "二维码生成器", + "description": "在浏览器中从文本或 URL 生成 PNG 二维码。" + }, + "ipSubnetCalculator": { + "title": "IP / 子网计算器", + "description": "IPv4 与 IPv6 CIDR:掩码、范围与地址数量。" + }, + "dnsLookup": { + "title": "DNS 查询", + "description": "查询任意域名的 DNS 记录 — A、AAAA、MX、TXT、NS、CNAME、SOA。" + }, + "hashGenerator": { + "title": "哈希生成器", + "description": "文本或文件的 MD5、SHA-1、SHA-2。" + }, + "hmacGenerator": { + "title": "HMAC 生成器", + "description": "使用 HMAC-SHA1/256/384/512 为 Webhook 签名并测试 API 集成。" + }, + "totpGenerator": { + "title": "TOTP / 2FA 验证码生成器", + "description": "粘贴身份验证器中的 Base32 密钥;六位数字每 30 秒刷新,便于测试登录与多因素验证。" + }, + "markdownPreview": { + "title": "Markdown 预览", + "description": "实时 Markdown 渲染,支持 HTML 导出和 HTML → Markdown 转换。" + }, + "numberBaseConverter": { + "title": "进制转换器", + "description": "在 2–36 进制之间转换整数。" + }, + "formatConverter": { + "title": "格式转换器", + "description": "在 JSON、YAML、TOML 与 XML 之间任意方向转换,支持全部 12 种组合。" + }, + "mockDataGenerator": { + "title": "模拟数据生成器", + "description": "定义列、选择数据类型,并导出用于种子数据、Postman 或测试的数据集。最多 5000 行,全部在浏览器本地完成,不会上传。" + }, + "httpStatusCodes": { + "title": "HTTP 状态码", + "description": "搜索带有描述的 HTTP 状态码(1xx–5xx)。" + }, + "svgOptimizer": { + "title": "SVG 优化", + "description": "粘贴 SVG,用 SVGO 去除冗余,查看 UTF-8 体积变化。" + }, + "unitConverter": { + "title": "单位换算器", + "description": "在 43 个科学和工程类别的 323 个单位之间转换。" + }, + "dockerComposeGenerator": { + "title": "Docker Compose 生成器", + "description": "选择数据库、缓存、队列、代理与可观测性镜像;导出用于本地栈的 docker-compose.yml。" + }, + "game2048": { + "title": "2048", + "description": "滑动合并方块,合成 2048。经典数字益智游戏。" + }, + "sudoku": { + "title": "数独", + "description": "经典数字填空游戏。三种难度,每种 150 关。" + }, + "redisCommander": { + "title": "Redis 指挥官", + "description": "浏览键、检查值并在任意 Redis 实例上执行命令。" + }, + "curlToCode": { + "title": "cURL to Code", + "description": "Convert a curl command into fetch, axios, Python, Go, and more." + }, + "jsonToCode": { + "title": "JSON to Code", + "description": "Generate TypeScript, Go, Rust, Python, and more type models from sample JSON." + }, + "beautifyMinify": { + "title": "Beautify & Minify", + "description": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON with a language dropdown." + }, + "stringCaseConverter": { + "title": "String Case Converter", + "description": "Convert text between camelCase, snake_case, kebab-case, CONSTANT_CASE, and more." + }, + "lineSortDedupe": { + "title": "Line Sort & Dedupe", + "description": "Sort, deduplicate, trim, and clean up lists of lines locally." + }, + "stringInspector": { + "title": "String Inspector", + "description": "Count characters, words, bytes, and detect invisible or non-ASCII characters." + }, + "escapeEncode": { + "title": "Escape / Encode", + "description": "Escape and unescape HTML entities, backslash strings, hex, and Unicode." + }, + "bcryptGenerator": { + "title": "Bcrypt 生成与校验", + "description": "使用 bcrypt 哈希密码并校验现有哈希值。" + }, + "chmodCalculator": { + "title": "Chmod 计算器", + "description": "在八进制、符号表示和复选框之间转换 Unix 权限。" + }, + "codeScreenshot": { + "title": "代码截图", + "description": "将源代码转换为精美、可分享的图片。" + }, + "cssGenerators": { + "title": "CSS 生成器", + "description": "Box-shadow、border-radius 和流式 clamp() 生成器。" + }, + "exifViewer": { + "title": "EXIF 查看与移除", + "description": "在本地检查并清除照片的 EXIF 元数据。" + }, + "faviconGenerator": { + "title": "Favicon 生成器", + "description": "生成 favicon、应用图标和清单代码片段。" + }, + "jsonDiff": { + "title": "JSON 对比", + "description": "对两个 JSON 文档进行语义化、结构感知的对比。" + }, + "jsonpathPlayground": { + "title": "JSONPath 沙盒", + "description": "使用 JSONPath 或 JMESPath 查询 JSON 并实时查看结果。" + }, + "keycodeInspector": { + "title": "键码检查器", + "description": "按下按键即可查看其 JavaScript 事件码和修饰键。" + }, + "markdownTableGenerator": { + "title": "Markdown 表格生成器", + "description": "在类似电子表格的网格编辑器中构建 Markdown 和 HTML 表格。" + }, + "timezoneConverter": { + "title": "时区转换器", + "description": "跨多个时区转换时间,并提示日期偏移和工作时间。" + }, + "tokenCounter": { + "title": "LLM Token 计数器", + "description": "统计 LLM token 数量、可视化分块并估算 API 成本。" + }, + "webhookTester": { + "title": "Webhook 测试器", + "description": "实时捕获并检查传入的 webhook 请求。" + }, + "websocketTester": { + "title": "WebSocket 测试器", + "description": "连接 WebSocket 端点并实时收发消息。" + }, + "whoisLookup": { + "title": "Whois 查询", + "description": "通过 RDAP 查询域名和 IP 的注册信息。" + } + }, + "tabs": { + "apps": "应用", + "analytics": "分析", + "ui": "界面" + }, + "analytics": { + "title": "数据一览", + "subtitle": "与服务器账户同步的计数。", + "loadError": "无法加载分析数据。", + "retry": "重试", + "loginHint": "登录后可查看使用分析与界面偏好。", + "passwordEntries": "密码条目", + "bookmarks": "书签", + "bookmarkFolders": "书签文件夹", + "tasksTotal": "任务", + "tasksCompleted": "已完成", + "tasksOngoing": "进行中", + "tasksNotStarted": "未开始", + "projects": "项目", + "nosqlConnections": "数据库连接", + "notes": "笔记", + "apiClientCollections": "API 集合", + "apiClientEnvironments": "API 环境", + "apiClientHistory": "API 历史记录", + "jsonFormatterDocuments": "JSON 格式化器文档", + "heroBadge": "Live sync", + "totalLabel": "Tracked items", + "totalHint": "Sum of every category below", + "groupVault": "Vault & bookmarks", + "groupWorkspace": "Workspace", + "groupToolkit": "Developer toolkit", + "taskMixTitle": "Task status", + "taskMixEmpty": "No tasks yet — add some in Tasks.", + "loadingLabel": "Syncing your stats…", + "codeSnippets": "代码片段", + "activityTitle": "Activity", + "activityEmpty": "Activity builds as you use tools", + "distributionTitle": "Data distribution", + "topToolsTitle": "Top tools", + "taskDonutTitle": "Task completion" + }, + "ui": { + "title": "界面偏好", + "subtitle": "主题、语言与侧边栏显示的工具 — 来自账户设置。", + "loadError": "无法加载偏好设置。", + "loginHint": "登录后可查看已保存的界面偏好。", + "theme": "主题", + "accent": "强调色", + "locale": "语言", + "enabledTools": "显示的工具", + "favoriteTools": "收藏的工具", + "toolsWithUsage": "已记录使用情况的工具" + }, + "signIn": "登录", + "noResults": "没有工具与您的搜索匹配", + "noResultsHint": "尝试不同的关键词或清除筛选条件。", + "filterAll": "全部", + "loginCta": { + "description": "登录以跟踪您最近使用的工具。" + }, + "popularTools": "Popular tools", + "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", + "viewActivity": "Recent activity", + "activity": { + "title": "Activity log", + "subtitle": "Everything you changed, when, and from which device.", + "filterModule": "Module", + "filterOutcome": "Outcome", + "filterRange": "Time range", + "search": "Search activity…", + "all": "All", + "success": "Success", + "failure": "Failure", + "today": "Today", + "yesterday": "Yesterday", + "last7": "7 days", + "last30": "30 days", + "allTime": "All time", + "resetFilters": "Reset filters", + "activeFilters": "{count, plural, =0 {No filters} =1 {1 filter} other {# filters}} applied", + "empty": "No activity yet.", + "emptyFiltered": "No activity matches the current filters.", + "loadError": "Could not load activity.", + "loadMore": "Load more", + "loadingMore": "Loading…", + "showingOf": "Showing {shown} of {total}", + "changedFields": "What changed", + "details": "Details", + "hideDetails": "Hide details", + "device": "Device", + "ipAddress": "IP", + "latency": "Latency", + "endpoint": "Endpoint", + "status": "Status", + "when": "When", + "actionKey": "Action", + "before": "Before", + "after": "After", + "viewAll": "View all activity", + "moduleAuth": "Account", + "moduleBookmarks": "Bookmarks", + "moduleBookmarkFolders": "Bookmark folders", + "moduleNotes": "Notes", + "moduleTasks": "Tasks", + "modulePasswords": "Passwords", + "moduleSnippets": "Snippets", + "moduleApiClient": "API client", + "moduleSqlClient": "SQL client", + "moduleNosql": "NoSQL", + "moduleEnvManager": "Environments", + "modulePrefs": "Preferences", + "moduleProjects": "Projects", + "moduleUrlShortener": "URL shortener", + "moduleGameScores": "Game scores", + "moduleFeedback": "Feedback" + } + }, + "Tasks": { + "page": { + "loading": "加载中…", + "myTasksTitle": "我的任务", + "statsLine": "{total} 个任务 · 已完成 {percent}%", + "statsLineDesktop": "共 {total} 个 · 已完成 {percent}%" + }, + "filters": { + "all": "全部", + "total": "总计", + "allProjects": "所有项目", + "export": "导出", + "kanban": "看板", + "list": "列表", + "searchPlaceholder": "搜索任务…", + "searchAria": "搜索任务", + "clearSearchAria": "清除搜索", + "foundTasks": "找到 {count} {count, plural, one {个任务} other {个任务}}", + "filterByAllAria": "按全部筛选", + "filterByStatusAria": "按 {status} 筛选" + }, + "status": { + "not-started": { + "label": "未开始", + "description": "尚未开始的任务" + }, + "ongoing": { + "label": "进行中", + "description": "正在进行的任务" + }, + "completed": { + "label": "已完成", + "description": "已完成的任务" + } + }, + "priorities": { + "high": "高", + "medium": "中", + "low": "低" + }, + "form": { + "addNewTaskLabel": "添加新任务", + "multilinePlaceholder": "添加任务详情…(Shift+Enter 换行)", + "singlelinePlaceholder": "要做什么?按 Enter 添加", + "taskInputAria": "任务输入", + "projectPlaceholder": "项目", + "selectProject": "选择项目", + "errorSelectProject": "请选择项目或新建项目后再添加任务。", + "hintMultiline": "Shift+Enter 换行,Enter 添加", + "hintSingleline": "Enter 添加", + "shortcutsAria": "键盘快捷键", + "shortcutsLabel": "快捷键", + "shortcutAddTask": "添加任务", + "shortcutNewLine": "换行", + "shortcutClear": "清空", + "addButton": "添加" + }, + "list": { + "ariaLabel": "任务列表", + "loading": "正在加载任务…", + "emptyTitle": "暂无任务", + "emptyPrimary": "在上方添加第一个任务即可开始。按 Enter 可快速添加。", + "emptySecondary": "试试调整筛选条件或搜索关键词。" + }, + "mobileDrawer": { + "addTaskSrOnly": "添加任务", + "addNewTaskTitle": "添加新任务", + "addNewTaskDescription": "创建新任务以跟踪进度。", + "cancel": "取消" + }, + "kanban": { + "ariaLabel": "看板", + "loading": "正在加载任务…", + "dropHere": "将任务拖放到此处", + "noTasksYet": "暂无任务", + "emptyColumnAria": "空列", + "columnAria": "{title} 列,共 {count} 个任务", + "countInColumnAria": "{title} 中有 {count} 个任务" + }, + "taskItem": { + "confirmDelete": "确定要删除此任务吗?", + "statusPlaceholder": "状态", + "changeStatusAria": "更改任务状态", + "taskOptionsAria": "任务选项", + "editTask": "编辑任务", + "copyTask": "复制任务", + "copied": "已复制!", + "deleteTask": "删除任务", + "statusHeading": "状态", + "editTaskAria": "编辑任务" + }, + "editDialog": { + "title": "编辑任务", + "description": "更新任务详情、添加子任务、设置优先级等。", + "taskTitleLabel": "任务标题 *", + "taskTitlePlaceholder": "要做什么?", + "descriptionLabel": "描述", + "descriptionPlaceholder": "补充此任务的更多说明…", + "statusLabel": "状态", + "priorityLabel": "优先级", + "dueDateLabel": "截止日期", + "pickDate": "选择日期", + "clearDate": "清除日期", + "projectLabel": "项目", + "selectProjectPlaceholder": "选择项目", + "noProject": "无项目", + "timeEstimateLabel": "预计耗时(分钟)", + "timeEstimatePlaceholder": "大约需要多久?", + "tagsLabel": "标签", + "customTagNamePlaceholder": "自定义标签名", + "subtasksLabel": "子任务", + "addSubtaskPlaceholder": "添加子任务…", + "cancel": "取消", + "saving": "保存中…", + "saveChanges": "保存更改" + }, + "export": { + "title": "导出任务", + "description": "将任务导出为 JSON、CSV 或 Excel。", + "projectLabel": "项目", + "statusLabel": "状态", + "tagLabel": "标签", + "allProjects": "所有项目", + "allStatuses": "所有状态", + "allTags": "所有标签", + "exportAsJsonTitle": "导出为 JSON", + "exportAsJsonDescription": "导出包含子任务、标签与元数据的任务数据。", + "exportJson": "导出 JSON", + "exportAsCsvExcelTitle": "导出为 CSV / Excel", + "exportAsCsvExcelDescription": "导出任务以便在表格软件中使用。", + "exportCsv": "导出 CSV", + "exportExcel": "导出 Excel", + "close": "关闭", + "toastNoTasks": "当前筛选条件下没有可导出的任务", + "toastExportedJson": "已将 {count} 个任务导出为 JSON!", + "toastExportedCsv": "已将 {count} 个任务导出为 CSV!", + "toastExportedExcel": "已将 {count} 个任务导出为 Excel!", + "toastFailedJson": "导出 JSON 失败", + "toastFailedCsv": "导出 CSV 失败", + "toastFailedExcel": "导出 Excel 失败", + "unknownProject": "未知项目" + }, + "ack": { + "taskAddedTitle": "任务添加成功", + "taskAddedFailedTitle": "任务添加失败", + "taskUpdatedTitle": "任务更新成功", + "taskUpdatedFailedTitle": "任务更新失败", + "taskStatusUpdateFailedTitle": "任务状态更新失败", + "taskMovedTitle": "任务已移至 {status}", + "tryAgain": "请重试。", + "taskDeletedTitle": "已删除任务「{text}」", + "undo": "撤销", + "taskRestoredTitle": "任务已恢复", + "taskRestoredSuccessTitle": "任务恢复成功", + "taskRestoreFailedTitle": "恢复任务失败,请重试。", + "taskDeleteFailedTitle": "删除任务失败,请重试。", + "tasksImportedTitle": "成功导入 {count} {count, plural, one {个任务} other {个任务}}!", + "tasksImportFailedTitle": "导入任务失败,请重试。", + "unknownProject": "未知项目", + "projectCreatedTitle": "项目创建成功", + "projectCreateFailedTitle": "项目创建失败", + "projectUpdatedTitle": "项目更新成功", + "projectUpdateFailedTitle": "项目更新失败", + "projectDeletedTitle": "项目删除成功", + "projectDeleteFailedTitle": "项目删除失败" + } + }, + "Notes": { + "page": { + "loading": "加载中…" + }, + "layout": { + "mobileNavTitle": "笔记导航", + "openMenuAria": "打开笔记菜单" + }, + "sidebar": { + "title": "笔记", + "createNoteAria": "创建笔记", + "searchPlaceholder": "搜索笔记…", + "loading": "加载中…", + "noNotesFound": "未找到笔记。", + "noNotesYet": "暂无笔记。点击 + 创建。", + "inParent": "位于 {parentTitle}", + "untitled": "无标题", + "addSubPage": "添加子页面", + "delete": "删除", + "deleteConfirmTitle": "删除笔记?", + "deleteConfirmDescription": "确定要删除「{title}」吗?此操作无法撤销。", + "cancel": "取消", + "confirmDelete": "删除", + "noteOptionsAria": "笔记选项" + }, + "editor": { + "emptyState": "选择一篇笔记或新建一篇以开始。", + "titlePlaceholder": "无标题", + "saving": "保存中…", + "ready": "就绪", + "saved": "已于 {time} 保存", + "stats": "{words} 字 · {read} 分钟阅读", + "template": "模板", + "applyTemplate": "应用模板", + "exportMdLabel": ".md", + "exportMd": "导出为 Markdown", + "exportHtmlLabel": ".html", + "exportHtml": "导出为 HTML", + "focusMode": "专注模式", + "exitFocusMode": "退出专注模式", + "save": "保存", + "addTag": "添加标签…", + "removeTag": "移除标签 {tag}", + "loading": "加载中…", + "searchHint": "以搜索", + "focusHint": "专注模式", + "notePath": "笔记路径" + }, + "context": { + "authRequiredError": "用户未登录", + "defaultTitle": "无标题", + "cloudSyncImageError": "开启云同步以存储笔记图片。文本笔记保留在本地。" + } + }, + "PasswordManager": { + "page": { + "loading": "加载中…", + "title": "密码管理器", + "loadFailed": "Failed to load passwords" + }, + "vault": { + "toastDecryptFailed": "密码解密失败", + "toastLoadFailed": "加载密码失败", + "toastUnlockedAuto": "保险库已自动解锁", + "toastAutoUnlockFailed": "自动解锁失败,请手动解锁。", + "toastConnectFailed": "连接服务器失败", + "toastAutoUnlockWarning": "保险库已解锁,但刷新后自动解锁可能失效", + "errorIncorrectPassword": "主密码不正确", + "errorUnlockFailed": "解锁保险库失败", + "errorPasswordsMismatch": "两次输入的密码不一致", + "errorPasswordTooShort": "密码至少需要 8 个字符", + "errorSetupFailed": "设置保险库失败", + "errorConnectFailed": "连接服务器失败。", + "mobileTitleCreate": "创建你的保险库", + "mobileTitleWelcome": "欢迎回来", + "mobileSubtitleCreate": "设置主密码以保护你的凭据", + "mobileSubtitleUnlock": "输入主密码以解锁", + "placeholderMasterPassword": "主密码", + "placeholderConfirmPassword": "确认密码", + "labelMasterPassword": "主密码", + "labelConfirmPassword": "确认密码", + "placeholderEnterMaster": "输入主密码", + "placeholderConfirmMaster": "再次确认主密码", + "buttonCreateVault": "创建保险库", + "buttonUnlock": "解锁", + "buttonUnlockVault": "解锁保险库", + "footerCreate": "你的密码不会被存储且无法找回", + "footerUnlock": "采用端到端加密保护", + "desktopTitleSetup": "设置密码保险库", + "desktopTitleUnlock": "解锁保险库", + "desktopDescriptionSetup": "创建主密码以保护凭据。该密码不会被存储且无法找回。", + "desktopDescriptionUnlock": "输入主密码以访问已加密的凭据。" + }, + "list": { + "loading": "正在加载密码…", + "emptyTitle": "保险库为空", + "emptyHintMobile": "点击 + 添加第一条密码", + "emptyHintDesktop": "尚未保存任何密码。点击上方「添加密码」开始。", + "addPassword": "添加密码", + "searchPlaceholderMobile": "搜索保险库…", + "searchPlaceholderDesktop": "搜索保险库…", + "importExport": "导入 / 导出", + "lockVault": "锁定保险库", + "allPasswordsTitle": "全部密码", + "passwordCount": "{count, plural, one {# 条密码} other {# 条密码}}", + "vaultHealthDrawerTitle": "保险库健康度", + "noResults": "未找到匹配「{query}」的密码", + "tableService": "服务", + "tableUsername": "用户名", + "tablePassword": "密码", + "tableUrl": "网址", + "tableActions": "操作", + "gridViewAria": "网格视图", + "listViewAria": "列表视图", + "passwordStrengthTitle": "密码强度", + "deleteDialogTitle": "删除密码", + "deleteDialogDescription": "确定要删除此密码吗?此操作无法撤销。", + "cancel": "取消", + "delete": "删除", + "edit": "编辑" + }, + "toasts": { + "copiedPassword": "密码已复制到剪贴板", + "copiedUsername": "用户名已复制到剪贴板", + "passwordDeleted": "密码已删除", + "deleteFailed": "删除密码失败", + "vaultLocked": "保险库已锁定", + "importedCount": "成功导入 {count, plural, one {# 条密码} other {# 条密码}}" + }, + "form": { + "addTitle": "添加新密码", + "editTitle": "编辑密码", + "serviceName": "服务名称", + "usernameEmail": "用户名 / 邮箱", + "password": "密码", + "urlOptional": "网站地址(可选)", + "notesOptional": "备注(可选)", + "tags": "标签", + "placeholderService": "例如 Netflix", + "placeholderUsername": "user@example.com", + "placeholderPassword": "输入或生成密码", + "placeholderUrl": "https://…", + "placeholderNotes": "补充说明…", + "placeholderTags": "添加标签(按 Enter 或逗号)", + "strength": "强度", + "strengthWeak": "弱", + "strengthMedium": "中", + "strengthStrong": "强", + "advancedGenerator": "高级生成器", + "cancel": "取消", + "saving": "保存中…", + "savePassword": "保存密码", + "updating": "更新中…", + "updatePassword": "更新密码", + "addPasswordSrOnly": "添加密码", + "triggerAdd": "添加密码", + "toastSaved": "密码保存成功", + "toastSaveFailed": "保存密码失败", + "toastUpdated": "密码更新成功", + "toastUpdateFailed": "更新密码失败" + }, + "security": { + "vaultHealth": "保险库健康度", + "statusLabel": "状态", + "totalPasswords": "密码总数", + "activeEntries": "保险库中的有效条目", + "protected": "已保护", + "weakPasswords": "弱密码", + "needingImprovement": "需要加强的密码", + "allGood": "一切良好", + "reusedPasswords": "重复使用的密码", + "usedMultipleTimes": "在多处使用的密码", + "unique": "唯一", + "total": "总计", + "weak": "弱", + "reused": "重复", + "totalCountLabel": "{count, plural, one {# 条密码} other {# 条密码}}", + "weakCountLabel": "{count} 个弱密码", + "allSecure": "全部安全", + "scoreExcellent": "优秀", + "scoreGood": "良好", + "scoreFair": "一般", + "scoreCritical": "危急" + }, + "importExport": { + "title": "导入 / 导出保险库", + "description": "管理密码数据。可将保险库导出为 JSON,或从 JSON 文件导入。", + "tabExport": "导出", + "tabImport": "导入", + "warningTitle": "警告", + "warningBody": "导出文件中的密码为明文。请妥善保管并在使用后删除。", + "exportBlurb": "将整个保险库下载为 JSON,包含所有服务、用户名、密码与备注。", + "downloadJson": "下载 JSON 导出", + "pasteLabel": "粘贴 JSON 数据", + "pastePlaceholder": "[{\"service\": \"Example\", \"username\": \"user\", \"password\": \"123\", ...}]", + "pasteHint": "在此粘贴先前导出的 JSON 文件内容。", + "importing": "导入中…", + "importPasswords": "导入密码", + "trigger": "导入 / 导出", + "toastExported": "保险库导出成功", + "toastExportFailed": "导出保险库失败", + "toastImportFailed": "导入失败:JSON 格式无效" + }, + "card": { + "copyUsername": "复制用户名", + "openUrl": "打开链接", + "strength": "强度" + }, + "swipeable": { + "copy": "复制", + "delete": "删除", + "copyPassword": "复制密码", + "copyUsername": "复制用户名", + "edit": "编辑", + "deleteAction": "删除" + }, + "floatingMenu": { + "lockVault": "锁定保险库", + "importExport": "导入 / 导出", + "addPassword": "添加密码" + }, + "generator": { + "tabPassword": "密码", + "tabPassphrase": "口令短语", + "strength": "强度", + "lengthLabel": "长度:{length} 个字符", + "lengthBadge": "{length} 字符", + "characterSets": "字符集", + "uppercase": "大写", + "lowercase": "小写", + "numbers": "数字", + "symbols": "符号", + "wordCountLabel": "词数:{count}", + "wordCountBadge": "{count, plural, one {# 个词} other {# 个词}}", + "separator": "分隔符", + "capitalization": "大小写", + "includeNumber": "包含数字" + } + }, + "EnvironmentManager": { + "subtitle": "按项目与环境加密存储的环境变量。", + "page": { + "loading": "加载中…", + "title": "环境管理", + "loadFailed": "无法加载环境集合" + }, + "list": { + "title": "环境", + "loading": "正在加载集合…", + "emptyTitle": "尚无环境集合", + "emptyHint": "为每个项目与环境创建集合(例如生产环境 API 密钥)。上传前均在浏览器中加密。", + "searchPlaceholder": "搜索项目、环境、标签、备注…", + "tagFilterPlaceholder": "按标签筛选", + "variableCount": "{count, plural, other {# 个变量}}", + "updatedAgo": "更新于 {time}", + "copyDotEnv": "复制 .env", + "edit": "编辑", + "delete": "删除", + "cancel": "取消", + "confirmDelete": "删除", + "deleteDialogTitle": "删除环境集合?", + "deleteDialogDescription": "将从账户中移除加密数据,无法撤销。", + "view": "View", + "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", + "viewNoVariables": "No variables in this set.", + "viewNotesHeading": "Notes", + "closeView": "Close", + "noResultsTitle": "No results", + "noResultsHint": "No sets match your search or filter. Try adjusting your query.", + "sort": { + "updatedDesc": "Newest first", + "updatedAsc": "Oldest first", + "nameAsc": "Name A→Z" + } + }, + "toasts": { + "copiedDotEnv": "已复制 .env 到剪贴板", + "deleted": "已删除环境集合", + "deleteFailed": "删除失败" + }, + "form": { + "addTitle": "新建环境集合", + "editTitle": "编辑环境集合", + "triggerAdd": "添加环境集合", + "addSrOnly": "添加环境集合", + "project": "项目", + "environment": "环境", + "placeholderProject": "例如 billing-api", + "placeholderEnvironment": "例如 production", + "pasteEnvLabel": "粘贴 .env", + "pasteEnvPlaceholder": "FOO=bar\nBAZ=qux", + "applyPaste": "合并粘贴的行", + "variables": "变量", + "addRow": "添加行", + "keyPlaceholder": "键", + "valuePlaceholder": "值", + "notesOptional": "备注(可选)", + "placeholderNotes": "轮换策略、负责人、链接…", + "tags": "标签", + "placeholderTags": "添加标签(回车或逗号)", + "cancel": "取消", + "save": "保存", + "saving": "保存中…", + "update": "更新", + "updating": "更新中…", + "toastSaved": "已保存环境集合", + "toastSaveFailed": "保存失败", + "toastUpdated": "已更新环境集合", + "toastUpdateFailed": "更新失败", + "toastPasteEmpty": "未找到 键=值 行", + "toastPasteApplied": "从粘贴合并了 {count} 个变量", + "toastProjectEnvRequired": "项目和环境为必填", + "pasteEnvHint": "Paste a .env here — active variables merge automatically. Lines starting with # are skipped unless you add them below.", + "pasteSkippedTitle": "Some lines were skipped", + "pasteCommentedSkipped": "{count, plural, one {# commented variable (# KEY=value) was not added} other {# commented variables (# KEY=value) were not added}}.", + "pastePlainCommentsSkipped": "{count, plural, one {# plain comment line was ignored} other {# plain comment lines were ignored}}.", + "addCommentedVariables": "Add commented variables", + "toastPasteAutoMerged": "{count, plural, one {Merged # variable from paste} other {Merged # variables from paste}}", + "toastPasteCommentedOnly": "Only commented-out variables found — add them with the button below if you need them.", + "toastCommentedAdded": "{count, plural, one {Added # commented variable} other {Added # commented variables}}", + "showValues": "Show values", + "hideValues": "Hide values" + } + }, + "Bookmarks": { + "page": { + "loading": "加载中…" + }, + "manager": { + "foldersHeading": "文件夹", + "tagsHeading": "标签", + "tagsMore": "另外 {count} 个", + "tagsShowLess": "Show less", + "searchPlaceholder": "搜索书签…", + "import": "导入", + "export": "导出", + "exportAsHtml": "导出为 HTML", + "exportAsJson": "导出为 JSON", + "addBookmark": "添加书签", + "matchingQuery": "匹配「{query}」", + "bookmarkCount": "{count, plural, one {# 个书签} other {# 个书签}}", + "allBookmarks": "全部书签", + "uncategorized": "未分类", + "unknownFolder": "未知" + }, + "card": { + "urlCopied": "链接已复制到剪贴板", + "bookmarkDeleted": "书签已删除", + "edit": "编辑", + "delete": "删除", + "copyUrl": "复制链接", + "deleteConfirmTitle": "删除书签?", + "deleteConfirmDescription": "将永久删除「{title}」。", + "cancel": "取消" + }, + "grid": { + "emptyTitle": "暂无书签", + "emptyDescription": "添加第一个书签或从浏览器导入以开始。" + }, + "folderTree": { + "allBookmarks": "全部书签", + "addSubfolder": "添加子文件夹", + "rename": "重命名", + "deleteFolderTitle": "删除文件夹「{name}」?", + "deleteFolderDescription": "将删除该文件夹及其所有子文件夹。其中的书签将移至未分类。" + }, + "import": { + "title": "导入书签", + "description": "从浏览器导出的文件导入书签。", + "parseError": "解析书签文件失败,请确认是有效的书签导出文件。", + "successToast": "已导入 {bookmarks} 个书签和 {folders} 个文件夹", + "errorToast": "导入书签失败", + "chromeInstruction": "菜单 → 书签 → 书签管理器 → ⋮ → 导出书签", + "firefoxInstruction": "菜单 → 书签 → 管理书签 → 导入和备份 → 导出", + "safariInstruction": "文件 → 导出书签…", + "edgeInstruction": "菜单 → 收藏夹 → ⋯ → 导出收藏夹", + "dropZoneTitle": "将书签文件拖放到此处", + "dropZoneHint": "或点击浏览(HTML 或 JSON)", + "selectedFileHint": "点击选择其他文件", + "errorTitle": "错误", + "previewTitle": "导入预览", + "previewBookmarks": "{count, plural, one {# 个书签} other {# 个书签}}", + "previewFolders": "{count, plural, one {# 个文件夹} other {# 个文件夹}}", + "importModeLabel": "导入模式", + "modeMerge": "与现有书签合并", + "modeReplace": "替换全部现有书签", + "cancel": "取消", + "importButton": "导入", + "importButtonWithCount": "导入 {count, plural, one {# 个书签} other {# 个书签}}" + }, + "addBookmark": { + "titleAdd": "添加书签", + "titleEdit": "编辑书签", + "urlLabel": "网址", + "urlPlaceholder": "https://example.com", + "urlInvalid": "请输入有效的网址", + "titleLabel": "标题", + "titlePlaceholder": "书签标题", + "titleRequired": "请输入标题", + "descriptionLabel": "描述(可选)", + "descriptionPlaceholder": "为此书签添加备注", + "folderLabel": "文件夹", + "folderPlaceholder": "选择文件夹", + "noFolder": "无文件夹", + "tagsLabel": "标签", + "tagsPlaceholder": "添加标签(按 Enter)", + "toastUpdated": "书签已更新", + "toastAdded": "书签已添加", + "cancel": "取消", + "submitAdd": "添加书签", + "submitUpdate": "更新书签" + }, + "addFolder": { + "title": "创建文件夹", + "nameLabel": "文件夹名称", + "namePlaceholder": "我的文件夹", + "nameRequired": "请输入文件夹名称", + "parentLabel": "父文件夹", + "parentPlaceholder": "选择父文件夹", + "rootParent": "根目录(无父级)", + "toastCreated": "文件夹已创建", + "cancel": "取消", + "submit": "创建文件夹" + }, + "editFolder": { + "title": "重命名文件夹", + "nameLabel": "文件夹名称", + "namePlaceholder": "文件夹名称", + "nameRequired": "请输入文件夹名称", + "toastRenamed": "文件夹已重命名", + "cancel": "取消", + "save": "保存" + } + }, + "EmailValidator": { + "header": { + "title": "邮箱验证", + "description": "校验邮箱地址的语法、域名与可投递性。" + }, + "tabs": { + "single": "单个验证", + "bulk": "批量上传" + }, + "single": { + "cardTitle": "单个邮箱验证", + "cardDescription": "输入邮箱地址以检查可投递性与信誉。", + "placeholder": "输入邮箱地址…", + "clearAria": "清除邮箱", + "validate": "验证", + "validating": "验证中…", + "formatError": "请输入有效的邮箱地址" + }, + "toasts": { + "errorTitle": "错误", + "enterEmail": "请输入邮箱地址", + "invalidFormatTitle": "邮箱格式无效", + "invalidFormatDescription": "请输入有效的邮箱地址", + "validateFailedDescription": "验证邮箱时发生错误", + "noEmailsTitle": "未找到邮箱", + "noEmailsDescription": "请确保 Excel 中包含「email」列。", + "fileTooLargeTitle": "文件过大", + "fileTooLargeDescription": "共发现 {total} 个邮箱,仅验证前 4000 个。", + "successTitle": "成功", + "successValidated": "已成功验证 {count} 个邮箱。", + "batchFailedTitle": "批量验证失败", + "batchFailedDescription": "处理批量验证时发生错误。", + "noExportTitle": "无可导出数据", + "noExportValidOnly": "没有可导出的有效邮箱。", + "noExportAll": "没有可导出的结果。", + "exportSuccessTitle": "导出成功", + "exportSuccessExcelAll": "已将 {count} 个邮箱导出到 Excel。", + "exportSuccessExcelValid": "已将 {count} 个有效邮箱导出到 Excel。", + "exportSuccessCsvAll": "已将 {count} 个邮箱导出到 CSV。", + "exportSuccessCsvValid": "已将 {count} 个有效邮箱导出到 CSV。" + }, + "disposableAlert": { + "title": "检测到临时邮箱", + "description": "该地址属于临时邮箱服务,可能不稳定或不安全。" + }, + "score": { + "reliabilityTitle": "可靠性评分", + "scoreLabel": "分数" + }, + "details": { + "sectionTitle": "验证详情" + }, + "validation": { + "syntaxLabel": "语法有效", + "syntaxTooltip": "检查邮箱是否符合正确格式(例如 user@domain.com)", + "domainLabel": "域名存在", + "domainTooltip": "验证邮箱中的域名是否真实存在", + "mxLabel": "已找到 MX 记录", + "mxTooltip": "检查域名是否配置了用于接收邮件的 MX 记录", + "mailboxLabel": "邮箱存在", + "mailboxTooltip": "验证该邮箱账户在邮件服务器上是否存在", + "disposableLabel": "临时邮箱", + "disposableTooltip": "标识是否属于临时/一次性邮箱服务(可能有风险)", + "roleBasedLabel": "角色邮箱", + "roleBasedTooltip": "标识是否为角色类邮箱(如 support@、info@)而非个人地址" + }, + "copy": { + "emailCopied": "邮箱已复制!", + "copyTooltip": "复制邮箱" + }, + "bulk": { + "dropHere": "将文件拖放到此处", + "dragHere": "将文件拖放到此处", + "uploadHint": "上传包含「email」列的 .xlsx、.xls 或 .csv 文件,最多验证 4000 个邮箱。", + "filePrefix": "文件:", + "selectFile": "选择文件", + "downloadTemplate": "下载模板" + }, + "bulkLoading": { + "title": "正在验证邮箱…", + "batchProgress": "正在处理第 {current} / {total} 批…" + }, + "stats": { + "totalProcessed": "已处理总计", + "valid": "有效", + "risky": "有风险", + "invalid": "无效" + }, + "results": { + "detailedTitle": "详细结果", + "searchPlaceholder": "搜索邮箱…", + "newVerification": "新的验证", + "exportAll": "全部导出", + "exportExcel": "Excel(.xlsx)", + "exportCsv": "CSV(.csv)", + "exportValid": "导出有效({count})" + }, + "showingResults": "显示 {shown} / {total} 条结果", + "table": { + "email": "邮箱", + "status": "状态", + "score": "分数", + "checks": "检查项" + }, + "noSearchResults": "未找到匹配「{query}」的结果", + "checksTooltip": { + "syntax": "语法", + "domain": "域名", + "mx": "MX", + "mailbox": "邮箱" + }, + "exportColumns": { + "email": "邮箱", + "status": "状态", + "score": "分数", + "syntax": "语法", + "domain": "域名", + "mx": "MX", + "mailbox": "邮箱", + "disposable": "临时邮箱", + "roleBased": "角色邮箱", + "valid": "有效", + "invalid": "无效", + "yes": "是", + "no": "否" + }, + "sheets": { + "validEmails": "有效邮箱", + "results": "结果", + "template": "模板" + } + }, + "NoSqlExplorer": { + "page": { + "heroTitle": "NoSQL 浏览器", + "heroSubtitle": "连接数据库,管理集合、执行查询并浏览数据。", + "featureMultiTitle": "多连接", + "featureMultiDesc": "同时管理多个数据库连接。", + "featureMongoTitle": "MongoDB 支持", + "featureMongoDesc": "原生支持 MongoDB,含高级筛选。", + "featureQueryTitle": "智能查询", + "featureQueryDesc": "强大的查询构建器,支持语法高亮。", + "connectCta": "连接 MongoDB", + "footerNote": "当前支持 {db},更多数据库即将推出。", + "footerDb": "MongoDB", + "selectCollectionTitle": "选择集合", + "selectCollectionDesc": "从侧边栏选择一个集合以查看文档并开始浏览。", + "dialogAddConnection": "添加连接", + "toastConnectionAdded": "连接已添加", + "toastFetchFailed": "获取集合 {collection} 的文档失败", + "toastDocDeleted": "文档已删除", + "deleteDocTitle": "确定要执行此操作吗?", + "deleteDocDescription": "此操作无法撤销,将从集合中永久删除该文档。", + "closeAllTitle": "关闭所有标签?", + "closeAllDescription": "确定要关闭所有标签吗?查询筛选中未保存的更改将丢失。", + "closeAllConfirm": "全部关闭", + "cancel": "取消", + "delete": "删除" + }, + "connection": { + "titleEdit": "编辑连接", + "titleConnect": "连接 MongoDB", + "subtitleEdit": "在下方更新连接信息。", + "subtitleConnect": "输入 MongoDB 连接字符串以开始浏览数据。", + "labelName": "名称", + "placeholderName": "例如 生产库", + "labelConnectionString": "连接字符串", + "placeholderConnectionString": "mongodb://localhost:27017", + "hintFormat": "格式:mongodb://用户名:密码@主机:端口/数据库", + "testConnection": "测试连接", + "connecting": "连接中…", + "updateConnect": "更新并连接", + "connect": "连接", + "cancelEdit": "取消编辑", + "savedConnections": "已保存的连接", + "emptySaved": "暂无已保存的连接。", + "lastUsed": "上次使用 {time}", + "toastTestOk": "连接成功!", + "toastTestFail": "连接失败:{message}", + "toastUpdated": "连接已更新", + "confirmDelete": "确定要删除此已保存的连接吗?", + "toastDeletedConn": "连接已删除", + "toastDeleteFail": "删除连接失败", + "copyStringTitle": "复制连接字符串", + "toastStringCopied": "连接字符串已复制!", + "editTitle": "编辑", + "deleteTitle": "删除" + }, + "sidebar": { + "explorer": "浏览器", + "add": "添加", + "tooltipAdd": "添加新连接", + "tooltipRefresh": "刷新连接", + "searchPlaceholder": "搜索…", + "loadingConnections": "正在加载连接…", + "noConnectionsFound": "未找到连接", + "toastLoadFail": "加载连接失败", + "toastConnectFail": "连接 {name} 失败", + "toastCollectionsFail": "获取 {name} 的集合失败", + "toastRenamed": "连接已重命名", + "toastRenameFail": "重命名连接失败", + "confirmDeleteConnection": "确定要删除连接「{name}」吗?", + "toastDeleted": "连接已删除", + "toastDeleteConnFail": "删除连接失败", + "confirmDropDb": "确定要删除数据库「{name}」吗?此操作无法撤销。", + "toastDbDropped": "已删除数据库 {name}", + "confirmDropCollection": "确定要删除集合「{name}」吗?此操作无法撤销。", + "toastCollectionDropped": "已删除集合 {name}", + "toastCollectionRenamed": "集合已重命名为 {name}", + "toastDatabaseRenamed": "数据库已重命名为 {name}", + "connecting": "连接中…", + "connectionFailed": "连接失败", + "noDatabases": "无数据库", + "loading": "加载中…", + "noCollections": "无集合", + "renameCollectionTitle": "重命名集合", + "renameDatabaseTitle": "重命名数据库", + "placeholderNewCollection": "新集合名称", + "placeholderNewDatabase": "新数据库名称", + "rename": "重命名", + "refresh": "刷新", + "copyDbName": "复制数据库名", + "toastDbNameCopied": "数据库名已复制到剪贴板", + "dropDatabase": "删除数据库", + "dropCollection": "删除集合", + "menuRename": "重命名", + "menuCopyConnectionString": "复制连接字符串", + "menuDeleteConnection": "删除连接", + "toastSidebarStringCopied": "连接字符串已复制!", + "cancel": "取消", + "confirmDeleteConnectionDesc": "This will permanently remove this connection. The database itself will not be affected.", + "confirmDropDbDesc": "This will permanently delete the database and all its collections. This cannot be undone.", + "confirmDropCollectionDesc": "This will permanently delete the collection and all its documents. This cannot be undone." + }, + "document": { + "docsBreadcrumb": "{n} 条文档", + "table": "表格", + "json": "JSON", + "tree": "树形", + "tooltipTable": "表格视图", + "tooltipJson": "JSON 视图", + "tooltipTree": "树形视图", + "itemsPerPage": "每页条数", + "perPage": "每页 {n} 条", + "prevPage": "上一页", + "nextPage": "下一页", + "expandAll": "全部展开", + "collapseAll": "全部折叠", + "refreshData": "刷新数据", + "insert": "插入", + "export": "导出", + "actions": "操作", + "invalidJson": "JSON 内容无效", + "copiedClipboard": "已复制到剪贴板", + "docUpdated": "文档更新成功", + "docInserted": "文档插入成功", + "invalidJsonShort": "JSON 无效", + "docCopied": "文档已复制到剪贴板", + "openFullEditor": "在完整编辑器中打开", + "cellArray": "数组({length})", + "cellObject": "对象", + "cellArrayBracket": "数组[{length}]", + "emptyFilterTitle": "没有符合查询的文档", + "emptyFilterHint": "尝试调整筛选或清空查询。", + "clearFilter": "清除筛选", + "emptyCollectionTitle": "此集合为空", + "emptyCollectionHint": "插入一条文档以开始。", + "insertDocument": "插入文档", + "formatJson": "格式化 JSON", + "compactJson": "压缩 JSON", + "copyClipboard": "复制到剪贴板", + "copyJson": "复制 JSON", + "duplicate": "复制", + "editDoc": "编辑文档", + "deleteDoc": "删除文档", + "documentLabel": "文档 {n}", + "editDialogTitle": "编辑文档", + "insertDialogTitle": "插入文档", + "viewValueTitle": "查看值", + "saveChanges": "保存更改", + "close": "关闭", + "cancel": "取消", + "insertButton": "插入", + "import": "Import", + "importExport": "Import / Export", + "importExportTitle": "Import or export data", + "importExportDescription": "Choose whether to bring documents in from a JSON file or download the current page of results.", + "importExportTooltip": "Import or export collection data", + "importExportImportHint": "JSON array, object, or NDJSON file", + "importExportExportHint": "Spreadsheet (XLSX or CSV)", + "schema": "Schema", + "indexes": "Indexes" + }, + "tabs": { + "filterActive": "已应用筛选", + "filterActiveTooltip": "筛选已启用", + "closeAll": "全部关闭" + }, + "query": { + "migrateHistory": "查询历史已迁移到云端", + "saveHistoryFail": "保存查询历史失败", + "deleteQueryFail": "删除查询失败", + "invalidJsonQuery": "JSON 查询无效", + "placeholder": "例如 { \"status\": \"active\" }", + "clearQuery": "清除查询", + "openAdvanced": "打开高级编辑器", + "visualBuilder": "可视化查询构建器", + "filterRules": "筛选规则", + "addRule": "添加规则", + "noFilters": "尚未应用筛选,添加规则以筛选文档。", + "where": "WHERE", + "and": "AND", + "fieldPlaceholder": "字段", + "opPlaceholder": "运算符", + "typePlaceholder": "类型", + "opRegex": "正则", + "opIn": "包含于", + "opNin": "不包含于", + "opExists": "存在", + "typeAuto": "自动", + "typeString": "字符串", + "typeNumber": "数字", + "typeBoolean": "布尔", + "typeDate": "日期", + "typeObjectId": "ObjectId", + "true": "真", + "false": "假", + "valuePlaceholder": "值", + "valueListPlaceholder": "值1, 值2", + "clearFilters": "清除筛选", + "applyFilters": "应用筛选", + "queryHistory": "查询历史", + "noSavedQueries": "暂无已保存的查询", + "advancedEditorTitle": "高级查询编辑器", + "templatesPlaceholder": "模板", + "templateEmptyAgg": "空聚合", + "templateMatch": "$match(筛选)", + "templateLookup": "$lookup(关联)", + "templateGroup": "$group(计数/求和)", + "format": "格式化", + "formattedJson": "格式化 JSON", + "runQuery": "运行查询" + }, + "export": { + "selectFieldsError": "请至少选择一个要导出的字段", + "success": "已成功导出到 {filename}", + "fail": "导出数据失败", + "title": "导出数据", + "formatLabel": "导出格式", + "formatXlsx": "Excel(XLSX)", + "formatCsv": "CSV", + "selectFields": "选择字段", + "selectAll": "全选", + "fieldsSelected": "{count, plural, one {已选 # 个字段} other {已选 # 个字段}}", + "cancel": "取消", + "export": "导出", + "sheetName": "数据" + }, + "jsonTree": { + "typeLabel": "类型:{type}", + "itemSummary": "{count, plural, one {# 项} other {# 项}}" + } + }, + "ApiClient": { + "defaults": { + "newRequest": "新请求", + "importedRequest": "已导入的请求", + "untitledRequest": "未命名请求" + }, + "toasts": { + "curlImported": "cURL 导入成功", + "curlParseFailed": "解析 cURL 命令失败", + "invalidJsonBody": "JSON 请求体无效", + "requestFailed": "请求失败:{message}", + "curlPasted": "cURL 已粘贴并解析成功", + "responseCopied": "响应已复制到剪贴板", + "codeCopied": "代码已复制到剪贴板", + "copyFailed": "复制到剪贴板失败" + }, + "layout": { + "collections": "集合", + "closeSidebar": "关闭侧边栏", + "openSidebar": "打开侧边栏", + "request": "Request", + "response": "Response" + }, + "requestPanel": { + "methodPlaceholder": "方法", + "urlPlaceholder": "https://api.example.com/v1/...", + "sending": "发送中…", + "send": "发送", + "invalidJsonBodyHelp": "无法发送:JSON 正文无效" + }, + "requestTabs": { + "params": "参数", + "headers": "请求头", + "body": "正文", + "auth": "认证", + "bodyType": "正文类型", + "selectType": "选择类型", + "bodyNone": "无", + "bodyJson": "JSON", + "bodyText": "文本", + "noBodyHint": "此请求无正文内容", + "switchToJson": "切换为 JSON 正文", + "authType": "认证类型", + "noAuth": "无认证", + "bearerToken": "Bearer 令牌", + "basicAuth": "基本认证", + "apiKey": "API 密钥", + "labelToken": "令牌", + "placeholderBearerToken": "Bearer 令牌", + "labelUsername": "用户名", + "placeholderUsername": "用户名", + "labelPassword": "密码", + "placeholderPassword": "密码", + "labelKey": "键名", + "placeholderApiKeyName": "键名(例如 X-Api-Key)", + "labelValue": "值", + "placeholderValue": "值", + "addTo": "添加到", + "header": "请求头", + "queryParams": "查询参数", + "noAuthHint": "此请求未使用认证。" + }, + "responsePanel": { + "emptyTitle": "准备开始?", + "emptyHint": "输入网址并点击发送,在此查看响应数据。", + "responsePreviewAlt": "响应预览", + "previewUnavailable": "此内容类型无法预览", + "status": "状态", + "time": "耗时", + "size": "大小", + "copyBody": "复制正文", + "downloadResponse": "下载响应", + "bodyTab": "正文", + "previewTab": "预览", + "headersTab": "响应头", + "binaryRawView": "二进制数据无法在原始视图中显示。", + "errorStatusLabel": "错误", + "truncatedBanner": "Showing first {shownKb} KB of a large response.", + "downloadFullBody": "Download full body" + }, + "tabBar": { + "addTabAria": "新标签" + }, + "collectionsSidebar": { + "title": "API 资源", + "newCollection": "新建集合", + "tabCollections": "集合", + "tabHistory": "历史", + "noCollectionsTitle": "暂无集合", + "noCollectionsHint": "创建集合以整理 API 请求。", + "createCollection": "创建集合", + "newFolder": "新建文件夹", + "rename": "重命名", + "delete": "删除", + "emptyCollection": "空集合", + "recentRequests": "最近请求", + "clearAll": "全部清除", + "noHistoryTitle": "暂无历史", + "noHistoryHint": "发送请求后将显示在此处。", + "dialogNewFolderTitle": "新建文件夹", + "dialogNewFolderDescription": "创建新文件夹以整理请求。", + "labelFolderName": "文件夹名称", + "placeholderFolderName": "我的文件夹", + "createFolder": "创建文件夹", + "dialogNewCollectionTitle": "新建集合", + "dialogNewCollectionDescription": "创建新的顶级集合。", + "labelCollectionName": "集合名称", + "placeholderCollectionName": "我的集合", + "dialogRenameCollectionTitle": "重命名集合", + "dialogRenameCollectionDescription": "输入集合的新名称。", + "placeholderRenameCollection": "集合名称", + "renameAction": "重命名", + "dialogDeleteBulkTitle": "Delete Collections", + "dialogDeleteBulkDescription": "This action cannot be undone. The following collections will be permanently deleted:", + "cancel": "Cancel" + }, + "collectionItem": { + "newFolder": "新建文件夹", + "delete": "删除", + "emptyFolder": "空文件夹" + }, + "environmentManager": { + "noEnvironment": "无环境", + "manageDialogTitle": "管理环境", + "placeholderNewEnv": "新环境", + "variablesHeading": "{name} 变量", + "addVariable": "添加变量", + "columnVariable": "变量", + "columnValue": "值", + "placeholderKey": "键", + "placeholderValue": "值", + "selectEnvToEdit": "选择要编辑的环境" + }, + "codeGenerator": { + "triggerTitle": "生成代码", + "dialogTitle": "生成代码", + "languageCurl": "cURL", + "languageJavaScript": "JavaScript(fetch)", + "languagePython": "Python(requests)", + "languageGo": "Go", + "copy": "复制", + "errorGenerating": "生成代码出错" + }, + "keyValueEditor": { + "columnKey": "键", + "columnValue": "值", + "placeholderKey": "键", + "placeholderValue": "值", + "addItem": "添加项" + }, + "importCurl": { + "trigger": "导入 cURL", + "dialogTitle": "导入 cURL", + "dialogDescription": "在下方粘贴 cURL 命令以导入为新请求。", + "placeholder": "curl -X POST https://api.example.com/data -d '...'", + "import": "导入" + }, + "saveRequest": { + "trigger": "保存", + "dialogTitle": "保存请求", + "dialogDescription": "将此请求保存到集合文件夹。", + "labelName": "请求名称", + "placeholderName": "我的请求", + "labelFolder": "文件夹", + "placeholderFolder": "选择文件夹", + "save": "保存" + }, + "shortcuts": { + "ariaLabel": "Keyboard shortcuts", + "title": "Keyboard shortcuts", + "newTab": "New tab", + "closeTab": "Close tab", + "sendRequest": "Send request" + }, + "toolbar": { + "moreActions": "More actions", + "code": "Generate code", + "save": "Save request", + "importCurl": "Import cURL", + "environments": "Environments", + "shortcuts": "Keyboard shortcuts" + } + }, + "RichEditor": { + "toolbar": { + "editMode": "编辑模式", + "viewOnlyMode": "只读模式", + "insertComponent": "插入组件", + "addUnorderedList": "添加无序列表", + "addOrderedList": "添加有序列表", + "addTable": "添加表格" + }, + "addBlock": "添加块", + "media": { + "popoverTitle": "添加媒体", + "singleImage": "单张图片", + "singleImageDesc": "上传一张图片", + "multipleImages": "多张图片", + "multipleImagesDesc": "上传图片网格", + "video": "视频", + "videoDesc": "上传视频文件", + "uploadingAlt": "上传中…", + "uploadFailedAlt": "上传失败" + }, + "export": { + "fabAria": "导出代码", + "title": "导出代码", + "description": "复制编辑器内容的 HTML 或 JSON 输出", + "preview": "预览", + "html": "HTML", + "json": "JSON", + "previewOptions": "预览选项", + "enhanceSpaces": "增强空白", + "livePreview": "渲染 HTML 的实时预览", + "htmlTailwind": "含 Tailwind CSS 类的 HTML", + "editorJson": "编辑器状态 JSON", + "copyHtml": "复制 HTML", + "copyJson": "复制 JSON", + "copied": "已复制!" + }, + "template": { + "fabTitle": "切换模板", + "title": "选择模板", + "description": "选择模板以使用预设版式与内容快速开始", + "catAll": "全部模板", + "catProductivity": "效率", + "catCreative": "创意", + "catBusiness": "商务", + "catPersonal": "个人", + "noTemplates": "该分类下暂无模板", + "confirmTitle": "替换现有内容?", + "confirmDescription": "编辑器中已有内容。应用此模板将替换当前全部内容。", + "cannotUndo": "此操作无法撤销。", + "cancel": "取消", + "replaceContent": "替换内容" + }, + "command": { + "searchPlaceholder": "搜索命令…", + "noResults": "未找到命令。", + "turnInto": "转换为", + "h1": { + "label": "一级标题", + "description": "大节标题", + "keywords": "h1,标题,一级,heading" + }, + "h2": { + "label": "二级标题", + "description": "中节标题", + "keywords": "h2,标题,二级,副标题" + }, + "h3": { + "label": "三级标题", + "description": "小节标题", + "keywords": "h3,标题,三级" + }, + "h4": { + "label": "四级标题", + "description": "较小节标题", + "keywords": "h4,标题" + }, + "h5": { + "label": "五级标题", + "description": "更小标题", + "keywords": "h5,标题" + }, + "h6": { + "label": "六级标题", + "description": "最小标题", + "keywords": "h6,标题" + }, + "p": { + "label": "段落", + "description": "普通文本段落", + "keywords": "p,段落,正文,text" + }, + "code": { + "label": "代码块", + "description": "代码片段", + "keywords": "code,代码,代码块,snippet" + }, + "blockquote": { + "label": "引用", + "description": "块级引用", + "keywords": "quote,引用,blockquote" + }, + "li": { + "label": "无序列表项", + "description": "带项目符号的列表项", + "keywords": "list,列表,无序,ul,li" + }, + "ol": { + "label": "有序列表项", + "description": "编号列表项", + "keywords": "list,列表,有序,ol,li" + }, + "img": { + "label": "图片", + "description": "上传或嵌入图片", + "keywords": "image,图片,照片,上传" + }, + "video": { + "label": "视频", + "description": "上传或嵌入视频", + "keywords": "video,视频,mp4,上传" + }, + "table": { + "label": "表格", + "description": "创建表格", + "keywords": "table,表格,行,列" + } + }, + "toasts": { + "tableImported": "表格已导入", + "tableImportedDesc": "Markdown 表格已成功导入", + "mediaPasted": "媒体已粘贴", + "pasteMixed": "已成功粘贴 {imageCount} 张图片和 {videoCount} 个视频。", + "pasteImages": "已成功粘贴 {count} 张图片。", + "pasteVideos": "已成功粘贴 {count} 个视频。", + "pasteFailed": "粘贴失败", + "unexpectedError": "发生意外错误", + "uploadFailed": "上传失败" + }, + "blockBackground": { + "menuItem": "更改背景色", + "dialogTitle": "块背景色", + "dialogDescription": "为此块选择背景色", + "tabPreset": "预设颜色", + "tabCustom": "自定义颜色", + "sectionHeading": "背景色", + "apply": "应用", + "colors": { + "none": "无", + "red": "红", + "orange": "橙", + "yellow": "黄", + "green": "绿", + "blue": "蓝", + "indigo": "靛", + "purple": "紫", + "pink": "粉", + "teal": "青绿", + "cyan": "青", + "gray": "灰" + } + }, + "tableDialog": { + "title": "创建表格", + "description": "新建表格或从 Markdown 导入", + "markdownCheckbox": "我有 Markdown 表格", + "pasteMarkdownLabel": "粘贴 Markdown 表格", + "markdownPlaceholder": "| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |", + "markdownHint": "在上方粘贴 Markdown 表格,须包含表头行与分隔行。", + "rows": "行", + "columns": "列", + "maxDimensions": "最多:20 行 × 10 列", + "cancel": "取消", + "importTable": "导入表格", + "createTable": "创建表格" + }, + "tableParse": { + "errors": { + "minRows": "表格至少须包含表头行与分隔行", + "pipeFormat": "表格行须以 | 开头和结尾", + "headerMinCol": "表头行至少须有一列", + "separatorRow": "第二行须为分隔行(例如 |---|---|)", + "rowColumnMismatch": "第 {row} 行有 {got} 列,期望 {expected} 列", + "parseFailed": "解析 Markdown 表格失败" + } + }, + "link": { + "editLink": "编辑链接", + "addLink": "添加链接", + "selectedText": "所选文本:「{text}」", + "linkUrl": "链接地址", + "urlPlaceholder": "https://example.com", + "update": "更新", + "addLinkButton": "添加链接", + "toastApplied": "链接已应用", + "toastLinkedTo": "已链接到:{url}", + "toastRemoved": "链接已移除", + "toastRemovedDesc": "已从选中内容移除链接" + }, + "customClass": { + "devMode": "开发者模式", + "toggleDevModeAria": "切换开发者模式", + "searchPlaceholderDev": "搜索类名…(例如 text、bg、flex)", + "searchPlaceholderUser": "搜索样式…(例如 red、bold、shadow)", + "appliesTitle": "应用:{className}", + "noMatches": "未找到匹配「{query}」的类", + "toastClassReplaced": "类已替换", + "toastClassReplacedDesc": "已将「{replaced}」替换为「{className}」", + "toastCustomClassApplied": "已应用自定义类", + "toastAppliedClass": "已应用类:{className}" + }, + "insertComponents": { + "title": "插入组件", + "description": "选择要插入文档的组件", + "noComponents": "暂无可用组件", + "categories": { + "media": "媒体", + "layout": "布局", + "interactive": "交互", + "decoration": "装饰" + }, + "freeImage": { + "name": "自由浮动图片", + "description": "添加可任意放置与缩放的图片" + } + } + }, + "JsonFormatter": { + "title": "JSON 格式化器", + "description": "在文本和树形视图中格式化、校验并编辑 JSON。", + "documentNameText": "文本文档 {n}", + "documentNameTree": "树形文档 {n}", + "newDocument": "新建", + "copy": "复制", + "save": "保存", + "load": "加载", + "saving": "保存中...", + "leftDocNameLabel": "左侧文档名称", + "rightDocNameLabel": "右侧文档名称", + "toastCopiedText": "已复制文本面板", + "toastCopiedTree": "已复制树形面板", + "toastCopyFailed": "复制 JSON 失败", + "toastLoginRequired": "请登录以保存文档", + "toastSaved": "文档已保存", + "toastSaveFailed": "文档保存失败", + "format": "格式化", + "compact": "压缩", + "sortKeys": "键排序", + "repair": "修复", + "query": "查询", + "toastInvalidJson": "无效的 JSON", + "toastAlreadyValid": "JSON 已经有效", + "toastRepaired": "JSON 已修复", + "toastRepairFailed": "无法修复 JSON" + }, + "JwtDecoder": { + "title": "JWT 解码器", + "subtitle": "查看头部和载荷。过期时间使用 exp 声明。不会发送到服务器;不验证签名。", + "tokenLabel": "令牌", + "clear": "清空", + "placeholder": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "copy": "复制", + "expiryLabel": "过期 (exp)", + "signaturePresent": "包含签名(未验证)", + "noExp": "载荷中没有 exp 声明。", + "status": { + "expired": "已过期", + "valid": "有效" + }, + "fields": { + "unix": "Unix", + "local": "本地", + "utc": "UTC" + }, + "claims": { + "issuedAt": "签发时间 (iat)", + "notBefore": "生效时间 (nbf)" + }, + "panels": { + "header": "头部", + "payload": "载荷" + }, + "errors": { + "empty": "粘贴一个 JWT 进行解码。", + "segments": "JWT 至少需要头部和载荷(两个段)。", + "base64": "无法 base64url 解码头部或载荷。" + }, + "relative": { + "future": "{n} {unit}后", + "past": "{n} {unit}前" + }, + "units": { + "second": "{count, plural, one {秒} other {秒}}", + "minute": "{count, plural, one {分钟} other {分钟}}", + "hour": "{count, plural, one {小时} other {小时}}", + "day": "{count, plural, one {天} other {天}}" + }, + "tabs": { + "decode": "解码", + "sign": "生成 / 签名" + }, + "signer": { + "algLabel": "算法(alg)", + "tabs": { + "payload": "载荷", + "header": "头部" + }, + "payloadLabel": "载荷 JSON", + "payloadPlaceholder": "e.g. \"sub\": \"123\", \"exp\": 1700000000", + "headerLabel": "头部 JSON", + "headerPlaceholder": "e.g. \"typ\": \"JWT\", \"alg\": \"HS256\"", + "secretLabel": "密钥(HS*)", + "secretPlaceholder": "粘贴你的 HMAC 密钥…", + "privateKeyLabel": "私钥 PEM(RS*)", + "privateKeyPlaceholder": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + "noneHint": "\"none\" 算法会生成未签名的令牌。不安全,仅用于测试。", + "sign": "签名", + "verify": "验证签名", + "outputLabel": "令牌(紧凑 JWS)", + "outputPlaceholder": "点击“签名”…", + "copyToken": "复制令牌", + "copied": "已复制", + "verifyKeyLabel": "验证密钥(可选)", + "verifyKeyPlaceholder": "RS*:粘贴公钥(SPKI)PEM。HS*:留空以复用密钥。", + "verifyKeyHint": "若留空,将使用签名密钥进行验证(HS* 很方便)。", + "verifyStatus": { + "valid": "签名有效", + "invalid": "签名无效" + }, + "securityNote": "全部在浏览器本地运行;不会上传密钥或令牌。" + }, + "signErrors": { + "invalidJson": "头部和载荷必须是有效的 JSON。", + "unsupportedAlg": "不支持的算法。", + "missingKey": "该算法需要签名密钥。", + "cryptoFailed": "签名失败。请检查密钥格式后重试。" + }, + "verifyErrors": { + "empty": "请输入要验证的 JWT。", + "segments": "验证签名需要 JWT 恰好包含 3 段。", + "unsupportedAlg": "头部中缺少 alg 或算法不受支持。", + "missingKey": "该算法需要验证密钥。", + "base64": "无法解码 JWT 头部或签名。", + "cryptoFailed": "验证失败。请检查密钥格式后重试。" + } + }, + "CertificatePemDecoder": { + "title": "证书 / PEM 解码器", + "subtitle": "粘贴 X.509 证书(-----BEGIN CERTIFICATE-----)或 CSR(-----BEGIN CERTIFICATE REQUEST-----)。主题、颁发者、日期与 SAN 在本地解析;不会上传任何内容。", + "pemLabel": "PEM 输入", + "clear": "清除", + "placeholder": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "parsing": "正在解析…", + "copyHex": "复制指纹(无冒号的十六进制)", + "errors": { + "empty": "请粘贴 PEM 证书或 CSR 以解码。", + "noBlock": "未找到证书或 CSR 的 PEM 块。", + "parseFailed": "无法解析此 PEM 数据。" + }, + "block": { + "title": "PEM 块 {index}", + "certificate": "X.509 证书", + "csr": "证书签名请求" + }, + "fields": { + "subject": "主题", + "issuer": "颁发者", + "notBefore": "生效前无效", + "notAfter": "过期后无效", + "serial": "序列号(十六进制)", + "sha256": "SHA-256 指纹", + "sans": "主题备用名称", + "sansNone": "无 SAN 扩展。", + "signatureAlgorithm": "签名算法", + "csrIssuerNote": "CSR 不包含颁发者(颁发者出现在已签发的证书上)。", + "csrValidityNote": "CSR 不包含有效期(有效期出现在已签发的证书上)。" + }, + "sanType": { + "dns": "DNS", + "dn": "可分辨名称", + "email": "电子邮件", + "ip": "IP", + "url": "URI", + "guid": "GUID", + "upn": "UPN", + "id": "注册 ID", + "other": "{type}" + } + }, + "SqlFormatter": { + "title": "SQL 格式化", + "subtitle": "格式化 MySQL、PostgreSQL 或 SQLite 的 SQL。在浏览器中运行;不会上传任何内容。", + "dialectLabel": "方言", + "keywordsLabel": "关键字", + "indentLabel": "缩进", + "dialects": { + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "sqlite": "SQLite" + }, + "keywordCase": { + "upper": "大写", + "lower": "小写", + "preserve": "保留" + }, + "spaces": "{n} 个空格", + "format": "格式化", + "clear": "清空", + "counter": "{current} / {max} 个字符", + "inputPanel": "输入", + "formattedPanel": "已格式化", + "copy": "复制", + "inputPlaceholder": "SELECT * FROM ...", + "outputPlaceholder": "点击“格式化”…", + "errors": { + "tooLong": "输入限制为 {max} 个字符。", + "couldNotFormat": "无法格式化该 SQL。" + } + }, + "YamlFormatter": { + "title": "YAML 格式化", + "subtitle": "在浏览器中格式化、验证和转换 YAML。不会上传任何内容。", + "outputLabel": "输出", + "outputModes": { + "yaml": "YAML(已格式化)", + "json": "JSON" + }, + "indentLabel": "缩进", + "spaces": "{n} 空格", + "format": "格式化", + "clear": "清除", + "copy": "复制", + "counter": "{current} / {max} 字符", + "inputPanel": "输入", + "outputPanel": "输出", + "errors": { + "tooLong": "输入限制为 {max} 个字符。", + "couldNotFormat": "无法解析此 YAML。" + } + }, + "GraphqlFormatter": { + "title": "GraphQL 格式化", + "subtitle": "在浏览器中格式化 GraphQL 查询、变更与订阅。不会上传任何内容。", + "tabs": { + "format": "格式化", + "build": "查询构建器" + }, + "outputModeLabel": "输出", + "outputModes": { + "pretty": "美化输出", + "minify": "压缩" + }, + "format": "格式化", + "clear": "清空", + "counter": "{current} / {max} 个字符", + "inputPanel": "输入", + "outputPanel": "输出", + "copy": "复制", + "builderHint": "组合文档骨架后应用到编辑器。变量定义使用 GraphQL 语法(例如 $id: ID!),括号可选。", + "builder": { + "operation": "操作类型", + "operationName": "操作名称", + "operationNamePlaceholder": "MyOperation", + "variableDefs": "变量定义", + "variableDefsPlaceholder": "$id: ID!, $first: Int = 10", + "selection": "字段选择", + "apply": "应用到输入", + "operations": { + "query": "Query", + "mutation": "Mutation", + "subscription": "Subscription" + } + }, + "errors": { + "tooLong": "输入最多 {max} 个字符。", + "couldNotFormat": "无法解析或输出此 GraphQL。", + "emptySelection": "请先填写选择集再应用。" + } + }, + "RegexTester": { + "title": "正则测试器", + "subtitle": "对你的文本实时运行 JavaScript 风格正则。匹配会高亮显示;内容不会离开浏览器。", + "patternLabel": "模式", + "patternPlaceholder": "[\\w.-]+@[\\w.-]+\\.\\w+", + "flags": { + "g": "g 全局", + "i": "i 忽略大小写", + "m": "m 多行", + "s": "s dotAll", + "u": "u Unicode", + "gTitle": "Find all matches, not just the first", + "iTitle": "Match regardless of letter case", + "mTitle": "^ and $ match the start/end of each line, not just the whole string", + "sTitle": "Dot (.) also matches newline characters", + "uTitle": "Treat the pattern as a sequence of Unicode code points" + }, + "emptyPattern": "(空)", + "noFlags": "—", + "matches": "{count, plural, one {处匹配} other {处匹配}}", + "testTextLabel": "测试文本", + "counter": "{current} / {max}", + "clear": "清空", + "sampleText": "The quick brown fox jumps over the lazy dog.\\nEmail: test@example.com\\nIDs: abc-123, xyz_456", + "errors": { + "tooLong": "文本限制为 {max} 个字符。", + "invalid": "正则表达式无效。", + "runaway": "在 {max} 次匹配后停止(可能存在失控的模式)。" + }, + "matchNav": { + "prev": "Previous match", + "next": "Next match", + "position": "{current} / {total}" + } + }, + "TimestampConverter": { + "title": "时间戳转换器", + "subtitle": "支持 Unix 秒(≤10 位)或毫秒(更长整数)、ISO-8601,或浏览器可解析的日期字符串。全部在本地运行。", + "inputLabel": "输入", + "placeholder": "1744200000 或 2025-04-09T12:00:00Z", + "now": "现在", + "copy": "复制", + "hint": "粘贴一个值或点击 现在。", + "sections": { + "unix": "Unix", + "iso": "ISO", + "relative": "相对", + "human": "可读格式" + }, + "fields": { + "seconds": "秒", + "milliseconds": "毫秒", + "utc": "UTC", + "local": "本地", + "distance": "距离" + }, + "errors": { + "empty": "请输入 Unix 时间戳、ISO 字符串或日期文本。", + "invalidNumber": "数字时间戳无效。", + "outOfRange": "超出 JavaScript Date 范围。", + "couldNotParse": "无法按 Unix、ISO-8601 或浏览器日期字符串解析。" + } + }, + "HttpStatusCodes": { + "title": "HTTP 状态码", + "subtitle": "搜索并浏览 HTTP 响应状态码(1xx5xx),包含简短说明与 RFC 参考。完全在本地运行。", + "header": { + "title": "HTTP 状态码", + "description": "可搜索的 HTTP 响应状态码参考,包含说明与 RFC 参考。" + }, + "searchPlaceholder": "搜索:404、not found、rate limit、gateway…", + "classFilterPlaceholder": "筛选类别…", + "classFilterAll": "全部类别", + "resultsCount": "{count} / {total}", + "table": { + "code": "Code", + "phrase": "短语", + "description": "说明", + "copy": "复制" + }, + "noResults": "未找到“{query}”的匹配项。", + "specPrefix": "规范:", + "copied": "已复制状态码" + }, + "CronBuilder": { + "title": "Cron 表达式生成器", + "subtitle": "标准 5 段 cron:minute hour day month weekday。解析器内部使用 seconds=0。表达式编辑器也支持可选的 6 段格式(sec min hour dom mon dow)。", + "tabs": { + "builder": "可视化构建器", + "expression": "表达式" + }, + "presetsLabel": "预设", + "fieldsLabel": "字段", + "rawExpressionLabel": "原始表达式", + "expressionHint": "离开输入框以解析并同步构建器。{count} 个{count, plural, one {token} other {tokens}}。", + "currentExpressionLabel": "当前表达式", + "copy": "复制", + "quickPick": "快速选择", + "emptyExpression": "—", + "plainLanguageLabel": "自然语言", + "nextRunsLabel": "下次运行(本地时间)", + "fields": { + "minute": "分钟", + "hour": "小时", + "dayOfMonth": "月中的天", + "month": "月份", + "dayOfWeek": "星期" + }, + "presets": { + "everyMinute": "每分钟", + "every5Minutes": "每 5 分钟", + "every15Minutes": "每 15 分钟", + "everyHour": "每小时(:00)", + "every6Hours": "每 6 小时", + "dailyMidnight": "每天午夜", + "daily0900": "每天 09:00", + "weekdays0900": "工作日 09:00", + "weeklySunday": "每周(周日 00:00)", + "monthlyFirst": "每月(1 日 00:00)", + "yearlyJan1": "每年(1 月 1 日 00:00)" + }, + "picks": { + "minute": { + "every": "每分钟" + }, + "hour": { + "every": "每小时" + }, + "dom": { + "any": "任意日", + "last": "最后一天" + }, + "month": { + "every": "每月" + }, + "dow": { + "any": "任意星期", + "weekdays": "工作日", + "weekend": "周末" + } + }, + "months": { + "jan": "1月", + "feb": "2月", + "mar": "3月", + "apr": "4月", + "may": "5月", + "jun": "6月", + "jul": "7月", + "aug": "8月", + "sep": "9月", + "oct": "10月", + "nov": "11月", + "dec": "12月" + }, + "weekdays": { + "sun": "日", + "mon": "一", + "tue": "二", + "wed": "三", + "thu": "四", + "fri": "五", + "sat": "六" + }, + "errors": { + "fieldCount": "需要 5 段(minute hour day month weekday)或包含秒的 6 段。", + "invalid": "无效的 cron 表达式。" + } + }, + "Base64": { + "title": "Base64 编码 / 解码", + "subtitle": "将文本编码为 Base64 或解码 Base64 字符串", + "upload": "上传", + "clear": "清空", + "modes": { + "encode": "编码", + "decode": "解码" + }, + "swapTitle": "交换输入与输出", + "panels": { + "plainText": "纯文本", + "base64Input": "Base64 输入", + "base64Output": "Base64 输出", + "decodedText": "解码文本" + }, + "placeholders": { + "encode": "输入要编码为 Base64 的文本…", + "decode": "粘贴要解码的 Base64 字符串…" + }, + "dropHere": "将文件拖放到这里", + "charCount": "{count} 个字符", + "download": { + "title": "下载输出", + "encodedFilename": "encoded.b64", + "decodedFilename": "decoded.txt" + }, + "copyTitle": "复制到剪贴板", + "outputPlaceholder": "输出将显示在这里…", + "errors": { + "invalidBase64": "无效的 Base64 字符串,请检查输入。", + "encodeFailed": "编码失败。" + } + }, + "UrlEncode": { + "title": "URL 编码 / 解码", + "subtitle": "使用 encodeURIComponent / decodeURIComponent(UTF‑8)进行百分号编码/解码", + "upload": "上传", + "clear": "清空", + "modes": { + "encode": "编码", + "decode": "解码" + }, + "swapTitle": "交换输入与输出", + "panels": { + "plainText": "纯文本", + "encodedInput": "已编码输入", + "encodedOutput": "已编码输出", + "decodedText": "已解码文本" + }, + "placeholders": { + "encode": "输入要进行百分号编码的文本…", + "decode": "粘贴要解码的 percent-encoded 文本…" + }, + "dropHere": "将文件拖放到此处", + "charCount": "{count} 个字符", + "download": { + "title": "下载输出", + "encodedFilename": "url-encoded.txt", + "decodedFilename": "url-decoded.txt" + }, + "copyTitle": "复制到剪贴板", + "outputPlaceholder": "输出将显示在这里…", + "errors": { + "invalidEncoding": "百分号编码无效。请检查不完整的 % 对或错误的转义序列。", + "encodeFailed": "无法编码(输入中包含无效的 Unicode)。" + } + }, + "UrlParser": { + "title": "URL 解析器", + "subtitle": "将 URL 拆分为协议、主机、路径、查询参数和哈希(片段)。", + "upload": "上传", + "clear": "清空", + "panels": { + "input": "URL 输入", + "output": "解析结果" + }, + "placeholders": { + "input": "粘贴 URL(例如 https://example.com/path?foo=bar#section)或主机/路径(例如 example.com/path)…" + }, + "dropHere": "将文件拖放到此处", + "charCount": "{count} 个字符", + "outputPlaceholder": "解析后的组件将显示在这里…", + "fields": { + "protocol": "协议", + "host": "主机", + "path": "路径", + "hash": "哈希", + "queryParams": "查询参数" + }, + "paramCount": "{count} 个参数", + "noParams": "没有查询参数。", + "paramKey": "键", + "paramValue": "值", + "copyTitle": "复制到剪贴板", + "copyParamTitle": "复制为 key=value", + "download": { + "title": "下载 JSON", + "filename": "url-parsed.json" + }, + "errors": { + "invalidUrl": "无效的 URL。请包含主机名(example.com)并检查非法字符。" + } + }, + "UuidGenerator": { + "title": "UUID / ULID 生成器", + "subtitle": "在浏览器中生成 RFC 9562 UUID 或 ULID。每次最多批量导出 {max} 个。", + "format": "格式", + "kindHints": { + "ulid": "可排序、URL 安全的 26 位标识符", + "uuid4": "随机(RFC 9562)", + "uuid7": "按时间排序,Unix 毫秒 + 随机", + "uuid1": "基于 MAC/时间(RFC 9562)", + "uuid6": "重新排序的格里历时间", + "uuid3": "名称 + 命名空间的 MD5 哈希", + "uuid5": "名称 + 命名空间的 SHA‑1 哈希", + "nil": "空 UUID(全为零,RFC 9562)" + }, + "countLabel": "数量(1–{max})", + "nameLabel": "名称(输入字符串)", + "namePlaceholder": "例如 my.app/resource", + "bulkHint": "批量生成会追加 #0#1… 以确保每个 ID 不同(v3/v5 为确定性)。", + "namespace": "命名空间", + "namespaceOptions": { + "dns": "DNS(RFC 4122)", + "url": "URL(RFC 4122)", + "custom": "自定义 UUID" + }, + "customNamespaceLabel": "命名空间 UUID", + "customNamespacePlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "generate": "生成", + "output": "输出", + "lines": "{count} 行", + "download": { + "title": "下载 .txt", + "filename": "ids-{kind}.txt" + }, + "copyTitle": "复制全部", + "uppercaseToggle": "大写输出", + "hyphensToggle": "显示连字符", + "copyJsonTitle": "复制为 JSON 数组", + "outputPlaceholder": "点击“生成”…", + "errors": { + "invalidCustomNamespace": "自定义命名空间必须是有效的 UUID。", + "missingName": "UUID v3 和 v5 需要名称。", + "unknown": "无法生成 ID。" + } + }, + "SecretApiKeyGenerator": { + "title": "密钥 / API 密钥生成器", + "subtitle": "使用可配置长度与字符集生成密码学随机字符串。每次最多批量导出 {max} 条。可与 UUID / ULID 生成器 搭配使用。", + "alphabetLabel": "字符集", + "alphabetHint": "会去除重复字符。每个字符通过 crypto.getRandomValues 与拒绝采样均匀抽取(无取模偏差)。", + "alphabetCount": "不重复字符 {count} 个", + "presetsLabel": "预设", + "preset": { + "base64url": "Base64url", + "alphanumeric": "字母数字", + "hex": "十六进制(小写)", + "asciiPrintable": "ASCII 可打印" + }, + "lengthLabel": "每条长度(1–{maxLen})", + "countLabel": "数量(1–{maxBulk})", + "generate": "生成", + "output": "输出", + "lines": "{count} 行", + "download": { + "title": "下载 .txt", + "filename": "secrets.txt" + }, + "copyTitle": "复制全部", + "outputPlaceholder": "点击「生成」…", + "entropy": "{bits} 位熵", + "strength": { + "weak": "弱", + "fair": "一般", + "strong": "强" + }, + "excludeAmbiguous": "排除易混淆字符", + "excludeAmbiguousHint": "移除 {chars} —— 容易误读或对 shell 不友好的字符。", + "prefixLabel": "密钥前缀(可选)", + "errors": { + "emptyAlphabet": "请至少向字符集中添加一个字符。", + "alphabetTooLong": "去除重复后,字符集最多 {maxAlpha} 个字符。", + "lengthOutOfRange": "长度必须在 1 与 {maxLen} 之间。", + "countOutOfRange": "数量必须在 1 与 {maxBulk} 之间。", + "unknown": "无法生成密钥字符串。" + } + }, + "LoremIpsum": { + "title": "Lorem Ipsum 生成器", + "subtitle": "为您的布局和原型生成拉丁文占位文本。", + "paragraphs": "段落", + "sentences": "句子", + "words": "单词", + "count": "数量", + "generate": "生成", + "copy": "复制", + "startWith": "以 'Lorem ipsum...' 开头", + "outputPlaceholder": "输出将在此处显示...", + "outputType": "输出类型", + "bulletList": "项目符号列表", + "htmlMarkup": "HTML 标记", + "htmlMarkupHint": "将输出包装在段落中,或在项目符号模式下使用列表", + "output": "输出", + "download": "下载" + }, + "ColorPicker": { + "title": "颜色选择器和转换器", + "subtitle": "转换 HEX、RGB 和 HSL;构建和谐调色板。所有处理在您的浏览器中完成。", + "nativePicker": "原生选择器", + "chooseColor": "选择颜色", + "eyeDropper": "吸管工具", + "invalidHex": "无效的 HEX 颜色。", + "cssValues": "CSS 值", + "palettes": "调色板", + "copied": "已复制", + "clickToCopy": "点击复制", + "copyAllHex": "复制所有 HEX", + "copy": "复制", + "copyColor": "复制 {color}", + "paletteShades": "色调", + "paletteComplementary": "互补色", + "paletteTriadic": "三角配色", + "paletteAnalogous": "邻近色", + "paletteSplit": "分裂互补", + "paletteSquare": "正方形配色" + }, + "ContrastChecker": { + "title": "对比度检查", + "subtitle": "计算前景与背景之间的 WCAG 2.1 对比度,并显示常规与大号文字的 AA、AAA 达标情况。全部在浏览器本地完成。", + "foreground": "文字 / 前景", + "background": "背景", + "hexPlaceholder": "#RRGGBB", + "swap": "交换颜色", + "chooseForeground": "选择前景色", + "chooseBackground": "选择背景色", + "eyeDropper": "取色器", + "ratio": "对比度", + "copyRatio": "复制比值", + "copied": "已复制", + "previewHeading": "预览", + "previewBody": "天地玄黄,宇宙洪荒。日月盈昃,辰宿列张。寒来暑往,秋收冬藏。闰余成岁,律吕调阳。", + "previewCaption": "默认字号示例正文。", + "wcagHeading": "WCAG 2.1", + "levelAANormal": "AA — 常规文字(4.5:1)", + "levelAALarge": "AA — 大号文字(3:1)", + "levelAAANormal": "AAA — 常规文字(7:1)", + "levelAAALarge": "AAA — 大号文字(4.5:1)", + "pass": "通过", + "fail": "未通过", + "invalidHex": "无效的 HEX 颜色。", + "largeTextHint": "大号文字:常规 18pt 及以上,或粗体 14pt 及以上(WCAG 定义)。" + }, + "DiffChecker": { + "title": "差异检查器", + "subtitle": "并排逐行比较两段文本。所有处理都在您的浏览器中完成。", + "rowsCount": "{count} 行", + "truncatedWarning": "显示前 {max} 行。", + "limitExceeded": "每侧限制为 {max} 个字符。", + "clearBoth": "清除两者", + "original": "原始", + "modified": "已修改", + "originalPlaceholder": "粘贴原始文本…", + "modifiedPlaceholder": "粘贴已修改的文本…", + "comparisonLabel": "并排比较", + "emptyState": "在两个面板中输入文本以进行比较。" + }, + "JsonSchemaGenerator": { + "title": "JSON 模式生成器", + "subtitle": "粘贴有效的 JSON 以生成 JSON 模式或类型化模型。全部在浏览器中完成。", + "languageLabel": "输出语言", + "inputLabel": "JSON 输入", + "inputPlaceholder": "在此粘贴 JSON…", + "outputLabel": "生成结果", + "outputPlaceholder": "生成的模式或类型将显示在此…", + "parseError": "无效的 JSON", + "clear": "清空", + "copy": "复制", + "copied": "已复制到剪贴板", + "copyFailed": "无法复制", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON 模式(Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "正在加载编辑器…", + "detectedFormats": "Detected formats", + "download": "Download" + }, + "ImageToBase64": { + "title": "图像转 Base64", + "subtitle": "删除图像,获取数据 URI 或原始 Base64 字符串。一切都保留在您的浏览器中。", + "upload": "上传图片", + "clear": "清除", + "modes": { + "dataUri": "数据URI", + "rawString": "原始 Base64" + }, + "panels": { + "imageInput": "图像输入", + "output": "Base64 输出" + }, + "dropHere": "将图像拖放到此处", + "charCount": "{count} 个字符", + "placeholders": { + "uploadImage": "拖放图像或单击上传开始。" + }, + "fileTooLarge": "文件太大。处理过程可能会冻结您的浏览器。", + "copyTitle": "复制到剪贴板", + "download": { + "title": "下载文字", + "filename": "图像-base64.txt" + } + }, + "ImageCompressor": { + "title": "图片压缩", + "subtitle": "在本地通过质量设置缩小 JPEG / PNG / WebP 输出 — 不会上传任何内容。", + "upload": "上传图片", + "clear": "清除", + "dropHere": "将图片拖放到此处", + "placeholders": { + "uploadImage": "拖放图片,或点击选择文件。" + }, + "fileTooLarge": "文件过大(最大 {maxMb} MB)。", + "unsupportedFormat": "不支持的文件类型。请使用 JPEG、PNG、WebP、GIF、AVIF 或 BMP。", + "compressFailed": "无法压缩此图片。请尝试其他文件或格式。", + "panels": { + "original": "原图", + "compressed": "压缩预览" + }, + "outputFormat": "输出格式", + "format": { + "jpeg": "JPEG (.jpg)", + "png": "PNG (.png)", + "webp": "WebP (.webp)" + }, + "quality": "质量", + "qualityPercent": "{value}%", + "qualityPngHint": "PNG 在浏览器中为无损;质量滑块仅适用于 JPEG 和 WebP。", + "sizes": { + "original": "原始:{size}", + "compressed": "压缩后:{size}", + "saved": "减小 {percent}%" + }, + "largerWarning": "输出大于输入 — 请尝试其他格式或质量。", + "download": "下载", + "downloadFilename": "compressed-image", + "processing": "正在压缩…", + "emptyCompressed": "压缩预览将显示在此处。" + }, + "CssGradientBuilder": { + "title": "CSS 渐变生成器", + "subtitle": "具有角度控制、色标和一键 CSS 复制功能的可视化 CSS 渐变生成器。", + "preview": "渐变预览", + "settings": "设置", + "type": "类型", + "linear": "线性", + "radial": "径向", + "angle": "角度", + "stops": "色标", + "color": "颜色", + "position": "位置 (%)", + "addStop": "添加停靠站", + "deleteStop": "删除停靠点", + "output": "CSS 输出", + "copy": "复制CSS", + "copied": "复制了!", + "conic": "Conic", + "presets": "Presets", + "randomize": "Randomize", + "reverse": "Reverse", + "downloadImage": "Download" + }, + "GitignoreGenerator": { + "title": ".gitignore 生成器", + "subtitle": "立即为特定技术堆栈或组合生成合并的 .gitignore 文件。", + "searchPlaceholder": "搜索堆栈(例如 Node、Python、macOS)...", + "noResults": "没有找到堆栈。", + "selectedStacks": "选定的堆栈", + "output": "预览输出", + "copy": "复制到剪贴板", + "copied": "复制了!", + "download": "下载.gitignore", + "downloading": "正在下载...", + "errorFetching": "无法获取最高列表。", + "errorGenerating": "无法从选定的堆栈生成 .gitignore。", + "generating": "生成...", + "emptyState": "选择一个或多个技术堆栈来生成 .gitignore。" + }, + "CsvExcelJson": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "上传 CSV 或 Excel,编辑 JSON,再下载 CSV 或 XLSX。仅支持表格数据;Excel 日期单元格在 JSON 中为 ISO 时间戳。", + "dropTitle": "拖放 CSV 或 Excel 文件", + "dropHint": ".xlsx / .xls 使用第一个工作表。CSV 需包含标题行。", + "chooseFile": "选择文件", + "jsonLabel": "JSON(对象数组)", + "jsonPlaceholder": "粘贴对象组成的 JSON 数组,或导入 CSV / Excel…", + "loadSample": "加载示例", + "actions": { + "format": "格式化 JSON", + "clear": "清空", + "copy": "复制", + "copied": "已复制", + "downloadCsv": "下载 CSV", + "downloadXlsx": "下载 Excel" + }, + "filenames": { + "csv": "data.csv", + "xlsx": "data.xlsx" + }, + "sheetName": "数据", + "toastLoadedCsv": "已将 CSV 加载到编辑器", + "toastLoadedExcel": "已将 Excel 加载到编辑器", + "toastFormatted": "已格式化 JSON", + "toastExportedCsv": "已下载 CSV", + "toastExportedXlsx": "已下载 Excel", + "toastCopied": "已复制到剪贴板", + "footnote": "全部在浏览器中完成。JSON 必须是非空对象数组。嵌套值在 CSV 单元格中会序列化为 JSON。", + "errors": { + "unsupportedFile": "请使用 .csv、.xlsx 或 .xls 文件。", + "parseFailed": "无法解析此文件。", + "invalidJson": "无效的 JSON", + "exportFailed": "导出失败", + "copyFailed": "无法复制" + } + }, + "SnippetManager": { + "title": "代码片段管理器", + "subtitle": "在浏览器中保存代码片段,或登录以同步到账户。在查看与编辑间切换、一键复制、选择语言或使用自动检测,并格式化 JSON、SQL 或 Monaco 支持的代码。", + "snippets": "片段", + "newSnippet": "新建片段", + "newSnippetTitle": "新建片段", + "defaultSnippetTitle": "欢迎", + "defaultSnippetCode": "// 欢迎使用代码片段\n// 试试查看/编辑、复制、格式化与自动语言\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "搜索片段…", + "searchAria": "搜索片段", + "emptySearch": "没有匹配的片段。", + "snippetTitle": "标题", + "snippetTitlePlaceholder": "片段名称", + "language": "语言", + "detectedHint": "高亮为 {lang}", + "badgeAuto": "自动 · {lang}", + "modeView": "查看", + "modeEdit": "编辑", + "copy": "复制", + "copied": "已复制", + "format": "格式化", + "cancel": "取消", + "deleteTitle": "删除片段?", + "deleteDescription": "将从此浏览器移除「{title}」。", + "deleteConfirm": "删除", + "editorAria": "片段代码编辑器", + "editorLoading": "正在加载编辑器…", + "toastCopied": "已复制到剪贴板", + "toastCopyFailed": "无法复制", + "toastNothingToCopy": "没有可复制的内容", + "toastFormatted": "已格式化", + "toastFormatFailed": "无法格式化", + "toastFormatJsonInvalid": "JSON 无效 — 请先修正语法", + "toastFormatUnsupported": "此语言没有格式化程序 — 请尝试 JSON 或 SQL,或使用编辑模式", + "toastDeleted": "片段已删除", + "toastLoadFailed": "无法从服务器加载代码片段。", + "toastSyncFailed": "无法与服务器同步。", + "languages": { + "auto": "自动检测", + "plaintext": "纯文本", + "javascript": "JavaScript", + "typescript": "TypeScript", + "json": "JSON", + "html": "HTML", + "css": "CSS", + "scss": "SCSS", + "less": "Less", + "python": "Python", + "markdown": "Markdown", + "xml": "XML", + "yaml": "YAML", + "shell": "Shell", + "sql": "SQL", + "dockerfile": "Dockerfile", + "ini": "INI", + "csharp": "C#", + "java": "Java", + "go": "Go", + "rust": "Rust", + "php": "PHP" + } + }, + "SqlClient": { + "page": { + "title": "SQL 客户端", + "dialogTitle": "SQL 连接", + "mobileSheetTitle": "SQL 浏览器", + "newTabLabel": "新建查询标签页", + "vaultLockedTitle": "保险库已锁定", + "vaultLockedDesc": "解锁保险库以访问保存的连接。", + "heroTitle": "SQL 客户端", + "heroDesc": "连接到 PostgreSQL、MySQL 或 MariaDB。凭据在浏览器中加密,绝不以明文存储。", + "heroAddConnection": "添加连接", + "selectTableTitle": "选择表或打开查询标签页", + "selectTableDesc": "在侧边栏中点击表,或使用 ⌘↩ 运行查询。", + "toastLoadFail": "加载连接失败。" + }, + "connection": { + "titleNew": "新建 SQL 连接", + "titleEdit": "编辑连接", + "labelDbType": "数据库类型", + "labelName": "连接名称", + "placeholderName": "我的生产数据库", + "labelHostPort": "主机与端口", + "labelDatabase": "数据库", + "placeholderDatabase": "mydb", + "labelUsername": "用户名", + "labelPassword": "密码", + "labelSsl": "启用 SSL / TLS", + "sslDesc": "加密与服务器的连接", + "securityNote": "凭据在浏览器中经 AES-256 加密后才会存储", + "btnTest": "测试连接", + "btnSave": "保存并连接", + "btnUpdate": "更新", + "btnCancelEdit": "取消编辑", + "savedConnections": "已保存的连接", + "emptySaved": "保存的连接将显示在此处", + "confirmDelete": "删除此连接?", + "toastFillFields": "请在测试前填写主机和数据库。", + "toastConnectOk": "已连接!{version}", + "toastConnectFail": "连接失败:{message}", + "toastUnlockVault": "请先解锁您的保险库。", + "toastFieldsRequired": "主机和数据库是必填项。", + "toastUpdated": "连接已更新。", + "toastSaved": "连接已保存。", + "toastSaveFail": "保存失败:{message}", + "toastDeleted": "连接已删除。", + "toastDeleteFail": "删除连接失败。" + }, + "sidebar": { + "title": "SQL 浏览器", + "tooltipAddConnection": "添加连接", + "tooltipRefreshSchema": "刷新架构", + "noConnections": "尚无保存的连接", + "toastSchemaFail": "加载架构失败:{message}" + }, + "editor": { + "btnRun": "运行", + "executing": "正在执行查询…", + "emptyTitle": "运行查询以查看结果", + "emptyHint": "按 ⌘↩ 或点击运行", + "toastNoConnection": "无活动连接。" + }, + "results": { + "filterPlaceholder": "筛选结果…", + "rowCount": "{count} 行", + "rowCountFiltered": "{filtered} / {total} 行", + "exportCsv": "CSV", + "noRows": "未返回任何行" + } + }, + "QrCodeGenerator": { + "title": "二维码生成器", + "subtitle": "将任意文本或 URL 转为 PNG 二维码。全部在浏览器本地渲染。", + "contentLabel": "内容", + "contentPlaceholder": "https://example.com、纯文本、Wi‑Fi 字符串、vCard…", + "styleLabel": "样式", + "dotTypeLabel": "点形状", + "dotTypeSquare": "方形", + "dotTypeDots": "圆点", + "dotTypeRounded": "圆角", + "dotTypeExtraRounded": "超圆角", + "dotTypeClassy": "经典", + "dotTypeClassyRounded": "经典圆角", + "cornerSquareLabel": "角落方块", + "cornerSquareDot": "圆点", + "cornerSquareSquare": "方形", + "cornerSquareExtraRounded": "超圆角", + "cornerDotLabel": "角落圆点", + "cornerDotDot": "圆点", + "cornerDotSquare": "方形", + "eccLabel": "纠错级别", + "eccL": "低(约 7%)", + "eccM": "中(约 15%)", + "eccQ": "四分位(约 25%)", + "eccH": "高(约 30%)", + "marginLabel": "边距(模块)", + "widthLabel": "图片宽度", + "widthEffective": "以 {px} px 渲染", + "darkColor": "深色模块", + "lightColor": "浅色模块", + "resetColors": "重置颜色", + "logoLabel": "中心徽标", + "logoMarginLabel": "边距", + "uploadLogo": "上传图片", + "removeLogo": "删除徽标", + "downloadPng": "下载 PNG", + "downloadSvg": "下载 SVG", + "copyImage": "复制图片", + "copied": "已复制", + "clear": "清空", + "previewAlt": "二维码预览", + "emptyHint": "输入或粘贴内容以预览二维码。", + "filename": "qr-code.png", + "errors": { + "generationFailed": "无法生成二维码。请尝试更短文本或更低纠错级别。", + "copyUnsupported": "当前浏览器不支持将图片复制到剪贴板。", + "copyFailed": "无法复制到剪贴板。" + } + }, + "IpSubnetCalculator": { + "title": "IP / 子网计算器", + "subtitle": "在浏览器中计算 IPv4 与 IPv6 CIDR—不发送外部请求。", + "tabIpv4": "IPv4", + "tabIpv6": "IPv6", + "inputLabel": "网络", + "placeholderIpv4": "10.0.0.0/24 或 192.168.1.0 255.255.255.0", + "placeholderIpv6": "2001:db8::/32 或 2001:db8::1/64", + "hintIpv4": "使用 CIDR(如 /24)或 IPv4 加点分十进制子网掩码。", + "hintIpv6": "包含前缀长度(如 /64)。省略时默认为 /128。", + "copyValue": "复制值", + "rows": { + "cidr": "CIDR", + "network": "网络地址", + "netmask": "子网掩码", + "wildcard": "通配符掩码", + "broadcast": "广播地址", + "firstHost": "首主机", + "lastHost": "末主机", + "totalAddresses": "总地址数", + "usableHosts": "可用主机数", + "prefixLength": "前缀长度(位)", + "networkIpv6": "网络地址", + "lastIpv6": "范围内最后一个地址", + "totalAddressesIpv6": "子网内地址数" + }, + "usableNote": { + "pointToPoint": "/31 点对点链路将两个地址都作为主机(RFC 3021)。", + "singleHost": "/32 表示单个主机。" + }, + "ipv6RangeHint": { + "normal": "显示完整数学范围;路由器上的规则可能不同。", + "single": "/128 为单个地址。", + "full": "/0 覆盖全部 IPv6。" + }, + "errors": { + "invalidIpv4": "无效的 IPv4 网络。请使用 CIDR 或地址+掩码。", + "invalidIpv6": "无效的 IPv6 网络。请使用 地址/前缀,例如 2001:db8::/48。" + } + }, + "HashGenerator": { + "title": "哈希生成器", + "subtitle": "适用于文本或文件的 MD5、SHA-1、SHA-2 摘要;适用于密码的 bcrypt(文本输入)。全部在浏览器本地计算。", + "algorithmLabel": "算法", + "algorithms": { + "md5": "MD5", + "sha1": "SHA-1", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "bcrypt": "bcrypt" + }, + "modeText": "文本", + "modeFile": "文件", + "inputLabelText": "输入", + "placeholderText": "输入或粘贴要哈希的文本…", + "chooseFile": "选择文件", + "clearFile": "清除", + "fileHint": "仅在本机内存中读取文件;最大 {maxMb} MB。不会上传。", + "outputLabel": "摘要(十六进制)", + "copyHash": "复制摘要", + "copied": "已复制", + "byteCount": "{count, plural, other {# 字节}}", + "charCount": "{count, plural, other {# 个字符}}", + "emptyHint": "输入文本或选择文件以计算哈希。", + "securityNote": "新的安全场景不建议使用 MD5 与 SHA-1;请优先使用 SHA-256 或 SHA-512。", + "errors": { + "fileTooLarge": "文件超过 {maxMb} MB。", + "fileReadFailed": "无法读取该文件。" + }, + "bcryptRoundsLabel": "成本(轮数)", + "bcryptRoundsHint": "数值越高越慢,也更抗暴力破解。常见默认值为 10。", + "fileTabDisabledTitle": "bcrypt 仅对文本中的密码进行哈希,不处理任意文件。", + "placeholderBcrypt": "输入或粘贴要哈希的密码…", + "outputLabelBcrypt": "bcrypt 哈希", + "copyBcrypt": "复制哈希", + "emptyHintBcrypt": "输入密码以计算 bcrypt 哈希。", + "securityNoteBcrypt": "每次 bcrypt 输出都包含随机盐;同一密码每次会得到不同字符串,这是预期行为。生产环境请使用足够高的成本。", + "verifyLabel": "Verify against expected hash", + "verifyPlaceholder": "Paste an expected digest to compare…", + "verifyPlaceholderBcrypt": "Paste a bcrypt hash to check the password above…", + "verifyHintBcrypt": "Bcrypt embeds a random salt, so two hashes of the same password never match as strings — the password in the input is checked against this hash instead.", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "EncryptionPlayground": { + "title": "加密演练场", + "subtitle": "在浏览器中使用 AES-GCM:用口令或原始密钥加密为 JSON 包,再在本地解密。适合学习与偶尔实用 — 不会上传任何内容。", + "tabEncrypt": "加密", + "tabDecrypt": "解密", + "keyModeLabel": "密钥材料", + "modePassphrase": "口令(PBKDF2 + AES-GCM)", + "modeRawKey": "原始 AES 密钥(十六进制或 Base64)", + "aesBitsLabel": "AES 密钥长度", + "aesBits": { + "128": "AES-128", + "192": "AES-192", + "256": "AES-256" + }, + "rawEncodingLabel": "原始密钥编码", + "encodingAuto": "自动(看似十六进制则用十六进制,否则 Base64)", + "encodingHex": "十六进制", + "encodingBase64": "Base64", + "rawKeyLabel": "原始密钥", + "rawKeyPlaceholder": "32 字节密钥可写为 64 个十六进制字符,或 AES-256 的 Base64(长度取决于上方 AES 位数)。", + "rawKeyPlaceholderDecrypt": "仅当数据包中 keyDerivation 为 \"none\" 时需要填写。", + "passphraseLabel": "口令", + "passphrasePlaceholder": "口令仅在浏览器中用于派生密钥。", + "passphrasePlaceholderDecrypt": "仅当数据包使用 PBKDF2-SHA256 时需要填写。", + "pbkdf2IterLabel": "PBKDF2 迭代次数", + "pbkdf2Hint": "限制在 10,000 至 2,000,000 之间。默认值与常见密码库设置一致。", + "plaintextLabel": "明文", + "plaintextPlaceholder": "输入或粘贴要加密的消息…", + "bundleInputLabel": "加密数据包(JSON)", + "bundlePlaceholder": "粘贴来自「加密」的 JSON(v、alg、ivB64、cipherB64 等)。", + "bundleOutputLabel": "加密数据包", + "plaintextOutputLabel": "明文", + "encryptButton": "加密", + "decryptButton": "解密", + "copyBundle": "复制 JSON", + "copyPlaintext": "复制明文", + "copied": "已复制", + "emptyBundleHint": "加密后,加密后的 JSON 将显示在此处。", + "emptyDecryptHint": "解密后的文本将显示在此处。", + "decryptKeyHint": "若 keyDerivation 为 \"none\" 请使用原始密钥;若为 PBKDF2-SHA256 请使用口令。数据包包含盐、迭代次数、IV 与 AES 长度。", + "securityNote": "密钥与明文不会离开您的浏览器。生产环境机密请使用专用工具。", + "errors": { + "emptyKey": "请输入与 AES 长度匹配的原始密钥。", + "emptyPassphrase": "请输入口令。", + "badKeyMaterial": "无法将密钥解析为十六进制或 Base64。", + "wrongKeyLength": "密钥长度与所选 AES(16 / 24 / 32 字节)不符。", + "invalidJson": "不是有效的 JSON。", + "invalidBundle": "此 JSON 不是受支持的加密数据包。", + "decryptFailed": "解密失败 — 密钥错误、数据包损坏或参数不匹配。", + "encryptFailed": "加密失败。请检查 Web Crypto 与输入。" + } + }, + "HmacGenerator": { + "title": "HMAC 生成器", + "subtitle": "用于 Webhook 签名与 API 集成测试。支持 HMAC-SHA1/256/384/512,输出 hex 或 Base64—均在浏览器本地计算。", + "secretLabel": "密钥", + "messageLabel": "消息 / 负载", + "placeholderSecret": "粘贴签名密钥…", + "placeholderMessage": "粘贴要验证的原始正文或字符串(通常为 Webhook 正文的精确字节)。", + "algorithmLabel": "HMAC 算法", + "algorithms": { + "sha1": "HMAC-SHA1", + "sha256": "HMAC-SHA256", + "sha384": "HMAC-SHA384", + "sha512": "HMAC-SHA512" + }, + "outputFormatLabel": "输出格式", + "formatHex": "Hex(小写)", + "formatBase64": "Base64", + "outputLabel": "签名", + "copyOutput": "复制签名", + "copied": "已复制", + "byteCountHint": "{count, plural, other {# 字节}} (UTF-8)", + "emptyHint": "输入密钥以计算 HMAC。", + "securityNote": "切勿泄露签名密钥。本页不会上传数据,一切在浏览器中运行。", + "errors": { + "cryptoFailed": "无法计算 HMAC。请检查浏览器是否支持 Web Crypto 后重试。", + "invalidKey": "Secret key isn't valid for the selected encoding." + }, + "keyEncodingLabel": "Key encoding", + "keyEncodingUtf8": "UTF-8 text", + "keyEncodingHex": "Hex", + "keyEncodingBase64": "Base64", + "keyByteCountHint": "{count, plural, one {# key byte} other {# key bytes}} decoded", + "verifyLabel": "Verify against expected signature", + "verifyPlaceholder": "Paste an expected signature to compare…", + "verifyMatch": "Match", + "verifyMismatch": "Mismatch" + }, + "TotpGenerator": { + "title": "TOTP / 2FA 验证码生成器", + "subtitle": "粘贴 Base32 密钥,获取当前六位 RFC 6238 验证码(SHA-1,30 秒)。全部在浏览器本地完成,适合测试登录或多因素验证流程。", + "secretLabel": "密钥(Base32)", + "placeholderSecret": "粘贴密钥(字母 A–Z 与数字 2–7,与设置身份验证器时相同)…", + "codeLabel": "当前验证码", + "copyCode": "复制验证码", + "copied": "已复制", + "nextIn": "{seconds} 秒后刷新", + "standardHint": "30 秒时间步长,6 位数字,HMAC-SHA-1(兼容 Google Authenticator)。", + "digitsLabel": "位数", + "periodLabel": "周期", + "algorithmLabel": "算法", + "periodSeconds": "{seconds} 秒", + "emptyHint": "粘贴密钥以生成六位验证码。", + "invalidHint": "请修正密钥以显示验证码。", + "securityNote": "请像密码一样保管。密钥不会离开浏览器;不会上传任何数据。", + "importLabel": "从 otpauth:// URI 导入", + "importPlaceholder": "otpauth://totp/Example:alice@example.com?secret=…", + "importAction": "导入", + "qrSectionTitle": "预配 QR 码", + "qrHint": "使用身份验证器应用(Google Authenticator、1Password、Aegis 等)扫描以添加此密钥。QR 码对密钥进行了编码 — 请像对待密码一样对待它。", + "accountLabel": "账户", + "accountPlaceholder": "alice@example.com", + "issuerLabel": "颁发者(可选)", + "issuerPlaceholder": "MyDevTools", + "qrEmptyHint": "输入有效的密钥以生成 QR 码。", + "errors": { + "invalidSecret": "不是有效的 Base32 密钥。", + "cryptoFailed": "无法计算 TOTP。请检查 Web Crypto 是否可用并重试。", + "invalidUri": "这不是有效的 otpauth:// TOTP URI。", + "unsupportedUri": "此 URI 使用的值不受工具支持(位数必须为 6 或 8;周期为 15、30 或 60 秒)。" + } + }, + "MarkdownPreview": { + "title": "Markdown 预览 & HTML", + "subtitle": "实时 Markdown 渲染,支持 HTML 导出和 HTML → Markdown 转换。一切在浏览器中运行。", + "tabMarkdownToHtml": "Markdown → HTML", + "tabHtmlToMd": "HTML → Markdown", + "inputLabel": "Markdown 输入", + "htmlInputLabel": "HTML 输入", + "previewLabel": "预览", + "htmlOutputLabel": "HTML 源码", + "mdOutputLabel": "Markdown 输出", + "inputPlaceholder": "# 你好\\n\\n在此输入你的 **markdown**…", + "htmlInputPlaceholder": "在此粘贴你的 HTML…", + "copyHtml": "复制 HTML", + "copyMarkdown": "复制 Markdown", + "exportHtml": "导出 HTML", + "copied": "已复制", + "charCount": "{count, plural, other {# 个字符}}", + "emptyHint": "开始输入 Markdown 以查看实时预览和 HTML 输出。", + "emptyHintHtmlToMd": "粘贴 HTML 以将其转换为 Markdown。", + "securityNote": "一切在您的浏览器本地运行,不会上传任何内容。" + }, + "NumberBaseConverter": { + "title": "进制转换器", + "subtitle": "在 2–36 进制之间转换整数。支持可选前缀 0x / 0b / 0o。全部在浏览器本地计算。", + "inputBaseLabel": "源进制", + "outputBaseLabel": "目标进制", + "inputLabel": "输入数字", + "placeholderInput": "以所选输入进制输入一个整数…", + "outputLabel": "转换结果", + "copyOutput": "复制结果", + "copied": "已复制", + "swap": "交换进制", + "byteCountHint": "{count, plural, other {# 字节}} (UTF-8 输入长度)", + "emptyHint": "输入要转换的数字。", + "note": "大于 9 的数位使用字母 A–Z(不区分大小写)。空格与下划线会被忽略。", + "errors": { + "invalidNumber": "该值对所选输入进制无效。" + } + }, + "FormatConverter": { + "title": "格式转换器", + "subtitle": "在 JSON、YAML、TOML 和 XML 之间任意方向转换。支持全部 12 种组合。所有操作均在浏览器中本地运行。", + "fromLabel": "从", + "toLabel": "到", + "swap": "交换", + "swapTitle": "交换格式并将输出用作输入", + "copy": "复制", + "copied": "已复制", + "charCount": "{count, plural, other {# 个字符}}", + "emptyHint": "在左侧粘贴或输入内容,即可看到转换后的输出。", + "localNote": "所有操作均在您的浏览器中本地运行。不会上传任何内容。" + }, + "MimeTypeLookup": { + "title": "MIME 类型查询", + "subtitle": "根据文件扩展名或完整文件名查询 MIME 类型(媒体类型)。所有操作都在浏览器本地完成。", + "inputLabel": "扩展名或文件名", + "placeholder": "例如 .png、package.json、photo.jpeg", + "hint": "提示:可粘贴扩展名(带或不带点)或完整文件名。", + "clear": "清空", + "exampleFilename": "使用示例文件名", + "exampleExtension": "使用示例扩展名", + "resultLabel": "结果", + "normalizedLabel": "规范化输入:", + "mimeTypeLabel": "MIME 类型", + "contentTypeLabel": "Content-Type 头", + "charsetLabel": "字符集", + "copyMimeType": "复制 MIME 类型", + "copy": "复制", + "copied": "已复制", + "emptyHint": "输入扩展名或文件名以查询 MIME 类型。", + "notFound": "未找到该输入对应的 MIME 类型。", + "localNote": "所有操作都在浏览器本地完成。不会上传任何内容。" + }, + "UserAgentParser": { + "title": "User-Agent 解析", + "subtitle": "粘贴一段 User-Agent(UA)字符串,即可查看浏览器、操作系统、引擎、设备与 CPU 的解析结果。所有操作均在浏览器本地完成。", + "inputLabel": "User-Agent 字符串", + "placeholder": "在此粘贴你的 User-Agent…", + "hint": "提示:你可以从请求头、日志或浏览器开发者工具中复制 UA。", + "clear": "清空", + "exampleDesktop": "使用桌面示例", + "exampleMobile": "使用移动端示例", + "resultLabel": "解析结果", + "browserLabel": "浏览器", + "osLabel": "操作系统", + "engineLabel": "引擎", + "deviceTypeLabel": "设备类型", + "deviceVendorLabel": "设备厂商", + "deviceModelLabel": "设备型号", + "cpuArchLabel": "CPU 架构", + "copyJson": "复制 JSON", + "copy": "复制", + "copied": "已复制", + "emptyHint": "粘贴 User-Agent 字符串以查看解析结果。", + "localNote": "所有操作均在浏览器本地完成。不会上传任何内容。" + }, + "MockDataGenerator": { + "title": "模拟数据生成器", + "subtitle": "定义列、选择数据类型,并导出用于种子数据、Postman 或测试的数据集。最多 {max} 行,全部在浏览器本地完成,不会上传。", + "mockarooLink": "Mockaroo 式工作流", + "rowsLabel": "行数", + "rowsHint": "每次生成最多 {max} 行,以保持标签页流畅。", + "formatLabel": "格式", + "formats": { + "json": "JSON", + "csv": "CSV", + "sql": "SQL 插入", + "xml": "XML" + }, + "tableNameLabel": "SQL 表名", + "presetsLabel": "预设", + "presetPlaceholder": "加载预设…", + "presetDefault": "入门(用户)", + "presetUsers": "用户与角色", + "presetProducts": "电商 SKU", + "presetApiLog": "HTTP 请求日志", + "generate": "生成", + "warningDuplicateNames": "列名重复:在 JSON 对象中,后出现的列会覆盖先出现的列。请重命名。", + "schemaHeading": "架构", + "addField": "添加列", + "removeField": "删除列", + "duplicateField": "复制列", + "colName": "名称", + "colType": "类型", + "colOptions": "选项", + "colBlank": "空值 %", + "optMin": "最小", + "optMax": "最大", + "optDecimals": "小数", + "optDateFrom": "起始日期", + "optDateTo": "结束日期", + "optValues": "a, b, c", + "optStep": "步长", + "optRowBase": "基准", + "output": "输出", + "outputPlaceholder": "点击「生成」以根据架构生成数据…", + "copy": "复制", + "download": "下载", + "copied": "已复制", + "groups": { + "personal": "个人", + "location": "位置", + "internetApi": "网络与 API", + "numbersIds": "数字与标识", + "dateTime": "日期与时间", + "textDesign": "文本与设计", + "custom": "自定义" + }, + "fieldTypes": { + "first_name": "名", + "last_name": "姓", + "full_name": "全名", + "email": "Email", + "username": "用户名", + "password": "密码", + "phone": "电话", + "company": "公司", + "job_title": "职位", + "avatar_url": "头像 URL", + "city": "城市", + "country": "国家", + "country_code": "国家代码(ISO)", + "street_address": "街道地址", + "zip_code": "邮政编码", + "latitude": "纬度", + "longitude": "经度", + "url": "URL", + "domain": "域名", + "ip_address": "IPv4", + "ipv6": "IPv6", + "mac_address": "MAC address", + "http_method": "HTTP method", + "http_status": "HTTP status", + "integer": "整数", + "float": "浮点数", + "boolean": "布尔值", + "uuid": "UUID", + "credit_card": "测试用信用卡", + "semver": "SemVer", + "row_index": "行号", + "sequence": "序列", + "date": "日期", + "datetime": "日期时间", + "unix_timestamp": "Unix 时间戳", + "timezone": "时区", + "word": "词", + "sentence": "句子", + "paragraph": "段落", + "slug": "Slug", + "hex_color": "Hex color", + "rgb_color": "RGB color", + "currency_code": "货币代码", + "language_code": "语言标签", + "custom_list": "自定义列表" + }, + "seedLabel": "Seed (optional)", + "seedPlaceholder": "e.g. 42", + "seedHint": "Same seed + same schema reproduces the same data.", + "seedRandomize": "Randomize seed" + }, + "SvgOptimizer": { + "title": "SVG 优化", + "subtitle": "粘贴 SVG,在浏览器中用 SVGO 压缩,并对比优化前后的 UTF-8 大小。", + "inputLabel": "输入", + "outputLabel": "已优化", + "placeholder": "粘贴 片段或完整文件…", + "outputPlaceholder": "优化后的 SVG 将显示在这里…", + "chars": "{count} 个字符 • {bytes} 字节(UTF-8)", + "preview": "预览", + "localNote": "全部在浏览器本地处理,不会上传任何内容。", + "stats": { + "title": "大小(UTF-8)", + "before": "优化前", + "after": "优化后", + "savedBytes": "节省 {count} 字节({percent}%)", + "noChange": "无变化 — 已足够精简或输出为空。" + }, + "actions": { + "clear": "清空", + "sample": "加载示例", + "copy": "复制", + "copied": "已复制", + "download": "下载" + }, + "errors": { + "optimizeFailed": "无法优化此 SVG。" + } + }, + "UnitConverter": { + "title": "单位换算器", + "subtitle": "在 43 个类别323 个单位之间转换", + "category": "类别", + "from": "从", + "to": "到", + "value": "数值", + "result": "结果", + "swapUnits": "交换单位", + "placeholder": "输入数值", + "copyResult": "复制结果", + "searchCategory": "搜索类别...", + "selectCategory": "选择类别", + "noResults": "未找到类别。" + }, + "DockerComposeGenerator": { + "title": "Docker Compose 生成器", + "subtitle": "选择常用数据库、缓存、消息队列、反向代理与可观测性组件——复制即可用于本地开发的 docker-compose.yml。", + "searchPlaceholder": "搜索(postgres、redis、kafka、nginx、grafana…)", + "selectedLabel": "服务", + "selectedCount": "{count, plural, other {# 个服务}}", + "outputTitle": "docker-compose.yml", + "copy": "复制", + "copied": "已复制!", + "download": "下载", + "emptyState": "请选择一个或多个服务以生成 Compose YAML。", + "hintPorts": "默认主机端口已错开以降低冲突;若 Docker 提示端口占用请修改。在共享环境前务必替换演示密码。", + "categories": { + "databases": "数据库与存储", + "cache": "缓存与内存", + "queues": "队列与消息", + "search": "搜索与日志", + "storage": "对象存储", + "proxy": "反向代理与入口", + "observability": "指标与追踪", + "mail": "邮件(本地开发)", + "admin": "管理与 BI 界面" + }, + "presets": { + "stackWeb": "Postgres + Redis + Nginx", + "stackMern": "MongoDB + Redis + Nginx", + "stackObserve": "Prometheus + Grafana + Loki", + "stackMessaging": "RabbitMQ + Redis", + "stackSearch": "Meilisearch + Postgres", + "clear": "清空" + }, + "services": { + "postgres": "PostgreSQL", + "mysql": "MySQL", + "mariadb": "MariaDB", + "mongodb": "MongoDB", + "redis": "Redis", + "valkey": "Valkey(兼容 Redis)", + "memcached": "Memcached", + "rabbitmq": "RabbitMQ", + "nats": "NATS", + "kafka": "Apache Kafka (KRaft)", + "redpanda": "Redpanda(兼容 Kafka)", + "elasticsearch": "Elasticsearch", + "opensearch": "OpenSearch", + "meilisearch": "Meilisearch", + "typesense": "Typesense", + "minio": "MinIO(兼容 S3)", + "clickhouse": "ClickHouse", + "cockroachdb": "CockroachDB", + "cassandra": "Apache Cassandra", + "neo4j": "Neo4j", + "influxdb": "InfluxDB", + "couchdb": "CouchDB", + "dynamodb-local": "Amazon DynamoDB Local", + "timescaledb": "TimescaleDB", + "nginx": "NGINX", + "traefik": "Traefik", + "caddy": "Caddy", + "prometheus": "Prometheus", + "grafana": "Grafana", + "jaeger": "Jaeger", + "zipkin": "Zipkin", + "loki": "Grafana Loki", + "mailpit": "Mailpit(SMTP 捕获)", + "pgadmin": "pgAdmin 4", + "adminer": "Adminer", + "phpmyadmin": "phpMyAdmin", + "vault": "HashiCorp Vault(开发模式)", + "keycloak": "Keycloak", + "portainer": "Portainer CE", + "metabase": "Metabase", + "gotenberg": "Gotenberg(Chromium / PDF)" + } + }, + "Game2048": { + "title": "2048", + "subtitle": "滑动方块,合并相同数字,达成 2048", + "score": "分数", + "best": "最高分", + "newGame": "新游戏", + "howToPlay": "用方向键(或在手机上滑动)移动方块。相同数字会合并。合成 2048 即可获胜!", + "youWin": "你达到了 2048!", + "keepGoing": "继续玩", + "gameOver": "游戏结束", + "tryAgain": "再试一次", + "gameOverMessage": "没有可走的步了。", + "undo": "撤销" + }, + "Sudoku": { + "title": "数独", + "stagesCompleted": "已完成关卡", + "time": "时间", + "best": "最佳", + "difficulty": { + "easy": "简单", + "medium": "中等", + "hard": "困难" + }, + "stage": "关卡", + "solved": "完成!", + "score": "得分", + "nextStage": "下一关", + "replay": "重玩", + "erase": "清除", + "howToPlay": "先点选格子,再点数字或按 1–9。退格或删除可清空。加粗数字为提示。填满九宫格,使每行、每列和每个 3×3 宫格都包含 1–9 且不重复。", + "pencil": "铅笔", + "undo": "撤销", + "hint": "提示", + "hintsUsed": "提示已使用", + "mistakes": "错误", + "failed": "失败!", + "mistakeMode": "3错误模式" + }, + "Snake": { + "title": "贪吃蛇", + "subtitle": "吃食物、成长、避开墙壁和自己", + "score": "分数", + "best": "最佳", + "startPrompt": "按任意方向键", + "startButton": "开始游戏", + "gameOver": "游戏结束", + "playAgain": "再玩一次", + "howToPlay": "用方向键或WASD移动。吃食物来成长。每50分速度加快。", + "diffEasy": "简单", + "diffNormal": "普通", + "diffHard": "困难", + "diffInfinite": "无限", + "orTap": "或点击下方", + "howToPlayInfinite": "穿墙模式 — 不会因碰墙死亡。吃食物来成长。", + "shieldActive": "护盾激活", + "speedBoost": "速度提升", + "powerSpeed": "速度 x2", + "powerShield": "免疫一次死亡", + "powerShrink": "缩短尾巴" + }, + "RedisCommander": { + "page": { + "title": "Redis 指挥官", + "dialogTitle": "Redis 连接", + "heroTitle": "Redis 指挥官", + "heroDesc": "连接到 Redis 实例。浏览键、编辑值并执行命令。凭据在您的浏览器中加密存储。", + "heroAddConnection": "添加连接", + "vaultLockedTitle": "保险库已锁定", + "vaultLockedDesc": "解锁保险库以访问已保存的连接。" + } + }, + "Minesweeper": { + "title": "扫雷", + "subtitle": "找出所有安全格子,不要引爆地雷", + "mines": "地雷", + "newGame": "新游戏", + "win": "你赢了!", + "loss": "你输了!", + "playAgain": "再玩一次", + "tryAgain": "再试一次", + "startPrompt": "点击任意格子开始", + "difficulty": { + "beginner": "初级", + "intermediate": "中级", + "expert": "高级" + } + }, + "Tetris": { + "title": "俄罗斯方块", + "subtitle": "排列下落的方块来消除整行", + "next": "下一个", + "score": "分数", + "best": "最佳", + "lines": "行数", + "level": "等级", + "pause": "暂停", + "resume": "继续", + "start": "开始", + "restart": "重新开始", + "paused": "已暂停", + "gameOver": "游戏结束" + }, + "SshKeyGenerator": { + "title": "SSH / RSA 密钥生成器", + "subtitle": "在浏览器中生成 Ed25519 或 RSA 密钥对。以 OpenSSH 和 PEM 格式下载私钥和公钥。不会上传任何内容。", + "keyTypeLabel": "密钥类型", + "recommended": "推荐", + "ed25519Desc": "快速、紧凑、现代。受 GitHub、GitLab 及所有主要 SSH 服务器支持。", + "rsa2048Desc": "广泛兼容。适合大多数用途。", + "rsa4096Desc": "为旧版系统提供最大兼容性和强度。", + "commentLabel": "注释(可选)", + "commentHint": "附加到 OpenSSH 公钥行。", + "generateBtn": "生成密钥对", + "generating": "生成中…", + "generatingLong": "生成中(可能需要片刻)…", + "copy": "复制", + "download": "下载", + "privateKeyLabel": "私钥(PKCS#8 PEM)", + "publicKeyOpenSSHLabel": "公钥(OpenSSH 格式)", + "publicKeySpkiLabel": "公钥(SPKI PEM)", + "browserNote": "所有密钥均在您的浏览器中生成。不会向任何服务器发送任何内容。", + "errors": { + "generationFailed": "密钥生成失败" + } + }, + "DnsLookup": { + "title": "DNS 查询", + "subtitle": "查询任意域名的 DNS 记录 — A、AAAA、MX、TXT、NS、CNAME、SOA。", + "placeholder": "输入域名(例如 github.com)", + "lookupBtn": "查询", + "looking": "正在查询…", + "refreshBtn": "刷新", + "recordTypesLabel": "记录类型", + "allBtn": "全部", + "tryLabel": "试试:", + "errors": { + "lookupFailed": "DNS 查询失败" + } + }, + "CurlToCode": { + "title": "cURL to Code", + "subtitle": "Convert a curl command into fetch, axios, Python, Go, and more.", + "panels": { + "input": "curl command", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a curl command here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated request code will appear here…" + }, + "JsonToCode": { + "title": "JSON to Code", + "subtitle": "Generate typed models in your language from a sample JSON payload.", + "panels": { + "input": "JSON input", + "output": "Generated code" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste a sample JSON object here…", + "rootName": "Root name", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Generated type models will appear here…" + }, + "JsonVisualizer": { + "title": "JSON Visualizer", + "subtitle": "Visualize JSON as an interactive node graph — search, collapse, and export as an image.", + "panels": { + "input": "JSON input", + "graph": "Graph" + }, + "sample": "Sample", + "clear": "Clear", + "placeholder": "Paste or type JSON here…", + "searchPlaceholder": "Search keys & values", + "prevMatch": "Previous match", + "nextMatch": "Next match", + "nodeCount": "{count} nodes", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fitView": "Fit to view", + "expandAll": "Expand all", + "collapseAll": "Collapse all", + "export": "Export", + "exportPng": "PNG image", + "exportSvg": "SVG image", + "copyPath": "Copy path", + "copyValue": "Copy value", + "pathCopied": "Path copied", + "valueCopied": "Value copied", + "viewTable": "View as table", + "tableTitle": "Table view", + "locateInText": "Locate in text", + "largeDocNotice": "Large document ({count} nodes) — subtrees start collapsed.", + "tooLarge": "Too many nodes to visualize (limit: {cap}). Try a smaller document.", + "emptyState": "Paste JSON in the input to see it as an interactive graph." + }, + "BeautifyMinify": { + "title": "Beautify & Minify", + "subtitle": "Beautify or minify HTML, CSS, JavaScript, XML, and JSON.", + "langLabel": "Language", + "actionLabel": "Action", + "beautify": "Beautify", + "minify": "Minify", + "panels": { + "input": "Input", + "output": "Output" + }, + "bytes": "{count} bytes", + "clear": "Clear", + "placeholder": "Paste code here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Formatted output will appear here…" + }, + "StringCaseConverter": { + "title": "String Case Converter", + "subtitle": "Convert text between camelCase, snake_case, kebab-case, and more.", + "panels": { + "input": "Input", + "output": "Case variants" + }, + "wordCount": "{count} words", + "clear": "Clear", + "placeholder": "Type or paste text to convert…", + "copyTitle": "Copy to clipboard", + "emptyHint": "—" + }, + "LineSortDedupe": { + "title": "Line Sort & Dedupe", + "subtitle": "Sort, deduplicate, trim, and clean up lists of lines.", + "opts": { + "sort": "Sort", + "dedupe": "Remove duplicates", + "removeEmpty": "Remove empty lines", + "trim": "Trim lines", + "caseInsensitive": "Case-insensitive" + }, + "sortModes": { + "none": "No sort", + "asc": "Ascending (A–Z)", + "desc": "Descending (Z–A)", + "natural": "Natural (numeric)", + "length": "By length", + "reverse": "Reverse order" + }, + "panels": { + "input": "Input", + "output": "Output" + }, + "lineCount": "{count} lines", + "clear": "Clear", + "placeholder": "Paste lines here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Processed lines will appear here…" + }, + "StringInspector": { + "title": "String Inspector", + "subtitle": "Count characters, words, and bytes, and reveal hidden characters.", + "panels": { + "input": "Input" + }, + "clear": "Clear", + "placeholder": "Type or paste text to inspect…", + "stats": { + "characters": "Characters", + "charactersNoSpaces": "No spaces", + "words": "Words", + "lines": "Lines", + "sentences": "Sentences", + "paragraphs": "Paragraphs", + "bytes": "Bytes (UTF-8)", + "utf16": "UTF-16 units", + "letters": "Letters", + "digits": "Digits", + "whitespace": "Whitespace", + "punctuation": "Punctuation", + "readingTime": "Reading time" + }, + "specialTitle": "Special & non-ASCII characters", + "noSpecial": "No special or non-ASCII characters found.", + "specialEmpty": "Special characters will be listed here…", + "cols": { + "codepoint": "Code point", + "name": "Name", + "count": "Count" + }, + "nonAscii": "Non-ASCII character" + }, + "EscapeEncode": { + "title": "Escape / Encode", + "subtitle": "Escape and unescape HTML entities, backslash strings, hex, and Unicode.", + "modeLabel": "Mode", + "modes": { + "html": "HTML Entities", + "backslash": "Backslash (JS / JSON string)", + "hex": "Hex ↔ ASCII", + "unicode": "Unicode \\uXXXX" + }, + "directionLabel": "Direction", + "encode": "Encode", + "decode": "Decode", + "swap": "Swap", + "panels": { + "input": "Input", + "output": "Output" + }, + "clear": "Clear", + "placeholder": "Type or paste text here…", + "copy": "Copy", + "copied": "Copied", + "outputPlaceholder": "Result will appear here…" + }, + "BcryptGenerator": { + "title": "Bcrypt 生成与校验", + "subtitle": "使用 bcrypt 哈希密码并校验哈希 — 一切都在你的浏览器中完成", + "tabs": { + "hash": "哈希", + "verify": "校验" + }, + "showPassword": "显示密码", + "hidePassword": "隐藏密码", + "copy": "复制", + "copied": "已复制", + "hash": { + "passwordLabel": "密码", + "passwordPlaceholder": "输入要哈希的密码…", + "costLabel": "成本因子", + "costHint": "更高的成本因子会使每一步的计算量翻倍。生产环境常用 10-12。", + "slowWarning": "成本因子达到 13 及以上可能需要数秒才能计算完成。", + "generate": "生成哈希", + "generating": "正在哈希…", + "resultLabel": "Bcrypt 哈希", + "timeTaken": "{ms} 毫秒", + "anatomy": { + "title": "哈希结构", + "algorithm": "算法", + "cost": "成本", + "salt": "盐值(22 个字符)", + "hash": "哈希(31 个字符)" + } + }, + "verify": { + "passwordLabel": "密码", + "passwordPlaceholder": "输入要校验的密码…", + "hashLabel": "Bcrypt 哈希", + "hashPlaceholder": "$2b$10$…", + "idle": "输入密码和 bcrypt 哈希以进行比对。", + "invalidHash": "这看起来不是有效的 bcrypt 哈希。", + "checking": "正在比对…", + "match": "匹配 — 该密码可生成此哈希。", + "noMatch": "不匹配 — 该密码无法生成此哈希。" + } + }, + "ChmodCalculator": { + "title": "Chmod 计算器", + "subtitle": "在复选框、八进制和符号表示之间转换 Unix 文件权限", + "grid": { + "title": "权限", + "read": "读取", + "write": "写入", + "execute": "执行", + "owner": "所有者", + "group": "组", + "others": "其他用户" + }, + "special": { + "title": "特殊位", + "setuid": "Setuid", + "setgid": "Setgid", + "sticky": "Sticky" + }, + "octal": { + "label": "八进制", + "placeholder": "755 或 4755", + "invalid": "请输入 3 或 4 位八进制数字(0-7)。" + }, + "symbolic": { + "label": "符号表示", + "placeholder": "rwxr-xr-x", + "invalid": "请输入 9 个字符,如 rwxr-xr-x(允许 s/S、t/T)。" + }, + "output": { + "title": "命令", + "copy": "复制", + "copied": "已复制" + }, + "explain": { + "line": "{cls}可以{perms}。", + "none": "{cls}没有任何权限。", + "setuid": "Setuid:文件以其所有者的权限运行。", + "setgid": "Setgid:文件以其所属组的权限运行(目录中的新文件会继承该组)。", + "sticky": "Sticky:只有文件的所有者才能在此目录中删除或重命名它。" + }, + "presets": { + "title": "常用预设", + "octal": "八进制", + "permissions": "权限", + "description": "典型用途", + "p644": "普通文件:所有者可写,所有人可读。", + "p755": "可执行文件和目录:所有人可读取和进入。", + "p600": "私有文件:只有所有者可读写。", + "p700": "私有可执行文件和目录:仅限所有者。", + "p664": "组可写文件。", + "p775": "组可写目录和可执行文件。", + "p400": "所有者只读(例如 SSH 私钥)。" + } + }, + "CodeScreenshot": { + "title": "代码截图", + "subtitle": "将代码转换为精美、可分享的图片", + "languageLabel": "语言", + "autoDetect": "自动检测", + "backgroundLabel": "背景", + "paddingLabel": "内边距", + "filenameLabel": "文件名", + "filenamePlaceholder": "example.ts", + "windowLabel": "窗口圆点", + "themeLabel": "窗口主题", + "themeDark": "深色", + "themeLight": "浅色", + "scaleLabel": "导出倍率", + "copyImage": "复制图片", + "copied": "已复制", + "exportPng": "导出 PNG", + "inputLabel": "代码", + "detected": "已检测:{language}", + "clear": "清空", + "placeholder": "在此粘贴你的代码…", + "previewLabel": "预览", + "emptyPreview": "你的代码预览将显示在这里", + "exportFailed": "导出图片失败", + "copyUnsupported": "此浏览器不支持复制图片", + "copyFailed": "复制图片失败", + "backgrounds": { + "ocean": "海洋", + "sunset": "日落", + "candy": "糖果", + "forest": "森林", + "midnight": "午夜", + "gold": "金色", + "aurora": "极光", + "slate": "石板灰", + "transparent": "透明" + } + }, + "CssGenerators": { + "title": "CSS 生成器", + "subtitle": "Box-shadow、border-radius 和流式 clamp() — 复制 CSS。", + "preview": "预览", + "output": "CSS", + "copy": "复制", + "copied": "已复制", + "tabs": { + "boxShadow": "Box Shadow", + "borderRadius": "Border Radius", + "clamp": "流式 clamp()" + }, + "boxShadow": { + "presets": "预设", + "layer": "图层", + "addLayer": "添加图层", + "removeLayer": "移除图层", + "x": "X", + "y": "Y", + "blur": "模糊", + "spread": "扩展", + "color": "颜色", + "alpha": "透明度", + "inset": "内阴影", + "presetNames": { + "subtleCard": "轻微卡片", + "elevated": "悬浮", + "hardOffset": "硬偏移", + "neonGlow": "霓虹光晕", + "inner": "内阴影", + "layeredSmooth": "多层柔和" + } + }, + "borderRadius": { + "modeLabel": "模式", + "modes": { + "simple": "逐角", + "blob": "有机形状" + }, + "unit": "单位", + "linkCorners": "关联各角", + "randomize": "随机", + "horizontal": "水平半径", + "vertical": "垂直半径", + "corners": { + "tl": "左上", + "tr": "右上", + "br": "右下", + "bl": "左下" + } + }, + "clamp": { + "minFont": "最小字号 (px)", + "maxFont": "最大字号 (px)", + "minViewport": "最小视口 (px)", + "maxViewport": "最大视口 (px)", + "rootFont": "根字号 (px)", + "viewportWidth": "模拟视口", + "computedSize": "计算尺寸", + "slope": "斜率", + "intercept": "截距", + "previewText": "The quick brown fox", + "invalid": "请输入有效且不同的视口范围。" + } + }, + "ExifViewer": { + "title": "EXIF 查看与移除", + "subtitle": "在浏览器中检查照片元数据并将其清除", + "dropTitle": "将图片拖放到此处", + "dropHint": "或点击浏览 — 你的图片绝不会离开你的设备", + "browse": "选择图片", + "formats": "JPEG、PNG、WebP、HEIC、TIFF", + "parsing": "正在读取元数据…", + "parseFailed": "读取此图片失败", + "clear": "移除图片", + "gpsWarning": "包含 GPS 位置信息", + "openMap": "在 OpenStreetMap 上查看", + "noMetadata": "此图片中未找到嵌入的元数据。", + "facts": { + "name": "名称", + "type": "类型", + "dimensions": "尺寸", + "size": "大小" + }, + "groups": { + "camera": "相机", + "exposure": "曝光", + "timestamps": "时间戳", + "gps": "GPS 位置", + "software": "软件与其他" + }, + "gpsCard": { + "latitude": "纬度", + "longitude": "经度", + "altitude": "海拔" + }, + "raw": { + "title": "全部元数据", + "copyJson": "复制 JSON", + "copied": "已复制" + }, + "remove": { + "title": "移除元数据", + "jpegDescription": "无损移除 EXIF、XMP、IPTC 和注释,且不重新压缩图片。", + "reencodeDescription": "重新编码图片以清除嵌入的元数据。这会重新压缩像素。", + "keepIcc": "保留颜色配置文件 (ICC)", + "stripButton": "清除元数据", + "reencodeButton": "重新编码并清除", + "unsupported": "此文件类型不支持移除元数据。", + "failed": "移除元数据失败", + "before": "之前", + "after": "之后", + "download": "下载已清理的图片" + } + }, + "FaviconGenerator": { + "title": "Favicon 生成器", + "subtitle": "从一张图片创建 favicon 和应用图标", + "dropTitle": "将图片拖放到此处", + "dropHint": "PNG、JPEG 或 SVG — 点击浏览", + "browse": "选择图片", + "clear": "移除图片", + "backgroundLabel": "背景", + "transparent": "透明", + "solidColor": "纯色", + "cornerLabel": "圆角形状", + "cornerNone": "方形", + "cornerRounded": "圆角", + "cornerCircle": "圆形", + "maskPaddingLabel": "可遮罩内边距", + "maskable": "maskable", + "previewLabel": "预览", + "copy": "复制", + "headSnippet": "HTML head 代码片段", + "downloadIco": "下载 favicon.ico", + "downloadZip": "下载全部 (.zip)" + }, + "JsonDiff": { + "title": "JSON 对比", + "subtitle": "对两个 JSON 文档进行语义化、结构感知的比较 — 忽略键顺序和格式", + "options": { + "ignoreArrayOrder": "忽略数组顺序" + }, + "loadBothSamples": "加载示例", + "sample": "示例", + "format": "格式化", + "clear": "清空", + "formatError": "无法格式化:输入不是有效的 JSON", + "placeholder": "在此粘贴或输入 JSON…", + "panels": { + "left": "原始(左)", + "right": "修改后(右)", + "results": "差异" + }, + "summary": { + "added": "{count} 项新增", + "removed": "{count} 项移除", + "changed": "{count} 项变更" + }, + "types": { + "added": "新增", + "removed": "移除", + "changed": "变更" + }, + "copyChanges": "复制为 JSON", + "copied": "已复制", + "empty": "在两个面板中粘贴 JSON 以查看它们的差异。", + "identical": "这两个文档在语义上完全相同。" + }, + "JsonpathPlayground": { + "title": "JSONPath 沙盒", + "subtitle": "使用 JSONPath 或 JMESPath 查询 JSON 文档并实时查看结果", + "engineLabel": "引擎", + "queryLabel": "查询", + "engines": { + "jsonpath": "JSONPath", + "jmespath": "JMESPath" + }, + "cheatsheet": "速查表", + "cheats": { + "jpAllTitles": "所有书名", + "jpAllAuthors": "文档中任意位置的所有作者", + "jpFirstBook": "第一本书", + "jpLastBook": "最后一本书", + "jpSlice": "前两本书(切片)", + "jpFilterPrice": "价格低于 40 的书", + "jpFilterInStock": "有库存书籍的书名", + "jpRecursive": "递归获取所有值", + "jmAllTitles": "所有书名", + "jmFirstAuthor": "第一本书的作者", + "jmLastBook": "最后一本书", + "jmSlice": "前两本书(切片)", + "jmFilterPrice": "价格低于 40 的书名", + "jmCount": "统计有库存的书籍", + "jmProjection": "将每本书重塑为新对象", + "jmSort": "按价格排序的书名" + }, + "panels": { + "input": "JSON 输入", + "results": "结果" + }, + "sample": "示例", + "format": "格式化", + "clear": "清空", + "formatError": "无法格式化:输入不是有效的 JSON", + "placeholder": "在此粘贴或输入 JSON…", + "outputPlaceholder": "匹配结果将显示在这里…", + "matchCount": "{count, plural, one {# 个匹配} other {# 个匹配}}", + "copy": "复制", + "copied": "已复制" + }, + "KeycodeInspector": { + "title": "键码检查器", + "subtitle": "按下任意键以检查其 JavaScript 键盘事件", + "capture": { + "prompt": "按下任意键…", + "ready": "正在监听 — 请按键盘上的任意键", + "clickToFocus": "点击此处以聚焦,然后按任意键" + }, + "hero": { + "spaceKey": "(空格)", + "keyLabel": "event.key", + "repeat": "重复" + }, + "copyJson": "复制 JSON", + "copiedJson": "事件 JSON 已复制到剪贴板", + "deprecated": "已弃用", + "fields": { + "code": "event.code", + "keyCode": "event.keyCode", + "which": "event.which", + "location": "event.location" + }, + "location": { + "standard": "标准", + "left": "左", + "right": "右", + "numpad": "小键盘", + "unknown": "未知" + }, + "history": { + "title": "历史记录", + "clear": "清空历史记录", + "empty": "按键记录将显示在这里。" + } + }, + "MarkdownTableGenerator": { + "title": "Markdown 表格生成器", + "subtitle": "编辑网格、从电子表格粘贴,并复制对齐美观的 Markdown 或 HTML 表格", + "addRow": "添加行", + "addColumn": "添加列", + "importLabel": "导入", + "importPlaceholder": "在此粘贴 TSV(来自 Excel / Google Sheets)或 CSV…", + "importButton": "导入到网格", + "pasteClipboard": "从剪贴板粘贴", + "importEmpty": "无内容可导入 — 请先粘贴一些行", + "clipboardError": "无法从剪贴板读取", + "clear": "清空", + "panels": { + "editor": "表格编辑器", + "output": "输出" + }, + "align": { + "left": "左对齐", + "center": "居中对齐", + "right": "右对齐" + }, + "headerPlaceholder": "表头", + "cellPlaceholder": "单元格", + "removeRow": "移除行", + "removeColumn": "移除列", + "tabs": { + "markdown": "Markdown", + "html": "HTML" + }, + "copy": "复制", + "copied": "已复制" + }, + "TimezoneConverter": { + "title": "时区转换器", + "subtitle": "在全球范围内转换某一时刻,方便安排会议。", + "dateTimeLabel": "日期和时间", + "now": "现在", + "sourceZoneLabel": "源时区", + "selectZone": "选择时区", + "searchZone": "搜索时区...", + "noZone": "未找到时区。", + "scrubLabel": "调整时间", + "reset": "重置", + "targetsLabel": "目标时区", + "addZone": "添加时区", + "remove": "移除", + "localBadge": "本地", + "invalidDate": "请输入有效的日期和时间。", + "bands": { + "work": "工作时间(09:00–18:00)", + "edge": "边缘时间(清晨 / 傍晚)", + "night": "夜间" + } + }, + "TokenCounter": { + "title": "LLM Token 计数器", + "subtitle": "统计 token、可视化分块并估算 API 成本。", + "inputLabel": "文本", + "sample": "示例", + "clear": "清空", + "placeholder": "粘贴文本、代码或 JSON 以统计 token...", + "encoding": "编码", + "loading": "正在加载分词器...", + "encodings": { + "o200k": "o200k_base (GPT-4o / o 系列)", + "cl100k": "cl100k_base (GPT-4 / 3.5)" + }, + "stats": { + "tokens": "Token 数", + "characters": "字符数", + "words": "单词数", + "ratio": "Token/单词", + "ratioSub": "约" + }, + "claudeRow": "Claude(近似)", + "estimate": "估算", + "viz": { + "title": "Token 可视化", + "toggle": "显示", + "hint": "切换以将每个 token 着色为一个分块。", + "empty": "输入文本以可视化 token。", + "truncated": "显示 {total} 个 token 中的前 {limit} 个。" + }, + "cost": { + "title": "成本计算器", + "disclaimer": "可编辑的默认值 — 请核对当前定价", + "model": "模型", + "expectedOutput": "预期输出 token 数", + "customInput": "输入 $/MTok", + "customOutput": "输出 $/MTok", + "inputCost": "输入成本", + "outputCost": "输出成本", + "total": "总计", + "per1000": "每 1000 次调用" + } + }, + "WebhookTester": { + "title": "Webhook 测试器", + "subtitle": "创建临时 URL 并实时检查传入的 webhook 请求。", + "intro": { + "title": "创建 webhook URL 以开始捕获请求", + "hint": "你将获得一个无法猜测的 URL,它会记录每个传入的请求。24 小时后过期。" + }, + "createBtn": "创建 webhook URL", + "creating": "正在创建…", + "urlLabel": "你的 webhook URL", + "expiresIn": "{countdown} 后过期", + "copyUrlBtn": "复制 URL", + "copied": "已复制", + "clearBtn": "清空", + "newBinBtn": "新建 URL", + "requestsTitle": "请求", + "selectPrompt": "选择一个请求以查看其详细信息。", + "expiredNotice": "此 webhook URL 已过期。创建一个新的以继续测试。", + "base64Badge": "base64", + "truncatedBadge": "已截断", + "empty": { + "title": "正在等待请求…", + "hint": "向你的 URL 发送请求,它会立即显示在这里。试试:" + }, + "detail": { + "receivedAt": "接收时间", + "source": "来源 IP", + "contentType": "Content-Type", + "query": "查询参数", + "headers": "请求头", + "body": "正文", + "copyBody": "复制正文", + "emptyBody": "无正文" + }, + "errors": { + "createFailed": "创建 webhook URL 失败。请重试。", + "clearFailed": "清空请求失败。请重试。" + } + }, + "WebsocketTester": { + "title": "WebSocket 测试器", + "subtitle": "连接 WebSocket 端点并实时发送、接收和检查消息", + "urlLabel": "端点 URL", + "protocolsLabel": "子协议", + "protocolsPlaceholder": "可选,以逗号分隔", + "connect": "连接", + "disconnect": "断开连接", + "status": { + "disconnected": "已断开", + "connecting": "正在连接", + "connected": "已连接", + "error": "错误" + }, + "messages": { + "connecting": "正在连接到 {url}…", + "connected": "连接已建立", + "connectedWithProtocol": "连接已建立(协议:{protocol})", + "invalidUrl": "无效的 URL — 必须以 ws:// 或 wss:// 开头", + "connectFailed": "打开连接失败", + "socketError": "发生连接错误", + "notConnected": "未连接 — 请先连接", + "binaryMessage": "收到二进制消息" + }, + "log": { + "title": "消息日志", + "empty": "暂无消息。连接后开始发送。", + "copyAll": "全部复制", + "download": "下载为 .txt", + "clear": "清空日志", + "prettyPrint": "美化 JSON", + "showRaw": "显示原始文本" + }, + "composer": { + "placeholder": "输入要发送的消息…", + "send": "发送", + "formatJson": "格式化为 JSON", + "invalidJson": "消息不是有效的 JSON", + "hint": "Cmd/Ctrl+Enter 发送" + }, + "ping": { + "label": "自动 ping", + "seconds": "秒", + "intervalTitle": "Ping 间隔(秒)", + "payloadPlaceholder": "ping 负载" + } + }, + "WhoisLookup": { + "title": "Whois 查询", + "subtitle": "通过 RDAP 查询任意域名或 IP 地址的注册信息", + "placeholder": "example.com 或 8.8.8.8", + "lookupBtn": "查询", + "looking": "正在查询…", + "refreshBtn": "刷新", + "tryLabel": "试试:", + "copy": "复制", + "copied": "已复制", + "notAvailable": "不可用", + "summary": { + "registrar": "注册商", + "network": "网络" + }, + "dates": { + "created": "创建时间", + "updated": "更新时间", + "expires": "过期时间" + }, + "sections": { + "status": "状态", + "nameservers": "名称服务器", + "contacts": "联系人", + "rawRdap": "原始 RDAP" + }, + "contacts": { + "roles": "角色", + "name": "名称", + "organization": "组织" + }, + "errors": { + "invalidTarget": "请输入有效的域名或 IP 地址", + "lookupFailed": "查询失败(状态 {status})" + } + } +} diff --git a/apps/desktop-ui/next.config.ts b/apps/desktop-ui/next.config.ts new file mode 100644 index 00000000..a9cf911c --- /dev/null +++ b/apps/desktop-ui/next.config.ts @@ -0,0 +1,74 @@ +import type { NextConfig } from 'next'; +import createNextIntlPlugin from 'next-intl/plugin'; + +const withNextIntl = createNextIntlPlugin(); + +/** Baked into client + server at build time; set APP_BUILD_ID in CI. */ +function getAppBuildId(): string { + return ( + process.env.APP_BUILD_ID || + (process.env.NODE_ENV === 'development' ? 'development' : 'local') + ); +} + +/** + * Desktop-only frontend. The packaged app loads a static export (build-tauri.mjs + * sets TAURI_BUILD=1). In `tauri dev` it runs a normal `next dev` server over + * http — export mode stays OFF there so request-dynamic APIs (the NEXT_LOCALE + * cookie read in i18n/request.ts) work. + */ +const isTauriBuild = process.env.TAURI_BUILD === '1'; + +const nextConfig: NextConfig = { + ...(isTauriBuild ? { output: 'export' as const } : {}), + env: { + NEXT_PUBLIC_APP_BUILD_ID: getAppBuildId(), + }, + images: { + unoptimized: true, + remotePatterns: [ + { + protocol: 'https', + hostname: 'www.google.com', + }, + { + protocol: 'https', + hostname: 'api.qrserver.com', + }, + ], + }, + experimental: { + optimizePackageImports: [ + '@radix-ui/react-accordion', + '@radix-ui/react-alert-dialog', + '@radix-ui/react-avatar', + '@radix-ui/react-checkbox', + '@radix-ui/react-collapsible', + '@radix-ui/react-context-menu', + '@radix-ui/react-dialog', + '@radix-ui/react-dropdown-menu', + '@radix-ui/react-label', + '@radix-ui/react-popover', + '@radix-ui/react-progress', + '@radix-ui/react-radio-group', + '@radix-ui/react-scroll-area', + '@radix-ui/react-select', + '@radix-ui/react-separator', + '@radix-ui/react-slider', + '@radix-ui/react-slot', + '@radix-ui/react-switch', + '@radix-ui/react-tabs', + '@radix-ui/react-toast', + '@radix-ui/react-toggle', + '@radix-ui/react-toggle-group', + '@radix-ui/react-tooltip', + '@tabler/icons-react', + 'lucide-react', + 'date-fns', + 'lodash', + 'framer-motion', + ], + }, +}; + +export default withNextIntl(nextConfig); diff --git a/apps/desktop-ui/package.json b/apps/desktop-ui/package.json new file mode 100644 index 00000000..4a9b01e9 --- /dev/null +++ b/apps/desktop-ui/package.json @@ -0,0 +1,156 @@ +{ + "name": "@mydevtools/desktop-ui", + "version": "0.1.0", + "private": true, + "engines": { + "node": ">=20.19.0" + }, + "scripts": { + "dev": "next dev", + "build": "next build", + "lint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "build:tauri": "node scripts/build-tauri.mjs", + "dev:tauri": "NEXT_PUBLIC_TAURI=1 next dev" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@hookform/resolvers": "^3", + "@microsoft/clarity": "^1.0.2", + "@milkdown/crepe": "7.21.3", + "@milkdown/kit": "7.21.3", + "@monaco-editor/react": "^4.7.0", + "@peculiar/x509": "^2.0.0", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toast": "^1.2.15", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", + "@radix-ui/react-visually-hidden": "^1.2.7", + "@simplewebauthn/browser": "^11.0.0", + "@space-man/react-theme-animation": "^1.1.1", + "@tabler/icons-react": "^3.35.0", + "@tanstack/react-query": "^5", + "@tanstack/react-virtual": "^3.14.3", + "@tauri-apps/api": "^2.11.1", + "@tauri-apps/plugin-deep-link": "^2.4.9", + "@tauri-apps/plugin-opener": "^2.5.4", + "@tauri-apps/plugin-process": "^2.3.1", + "@tauri-apps/plugin-updater": "^2.10.1", + "@vercel/analytics": "^1.5.0", + "@vercel/speed-insights": "^1.2.0", + "aws4fetch": "^1.0.20", + "bcryptjs": "^3.0.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "1.1.1", + "color": "^5.0.3", + "comlink": "^4.4.2", + "cron-parser": "^5.0.0", + "cronstrue": "^2.0.0", + "date-fns": "^4.1.0", + "diff": "^8.0.2", + "exifr": "^7.1.3", + "fast-xml-parser": "^5.6.0", + "file-saver": "^2.0.5", + "firebase": "^12.5.0", + "framer-motion": "^12.23.24", + "geist": "^1.5.1", + "gpt-tokenizer": "^3.4.0", + "graphql": "^16.11.0", + "highlight.js": "^11.11.1", + "html-to-image": "^1.11.13", + "ioredis": "^5.10.1", + "ipaddr.js": "^2.3.0", + "jmespath": "^0.16.0", + "js-md5": "^0.8.3", + "js-yaml": "^4.1.1", + "jsonpath-plus": "^10.4.0", + "jsonrepair": "^3.13.1", + "jspdf": "^4.2.1", + "jszip": "^3.10.1", + "lowlight": "^3.3.0", + "lucide-react": "^0.552.0", + "marked": "^18.0.0", + "mime-types": "^3.0.2", + "monaco-editor": "^0.53.0", + "mongodb": "^7.0.0", + "mysql2": "^3.22.0", + "netmask": "^2.0.2", + "next": "^16.2.2", + "next-intl": "^4.8.3", + "next-themes": "^0.4.6", + "pg": "^8.20.0", + "protobufjs": "^8.6.5", + "qr-code-styling": "^1.9.2", + "qrcode": "^1.5.4", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-firebase-hooks": "^5.1.1", + "react-github-calendar": "^5.0.6", + "react-hook-form": "^7", + "react-resizable-panels": "^3.0.6", + "reflect-metadata": "^0.2.2", + "smol-toml": "^1.6.1", + "sonner": "^2.0.7", + "sql-formatter": "^15.7.3", + "svgo": "^4.0.0", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "turndown": "^7.2.4", + "ua-parser-js": "^2.0.9", + "undici": "^8.5.0", + "use-debounce": "^10.0.6", + "uuid": "^13.0.0", + "vanilla-jsoneditor": "^3.12.0", + "vaul": "^1.1.2", + "xlsx": "^0.18.5", + "zod": "^3", + "zustand": "^5.0.8" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@next/bundle-analyzer": "^15.5.15", + "@types/file-saver": "^2.0.7", + "@types/jest": "^30.0.0", + "@types/jmespath": "^0.15.2", + "@types/js-yaml": "^4.0.9", + "@types/mime-types": "^3.0.1", + "@types/node": "^24", + "@types/pg": "^8.20.0", + "@types/qrcode": "^1.5.6", + "@types/react": "^19", + "@types/react-dom": "^19", + "@types/turndown": "^5.0.6", + "autoprefixer": "^10.4.21", + "eslint": "^9.39.4", + "eslint-config-next": "16.0.1", + "jest": "^30.4.2", + "postcss": "^8", + "rimraf": "^6.1.0", + "tailwindcss": "^3.4.17", + "ts-jest": "^29.4.11", + "typescript": "^5.7.3" + } +} diff --git a/apps/desktop-ui/postcss.config.mjs b/apps/desktop-ui/postcss.config.mjs new file mode 100644 index 00000000..2ef30fcf --- /dev/null +++ b/apps/desktop-ui/postcss.config.mjs @@ -0,0 +1,9 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; + +export default config; diff --git a/apps/desktop-ui/public/7123025_logo_google_g_icon.svg b/apps/desktop-ui/public/7123025_logo_google_g_icon.svg new file mode 100644 index 00000000..d949ddeb --- /dev/null +++ b/apps/desktop-ui/public/7123025_logo_google_g_icon.svg @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/apps/desktop-ui/public/api-client-sw.js b/apps/desktop-ui/public/api-client-sw.js new file mode 100644 index 00000000..d0622e7a --- /dev/null +++ b/apps/desktop-ui/public/api-client-sw.js @@ -0,0 +1,63 @@ +/** + * API-client service worker. + * + * Two jobs: + * 1. Cache GET responses from `/api/backend/api-client/*` with stale-while-revalidate + * so the sidebar / collections render from cache offline. + * 2. Pass-through for everything else. + * + * Scope limited to /app/api-client paths so we don't shadow the rest of the site. + */ + +const CACHE_NAME = "mdt-api-client-v1" +const CACHED_PREFIXES = [ + "/api/backend/api-client/collections", + "/api/backend/api-client/environments", + "/api/backend/api-client/history", +] + +self.addEventListener("install", (event) => { + self.skipWaiting() + event.waitUntil(caches.open(CACHE_NAME)) +}) + +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))) + ) + ) + self.clients.claim() +}) + +function isCacheable(url) { + return CACHED_PREFIXES.some((p) => url.pathname.startsWith(p)) +} + +self.addEventListener("fetch", (event) => { + if (event.request.method !== "GET") return + const url = new URL(event.request.url) + if (!isCacheable(url)) return + + event.respondWith((async () => { + const cache = await caches.open(CACHE_NAME) + const cached = await cache.match(event.request) + const networkPromise = fetch(event.request) + .then(async (res) => { + if (res.ok) await cache.put(event.request, res.clone()) + return res + }) + .catch(() => null) + if (cached) { + // Refresh in background. + event.waitUntil(networkPromise) + return cached + } + const fresh = await networkPromise + if (fresh) return fresh + return new Response(JSON.stringify({ error: "offline + no cached copy" }), { + status: 503, + headers: { "Content-Type": "application/json" }, + }) + })()) +}) diff --git a/apps/desktop-ui/public/favicon.ico b/apps/desktop-ui/public/favicon.ico new file mode 100644 index 00000000..7cb8a480 Binary files /dev/null and b/apps/desktop-ui/public/favicon.ico differ diff --git a/apps/desktop-ui/public/file.svg b/apps/desktop-ui/public/file.svg new file mode 100644 index 00000000..004145cd --- /dev/null +++ b/apps/desktop-ui/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-ui/public/gitignore-templates.json b/apps/desktop-ui/public/gitignore-templates.json new file mode 100644 index 00000000..bf8fc071 --- /dev/null +++ b/apps/desktop-ui/public/gitignore-templates.json @@ -0,0 +1 @@ +{"list":["1c","1c-bitrix","a-frame","actionscript","ada","adobe","advancedinstaller","adventuregamestudio","agda","al","alteraquartusii","altium","amplify","android","androidstudio","angular","anjuta","ansible","ansibletower","apachecordova","apachehadoop","appbuilder","appceleratortitanium","appcode","appcode+all","appcode+iml","appengine","aptanastudio","arcanist","archive","archives","archlinuxpackages","asdf","aspnetcore","assembler","astro","ate","atmelstudio","ats","audio","autohotkey","automationstudio","autotools","autotools+strict","awr","azurefunctions","azurite","backup","ballerina","basercms","basic","batch","bazaar","bazel","bitrise","bitrix","bittorrent","blackbox","blender","bloop","bluej","bookdown","bower","bricxcc","buck","c","c++","cake","cakephp","cakephp2","cakephp3","calabash","carthage","certificates","ceylon","cfwheels","chefcookbook","chocolatey","circuitpython","clean","clion","clion+all","clion+iml","clojure","cloud9","cmake","cocoapods","cocos2dx","cocoscreator","codeblocks","codecomposerstudio","codeigniter","codeio","codekit","codesniffer","coffeescript","commonlisp","compodoc","composer","compressed","compressedarchive","compression","conan","concrete5","coq","cordova","craftcms","crashlytics","crbasic","crossbar","crystal","cs-cart","csharp","cuda","cvs","cypressio","d","dart","darteditor","data","database","datarecovery","dbeaver","dbt","defold","delphi","deno","dframe","diff","direnv","diskimage","django","dm","docfx","docpress","docusaurus","docz","dotenv","dotfilessh","dotnetcore","dotsettings","doxygen","dreamweaver","dropbox","drupal","drupal7","drupal8","e2studio","eagle","easybook","eclipse","eiffelstudio","elasticbeanstalk","elisp","elixir","elm","emacs","ember","ensime","episerver","erlang","espresso","executable","exercism","expressionengine","extjs","fancy","fastlane","finale","firebase","fish","flashbuilder","flask","flatpak","flex","flexbuilder","floobits","flutter","font","fontforge","forcedotcom","forgegradle","fortran","freecad","freepascal","fsharp","fuelphp","fusetools","games","gatsby","gcov","genero4gl","geth","ggts","gis","git","gitbook","go","godot","goland","goland+all","goland+iml","goodsync","gpg","gradle","grails","greenfoot","groovy","grunt","gwt","haskell","helm","hexo","hol","homeassistant","homebrew","hsp","hugo","hyperledgercomposer","iar","iar_ewarm","iarembeddedworkbench","idapro","idris","igorpro","images","infer","inforcms","inforcrm","intellij","intellij+all","intellij+iml","ionic3","jabref","janet","java","jboss","jboss-4-2-3-ga","jboss-6-x","jboss4","jboss6","jdeveloper","jekyll","jenv","jetbrains","jetbrains+all","jetbrains+iml","jgiven","jigsaw","jmeter","joe","joomla","jsonnet","jspm","julia","jupyternotebooks","justcode","kaldi","kate","kdevelop4","kdiff3","keil","kentico","keystonejs","kicad","kirby2","kirby3","kobalt","kohana","komodoedit","konyvisualizer","kotlin","labview","labviewnxg","lamp","laravel","latex","lazarus","leiningen","lemonstand","less","liberosoc","librarian-chef","libreoffice","lighthouseci","lilypond","linux","lithium","localstack","logtalk","lsspice","ltspice","lua","lyx","macos","magento","magento1","magento2","magic-xpa","matlab","maven","mavensmate","mdbook","mean","mercurial","mercury","meson","metals","metalsmith","metaprogrammingsystem","meteor","meteorjs","microsoftoffice","mikroc","mill","moban","modelsim","modx","momentics","monodevelop","mplabx","mule","nanoc","nativescript","ncrunch","nesc","netbeans","nette","nextjs","nikola","nim","ninja","node","nodechakratimetraveldebug","nohup","notepadpp","nova","now","nuxtjs","nwjs","objective-c","obsidian","ocaml","octave","octobercms","opa","opencart","opencv","openfoam","openframeworks","openframeworks+visualstudio","oracleforms","orcad","osx","otto","oxideshop","oxygenxmleditor","packer","pants","particle","patch","pawn","perl","perl6","ph7cms","phalcon","phoenix","php-cs-fixer","phpcodesniffer","phpstorm","phpstorm+all","phpstorm+iml","phpunit","pico8","pimcore","pimcore4","pimcore5","pinegrow","platformio","playframework","plone","polymer","powershell","premake-gmake","prepros","prestashop","processing","progressabl","psoccreator","pulumi","pulumi+stacks","puppet","puppet-librarian","purebasic","purescript","putty","pvs","pycharm","pycharm+all","pycharm+iml","pydev","python","pythonvanilla","qml","qooxdoo","qt","qtcreator","r","racket","rails","react","reactnative","reasonml","red","redcar","redis","remix","remix+arc","remix+cloudflarepages","remix+cloudflareworkers","remix+netlify","remix+vercel","renpy","replit","retool","rhodesrhomobile","rider","robotframework","root","ros","ros2","ruby","rubymine","rubymine+all","rubymine+iml","rust","rust-analyzer","salesforce","salesforcedx","sam","sam+config","sas","sass","sbt","scala","scheme","scons","scrivener","sdcc","seamgen","senchatouch","serverless","shopware","silverstripe","sketchup","slickedit","smalltalk","snap","snapcraft","snyk","solidity","soliditytruffle","sonar","sonarqube","sourcepawn","spark","specflow","splunk","spreadsheet","ssh","standardml","stata","stdlib","stella","stellar","storybookjs","strapi","stylus","sublimetext","sugarcrm","svelte","svn","swift","swiftpackagemanager","swiftpm","symfony","symphonycms","synology","synopsysvcs","tags","tarmainstallmate","terraform","terragrunt","test","testcomplete","testinfra","tex","text","textmate","textpattern","theos-tweak","thinkphp","tla+","toit","tortoisegit","tower","turbo","turbogears2","twincat3","tye","typings","typo3","typo3-composer","umbraco","unity","unrealengine","vaadin","vagrant","valgrind","vapor","vcpkg","venv","vercel","vertx","video","vim","virtualenv","virtuoso","visualbasic","visualstudio","visualstudiocode","vivado","vlab","vrealizeorchestrator","vs","vue","vuejs","vvvv","waf","wakanda","web","webmethods","webstorm","webstorm+all","webstorm+iml","werckercli","windows","wintersmith","wordpress","wyam","xamarinstudio","xcode","xcodeinjection","xilinx","xilinxise","xilinxvivado","xill","xmake","xojo","xtext","y86","yalc","yarn","yeoman","yii","yii2","zendframework","zephir","zig","zsh","zukencr8000"],"templates":{"1c":"# Created by https://www.toptal.com/developers/gitignore/api/1c\n# Edit at https://www.toptal.com/developers/gitignore?templates=1c\n\n### 1C ###\n# 1C external data processors on unmanaged formes\n\n**/Form/**/form\nsrc/**/und/*\nsrc/**/maps.txt\nsrc/**/renames.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/1c\n","1c-bitrix":"# Created by https://www.toptal.com/developers/gitignore/api/1c-bitrix\n# Edit at https://www.toptal.com/developers/gitignore?templates=1c-bitrix\n\n### 1C-Bitrix ###\n# core\n/bitrix/\n\n# uploaded files\n/upload/\n\n# other files\ntest.php\n*.zip\n*.gzip\n*.gz\n*.tgz\n*.tar\n*.gz.[0-9]\n*.sql\n*.log\n*.mp3\n*.exe\n*.db\n*.csv\n*.xml\n*.psd\n*.[oa]\n*~\n\n# End of https://www.toptal.com/developers/gitignore/api/1c-bitrix\n","a-frame":"# Created by https://www.toptal.com/developers/gitignore/api/a-frame\n# Edit at https://www.toptal.com/developers/gitignore?templates=a-frame\n\n### A-Frame ###\n.DS_Store\n*.sw[mnop]\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n\n# End of https://www.toptal.com/developers/gitignore/api/a-frame\n","actionscript":"# Created by https://www.toptal.com/developers/gitignore/api/actionscript\n# Edit at https://www.toptal.com/developers/gitignore?templates=actionscript\n\n### Actionscript ###\n# Build and Release Folders\nbin-debug/\nbin-release/\n[Oo]bj/\n[Bb]in/\n\n# Other files and folders\n.settings/\n\n# Executables\n*.swf\n*.air\n*.ipa\n*.apk\n\n# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`\n# should NOT be excluded as they contain compiler settings and other important\n# information for Eclipse / Flash Builder.\n\n# End of https://www.toptal.com/developers/gitignore/api/actionscript\n","ada":"# Created by https://www.toptal.com/developers/gitignore/api/ada\n# Edit at https://www.toptal.com/developers/gitignore?templates=ada\n\n### Ada ###\n# Object file\n*.o\n\n# Ada Library Information\n*.ali\n\n# End of https://www.toptal.com/developers/gitignore/api/ada\n","adobe":"# Created by https://www.toptal.com/developers/gitignore/api/adobe\n# Edit at https://www.toptal.com/developers/gitignore?templates=adobe\n\n### Adobe ###\n# Adobe Source Files\n*.psd\n*.ai\n*.indd\n*.prproj\n*.aep\n\n# End of https://www.toptal.com/developers/gitignore/api/adobe\n","advancedinstaller":"# Created by https://www.toptal.com/developers/gitignore/api/advancedinstaller\n# Edit at https://www.toptal.com/developers/gitignore?templates=advancedinstaller\n\n### AdvancedInstaller ###\n# Advanced Installer caches and default build output\n*-cache/\n*-SetupFiles/\nPrerequisites/\n\n# End of https://www.toptal.com/developers/gitignore/api/advancedinstaller\n","adventuregamestudio":"# Created by https://www.toptal.com/developers/gitignore/api/adventuregamestudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=adventuregamestudio\n\n### AdventureGameStudio ###\n# Built things\n_Debug/\nCompiled/\n\n# AudioCache can be rebuilt from sources\nAudioCache/\n\n# Lockfile\n_OpenInEditor.lock\n\n# User settings\nGame.agf.user\n*.crm.user\n\n# Backups\nGame.agf.bak\nbackup_acsprset.spr\n\n# Memory dumps\n*.dmp\n\n# Temporary files\n# temporarily created during sprite or room background compression\n~aclzw.tmp\n# temporary, main game data, before getting packed into exe\ngame28.dta\n# temporary build of the game before being moved to Compiled/ folder\n*.exe\n\n# Log files\nwarnings.log\n\n# End of https://www.toptal.com/developers/gitignore/api/adventuregamestudio\n","agda":"# Created by https://www.toptal.com/developers/gitignore/api/agda\n# Edit at https://www.toptal.com/developers/gitignore?templates=agda\n\n### Agda ###\n*.agdai\nMAlonzo/**\n\n# End of https://www.toptal.com/developers/gitignore/api/agda\n","al":"# Created by https://www.toptal.com/developers/gitignore/api/al\n# Edit at https://www.toptal.com/developers/gitignore?templates=al\n\n### AL ###\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# Local History for Visual Studio Code\n.history/\n*.app\n.snapshots/*\n\n# End of https://www.toptal.com/developers/gitignore/api/al\n","alteraquartusii":"# Created by https://www.toptal.com/developers/gitignore/api/alteraquartusii\n# Edit at https://www.toptal.com/developers/gitignore?templates=alteraquartusii\n\n### AlteraQuartusII ###\n##list taken from http://www.alterawiki.com/wiki/Version_Control (01.10.2015)\n\n######### Quartus II source files\n\n# project files:\n### project_name.qpf Quartus II project file\n### project_name.qsf Quartus constraint file (lists the hardware constraints defined for a project, from the used chip and pinout to timing constraints)\n### project_name.qws Quartus Window Settings ? (the configuration of the Quartus gui for the project, may be omitted)\n\n# top level source files:\n### project_name.bdf Block diagram / Schematic file (top level schematic file, there may be many nested files)\n### project_name.vhd VHDL file (top level VHDL file)\n### project_name.v Verilog file (top level Verilog file)\n\n# component source files:\n### component_name.bsf Block Symbol file (component symbol file)\n### component_name.vhd VHDL file (top level VHDL file)\n### component_name.v Verilog file (top level Verilog file)\n\n# SOPC builder project source files (SOPC builder creates many VHDL or Verilog files, that you do not need to store)\n### sopc_project_name.ptf the list and configuration of components selected in the SOPC gui\n### sopc_project_name.bsf Block Symbol file (SOPC component symbol file, especially if you modified it)\n\n# Board Description (if you created your own board, the list is incomplete!)\n### board_name/class.ptf\n\n# software source files:\n### tbd\n\n######## Quartus II binary files\n\n# hardware binary files\n### project_name.sof SRAM Object File\n\n# software binary files\n\n/*\n!/*.gitignore\n##\n!*.qpf\n!*.qsf\n!*.qws\n!*.bdf\n!*.vhd\n!*.v\n!*.ptf\n!*.bsf\n!**/class.ptf\n!*.tbd\n!*.sof\n## tbd\n\n!*.tcl\n\n# End of https://www.toptal.com/developers/gitignore/api/alteraquartusii\n","altium":"# Created by https://www.toptal.com/developers/gitignore/api/altium\n# Edit at https://www.toptal.com/developers/gitignore?templates=altium\n\n### Altium ###\n# Previews Folders\n**/__Previews/\n\n# History Folders\n**/History/*\n\n# Project Logs\nProject\\ Logs*/\n\n# Project Outputs\nProject\\ Outputs*/\n\n# Auto-conversion notices\n*.PcbDoc.htm\n\n# Access lock file for dbLib sources\n**/*.ldb\n\n\n# End of https://www.toptal.com/developers/gitignore/api/altium\n","amplify":"# Created by https://www.toptal.com/developers/gitignore/api/amplify\n# Edit at https://www.toptal.com/developers/gitignore?templates=amplify\n\n### Amplify ###\n# AWS Amplify\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/awscloudformation\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplify-build-config.json\namplify-gradle-config.json\namplifyxc.config\n\n# End of https://www.toptal.com/developers/gitignore/api/amplify\n","android":"# Created by https://www.toptal.com/developers/gitignore/api/android\n# Edit at https://www.toptal.com/developers/gitignore?templates=android\n\n### Android ###\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Log/OS Files\n*.log\n\n# Android Studio generated files and folders\ncaptures/\n.externalNativeBuild/\n.cxx/\n*.apk\noutput.json\n\n# IntelliJ\n*.iml\n.idea/\nmisc.xml\ndeploymentTargetDropDown.xml\nrender.experimental.xml\n\n# Keystore files\n*.jks\n*.keystore\n\n# Google Services (e.g. APIs or Firebase)\ngoogle-services.json\n\n# Android Profiling\n*.hprof\n\n### Android Patch ###\ngen-external-apklibs\n\n# Replacement of .externalNativeBuild directories introduced\n# with Android Studio 3.5.\n\n# End of https://www.toptal.com/developers/gitignore/api/android\n","androidstudio":"# Created by https://www.toptal.com/developers/gitignore/api/androidstudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio\n\n### AndroidStudio ###\n# Covers files to be ignored for android development using Android Studio.\n\n# Built application files\n*.apk\n*.ap_\n*.aab\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n\n# Gradle files\n.gradle\n.gradle/\nbuild/\n\n# Signing files\n.signing/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\n# Android Studio\n/*/build/\n/*/local.properties\n/*/out\n/*/*/build\n/*/*/production\ncaptures/\n.navigation/\n*.ipr\n*~\n*.swp\n\n# Keystore files\n*.jks\n*.keystore\n\n# Google Services (e.g. APIs or Firebase)\n# google-services.json\n\n# Android Patch\ngen-external-apklibs\n\n# External native build folder generated in Android Studio 2.2 and later\n.externalNativeBuild\n\n# NDK\nobj/\n\n# IntelliJ IDEA\n*.iml\n*.iws\n/out/\n\n# User-specific configurations\n.idea/caches/\n.idea/libraries/\n.idea/shelf/\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/.name\n.idea/compiler.xml\n.idea/copyright/profiles_settings.xml\n.idea/encodings.xml\n.idea/misc.xml\n.idea/modules.xml\n.idea/scopes/scope_settings.xml\n.idea/dictionaries\n.idea/vcs.xml\n.idea/jsLibraryMappings.xml\n.idea/datasources.xml\n.idea/dataSources.ids\n.idea/sqlDataSources.xml\n.idea/dynamic.xml\n.idea/uiDesigner.xml\n.idea/assetWizardSettings.xml\n.idea/gradle.xml\n.idea/jarRepositories.xml\n.idea/navEditor.xml\n\n# Legacy Eclipse project files\n.classpath\n.project\n.cproject\n.settings/\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.war\n*.ear\n\n# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)\nhs_err_pid*\n\n## Plugin-specific files:\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Mongo Explorer plugin\n.idea/mongoSettings.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n### AndroidStudio Patch ###\n\n!/gradle/wrapper/gradle-wrapper.jar\n\n# End of https://www.toptal.com/developers/gitignore/api/androidstudio\n","angular":"# Created by https://www.toptal.com/developers/gitignore/api/angular\n# Edit at https://www.toptal.com/developers/gitignore?templates=angular\n\n### Angular ###\n## Angular ##\n# compiled output\ndist/\ntmp/\napp/**/*.js\napp/**/*.js.map\n\n# dependencies\nnode_modules/\nbower_components/\n\n# IDEs and editors\n.idea/\n\n# misc\n.sass-cache/\nconnect.lock/\ncoverage/\nlibpeerconnection.log/\nnpm-debug.log\ntestem.log\ntypings/\n.angular/\n\n# e2e\ne2e/*.js\ne2e/*.map\n\n# System Files\n.DS_Store/\n\n# End of https://www.toptal.com/developers/gitignore/api/angular\n","anjuta":"# Created by https://www.toptal.com/developers/gitignore/api/anjuta\n# Edit at https://www.toptal.com/developers/gitignore?templates=anjuta\n\n### Anjuta ###\n# Local configuration folder and symbol database\n/.anjuta/\n/.anjuta_sym_db.db\n\n# End of https://www.toptal.com/developers/gitignore/api/anjuta\n","ansible":"# Created by https://www.toptal.com/developers/gitignore/api/ansible\n# Edit at https://www.toptal.com/developers/gitignore?templates=ansible\n\n### Ansible ###\n*.retry\n\n# End of https://www.toptal.com/developers/gitignore/api/ansible\n","ansibletower":"# Created by https://www.toptal.com/developers/gitignore/api/ansibletower\n# Edit at https://www.toptal.com/developers/gitignore?templates=ansibletower\n\n### AnsibleTower ###\n# Ansible runtime and backups\n*.original\n*.tmp\n*.bkp\n*.retry\n*.*~\n\n# Tower runtime roles\nroles/**\n!roles/requirements.yml\n\n# Avoid plain-text passwords\n*pwd*\n*pass*\n*password*\n*.txt\n\n# Exclude all binaries\n*.bin\n*.jar\n*.tar\n*.zip\n*.gzip\n*.tgz\n\n\n# End of https://www.toptal.com/developers/gitignore/api/ansibletower\n","apachecordova":"# Created by https://www.toptal.com/developers/gitignore/api/apachecordova\n# Edit at https://www.toptal.com/developers/gitignore?templates=apachecordova\n\n### ApacheCordova ###\n# Apache Cordova generated files and directories\nbin/*\n!/plugins\n!/plugins/android.json\n!/plugins/fetch.json\nplugins/*\nplatforms/*\n\n# End of https://www.toptal.com/developers/gitignore/api/apachecordova\n","apachehadoop":"# Created by https://www.toptal.com/developers/gitignore/api/apachehadoop\n# Edit at https://www.toptal.com/developers/gitignore?templates=apachehadoop\n\n### ApacheHadoop ###\n*.iml\n*.ipr\n*.iws\n*.orig\n*.rej\n.idea\n.svn\n.classpath\n.project\n.settings\ntarget\nhadoop-common-project/hadoop-kms/downloads/\nhadoop-hdfs-project/hadoop-hdfs/downloads\nhadoop-hdfs-project/hadoop-hdfs-httpfs/downloads\nhadoop-common-project/hadoop-common/src/test/resources/contract-test-options.xml\nhadoop-tools/hadoop-openstack/src/test/resources/contract-test-options.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/apachehadoop\n","appbuilder":"# Created by https://www.toptal.com/developers/gitignore/api/appbuilder\n# Edit at https://www.toptal.com/developers/gitignore?templates=appbuilder\n\n### AppBuilder ###\n.ab/\n\n# End of https://www.toptal.com/developers/gitignore/api/appbuilder\n","appceleratortitanium":"# Created by https://www.toptal.com/developers/gitignore/api/appceleratortitanium\n# Edit at https://www.toptal.com/developers/gitignore?templates=appceleratortitanium\n\n### AppceleratorTitanium ###\n# Build folder and log file\nbuild/\nbuild.log\n\n# End of https://www.toptal.com/developers/gitignore/api/appceleratortitanium\n","appcode":"# Created by https://www.toptal.com/developers/gitignore/api/appcode\n# Edit at https://www.toptal.com/developers/gitignore?templates=appcode\n\n### AppCode ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### AppCode Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/appcode\n","appcode+all":"# Created by https://www.toptal.com/developers/gitignore/api/appcode+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=appcode+all\n\n### AppCode+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### AppCode+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/appcode+all\n","appcode+iml":"# Created by https://www.toptal.com/developers/gitignore/api/appcode+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=appcode+iml\n\n### AppCode+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### AppCode+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/appcode+iml\n","appengine":"# Created by https://www.toptal.com/developers/gitignore/api/appengine\n# Edit at https://www.toptal.com/developers/gitignore?templates=appengine\n\n### AppEngine ###\n# Google App Engine generated folder\nappengine-generated/\n\n# End of https://www.toptal.com/developers/gitignore/api/appengine\n","aptanastudio":"# Created by https://www.toptal.com/developers/gitignore/api/aptanastudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=aptanastudio\n\n### AptanaStudio ###\n.metadata\nbin/\ntmp/\n*.tmp\n*.bak\n*.swp\n*~.nib\nlocal.properties\n.settings/\n.loadpath\n.recommenders\n\n# External tool builders\n.externalToolBuilders/\n\n# Locally stored \"Eclipse launch configurations\"\n*.launch\n\n# PyDev specific (Python IDE for Eclipse)\n*.pydevproject\n\n# CDT-specific (C/C++ Development Tooling)\n.cproject\n\n# CDT- autotools\n.autotools\n\n# Java annotation processor (APT)\n.factorypath\n\n# PDT-specific (PHP Development Tools)\n.buildpath\n\n# sbteclipse plugin\n.target\n\n# Tern plugin\n.tern-project\n\n# TeXlipse plugin\n.texlipse\n\n# STS (Spring Tool Suite)\n.springBeans\n\n# Code Recommenders\n.recommenders/\n\n# Annotation Processing\n.apt_generated/\n.apt_generated_test/\n\n# Scala IDE specific (Scala & Java development for Eclipse)\n.cache-main\n.scala_dependencies\n.worksheet\n\n# Uncomment this line if you wish to ignore the project description file.\n# Typically, this file would be tracked if it contains build/dependency configurations:\n#.project\n\n# End of https://www.toptal.com/developers/gitignore/api/aptanastudio\n","arcanist":"# Created by https://www.toptal.com/developers/gitignore/api/arcanist\n# Edit at https://www.toptal.com/developers/gitignore?templates=arcanist\n\n### Arcanist ###\n.phutil_module_cache\n.divinercache/\n\n# End of https://www.toptal.com/developers/gitignore/api/arcanist\n","archive":"# Created by https://www.toptal.com/developers/gitignore/api/archive\n# Edit at https://www.toptal.com/developers/gitignore?templates=archive\n\n### Archive ###\n\n### Mostly from https://en.wikipedia.org/wiki/List_of_archive_formats\n\n## Archiving only\n# The traditional archive format on Unix-like systems, now used mainly for the creation of static libraries.\n*.a\n*.ar\n# RPM files consist of metadata concatenated with (usually) a cpio archive. Newer RPM systems also support other archives, as cpio is becoming obsolete. cpio is also used with initramfs.\n*.cpio\n\n# A self-extracting archive that uses the Bourne shell (sh).\n*.shar\n# A system for storing multiple files. LBR archives typically contained files processed by SQ, or the archive itself was compressed with SQ. LBR archives that were compressed with SQ ended with the extension .LQR\t\n*.LBR\n# An archive format originally used mainly for archiving and distribution of the exact, nearly-exact, or custom-modified contents of an optical storage medium such as a CD-ROM or DVD-ROM. However, it can be used to archive the contents of other storage media, selected partitions, folders, and/or files. The resulting archive is typically optimized for convenient rendering to (re-)writable CD or DVD media.\n*.iso\n# A library format used primarily on the Commodore 64 and 128 lines of computers. This bears no resemblance to the DOS LBR format. While library files were quick to implement (a number of programs exist to work with them) they are crippled in that they cannot grow with use: once a file has been created it cannot be amended (files added, changed or deleted) without recreating the entire file.\n*.lbr\n# An archive format used by Mozilla for storing binary diffs. Used in conjunction with bzip2.\n*.mar\n# A common archive format used on Unix-like systems. Generally used in conjunction with compressors such as gzip, bzip2, compress or xz to create .tar.gz, .tar.bz2, .tar.Z or tar.xz files.\n*.tar\n\n# Package managers\n# Red Hat Package Manager\n*.rpm\n# Debian package\n*.deb\n# MicroSoft Installer\n*.msi\n*.msm\n*.msp\n# Mozilla package installer\n*.xpi\n# Ruby Package\n*.gem\n\n\n# End of https://www.toptal.com/developers/gitignore/api/archive\n","archives":"# Created by https://www.toptal.com/developers/gitignore/api/archives\n# Edit at https://www.toptal.com/developers/gitignore?templates=archives\n\n### Archives ###\n# It's better to unpack these files and commit the raw source because\n# git has its own built in compression methods.\n*.7z\n*.jar\n*.rar\n*.zip\n*.gz\n*.gzip\n*.tgz\n*.bzip\n*.bzip2\n*.bz2\n*.xz\n*.lzma\n*.cab\n*.xar\n\n# Packing-only formats\n*.iso\n*.tar\n\n# Package management formats\n*.dmg\n*.xpi\n*.gem\n*.egg\n*.deb\n*.rpm\n*.msi\n*.msm\n*.msp\n*.txz\n\n# End of https://www.toptal.com/developers/gitignore/api/archives\n","archlinuxpackages":"# Created by https://www.toptal.com/developers/gitignore/api/archlinuxpackages\n# Edit at https://www.toptal.com/developers/gitignore?templates=archlinuxpackages\n\n### ArchLinuxPackages ###\n*.tar\n*.tar.*\n*.jar\n*.exe\n*.msi\n*.zip\n*.tgz\n*.log\n*.log.*\n*.sig\n\npkg/\nsrc/\n\n# End of https://www.toptal.com/developers/gitignore/api/archlinuxpackages\n","asdf":"# Created by https://www.toptal.com/developers/gitignore/api/asdf\n# Edit at https://www.toptal.com/developers/gitignore?templates=asdf\n\n### asdf ###\n/.tool-versions\n\n# End of https://www.toptal.com/developers/gitignore/api/asdf\n","aspnetcore":"# Created by https://www.toptal.com/developers/gitignore/api/aspnetcore\n# Edit at https://www.toptal.com/developers/gitignore?templates=aspnetcore\n\n### ASPNETCore ###\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignoreable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/\n\n# End of https://www.toptal.com/developers/gitignore/api/aspnetcore\n","assembler":"# Created by https://www.toptal.com/developers/gitignore/api/assembler\n# Edit at https://www.toptal.com/developers/gitignore?templates=assembler\n\n### Assembler ###\n*.exe\n*.o\n*.obj\n*.bc\n\n# End of https://www.toptal.com/developers/gitignore/api/assembler\n","astro":"# Created by https://www.toptal.com/developers/gitignore/api/astro\n# Edit at https://www.toptal.com/developers/gitignore?templates=astro\n\n### Astro ###\n# Gitignore template for Astro projects\n# Ignore content collection generated files\n.astro\n\n# End of https://www.toptal.com/developers/gitignore/api/astro\n","ate":"# Created by https://www.toptal.com/developers/gitignore/api/ate\n# Edit at https://www.toptal.com/developers/gitignore?templates=ate\n\n### ATE ###\n\n### For C++ language related files\n# Prerequisites\n*.d\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n*.smod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n\n\n### For D10/DX Platform\n\n*.pat\n*/**/*rto/*\n*/**/*rtol/*\n*.make\n*.properties\n*.vpj\n*.vpw\n*.scan\n*.patl\n*.bakvpj\n*.bakvpw\n*.vpwhistu\n*.vtg\n*.pvs\n\n\n### For Project Management Related files\n.bak*\n*.xml\n*.zip\n*.tar\n*.gz\n\n# End of https://www.toptal.com/developers/gitignore/api/ate\n","atmelstudio":"# Created by https://www.toptal.com/developers/gitignore/api/atmelstudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=atmelstudio\n\n### AtmelStudio ###\n## Ignore Atmel Studio temporary files and build results\n# https://www.microchip.com/mplab/avr-support/atmel-studio-7\n\n# Atmel Studio is powered by an older version of Visual Studio,\n# so most of the project and solution files are the same as VS files,\n# only prefixed by an `at`.\n\n#Build Directories\n[Dd]ebug/\n[Rr]elease/\n\n#Build Results\n*.o\n*.d\n*.eep\n*.elf\n*.hex\n*.map\n*.srec\n\n#User Specific Files\n*.atsuo\n\n# End of https://www.toptal.com/developers/gitignore/api/atmelstudio\n","ats":"# Created by https://www.toptal.com/developers/gitignore/api/ats\n# Edit at https://www.toptal.com/developers/gitignore?templates=ats\n\n### ATS ###\n# Compiled C Files\n*~\n*_?ats.c\n*_?ats.o\n\n# Compiled Javascript Files\n*_?ats.js\n\n# End of https://www.toptal.com/developers/gitignore/api/ats\n","audio":"# Created by https://www.toptal.com/developers/gitignore/api/audio\n# Edit at https://www.toptal.com/developers/gitignore?templates=audio\n\n### Audio ###\n*.aif\n*.iff\n*.m3u\n*.m4a\n*.mid\n*.mp3\n*.mpa\n*.ra\n*.wav\n*.wma\n*.ogg\n*.flac\n\n# End of https://www.toptal.com/developers/gitignore/api/audio\n","autohotkey":"# Created by https://www.toptal.com/developers/gitignore/api/autohotkey\n# Edit at https://www.toptal.com/developers/gitignore?templates=autohotkey\n\n### Autohotkey ###\n# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# =========================\n# Operating System Files\n# =========================\n\n# OSX\n# =========================\n\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# End of https://www.toptal.com/developers/gitignore/api/autohotkey\n","automationstudio":"# Created by https://www.toptal.com/developers/gitignore/api/automationstudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=automationstudio\n\n### AutomationStudio ###\n## Automation Studio : ignore temporary files and build results (.gitignore parallel to *.apj)\n# Ignore AR versions as those are not considered part of the project\n/AS/System/\n# Ignore build results\n/Binaries/\n# Ignore debugging configurations (Watch,...)\n/Diagnosis/\n# Ignore Temporary Safety files\n/Physical/*/*/*/DLFiles/\n# Ignore standard temp folder\n/Temp/\n# Ignore upgrades as those are not considered part of the project source code\n/Upgrades/\n# Ignore user settings file\n/*.set\n# Ignore is open indication\n/*.isopen\n# Ignore debug logs\n/debug.log\n# Ignore Conversion logs (AS2 to AS3)\n/ConvertLog.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/automationstudio\n","autotools":"# Created by https://www.toptal.com/developers/gitignore/api/autotools\n# Edit at https://www.toptal.com/developers/gitignore?templates=autotools\n\n### Autotools ###\n# http://www.gnu.org/software/automake\n\nMakefile.in\n/ar-lib\n/mdate-sh\n/py-compile\n/test-driver\n/ylwrap\n.deps/\n.dirstamp\n\n# http://www.gnu.org/software/autoconf\n\nautom4te.cache\n/autoscan.log\n/autoscan-*.log\n/aclocal.m4\n/compile\n/config.cache\n/config.guess\n/config.h.in\n/config.log\n/config.status\n/config.sub\n/configure\n/configure.scan\n/depcomp\n/install-sh\n/missing\n/stamp-h1\n\n# https://www.gnu.org/software/libtool/\n\n/ltmain.sh\n\n# http://www.gnu.org/software/texinfo\n\n/texinfo.tex\n\n# http://www.gnu.org/software/m4/\n\nm4/libtool.m4\nm4/ltoptions.m4\nm4/ltsugar.m4\nm4/ltversion.m4\nm4/lt~obsolete.m4\n\n# Generated Makefile\n# (meta build system like autotools,\n# can automatically generate from config.status script\n# (which is called by configure script))\nMakefile\n\n### Autotools Patch ###\n\n# End of https://www.toptal.com/developers/gitignore/api/autotools\n","autotools+strict":"# Created by https://www.toptal.com/developers/gitignore/api/autotools+strict\n# Edit at https://www.toptal.com/developers/gitignore?templates=autotools+strict\n\n### Autotools+strict ###\n# http://www.gnu.org/software/automake\n\nMakefile.in\n/ar-lib\n/mdate-sh\n/py-compile\n/test-driver\n/ylwrap\n.deps/\n.dirstamp\n\n# http://www.gnu.org/software/autoconf\n\nautom4te.cache\n/autoscan.log\n/autoscan-*.log\n/aclocal.m4\n/compile\n/config.cache\n/config.guess\n/config.h.in\n/config.log\n/config.status\n/config.sub\n/configure\n/configure.scan\n/depcomp\n/install-sh\n/missing\n/stamp-h1\n\n# https://www.gnu.org/software/libtool/\n\n/ltmain.sh\n\n# http://www.gnu.org/software/texinfo\n\n/texinfo.tex\n\n# http://www.gnu.org/software/m4/\n\nm4/libtool.m4\nm4/ltoptions.m4\nm4/ltsugar.m4\nm4/ltversion.m4\nm4/lt~obsolete.m4\n\n# Generated Makefile\n# (meta build system like autotools,\n# can automatically generate from config.status script\n# (which is called by configure script))\nMakefile\n\n### Autotools+strict Patch ###\n# Generated source files\nconfigure\n\n# End of https://www.toptal.com/developers/gitignore/api/autotools+strict\n","awr":"# Created by https://www.toptal.com/developers/gitignore/api/awr\n# Edit at https://www.toptal.com/developers/gitignore?templates=awr\n\n### AWR ###\n### National Instruments AWR ###\n*.dsf\n*.dsf.ext\n*.dsf.ext*\n*.emi\n*.opt\n*.stderr.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/awr\n","azurefunctions":"# Created by https://www.toptal.com/developers/gitignore/api/azurefunctions\n# Edit at https://www.toptal.com/developers/gitignore?templates=azurefunctions\n\n### AzureFunctions ###\n# Azure Functions localsettings file\nlocal.settings.json\n\n# End of https://www.toptal.com/developers/gitignore/api/azurefunctions\n","azurite":"# Created by https://www.toptal.com/developers/gitignore/api/azurite\n# Edit at https://www.toptal.com/developers/gitignore?templates=azurite\n\n### Azurite ###\n# Azurite queue\n__queuestorage__\n__azurite_db_queue__.json\n__azurite_db_queue_extent__.json\n\n# Azurite blob\n__blobstorage__\n__azurite_db_blob__.json\n__azurite_db_blob_extent__.json\n\n# Azurite table\n__azurite_db_table__.json\n__azurite_db_table_extent__.json\n\n# End of https://www.toptal.com/developers/gitignore/api/azurite\n","backup":"# Created by https://www.toptal.com/developers/gitignore/api/backup\n# Edit at https://www.toptal.com/developers/gitignore?templates=backup\n\n### Backup ###\n*.bak\n*.gho\n*.ori\n*.orig\n*.tmp\n\n# End of https://www.toptal.com/developers/gitignore/api/backup\n","ballerina":"# Created by https://www.toptal.com/developers/gitignore/api/ballerina\n# Edit at https://www.toptal.com/developers/gitignore?templates=ballerina\n\n### Ballerina ###\n*.class\n*.log\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.ear\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n\n.idea\n*.iml\n*.ipr\n*.iws\n\n# generated files\ntarget\nresults\n.ballerina\n/gen\n/compiler/ballerina-lang/src/main/resources/grammar/BallerinaLexer.tokens\n/tool-plugins/intellij/src/main/antlr/org/ballerinalang/plugins/idea/grammar/BallerinaLexer.tokens\n/composer/modules/web/dist-electron/\nvelocity.log\n/composer/modules/web/dist-electron\n\n# gradle\n.gradle\nbuild/\ngradle-app.setting\n!gradle-wrapper.jar\n.gradletasknamecache\n\n# mac\n.DS_Store\n\n.classpath\n.project\n.settings\n.vscode\n\n# End of https://www.toptal.com/developers/gitignore/api/ballerina\n","basercms":"# Created by https://www.toptal.com/developers/gitignore/api/basercms\n# Edit at https://www.toptal.com/developers/gitignore?templates=basercms\n\n### baserCMS ###\n## Configuration and Temporary files\napp/Config/database.php\napp/Config/install.php\napp/tmp/*\n!app/tmp/empty\napp/db/*\n!app/db/empty\n!app/Plugin/empty\napp/webroot/files/*\n!app/webroot/files/empty\nfiles/*\n!files/empty\n\n## IDE files and folders\n.cache\n.project\n.settings\nnbproject\n.idea\n.DS_Store\n_notes\n.svn\n\n## Allow 'empty' files\n!empty\n\n## Theme\n!app/webroot/theme/empty\ntheme/demo/*\n!theme/demo/empty\n\n## Project\n# The PROJECT of character,\n# please rewrite on a project\n# that you are participating.\n\ntheme/PROJECT/Pages/*\n!theme/PROJECT/Pages/empty\napp/webroot/theme/PROJECT/Pages/*\napp/webroot/theme/PROJECT/Pages/empty\n\n# End of https://www.toptal.com/developers/gitignore/api/basercms\n","basic":"# Created by https://www.toptal.com/developers/gitignore/api/basic\n# Edit at https://www.toptal.com/developers/gitignore?templates=basic\n\n### Basic ###\n# Apples Build\n*.build\n*.apples\n\n# Initialized files\n*.exe\n*.ini\n*.basic\n\n# End of https://www.toptal.com/developers/gitignore/api/basic\n","batch":"# Created by https://www.toptal.com/developers/gitignore/api/batch\n# Edit at https://www.toptal.com/developers/gitignore?templates=batch\n\n### Batch ###\n# BatchFiles\n*.bat\n*.cmd\n*.btm\n\n# End of https://www.toptal.com/developers/gitignore/api/batch\n","bazaar":"# Created by https://www.toptal.com/developers/gitignore/api/bazaar\n# Edit at https://www.toptal.com/developers/gitignore?templates=bazaar\n\n### Bazaar ###\n.bzr/\n.bzrignore\n\n# End of https://www.toptal.com/developers/gitignore/api/bazaar\n","bazel":"# Created by https://www.toptal.com/developers/gitignore/api/bazel\n# Edit at https://www.toptal.com/developers/gitignore?templates=bazel\n\n### Bazel ###\n# gitignore template for Bazel build system\n# website: https://bazel.build/\n\n# Ignore all bazel-* symlinks. There is no full list since this can change\n# based on the name of the directory bazel is cloned into.\n/bazel-*\n\n# Directories for the Bazel IntelliJ plugin containing the generated\n# IntelliJ project files and plugin configuration. Seperate directories are\n# for the IntelliJ, Android Studio and CLion versions of the plugin.\n/.ijwb/\n/.aswb/\n/.clwb/\n\n# End of https://www.toptal.com/developers/gitignore/api/bazel\n","bitrise":"# Created by https://www.toptal.com/developers/gitignore/api/bitrise\n# Edit at https://www.toptal.com/developers/gitignore?templates=bitrise\n\n### Bitrise ###\n.bitrise.secrets.yml\n\n# End of https://www.toptal.com/developers/gitignore/api/bitrise\n","bitrix":"# Created by https://www.toptal.com/developers/gitignore/api/bitrix\n# Edit at https://www.toptal.com/developers/gitignore?templates=bitrix\n\n### Bitrix ###\n# gitignore template for 1C-Bitrix, a PHP-based CMS\n# website: https://www.1c-bitrix.ru\n\n#Exclude all of core files\n/bitrix/*\n\n#But not the templates and non bitrix components\n!/bitrix/templates\n!/bitrix/components\n/bitrix/components/bitrix\n\n#Exclude bitrix gadgets\n!/bitrix/gadgets\n/bitrix/gadgets/bitrix\n\n#User can use that directory to store some stuff, but it's not really recommended, just use /local instead of this\n!/bitrix/php_interface/\n\n#Exclude database configs\n/bitrix/php_interface/dbconn.php\n\n#Exclude default file storage directory\n/upload/\n\n# End of https://www.toptal.com/developers/gitignore/api/bitrix\n","bittorrent":"# Created by https://www.toptal.com/developers/gitignore/api/bittorrent\n# Edit at https://www.toptal.com/developers/gitignore?templates=bittorrent\n\n### BitTorrent ###\n*.torrent\n\n# End of https://www.toptal.com/developers/gitignore/api/bittorrent\n","blackbox":"# Created by https://www.toptal.com/developers/gitignore/api/blackbox\n# Edit at https://www.toptal.com/developers/gitignore?templates=blackbox\n\n### Blackbox ###\n# https://github.com/StackExchange/blackbox\n/keyrings/live/pubring.gpg~\n/keyrings/live/pubring.kbx~\n/keyrings/live/secring.gpg\n\n# End of https://www.toptal.com/developers/gitignore/api/blackbox\n","blender":"# Created by https://www.toptal.com/developers/gitignore/api/blender\n# Edit at https://www.toptal.com/developers/gitignore?templates=blender\n\n### Blender ###\n# Ignore temporary Blender files.\n*.blend[0-9]*\n\n# End of https://www.toptal.com/developers/gitignore/api/blender\n","bloop":"# Created by https://www.toptal.com/developers/gitignore/api/bloop\n# Edit at https://www.toptal.com/developers/gitignore?templates=bloop\n\n### Bloop ###\n.bloop/\n\n# End of https://www.toptal.com/developers/gitignore/api/bloop\n","bluej":"# Created by https://www.toptal.com/developers/gitignore/api/bluej\n# Edit at https://www.toptal.com/developers/gitignore?templates=bluej\n\n### bluej ###\n# BlueJ: A Java IDE for programming beginners. http://bluej.org\n\n# Editor preferences for a class etc, see http://lists.bluej.org/pipermail/bluej-discuss/2003-May/002351.html\n*.ctxt\n\n# Duplicate of project file, see http://www.hs-owl.de/fb5/labor/it/de/if1/vl/IF1JavaOhneBluej.pdf, page 4\nbluej.pkh\n\n# End of https://www.toptal.com/developers/gitignore/api/bluej\n","bookdown":"# Created by https://www.toptal.com/developers/gitignore/api/bookdown\n# Edit at https://www.toptal.com/developers/gitignore?templates=bookdown\n\n### Bookdown ###\n# R package: bookdown caching files\n/*_files/\n\n# End of https://www.toptal.com/developers/gitignore/api/bookdown\n","bower":"# Created by https://www.toptal.com/developers/gitignore/api/bower\n# Edit at https://www.toptal.com/developers/gitignore?templates=bower\n\n### Bower ###\nbower_components\n.bower-cache\n.bower-registry\n.bower-tmp\n\n# End of https://www.toptal.com/developers/gitignore/api/bower\n","bricxcc":"# Created by https://www.toptal.com/developers/gitignore/api/bricxcc\n# Edit at https://www.toptal.com/developers/gitignore?templates=bricxcc\n\n### BricxCC ###\n# Bricx Command Center IDE\n# http://bricxcc.sourceforge.net\n*.bak\n*.sym\n\n# End of https://www.toptal.com/developers/gitignore/api/bricxcc\n","buck":"# Created by https://www.toptal.com/developers/gitignore/api/buck\n# Edit at https://www.toptal.com/developers/gitignore?templates=buck\n\n### Buck ###\nbuck-out/\n.buckconfig.local\n.buckd/\n.buckversion\n.fakebuckversion\n\n# End of https://www.toptal.com/developers/gitignore/api/buck\n","c":"# Created by https://www.toptal.com/developers/gitignore/api/c\n# Edit at https://www.toptal.com/developers/gitignore?templates=c\n\n### C ###\n# Prerequisites\n*.d\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Linker output\n*.ilk\n*.map\n*.exp\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n# Debug files\n*.dSYM/\n*.su\n*.idb\n*.pdb\n\n# Kernel Module Compile Results\n*.mod*\n*.cmd\n.tmp_versions/\nmodules.order\nModule.symvers\nMkfile.old\ndkms.conf\n\n# End of https://www.toptal.com/developers/gitignore/api/c\n","c++":"# Created by https://www.toptal.com/developers/gitignore/api/c++\n# Edit at https://www.toptal.com/developers/gitignore?templates=c++\n\n### C++ ###\n# Prerequisites\n*.d\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n*.smod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n\n# End of https://www.toptal.com/developers/gitignore/api/c++\n","cake":"# Created by https://www.toptal.com/developers/gitignore/api/cake\n# Edit at https://www.toptal.com/developers/gitignore?templates=cake\n\n### Cake ###\ntools/*\n!tools/packages.config\n\n# End of https://www.toptal.com/developers/gitignore/api/cake\n","cakephp":"# Created by https://www.toptal.com/developers/gitignore/api/cakephp\n# Edit at https://www.toptal.com/developers/gitignore?templates=cakephp\n\n### CakePHP ###\n# CakePHP 3\n\n/vendor/*\n/config/app.php\n\n/tmp/cache/models/*\n!/tmp/cache/models/empty\n/tmp/cache/persistent/*\n!/tmp/cache/persistent/empty\n/tmp/cache/views/*\n!/tmp/cache/views/empty\n/tmp/sessions/*\n!/tmp/sessions/empty\n/tmp/tests/*\n!/tmp/tests/empty\n\n/logs/*\n!/logs/empty\n\n# CakePHP 2\n\n/app/tmp/*\n/app/Config/core.php\n/app/Config/database.php\n/vendors/*\n\n# End of https://www.toptal.com/developers/gitignore/api/cakephp\n","cakephp2":"# Created by https://www.toptal.com/developers/gitignore/api/cakephp2\n# Edit at https://www.toptal.com/developers/gitignore?templates=cakephp2\n\n### CakePHP2 ###\n# CakePHP 2\n\n/app/tmp/*\n/app/Config/core.php\n/app/Config/database.php\n/vendors/*\n\n# End of https://www.toptal.com/developers/gitignore/api/cakephp2\n","cakephp3":"# Created by https://www.toptal.com/developers/gitignore/api/cakephp3\n# Edit at https://www.toptal.com/developers/gitignore?templates=cakephp3\n\n### CakePHP3 ###\n# CakePHP 3\n\n/config/app.php\n/config/.env\n/logs/*\n/tmp/*\n/vendor/*\n\n# End of https://www.toptal.com/developers/gitignore/api/cakephp3\n","calabash":"# Created by https://www.toptal.com/developers/gitignore/api/calabash\n# Edit at https://www.toptal.com/developers/gitignore?templates=calabash\n\n### Calabash ###\n# Calabash / Cucumber\nrerun/\nreports/\nscreenshots/\nscreenshot*.png\ntest-servers/\n\n# bundler\n.bundle\nvendor\n\n# End of https://www.toptal.com/developers/gitignore/api/calabash\n","carthage":"# Created by https://www.toptal.com/developers/gitignore/api/carthage\n# Edit at https://www.toptal.com/developers/gitignore?templates=carthage\n\n### Carthage ###\n# Carthage\n#\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build\n\n# End of https://www.toptal.com/developers/gitignore/api/carthage\n","certificates":"# Created by https://www.toptal.com/developers/gitignore/api/certificates\n# Edit at https://www.toptal.com/developers/gitignore?templates=certificates\n\n### certificates ###\n*.pem\n*.key\n*.crt\n*.cer\n*.der\n*.priv\n\n# End of https://www.toptal.com/developers/gitignore/api/certificates\n","ceylon":"# Created by https://www.toptal.com/developers/gitignore/api/ceylon\n# Edit at https://www.toptal.com/developers/gitignore?templates=ceylon\n\n### Ceylon ###\n# build\nmodules\ntest-modules\n\n# fat-jar build\n*.jar\n\n# Auto backup setting file\n.ceylon/config.old\n\n# End of https://www.toptal.com/developers/gitignore/api/ceylon\n","cfwheels":"# Created by https://www.toptal.com/developers/gitignore/api/cfwheels\n# Edit at https://www.toptal.com/developers/gitignore?templates=cfwheels\n\n### CFWheels ###\n# unpacked plugin folders\nplugins/**/*\n\n# files directory where uploads go\nfiles\n\n# DBMigrate plugin: generated SQL\ndb/sql\n\n# AssetBundler plugin: generated bundles\njavascripts/bundles\nstylesheets/bundles\n\n# End of https://www.toptal.com/developers/gitignore/api/cfwheels\n","chefcookbook":"# Created by https://www.toptal.com/developers/gitignore/api/chefcookbook\n# Edit at https://www.toptal.com/developers/gitignore?templates=chefcookbook\n\n### ChefCookbook ###\n.vagrant\n/cookbooks\n\n# Bundler\nbin/*\n.bundle/*\n\n.kitchen/\n.kitchen.local.yml\n\n# End of https://www.toptal.com/developers/gitignore/api/chefcookbook\n","chocolatey":"# Created by https://www.toptal.com/developers/gitignore/api/chocolatey\n# Edit at https://www.toptal.com/developers/gitignore?templates=chocolatey\n\n### Chocolatey ###\n# Exclude packaged modules\n*.nupkg\n\n# End of https://www.toptal.com/developers/gitignore/api/chocolatey\n","circuitpython":"# Created by https://www.toptal.com/developers/gitignore/api/circuitpython\n# Edit at https://www.toptal.com/developers/gitignore?templates=circuitpython\n\n### CircuitPython ###\n.Trashes\n.metadata_never_index\n.fseventsd/\nboot_out.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/circuitpython\n","clean":"# Created by https://www.toptal.com/developers/gitignore/api/clean\n# Edit at https://www.toptal.com/developers/gitignore?templates=clean\n\n### Clean ###\n# Binaries\n*.exe\n*.out\n\n# Project files\n*.prj\n\n# Directory used to store object files, abc files and assembly files\nClean System Files/\n\n# iTasks environment extra data\n*-data/\n*-www/\n*-sapl/\n\n# End of https://www.toptal.com/developers/gitignore/api/clean\n","clion":"# Created by https://www.toptal.com/developers/gitignore/api/clion\n# Edit at https://www.toptal.com/developers/gitignore?templates=clion\n\n### CLion ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### CLion Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/clion\n","clion+all":"# Created by https://www.toptal.com/developers/gitignore/api/clion+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=clion+all\n\n### CLion+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### CLion+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/clion+all\n","clion+iml":"# Created by https://www.toptal.com/developers/gitignore/api/clion+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=clion+iml\n\n### CLion+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### CLion+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/clion+iml\n","clojure":"# Created by https://www.toptal.com/developers/gitignore/api/clojure\n# Edit at https://www.toptal.com/developers/gitignore?templates=clojure\n\n### Clojure ###\npom.xml\npom.xml.asc\n*.jar\n*.class\n/lib/\n/classes/\n/target/\n/checkouts/\n.lein-deps-sum\n.lein-repl-history\n.lein-plugins/\n.lein-failures\n.nrepl-port\n.cpcache/\n\n# End of https://www.toptal.com/developers/gitignore/api/clojure\n","cloud9":"# Created by https://www.toptal.com/developers/gitignore/api/cloud9\n# Edit at https://www.toptal.com/developers/gitignore?templates=cloud9\n\n### Cloud9 ###\n# Cloud9 IDE - http://c9.io\n.c9revisions\n.c9\n\n# End of https://www.toptal.com/developers/gitignore/api/cloud9\n","cmake":"# Created by https://www.toptal.com/developers/gitignore/api/cmake\n# Edit at https://www.toptal.com/developers/gitignore?templates=cmake\n\n### CMake ###\nCMakeLists.txt.user\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nTesting\nMakefile\ncmake_install.cmake\ninstall_manifest.txt\ncompile_commands.json\nCTestTestfile.cmake\n_deps\n\n### CMake Patch ###\nCMakeUserPresets.json\n\n# External projects\n*-prefix/\n\n# End of https://www.toptal.com/developers/gitignore/api/cmake\n","cocoapods":"# Created by https://www.toptal.com/developers/gitignore/api/cocoapods\n# Edit at https://www.toptal.com/developers/gitignore?templates=cocoapods\n\n### CocoaPods ###\n## CocoaPods GitIgnore Template\n\n# CocoaPods - Only use to conserve bandwidth / Save time on Pushing\n# - Also handy if you have a large number of dependant pods\n# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE\nPods/\n\n# End of https://www.toptal.com/developers/gitignore/api/cocoapods\n","cocos2dx":"# Created by https://www.toptal.com/developers/gitignore/api/cocos2dx\n# Edit at https://www.toptal.com/developers/gitignore?templates=cocos2dx\n\n### Cocos2dx ###\n# Ignore thumbnails created by windows\nThumbs.db\n\n# Ignore files build by Visual Studio\n*.obj\n*.exe\n*.pdb\n*.aps\n*.vcproj.*.user\n*.vcxproj.user\n*.csproj.user\n*.vspscc\n*_i.c\n*.i\n*.icf\n*_p.c\n*.ncb\n*.suo\n*.tlb\n*.tlh\n*.bak\n*.cache\n*.ilk\n*.log\n[Bb]in\n[Dd]ebug/\n[Dd]ebug.win32/\n*.sbr\n*.sdf\nobj/\n[Rr]elease/\n[Rr]elease.win32/\n_ReSharper*/\n[Tt]est[Rr]esult*\nipch/\n*.opensdf\n*.opendb\nSubmissionInfo\nGenerated Files\nAppPackages\nBundleArtifacts\n.vs/\n\n# Ignore files build by ndk and eclipse\nlibs/\nbin/\ngen/\nassets/\nlocal.properties\n\n# Ignore python compiled files\n*.pyc\n\n# Ignore files build by airplay and marmalade\nbuild_*_xcode/\nbuild_*_vc10/\n\n# Ignore files build by xcode\n*.mode*v*\n*.pbxuser\n*.xcbkptlist\n*.xcworkspacedata\n*.xcuserstate\n*.xccheckout\nxcschememanagement.plist\n.DS_Store\n._.*\nxcuserdata/\nDerivedData/\n\n# Ignore files built by AppCode\n.idea/\n\n# Ignore files built by bada\n.Simulator-Debug/\n.Target-Debug/\n.Target-Release/\n\n# Ignore files built by blackberry\nSimulator/\nDevice-Debug/\nDevice-Release/\n\n# Ignore vim swaps\n*.swp\n*.swo\n\n# Ignore files created by create_project.py\nprojects/\n\n# Ignore config files in javascript bindings generator\ntools/tojs/user.cfg\n# ... userconf.ini generated if running from tools/tojs\ntools/tojs/userconf.ini\ntools/tolua/userconf.ini\n# ... userconf.ini generated if running from tools/jenkins_scripts/mac/android/\ntools/jenkins_scripts/mac/android/userconf.ini\n\n# CTags\ntags\n\n# ignore files, created with make-all-linux-project script\n/lib\n/build/linux-build\n\n# Cmake files\nCMakeCache.txt\nCMakeFiles\nMakefile\ncmake_install.cmake\nCMakeLists.txt.user\n\n# Ignore files generated by console\nbuild/build/\ncocos/scripting/lua-bindings/proj.ios_mac/build/\ntests/*/runtime/\ntests/*/publish/\ntests/*/project/proj.android-studio/app/build.xml\ntests/*/project/proj.android-studio/app/proguard-project.txt\ntests/*/proj.android-studio/app/build.xml\ntests/*/proj.android-studio/app/proguard-project.txt\n\n# Android\nproject.properties\n*.iml\n\n# Ignore prebuilt libraries folder\n/external/*\n!/external/config.json\n/templates/lua-template-runtime/runtime\n/v*-deps-*.zip\n/v*-lua-runtime-*.zip\n/v*-console-*.zip\n/tools/fbx-conv/\ntests/cpp-tests/Resources/audio\n/tests/lua-empty-test/src/cocos/\n/tests/lua-game-controller-test/src/cocos/\n/tests/lua-tests/src/cocos/\n/tests/js-tests/res/\n/tools/framework-compile/bin/proj_modifier/plutil-win32/\n!/tools/framework-compile/bin/\n\n# generated by framework-compile\n/templates/*-template-binary/\n/prebuilt/\n/*/prebuilt-mk/Android.mk\n/*/*/prebuilt-mk/Android.mk\n/*/*/*/prebuilt-mk/Android.mk\n/*/*/*/*/prebuilt-mk/Android.mk\n*.xcscmblueprint\n\n# End of https://www.toptal.com/developers/gitignore/api/cocos2dx\n","cocoscreator":"# Created by https://www.toptal.com/developers/gitignore/api/cocoscreator\n# Edit at https://www.toptal.com/developers/gitignore?templates=cocoscreator\n\n### CocosCreator ###\n#/////////////////////////////////////////////////////////////////////////////\n# Cocos Creator Projects\n\nbuild/\nlibrary/\ntemp/\nlocal/\njsb-default/\njsb-binary/\njsb-link/\nweb-desktop/\nweb-mobile/\nruntime/\njsb/\nexport/\n\n# Logs and databases\n\n*.log\n*.sql\n*.sqlite\n\n# files for debugger\n\n*.sln\n*.csproj\n*.pidb\n*.unityproj\n*.suo\n\n# OS generated files\n\n.DS_Store\nehthumbs.db\nThumbs.db\n\n# exvim files\n\n*UnityVS.meta\n*.err\n*.err.meta\n*.exvim\n*.exvim.meta\n*.vimentry\n*.vimentry.meta\n*.vimproject\n*.vimproject.meta\n.vimfiles.*/\n.exvim.*/\nquick_gen_project_*_autogen.bat\nquick_gen_project_*_autogen.bat.meta\nquick_gen_project_*_autogen.sh\nquick_gen_project_*_autogen.sh.meta\n.exvim.app\n\n# webstorm files\n\n.idea/\n\n# VSCode files\n\ncreator.d.ts\n.vscode/\n\n# End of https://www.toptal.com/developers/gitignore/api/cocoscreator\n","codeblocks":"# Created by https://www.toptal.com/developers/gitignore/api/codeblocks\n# Edit at https://www.toptal.com/developers/gitignore?templates=codeblocks\n\n### CodeBlocks ###\n# specific to CodeBlocks IDE\n*.layout\n*.depend\n# generated directories\nbin/\nobj/\n\n# End of https://www.toptal.com/developers/gitignore/api/codeblocks\n","codecomposerstudio":"# Created by https://www.toptal.com/developers/gitignore/api/codecomposerstudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=codecomposerstudio\n\n### CodeComposerStudio ###\n\ntmp/\n*.tmp\n*.bak\n# Generated build artifact directories\nDebug/\nRelease/\nFlash/\nRAM/\n# Generated build artifacts\nmakefile\n*.map\n*.mk\n*.opt\n*.pp\n*.xml\n# Generated directories\n.config/\n.launches/\n# Generated file used to help display error messages in the problems view\n.xdchelp\n# Uniflash session files\n*.uniflashsession\n\n# End of https://www.toptal.com/developers/gitignore/api/codecomposerstudio\n","codeigniter":"# Created by https://www.toptal.com/developers/gitignore/api/codeigniter\n# Edit at https://www.toptal.com/developers/gitignore?templates=codeigniter\n\n### CodeIgniter ###\n*/config/development\n*/logs/log-*.php\n!*/logs/index.html\n*/cache/*\n!system/cache/*\n!*/cache/index.html\n!*/cache/.htaccess\n\nuser_guide_src/build/*\nuser_guide_src/cilexer/build/*\nuser_guide_src/cilexer/dist/*\nuser_guide_src/cilexer/pycilexer.egg-info/*\n\n#codeigniter 3\napplication/logs/*\n!application/logs/index.html\n!application/logs/.htaccess\n/vendor/\n\n### CodeIgniter Patch ###\n\n# End of https://www.toptal.com/developers/gitignore/api/codeigniter\n","codeio":"# Created by https://www.toptal.com/developers/gitignore/api/codeio\n# Edit at https://www.toptal.com/developers/gitignore?templates=codeio\n\n### Codeio ###\n.codio\n\n# End of https://www.toptal.com/developers/gitignore/api/codeio\n","codekit":"# Created by https://www.toptal.com/developers/gitignore/api/codekit\n# Edit at https://www.toptal.com/developers/gitignore?templates=codekit\n\n### CodeKit ###\n# General CodeKit files to ignore\nconfig.codekit\nconfig.codekit3\n/min\n\n### CodeKit Patch ###\n*/codekit-config.json\n.codekit-cache\n\n# End of https://www.toptal.com/developers/gitignore/api/codekit\n","codesniffer":"# Created by https://www.toptal.com/developers/gitignore/api/codesniffer\n# Edit at https://www.toptal.com/developers/gitignore?templates=codesniffer\n\n### CodeSniffer ###\n# gitignore for the PHP Codesniffer framework\n# website: https://github.com/squizlabs/PHP_CodeSniffer\n#\n# Recommended template: PHP.gitignore\n\n/wpcs/*\n\n# End of https://www.toptal.com/developers/gitignore/api/codesniffer\n","coffeescript":"# Created by https://www.toptal.com/developers/gitignore/api/coffeescript\n# Edit at https://www.toptal.com/developers/gitignore?templates=coffeescript\n\n### CoffeeScript ###\n*.js\n\n# End of https://www.toptal.com/developers/gitignore/api/coffeescript\n","commonlisp":"# Created by https://www.toptal.com/developers/gitignore/api/commonlisp\n# Edit at https://www.toptal.com/developers/gitignore?templates=commonlisp\n\n### CommonLisp ###\n*.FASL\n*.fasl\n*.lisp-temp\n*.dfsl\n*.pfsl\n*.d64fsl\n*.p64fsl\n*.lx64fsl\n*.lx32fsl\n*.dx64fsl\n*.dx32fsl\n*.fx64fsl\n*.fx32fsl\n*.sx64fsl\n*.sx32fsl\n*.wx64fsl\n*.wx32fsl\n\n# End of https://www.toptal.com/developers/gitignore/api/commonlisp\n","compodoc":"# Created by https://www.toptal.com/developers/gitignore/api/compodoc\n# Edit at https://www.toptal.com/developers/gitignore?templates=compodoc\n\n### Compodoc ###\n# gitignore template for the Compodoc, UI guide for front apps\n# website: https://compodoc.app/\n\ndocumentation/\n\n# End of https://www.toptal.com/developers/gitignore/api/compodoc\n","composer":"# Created by https://www.toptal.com/developers/gitignore/api/composer\n# Edit at https://www.toptal.com/developers/gitignore?templates=composer\n\n### Composer ###\ncomposer.phar\n/vendor/\n\n# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control\n# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file\n# composer.lock\n\n# End of https://www.toptal.com/developers/gitignore/api/composer\n","compressed":"# Created by https://www.toptal.com/developers/gitignore/api/compressed\n# Edit at https://www.toptal.com/developers/gitignore?templates=compressed\n\n### Compressed ###\n*.7z\n*.deb\n*.gz\n*.pkg\n*.rar\n*.rpm\n*.sit\n*.sitx\n*.tar\n*.zip\n*.zipx\n*.tgz\n\n# End of https://www.toptal.com/developers/gitignore/api/compressed\n","compressedarchive":"# Created by https://www.toptal.com/developers/gitignore/api/compressedarchive\n# Edit at https://www.toptal.com/developers/gitignore?templates=compressedarchive\n\n### CompressedArchive ###\n\n### Mostly from https://en.wikipedia.org/wiki/List_of_archive_formats\n\n## Archiving and compression\n# Open source file format. Used by 7-Zip.\n*.7z\n# Mac OS X, restoration on different platforms is possible although not immediate \tYes \tBased on 7z. Preserves Spotlight metadata, resource forks, owner/group information, dates and other data which would be otherwise lost with compression.\n*.s7z\n# Old archive versions only \tProprietary format\n*.ace\n# A format that compresses and doubly encrypt the data (AES256 and CAS256) avoiding brute force attacks, also hide files in an AFA file. It has two ways to safeguard data integrity and subsequent repair of the file if has an error (repair with AstroA2P (online) or Astrotite (offline)).\n*.afa\n# A mainly Korean format designed for very large archives.\n*.alz\n# Android application package (variant of JAR file format).\n*.apk\n# ??\n*.arc\n# Originally DOS, now multiple\n*.arj\n# Open archive format, used by B1 Free Archiver (http://dev.b1.org/standard/archive-format.html)\n*.b1\n# Binary Archive with external header\n*.ba\n# Proprietary format from the ZipTV Compression Components\n*.bh\n# The Microsoft Windows native archive format, which is also used by many commercial installers such as InstallShield and WISE.\n*.cab\n# Originally DOS, now DOS and Windows \tCreated by Yaakov Gringeler; released last in 2003 (Compressia 1.0.0.1 beta), now apparently defunct. Free trial of 30 days lets user create and extract archives; after that it is possible to extract, but not to create.\n*.car\n# Open source file format.\n*.cfs\n# Compact Pro archive, a common archiver used on Mac platforms until about Mac OS 7.5.x. Competed with StuffIt; now obsolete.\n*.cpt\n# Windows, Unix-like, Mac OS X Open source file format. Files are compressed individually with either gzip, bzip2 or lzo.\n*.dar\n# DiskDoubler \tMac OS \t\t\tobsolete\n*.dd\n# ??\n*.dgc\n# Apple Disk Image upports \"Internet-enabled\" disk images, which, once downloaded, are automatically decompressed, mounted, have the contents extracted, and thrown away. Currently, Safari is the only browser that supports this form of extraction; however, the images can be manually extracted as well. This format can also be password-protected or encrypted with 128-bit or 256-bit AES encryption.\n*.dmg\n# Enterprise Java Archive archive\n*.ear\n# ETSoft compressed archive\n*.egg\n# The predecessor of DGCA.\n*.gca\n# Originally DOS \tYes, but may be covered by patents \tDOS era format; uses arithmetic/Markov coding\n*.ha\n# MS Windows \tHKI\n*.hki\n# Produced by ICEOWS program. Excels at text file compression.\n*.ice\n# Java archive, compatible with ZIP files\n*.jar\n# Open sourced archiver with compression using the PAQ family of algorithms and optional encryption.\n*.kgb\n# Originally DOS, now multiple \tMultiple \tYes \tThe standard format on Amiga.\n*.lzh\n*.lha\n# Archiver originally used on The Amiga. Now copied by Microsoft to use in their .cab and .chm files.\n*.lzx\n# file format from NoGate Consultings, a rival from ARC-Compressor.\n*.pak\n# A disk image archive format that supports several compression methods as well as splitting the archive into smaller pieces.\n*.partimg\n# An experimental open source packager (http://mattmahoney.net/dc)\n*.paq*\n# Open source archiver supporting authenticated encryption, volume spanning, customizable object level and volume level integrity checks (form CRCs to SHA-512 and Whirlpool hashes), fast deflate based compression\n*.pea\n# The format from the PIM - a freeware compression tool by Ilia Muraviev. It uses an LZP-based compression algorithm with set of filters for executable, image and audio files.\n*.pim\n# PackIt \tMac OS \t\t\tobsolete\n*.pit\n# Used for data in games written using the Quadruple D library for Delphi. Uses byte pair compression.\n*.qda\n# A proprietary archive format, second in popularity to .zip files.\n*.rar\n# The format from a commercial archiving package. Odd among commercial packages in that they focus on incorporating experimental algorithms with the highest possible compression (at the expense of speed and memory), such as PAQ, PPMD and PPMZ (PPMD with unlimited-length strings), as well as a proprietary algorithms.\n*.rk\n# Self Dissolving ARChive \tCommodore 64, Commodore 128 \tCommodore 64, Commodore 128 \tYes \tSDAs refer to Self Dissolving ARC files, and are based on the Commodore 64 and Commodore 128 versions of ARC, originally written by Chris Smeets. While the files share the same extension, they are not compatible between platforms. That is, an SDA created on a Commodore 64 but run on a Commodore 128 in Commodore 128 mode will crash the machine, and vice versa. The intended successor to SDA is SFX.\n*.sda\n# A pre-Mac OS X Self-Extracting Archive format. StuffIt, Compact Pro, Disk Doubler and others could create .sea files, though the StuffIt versions were the most common.\n*.sea\n# Scifer Archive with internal header\n*.sen\n# Commodore 64, Commodore 128 \tSFX is a Self Extracting Archive which uses the LHArc compression algorithm. It was originally developed by Chris Smeets on the Commodore platform, and runs primarily using the CS-DOS extension for the Commodore 128. Unlike its predecessor SDA, SFX files will run on both the Commodore 64 and Commodore 128 regardless of which machine they were created on.\n*.sfx\n# An archive format designed for the Apple II series of computers. The canonical implementation is ShrinkIt, which can operate on disk images as well as files. Preferred compression algorithm is a combination of RLE and 12-bit LZW. Archives can be manipulated with the command-line NuLib tool, or the Windows-based CiderPress.\n*.shk\n# A compression format common on Apple Macintosh computers. The free StuffIt Expander is available for Windows and OS X.\n*.sit\n# The replacement for the .sit format that supports more compression methods, UNIX file permissions, long file names, very large files, more encryption options, data specific compressors (JPEG, Zip, PDF, 24-bit image, MP3). The free StuffIt Expander is available for Windows and OS X.\n*.sitx\n# A royalty-free compressing format\n*.sqx\n# The \"tarball\" format combines tar archives with a file-based compression scheme (usually gzip). Commonly used for source and binary distribution on Unix-like platforms, widely available elsewhere.\n*.tar.gz\n*.tgz\n*.tar.Z\n*.tar.bz2\n*.tbz2\n*.tar.lzma\n*.tlz\n# UltraCompressor 2.3 was developed to act as an alternative to the then popular PKZIP application. The main feature of the application is its ability to create large archives. This means that compressed archives with the UC2 file extension can hold almost 1 million files.\n*.uc\n*.uc0\n*.uc2\n*.ucn\n*.ur2\n*.ue2\n# Based on PAQ, RZM, CSC, CCM, and 7zip. The format consists of a PAQ, RZM, CSC, or CCM compressed file and a manifest with compression settings stored in a 7z archive.\n*.uca\n# A high compression rate archive format originally for DOS.\n*.uha\n# Web Application archive (Java-based web app)\n*.war\n# File-based disk image format developed to deploy Microsoft Windows.\n*.wim\n# XAR\n*.xar\n# Native format of the Open Source KiriKiri Visual Novel engine. Uses combination of block splitting and zlib compression. The filenames and pathes are stored in UTF-16 format. For integrity check, the Adler-32 hashsum is used. For many commercial games, the files are encrypted (and decoded on runtime) via so-called \"cxdec\" module, which implements xor-based encryption.\n*.xp3\n# Yamazaki zipper archive. Compression format used in DeepFreezer archiver utility created by Yamazaki Satoshi. Read and write support exists in TUGZip, IZArc and ZipZag\n*.yz1\n# The most widely used compression format on Microsoft Windows. Commonly used on Macintosh and Unix systems as well.\n*.zip\n*.zipx\n# application/x-zoo \tzoo \tMultiple \tMultiple \tYes \t\n*.zoo\n# Journaling (append-only) archive format with rollback capability. Supports deduplication and incremental update based on last-modified dates. Multi-threaded. Compresses in LZ77, BWT, and context mixing formats. Open source.\n*.zpaq\n# Archiver with a compression algorithm based on the Burrows-Wheeler transform method.\n*.zz\n\n\n# End of https://www.toptal.com/developers/gitignore/api/compressedarchive\n","compression":"# Created by https://www.toptal.com/developers/gitignore/api/compression\n# Edit at https://www.toptal.com/developers/gitignore?templates=compression\n\n### Compression ###\n\n### From https://en.wikipedia.org/wiki/List_of_archive_formats\n\n## Compression only\n# An open source, patent- and royalty-free compression format. The compression algorithm is a Burrows-Wheeler transform followed by a move-to-front transform and finally Huffman coding\n*.bz2\n# Old compressor for QNX4 OS. The compression algorithm is a modified LZSS, with an adaptive Huffman coding.\n*.F\n# GNU Zip, the primary compression format used by Unix-like systems. The compression algorithm is DEFLATE.\n*.gz\n# An alternate LZMA algorithm implementation, with support for checksums and ident bytes.\n*.lz\n# The LZMA compression algorithm as used by 7-Zip\n*.lzma\n# An implementation of the LZO data compression algorithm\n*.lzo\n# A compression program designed to do particularly well on very large files containing long distance redundancy.\n*.rz\n# Windows compress/decompress- Linux and Mac OS X decompress only \tA compression program designed to do high compression on SF2 files (SoundFont)\n*.sfark\n# A compression format invented by Google and open-sourced in 2011. Snappy aims for very high speeds, reasonable compression, and maximum stability rather than maximum compression or compatibility with any other compression library.\n*.sz\n# Squeeze: A program which compressed files. A file which was \"squeezed\" had the middle initial of the name changed to \"Q\", so that a squeezed text file would end with .TQT, a squeezed executable would end with .CQM or .EQE. Typically used with .LBR archives, either by storing the squeezed files in the archive, or by storing the files decompressed and then compressing the archive, which would have a name ending in \".LQR\".\n*.?Q?\n# A compression program written by Steven Greenberg implementing the LZW algorithm. For several years in the CP/M world when no implementation was available of ARC, CRUNCHed files stored in .LBR archives were very popular. CRUNCH's implementation of LZW had a somewhat unique feature of modifying and occasionally clearing the code table in memory when it became full, resulting in a few percent better compression on many files.\n*.?Z?\n# A compression format using LZMA2 to yield very high compression ratios.\n*.xz\n# The traditional Huffman coding compression format.\n*.z\n# The traditional LZW compression format.\n*.Z\n# Joke compression program, actually increasing file size\n*.infl\n# Compression format(s) used by some DOS and Windows install programs. MS-DOS includes expand.exe to decompress its install files. The compressed files are created with a matching compress.exe command. The compression algorithm is LZSS.\n*.??_\n\n\n# End of https://www.toptal.com/developers/gitignore/api/compression\n","conan":"# Created by https://www.toptal.com/developers/gitignore/api/conan\n# Edit at https://www.toptal.com/developers/gitignore?templates=conan\n\n### Conan ###\n# Conan build information\nconan.lock\nconanbuildinfo.*\nconaninfo.txt\ngraph_info.json\n\n## Various built-in generators\n# cmake_find_package generator https://docs.conan.io/en/latest/reference/generators/cmake_find_package.html\nFind*.cmake\n\n# cmake_paths generator https://docs.conan.io/en/1.4/integrations/cmake/cmake_paths_generator.html\nconan_paths.*\n\n# Environment activation scripts produced by https://docs.conan.io/en/latest/mastering/virtualenv.html#virtualenv-generator\nactivate_run.ps1\nactivate_run.sh\ndeactivate_run.ps1\ndeactivate_run.sh\nenvironment_run.ps1.env\nenvironment_run.sh.env\n\n# End of https://www.toptal.com/developers/gitignore/api/conan\n","concrete5":"# Created by https://www.toptal.com/developers/gitignore/api/concrete5\n# Edit at https://www.toptal.com/developers/gitignore?templates=concrete5\n\n### Concrete5 ###\n# ignore the error log and .htaccess and others\nerror_log\n.htaccess\n\n# concrete5 5.6 specific\n\nconfig/site.php\nfiles/cache/*\nfiles/tmp/*\n\n# concrete5 5.7 specific\n\n# ignore everything but the index.html\n/application/files/*\n!/application/files/index.html\n\n# ignore updates folder\n/updates/*\n\n# ignore sitemap.xml\n/sitemap.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/concrete5\n","coq":"# Created by https://www.toptal.com/developers/gitignore/api/coq\n# Edit at https://www.toptal.com/developers/gitignore?templates=coq\n\n### Coq ###\n.*.aux\n.*.d\n*.a\n*.cma\n*.cmi\n*.cmo\n*.cmx\n*.cmxa\n*.cmxs\n*.glob\n*.ml.d\n*.ml4.d\n*.mlg.d\n*.mli.d\n*.mllib.d\n*.mlpack.d\n*.native\n*.o\n*.v.d\n*.vio\n*.vo\n*.vok\n*.vos\n.coq-native\n.csdp.cache\n.lia.cache\n.nia.cache\n.nlia.cache\n.nra.cache\ncsdp.cache\nlia.cache\nnia.cache\nnlia.cache\nnra.cache\nnative_compute_profile_*.data\n\n# generated timing files\n*.timing.diff\n*.v.after-timing\n*.v.before-timing\n*.v.timing\ntime-of-build-after.log\ntime-of-build-before.log\ntime-of-build-both.log\ntime-of-build-pretty.log\n\n# End of https://www.toptal.com/developers/gitignore/api/coq\n","cordova":"# Created by https://www.toptal.com/developers/gitignore/api/cordova\n# Edit at https://www.toptal.com/developers/gitignore?templates=cordova\n\n### Cordova ###\n# gitignore template for the Cordova framework\n# website: https://cordova.apache.org/\n#\n# Recommended template: Node.gitignore\n\n# App platform binaries and built files\n/platforms\n\n# Optional to ignore plugin Git clones\n#/plugins\n\n# End of https://www.toptal.com/developers/gitignore/api/cordova\n","craftcms":"# Created by https://www.toptal.com/developers/gitignore/api/craftcms\n# Edit at https://www.toptal.com/developers/gitignore?templates=craftcms\n\n### CraftCMS ###\n# Craft 2 Storage (https://craftcms.com/support/craft-storage-gitignore)\n# not necessary for Craft 3 (https://github.com/craftcms/craft/issues/26)\n/craft/storage/*\n!/craft/storage/rebrand\n\n# End of https://www.toptal.com/developers/gitignore/api/craftcms\n","crashlytics":"# Created by https://www.toptal.com/developers/gitignore/api/crashlytics\n# Edit at https://www.toptal.com/developers/gitignore?templates=crashlytics\n\n### Crashlytics ###\n#Crashlytics\ncrashlytics-build.properties\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\nfabric.properties\n\n# End of https://www.toptal.com/developers/gitignore/api/crashlytics\n","crbasic":"# Created by https://www.toptal.com/developers/gitignore/api/crbasic\n# Edit at https://www.toptal.com/developers/gitignore?templates=crbasic\n\n### CRBasic ###\n# optional table definintions created at compile time\n*.tdf\n\n# any encrypted files\n*_Enc.*\n\n# End of https://www.toptal.com/developers/gitignore/api/crbasic\n","crossbar":"# Created by https://www.toptal.com/developers/gitignore/api/crossbar\n# Edit at https://www.toptal.com/developers/gitignore?templates=crossbar\n\n### Crossbar ###\n.crossbar/node.pid\n\n# End of https://www.toptal.com/developers/gitignore/api/crossbar\n","crystal":"# Created by https://www.toptal.com/developers/gitignore/api/crystal\n# Edit at https://www.toptal.com/developers/gitignore?templates=crystal\n\n### Crystal ###\n/docs/\n/lib/\n/bin/\n/.shards/\n*.dwarf\n\n# Libraries don't need dependency lock\n# Dependencies will be locked in application that uses them\n#/shard.lock\n\n# End of https://www.toptal.com/developers/gitignore/api/crystal\n","cs-cart":"# Created by https://www.toptal.com/developers/gitignore/api/cs-cart\n# Edit at https://www.toptal.com/developers/gitignore?templates=cs-cart\n\n### CS-Cart ###\n# CS-Cart and CS-Cart Multi-Vendor, a PHP-based CMS\n## Maintainer https://simtechdev.com\n\njs/core/node_modules\njs/tygh/core.js\njs/tygh/notifications_center.js\njs/tygh/bottom_panel.js\njs/core/npm-debug.log\n\napp/lib/composer.phar\ndesign/backend/css/twitterbootstrap.css\nimages/*\nusers/*\nvar/*\n!var/database\n!var/backups/.htaccess\n!var/restore/.htaccess\n!var/resources\n!var/langs\n!var/conf\nvar/langs/*\n!var/langs/en\n!var/.htaccess\n!var/index.php\n!var/builds\n!images/promo\n\nupgrades/*\n!upgrades/source_restore.php\n\nlocal_conf.php\ninstall/\nsph*.php\nget_addon.php\nget_addon/\n\nerror_log*\n\n_tools/selenium_WebDriver/tests/share/config/sessions.local.php\n_tools/selenium_WebDriver/tests/share/storage/local.php\n_tools/selenium_WebDriver/tests/share/storage/screens/*.png\n\n_tools/migration/composer.lock\n_tools/migration/conf.php\n_tools/migration/vendor\n\n*.log\n*.sql\n*.bak\n*.swp\n*.orig\n*.tmp\n\n# End of https://www.toptal.com/developers/gitignore/api/cs-cart\n","csharp":"# Created by https://www.toptal.com/developers/gitignore/api/csharp\n# Edit at https://www.toptal.com/developers/gitignore?templates=csharp\n\n### Csharp ###\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.tlog\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio 6 auto-generated project file (contains which files were open etc.)\n*.vbp\n\n# Visual Studio 6 workspace and project file (working project files containing files to include in project)\n*.dsw\n*.dsp\n\n# Visual Studio 6 technical files\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# Visual Studio History (VSHistory) files\n.vshistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd\n\n# VS Code files for those working on multiple tools\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# Local History for Visual Studio Code\n.history/\n\n# Windows Installer files from build outputs\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# JetBrains Rider\n*.sln.iml\n\n# End of https://www.toptal.com/developers/gitignore/api/csharp\n","cuda":"# Created by https://www.toptal.com/developers/gitignore/api/cuda\n# Edit at https://www.toptal.com/developers/gitignore?templates=cuda\n\n### CUDA ###\n*.i\n*.ii\n*.gpu\n*.ptx\n*.cubin\n*.fatbin\n\n# End of https://www.toptal.com/developers/gitignore/api/cuda\n","cvs":"# Created by https://www.toptal.com/developers/gitignore/api/cvs\n# Edit at https://www.toptal.com/developers/gitignore?templates=cvs\n\n### CVS ###\n/CVS/*\n**/CVS/*\n.cvsignore\n*/.cvsignore\n\n# End of https://www.toptal.com/developers/gitignore/api/cvs\n","cypressio":"# Created by https://www.toptal.com/developers/gitignore/api/cypressio\n# Edit at https://www.toptal.com/developers/gitignore?templates=cypressio\n\n### CypressIO ###\n# gitignore template for the CypressIO, browser test framework\n# website: https://www.cypress.io/\n\ncypress/results/*\ncypress/reports/*\ncypress/screenshots/*\ncypress/videos/*\n\n# End of https://www.toptal.com/developers/gitignore/api/cypressio\n","d":"# Created by https://www.toptal.com/developers/gitignore/api/d\n# Edit at https://www.toptal.com/developers/gitignore?templates=d\n\n### D ###\n# Compiled Object files\n*.o\n*.obj\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Compiled Static libraries\n*.a\n*.lib\n\n# Executables\n*.exe\n\n# DUB\n.dub\ndocs.json\n__dummy.html\ndocs/\n\n# Code coverage\n*.lst\n\n### D Patch ###\n# Test Executables\n*-test-*\n\n# Comment to allow dub lock file to be version controlled as well\ndub.selections.json\n\n# End of https://www.toptal.com/developers/gitignore/api/d\n","dart":"# Created by https://www.toptal.com/developers/gitignore/api/dart\n# Edit at https://www.toptal.com/developers/gitignore?templates=dart\n\n### Dart ###\n# See https://www.dartlang.org/guides/libraries/private-files\n\n# Files and directories created by pub\n.dart_tool/\n.packages\nbuild/\n# If you're building an application, you may want to check-in your pubspec.lock\npubspec.lock\n\n# Directory created by dartdoc\n# If you don't generate documentation locally you can remove this line.\ndoc/api/\n\n# dotenv environment variables file\n.env*\n\n# Avoid committing generated Javascript files:\n*.dart.js\n*.info.json # Produced by the --dump-info flag.\n*.js # When generated by dart2js. Don't specify *.js if your\n # project includes source files written in JavaScript.\n*.js_\n*.js.deps\n*.js.map\n\n.flutter-plugins\n.flutter-plugins-dependencies\n\n### Dart Patch ###\n# dotenv environment variables file\n.env\n\n# End of https://www.toptal.com/developers/gitignore/api/dart\n","darteditor":"# Created by https://www.toptal.com/developers/gitignore/api/darteditor\n# Edit at https://www.toptal.com/developers/gitignore?templates=darteditor\n\n### DartEditor ###\n.project\n.buildlog\n\n# End of https://www.toptal.com/developers/gitignore/api/darteditor\n","data":"# Created by https://www.toptal.com/developers/gitignore/api/data\n# Edit at https://www.toptal.com/developers/gitignore?templates=data\n\n### Data ###\n*.csv\n*.dat\n*.efx\n*.gbr\n*.key\n*.pps\n*.ppt\n*.pptx\n*.sdf\n*.tax2010\n*.vcf\n*.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/data\n","database":"# Created by https://www.toptal.com/developers/gitignore/api/database\n# Edit at https://www.toptal.com/developers/gitignore?templates=database\n\n### Database ###\n*.accdb\n*.db\n*.dbf\n*.mdb\n*.pdb\n*.sqlite3\n*.db-shm\n*.db-wal\n\n# End of https://www.toptal.com/developers/gitignore/api/database\n","datarecovery":"# Created by https://www.toptal.com/developers/gitignore/api/datarecovery\n# Edit at https://www.toptal.com/developers/gitignore?templates=datarecovery\n\n### DataRecovery ###\n\n### From https://en.wikipedia.org/wiki/List_of_archive_formats\n\n## Data recovery\n# File format used by dvdisaster to be used for data recovery when discs become damaged or partially unreadable.\n*.ecc\n# File format used in conjunction with any archive format to provide redundancy and data recovery, most often in newsgroup distribution of binary files.\n*.par\n*.par2\n\n\n# End of https://www.toptal.com/developers/gitignore/api/datarecovery\n","dbeaver":"# Created by https://www.toptal.com/developers/gitignore/api/dbeaver\n# Edit at https://www.toptal.com/developers/gitignore?templates=dbeaver\n\n### DBeaver ###\n# ide config file\n.dbeaver-data-sources*.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/dbeaver\n","dbt":"# Created by https://www.toptal.com/developers/gitignore/api/dbt\n# Edit at https://www.toptal.com/developers/gitignore?templates=dbt\n\n### dbt ###\ntarget/\ndbt_modules/\ndbt_packages/\nlogs/\n\n# End of https://www.toptal.com/developers/gitignore/api/dbt\n","defold":"# Created by https://www.toptal.com/developers/gitignore/api/defold\n# Edit at https://www.toptal.com/developers/gitignore?templates=defold\n\n### Defold ###\n/.internal\n/build\n.externalToolBuilders\n.DS_Store\nThumbs.db\n.lock-wscript\n*.pyc\n.project\n.cproject\nbuiltins\n\n# End of https://www.toptal.com/developers/gitignore/api/defold\n","delphi":"# Created by https://www.toptal.com/developers/gitignore/api/delphi\n# Edit at https://www.toptal.com/developers/gitignore?templates=delphi\n\n### Delphi ###\n# Uncomment these types if you want even more clean repository. But be careful.\n# It can make harm to an existing project source. Read explanations below.\n#\n# Resource files are binaries containing manifest, project icon and version info.\n# They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files.\n#*.res\n# Type library file (binary). In old Delphi versions it should be stored.\n# Since Delphi 2009 it is produced from .ridl file and can safely be ignored.\n#*.tlb\n# Diagram Portfolio file. Used by the diagram editor up to Delphi 7.\n# Uncomment this if you are not using diagrams or use newer Delphi version.\n#*.ddp\n# Visual LiveBindings file. Added in Delphi XE2.\n# Uncomment this if you are not using LiveBindings Designer.\n#*.vlb\n# Deployment Manager configuration file for your project. Added in Delphi XE2.\n# Uncomment this if it is not mobile development and you do not use remote debug feature.\n#*.deployproj\n# C++ object files produced when C/C++ Output file generation is configured.\n# Uncomment this if you are not using external objects (zlib library for example).\n#*.obj\n\n# Delphi compiler-generated binaries (safe to delete)\n*.exe\n*.dll\n*.bpl\n*.bpi\n*.dcp\n*.so\n*.apk\n*.drc\n*.map\n*.dres\n*.rsm\n*.tds\n*.dcu\n*.lib\n*.a\n*.o\n*.ocx\n\n# Delphi autogenerated files (duplicated info)\n*.cfg\n*.hpp\n*Resource.rc\n\n# Delphi local files (user-specific info)\n*.local\n*.identcache\n*.projdata\n*.tvsconfig\n*.dsk\n\n# Delphi history and backups\n__history/\n__recovery/\n*.~*\n\n# Castalia statistics file (since XE7 Castalia is distributed with Delphi)\n*.stat\n\n# Boss dependency manager vendor folder https://github.com/HashLoad/boss\nmodules/\n\n# End of https://www.toptal.com/developers/gitignore/api/delphi\n","deno":"# Created by https://www.toptal.com/developers/gitignore/api/deno\n# Edit at https://www.toptal.com/developers/gitignore?templates=deno\n\n### Deno ###\n/.idea/\n/.vscode/\n\n/node_modules\n\n.env\n*.orig\n*.pyc\n*.swp\n\n# End of https://www.toptal.com/developers/gitignore/api/deno\n","dframe":"# Created by https://www.toptal.com/developers/gitignore/api/dframe\n# Edit at https://www.toptal.com/developers/gitignore?templates=dframe\n\n### Dframe ###\n# Ignore files that are auto-generated\napp/View/templates_c/*\napp/View/cache/*\napp/View/logs/*\nweb/assets/*\nvendor\n\n# Force include\n!app/View/templates_c/.gitkeep\n!app/View/cache/.gitkeep\n!app/View/logs/.gitkeep\n!web/assets/.gitkeep\n\n# Ignore IDE\n.project\n.vscode/\n\n# End of https://www.toptal.com/developers/gitignore/api/dframe\n","diff":"# Created by https://www.toptal.com/developers/gitignore/api/diff\n# Edit at https://www.toptal.com/developers/gitignore?templates=diff\n\n### Diff ###\n*.patch\n*.diff\n\n# End of https://www.toptal.com/developers/gitignore/api/diff\n","direnv":"# Created by https://www.toptal.com/developers/gitignore/api/direnv\n# Edit at https://www.toptal.com/developers/gitignore?templates=direnv\n\n### direnv ###\n.direnv\n.envrc\n\n# End of https://www.toptal.com/developers/gitignore/api/direnv\n","diskimage":"# Created by https://www.toptal.com/developers/gitignore/api/diskimage\n# Edit at https://www.toptal.com/developers/gitignore?templates=diskimage\n\n### DiskImage ###\n*.dmg\n*.iso\n*.toast\n*.vcd\n\n# End of https://www.toptal.com/developers/gitignore/api/diskimage\n","django":"# Created by https://www.toptal.com/developers/gitignore/api/django\n# Edit at https://www.toptal.com/developers/gitignore?templates=django\n\n### Django ###\n*.log\n*.pot\n*.pyc\n__pycache__/\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\nmedia\n\n# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/\n# in your Git repository. Update and uncomment the following line accordingly.\n# /staticfiles/\n\n### Django.Python Stack ###\n# Byte-compiled / optimized / DLL files\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n# Usually these files are written by a python script from a template\n# before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n\n# Django stuff:\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n# For a library or package, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n# However, in case of collaboration, if having platform-specific dependencies or dependencies\n# having no cross-platform support, pipenv may install dependencies that don't work, or not\n# install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n# This is especially recommended for binary packages to ensure reproducibility, and is more\n# commonly ignored for libraries.\n# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n# in version control.\n# https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n# JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n# and can be added to the global gitignore or merged into this file. For a more nuclear\n# option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n# End of https://www.toptal.com/developers/gitignore/api/django\n","dm":"# Created by https://www.toptal.com/developers/gitignore/api/dm\n# Edit at https://www.toptal.com/developers/gitignore?templates=dm\n\n### DM ###\n*.dmb\n*.rsc\n*.int\n*.lk\n*.zip\n\n# End of https://www.toptal.com/developers/gitignore/api/dm\n","docfx":"# Created by https://www.toptal.com/developers/gitignore/api/docfx\n# Edit at https://www.toptal.com/developers/gitignore?templates=docfx\n\n### DocFx ###\n.cache\n/**/_site/\n\n# End of https://www.toptal.com/developers/gitignore/api/docfx\n","docpress":"# Created by https://www.toptal.com/developers/gitignore/api/docpress\n# Edit at https://www.toptal.com/developers/gitignore?templates=docpress\n\n### Docpress ###\n# docpress documentation generator: https://docpress.github.io/index.html\n\n_docpress/\n\n# End of https://www.toptal.com/developers/gitignore/api/docpress\n","docusaurus":"# Created by https://www.toptal.com/developers/gitignore/api/docusaurus\n# Edit at https://www.toptal.com/developers/gitignore?templates=docusaurus\n\n### Docusaurus ###\n# Docusaurus cache and generated files\n.docusaurus\n\n# Docusaurus Build Directory\n/build/\n\n### Docusaurus.Node Stack ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# End of https://www.toptal.com/developers/gitignore/api/docusaurus\n","docz":"# Created by https://www.toptal.com/developers/gitignore/api/docz\n# Edit at https://www.toptal.com/developers/gitignore?templates=docz\n\n### Docz ###\n.docz\n\n\n# End of https://www.toptal.com/developers/gitignore/api/docz\n","dotenv":"# Created by https://www.toptal.com/developers/gitignore/api/dotenv\n# Edit at https://www.toptal.com/developers/gitignore?templates=dotenv\n\n### dotenv ###\n.env\n\n# End of https://www.toptal.com/developers/gitignore/api/dotenv\n","dotfilessh":"# Created by https://www.toptal.com/developers/gitignore/api/dotfilessh\n# Edit at https://www.toptal.com/developers/gitignore?templates=dotfilessh\n\n### DotfilesSh ###\nlocal-patch\npatched-src\n\n# End of https://www.toptal.com/developers/gitignore/api/dotfilessh\n","dotnetcore":"# Created by https://www.toptal.com/developers/gitignore/api/dotnetcore\n# Edit at https://www.toptal.com/developers/gitignore?templates=dotnetcore\n\n### DotnetCore ###\n# .NET Core build folders\nbin/\nobj/\n\n# Common node modules locations\n/node_modules\n/wwwroot/node_modules\n\n# End of https://www.toptal.com/developers/gitignore/api/dotnetcore\n","dotsettings":"# Created by https://www.toptal.com/developers/gitignore/api/dotsettings\n# Edit at https://www.toptal.com/developers/gitignore?templates=dotsettings\n\n### DotSettings ###\n*.DotSettings\n\n# End of https://www.toptal.com/developers/gitignore/api/dotsettings\n","doxygen":"# Created by https://www.toptal.com/developers/gitignore/api/doxygen\n# Edit at https://www.toptal.com/developers/gitignore?templates=doxygen\n\n### Doxygen ###\nhtml\nlatex\n\n# End of https://www.toptal.com/developers/gitignore/api/doxygen\n","dreamweaver":"# Created by https://www.toptal.com/developers/gitignore/api/dreamweaver\n# Edit at https://www.toptal.com/developers/gitignore?templates=dreamweaver\n\n### Dreamweaver ###\n# DW Dreamweaver added files\n_notes\n_compareTemp\nconfigs/\ndwsync.xml\ndw_php_codehinting.config\n*.mno\n\n# End of https://www.toptal.com/developers/gitignore/api/dreamweaver\n","dropbox":"# Created by https://www.toptal.com/developers/gitignore/api/dropbox\n# Edit at https://www.toptal.com/developers/gitignore?templates=dropbox\n\n### Dropbox ###\n# Dropbox settings and caches\n.dropbox\n.dropbox.attr\n.dropbox.cache\n\n# End of https://www.toptal.com/developers/gitignore/api/dropbox\n","drupal":"# Created by https://www.toptal.com/developers/gitignore/api/drupal\n# Edit at https://www.toptal.com/developers/gitignore?templates=drupal\n\n### Drupal ###\n# gitignore template for Drupal 8 projects\n#\n# earlier versions of Drupal are tracked in `community/PHP/`\n# follows official upstream conventions:\n# https://www.drupal.org/docs/develop/using-composer\n\n# Ignore configuration files that may contain sensitive information\n/web/sites/*/*settings*.php\n/web/sites/*/*services*.yml\n\n# Ignore paths that may contain user-generated content\n/web/sites/*/files\n/web/sites/*/public\n/web/sites/*/private\n/web/sites/*/files-public\n/web/sites/*/files-private\n\n# Ignore paths that may contain temporary files\n/web/sites/*/translations\n/web/sites/*/tmp\n/web/sites/*/cache\n\n# Ignore drupal core (if not versioning drupal sources)\n/web/vendor\n/web/core\n/web/modules/README.txt\n/web/profiles/README.txt\n/web/sites/development.services.yml\n/web/sites/example.settings.local.php\n/web/sites/example.sites.php\n/web/sites/README.txt\n/web/themes/README.txt\n/web/.csslintrc\n/web/.editorconfig\n/web/.eslintignore\n/web/.eslintrc.json\n/web/.gitattributes\n/web/.htaccess\n/web/.ht.router.php\n/web/autoload.php\n/web/composer.json\n/web/composer.lock\n/web/example.gitignore\n/web/index.php\n/web/INSTALL.txt\n/web/LICENSE.txt\n/web/README.txt\n/web/robots.txt\n/web/update.php\n/web/web.config\n\n# Ignore vendor dependencies and scripts\n/vendor\n/composer.phar\n/composer\n/robo.phar\n/robo\n/drush.phar\n/drush\n/drupal.phar\n/drupal\n\n# End of https://www.toptal.com/developers/gitignore/api/drupal\n","drupal7":"# Created by https://www.toptal.com/developers/gitignore/api/drupal7\n# Edit at https://www.toptal.com/developers/gitignore?templates=drupal7\n\n### Drupal7 ###\n# gitignore template for Drupal 7 projects\n#\n# It is recommended that you use `Drupal.gitignore` as this is the latest version\n\n# Ignore configuration files that may contain sensitive information.\nsites/*/*settings*.php\nsites/example.sites.php\n\n# Ignore paths that contain generated content.\nfiles/\nsites/*/files\nsites/*/private\nsites/*/translations\n\n# Ignore default text files\nrobots.txt\n/CHANGELOG.txt\n/COPYRIGHT.txt\n/INSTALL*.txt\n/LICENSE.txt\n/MAINTAINERS.txt\n/UPGRADE.txt\n/README.txt\nsites/README.txt\nsites/all/libraries/README.txt\nsites/all/modules/README.txt\nsites/all/themes/README.txt\n\n# Ignore everything but the \"sites\" folder ( for non core developer )\n.htaccess\nweb.config\nauthorize.php\ncron.php\nindex.php\ninstall.php\nupdate.php\nxmlrpc.php\n/includes\n/misc\n/modules\n/profiles\n/scripts\n/themes\n\n# End of https://www.toptal.com/developers/gitignore/api/drupal7\n","drupal8":"# Created by https://www.toptal.com/developers/gitignore/api/drupal8\n# Edit at https://www.toptal.com/developers/gitignore?templates=drupal8\n\n### Drupal8 ###\n# gitignore template for Drupal 8 projects\n#\n# earlier versions of Drupal are tracked in `community/PHP/`\n# follows official upstream conventions:\n# https://www.drupal.org/docs/develop/using-composer\n\n# Ignore configuration files that may contain sensitive information\n/web/sites/*/*settings*.php\n/web/sites/*/*services*.yml\n\n# Ignore paths that may contain user-generated content\n/web/sites/*/files\n/web/sites/*/public\n/web/sites/*/private\n/web/sites/*/files-public\n/web/sites/*/files-private\n\n# Ignore paths that may contain temporary files\n/web/sites/*/translations\n/web/sites/*/tmp\n/web/sites/*/cache\n\n# Ignore drupal core (if not versioning drupal sources)\n/web/vendor\n/web/core\n/web/modules/README.txt\n/web/profiles/README.txt\n/web/sites/development.services.yml\n/web/sites/example.settings.local.php\n/web/sites/example.sites.php\n/web/sites/README.txt\n/web/themes/README.txt\n/web/.csslintrc\n/web/.editorconfig\n/web/.eslintignore\n/web/.eslintrc.json\n/web/.gitattributes\n/web/.htaccess\n/web/.ht.router.php\n/web/autoload.php\n/web/composer.json\n/web/composer.lock\n/web/example.gitignore\n/web/index.php\n/web/INSTALL.txt\n/web/LICENSE.txt\n/web/README.txt\n/web/robots.txt\n/web/update.php\n/web/web.config\n\n# Ignore vendor dependencies and scripts\n/vendor\n/composer.phar\n/composer\n/robo.phar\n/robo\n/drush.phar\n/drush\n/drupal.phar\n/drupal\n\n# End of https://www.toptal.com/developers/gitignore/api/drupal8\n","e2studio":"# Created by https://www.toptal.com/developers/gitignore/api/e2studio\n# Edit at https://www.toptal.com/developers/gitignore?templates=e2studio\n\n### e2studio ###\n# Modified gitignore for Renesas E2Studio (Built on eclipse): maintain all data needed by eclipse to setting up a workspace.\n\n.history\n*.tmp\n*.obj\n*.log\nhistory.index\n\n# E2 Studio Specific\nHardwareDebug/src\n\n\n# End of https://www.toptal.com/developers/gitignore/api/e2studio\n","eagle":"# Created by https://www.toptal.com/developers/gitignore/api/eagle\n# Edit at https://www.toptal.com/developers/gitignore?templates=eagle\n\n### Eagle ###\n# Ignore list for Eagle, a PCB layout tool\n\n# Backup files\n*.s#?\n*.b#?\n*.l#?\n*.b$?\n*.s$?\n*.l$?\n\n# Eagle project file\n# It contains a serial number and references to the file structure\n# on your computer.\n# comment the following line if you want to have your project file included.\neagle.epf\n\n# Autorouter files\n*.pro\n*.job\n\n# CAM files\n*.$$$\n*.cmp\n*.ly2\n*.l15\n*.sol\n*.plc\n*.stc\n*.sts\n*.crc\n*.crs\n\n*.dri\n*.drl\n*.gpi\n*.pls\n*.ger\n*.xln\n\n*.drd\n*.drd.*\n\n*.s#*\n*.b#*\n\n*.info\n\n*.eps\n\n# file locks introduced since 7.x\n*.lck\n\n# End of https://www.toptal.com/developers/gitignore/api/eagle\n","easybook":"# Created by https://www.toptal.com/developers/gitignore/api/easybook\n# Edit at https://www.toptal.com/developers/gitignore?templates=easybook\n\n### easybook ###\n# Destination directory of the various publications\nOutput\n\n# End of https://www.toptal.com/developers/gitignore/api/easybook\n","eclipse":"# Created by https://www.toptal.com/developers/gitignore/api/eclipse\n# Edit at https://www.toptal.com/developers/gitignore?templates=eclipse\n\n### Eclipse ###\n.metadata\nbin/\ntmp/\n*.tmp\n*.bak\n*.swp\n*~.nib\nlocal.properties\n.settings/\n.loadpath\n.recommenders\n\n# External tool builders\n.externalToolBuilders/\n\n# Locally stored \"Eclipse launch configurations\"\n*.launch\n\n# PyDev specific (Python IDE for Eclipse)\n*.pydevproject\n\n# CDT-specific (C/C++ Development Tooling)\n.cproject\n\n# CDT- autotools\n.autotools\n\n# Java annotation processor (APT)\n.factorypath\n\n# PDT-specific (PHP Development Tools)\n.buildpath\n\n# sbteclipse plugin\n.target\n\n# Tern plugin\n.tern-project\n\n# TeXlipse plugin\n.texlipse\n\n# STS (Spring Tool Suite)\n.springBeans\n\n# Code Recommenders\n.recommenders/\n\n# Annotation Processing\n.apt_generated/\n.apt_generated_test/\n\n# Scala IDE specific (Scala & Java development for Eclipse)\n.cache-main\n.scala_dependencies\n.worksheet\n\n# Uncomment this line if you wish to ignore the project description file.\n# Typically, this file would be tracked if it contains build/dependency configurations:\n#.project\n\n### Eclipse Patch ###\n# Spring Boot Tooling\n.sts4-cache/\n\n# End of https://www.toptal.com/developers/gitignore/api/eclipse\n","eiffelstudio":"# Created by https://www.toptal.com/developers/gitignore/api/eiffelstudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=eiffelstudio\n\n### EiffelStudio ###\n# The compilation directory\nEIFGENs\n\n# End of https://www.toptal.com/developers/gitignore/api/eiffelstudio\n","elasticbeanstalk":"# Created by https://www.toptal.com/developers/gitignore/api/elasticbeanstalk\n# Edit at https://www.toptal.com/developers/gitignore?templates=elasticbeanstalk\n\n### ElasticBeanstalk ###\n.elasticbeanstalk/\n\n# End of https://www.toptal.com/developers/gitignore/api/elasticbeanstalk\n","elisp":"# Created by https://www.toptal.com/developers/gitignore/api/elisp\n# Edit at https://www.toptal.com/developers/gitignore?templates=elisp\n\n### Elisp ###\n# Compiled\n*.elc\n\n# Packaging\n.cask\n\n# Backup files\n*~\n\n# Undo-tree save-files\n*.~undo-tree\n\n# End of https://www.toptal.com/developers/gitignore/api/elisp\n","elixir":"# Created by https://www.toptal.com/developers/gitignore/api/elixir\n# Edit at https://www.toptal.com/developers/gitignore?templates=elixir\n\n### Elixir ###\n/_build\n/cover\n/deps\n/doc\n/.fetch\nerl_crash.dump\n*.ez\n*.beam\n/config/*.secret.exs\n.elixir_ls/\n\n### Elixir Patch ###\n\n# End of https://www.toptal.com/developers/gitignore/api/elixir\n","elm":"# Created by https://www.toptal.com/developers/gitignore/api/elm\n# Edit at https://www.toptal.com/developers/gitignore?templates=elm\n\n### Elm ###\n# elm-package generated files\nelm-stuff\n# elm-repl generated files\nrepl-temp-*\n\n# End of https://www.toptal.com/developers/gitignore/api/elm\n","emacs":"# Created by https://www.toptal.com/developers/gitignore/api/emacs\n# Edit at https://www.toptal.com/developers/gitignore?templates=emacs\n\n### Emacs ###\n# -*- mode: gitignore; -*-\n*~\n\\#*\\#\n/.emacs.desktop\n/.emacs.desktop.lock\n*.elc\nauto-save-list\ntramp\n.\\#*\n\n# Org-mode\n.org-id-locations\n*_archive\n\n# flymake-mode\n*_flymake.*\n\n# eshell files\n/eshell/history\n/eshell/lastdir\n\n# elpa packages\n/elpa/\n\n# reftex files\n*.rel\n\n# AUCTeX auto folder\n/auto/\n\n# cask packages\n.cask/\ndist/\n\n# Flycheck\nflycheck_*.el\n\n# server auth directory\n/server/\n\n# projectiles files\n.projectile\n\n# directory configuration\n.dir-locals.el\n\n# network security\n/network-security.data\n\n\n# End of https://www.toptal.com/developers/gitignore/api/emacs\n","ember":"# Created by https://www.toptal.com/developers/gitignore/api/ember\n# Edit at https://www.toptal.com/developers/gitignore?templates=ember\n\n### Ember ###\n# see https://github.com/ember-cli/ember-cli/blob/master/blueprints/app/files/gitignore\n# compiled output\n/dist\n/tmp\n\n# dependencies\n/node_modules\n/bower_components\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage/*\n/libpeerconnection.log\nnpm-debug.log\ntestem.log\n\n# End of https://www.toptal.com/developers/gitignore/api/ember\n","ensime":"# Created by https://www.toptal.com/developers/gitignore/api/ensime\n# Edit at https://www.toptal.com/developers/gitignore?templates=ensime\n\n### Ensime ###\n# Ensime specific\n.ensime\n.ensime_cache/\n.ensime_lucene/\n\n# End of https://www.toptal.com/developers/gitignore/api/ensime\n","episerver":"# Created by https://www.toptal.com/developers/gitignore/api/episerver\n# Edit at https://www.toptal.com/developers/gitignore?templates=episerver\n\n### EPiServer ###\n######################\n## EPiServer Files\n*License.config\n\n# End of https://www.toptal.com/developers/gitignore/api/episerver\n","erlang":"# Created by https://www.toptal.com/developers/gitignore/api/erlang\n# Edit at https://www.toptal.com/developers/gitignore?templates=erlang\n\n### Erlang ###\n.eunit\n*.o\n*.beam\n*.plt\nerl_crash.dump\n.concrete/DEV_MODE\n\n# rebar 2.x\n.rebar\nrel/example_project\nebin/*.beam\ndeps\n\n# rebar 3\n.rebar3\n_build/\n_checkouts/\n\n### Erlang Patch ###\nrebar3.crashdump\n\n# End of https://www.toptal.com/developers/gitignore/api/erlang\n","espresso":"# Created by https://www.toptal.com/developers/gitignore/api/espresso\n# Edit at https://www.toptal.com/developers/gitignore?templates=espresso\n\n### Espresso ###\n*.esproj\n\n# End of https://www.toptal.com/developers/gitignore/api/espresso\n","executable":"# Created by https://www.toptal.com/developers/gitignore/api/executable\n# Edit at https://www.toptal.com/developers/gitignore?templates=executable\n\n### Executable ###\n*.app\n*.bat\n*.cgi\n*.com\n*.exe\n*.gadget\n*.jar\n*.pif\n*.vb\n*.wsf\n\n# End of https://www.toptal.com/developers/gitignore/api/executable\n","exercism":"# Created by https://www.toptal.com/developers/gitignore/api/exercism\n# Edit at https://www.toptal.com/developers/gitignore?templates=exercism\n\n### Exercism ###\n# gitignore template for Exercism project\n# website: https://exercism.io/\n\n# Ignore .exercism folder which contain sensitive data\n.exercism\n\n# End of https://www.toptal.com/developers/gitignore/api/exercism\n","expressionengine":"# Created by https://www.toptal.com/developers/gitignore/api/expressionengine\n# Edit at https://www.toptal.com/developers/gitignore?templates=expressionengine\n\n### ExpressionEngine ###\n.DS_Store\n\n# Images\nimages/avatars/\nimages/captchas/\nimages/smileys/\nimages/member_photos/\nimages/signature_attachments/\nimages/pm_attachments/\n\n# For security do not publish the following files\nsystem/expressionengine/config/database.php\nsystem/expressionengine/config/config.php\n\n# Caches\nsized/\nthumbs/\n_thumbs/\n*/expressionengine/cache/*\n\n# End of https://www.toptal.com/developers/gitignore/api/expressionengine\n","extjs":"# Created by https://www.toptal.com/developers/gitignore/api/extjs\n# Edit at https://www.toptal.com/developers/gitignore?templates=extjs\n\n### ExtJs ###\n.architect\nbootstrap.css\nbootstrap.js\nbootstrap.json\nbootstrap.jsonp\nbuild/\nclassic.json\nclassic.jsonp\next/\nmodern.json\nmodern.jsonp\nresources/sass/.sass-cache/\nresources/.arch-internal-preview.css\n.arch-internal-preview.css\n\n# End of https://www.toptal.com/developers/gitignore/api/extjs\n","fancy":"# Created by https://www.toptal.com/developers/gitignore/api/fancy\n# Edit at https://www.toptal.com/developers/gitignore?templates=fancy\n\n### Fancy ###\n*.rbc\n*.fyc\n\n# End of https://www.toptal.com/developers/gitignore/api/fancy\n","fastlane":"# Created by https://www.toptal.com/developers/gitignore/api/fastlane\n# Edit at https://www.toptal.com/developers/gitignore?templates=fastlane\n\n### fastlane ###\n# fastlane - A streamlined workflow tool for Cocoa deployment\n#\n# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the\n# screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\n# fastlane specific\n**/fastlane/report.xml\n\n# deliver temporary files\n**/fastlane/Preview.html\n\n# snapshot generated screenshots\n**/fastlane/screenshots\n\n# scan temporary files\n**/fastlane/test_output\n\n# Fastlane.swift runner binary\n**/fastlane/FastlaneRunner\n\n# End of https://www.toptal.com/developers/gitignore/api/fastlane\n","finale":"# Created by https://www.toptal.com/developers/gitignore/api/finale\n# Edit at https://www.toptal.com/developers/gitignore?templates=finale\n\n### Finale ###\n*.bak\n*.db\n*.avi\n*.pdf\n*.ps\n*.mid\n*.midi\n*.mp3\n*.aif\n*.wav\n# Some versions of Finale have a bug and randomly save extra copies of\n# the music source as \" copy.mus\"\n*copy.mus\n\n# End of https://www.toptal.com/developers/gitignore/api/finale\n","firebase":"# Created by https://www.toptal.com/developers/gitignore/api/firebase\n# Edit at https://www.toptal.com/developers/gitignore?templates=firebase\n\n### Firebase ###\n.idea\n**/node_modules/*\n**/.firebaserc\n\n### Firebase Patch ###\n.runtimeconfig.json\n.firebase/\n\n# End of https://www.toptal.com/developers/gitignore/api/firebase\n","fish":"# Created by https://www.toptal.com/developers/gitignore/api/fish\n# Edit at https://www.toptal.com/developers/gitignore?templates=fish\n\n### Fish ###\nfishd.*\nfish_history\nfish_variables\nconfig.local.fish\n\n# End of https://www.toptal.com/developers/gitignore/api/fish\n","flashbuilder":"# Created by https://www.toptal.com/developers/gitignore/api/flashbuilder\n# Edit at https://www.toptal.com/developers/gitignore?templates=flashbuilder\n\n### FlashBuilder ###\n# Build and Release Folders\nbin/\nbin-debug/\nbin-release/\n.actionScriptProperties\n.flexConfig.xml\n\n# only for a non-library project\n.flexProperties\n\n# only for library project\n.flexLibProperties\n\n# Other files and folders\n.settings/\n.project/\n\n.FlexUnitSettings\n.externalToolBuilders\n.model\n\n\n# End of https://www.toptal.com/developers/gitignore/api/flashbuilder\n","flask":"# Created by https://www.toptal.com/developers/gitignore/api/flask\n# Edit at https://www.toptal.com/developers/gitignore?templates=flask\n\n### Flask ###\ninstance/*\n!instance/.gitignore\n.webassets-cache\n.env\n\n### Flask.Python Stack ###\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n# Usually these files are written by a python script from a template\n# before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n# For a library or package, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n# However, in case of collaboration, if having platform-specific dependencies or dependencies\n# having no cross-platform support, pipenv may install dependencies that don't work, or not\n# install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n# This is especially recommended for binary packages to ensure reproducibility, and is more\n# commonly ignored for libraries.\n# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n# in version control.\n# https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n# JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n# and can be added to the global gitignore or merged into this file. For a more nuclear\n# option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n# End of https://www.toptal.com/developers/gitignore/api/flask\n","flatpak":"# Created by https://www.toptal.com/developers/gitignore/api/flatpak\n# Edit at https://www.toptal.com/developers/gitignore?templates=flatpak\n\n### Flatpak ###\n.flatpak-builder\nbuild\nbuild-dir\nrepo\n\n# End of https://www.toptal.com/developers/gitignore/api/flatpak\n","flex":"# Created by https://www.toptal.com/developers/gitignore/api/flex\n# Edit at https://www.toptal.com/developers/gitignore?templates=flex\n\n### Flex ###\n\n# Build and Release Folders\nbin/\nbin-debug/\nbin-release/\n.actionScriptProperties\n\n# only for a non-library project\n.flexProperties\n\n# only for library project\n.flexLibProperties\n\n\n# Other files and folders\n.settings/\n.project/\n\n.FlexUnitSettings\n.externalToolBuilders\n.model\n\n# End of https://www.toptal.com/developers/gitignore/api/flex\n","flexbuilder":"# Created by https://www.toptal.com/developers/gitignore/api/flexbuilder\n# Edit at https://www.toptal.com/developers/gitignore?templates=flexbuilder\n\n### FlexBuilder ###\nbin/\nbin-debug/\nbin-release/\n\n# End of https://www.toptal.com/developers/gitignore/api/flexbuilder\n","floobits":"# Created by https://www.toptal.com/developers/gitignore/api/floobits\n# Edit at https://www.toptal.com/developers/gitignore?templates=floobits\n\n### floobits ###\n.floo\n\n# End of https://www.toptal.com/developers/gitignore/api/floobits\n","flutter":"# Created by https://www.toptal.com/developers/gitignore/api/flutter\n# Edit at https://www.toptal.com/developers/gitignore?templates=flutter\n\n### Flutter ###\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.fvm/flutter_sdk\n.packages\n.pub-cache/\n.pub/\nbuild/\ncoverage/\nlib/generated_plugin_registrant.dart\n# For library packages, don’t commit the pubspec.lock file.\n# Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies.\n# See https://dart.dev/guides/libraries/private-files#pubspeclock\n#pubspec.lock\n\n# Android related\n**/android/**/gradle-wrapper.jar\n**/android/.gradle\n**/android/captures/\n**/android/gradlew\n**/android/gradlew.bat\n**/android/key.properties\n**/android/local.properties\n**/android/**/GeneratedPluginRegistrant.java\n\n# iOS/XCode related\n**/ios/**/*.mode1v3\n**/ios/**/*.mode2v3\n**/ios/**/*.moved-aside\n**/ios/**/*.pbxuser\n**/ios/**/*.perspectivev3\n**/ios/**/*sync/\n**/ios/**/.sconsign.dblite\n**/ios/**/.tags*\n**/ios/**/.vagrant/\n**/ios/**/DerivedData/\n**/ios/**/Icon?\n**/ios/**/Pods/\n**/ios/**/.symlinks/\n**/ios/**/profile\n**/ios/**/xcuserdata\n**/ios/.generated/\n**/ios/Flutter/.last_build_id\n**/ios/Flutter/App.framework\n**/ios/Flutter/Flutter.framework\n**/ios/Flutter/Flutter.podspec\n**/ios/Flutter/Generated.xcconfig\n**/ios/Flutter/app.flx\n**/ios/Flutter/app.zip\n**/ios/Flutter/flutter_assets/\n**/ios/Flutter/flutter_export_environment.sh\n**/ios/ServiceDefinitions.json\n**/ios/Runner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!**/ios/**/default.mode1v3\n!**/ios/**/default.mode2v3\n!**/ios/**/default.pbxuser\n!**/ios/**/default.perspectivev3\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n\n# End of https://www.toptal.com/developers/gitignore/api/flutter\n","font":"# Created by https://www.toptal.com/developers/gitignore/api/font\n# Edit at https://www.toptal.com/developers/gitignore?templates=font\n\n### Font ###\n*.fnt\n*.fon\n*.otf\n*.ttf\n\n# End of https://www.toptal.com/developers/gitignore/api/font\n","fontforge":"# Created by https://www.toptal.com/developers/gitignore/api/fontforge\n# Edit at https://www.toptal.com/developers/gitignore?templates=fontforge\n\n### FontForge ###\n*.sfd-*\n*.sfd~\n\n# No Binaries\n\n*.ttf\n*.ttc\n*.otf\n*.woff\n*.pfb\n*.pfa\n*.pt3\n*.suit\n\n# End of https://www.toptal.com/developers/gitignore/api/fontforge\n","forcedotcom":"# Created by https://www.toptal.com/developers/gitignore/api/forcedotcom\n# Edit at https://www.toptal.com/developers/gitignore?templates=forcedotcom\n\n### ForceDotCom ###\n.project\n.settings\nsalesforce.schema\nReferenced Packages\n\n# End of https://www.toptal.com/developers/gitignore/api/forcedotcom\n","forgegradle":"# Created by https://www.toptal.com/developers/gitignore/api/forgegradle\n# Edit at https://www.toptal.com/developers/gitignore?templates=forgegradle\n\n### ForgeGradle ###\n# Minecraft client/server files\nrun/\n\n# End of https://www.toptal.com/developers/gitignore/api/forgegradle\n","fortran":"# Created by https://www.toptal.com/developers/gitignore/api/fortran\n# Edit at https://www.toptal.com/developers/gitignore?templates=fortran\n\n### Fortran ###\n# Prerequisites\n*.d\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n*.smod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n\n# End of https://www.toptal.com/developers/gitignore/api/fortran\n","freecad":"# Created by https://www.toptal.com/developers/gitignore/api/freecad\n# Edit at https://www.toptal.com/developers/gitignore?templates=freecad\n\n### FreeCad ###\n*.pyc\n*.fcstd1\n*.FCStd1\n*.fcstd2\n*.FCStd2\n\n# End of https://www.toptal.com/developers/gitignore/api/freecad\n","freepascal":"# Created by https://www.toptal.com/developers/gitignore/api/freepascal\n# Edit at https://www.toptal.com/developers/gitignore?templates=freepascal\n\n### FreePascal ###\n*.lps\n*.compiled\n*.[oa]\n*.ppu\n*.rst\n*.cgi\n*.exe\n*.log\n*.bak*\nfp.ini\nfp.cfg\nfp.dsk\n\n# End of https://www.toptal.com/developers/gitignore/api/freepascal\n","fsharp":"# Created by https://www.toptal.com/developers/gitignore/api/fsharp\n# Edit at https://www.toptal.com/developers/gitignore?templates=fsharp\n\n### fsharp ###\nlib/debug\nlib/release\nDebug\n*.suo\n*.user\nobj\nbin\n/build/\n*.exe\n!.paket/paket.bootstrapper.exe\n.ionide/\n.fake\n\n# End of https://www.toptal.com/developers/gitignore/api/fsharp\n","fuelphp":"# Created by https://www.toptal.com/developers/gitignore/api/fuelphp\n# Edit at https://www.toptal.com/developers/gitignore?templates=fuelphp\n\n### FuelPHP ###\n# the composer package lock file and install directory\n# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file\n# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file\n# /composer.lock\n/fuel/vendor\n\n# the fuelphp document\n/docs/\n\n# you may install these packages with `oil package`.\n# http://fuelphp.com/docs/packages/oil/package.html\n# /fuel/packages/auth/\n# /fuel/packages/email/\n# /fuel/packages/oil/\n# /fuel/packages/orm/\n# /fuel/packages/parser/\n\n# dynamically generated files\n/fuel/app/logs/*/*/*\n/fuel/app/cache/*/*\n/fuel/app/config/crypt.php\n\n# End of https://www.toptal.com/developers/gitignore/api/fuelphp\n","fusetools":"# Created by https://www.toptal.com/developers/gitignore/api/fusetools\n# Edit at https://www.toptal.com/developers/gitignore?templates=fusetools\n\n### FuseTools ###\n# Built and preview application files\nbuild\n\n# Scripts conversion file\n.uno\n\n# End of https://www.toptal.com/developers/gitignore/api/fusetools\n","games":"# Created by https://www.toptal.com/developers/gitignore/api/games\n# Edit at https://www.toptal.com/developers/gitignore?templates=games\n\n### Games ###\n*.gam\n*.nes\n*.rom\n*.sav\n\n# End of https://www.toptal.com/developers/gitignore/api/games\n","gatsby":"# Created by https://www.toptal.com/developers/gitignore/api/gatsby\n# Edit at https://www.toptal.com/developers/gitignore?templates=gatsby\n\n### Gatsby ###\nnode_modules/\n\n.cache/\npublic\n\nsrc/gatsby-types.d.ts\n\n# End of https://www.toptal.com/developers/gitignore/api/gatsby\n","gcov":"# Created by https://www.toptal.com/developers/gitignore/api/gcov\n# Edit at https://www.toptal.com/developers/gitignore?templates=gcov\n\n### Gcov ###\n# gcc coverage testing tool files\n\n*.gcno\n*.gcda\n*.gcov\n\n# End of https://www.toptal.com/developers/gitignore/api/gcov\n","genero4gl":"# Created by https://www.toptal.com/developers/gitignore/api/genero4gl\n# Edit at https://www.toptal.com/developers/gitignore?templates=genero4gl\n\n### Genero4GL ###\n# fglcomp compiled outputs\n# http://www.4js.com/online_documentation/fjs-fgl-manual-html/?path=fjs-fgl-manual#c_fgl_FileExtensions_001.html\n*.42m\n*.42f\n*.42s\n*.42r\n*.42x\n*.iem\n\n# Workspace view state (undocumented)\n4pwvs\n\n# Workspace database (undocumented)\n4pwdb\n\n# fglpc and fglform compiled outputs\n# http://publib.boulder.ibm.com/epubs/pdf/ct1vbna.pdf\n*.4go\n*.4gi\n*.4ge\n*.frm\n*.err\n*.4bl\n*.4bo\n*.ec\n*.c\n*.erc\n*.4ec\n*.pbr\n*.fbm\n\n# End of https://www.toptal.com/developers/gitignore/api/genero4gl\n","geth":"# Created by https://www.toptal.com/developers/gitignore/api/geth\n# Edit at https://www.toptal.com/developers/gitignore?templates=geth\n\n### Geth ###\n# .gitignore for Geth 0.4.16\n\n# caches\n/geth\n/keystore\nhistory\n\n# logs\n*.out\n*.log\n\n# End of https://www.toptal.com/developers/gitignore/api/geth\n","ggts":"# Created by https://www.toptal.com/developers/gitignore/api/ggts\n# Edit at https://www.toptal.com/developers/gitignore?templates=ggts\n\n### GGTS ###\n# .gitignore generated by: grails integrate-with --git\n# current using for Grails 2.4.4\n*.iws\n*Db.properties\n*Db.script\n.settings\nstacktrace.log\n/*.zip\n/plugin.xml\n/*.log\n/*DB.*\n/cobertura.ser\n/target/\n/target-eclipse/\n/out/\n/.link_to_grails_plugins/\n/web-app/plugins\n/web-app/WEB-INF/classes\n\n# End of https://www.toptal.com/developers/gitignore/api/ggts\n","gis":"# Created by https://www.toptal.com/developers/gitignore/api/gis\n# Edit at https://www.toptal.com/developers/gitignore?templates=gis\n\n### GIS ###\n*.gpx\n*.kml\n\n# End of https://www.toptal.com/developers/gitignore/api/gis\n","git":"# Created by https://www.toptal.com/developers/gitignore/api/git\n# Edit at https://www.toptal.com/developers/gitignore?templates=git\n\n### Git ###\n# Created by git for backups. To disable backups in Git:\n# $ git config --global mergetool.keepBackup false\n*.orig\n\n# Created by git when using merge tools for conflicts\n*.BACKUP.*\n*.BASE.*\n*.LOCAL.*\n*.REMOTE.*\n*_BACKUP_*.txt\n*_BASE_*.txt\n*_LOCAL_*.txt\n*_REMOTE_*.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/git\n","gitbook":"# Created by https://www.toptal.com/developers/gitignore/api/gitbook\n# Edit at https://www.toptal.com/developers/gitignore?templates=gitbook\n\n### GitBook ###\n# Node rules:\n## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n## Dependency directory\n## Commenting this out is preferred by some people, see\n## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# Book build output\n_book\n\n# eBook build output\n*.epub\n*.mobi\n*.pdf\n\n# End of https://www.toptal.com/developers/gitignore/api/gitbook\n","go":"# Created by https://www.toptal.com/developers/gitignore/api/go\n# Edit at https://www.toptal.com/developers/gitignore?templates=go\n\n### Go ###\n# If you prefer the allow list template instead of the deny list, see community template:\n# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore\n#\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\n# Go workspace file\ngo.work\n\n# End of https://www.toptal.com/developers/gitignore/api/go\n","godot":"# Created by https://www.toptal.com/developers/gitignore/api/godot\n# Edit at https://www.toptal.com/developers/gitignore?templates=godot\n\n### Godot ###\n# Godot 4+ specific ignores\n.godot/\n\n# Godot-specific ignores\n.import/\nexport.cfg\nexport_presets.cfg\n\n# Imported translations (automatically generated from CSV files)\n*.translation\n\n# Mono-specific ignores\n.mono/\ndata_*/\nmono_crash.*.json\n\n# End of https://www.toptal.com/developers/gitignore/api/godot\n","goland":"# Created by https://www.toptal.com/developers/gitignore/api/goland\n# Edit at https://www.toptal.com/developers/gitignore?templates=goland\n\n### GoLand ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### GoLand Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/goland\n","goland+all":"# Created by https://www.toptal.com/developers/gitignore/api/goland+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all\n\n### GoLand+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### GoLand+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/goland+all\n","goland+iml":"# Created by https://www.toptal.com/developers/gitignore/api/goland+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=goland+iml\n\n### GoLand+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### GoLand+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/goland+iml\n","goodsync":"# Created by https://www.toptal.com/developers/gitignore/api/goodsync\n# Edit at https://www.toptal.com/developers/gitignore?templates=goodsync\n\n### GoodSync ###\n_gsdata_\n\n# End of https://www.toptal.com/developers/gitignore/api/goodsync\n","gpg":"# Created by https://www.toptal.com/developers/gitignore/api/gpg\n# Edit at https://www.toptal.com/developers/gitignore?templates=gpg\n\n### GPG ###\nsecring.*\n\n\n# End of https://www.toptal.com/developers/gitignore/api/gpg\n","gradle":"# Created by https://www.toptal.com/developers/gitignore/api/gradle\n# Edit at https://www.toptal.com/developers/gitignore?templates=gradle\n\n### Gradle ###\n.gradle\n**/build/\n!src/**/build/\n\n# Ignore Gradle GUI config\ngradle-app.setting\n\n# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)\n!gradle-wrapper.jar\n\n# Avoid ignore Gradle wrappper properties\n!gradle-wrapper.properties\n\n# Cache of project\n.gradletasknamecache\n\n# Eclipse Gradle plugin generated files\n# Eclipse Core\n.project\n# JDT-specific (Eclipse Java Development Tools)\n.classpath\n\n### Gradle Patch ###\n# Java heap dump\n*.hprof\n\n# End of https://www.toptal.com/developers/gitignore/api/gradle\n","grails":"# Created by https://www.toptal.com/developers/gitignore/api/grails\n# Edit at https://www.toptal.com/developers/gitignore?templates=grails\n\n### Grails ###\n# .gitignore for Grails 1.2 and 1.3\n# Although this should work for most versions of grails, it is\n# suggested that you use the \"grails integrate-with --git\" command\n# to generate your .gitignore file.\n\n# web application files\n/web-app/WEB-INF/classes\n\n# default HSQL database files for production mode\n/prodDb.*\n\n# general HSQL database files\n*Db.properties\n*Db.script\n\n# logs\n/stacktrace.log\n/test/reports\n/logs\n\n# project release file\n/*.war\n\n# plugin release files\n/*.zip\n/plugin.xml\n\n# older plugin install locations\n/plugins\n/web-app/plugins\n\n# \"temporary\" build files\n/target\n\n# End of https://www.toptal.com/developers/gitignore/api/grails\n","greenfoot":"# Created by https://www.toptal.com/developers/gitignore/api/greenfoot\n# Edit at https://www.toptal.com/developers/gitignore?templates=greenfoot\n\n### greenfoot ###\n# Greenfoot: A BlueJ-based game IDE for programming beginners. http://bluej.org\n\n# Editor preferences for a class etc, see http://lists.bluej.org/pipermail/bluej-discuss/2003-May/002351.html\n*.ctxt\n\n# Project file. Continously checking it in destroys the project ocasionally (while merging etc.) because it changes completely at every compile. Instead, make a copy of the file after the first runtime and name it e.g. `project.example.greenfoot`. See http://bugs.bluej.org/greenfoot/ticket/409\n\nproject.greenfoot\n\n# End of https://www.toptal.com/developers/gitignore/api/greenfoot\n","groovy":"# Created by https://www.toptal.com/developers/gitignore/api/groovy\n# Edit at https://www.toptal.com/developers/gitignore?templates=groovy\n\n### Groovy ###\n# .gitignore created from Groovy contributors in https://github.com/apache/groovy/blob/master/.gitignore\n\nuser.gradle\n.gradle/\ntarget/\nbuild/\nout/\n\n*.DS_Store\n*.class\n*.swp\n*~\n\n.idea\n*.iml\n*.ipr\n*.iws\n.shelf\n\n.settings/\n.classpath\n.project\nbin/\n\n\n# End of https://www.toptal.com/developers/gitignore/api/groovy\n","grunt":"# Created by https://www.toptal.com/developers/gitignore/api/grunt\n# Edit at https://www.toptal.com/developers/gitignore?templates=grunt\n\n### grunt ###\n# Grunt usually compiles files inside this directory\ndist/\n\n# Grunt usually preprocesses files such as coffeescript, compass... inside the .tmp directory\n.tmp/\n\n# End of https://www.toptal.com/developers/gitignore/api/grunt\n","gwt":"# Created by https://www.toptal.com/developers/gitignore/api/gwt\n# Edit at https://www.toptal.com/developers/gitignore?templates=gwt\n\n### GWT ###\n*.class\n\n# Package Files #\n*.jar\n*.war\n\n# gwt caches and compiled units #\nwar/gwt_bree/\ngwt-unitCache/\n\n# boilerplate generated classes #\n.apt_generated/\n\n# more caches and things from deploy #\nwar/WEB-INF/deploy/\nwar/WEB-INF/classes/\n\n#compilation logs\n.gwt/\n\n#gwt junit compilation files\nwww-test/\n\n#old GWT (1.5) created this dir\n.gwt-tmp/\n\n# End of https://www.toptal.com/developers/gitignore/api/gwt\n","haskell":"# Created by https://www.toptal.com/developers/gitignore/api/haskell\n# Edit at https://www.toptal.com/developers/gitignore?templates=haskell\n\n### Haskell ###\ndist\ndist-*\ncabal-dev\n*.o\n*.hi\n*.hie\n*.chi\n*.chs.h\n*.dyn_o\n*.dyn_hi\n.hpc\n.hsenv\n.cabal-sandbox/\ncabal.sandbox.config\n*.prof\n*.aux\n*.hp\n*.eventlog\n.stack-work/\ncabal.project.local\ncabal.project.local~\n.HTF/\n.ghc.environment.*\n\n# End of https://www.toptal.com/developers/gitignore/api/haskell\n","helm":"# Created by https://www.toptal.com/developers/gitignore/api/helm\n# Edit at https://www.toptal.com/developers/gitignore?templates=helm\n\n### Helm ###\n# Chart dependencies\n**/charts/*.tgz\n\n# End of https://www.toptal.com/developers/gitignore/api/helm\n","hexo":"# Created by https://www.toptal.com/developers/gitignore/api/hexo\n# Edit at https://www.toptal.com/developers/gitignore?templates=hexo\n\n### Hexo ###\ndb.json\npublic/\n.deploy_git/\n\n# End of https://www.toptal.com/developers/gitignore/api/hexo\n","hol":"# Created by https://www.toptal.com/developers/gitignore/api/hol\n# Edit at https://www.toptal.com/developers/gitignore?templates=hol\n\n### HOL ###\n*Script\n\n# Holmake generated files\n*Theory.sig\n*Theory.sml\n*.uo\n*.ui\n\n# Holmake auxiliary files\n.hollogs\n.HOLMK\n\n# End of https://www.toptal.com/developers/gitignore/api/hol\n","homeassistant":"# Created by https://www.toptal.com/developers/gitignore/api/homeassistant\n# Edit at https://www.toptal.com/developers/gitignore?templates=homeassistant\n\n### HomeAssistant ###\n# Files with personal details\n*.crt\n*.csr\n*.key\n.cloud\n.google.token\n.HA_VERSION\n.storage\n.uuid\nicloud/\ngoogle_calendars.yaml\nharmony_media_room.conf\nhome-assistant.db\nhome-assistant_v2.db\nhome-assistant_v2.db-*\nhtml5_push_registrations.conf\nimage\nip_bans.yaml\nknown_devices.yaml\nphue.conf\nplex.conf\npyozw.sqlite\nsecrets.yaml\ntradfri.conf\nwebostv.conf\n\n# Temporary files\n*.db-journal\n*.pid\ntts\n\n# automatically downloaded dependencies\ndeps\nlib\n\n# Log files\nhome-assistant.log\nhome-assistant.log.*\nozw_log.txt\n\n# Development files\ncustom_components\n\n# End of https://www.toptal.com/developers/gitignore/api/homeassistant\n","homebrew":"# Created by https://www.toptal.com/developers/gitignore/api/homebrew\n# Edit at https://www.toptal.com/developers/gitignore?templates=homebrew\n\n### Homebrew ###\nBrewfile.lock.json\n\n# End of https://www.toptal.com/developers/gitignore/api/homebrew\n","hsp":"# Created by https://www.toptal.com/developers/gitignore/api/hsp\n# Edit at https://www.toptal.com/developers/gitignore?templates=hsp\n\n### HSP ###\n# Object files\nhsptmp*\npackfile\nobj\n*.ax\n\n# Binaries\n*.exe\n*.dpm\n\n# End of https://www.toptal.com/developers/gitignore/api/hsp\n","hugo":"# Created by https://www.toptal.com/developers/gitignore/api/hugo\n# Edit at https://www.toptal.com/developers/gitignore?templates=hugo\n\n### Hugo ###\n# Generated files by hugo\n/public/\n/resources/_gen/\n/assets/jsconfig.json\nhugo_stats.json\n\n# Executable may be added to repository\nhugo.exe\nhugo.darwin\nhugo.linux\n\n# Temporary lock file while building\n/.hugo_build.lock\n\n# End of https://www.toptal.com/developers/gitignore/api/hugo\n","hyperledgercomposer":"# Created by https://www.toptal.com/developers/gitignore/api/hyperledgercomposer\n# Edit at https://www.toptal.com/developers/gitignore?templates=hyperledgercomposer\n\n### HyperledgerComposer ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n# Output of Hyperledger 'composer' tools\n*.bna\n*.card\n\n# End of https://www.toptal.com/developers/gitignore/api/hyperledgercomposer\n","iar":"# Created by https://www.toptal.com/developers/gitignore/api/iar\n# Edit at https://www.toptal.com/developers/gitignore?templates=iar\n\n### IAR ###\n# Compiled binaries\n*.o\n*.bin\n*.elf\n*.hex\n*.map\n*.out\n*.obj\n\n# Trash\n*.bak\nthumbs.db\n*.~*\n\n# IAR Settings\n**/settings/*.crun\n**/settings/*.dbgdt\n**/settings/*.cspy\n**/settings/*.cspy.*\n**/settings/*.xcl\n**/settings/*.dni\n**/settings/*.wsdt\n**/settings/*.wspos\n\n# IAR Debug Exe\n**/Exe/*.sim\n\n# IAR Debug Obj\n**/Obj/*.pbd\n**/Obj/*.pbd.*\n**/Obj/*.pbi\n**/Obj/*.pbi.*\n\n# IAR project \"Debug\" directory\nDebug/\n\n# IAR project \"Release\" directory\nRelease/\n\n# IAR project settings directory\nsettings/\n\n# IAR backup files\nBackup*\n\n# IAR .dep files\n*.dep\n\n# End of https://www.toptal.com/developers/gitignore/api/iar\n","iar_ewarm":"# Created by https://www.toptal.com/developers/gitignore/api/iar_ewarm\n# Edit at https://www.toptal.com/developers/gitignore?templates=iar_ewarm\n\n### IAR_EWARM ###\n# gitignore template for the IAR EWARM\n# website: https://www.iar.com/knowledge/support/technical-notes/ide/which-files-should-be-version-controlled/\n\n# Some tools will put the EWARM files\n# under a subdirectory with the same name\n# as the configuration.\n# Example\n# EWARM/Config1/Obj /List /Exe\n# EWARM/Config2/Obj /List /Exe\nEWARM/**/Obj\nEWARM/**/List\nEWARM/**/Exe\n\n# Autogenerated project files\n*.dep\n*.ewt\n\n# Autogenerated folder for debugger\nEWARM/settings\n\n# End of https://www.toptal.com/developers/gitignore/api/iar_ewarm\n","iarembeddedworkbench":"# Created by https://www.toptal.com/developers/gitignore/api/iarembeddedworkbench\n# Edit at https://www.toptal.com/developers/gitignore?templates=iarembeddedworkbench\n\n### IAREmbeddedWorkBench ###\n# IAR C-STAT and C-RUN\n# Comment this out if you use C-Stat or C-Run to compile/analyze your project\n*.ewt\n\n# IAR Debugger files\n*.ewd\n\n# IAR Workspace files\n*.eww\n\n# IAR Settings\n**/settings/*.crun\n**/settings/*.dbgdt\n**/settings/*.cspy\n**/settings/*.cspy.*\n**/settings/*.xcl\n**/settings/*.dni\n**/settings/*.wsdt\n**/settings/*.wspos\n\n# IAR Debug Exe\n**/Debug/Exe/\n\n# IAR Debug List\n**/Debug/List\n\n# IAR Debug Obj\n**/Obj/*.pbd\n**/Obj/*.pbd.*\n**/Obj/*.pbi\n**/Obj/*.pbi.*\n\n# Log files\n**.log\n\n# IAR backup files\nBackup*\n\n# IAR .dep files\n*.dep\n\n# Compiled Binaries\n*.bin\n*.elf\n*.hex\n*.map\n\n# Trash files\n*.bak\n\n# End of https://www.toptal.com/developers/gitignore/api/iarembeddedworkbench\n","idapro":"# Created by https://www.toptal.com/developers/gitignore/api/idapro\n# Edit at https://www.toptal.com/developers/gitignore?templates=idapro\n\n### IDAPro ###\n# gitginore template for IDA Pro\n# website: https://www.hex-rays.com/index.shtml\n\n# IDA Pro Runtime temporary file\n*.id0\n*.id1\n*.id2\n*.nam\n*.til\n\n# IDA Pro 64 packaged data\n# *.i64\n# IDA Pro 32 packaged data\n# *.idb\n\n# End of https://www.toptal.com/developers/gitignore/api/idapro\n","idris":"# Created by https://www.toptal.com/developers/gitignore/api/idris\n# Edit at https://www.toptal.com/developers/gitignore?templates=idris\n\n### Idris ###\n# Idris 2\n*.ttc\n*.ttm\n\n# Idris 1\n*.ibc\n*.o\n\n# End of https://www.toptal.com/developers/gitignore/api/idris\n","igorpro":"# Created by https://www.toptal.com/developers/gitignore/api/igorpro\n# Edit at https://www.toptal.com/developers/gitignore?templates=igorpro\n\n### IGORPro ###\n# Avoid including Experiment files: they can be created and edited locally to test the ipf files\n*.pxp\n*.pxt\n*.uxp\n*.uxt\n\n# End of https://www.toptal.com/developers/gitignore/api/igorpro\n","images":"# Created by https://www.toptal.com/developers/gitignore/api/images\n# Edit at https://www.toptal.com/developers/gitignore?templates=images\n\n### Images ###\n# JPEG\n*.jpg\n*.jpeg\n*.jpe\n*.jif\n*.jfif\n*.jfi\n\n# JPEG 2000\n*.jp2\n*.j2k\n*.jpf\n*.jpx\n*.jpm\n*.mj2\n\n# JPEG XR\n*.jxr\n*.hdp\n*.wdp\n\n# Graphics Interchange Format\n*.gif\n\n# RAW\n*.raw\n\n# Web P\n*.webp\n\n# Portable Network Graphics\n*.png\n\n# Animated Portable Network Graphics\n*.apng\n\n# Multiple-image Network Graphics\n*.mng\n\n# Tagged Image File Format\n*.tiff\n*.tif\n\n# Scalable Vector Graphics\n*.svg\n*.svgz\n\n# Portable Document Format\n*.pdf\n\n# X BitMap\n*.xbm\n\n# BMP\n*.bmp\n*.dib\n\n# ICO\n*.ico\n\n# 3D Images\n*.3dm\n*.max\n\n# End of https://www.toptal.com/developers/gitignore/api/images\n","infer":"# Created by https://www.toptal.com/developers/gitignore/api/infer\n# Edit at https://www.toptal.com/developers/gitignore?templates=infer\n\n### infer ###\n# infer- http://fbinfer.com/\ninfer-out\n\n# End of https://www.toptal.com/developers/gitignore/api/infer\n","inforcms":"# Created by https://www.toptal.com/developers/gitignore/api/inforcms\n# Edit at https://www.toptal.com/developers/gitignore?templates=inforcms\n\n### InforCMS ###\n# gitignore template for InforCRM (formerly SalesLogix)\n# website: https://www.infor.com/product-summary/cx/infor-crm/\n#\n# Recommended: VisualStudio.gitignore\n\n# Ignore model files that are auto-generated\nModelIndex.xml\nExportedFiles.xml\n\n# Ignore deployment files\n[Mm]odel/[Dd]eployment\n\n# Force include portal SupportFiles\n!Model/Portal/*/SupportFiles/[Bb]in/\n!Model/Portal/PortalTemplates/*/SupportFiles/[Bb]in\n\n# End of https://www.toptal.com/developers/gitignore/api/inforcms\n","inforcrm":"# Created by https://www.toptal.com/developers/gitignore/api/inforcrm\n# Edit at https://www.toptal.com/developers/gitignore?templates=inforcrm\n\n### InforCRM ###\n#########################################\n# .gitignore for Infor CRM (SalesLogix)\n\n# Ignore model files that are auto-generated\nModelIndex.xml\nExportedFiles.xml\n\n# Ignore deployment files\n[Mm]odel/[Dd]eployment\n\n# Ignore Visual Studio/IDE user/cache/build/tooling files generated by build process\n*.user\n*.aps\n*.pch\n*.vspscc\n*_i.c\n*_p.c\n*.ncb\n*.suo\n*.bak\n*.cache\n*.ilk\n*.log\n[Bb]in\n[Dd]ebug*/\n*.sbr\n.vs/\nobj/\n[Rr]elease*/\n_ReSharper*/\n\n# Force include portal SupportFiles\n!Model/Portal/*/SupportFiles/[Bb]in/\n!Model/Portal/PortalTemplates/*/SupportFiles/[Bb]in\n\n# End of https://www.toptal.com/developers/gitignore/api/inforcrm\n","intellij":"# Created by https://www.toptal.com/developers/gitignore/api/intellij\n# Edit at https://www.toptal.com/developers/gitignore?templates=intellij\n\n### Intellij ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### Intellij Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/intellij\n","intellij+all":"# Created by https://www.toptal.com/developers/gitignore/api/intellij+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all\n\n### Intellij+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### Intellij+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/intellij+all\n","intellij+iml":"# Created by https://www.toptal.com/developers/gitignore/api/intellij+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+iml\n\n### Intellij+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### Intellij+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/intellij+iml\n","ionic3":"# Created by https://www.toptal.com/developers/gitignore/api/ionic3\n# Edit at https://www.toptal.com/developers/gitignore?templates=ionic3\n\n### Ionic3 ###\n# Log an tmp data\n*~\n*.sw[mnpcod]\n*.log\n*.tmp\n*.tmp.*\nlog.txt\n\n# NPM (Node Package Manager) files\nnpm-debug.log*\nnode_modules/\ntmp/\ntemp/\n\n# Preprocessors cache files\n.sourcemaps/\n.sass-cache/\n.tmp/\nhooks/\n\n# Ionic 3 plugins files\nplatforms/\nplugins/\nplugins/android.json\nplugins/ios.json\nwww/\n\n# Trash data\n$RECYCLE.BIN/\n.DS_Store\nThumbs.db\nUserInterfaceState.xcuserstate\n\n# End of https://www.toptal.com/developers/gitignore/api/ionic3\n","jabref":"# Created by https://www.toptal.com/developers/gitignore/api/jabref\n# Edit at https://www.toptal.com/developers/gitignore?templates=jabref\n\n### JabRef ###\n# JabRef - https://www.jabref.org/\n*.bak\n*.sav\n\n# End of https://www.toptal.com/developers/gitignore/api/jabref\n","janet":"# Created by https://www.toptal.com/developers/gitignore/api/janet\n# Edit at https://www.toptal.com/developers/gitignore?templates=janet\n\n### Janet ###\n# Compiled binaries\nbuild/\n\n# Dependency directory\njpm_tree/\n\n# End of https://www.toptal.com/developers/gitignore/api/janet\n","java":"# Created by https://www.toptal.com/developers/gitignore/api/java\n# Edit at https://www.toptal.com/developers/gitignore?templates=java\n\n### Java ###\n# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.nar\n*.ear\n*.zip\n*.tar.gz\n*.rar\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\nreplay_pid*\n\n# End of https://www.toptal.com/developers/gitignore/api/java\n","jboss":"# Created by https://www.toptal.com/developers/gitignore/api/jboss\n# Edit at https://www.toptal.com/developers/gitignore?templates=jboss\n\n### JBoss ###\njboss/server/all/deploy/project.ext\njboss/server/default/deploy/project.ext\njboss/server/minimal/deploy/project.ext\njboss/server/all/log/*.log\njboss/server/all/tmp/**/*\njboss/server/all/data/**/*\njboss/server/all/work/**/*\njboss/server/default/log/*.log\njboss/server/default/tmp/**/*\njboss/server/default/data/**/*\njboss/server/default/work/**/*\njboss/server/minimal/log/*.log\njboss/server/minimal/tmp/**/*\njboss/server/minimal/data/**/*\njboss/server/minimal/work/**/*\n\n# deployed package files #\n\n*.DEPLOYED\n\n# End of https://www.toptal.com/developers/gitignore/api/jboss\n","jboss-4-2-3-ga":"# Created by https://www.toptal.com/developers/gitignore/api/jboss-4-2-3-ga\n# Edit at https://www.toptal.com/developers/gitignore?templates=jboss-4-2-3-ga\n\n### JBoss-4-2-3-GA ###\n/server/all/data\n/server/all/log\n/server/all/tmp\n/server/all/work\n/server/default/data\n/server/default/log\n/server/default/tmp\n/server/default/work\n/server/minimal/data\n/server/minimal/log\n/server/minimal/tmp\n/server/minimal/work\n/server/default/deploy/*.xml.failed\n/server/default/deploy/*.war.failed\n\n/**\n* For ignore * .war, * .war.failed and * .xml.failed if you use other directories of jboss.\n* Do it similar configuration used for\n* /server/default/deploy\n*\n*/\n\n# End of https://www.toptal.com/developers/gitignore/api/jboss-4-2-3-ga\n","jboss-6-x":"# Created by https://www.toptal.com/developers/gitignore/api/jboss-6-x\n# Edit at https://www.toptal.com/developers/gitignore?templates=jboss-6-x\n\n### JBoss-6-x ###\n/server/all/data\n/server/all/log\n/server/all/tmp\n/server/all/work\n/server/default/data\n/server/default/log\n/server/default/tmp\n/server/default/work\n/server/minimal/data\n/server/minimal/log\n/server/minimal/tmp\n/server/minimal/work\n/server/jbossweb-standalone/data\n/server/jbossweb-standalone/log\n/server/jbossweb-standalone/tmp\n/server/jbossweb-standalone/work\n/server/standard/data\n/server/standard/log\n/server/standard/tmp\n/server/standard/work\n/server/default/deploy/*.jar.failed\n/server/default/deploy/*.jar.dodeploy\n/server/default/deploy/*.xml.failed\n/server/default/deploy/*.xml.dodeploy\n/server/default/deploy/*.war.failed\n/server/default/deploy/*.war.dodeploy\n\n\n# Important\n# Case your directory for example\n# /server/minimal/lib be empty.\n# The git will not submit this directory for you repository.\n# In this case is necessary create one archive, for example .gitignore empty in this directory.\n# Case you run your server without this directory the server will faild when you start service.\n\n# End of https://www.toptal.com/developers/gitignore/api/jboss-6-x\n","jboss4":"# Created by https://www.toptal.com/developers/gitignore/api/jboss4\n# Edit at https://www.toptal.com/developers/gitignore?templates=jboss4\n\n### JBoss4 ###\n# gitignore for JBoss v4 projects\n\n/server/all/data\n/server/all/log\n/server/all/tmp\n/server/all/work\n/server/default/data\n/server/default/log\n/server/default/tmp\n/server/default/work\n/server/minimal/data\n/server/minimal/log\n/server/minimal/tmp\n/server/minimal/work\n\n# Note:\n# there may be other directories that contain *.xml.failed or *.war.failed files\n/server/default/deploy/*.xml.failed\n/server/default/deploy/*.war.failed\n\n# End of https://www.toptal.com/developers/gitignore/api/jboss4\n","jboss6":"# Created by https://www.toptal.com/developers/gitignore/api/jboss6\n# Edit at https://www.toptal.com/developers/gitignore?templates=jboss6\n\n### JBoss6 ###\n# gitignore for JBoss v6 projects\n#\n# Note: to ensure empty directories remain part of the repository, like\n# `/server/minimal/lib`, you should add an empty `.gitignore` or `.gitkeep` file\n# to the directory - otherwise you may have issues when starting the service.\n\n/server/all/data\n/server/all/log\n/server/all/tmp\n/server/all/work\n/server/default/data\n/server/default/log\n/server/default/tmp\n/server/default/work\n/server/minimal/data\n/server/minimal/log\n/server/minimal/tmp\n/server/minimal/work\n/server/jbossweb-standalone/data\n/server/jbossweb-standalone/log\n/server/jbossweb-standalone/tmp\n/server/jbossweb-standalone/work\n/server/standard/data\n/server/standard/log\n/server/standard/tmp\n/server/standard/work\n/server/default/deploy/*.jar.failed\n/server/default/deploy/*.jar.dodeploy\n/server/default/deploy/*.xml.failed\n/server/default/deploy/*.xml.dodeploy\n/server/default/deploy/*.war.failed\n/server/default/deploy/*.war.dodeploy\n\n\n# End of https://www.toptal.com/developers/gitignore/api/jboss6\n","jdeveloper":"# Created by https://www.toptal.com/developers/gitignore/api/jdeveloper\n# Edit at https://www.toptal.com/developers/gitignore?templates=jdeveloper\n\n### JDeveloper ###\n# default application storage directory used by the IDE Performance Cache feature\n.data/\n\n# used for ADF styles caching\ntemp/\n\n# default output directories\nclasses/\ndeploy/\njavadoc/\n\n# lock file, a part of Oracle Credential Store Framework\ncwallet.sso.lck\n\n# End of https://www.toptal.com/developers/gitignore/api/jdeveloper\n","jekyll":"# Created by https://www.toptal.com/developers/gitignore/api/jekyll\n# Edit at https://www.toptal.com/developers/gitignore?templates=jekyll\n\n### Jekyll ###\n_site/\n.sass-cache/\n.jekyll-cache/\n.jekyll-metadata\n# Ignore folders generated by Bundler\n.bundle/\nvendor/\n\n# End of https://www.toptal.com/developers/gitignore/api/jekyll\n","jenv":"# Created by https://www.toptal.com/developers/gitignore/api/jenv\n# Edit at https://www.toptal.com/developers/gitignore?templates=jenv\n\n### JEnv ###\n# JEnv local Java version configuration file\n.java-version\n\n# Used by previous versions of JEnv\n.jenv-version\n\n# End of https://www.toptal.com/developers/gitignore/api/jenv\n","jetbrains":"# Created by https://www.toptal.com/developers/gitignore/api/jetbrains\n# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains\n\n### JetBrains ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### JetBrains Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/jetbrains\n","jetbrains+all":"# Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all\n\n### JetBrains+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### JetBrains+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/jetbrains+all\n","jetbrains+iml":"# Created by https://www.toptal.com/developers/gitignore/api/jetbrains+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+iml\n\n### JetBrains+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### JetBrains+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/jetbrains+iml\n","jgiven":"# Created by https://www.toptal.com/developers/gitignore/api/jgiven\n# Edit at https://www.toptal.com/developers/gitignore?templates=jgiven\n\n### JGiven ###\njgiven-reports/\n\n# End of https://www.toptal.com/developers/gitignore/api/jgiven\n","jigsaw":"# Created by https://www.toptal.com/developers/gitignore/api/jigsaw\n# Edit at https://www.toptal.com/developers/gitignore?templates=jigsaw\n\n### Jigsaw ###\n# Jigsaw Static Site Generator\n\n# Ignore build folder\n/build_local/\n/build_staging/\n/build_production/\n\n# Ignore node dependencies resolved by npm/yarn\n/node_modules/\n\n# Ignore php dependecies resolved by composer\n/vendor/\n\n# Optionally ignore lock files\n# composer.lock\n# yarn.lock\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# End of https://www.toptal.com/developers/gitignore/api/jigsaw\n","jmeter":"# Created by https://www.toptal.com/developers/gitignore/api/jmeter\n# Edit at https://www.toptal.com/developers/gitignore?templates=jmeter\n\n### JMeter ###\n# JMeter common ignore files\n# http://jmeter.apache.org/\n\n# Ignore Summary/Aggregrate reports\n*.jtl\n\n# Ignore log files\n*.log\n\n# Ignore customized user.properties\nuser.properties\n\n# End of https://www.toptal.com/developers/gitignore/api/jmeter\n","joe":"# Created by https://www.toptal.com/developers/gitignore/api/joe\n# Edit at https://www.toptal.com/developers/gitignore?templates=joe\n\n### Joe ###\n# File backups\n*~\n# Gets created when joe crashes\nDEADJOE\n\n# End of https://www.toptal.com/developers/gitignore/api/joe\n","joomla":"# Created by https://www.toptal.com/developers/gitignore/api/joomla\n# Edit at https://www.toptal.com/developers/gitignore?templates=joomla\n\n### Joomla ###\n/.htaccess\n/administrator/cache/*\n/administrator/components/com_actionlogs/*\n/administrator/components/com_admin/*\n/administrator/components/com_ajax/*\n/administrator/components/com_associations/*\n/administrator/components/com_banners/*\n/administrator/components/com_cache/*\n/administrator/components/com_categories/*\n/administrator/components/com_checkin/*\n/administrator/components/com_config/*\n/administrator/components/com_contact/*\n/administrator/components/com_content/*\n/administrator/components/com_contenthistory/*\n/administrator/components/com_cpanel/*\n/administrator/components/com_fields/*\n/administrator/components/com_finder/*\n/administrator/components/com_installer/*\n/administrator/components/com_joomlaupdate/*\n/administrator/components/com_languages/*\n/administrator/components/com_login/*\n/administrator/components/com_media/*\n/administrator/components/com_menus/*\n/administrator/components/com_messages/*\n/administrator/components/com_modules/*\n/administrator/components/com_newsfeeds/*\n/administrator/components/com_plugins/*\n/administrator/components/com_postinstall/*\n/administrator/components/com_privacy/*\n/administrator/components/com_redirect/*\n/administrator/components/com_search/*\n/administrator/components/com_tags/*\n/administrator/components/com_templates/*\n/administrator/components/com_users/*\n/administrator/help/*\n/administrator/includes/*\n/administrator/index.php\n/administrator/language/en-GB/en-GB.com_actionlogs.ini\n/administrator/language/en-GB/en-GB.com_actionlogs.sys.ini\n/administrator/language/en-GB/en-GB.com_admin.ini\n/administrator/language/en-GB/en-GB.com_admin.sys.ini\n/administrator/language/en-GB/en-GB.com_ajax.ini\n/administrator/language/en-GB/en-GB.com_ajax.sys.ini\n/administrator/language/en-GB/en-GB.com_associations.ini\n/administrator/language/en-GB/en-GB.com_associations.sys.ini\n/administrator/language/en-GB/en-GB.com_banners.ini\n/administrator/language/en-GB/en-GB.com_banners.sys.ini\n/administrator/language/en-GB/en-GB.com_cache.ini\n/administrator/language/en-GB/en-GB.com_cache.sys.ini\n/administrator/language/en-GB/en-GB.com_categories.ini\n/administrator/language/en-GB/en-GB.com_categories.sys.ini\n/administrator/language/en-GB/en-GB.com_checkin.ini\n/administrator/language/en-GB/en-GB.com_checkin.sys.ini\n/administrator/language/en-GB/en-GB.com_config.ini\n/administrator/language/en-GB/en-GB.com_config.sys.ini\n/administrator/language/en-GB/en-GB.com_contact.ini\n/administrator/language/en-GB/en-GB.com_contact.sys.ini\n/administrator/language/en-GB/en-GB.com_content.ini\n/administrator/language/en-GB/en-GB.com_content.sys.ini\n/administrator/language/en-GB/en-GB.com_contenthistory.ini\n/administrator/language/en-GB/en-GB.com_contenthistory.sys.ini\n/administrator/language/en-GB/en-GB.com_cpanel.ini\n/administrator/language/en-GB/en-GB.com_cpanel.sys.ini\n/administrator/language/en-GB/en-GB.com_fields.ini\n/administrator/language/en-GB/en-GB.com_fields.sys.ini\n/administrator/language/en-GB/en-GB.com_finder.ini\n/administrator/language/en-GB/en-GB.com_finder.sys.ini\n/administrator/language/en-GB/en-GB.com_installer.ini\n/administrator/language/en-GB/en-GB.com_installer.sys.ini\n/administrator/language/en-GB/en-GB.com_joomlaupdate.ini\n/administrator/language/en-GB/en-GB.com_joomlaupdate.sys.ini\n/administrator/language/en-GB/en-GB.com_languages.ini\n/administrator/language/en-GB/en-GB.com_languages.sys.ini\n/administrator/language/en-GB/en-GB.com_login.ini\n/administrator/language/en-GB/en-GB.com_login.sys.ini\n/administrator/language/en-GB/en-GB.com_mailto.sys.ini\n/administrator/language/en-GB/en-GB.com_media.ini\n/administrator/language/en-GB/en-GB.com_media.sys.ini\n/administrator/language/en-GB/en-GB.com_menus.ini\n/administrator/language/en-GB/en-GB.com_menus.sys.ini\n/administrator/language/en-GB/en-GB.com_messages.ini\n/administrator/language/en-GB/en-GB.com_messages.sys.ini\n/administrator/language/en-GB/en-GB.com_modules.ini\n/administrator/language/en-GB/en-GB.com_modules.sys.ini\n/administrator/language/en-GB/en-GB.com_newsfeeds.ini\n/administrator/language/en-GB/en-GB.com_newsfeeds.sys.ini\n/administrator/language/en-GB/en-GB.com_plugins.ini\n/administrator/language/en-GB/en-GB.com_plugins.sys.ini\n/administrator/language/en-GB/en-GB.com_postinstall.ini\n/administrator/language/en-GB/en-GB.com_postinstall.sys.ini\n/administrator/language/en-GB/en-GB.com_privacy.ini\n/administrator/language/en-GB/en-GB.com_privacy.sys.ini\n/administrator/language/en-GB/en-GB.com_redirect.ini\n/administrator/language/en-GB/en-GB.com_redirect.sys.ini\n/administrator/language/en-GB/en-GB.com_search.ini\n/administrator/language/en-GB/en-GB.com_search.sys.ini\n/administrator/language/en-GB/en-GB.com_tags.ini\n/administrator/language/en-GB/en-GB.com_tags.sys.ini\n/administrator/language/en-GB/en-GB.com_templates.ini\n/administrator/language/en-GB/en-GB.com_templates.sys.ini\n/administrator/language/en-GB/en-GB.com_users.ini\n/administrator/language/en-GB/en-GB.com_users.sys.ini\n/administrator/language/en-GB/en-GB.com_weblinks.ini\n/administrator/language/en-GB/en-GB.com_weblinks.sys.ini\n/administrator/language/en-GB/en-GB.com_wrapper.ini\n/administrator/language/en-GB/en-GB.com_wrapper.sys.ini\n/administrator/language/en-GB/en-GB.ini\n/administrator/language/en-GB/en-GB.lib_joomla.ini\n/administrator/language/en-GB/en-GB.localise.php\n/administrator/language/en-GB/en-GB.mod_custom.ini\n/administrator/language/en-GB/en-GB.mod_custom.sys.ini\n/administrator/language/en-GB/en-GB.mod_feed.ini\n/administrator/language/en-GB/en-GB.mod_feed.sys.ini\n/administrator/language/en-GB/en-GB.mod_latest.ini\n/administrator/language/en-GB/en-GB.mod_latest.sys.ini\n/administrator/language/en-GB/en-GB.mod_latestactions.ini\n/administrator/language/en-GB/en-GB.mod_latestactions.sys.ini\n/administrator/language/en-GB/en-GB.mod_logged.ini\n/administrator/language/en-GB/en-GB.mod_logged.sys.ini\n/administrator/language/en-GB/en-GB.mod_login.ini\n/administrator/language/en-GB/en-GB.mod_login.sys.ini\n/administrator/language/en-GB/en-GB.mod_menu.ini\n/administrator/language/en-GB/en-GB.mod_menu.sys.ini\n/administrator/language/en-GB/en-GB.mod_multilangstatus.ini\n/administrator/language/en-GB/en-GB.mod_multilangstatus.sys.ini\n/administrator/language/en-GB/en-GB.mod_online.ini\n/administrator/language/en-GB/en-GB.mod_online.sys.ini\n/administrator/language/en-GB/en-GB.mod_popular.ini\n/administrator/language/en-GB/en-GB.mod_popular.sys.ini\n/administrator/language/en-GB/en-GB.mod_privacy_dashboard.ini\n/administrator/language/en-GB/en-GB.mod_privacy_dashboard.sys.ini\n/administrator/language/en-GB/en-GB.mod_quickicon.ini\n/administrator/language/en-GB/en-GB.mod_quickicon.sys.ini\n/administrator/language/en-GB/en-GB.mod_sampledata.ini\n/administrator/language/en-GB/en-GB.mod_sampledata.sys.ini\n/administrator/language/en-GB/en-GB.mod_stats_admin.ini\n/administrator/language/en-GB/en-GB.mod_stats_admin.sys.ini\n/administrator/language/en-GB/en-GB.mod_status.ini\n/administrator/language/en-GB/en-GB.mod_status.sys.ini\n/administrator/language/en-GB/en-GB.mod_submenu.ini\n/administrator/language/en-GB/en-GB.mod_submenu.sys.ini\n/administrator/language/en-GB/en-GB.mod_title.ini\n/administrator/language/en-GB/en-GB.mod_title.sys.ini\n/administrator/language/en-GB/en-GB.mod_toolbar.ini\n/administrator/language/en-GB/en-GB.mod_toolbar.sys.ini\n/administrator/language/en-GB/en-GB.mod_unread.ini\n/administrator/language/en-GB/en-GB.mod_unread.sys.ini\n/administrator/language/en-GB/en-GB.mod_version.ini\n/administrator/language/en-GB/en-GB.mod_version.sys.ini\n/administrator/language/en-GB/en-GB.plg_actionlog_joomla.ini\n/administrator/language/en-GB/en-GB.plg_actionlog_joomla.sys.ini\n/administrator/language/en-GB/en-GB.plg_authentication_cookie.ini\n/administrator/language/en-GB/en-GB.plg_authentication_cookie.sys.ini\n/administrator/language/en-GB/en-GB.plg_authentication_example.ini\n/administrator/language/en-GB/en-GB.plg_authentication_example.sys.ini\n/administrator/language/en-GB/en-GB.plg_authentication_gmail.ini\n/administrator/language/en-GB/en-GB.plg_authentication_gmail.sys.ini\n/administrator/language/en-GB/en-GB.plg_authentication_joomla.ini\n/administrator/language/en-GB/en-GB.plg_authentication_joomla.sys.ini\n/administrator/language/en-GB/en-GB.plg_authentication_ldap.ini\n/administrator/language/en-GB/en-GB.plg_authentication_ldap.sys.ini\n/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.ini\n/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.sys.ini\n/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.ini\n/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_confirmconsent.ini\n/administrator/language/en-GB/en-GB.plg_content_confirmconsent.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_contact.ini\n/administrator/language/en-GB/en-GB.plg_content_contact.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_emailcloak.ini\n/administrator/language/en-GB/en-GB.plg_content_emailcloak.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_fields.ini\n/administrator/language/en-GB/en-GB.plg_content_fields.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_finder.ini\n/administrator/language/en-GB/en-GB.plg_content_finder.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_geshi.ini\n/administrator/language/en-GB/en-GB.plg_content_geshi.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_joomla.ini\n/administrator/language/en-GB/en-GB.plg_content_joomla.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_loadmodule.ini\n/administrator/language/en-GB/en-GB.plg_content_loadmodule.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_pagebreak.ini\n/administrator/language/en-GB/en-GB.plg_content_pagebreak.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_pagenavigation.ini\n/administrator/language/en-GB/en-GB.plg_content_pagenavigation.sys.ini\n/administrator/language/en-GB/en-GB.plg_content_vote.ini\n/administrator/language/en-GB/en-GB.plg_content_vote.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_article.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_article.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_contact.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_contact.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_fields.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_fields.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_image.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_image.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_menu.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_menu.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_module.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_module.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_pagebreak.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_pagebreak.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_readmore.ini\n/administrator/language/en-GB/en-GB.plg_editors-xtd_readmore.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors_codemirror.ini\n/administrator/language/en-GB/en-GB.plg_editors_codemirror.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors_none.ini\n/administrator/language/en-GB/en-GB.plg_editors_none.sys.ini\n/administrator/language/en-GB/en-GB.plg_editors_tinymce.ini\n/administrator/language/en-GB/en-GB.plg_editors_tinymce.sys.ini\n/administrator/language/en-GB/en-GB.plg_extension_joomla.ini\n/administrator/language/en-GB/en-GB.plg_extension_joomla.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_calendar.ini\n/administrator/language/en-GB/en-GB.plg_fields_calendar.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_checkboxes.ini\n/administrator/language/en-GB/en-GB.plg_fields_checkboxes.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_color.ini\n/administrator/language/en-GB/en-GB.plg_fields_color.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_editor.ini\n/administrator/language/en-GB/en-GB.plg_fields_editor.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_image.ini\n/administrator/language/en-GB/en-GB.plg_fields_image.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_imagelist.ini\n/administrator/language/en-GB/en-GB.plg_fields_imagelist.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_integer.ini\n/administrator/language/en-GB/en-GB.plg_fields_integer.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_list.ini\n/administrator/language/en-GB/en-GB.plg_fields_list.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_media.ini\n/administrator/language/en-GB/en-GB.plg_fields_media.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_radio.ini\n/administrator/language/en-GB/en-GB.plg_fields_radio.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_repeatable.ini\n/administrator/language/en-GB/en-GB.plg_fields_repeatable.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_sql.ini\n/administrator/language/en-GB/en-GB.plg_fields_sql.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_text.ini\n/administrator/language/en-GB/en-GB.plg_fields_text.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_textarea.ini\n/administrator/language/en-GB/en-GB.plg_fields_textarea.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_url.ini\n/administrator/language/en-GB/en-GB.plg_fields_url.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_user.ini\n/administrator/language/en-GB/en-GB.plg_fields_user.sys.ini\n/administrator/language/en-GB/en-GB.plg_fields_usergrouplist.ini\n/administrator/language/en-GB/en-GB.plg_fields_usergrouplist.sys.ini\n/administrator/language/en-GB/en-GB.plg_finder_categories.ini\n/administrator/language/en-GB/en-GB.plg_finder_categories.sys.ini\n/administrator/language/en-GB/en-GB.plg_finder_contacts.ini\n/administrator/language/en-GB/en-GB.plg_finder_contacts.sys.ini\n/administrator/language/en-GB/en-GB.plg_finder_content.ini\n/administrator/language/en-GB/en-GB.plg_finder_content.sys.ini\n/administrator/language/en-GB/en-GB.plg_finder_newsfeeds.ini\n/administrator/language/en-GB/en-GB.plg_finder_newsfeeds.sys.ini\n/administrator/language/en-GB/en-GB.plg_finder_tags.ini\n/administrator/language/en-GB/en-GB.plg_finder_tags.sys.ini\n/administrator/language/en-GB/en-GB.plg_finder_weblinks.ini\n/administrator/language/en-GB/en-GB.plg_finder_weblinks.sys.ini\n/administrator/language/en-GB/en-GB.plg_installer_folderinstaller.ini\n/administrator/language/en-GB/en-GB.plg_installer_folderinstaller.sys.ini\n/administrator/language/en-GB/en-GB.plg_installer_packageinstaller.ini\n/administrator/language/en-GB/en-GB.plg_installer_packageinstaller.sys.ini\n/administrator/language/en-GB/en-GB.plg_installer_urlinstaller.ini\n/administrator/language/en-GB/en-GB.plg_installer_urlinstaller.sys.ini\n/administrator/language/en-GB/en-GB.plg_installer_webinstaller.ini\n/administrator/language/en-GB/en-GB.plg_installer_webinstaller.sys.ini\n/administrator/language/en-GB/en-GB.plg_privacy_actionlogs.ini\n/administrator/language/en-GB/en-GB.plg_privacy_actionlogs.sys.ini\n/administrator/language/en-GB/en-GB.plg_privacy_consents.ini\n/administrator/language/en-GB/en-GB.plg_privacy_consents.sys.ini\n/administrator/language/en-GB/en-GB.plg_privacy_contact.ini\n/administrator/language/en-GB/en-GB.plg_privacy_contact.sys.ini\n/administrator/language/en-GB/en-GB.plg_privacy_content.ini\n/administrator/language/en-GB/en-GB.plg_privacy_content.sys.ini\n/administrator/language/en-GB/en-GB.plg_privacy_message.ini\n/administrator/language/en-GB/en-GB.plg_privacy_message.sys.ini\n/administrator/language/en-GB/en-GB.plg_privacy_user.ini\n/administrator/language/en-GB/en-GB.plg_privacy_user.sys.ini\n/administrator/language/en-GB/en-GB.plg_quickicon_extensionupdate.ini\n/administrator/language/en-GB/en-GB.plg_quickicon_extensionupdate.sys.ini\n/administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.ini\n/administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.sys.ini\n/administrator/language/en-GB/en-GB.plg_quickicon_phpversioncheck.ini\n/administrator/language/en-GB/en-GB.plg_quickicon_phpversioncheck.sys.ini\n/administrator/language/en-GB/en-GB.plg_quickicon_privacycheck.ini\n/administrator/language/en-GB/en-GB.plg_quickicon_privacycheck.sys.ini\n/administrator/language/en-GB/en-GB.plg_sampledata_blog.ini\n/administrator/language/en-GB/en-GB.plg_sampledata_blog.sys.ini\n/administrator/language/en-GB/en-GB.plg_search_categories.ini\n/administrator/language/en-GB/en-GB.plg_search_categories.sys.ini\n/administrator/language/en-GB/en-GB.plg_search_contacts.ini\n/administrator/language/en-GB/en-GB.plg_search_contacts.sys.ini\n/administrator/language/en-GB/en-GB.plg_search_content.ini\n/administrator/language/en-GB/en-GB.plg_search_content.sys.ini\n/administrator/language/en-GB/en-GB.plg_search_newsfeeds.ini\n/administrator/language/en-GB/en-GB.plg_search_newsfeeds.sys.ini\n/administrator/language/en-GB/en-GB.plg_search_tags.ini\n/administrator/language/en-GB/en-GB.plg_search_tags.sys.ini\n/administrator/language/en-GB/en-GB.plg_search_weblinks.ini\n/administrator/language/en-GB/en-GB.plg_search_weblinks.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_actionlogs.ini\n/administrator/language/en-GB/en-GB.plg_system_actionlogs.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_cache.ini\n/administrator/language/en-GB/en-GB.plg_system_cache.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_debug.ini\n/administrator/language/en-GB/en-GB.plg_system_debug.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_fields.ini\n/administrator/language/en-GB/en-GB.plg_system_fields.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_highlight.ini\n/administrator/language/en-GB/en-GB.plg_system_highlight.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_languagecode.ini\n/administrator/language/en-GB/en-GB.plg_system_languagecode.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_languagefilter.ini\n/administrator/language/en-GB/en-GB.plg_system_languagefilter.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_log.ini\n/administrator/language/en-GB/en-GB.plg_system_log.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_logout.ini\n/administrator/language/en-GB/en-GB.plg_system_logout.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_logrotation.ini\n/administrator/language/en-GB/en-GB.plg_system_logrotation.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_p3p.ini\n/administrator/language/en-GB/en-GB.plg_system_p3p.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_privacyconsent.ini\n/administrator/language/en-GB/en-GB.plg_system_privacyconsent.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_redirect.ini\n/administrator/language/en-GB/en-GB.plg_system_redirect.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_remember.ini\n/administrator/language/en-GB/en-GB.plg_system_remember.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_sef.ini\n/administrator/language/en-GB/en-GB.plg_system_sef.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_sessiongc.ini\n/administrator/language/en-GB/en-GB.plg_system_sessiongc.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_stats.ini\n/administrator/language/en-GB/en-GB.plg_system_stats.sys.ini\n/administrator/language/en-GB/en-GB.plg_system_updatenotification.ini\n/administrator/language/en-GB/en-GB.plg_system_updatenotification.sys.ini\n/administrator/language/en-GB/en-GB.plg_twofactorauth_totp.ini\n/administrator/language/en-GB/en-GB.plg_twofactorauth_totp.sys.ini\n/administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.ini\n/administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.sys.ini\n/administrator/language/en-GB/en-GB.plg_user_contactcreator.ini\n/administrator/language/en-GB/en-GB.plg_user_contactcreator.sys.ini\n/administrator/language/en-GB/en-GB.plg_user_joomla.ini\n/administrator/language/en-GB/en-GB.plg_user_joomla.sys.ini\n/administrator/language/en-GB/en-GB.plg_user_profile.ini\n/administrator/language/en-GB/en-GB.plg_user_profile.sys.ini\n/administrator/language/en-GB/en-GB.plg_user_terms.ini\n/administrator/language/en-GB/en-GB.plg_user_terms.sys.ini\n/administrator/language/en-GB/en-GB.tpl_hathor.ini\n/administrator/language/en-GB/en-GB.tpl_hathor.sys.ini\n/administrator/language/en-GB/en-GB.tpl_isis.ini\n/administrator/language/en-GB/en-GB.tpl_isis.sys.ini\n/administrator/language/en-GB/en-GB.xml\n/administrator/language/en-GB/install.xml\n/administrator/language/overrides/*\n/administrator/language/index.html\n/administrator/logs/*\n/administrator/manifests/files/joomla.xml\n/administrator/manifests/libraries/fof.xml\n/administrator/manifests/libraries/idna_convert.xml\n/administrator/manifests/libraries/joomla.xml\n/administrator/manifests/libraries/phpass.xml\n/administrator/manifests/libraries/phputf8.xml\n/administrator/manifests/packages/pkg_en-GB.xml\n/administrator/manifests/packages/index.html\n/administrator/modules/mod_custom/*\n/administrator/modules/mod_feed/*\n/administrator/modules/mod_latest/*\n/administrator/modules/mod_latestactions/*\n/administrator/modules/mod_logged/*\n/administrator/modules/mod_login/*\n/administrator/modules/mod_menu/*\n/administrator/modules/mod_multilangstatus/*\n/administrator/modules/mod_online/*\n/administrator/modules/mod_popular/*\n/administrator/modules/mod_privacy_dashboard/*\n/administrator/modules/mod_quickicon/*\n/administrator/modules/mod_sampledata/*\n/administrator/modules/mod_stats_admin/*\n/administrator/modules/mod_status/*\n/administrator/modules/mod_submenu/*\n/administrator/modules/mod_title/*\n/administrator/modules/mod_toolbar/*\n/administrator/modules/mod_unread/*\n/administrator/modules/mod_version/*\n/administrator/templates/hathor/*\n/administrator/templates/isis/*\n/administrator/templates/system/*\n/bin/*\n/cache/*\n/cli/*\n/components/com_ajax/*\n/components/com_banners/*\n/components/com_config/*\n/components/com_contact/*\n/components/com_content/*\n/components/com_contenthistory/*\n/components/com_fields/*\n/components/com_finder/*\n/components/com_mailto/*\n/components/com_media/*\n/components/com_menus/*\n/components/com_modules/*\n/components/com_newsfeeds/*\n/components/com_privacy/*\n/components/com_search/*\n/components/com_tags/*\n/components/com_users/*\n/components/com_wrapper/*\n/components/index.html\n/images/banners/*\n/images/headers/*\n/images/sampledata/*\n/images/index.html\n/images/joomla*\n/images/powered_by.png\n/includes/*\n/installation/*\n/language/en-GB/en-GB.com_ajax.ini\n/language/en-GB/en-GB.com_config.ini\n/language/en-GB/en-GB.com_contact.ini\n/language/en-GB/en-GB.com_content.ini\n/language/en-GB/en-GB.com_finder.ini\n/language/en-GB/en-GB.com_mailto.ini\n/language/en-GB/en-GB.com_media.ini\n/language/en-GB/en-GB.com_messages.ini\n/language/en-GB/en-GB.com_newsfeeds.ini\n/language/en-GB/en-GB.com_privacy.ini\n/language/en-GB/en-GB.com_search.ini\n/language/en-GB/en-GB.com_tags.ini\n/language/en-GB/en-GB.com_users.ini\n/language/en-GB/en-GB.com_weblinks.ini\n/language/en-GB/en-GB.com_wrapper.ini\n/language/en-GB/en-GB.files_joomla.sys.ini\n/language/en-GB/en-GB.finder_cli.ini\n/language/en-GB/en-GB.ini\n/language/en-GB/en-GB.lib_fof.ini\n/language/en-GB/en-GB.lib_fof.sys.ini\n/language/en-GB/en-GB.lib_idna_convert.sys.ini\n/language/en-GB/en-GB.lib_joomla.ini\n/language/en-GB/en-GB.lib_joomla.sys.ini\n/language/en-GB/en-GB.lib_phpass.sys.ini\n/language/en-GB/en-GB.lib_phpmailer.sys.ini\n/language/en-GB/en-GB.lib_phputf8.sys.ini\n/language/en-GB/en-GB.lib_simplepie.sys.ini\n/language/en-GB/en-GB.localise.php\n/language/en-GB/en-GB.mod_articles_archive.ini\n/language/en-GB/en-GB.mod_articles_archive.sys.ini\n/language/en-GB/en-GB.mod_articles_categories.ini\n/language/en-GB/en-GB.mod_articles_categories.sys.ini\n/language/en-GB/en-GB.mod_articles_category.ini\n/language/en-GB/en-GB.mod_articles_category.sys.ini\n/language/en-GB/en-GB.mod_articles_latest.ini\n/language/en-GB/en-GB.mod_articles_latest.sys.ini\n/language/en-GB/en-GB.mod_articles_news.ini\n/language/en-GB/en-GB.mod_articles_news.sys.ini\n/language/en-GB/en-GB.mod_articles_popular.ini\n/language/en-GB/en-GB.mod_articles_popular.sys.ini\n/language/en-GB/en-GB.mod_banners.ini\n/language/en-GB/en-GB.mod_banners.sys.ini\n/language/en-GB/en-GB.mod_breadcrumbs.ini\n/language/en-GB/en-GB.mod_breadcrumbs.sys.ini\n/language/en-GB/en-GB.mod_custom.ini\n/language/en-GB/en-GB.mod_custom.sys.ini\n/language/en-GB/en-GB.mod_feed.ini\n/language/en-GB/en-GB.mod_feed.sys.ini\n/language/en-GB/en-GB.mod_finder.ini\n/language/en-GB/en-GB.mod_finder.sys.ini\n/language/en-GB/en-GB.mod_footer.ini\n/language/en-GB/en-GB.mod_footer.sys.ini\n/language/en-GB/en-GB.mod_languages.ini\n/language/en-GB/en-GB.mod_languages.sys.ini\n/language/en-GB/en-GB.mod_login.ini\n/language/en-GB/en-GB.mod_login.sys.ini\n/language/en-GB/en-GB.mod_menu.ini\n/language/en-GB/en-GB.mod_menu.sys.ini\n/language/en-GB/en-GB.mod_random_image.ini\n/language/en-GB/en-GB.mod_random_image.sys.ini\n/language/en-GB/en-GB.mod_related_items.ini\n/language/en-GB/en-GB.mod_related_items.sys.ini\n/language/en-GB/en-GB.mod_search.ini\n/language/en-GB/en-GB.mod_search.sys.ini\n/language/en-GB/en-GB.mod_stats.ini\n/language/en-GB/en-GB.mod_stats.sys.ini\n/language/en-GB/en-GB.mod_syndicate.ini\n/language/en-GB/en-GB.mod_syndicate.sys.ini\n/language/en-GB/en-GB.mod_tags_popular.ini\n/language/en-GB/en-GB.mod_tags_popular.sys.ini\n/language/en-GB/en-GB.mod_tags_similar.ini\n/language/en-GB/en-GB.mod_tags_similar.sys.ini\n/language/en-GB/en-GB.mod_users_latest.ini\n/language/en-GB/en-GB.mod_users_latest.sys.ini\n/language/en-GB/en-GB.mod_weblinks.ini\n/language/en-GB/en-GB.mod_weblinks.sys.ini\n/language/en-GB/en-GB.mod_whosonline.ini\n/language/en-GB/en-GB.mod_whosonline.sys.ini\n/language/en-GB/en-GB.mod_wrapper.ini\n/language/en-GB/en-GB.mod_wrapper.sys.ini\n/language/en-GB/en-GB.tpl_atomic.ini\n/language/en-GB/en-GB.tpl_atomic.sys.ini\n/language/en-GB/en-GB.tpl_beez3.ini\n/language/en-GB/en-GB.tpl_beez3.sys.ini\n/language/en-GB/en-GB.tpl_beez5.ini\n/language/en-GB/en-GB.tpl_beez5.sys.ini\n/language/en-GB/en-GB.tpl_beez_20.ini\n/language/en-GB/en-GB.tpl_beez_20.sys.ini\n/language/en-GB/en-GB.tpl_protostar.ini\n/language/en-GB/en-GB.tpl_protostar.sys.ini\n/language/en-GB/en-GB.xml\n/language/en-GB/install.xml\n/language/overrides/*\n/language/index.html\n/layouts/joomla/*\n/layouts/libraries/*\n/layouts/plugins/*\n/layouts/index.html\n/libraries/cms/*\n/libraries/fof/*\n/libraries/idna_convert/*\n/libraries/joomla/*\n/libraries/legacy/*\n/libraries/php-encryption/*\n/libraries/phpass/*\n/libraries/phpmailer/*\n/libraries/phputf8/*\n/libraries/simplepie/*\n/libraries/src/*\n/libraries/vendor/*\n/libraries/classmap.php\n/libraries/cms.php\n/libraries/import.legacy.php\n/libraries/import.php\n/libraries/index.html\n/libraries/loader.php\n/media/cms/*\n/media/com_associations/*\n/media/com_contact/*\n/media/com_content/*\n/media/com_contenthistory/*\n/media/com_fields/*\n/media/com_finder/*\n/media/com_joomlaupdate/*\n/media/com_menus/*\n/media/com_modules/*\n/media/com_wrapper/*\n/media/contacts/*\n/media/editors/*\n/media/jui/*\n/media/mailto/*\n/media/media/*\n/media/mod_languages/*\n/media/mod_sampledata/*\n/media/overrider/*\n/media/plg_captcha_recaptcha/*\n/media/plg_captcha_recaptcha_invisible/*\n/media/plg_quickicon_extensionupdate/*\n/media/plg_quickicon_joomlaupdate/*\n/media/plg_quickicon_privacycheck/*\n/media/plg_system_highlight/*\n/media/plg_system_stats/*\n/media/plg_twofactorauth_totp/*\n/media/system/*\n/media/index.html\n/modules/mod_articles_archive/*\n/modules/mod_articles_categories/*\n/modules/mod_articles_category/*\n/modules/mod_articles_latest/*\n/modules/mod_articles_news/*\n/modules/mod_articles_popular/*\n/modules/mod_banners/*\n/modules/mod_breadcrumbs/*\n/modules/mod_custom/*\n/modules/mod_feed/*\n/modules/mod_finder/*\n/modules/mod_footer/*\n/modules/mod_languages/*\n/modules/mod_login/*\n/modules/mod_menu/*\n/modules/mod_random_image/*\n/modules/mod_related_items/*\n/modules/mod_search/*\n/modules/mod_stats/*\n/modules/mod_syndicate/*\n/modules/mod_tags_popular/*\n/modules/mod_tags_similar/*\n/modules/mod_users_latest/*\n/modules/mod_whosonline/*\n/modules/mod_wrapper/*\n/modules/index.html\n/plugins/actionlog/joomla/*\n/plugins/authentication/cookie/*\n/plugins/authentication/example/*\n/plugins/authentication/gmail/*\n/plugins/authentication/joomla/*\n/plugins/authentication/ldap/*\n/plugins/captcha/recaptcha/*\n/plugins/captcha/recaptcha_invisible/*\n/plugins/content/confirmconsent/*\n/plugins/content/contact/*\n/plugins/content/emailcloak/*\n/plugins/content/example/*\n/plugins/content/fields/*\n/plugins/content/finder/*\n/plugins/content/geshi/*\n/plugins/content/joomla/*\n/plugins/content/loadmodule/*\n/plugins/content/pagebreak/*\n/plugins/content/pagenavigation/*\n/plugins/content/vote/*\n/plugins/editors/codemirror/*\n/plugins/editors/none/*\n/plugins/editors/tinymce/*\n/plugins/editors-xtd/article/*\n/plugins/editors-xtd/contact/*\n/plugins/editors-xtd/fields/*\n/plugins/editors-xtd/image/*\n/plugins/editors-xtd/menu/*\n/plugins/editors-xtd/module/*\n/plugins/editors-xtd/pagebreak/*\n/plugins/editors-xtd/readmore/*\n/plugins/extension/example/*\n/plugins/extension/joomla/*\n/plugins/fields/calendar/*\n/plugins/fields/checkboxes/*\n/plugins/fields/color/*\n/plugins/fields/editor/*\n/plugins/fields/imagelist/*\n/plugins/fields/integer/*\n/plugins/fields/list/*\n/plugins/fields/media/*\n/plugins/fields/radio/*\n/plugins/fields/repeatable/*\n/plugins/fields/sql/*\n/plugins/fields/text/*\n/plugins/fields/textarea/*\n/plugins/fields/url/*\n/plugins/fields/user/*\n/plugins/fields/usergrouplist/*\n/plugins/finder/categories/*\n/plugins/finder/contacts/*\n/plugins/finder/content/*\n/plugins/finder/newsfeeds/*\n/plugins/finder/tags/*\n/plugins/installer/folderinstaller/*\n/plugins/installer/packageinstaller/*\n/plugins/installer/urlinstaller/*\n/plugins/privacy/actionlogs/*\n/plugins/privacy/consents/*\n/plugins/privacy/contact/*\n/plugins/privacy/content/*\n/plugins/privacy/message/*\n/plugins/privacy/user/*\n/plugins/quickicon/extensionupdate/*\n/plugins/quickicon/joomlaupdate/*\n/plugins/quickicon/phpversioncheck/*\n/plugins/quickicon/privacycheck/*\n/plugins/quickicon/index.html\n/plugins/sampledata/blog/*\n/plugins/search/categories/*\n/plugins/search/contacts/*\n/plugins/search/content/*\n/plugins/search/newsfeeds/*\n/plugins/search/tags/*\n/plugins/search/weblinks/*\n/plugins/search/index.html\n/plugins/system/actionlogs/*\n/plugins/system/cache/*\n/plugins/system/debug/*\n/plugins/system/fields/*\n/plugins/system/highlight/*\n/plugins/system/languagecode/*\n/plugins/system/languagefilter/*\n/plugins/system/log/*\n/plugins/system/logout/*\n/plugins/system/logrotation/*\n/plugins/system/p3p/*\n/plugins/system/privacyconsent/*\n/plugins/system/redirect/*\n/plugins/system/remember/*\n/plugins/system/sef/*\n/plugins/system/sessiongc/*\n/plugins/system/stats/*\n/plugins/system/updatenotification/*\n/plugins/system/index.html\n/plugins/twofactorauth/totp/*\n/plugins/twofactorauth/yubikey/*\n/plugins/user/contactcreator/*\n/plugins/user/example/*\n/plugins/user/joomla/*\n/plugins/user/profile/*\n/plugins/user/terms/*\n/plugins/user/index.html\n/plugins/index.html\n/templates/beez3/*\n/templates/protostar/*\n/templates/system/*\n/templates/index.html\n/tmp/*\n/configuration.php\n/htaccess.txt\n/index.php\n/joomla.xml\n/LICENSE.txt\n/README.txt\n/robots.txt.dist\n/web.config.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/joomla\n","jsonnet":"# Created by https://www.toptal.com/developers/gitignore/api/jsonnet\n# Edit at https://www.toptal.com/developers/gitignore?templates=jsonnet\n\n### Jsonnet ###\n# Dependency directories\nvendor/\n\n\n# End of https://www.toptal.com/developers/gitignore/api/jsonnet\n","jspm":"# Created by https://www.toptal.com/developers/gitignore/api/jspm\n# Edit at https://www.toptal.com/developers/gitignore?templates=jspm\n\n### jspm ###\njspm_packages\n\n# End of https://www.toptal.com/developers/gitignore/api/jspm\n","julia":"# Created by https://www.toptal.com/developers/gitignore/api/julia\n# Edit at https://www.toptal.com/developers/gitignore?templates=julia\n\n### Julia ###\n# Files generated by invoking Julia with --code-coverage\n*.jl.cov\n*.jl.*.cov\n\n# Files generated by invoking Julia with --track-allocation\n*.jl.mem\n\n# System-specific files and directories generated by the BinaryProvider and BinDeps packages\n# They contain absolute paths specific to the host computer, and so should not be committed\ndeps/deps.jl\ndeps/build.log\ndeps/downloads/\ndeps/usr/\ndeps/src/\n\n# Build artifacts for creating documentation generated by the Documenter package\ndocs/build/\ndocs/site/\n\n# File generated by Pkg, the package manager, based on a corresponding Project.toml\n# It records a fixed state of all packages used by the project. As such, it should not be\n# committed for packages, but should be committed for applications that require a static\n# environment.\nManifest.toml\n\n# End of https://www.toptal.com/developers/gitignore/api/julia\n","jupyternotebooks":"# Created by https://www.toptal.com/developers/gitignore/api/jupyternotebooks\n# Edit at https://www.toptal.com/developers/gitignore?templates=jupyternotebooks\n\n### JupyterNotebooks ###\n# gitignore template for Jupyter Notebooks\n# website: http://jupyter.org/\n\n.ipynb_checkpoints\n*/.ipynb_checkpoints/*\n\n# IPython\nprofile_default/\nipython_config.py\n\n# Remove previous ipynb_checkpoints\n# git rm -r .ipynb_checkpoints/\n\n# End of https://www.toptal.com/developers/gitignore/api/jupyternotebooks\n","justcode":"# Created by https://www.toptal.com/developers/gitignore/api/justcode\n# Edit at https://www.toptal.com/developers/gitignore?templates=justcode\n\n### JustCode ###\n.JustCode\n\n# End of https://www.toptal.com/developers/gitignore/api/justcode\n","kaldi":"# Created by https://www.toptal.com/developers/gitignore/api/kaldi\n# Edit at https://www.toptal.com/developers/gitignore?templates=kaldi\n\n### kaldi ###\nexp/\ndata/\n\n# End of https://www.toptal.com/developers/gitignore/api/kaldi\n","kate":"# Created by https://www.toptal.com/developers/gitignore/api/kate\n# Edit at https://www.toptal.com/developers/gitignore?templates=kate\n\n### Kate ###\n# Swap Files #\n.*.kate-swp\n.swp.*\n\n# End of https://www.toptal.com/developers/gitignore/api/kate\n","kdevelop4":"# Created by https://www.toptal.com/developers/gitignore/api/kdevelop4\n# Edit at https://www.toptal.com/developers/gitignore?templates=kdevelop4\n\n### KDevelop4 ###\n*.kdev4\n.kdev4/\n\n# End of https://www.toptal.com/developers/gitignore/api/kdevelop4\n","kdiff3":"# Created by https://www.toptal.com/developers/gitignore/api/kdiff3\n# Edit at https://www.toptal.com/developers/gitignore?templates=kdiff3\n\n### KDiff3 ###\n*.BACKUP.*\n*_BACKUP_*\n*.BASE.*\n*_BASE_*\n*.LOCAL.*\n*_LOCAL_*\n*.REMOTE.*\n*_REMOTE_*\n*.orig\n\n\n# End of https://www.toptal.com/developers/gitignore/api/kdiff3\n","keil":"# Created by https://www.toptal.com/developers/gitignore/api/keil\n# Edit at https://www.toptal.com/developers/gitignore?templates=keil\n\n### Keil ###\n# something maybe need to add\n# *.bat # generate batch file\n# output/project_name # uv project generate without file ext\n# *.SRC # generate source file\n# *.src\n\n# Prerequisites\n*.d\n*.dep\n# Object files\n*.o\n*.obj\n*.OBJ\n\n# Listing Files\n*.COD\n*.cod\n*.[Ii]\n*.LST\n*.lst\n*.MAP\n*.map\n*.[Mm]51\n*.[Mm]66\n*.SCR\n*.scr\n\n# LINK output\n*.ABS\n\n# Libraries\n*.lib\n*.LIB\n\n\n# uv output\n*.lnp\n*.build_log.htm\n*.HEX\n*.hex\n\n\n# Build Files\n*._IA\n*.__[Ii]\n*._[Ii][Ii]\n\n# project\n*.uvgui.*\n*.uvguix.*\n\n\n# End of https://www.toptal.com/developers/gitignore/api/keil\n","kentico":"# Created by https://www.toptal.com/developers/gitignore/api/kentico\n# Edit at https://www.toptal.com/developers/gitignore?templates=kentico\n\n### Kentico ###\n# gitignore template for using Kentico CMS\n# website: http://www.kentico.com/\n#\n# Recommended template: VisualStudio.gitignore\n\n# Include some Kentico folders excluded by Visual Studio rules\n!CMS/CMSAdminControls/*/\n!CMS/CMSModules/System/*/\n!CMS/App_Data/CIRepository/**\n\n# Kentico temporary/environment files\nCMS/App_Data/AzureCache\nCMS/App_Data/AzureTemp\nCMS/App_Data/CMSModules/DeviceProfile/logFiftyOne.txt\nCMS/App_Data/CMSModules/DeviceProfiles/logFiftyOne.txt\nCMS/App_Data/CMSModules/WebFarm/webfarm.sync\nCMS/App_Data/CMSTemp\nCMS/App_Data/Persistent\nCMS/CMSSiteUtils/Export\nCMS/CMSSiteUtils/Import\n\n# Ignore all smart search indexes, but not the other system folder contents\nCMS/App_Data/CMSModules/SmartSearch/**\n!CMS/App_Data/CMSModules/SmartSearch/*/\n!CMS/App_Data/CMSModules/SmartSearch/_StopWords/**\n!CMS/App_Data/CMSModules/SmartSearch/_Synonyms/**\n\n## Kentico Starter Sites\n# Starter site resource Files\nCMS/App_Data/DancingGoat\n\n# Starter site web templates\nCMS/App_Data/Templates/CommunitySite\nCMS/App_Data/Templates/CorporateSite\nCMS/App_Data/Templates/DancingGoat\nCMS/App_Data/Templates/EcommerceSite\nCMS/App_Data/Templates/IntranetPortal\nCMS/App_Data/Templates/PersonalSite\n\n# Starter site app themes\nCMS/App_Themes/CommunitySite\nCMS/App_Themes/CorporateSite\nCMS/App_Themes/EcommerceSite\nCMS/App_Themes/IntranetPortal*\nCMS/App_Themes/PersonalSite\n\n# Starter site ASPX templates\nCMS/CMSTemplates/CorporateSite\n\n# Starter site media libraries\nCMS/CommunitySite\nCMS/CorporateSite\nCMS/DancingGoat\nCMS/EcommerceSite\nCMS/IntranetPortal\nCMS/PersonalSite\n\n## Project specific ignores\n# Sensitive settings\nAppSettings.config\nConnectionStrings.config\n\n# Project media libraries (recommend shared file storage)\n# e.g. CMS/{SiteCodeName}\n\n# End of https://www.toptal.com/developers/gitignore/api/kentico\n","keystonejs":"# Created by https://www.toptal.com/developers/gitignore/api/keystonejs\n# Edit at https://www.toptal.com/developers/gitignore?templates=keystonejs\n\n### KeystoneJS ###\n# Keystone build dir\n.keystone/\n\n# End of https://www.toptal.com/developers/gitignore/api/keystonejs\n","kicad":"# Created by https://www.toptal.com/developers/gitignore/api/kicad\n# Edit at https://www.toptal.com/developers/gitignore?templates=kicad\n\n### KiCad ###\n# For PCBs designed using KiCad: https://www.kicad.org/\n# Format documentation: https://kicad.org/help/file-formats/\n\n# Temporary files\n*.000\n*.bak\n*.bck\n*.kicad_pcb-bak\n*.kicad_sch-bak\n*-backups\n*.kicad_prl\n*.sch-bak\n*~\n_autosave-*\n*.tmp\n*-save.pro\n*-save.kicad_pcb\nfp-info-cache\n\n# Netlist files (exported from Eeschema)\n*.net\n\n# Autorouter files (exported from Pcbnew)\n*.dsn\n*.ses\n\n# Exported BOM files\n*.xml\n*.csv\n\n### KiCad Patch ###\nrescue-backup/\n\n*.tsv\nbom/\n\n# Gerber export output\nout/\n\n# End of https://www.toptal.com/developers/gitignore/api/kicad\n","kirby2":"# Created by https://www.toptal.com/developers/gitignore/api/kirby2\n# Edit at https://www.toptal.com/developers/gitignore?templates=kirby2\n\n### Kirby2 ###\n## Kirby 2 | http://getkirby.com\n\n# Exclude account information\nsite/accounts/*\n!site/accounts/index.html\nassets/avatars/*\n!assets/avatars/.gitkeep\n\n# Exclude thumbnails\nthumbs/*\n!thumbs/index.html\n\n# Exclude cache\nsite/cache/*\n!site/cache/index.html\n\n# End of https://www.toptal.com/developers/gitignore/api/kirby2\n","kirby3":"# Created by https://www.toptal.com/developers/gitignore/api/kirby3\n# Edit at https://www.toptal.com/developers/gitignore?templates=kirby3\n\n### Kirby3 ###\n# Uncomment if you install core files via composer\n# kirby\n# site/plugins/*\n\n# Temporary files\n/media/*\n!/media/index.html\n\n# Lock files\n.lock\n\n# -------------SECURITY-------------\n# NEVER publish these files via Git!\n# -------------SECURITY-------------\n\n# Cache Files\n/site/cache/*\n!/site/cache/index.html\n\n# Accounts\n/site/accounts/*\n!/site/accounts/index.html\n\n# Sessions\n/site/sessions/*\n!/site/sessions/index.html\n\n# License\n/site/config/.license\n\n# End of https://www.toptal.com/developers/gitignore/api/kirby3\n","kobalt":"# Created by https://www.toptal.com/developers/gitignore/api/kobalt\n# Edit at https://www.toptal.com/developers/gitignore?templates=kobalt\n\n### Kobalt ###\n.kobalt\nkobaltBuild/\n\n\n# End of https://www.toptal.com/developers/gitignore/api/kobalt\n","kohana":"# Created by https://www.toptal.com/developers/gitignore/api/kohana\n# Edit at https://www.toptal.com/developers/gitignore?templates=kohana\n\n### Kohana ###\napplication/cache/*\napplication/logs/*\n\n# End of https://www.toptal.com/developers/gitignore/api/kohana\n","komodoedit":"# Created by https://www.toptal.com/developers/gitignore/api/komodoedit\n# Edit at https://www.toptal.com/developers/gitignore?templates=komodoedit\n\n### KomodoEdit ###\n*.komodoproject\n.komodotools\n\n# End of https://www.toptal.com/developers/gitignore/api/komodoedit\n","konyvisualizer":"# Created by https://www.toptal.com/developers/gitignore/api/konyvisualizer\n# Edit at https://www.toptal.com/developers/gitignore?templates=konyvisualizer\n\n### KonyVisualizer ###\n# Below list few files not added to gitignore list to support Headless/CI build\n\n# Auto-generated javascript. It's important to note that jssrc at the project root\n# can be ignored, but resources/customlibs/jsSrc must NOT be ignored because it stores\n# FFI binding code which can only be generated during development.\n/jssrc/*\n!/resources/customlibs/jsSrc\n\n# Starting with Visualizer 8.0 the Ant Contrib Jar used for the build process will\n# be regenerated when the build is fired. If you're using Visualizer 7.x then comment out\n# this line. Vis 7.x will not regenerate this jar and ignoring it will break your project.\nant-contrib-0.6.jar\n\n# Binaries Ex:*.apk,*.KAR,\nbinaries/\n\n# Other Visualizer auto-generated files.\n/.webmeta\n/.meta\nmiddleware.properties\nmodules/kony_sdk.js\nmodules/KonySyncLib.js\n\n\n# Starting with Vis 7.3 the /sdkplugin.properties file has been moved to\n# `/resources/customlibs/sdkplugin.properties` and can be automatically re-created by Vis\n# during the build, provided that folder `resources/customlibs` exists -consider adding a `.gitkeep` #file to it in order to force it into source control.\nresources/customlibs/sdkplugin.properties\n\n# The jar's and zip's under these directories can be ignored so long as\n# sdkplugin.properties (See above) is also ignored. The missing sdkplugin.properties will\n# trigger the regeneration of these jar's and zip's during the build.\nresources/customlibs/lib/android/\nresources/customlibs/lib/tabrcandroid/\nresources/customlibs/lib/iphone/\nresources/customlibs/lib/ipad/\nresources/customlibs/lib/windows10/\nresources/customlibs/lib/winphone10/\n\n# The missing sdkplugin.properties will regenerate below files\nresources/customlibs/jsSrc/android/com/konylabs/ffi/N_KonyLogger.java\nresources/customlibs/jsSrc/tabrcandroid/com/konylabs/ffi/N_KonyLogger.java\nresources/customlibs/jsSrc/kiosk/KonyLogger.js\nresources/customlibs/jsSrc/kiosk/KonyLogger.xml\nresources/customlibs/jsSrc/windows8/KonyLogger.js\nresources/customlibs/jsSrc/windows8/KonyLogger.xml\nresources/customlibs/jsSrc/winmobile/KonyLogger.js\nresources/customlibs/jsSrc/winmobile/KonyLogger.xml\nresources/customlibs/jsSrc/winphone8/KonyLogger.js\nresources/customlibs/jsSrc/winphone8/KonyLogger.xml\nresources/customlibs/jsSrc/winphone81s/KonyLogger.js\nresources/customlibs/jsSrc/winphone81s/KonyLogger.xml\nresources/customlibs/jsXml/KonyLogger.js\nresources/customlibs/jsXml/KonyLogger.xml\nresources/customlibs/jsSrc/kiosk/SSOFFI.js\nresources/customlibs/jsSrc/kiosk/SSOFFI.xml\nresources/customlibs/jsSrc/windows8/SSOFFI.js\nresources/customlibs/jsSrc/windows8/SSOFFI.xml\nresources/customlibs/jsSrc/winmobile/SSOFFI.js\nresources/customlibs/jsSrc/winmobile/SSOFFI.xml\nresources/customlibs/jsSrc/winphone8/SSOFFI.js\nresources/customlibs/jsSrc/winphone8/SSOFFI.xml\nresources/customlibs/jsSrc/winphone81s/SSOFFI.js\nresources/customlibs/jsSrc/winphone81s/SSOFFI.xml\nresources/customlibs/jsXml/SSOFFI.js\nresources/customlibs/jsXml/SSOFFI.xml\nresources/customlibs/jsSrc/android/com/konylabs/ffi/ND_binary_util.java\nresources/customlibs/jsSrc/android/com/konylabs/ffi/N_binarydata.java\nresources/customlibs/jsSrc/tabrcandroid/com/konylabs/ffi/ND_binary_util.java\nresources/customlibs/jsSrc/tabrcandroid/com/konylabs/ffi/N_binarydata.java\nresources/customlibs/jsXml/binary.util.js\nresources/customlibs/jsXml/binary.util.xml\nresources/customlibs/jsXml/binarydata.js\nresources/customlibs/jsXml/binarydata.xml\n\n# Visualizer headless build configuration files\nHeadlessBuild.properties\nHeadlessBuild-Global.properties\n\n# Out-of-the-box NFI bindings for Sync\n/nativeapi.json\n/nativebindings/Android/*/Kony_SyncV2-Android-*.zip\n/nativebindings/Android/*/Kony_SyncV2-Android-*/tern.json\n/nativebindings/iOS/*/Kony_SyncV2-iOS-*.zip\n/nativebindings/iOS/*/Kony_SyncV2-iOS-*/tern.json\n\n# Auxiliary files generated during a build to expose the widget ID's for testing.\nresources/mobile/native/android/values/widgetids.xml\nresources/tablet/native/androidtab/values/widgetids.xml\n\n# NPM dependencies folder which gets created for apps using the \"Nitro\" Cordova integration\n# when you choose NOT to use a globally installed Cordova SDK by unchecking\n# `Project Settings>Application>Cordova Settings>Use globally installed Cordova version`.\nnode_modules\n\n# When using the \"Nitro\" Cordova integration, the Cordova `plugins` and `platforms`\n# directories can be ignored as with any Cordova project.\ncordovatemp\nweb/cordova/plugins\nweb/cordova/platforms\n\n# Auto-generated temporary internationalization files.\nresources/i18n/\n\n# Logs created by building from the command line.\nvelocity.log\n\n# A Logger FFI that gets bundled with each project and is auto-generated by Visualizer with each build.\n\n# A Single Sign-On FFI that gets bundled with each project and is auto-generated by Visualizer with each build.\n\n# A utility FFI that gets bundled with each project and is auto-generated by Visualizer with each build.\n\n# Auto-generated project setting files. Generated the first time you save project settings, even if no changes were made.\n/_clouddata/\n/appSettings.xml\n/context.properties\n/defaults/\n/projectsplash.xml\n/windowsViews.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/konyvisualizer\n","kotlin":"# Created by https://www.toptal.com/developers/gitignore/api/kotlin\n# Edit at https://www.toptal.com/developers/gitignore?templates=kotlin\n\n### Kotlin ###\n# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.nar\n*.ear\n*.zip\n*.tar.gz\n*.rar\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\nreplay_pid*\n\n# End of https://www.toptal.com/developers/gitignore/api/kotlin\n","labview":"# Created by https://www.toptal.com/developers/gitignore/api/labview\n# Edit at https://www.toptal.com/developers/gitignore?templates=labview\n\n### LabVIEW ###\n# Libraries\n*.lvlibp\n*.llb\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n\n# Metadata\n*.aliases\n*.lvlps\n.cache/\n\n# End of https://www.toptal.com/developers/gitignore/api/labview\n","labviewnxg":"# Created by https://www.toptal.com/developers/gitignore/api/labviewnxg\n# Edit at https://www.toptal.com/developers/gitignore?templates=labviewnxg\n\n### LabVIEWNXG ###\n# caches\n.cache/\n*.lvcompile\n*.lvindexcache\n\n# Default build output directory for packages, executables, etc.\nBuilds/\n\n# End of https://www.toptal.com/developers/gitignore/api/labviewnxg\n","lamp":"# Created by https://www.toptal.com/developers/gitignore/api/lamp\n# Edit at https://www.toptal.com/developers/gitignore?templates=lamp\n\n### LAMP ###\n# LAMP Stack Base\n\n### LAMP.PHP Stack ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### LAMP.Linux Stack ###\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n# End of https://www.toptal.com/developers/gitignore/api/lamp\n","laravel":"# Created by https://www.toptal.com/developers/gitignore/api/laravel\n# Edit at https://www.toptal.com/developers/gitignore?templates=laravel\n\n### Laravel ###\n/vendor/\nnode_modules/\nnpm-debug.log\nyarn-error.log\n\n# Laravel 4 specific\nbootstrap/compiled.php\napp/storage/\n\n# Laravel 5 & Lumen specific\npublic/storage\npublic/hot\n\n# Laravel 5 & Lumen specific with changed public path\npublic_html/storage\npublic_html/hot\n\nstorage/*.key\n.env\nHomestead.yaml\nHomestead.json\n/.vagrant\n.phpunit.result.cache\n\n# End of https://www.toptal.com/developers/gitignore/api/laravel\n","latex":"# Created by https://www.toptal.com/developers/gitignore/api/latex\n# Edit at https://www.toptal.com/developers/gitignore?templates=latex\n\n### LaTeX ###\n## Core latex/pdflatex auxiliary files:\n*.aux\n*.lof\n*.log\n*.lot\n*.fls\n*.out\n*.toc\n*.fmt\n*.fot\n*.cb\n*.cb2\n.*.lb\n\n## Intermediate documents:\n*.dvi\n*.xdv\n*-converted-to.*\n# these rules might exclude image files for figures etc.\n# *.ps\n# *.eps\n# *.pdf\n\n## Generated if empty string is given at \"Please type another file name for output:\"\n.pdf\n\n## Bibliography auxiliary files (bibtex/biblatex/biber):\n*.bbl\n*.bcf\n*.blg\n*-blx.aux\n*-blx.bib\n*.run.xml\n\n## Build tool auxiliary files:\n*.fdb_latexmk\n*.synctex\n*.synctex(busy)\n*.synctex.gz\n*.synctex.gz(busy)\n*.pdfsync\n\n## Build tool directories for auxiliary files\n# latexrun\nlatex.out/\n\n## Auxiliary and intermediate files from other packages:\n# algorithms\n*.alg\n*.loa\n\n# achemso\nacs-*.bib\n\n# amsthm\n*.thm\n\n# beamer\n*.nav\n*.pre\n*.snm\n*.vrb\n\n# changes\n*.soc\n\n# comment\n*.cut\n\n# cprotect\n*.cpt\n\n# elsarticle (documentclass of Elsevier journals)\n*.spl\n\n# endnotes\n*.ent\n\n# fixme\n*.lox\n\n# feynmf/feynmp\n*.mf\n*.mp\n*.t[1-9]\n*.t[1-9][0-9]\n*.tfm\n\n#(r)(e)ledmac/(r)(e)ledpar\n*.end\n*.?end\n*.[1-9]\n*.[1-9][0-9]\n*.[1-9][0-9][0-9]\n*.[1-9]R\n*.[1-9][0-9]R\n*.[1-9][0-9][0-9]R\n*.eledsec[1-9]\n*.eledsec[1-9]R\n*.eledsec[1-9][0-9]\n*.eledsec[1-9][0-9]R\n*.eledsec[1-9][0-9][0-9]\n*.eledsec[1-9][0-9][0-9]R\n\n# glossaries\n*.acn\n*.acr\n*.glg\n*.glo\n*.gls\n*.glsdefs\n*.lzo\n*.lzs\n*.slg\n*.slo\n*.sls\n\n# uncomment this for glossaries-extra (will ignore makeindex's style files!)\n# *.ist\n\n# gnuplot\n*.gnuplot\n*.table\n\n# gnuplottex\n*-gnuplottex-*\n\n# gregoriotex\n*.gaux\n*.glog\n*.gtex\n\n# htlatex\n*.4ct\n*.4tc\n*.idv\n*.lg\n*.trc\n*.xref\n\n# hyperref\n*.brf\n\n# knitr\n*-concordance.tex\n# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files\n# *.tikz\n*-tikzDictionary\n\n# listings\n*.lol\n\n# luatexja-ruby\n*.ltjruby\n\n# makeidx\n*.idx\n*.ilg\n*.ind\n\n# minitoc\n*.maf\n*.mlf\n*.mlt\n*.mtc[0-9]*\n*.slf[0-9]*\n*.slt[0-9]*\n*.stc[0-9]*\n\n# minted\n_minted*\n*.pyg\n\n# morewrites\n*.mw\n\n# newpax\n*.newpax\n\n# nomencl\n*.nlg\n*.nlo\n*.nls\n\n# pax\n*.pax\n\n# pdfpcnotes\n*.pdfpc\n\n# sagetex\n*.sagetex.sage\n*.sagetex.py\n*.sagetex.scmd\n\n# scrwfile\n*.wrt\n\n# svg\nsvg-inkscape/\n\n# sympy\n*.sout\n*.sympy\nsympy-plots-for-*.tex/\n\n# pdfcomment\n*.upa\n*.upb\n\n# pythontex\n*.pytxcode\npythontex-files-*/\n\n# tcolorbox\n*.listing\n\n# thmtools\n*.loe\n\n# TikZ & PGF\n*.dpth\n*.md5\n*.auxlock\n\n# titletoc\n*.ptc\n\n# todonotes\n*.tdo\n\n# vhistory\n*.hst\n*.ver\n\n# easy-todo\n*.lod\n\n# xcolor\n*.xcp\n\n# xmpincl\n*.xmpi\n\n# xindy\n*.xdy\n\n# xypic precompiled matrices and outlines\n*.xyc\n*.xyd\n\n# endfloat\n*.ttt\n*.fff\n\n# Latexian\nTSWLatexianTemp*\n\n## Editors:\n# WinEdt\n*.bak\n*.sav\n\n# Texpad\n.texpadtmp\n\n# LyX\n*.lyx~\n\n# Kile\n*.backup\n\n# gummi\n.*.swp\n\n# KBibTeX\n*~[0-9]*\n\n# TeXnicCenter\n*.tps\n\n# auto folder when using emacs and auctex\n./auto/*\n*.el\n\n# expex forward references with \\gathertags\n*-tags.tex\n\n# standalone packages\n*.sta\n\n# Makeindex log files\n*.lpz\n\n# xwatermark package\n*.xwm\n\n# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib\n# option is specified. Footnotes are the stored in a file with suffix Notes.bib.\n# Uncomment the next line to have this generated file ignored.\n#*Notes.bib\n\n### LaTeX Patch ###\n# LIPIcs / OASIcs\n*.vtc\n\n# glossaries\n*.glstex\n\n# End of https://www.toptal.com/developers/gitignore/api/latex\n","lazarus":"# Created by https://www.toptal.com/developers/gitignore/api/lazarus\n# Edit at https://www.toptal.com/developers/gitignore?templates=lazarus\n\n### Lazarus ###\n# Lazarus compiler-generated binaries (safe to delete)\n*.exe\n*.dll\n*.so\n*.dylib\n*.lrs\n*.res\n*.compiled\n*.dbg\n*.ppu\n*.o\n*.or\n*.a\n\n# Lazarus autogenerated files (duplicated info)\n*.rst\n*.rsj\n*.lrt\n\n# Lazarus local files (user-specific info)\n*.lps\n\n# Lazarus backups and unit output folders.\n# These can be changed by user in Lazarus/project options.\nbackup/\n*.bak\nlib/\n\n# Application bundle for Mac OS\n*.app/\n\n# End of https://www.toptal.com/developers/gitignore/api/lazarus\n","leiningen":"# Created by https://www.toptal.com/developers/gitignore/api/leiningen\n# Edit at https://www.toptal.com/developers/gitignore?templates=leiningen\n\n### Leiningen ###\npom.xml\npom.xml.asc\n*.jar\n*.class\n/lib/\n/classes/\n/target/\n/checkouts/\n.lein-deps-sum\n.lein-repl-history\n.lein-plugins/\n.lein-failures\n.nrepl-port\n.cpcache/\n\n# End of https://www.toptal.com/developers/gitignore/api/leiningen\n","lemonstand":"# Created by https://www.toptal.com/developers/gitignore/api/lemonstand\n# Edit at https://www.toptal.com/developers/gitignore?templates=lemonstand\n\n### LemonStand ###\nboot.php\nindex.php\ninstall.php\n/config/*\n!/config/config.php\n/controllers/*\n/init/*\n/logs/*\n/phproad/*\n/temp/*\n/uploaded/*\n/installer_files/*\n/modules/backend/*\n/modules/blog/*\n/modules/cms/*\n/modules/core/*\n/modules/session/*\n/modules/shop/*\n/modules/system/*\n/modules/users/*\n# add content_*.php if you don't want erase client changes to content\n\n# End of https://www.toptal.com/developers/gitignore/api/lemonstand\n","less":"# Created by https://www.toptal.com/developers/gitignore/api/less\n# Edit at https://www.toptal.com/developers/gitignore?templates=less\n\n### Less ###\n*.less\n\n# End of https://www.toptal.com/developers/gitignore/api/less\n","liberosoc":"# Created by https://www.toptal.com/developers/gitignore/api/liberosoc\n# Edit at https://www.toptal.com/developers/gitignore?templates=liberosoc\n\n### LiberoSOC ###\n#Microsemi Libero SOC v11.5\n\n#ignore miscellaneous files in the following directories\nsimulation/*\npackage/*\ncomponent/*\ntooldata/*\nviewdraw/*\n\n#auto-generated constraint log files\nconstraint/*\n!constraint/**.sdc\n!constraint/**.pdc\n\n#synthesis log files\nsynthesis/*\n\n#smartgen log files\nsmartgen/*/*\nsmartgen/smartgen.aws\n!smartgen/*/*.gen\n!smartgen/*/*.vhd\n!smartgen/*/*.v\n\n#designer files\ndesigner/*/*\n\n# End of https://www.toptal.com/developers/gitignore/api/liberosoc\n","librarian-chef":"# Created by https://www.toptal.com/developers/gitignore/api/librarian-chef\n# Edit at https://www.toptal.com/developers/gitignore?templates=librarian-chef\n\n### librarian-chef ###\n# librarian-chef - https://github.com/applicationsonline/librarian-chef\n.tmp/\ncookbooks/\n\n# End of https://www.toptal.com/developers/gitignore/api/librarian-chef\n","libreoffice":"# Created by https://www.toptal.com/developers/gitignore/api/libreoffice\n# Edit at https://www.toptal.com/developers/gitignore?templates=libreoffice\n\n### LibreOffice ###\n# LibreOffice locks\n.~lock.*#\n\n# End of https://www.toptal.com/developers/gitignore/api/libreoffice\n","lighthouseci":"# Created by https://www.toptal.com/developers/gitignore/api/lighthouseci\n# Edit at https://www.toptal.com/developers/gitignore?templates=lighthouseci\n\n### LighthouseCI ###\n# Lighthouse reports\n.lighthouseci/\n\n# End of https://www.toptal.com/developers/gitignore/api/lighthouseci\n","lilypond":"# Created by https://www.toptal.com/developers/gitignore/api/lilypond\n# Edit at https://www.toptal.com/developers/gitignore?templates=lilypond\n\n### Lilypond ###\n*.pdf\n*.ps\n*.midi\n*.mid\n*.log\n*~\n\n# End of https://www.toptal.com/developers/gitignore/api/lilypond\n","linux":"# Created by https://www.toptal.com/developers/gitignore/api/linux\n# Edit at https://www.toptal.com/developers/gitignore?templates=linux\n\n### Linux ###\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n# End of https://www.toptal.com/developers/gitignore/api/linux\n","lithium":"# Created by https://www.toptal.com/developers/gitignore/api/lithium\n# Edit at https://www.toptal.com/developers/gitignore?templates=lithium\n\n### Lithium ###\nlibraries/*\nresources/tmp/*\n\n# End of https://www.toptal.com/developers/gitignore/api/lithium\n","localstack":"# Created by https://www.toptal.com/developers/gitignore/api/localstack\n# Edit at https://www.toptal.com/developers/gitignore?templates=localstack\n\n### LocalStack ###\n# https://github.com/localstack/localstack\n**/.localstack\n\n# End of https://www.toptal.com/developers/gitignore/api/localstack\n","logtalk":"# Created by https://www.toptal.com/developers/gitignore/api/logtalk\n# Edit at https://www.toptal.com/developers/gitignore?templates=logtalk\n\n### Logtalk ###\n# gitignore template for LogTalk, a programming language that builds upon Prolog\n# website: https://logtalk.org/\n\n# Logtalk temporary file directories\n.lgt_tmp/\nlgt_tmp/\n\n# Logtalk default unit testing and doclet results and logs directories\nlogtalk_tester_logs/\nlogtalk_doclet_logs/\n\n# backend Prolog compiler temporary files\n.pl-history\n*.out\n*.xwam\n*.qo\n*.ql\n*.itf\n*.po\n\n# End of https://www.toptal.com/developers/gitignore/api/logtalk\n","lsspice":"# Created by https://www.toptal.com/developers/gitignore/api/lsspice\n# Edit at https://www.toptal.com/developers/gitignore?templates=lsspice\n\n### LSspice ###\n### LTspice ###\n*.raw\n\n# End of https://www.toptal.com/developers/gitignore/api/lsspice\n","ltspice":"# Created by https://www.toptal.com/developers/gitignore/api/ltspice\n# Edit at https://www.toptal.com/developers/gitignore?templates=ltspice\n\n### LTspice ###\n# gitignore template for LTspice\n# website: https://www.analog.com/en/design-center/design-tools-and-calculators/ltspice-simulator.html\n\n# Logfile\n*.log\n\n# Simulation Outputs\n*.raw\n*.fft\n\n# Netlist\n*.net\n\n# End of https://www.toptal.com/developers/gitignore/api/ltspice\n","lua":"# Created by https://www.toptal.com/developers/gitignore/api/lua\n# Edit at https://www.toptal.com/developers/gitignore?templates=lua\n\n### Lua ###\n# Compiled Lua sources\nluac.out\n\n# luarocks build files\n*.src.rock\n*.zip\n*.tar.gz\n\n# Object files\n*.o\n*.os\n*.ko\n*.obj\n*.elf\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n*.def\n*.exp\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n\n# End of https://www.toptal.com/developers/gitignore/api/lua\n","lyx":"# Created by https://www.toptal.com/developers/gitignore/api/lyx\n# Edit at https://www.toptal.com/developers/gitignore?templates=lyx\n\n### LyX ###\n# Ignore LyX backup and autosave files\n# http://www.lyx.org/\n*.lyx~\n*.lyx#\n\n# End of https://www.toptal.com/developers/gitignore/api/lyx\n","macos":"# Created by https://www.toptal.com/developers/gitignore/api/macos\n# Edit at https://www.toptal.com/developers/gitignore?templates=macos\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\r\r\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### macOS Patch ###\n# iCloud generated files\n*.icloud\n\n# End of https://www.toptal.com/developers/gitignore/api/macos\n","magento":"# Created by https://www.toptal.com/developers/gitignore/api/magento\n# Edit at https://www.toptal.com/developers/gitignore?templates=magento\n\n### Magento ###\n#--------------------------#\n# Magento Default Files #\n\n/PATCH_*.sh\n\n/app/etc/local.xml\n\n/media/*\n!/media/.htaccess\n\n!/media/customer\n/media/customer/*\n!/media/customer/.htaccess\n\n!/media/dhl\n/media/dhl/*\n!/media/dhl/logo.jpg\n\n!/media/downloadable\n/media/downloadable/*\n!/media/downloadable/.htaccess\n\n!/media/xmlconnect\n/media/xmlconnect/*\n\n!/media/xmlconnect/custom\n/media/xmlconnect/custom/*\n!/media/xmlconnect/custom/ok.gif\n\n!/media/xmlconnect/original\n/media/xmlconnect/original/*\n!/media/xmlconnect/original/ok.gif\n\n!/media/xmlconnect/system\n/media/xmlconnect/system/*\n!/media/xmlconnect/system/ok.gif\n\n/var/*\n!/var/.htaccess\n\n!/var/package\n/var/package/*\n!/var/package/*.xml\n\n\n### Magento Patch ###\napp/bootstrap.php\nshell/.htaccess\ndev/tests/functional\n\n# End of https://www.toptal.com/developers/gitignore/api/magento\n","magento1":"# Created by https://www.toptal.com/developers/gitignore/api/magento1\n# Edit at https://www.toptal.com/developers/gitignore?templates=magento1\n\n### Magento1 ###\n# gitignore template for Magento v1 projects\n#\n# It is recommended that you use `Magento.gitignore` as this is the latest version\n\n/PATCH_*.sh\n\n/app/etc/local.xml\n\n/media/*\n!/media/.htaccess\n\n!/media/customer\n/media/customer/*\n!/media/customer/.htaccess\n\n!/media/dhl\n/media/dhl/*\n!/media/dhl/logo.jpg\n\n!/media/downloadable\n/media/downloadable/*\n!/media/downloadable/.htaccess\n\n!/media/xmlconnect\n/media/xmlconnect/*\n\n!/media/xmlconnect/custom\n/media/xmlconnect/custom/*\n!/media/xmlconnect/custom/ok.gif\n\n!/media/xmlconnect/original\n/media/xmlconnect/original/*\n!/media/xmlconnect/original/ok.gif\n\n!/media/xmlconnect/system\n/media/xmlconnect/system/*\n!/media/xmlconnect/system/ok.gif\n\n/var/*\n!/var/.htaccess\n\n!/var/package\n/var/package/*\n!/var/package/*.xml\n\n\n# End of https://www.toptal.com/developers/gitignore/api/magento1\n","magento2":"# Created by https://www.toptal.com/developers/gitignore/api/magento2\n# Edit at https://www.toptal.com/developers/gitignore?templates=magento2\n\n### Magento2 ###\n/.buildpath\n/.cache\n/.metadata\n/.project\n/.settings\n/.vscode\natlassian*\n/nbproject\n/robots.txt\n/pub/robots.txt\n/sitemap\n/sitemap.xml\n/pub/sitemap\n/pub/sitemap.xml\n/.idea\n/.gitattributes\n/app/config_sandbox\n/app/etc/config.php\n/app/etc/env.php\n/app/code/Magento/TestModule*\n/lib/internal/flex/uploader/.actionScriptProperties\n/lib/internal/flex/uploader/.flexProperties\n/lib/internal/flex/uploader/.project\n/lib/internal/flex/uploader/.settings\n/lib/internal/flex/varien/.actionScriptProperties\n/lib/internal/flex/varien/.flexLibProperties\n/lib/internal/flex/varien/.project\n/lib/internal/flex/varien/.settings\n/node_modules\n/.grunt\n/Gruntfile.js\n/package.json\n/.php_cs\n/.php_cs.cache\n/grunt-config.json\n/pub/media/*.*\n!/pub/media/.htaccess\n/pub/media/attribute/*\n!/pub/media/attribute/.htaccess\n/pub/media/analytics/*\n/pub/media/catalog/*\n!/pub/media/catalog/.htaccess\n/pub/media/customer/*\n!/pub/media/customer/.htaccess\n/pub/media/downloadable/*\n!/pub/media/downloadable/.htaccess\n/pub/media/favicon/*\n/pub/media/import/*\n!/pub/media/import/.htaccess\n/pub/media/logo/*\n/pub/media/custom_options/*\n!/pub/media/custom_options/.htaccess\n/pub/media/theme/*\n/pub/media/theme_customization/*\n!/pub/media/theme_customization/.htaccess\n/pub/media/wysiwyg/*\n!/pub/media/wysiwyg/.htaccess\n/pub/media/tmp/*\n!/pub/media/tmp/.htaccess\n/pub/media/captcha/*\n/pub/static/*\n!/pub/static/.htaccess\n\n/var/*\n!/var/.htaccess\n/vendor/*\n!/vendor/.htaccess\n/generated/*\n!/generated/.htaccess\n.DS_Store\n\n# End of https://www.toptal.com/developers/gitignore/api/magento2\n","magic-xpa":"# Created by https://www.toptal.com/developers/gitignore/api/magic-xpa\n# Edit at https://www.toptal.com/developers/gitignore?templates=magic-xpa\n\n### Magic-xpa ###\n# refer to http://www.magicsoftware.com/magic-xpa-application-platform\n# ProgramHeaders.xml serves as a cache file for program headers which is rebuilt if non existent\nProgramHeaders.xml\n# DataSourcesIndex.xml serves as a cache file for header information of DataSources.xml which is rebuilt if non existent\nDataSourcesIndex.xml\n# ignores cross reference results stored in project directory\n*.XRF\n# ignores studio options and their backups\n*.opt\n*.optback\n# sometimes Magic creates backups of different xml files, not deleted on crashes\n*_BCK.xml\n# if a project is opened, a .lock file is created in the project directory to prevent multiple opens, ignored.\n*.lock\n\n# End of https://www.toptal.com/developers/gitignore/api/magic-xpa\n","matlab":"# Created by https://www.toptal.com/developers/gitignore/api/matlab\n# Edit at https://www.toptal.com/developers/gitignore?templates=matlab\n\n### MATLAB ###\n# Windows default autosave extension\n*.asv\n\n# OSX / *nix default autosave extension\n*.m~\n\n# Compiled MEX binaries (all platforms)\n*.mex*\n\n# Packaged app and toolbox files\n*.mlappinstall\n*.mltbx\n\n# Generated helpsearch folders\nhelpsearch*/\n\n# Simulink code generation folders\nslprj/\nsccprj/\n\n# Matlab code generation folders\ncodegen/\n\n# Simulink autosave extension\n*.autosave\n\n# Simulink cache files\n*.slxc\n\n# Octave session info\noctave-workspace\n\n# End of https://www.toptal.com/developers/gitignore/api/matlab\n","maven":"# Created by https://www.toptal.com/developers/gitignore/api/maven\n# Edit at https://www.toptal.com/developers/gitignore?templates=maven\n\n### Maven ###\ntarget/\npom.xml.tag\npom.xml.releaseBackup\npom.xml.versionsBackup\npom.xml.next\nrelease.properties\ndependency-reduced-pom.xml\nbuildNumber.properties\n.mvn/timing.properties\n# https://github.com/takari/maven-wrapper#usage-without-binary-jar\n.mvn/wrapper/maven-wrapper.jar\n\n# Eclipse m2e generated files\n# Eclipse Core\n.project\n# JDT-specific (Eclipse Java Development Tools)\n.classpath\n\n# End of https://www.toptal.com/developers/gitignore/api/maven\n","mavensmate":"# Created by https://www.toptal.com/developers/gitignore/api/mavensmate\n# Edit at https://www.toptal.com/developers/gitignore?templates=mavensmate\n\n### MavensMate ###\n#Sublime Text exclusions\n*.sublime-workspace\n*.sublime-project\n*.sublime-settings\n\n#MavensMate exclusions\nconfig/*\nmm.log\n\n#OPTIONAL Apex scripts exclusion\n# - Rule 1 excludes the contents of the apex-scripts folder\n# - Rule 2 adds in NAMED apex scripts\n# Together: excludes scripts run though MavensMate’s\n# execute anonymous functionality, while keeping named scripts\napex-scripts/*\n!apex-scripts/*.cls\n\n# End of https://www.toptal.com/developers/gitignore/api/mavensmate\n","mdbook":"# Created by https://www.toptal.com/developers/gitignore/api/mdbook\n# Edit at https://www.toptal.com/developers/gitignore?templates=mdbook\n\n### MdBook ###\nbook\n\n\n# End of https://www.toptal.com/developers/gitignore/api/mdbook\n","mean":"# Created by https://www.toptal.com/developers/gitignore/api/mean\n# Edit at https://www.toptal.com/developers/gitignore?templates=mean\n\n### MEAN ###\n# MEAN Stack Base\n\n### MEAN.Anglar Stack ###\n## Angular ##\n# compiled output\ndist/\ntmp/\napp/**/*.js\napp/**/*.js.map\n\n# dependencies\nnode_modules/\nbower_components/\n\n# IDEs and editors\n.idea/\n\n# misc\n.sass-cache/\nconnect.lock/\ncoverage/\nlibpeerconnection.log/\nnpm-debug.log\ntestem.log\ntypings/\n.angular/\n\n# e2e\ne2e/*.js\ne2e/*.map\n\n# System Files\n.DS_Store/\n\n### MEAN.Node Stack ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# End of https://www.toptal.com/developers/gitignore/api/mean\n","mercurial":"# Created by https://www.toptal.com/developers/gitignore/api/mercurial\n# Edit at https://www.toptal.com/developers/gitignore?templates=mercurial\n\n### Mercurial ###\n.hg/\n.hgignore\n.hgsigs\n.hgsub\n.hgsubstate\n.hgtags\n\n# End of https://www.toptal.com/developers/gitignore/api/mercurial\n","mercury":"# Created by https://www.toptal.com/developers/gitignore/api/mercury\n# Edit at https://www.toptal.com/developers/gitignore?templates=mercury\n\n### Mercury ###\nMercury/\nMercury.modules\n*.mh\n*.err\n*.init\n*.dll\n*.exe\n*.a\n*.so\n*.dylib\n*.beams\n*.d\n*.c_date\n\n# End of https://www.toptal.com/developers/gitignore/api/mercury\n","meson":"# Created by https://www.toptal.com/developers/gitignore/api/meson\n# Edit at https://www.toptal.com/developers/gitignore?templates=meson\n\n### Meson ###\n# subproject directories\n/subprojects/*\n!/subprojects/*.wrap\n\n# Meson Directories\nmeson-logs\nmeson-private\n\n# Meson Files\nmeson_benchmark_setup.dat\nmeson_test_setup.dat\nsanitycheckcpp.cc # C++ specific\nsanitycheckcpp.exe # C++ specific\n\n# Ninja\nbuild.ninja\n.ninja_deps\n.ninja_logs\n\n# Misc\ncompile_commands.json\n\n# End of https://www.toptal.com/developers/gitignore/api/meson\n","metals":"# Created by https://www.toptal.com/developers/gitignore/api/metals\n# Edit at https://www.toptal.com/developers/gitignore?templates=metals\n\n### Metals ###\n.metals/\n.bloop/\nproject/**/metals.sbt\n\n# End of https://www.toptal.com/developers/gitignore/api/metals\n","metalsmith":"# Created by https://www.toptal.com/developers/gitignore/api/metalsmith\n# Edit at https://www.toptal.com/developers/gitignore?templates=metalsmith\n\n### Metalsmith ###\n# gitignore template for the Metalsmith, a simple, pluggable static site generator\n# website: https://metalsmith.io/\n\nbuild\n\n# End of https://www.toptal.com/developers/gitignore/api/metalsmith\n","metaprogrammingsystem":"# Created by https://www.toptal.com/developers/gitignore/api/metaprogrammingsystem\n# Edit at https://www.toptal.com/developers/gitignore?templates=metaprogrammingsystem\n\n### MetaProgrammingSystem ###\nworkspace.xml\njunitvmwatcher*.properties\nbuild.properties\n\n# generated java classes and java source files\n# manually add any custom artifacts that can't be generated from the models\n# http://confluence.jetbrains.com/display/MPSD25/HowTo+--+MPS+and+Git\nclasses_gen\nsource_gen\nsource_gen.caches\n\n# generated test code and test results\ntest_gen\ntest_gen.caches\nTEST-*.xml\njunit*.properties\n\n# End of https://www.toptal.com/developers/gitignore/api/metaprogrammingsystem\n","meteor":"# Created by https://www.toptal.com/developers/gitignore/api/meteor\n# Edit at https://www.toptal.com/developers/gitignore?templates=meteor\n\n### Meteor ###\n# gitignore template for the Meteor framework\n# website: https://www.meteor.com/\n#\n# Recommended template: Node.gitignore\n\n# protect api keys in setting json\nsettings-production.json\nsettings.json\n\n# protect your mup.json settings\nmup.json\nmup.js\n\n# End of https://www.toptal.com/developers/gitignore/api/meteor\n","meteorjs":"# Created by https://www.toptal.com/developers/gitignore/api/meteorjs\n# Edit at https://www.toptal.com/developers/gitignore?templates=meteorjs\n\n### MeteorJS ###\n# default meteor build and local packages\n.meteor/local\n\n# meteor settings file\nsettings.json\n\n# meteor build output files\n*.tar.gz\n\n# general swp files from vim\n*.swp\n\n# End of https://www.toptal.com/developers/gitignore/api/meteorjs\n","microsoftoffice":"# Created by https://www.toptal.com/developers/gitignore/api/microsoftoffice\n# Edit at https://www.toptal.com/developers/gitignore?templates=microsoftoffice\n\n### MicrosoftOffice ###\n*.tmp\n\n# Word temporary\n~$*.doc*\n\n# Word Auto Backup File\nBackup of *.doc*\n\n# Excel temporary\n~$*.xls*\n\n# Excel Backup File\n*.xlk\n\n# PowerPoint temporary\n~$*.ppt*\n\n# Visio autosave temporary files\n*.~vsd*\n\n# End of https://www.toptal.com/developers/gitignore/api/microsoftoffice\n","mikroc":"# Created by https://www.toptal.com/developers/gitignore/api/mikroc\n# Edit at https://www.toptal.com/developers/gitignore?templates=mikroc\n\n### MikroC ###\n# Backup\n*.pdsbak\n\n# Debug\nDebug\n\n# Generated files\n*.workspace\n*.log\n*.bmk\n*.brk\n*.cfg\n*.dbg\n*.dct\n*.dlt\n*.hex\n*.lst\n*.dic\n*_callertable.txt\n*.user.dic\n*.asm\n*.c.ini\n*.h.ini\n*.cp\n*.mcl\n\n# End of https://www.toptal.com/developers/gitignore/api/mikroc\n","mill":"# Created by https://www.toptal.com/developers/gitignore/api/mill\n# Edit at https://www.toptal.com/developers/gitignore?templates=mill\n\n### Mill ###\nout\n\n# End of https://www.toptal.com/developers/gitignore/api/mill\n","moban":"# Created by https://www.toptal.com/developers/gitignore/api/moban\n# Edit at https://www.toptal.com/developers/gitignore?templates=moban\n\n### Moban ###\n.moban.hashes\n\n# End of https://www.toptal.com/developers/gitignore/api/moban\n","modelsim":"# Created by https://www.toptal.com/developers/gitignore/api/modelsim\n# Edit at https://www.toptal.com/developers/gitignore?templates=modelsim\n\n### ModelSim ###\n# ignore ModelSim generated files and directories (temp files and so on)\n[_@]*\n\n# ignore compilation output of ModelSim\n*.mti\n*.dat\n*.dbs\n*.psm\n*.bak\n*.cmp\n*.jpg\n*.html\n*.bsf\n\n# ignore simulation output of ModelSim\nwlf*\n*.wlf\n*.vstf\n*.ucdb\ncov*/\ntranscript*\nsc_dpiheader.h\nvsim.dbg\n\n# End of https://www.toptal.com/developers/gitignore/api/modelsim\n","modx":"# Created by https://www.toptal.com/developers/gitignore/api/modx\n# Edit at https://www.toptal.com/developers/gitignore?templates=modx\n\n### MODX ###\n/core/cache/*\n!/core/cache/.gitkeep\n\n# Depends on how you version your MODX site\n/core/packages/*/*\n\n/core/import/*\n/core/export/*\n\n# End of https://www.toptal.com/developers/gitignore/api/modx\n","momentics":"# Created by https://www.toptal.com/developers/gitignore/api/momentics\n# Edit at https://www.toptal.com/developers/gitignore?templates=momentics\n\n### Momentics ###\n# Built files\nx86/\narm/\narm-p/\ntranslations/*.qm\n\n# IDE settings\n.settings/\n\n# End of https://www.toptal.com/developers/gitignore/api/momentics\n","monodevelop":"# Created by https://www.toptal.com/developers/gitignore/api/monodevelop\n# Edit at https://www.toptal.com/developers/gitignore?templates=monodevelop\n\n### MonoDevelop ###\n#User Specific\n*.userprefs\n*.usertasks\n\n#Mono Project Files\n*.pidb\n*.resources\ntest-results/\n\n# End of https://www.toptal.com/developers/gitignore/api/monodevelop\n","mplabx":"# Created by https://www.toptal.com/developers/gitignore/api/mplabx\n# Edit at https://www.toptal.com/developers/gitignore?templates=mplabx\n\n### MPLabX ###\n#Ignore List for Microchip's MPLAB X IDE\n#It's a form of NetBeans with vendor specific changes\n#Taken from zeha on GIST https://gist.github.com/zeha/5999375\n#Updated by Cristian Cristea (https://github.com/cristiancristea00)\n\n*.d\n*.pre\n*.p1\n*.lst\n*.sym\n*.obj\n*.o\n*.sdb\n*.obj.dmp\n*.mk\n*.map\n*.properties\n*.production\n*.debug\nhtml/\nnbproject/private/\nnbproject/Package-*.bash\nbuild/\nnbbuild/\ndist/\nnbdist/\nnbactions.xml\nnb-configuration.xml\nfunclist\nnbproject/Makefile-*\ndisassembly/\n.generated_files/\n\n# End of https://www.toptal.com/developers/gitignore/api/mplabx\n","mule":"# Created by https://www.toptal.com/developers/gitignore/api/mule\n# Edit at https://www.toptal.com/developers/gitignore?templates=mule\n\n### mule ###\n# gitignore template for Mule projects\n# website: https://www.mulesoft.com/platform/enterprise-integration\n#\n# Recommended template: Mule.gitignore\n\n# Java defaults (https://github.com/github/gitignore/blob/master/Java.gitignore) #\n# --- #\n*.class\n# Package Files #\n*.jar\n*.war\n*.ear\n# --- #\n# Eclipse-specific (https://github.com/github/gitignore/blob/master/Global/Eclipse.gitignore) #\n# --- #\n*.pydevproject\n.metadata\nbin/**\ntmp/**\ntmp/**/*\n*.tmp\n*.bak\n*.swp\n*~.nib\nlocal.properties\n.settings/\n.loadpath\n# You may want to remove the sharp symbols here if you are using Maven\n.project\n.classpath\n# External tool builders\n.externalToolBuilders/\n# Locally stored \"Eclipse launch configurations\"\n*.launch\n# CDT-specific\n.cproject\n# PDT-specific\n.buildpath\n# --------------- #\n# Studio-specific #\n# --------------- #\n.studio/\nflows/\ntarget/\n**/*.mflow\ncatalog/\nreports/\n.mule/\n\n# End of https://www.toptal.com/developers/gitignore/api/mule\n","nanoc":"# Created by https://www.toptal.com/developers/gitignore/api/nanoc\n# Edit at https://www.toptal.com/developers/gitignore?templates=nanoc\n\n### Nanoc ###\n# For projects using Nanoc (http://nanoc.ws/)\n\n# Default location for output (needs to match output_dir's value found in nanoc.yaml)\noutput/\n\n# Temporary file directory\ntmp/nanoc/\n\n# Crash Log\ncrash.log\n\n# End of https://www.toptal.com/developers/gitignore/api/nanoc\n","nativescript":"# Created by https://www.toptal.com/developers/gitignore/api/nativescript\n# Edit at https://www.toptal.com/developers/gitignore?templates=nativescript\n\n### NativeScript ###\n/platforms\n/hooks\n/node_modules\n/lib\n/debugger\n/dist\n\n/app/**/*.map\n# shouldn't ignore this for vanilla nativescript\n# /app/**/*.js\n\n.DS_Store\n**/*.log\ntags\n\ners.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/nativescript\n","ncrunch":"# Created by https://www.toptal.com/developers/gitignore/api/ncrunch\n# Edit at https://www.toptal.com/developers/gitignore?templates=ncrunch\n\n### NCrunch ###\n# NCrunch\n*.ncrunch*.user\n_NCrunch_*\n*.crunch.xml\nnCrunchTemp_*\n\n# End of https://www.toptal.com/developers/gitignore/api/ncrunch\n","nesc":"# Created by https://www.toptal.com/developers/gitignore/api/nesc\n# Edit at https://www.toptal.com/developers/gitignore?templates=nesc\n\n### NesC ###\n# telosB build folder\nbuild/\n\n# End of https://www.toptal.com/developers/gitignore/api/nesc\n","netbeans":"# Created by https://www.toptal.com/developers/gitignore/api/netbeans\n# Edit at https://www.toptal.com/developers/gitignore?templates=netbeans\n\n### NetBeans ###\n**/nbproject/private/\n**/nbproject/Makefile-*.mk\n**/nbproject/Package-*.bash\nbuild/\nnbbuild/\ndist/\nnbdist/\n.nb-gradle/\n\n# End of https://www.toptal.com/developers/gitignore/api/netbeans\n","nette":"# Created by https://www.toptal.com/developers/gitignore/api/nette\n# Edit at https://www.toptal.com/developers/gitignore?templates=nette\n\n### Nette ###\n# Nette - http://nette.org\n# Ignore /log and /temp directories content but commit .htaccess settings\n/log/*\n!/log/.htaccess\n/temp/*\n!/temp/.htaccess\n\n# End of https://www.toptal.com/developers/gitignore/api/nette\n","nextjs":"# Created by https://www.toptal.com/developers/gitignore/api/nextjs\n# Edit at https://www.toptal.com/developers/gitignore?templates=nextjs\n\n### NextJS ###\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\n# End of https://www.toptal.com/developers/gitignore/api/nextjs\n","nikola":"# Created by https://www.toptal.com/developers/gitignore/api/nikola\n# Edit at https://www.toptal.com/developers/gitignore?templates=nikola\n\n### Nikola ###\n# gitignore template for Nikola static site generator\n# website: https://getnikola.com/\n\n.doit.db\n*.py[cod]\ncache/\noutput/\n\n# End of https://www.toptal.com/developers/gitignore/api/nikola\n","nim":"# Created by https://www.toptal.com/developers/gitignore/api/nim\n# Edit at https://www.toptal.com/developers/gitignore?templates=nim\n\n### Nim ###\nnimcache/\nnimblecache/\nhtmldocs/\n\n# End of https://www.toptal.com/developers/gitignore/api/nim\n","ninja":"# Created by https://www.toptal.com/developers/gitignore/api/ninja\n# Edit at https://www.toptal.com/developers/gitignore?templates=ninja\n\n### Ninja ###\n.ninja_deps\n.ninja_log\n\n# End of https://www.toptal.com/developers/gitignore/api/ninja\n","node":"# Created by https://www.toptal.com/developers/gitignore/api/node\n# Edit at https://www.toptal.com/developers/gitignore?templates=node\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n### Node Patch ###\n# Serverless Webpack directories\n.webpack/\n\n# Optional stylelint cache\n\n# SvelteKit build / generate output\n.svelte-kit\n\n# End of https://www.toptal.com/developers/gitignore/api/node\n","nodechakratimetraveldebug":"# Created by https://www.toptal.com/developers/gitignore/api/nodechakratimetraveldebug\n# Edit at https://www.toptal.com/developers/gitignore?templates=nodechakratimetraveldebug\n\n### NodeChakraTimeTravelDebug ###\n# gitignore template for NodeChakra Time Travel Debug\n# website: https://marketplace.visualstudio.com/items?itemName=ttd-trace-tools.node-chakracore-time-travel-debugger\n\n# Ignore time-travel trace files\n# https://github.com/mrkmarron/node-chakracore-time-travel-debugger#notes\n_ttd_log_\n\n# End of https://www.toptal.com/developers/gitignore/api/nodechakratimetraveldebug\n","nohup":"# Created by https://www.toptal.com/developers/gitignore/api/nohup\n# Edit at https://www.toptal.com/developers/gitignore?templates=nohup\n\n### Nohup ###\nnohup.out\n\n# End of https://www.toptal.com/developers/gitignore/api/nohup\n","notepadpp":"# Created by https://www.toptal.com/developers/gitignore/api/notepadpp\n# Edit at https://www.toptal.com/developers/gitignore?templates=notepadpp\n\n### NotepadPP ###\n# Notepad++ backups #\n*.bak\n\n# End of https://www.toptal.com/developers/gitignore/api/notepadpp\n","nova":"# Created by https://www.toptal.com/developers/gitignore/api/nova\n# Edit at https://www.toptal.com/developers/gitignore?templates=nova\n\n### nova ###\n.nova/*\n\n# End of https://www.toptal.com/developers/gitignore/api/nova\n","now":"# Created by https://www.toptal.com/developers/gitignore/api/now\n# Edit at https://www.toptal.com/developers/gitignore?templates=now\n\n### now ###\n.now\n.vercel\n\n# End of https://www.toptal.com/developers/gitignore/api/now\n","nuxtjs":"# Created by https://www.toptal.com/developers/gitignore/api/nuxtjs\n# Edit at https://www.toptal.com/developers/gitignore?templates=nuxtjs\n\n### NuxtJS ###\n# Generated dirs\ndist\n.nuxt\n.nuxt-*\n.output\n.gen\n\n# Node dependencies\nnode_modules\n\n# System files\n*.log\n\n# End of https://www.toptal.com/developers/gitignore/api/nuxtjs\n","nwjs":"# Created by https://www.toptal.com/developers/gitignore/api/nwjs\n# Edit at https://www.toptal.com/developers/gitignore?templates=nwjs\n\n### NWjs ###\n# gitignore template for NW.js projects\n# website: https://nwjs.io/\n\n# Seen in standard and sdk versions\ncredits.html\nlocales/\nlibEGL.dll\nlibGLEv2.dll\nnode.dll\nnw.dll\nnw.exe\nnatives_blob.bin\nnw_100_percent.pak\nnw_200_percent.pak\nnw_elf.dll\nsnapshot_blob.bin\nresources.pak\n\n# Seen only in standard\nd3dcompiler_47.dll\nffmpeg.dll\nicudtl.dat\n\n# Seen only in sdk\npnacl/\nchromedriver.exe\nnacl_irt_x86_64.nexe\nnwjc.exe\npayload.exe\n\n# End of https://www.toptal.com/developers/gitignore/api/nwjs\n","objective-c":"# Created by https://www.toptal.com/developers/gitignore/api/objective-c\n# Edit at https://www.toptal.com/developers/gitignore?templates=objective-c\n\n### Objective-C ###\n# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n## User settings\nxcuserdata/\n\n## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)\n*.xcscmblueprint\n*.xccheckout\n\n## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)\nbuild/\nDerivedData/\n*.moved-aside\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\n\n## Obj-C/Swift specific\n*.hmap\n\n## App packaging\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n# CocoaPods\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n# Pods/\n# Add this line if you want to avoid checking in source code from the Xcode workspace\n# *.xcworkspace\n\n# Carthage\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build/\n\n# fastlane\n# It is recommended to not store the screenshots in the git repo.\n# Instead, use fastlane to re-generate the screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots/**/*.png\nfastlane/test_output\n\n# Code Injection\n# After new code Injection tools there's a generated folder /iOSInjectionProject\n# https://github.com/johnno1962/injectionforxcode\n\niOSInjectionProject/\n\n### Objective-C Patch ###\n\n# End of https://www.toptal.com/developers/gitignore/api/objective-c\n","obsidian":"# Created by https://www.toptal.com/developers/gitignore/api/obsidian\n# Edit at https://www.toptal.com/developers/gitignore?templates=obsidian\n\n### Obsidian ###\n# config dir\n.obsidian/\n\n# End of https://www.toptal.com/developers/gitignore/api/obsidian\n","ocaml":"# Created by https://www.toptal.com/developers/gitignore/api/ocaml\n# Edit at https://www.toptal.com/developers/gitignore?templates=ocaml\n\n### OCaml ###\n*.annot\n*.cmo\n*.cma\n*.cmi\n*.a\n*.o\n*.cmx\n*.cmxs\n*.cmxa\n\n# ocamlbuild working directory\n_build/\n\n# ocamlbuild targets\n*.byte\n*.native\n\n# oasis generated files\nsetup.data\nsetup.log\n\n# Merlin configuring file for Vim and Emacs\n.merlin\n\n# Dune generated files\n*.install\n\n# Local OPAM switch\n_opam/\n\n# End of https://www.toptal.com/developers/gitignore/api/ocaml\n","octave":"# Created by https://www.toptal.com/developers/gitignore/api/octave\n# Edit at https://www.toptal.com/developers/gitignore?templates=octave\n\n### Octave ###\n# Windows default autosave extension\n*.asv\n\n# OSX / *nix default autosave extension\n*.m~\n\n# Compiled MEX binaries (all platforms)\n*.mex*\n\n# Packaged app and toolbox files\n*.mlappinstall\n*.mltbx\n\n# Generated helpsearch folders\nhelpsearch*/\n\n# Simulink code generation folders\nslprj/\nsccprj/\n\n# Matlab code generation folders\ncodegen/\n\n# Simulink autosave extension\n*.autosave\n\n# Simulink cache files\n*.slxc\n\n# Octave session info\noctave-workspace\n\n# End of https://www.toptal.com/developers/gitignore/api/octave\n","octobercms":"# Created by https://www.toptal.com/developers/gitignore/api/octobercms\n# Edit at https://www.toptal.com/developers/gitignore?templates=octobercms\n\n### OctoberCms ###\n/bootstrap/compiled.php\n/vendor\ncomposer.phar\n.DS_Store\n.idea\n.env\n.env.*.php\n.env.php\nphp_errors.log\nnginx-error.log\nnginx-access.log\nnginx-ssl.access.log\nnginx-ssl.error.log\nphp-errors.log\nsftp-config.json\nselenium.php\n\n# for netbeans compiler\nnbproject\n\n# End of https://www.toptal.com/developers/gitignore/api/octobercms\n","opa":"# Created by https://www.toptal.com/developers/gitignore/api/opa\n# Edit at https://www.toptal.com/developers/gitignore?templates=opa\n\n### Opa ###\n_build\n_tracks\n\nopa-debug-js\n\n*.opp\n*.opx\n*.opx.broken\n*.dump\n*.api\n*.api-txt\n*.exe\n*.log\n\n# End of https://www.toptal.com/developers/gitignore/api/opa\n","opencart":"# Created by https://www.toptal.com/developers/gitignore/api/opencart\n# Edit at https://www.toptal.com/developers/gitignore?templates=opencart\n\n### OpenCart ###\n.htaccess\n/config.php\nadmin/config.php\n\n!index.html\n\ndownload/\nimage/data/\nimage/cache/\nsystem/cache/\nsystem/logs/\n\nsystem/storage/\n\n# vQmod log files\nvqmod/logs/*\n# vQmod cache files\nvqmod/vqcache/*\nvqmod/checked.cache\nvqmod/mods.cache\n\n# End of https://www.toptal.com/developers/gitignore/api/opencart\n","opencv":"# Created by https://www.toptal.com/developers/gitignore/api/opencv\n# Edit at https://www.toptal.com/developers/gitignore?templates=opencv\n\n### OpenCV ###\n#OpenCV for Mac and Linux\n#build and release folders\n*/CMakeFiles\n*/CMakeCache.txt\n*/Makefile\n*/cmake_install.cmake\n.DS_Store\n\n# End of https://www.toptal.com/developers/gitignore/api/opencv\n","openfoam":"# Created by https://www.toptal.com/developers/gitignore/api/openfoam\n# Edit at https://www.toptal.com/developers/gitignore?templates=openfoam\n\n### OpenFOAM ###\n# Timestep directories\n0.*\n[1-9]*\n\n# Fields that must get ignored\n*/phi*\n*/meshPhi*\n\n# Decomposed domain\nprocessor*\n\n# logfiles\nlog.*\n*log\n\n# paraview/ParaFoam\n*.foam\n*.OpenFOAM\n*.pvsm\n\n# mesh data that is no dictionary\nconstant/polyMesh/points*\nconstant/polyMesh/faces*\nconstant/polyMesh/owner*\nconstant/polyMesh/neighbour*\nconstant/polyMesh/boundary*\nconstant/polyMesh/sets\nconstant/polyMesh/*\n\n# Zones and levels\n*Level*\n*Zone*\n\n# snappyHexMesh files that are not snappyHexMeshDict\nconstant/polyMesh/refinementHistory*\nconstant/polyMesh/surfaceIndex*\nconstant/triSurface\n\n# function object and post-processing data\nforces\npostProcessing\n\n# Needed when C++ code is built using the OpenFOAM library, since it does not\n# use a \"normal\" build tool (make, cmake, ..).\nlnInclude\n*.dep\nlinux*\nDarwin*\n\n#exclude important folders and files:\n!0/\n!constant/thermophysicalProperties\n!constant/turbulenceProperties\n!system/\n!.gitignore\n\n# End of https://www.toptal.com/developers/gitignore/api/openfoam\n","openframeworks":"# Created by https://www.toptal.com/developers/gitignore/api/openframeworks\n# Edit at https://www.toptal.com/developers/gitignore?templates=openframeworks\n\n### OpenFrameworks ###\n# ignore generated binaries\n# but not the data folder\n\n/bin/*\n!/bin/data/\n\n# general\n\n[Bb]uild/\n[Oo]bj/\n*.o\n[Dd]ebug*/\n[Rr]elease*/\n*.mode*\n*.app/\n*.pyc\n.svn/\n*.log\n\n# IDE files which should\n# be ignored\n\n# XCode\n*.pbxuser\n*.perspective\n*.perspectivev3\n*.mode1v3\n*.mode2v3\n# XCode 4\nxcuserdata\n*.xcworkspace\n\n# Code::Blocks\n*.depend\n*.layout\n\n# Visual Studio\n*.sdf\n*.opensdf\n*.suo\n*.pdb\n*.ilk\n*.aps\nipch/\n\n# Eclipse\n.metadata\nlocal.properties\n.externalToolBuilders\n\n# operating system\n\n# Linux\n*~\n# KDE\n.directory\n.AppleDouble\n\n# OSX\n.DS_Store\n*.swp\n*~.nib\n# Thumbnails\n._*\n\n# Windows\n# Image file caches\nThumbs.db\n# Folder config file\nDesktop.ini\n\n# Android\n.csettings\n\n# End of https://www.toptal.com/developers/gitignore/api/openframeworks\n","openframeworks+visualstudio":"# Created by https://www.toptal.com/developers/gitignore/api/openframeworks+visualstudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=openframeworks+visualstudio\n\n### OpenFrameworks+VisualStudio ###\n# ignore generated binaries\n# but not the data folder\n\n/bin/*\n!/bin/data/\n\n# general\n\n[Bb]uild/\n[Oo]bj/\n*.o\n[Dd]ebug*/\n[Rr]elease*/\n*.mode*\n*.app/\n*.pyc\n.svn/\n*.log\n\n# IDE files which should\n# be ignored\n\n# XCode\n*.pbxuser\n*.perspective\n*.perspectivev3\n*.mode1v3\n*.mode2v3\n# XCode 4\nxcuserdata\n*.xcworkspace\n\n# Code::Blocks\n*.depend\n*.layout\n\n# Visual Studio\n*.sdf\n*.opensdf\n*.suo\n*.pdb\n*.ilk\n*.aps\nipch/\n\n# Eclipse\n.metadata\nlocal.properties\n.externalToolBuilders\n\n# operating system\n\n# Linux\n*~\n# KDE\n.directory\n.AppleDouble\n\n# OSX\n.DS_Store\n*.swp\n*~.nib\n# Thumbnails\n._*\n\n# Windows\n# Image file caches\nThumbs.db\n# Folder config file\nDesktop.ini\n\n# Android\n.csettings\n\n### OpenFrameworks+VisualStudio Patch ###\n.vs/\n*.ncb\n*.opendb\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# End of https://www.toptal.com/developers/gitignore/api/openframeworks+visualstudio\n","oracleforms":"# Created by https://www.toptal.com/developers/gitignore/api/oracleforms\n# Edit at https://www.toptal.com/developers/gitignore?templates=oracleforms\n\n### OracleForms ###\n# Compiled Form Modules\n*.fmx\n\n# Compiled Menu Modules\n*.mmx\n\n# Compiled Pre-Linked Libraries\n*.plx\n\n# End of https://www.toptal.com/developers/gitignore/api/oracleforms\n","orcad":"# Created by https://www.toptal.com/developers/gitignore/api/orcad\n# Edit at https://www.toptal.com/developers/gitignore?templates=orcad\n\n### OrCAD ###\n### Cadence Design Systm's OrCAD ###\n*.dat\n\n# End of https://www.toptal.com/developers/gitignore/api/orcad\n","osx":"# Created by https://www.toptal.com/developers/gitignore/api/osx\n# Edit at https://www.toptal.com/developers/gitignore?templates=osx\n\n### OSX ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\r\r\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# End of https://www.toptal.com/developers/gitignore/api/osx\n","otto":"# Created by https://www.toptal.com/developers/gitignore/api/otto\n# Edit at https://www.toptal.com/developers/gitignore?templates=otto\n\n### Otto ###\n.otto/\n\n# End of https://www.toptal.com/developers/gitignore/api/otto\n","oxideshop":"# Created by https://www.toptal.com/developers/gitignore/api/oxideshop\n# Edit at https://www.toptal.com/developers/gitignore?templates=oxideshop\n\n### OxidEshop ###\n/vendor\n\n!/source\n/source/*\n/source/tmp\n\n!/source/Application\n/source/Application/translations\n/source/Application/views/admin\n/source/Application/views/azure\n/source/Application/views/flow\n\n!/source/modules\n/source/modules/*\n\n!/source/out\n/source/out/admin\n/source/out/azure\n/source/out/downloads\n/source/out/flow\n/source/out/media\n/source/out/pictures\n\n# End of https://www.toptal.com/developers/gitignore/api/oxideshop\n","oxygenxmleditor":"# Created by https://www.toptal.com/developers/gitignore/api/oxygenxmleditor\n# Edit at https://www.toptal.com/developers/gitignore?templates=oxygenxmleditor\n\n### oXygenXMLEditor ###\n# oXygen XML Editor by SyncRO Soft SRL\n# https://www.oxygenxml.com\n/**/out/\n\n# End of https://www.toptal.com/developers/gitignore/api/oxygenxmleditor\n","packer":"# Created by https://www.toptal.com/developers/gitignore/api/packer\n# Edit at https://www.toptal.com/developers/gitignore?templates=packer\n\n### Packer ###\n# Cache objects\npacker_cache/\n\n# Crash log\ncrash.log\n\n# https://www.packer.io/guides/hcl/variables\n# Exclude all .pkrvars.hcl files, which are likely to contain sensitive data,\n# such as password, private keys, and other secrets. These should not be part of\n# version control as they are data points which are potentially sensitive and\n# subject to change depending on the environment.\n#\n*.pkrvars.hcl\n\n# For built boxes\n*.box\n\n### Packer Patch ###\n# ignore temporary output files\noutput-*/\n\n# End of https://www.toptal.com/developers/gitignore/api/packer\n","pants":"# Created by https://www.toptal.com/developers/gitignore/api/pants\n# Edit at https://www.toptal.com/developers/gitignore?templates=pants\n\n### Pants ###\n# Pants workspace files\n/.pants.*\n/dist/\n/.pids\n\n# End of https://www.toptal.com/developers/gitignore/api/pants\n","particle":"# Created by https://www.toptal.com/developers/gitignore/api/particle\n# Edit at https://www.toptal.com/developers/gitignore?templates=particle\n\n### Particle ###\n# Compiled output #\n\n###################\n\n*.bin\n\n# End of https://www.toptal.com/developers/gitignore/api/particle\n","patch":"# Created by https://www.toptal.com/developers/gitignore/api/patch\n# Edit at https://www.toptal.com/developers/gitignore?templates=patch\n\n### Patch ###\n*.orig\n*.rej\n\n# End of https://www.toptal.com/developers/gitignore/api/patch\n","pawn":"# Created by https://www.toptal.com/developers/gitignore/api/pawn\n# Edit at https://www.toptal.com/developers/gitignore?templates=pawn\n\n### PAWN ###\n# Compiled Bytecode, precompiled output and assembly\n*.amx\n*.lst\n*.asm\n\n# Vendor directory for dependencies\ndependencies/\n\n# Dependency versions lockfile\npawn.lock\n\n# IDE setting files\nsettings.ini\n\n# End of https://www.toptal.com/developers/gitignore/api/pawn\n","perl":"# Created by https://www.toptal.com/developers/gitignore/api/perl\n# Edit at https://www.toptal.com/developers/gitignore?templates=perl\n\n### Perl ###\n!Build/\n.last_cover_stats\n/META.yml\n/META.json\n/MYMETA.*\n*.o\n*.pm.tdy\n*.bs\n\n# Devel::Cover\ncover_db/\n\n# Devel::NYTProf\nnytprof.out\n\n# Dist::Zilla\n/.build/\n\n# Module::Build\n_build/\nBuild\nBuild.bat\n\n# Module::Install\ninc/\n\n# ExtUtils::MakeMaker\n/blib/\n/_eumm/\n/*.gz\n/Makefile\n/Makefile.old\n/MANIFEST.bak\n/pm_to_blib\n/*.zip\n\n# Carton\nlocal/\n\n# End of https://www.toptal.com/developers/gitignore/api/perl\n","perl6":"# Created by https://www.toptal.com/developers/gitignore/api/perl6\n# Edit at https://www.toptal.com/developers/gitignore?templates=perl6\n\n### Perl6 ###\n# Gitignore for Perl 6 (http://www.perl6.org)\n# As part of https://github.com/github/gitignore\n\n# precompiled files\n.precomp\nlib/.precomp\n\n# End of https://www.toptal.com/developers/gitignore/api/perl6\n","ph7cms":"# Created by https://www.toptal.com/developers/gitignore/api/ph7cms\n# Edit at https://www.toptal.com/developers/gitignore?templates=ph7cms\n\n### pH7CMS ###\n_test/*\npublic/_repository/upgrade/*\npublic/_install/_license-key.txt\npublic/_install/data/caches/smarty_cache/*\npublic/_install/data/caches/smarty_compile/*\npublic/data/*\npublic/_constants.php\n_protected/app/configs/config.ini\n_protected/app/system/core/assets/cron/_delay/\n_protected/data/tmp/\n_protected/data/backup/*\n_protected/data/cache/pH7_cache/*\n_protected/data/cache/pH7_static/*\n_protected/data/cache/pH7tpl_cache/*\n_protected/data/cache/pH7tpl_compile/*\n*.log\n*.tmp\n\n## Gettext ##\n*.mo\n\n# End of https://www.toptal.com/developers/gitignore/api/ph7cms\n","phalcon":"# Created by https://www.toptal.com/developers/gitignore/api/phalcon\n# Edit at https://www.toptal.com/developers/gitignore?templates=phalcon\n\n### Phalcon ###\n/cache/\n/config/development/\n\n# End of https://www.toptal.com/developers/gitignore/api/phalcon\n","phoenix":"# Created by https://www.toptal.com/developers/gitignore/api/phoenix\n# Edit at https://www.toptal.com/developers/gitignore?templates=phoenix\n\n### Phoenix ###\n# gitignore template for Phoenix projects\n# website: http://www.phoenixframework.org/\n#\n# Recommended template: Elixir.gitignore\n\n# Temporary files\n/tmp\n\n# Static artifacts\n/node_modules\n/assets/node_modules\n\n# Since we are building assets from web/static,\n# we ignore priv/static. You may want to comment\n# this depending on your deployment strategy.\n/priv/static/\n\n# Installer-related files\n/installer/_build\n/installer/tmp\n/installer/doc\n/installer/deps\n\n# End of https://www.toptal.com/developers/gitignore/api/phoenix\n","php-cs-fixer":"# Created by https://www.toptal.com/developers/gitignore/api/php-cs-fixer\n# Edit at https://www.toptal.com/developers/gitignore?templates=php-cs-fixer\n\n### PHP-CS-Fixer ###\n# Covers PHP CS Fixer\n# Reference: https://cs.symfony.com/\n\n# Generated files\n.php-cs-fixer.cache\n\n# Local config See: https://cs.symfony.com/doc/config.html\n.php-cs-fixer.php\n\n# End of https://www.toptal.com/developers/gitignore/api/php-cs-fixer\n","phpcodesniffer":"# Created by https://www.toptal.com/developers/gitignore/api/phpcodesniffer\n# Edit at https://www.toptal.com/developers/gitignore?templates=phpcodesniffer\n\n### PHPCodeSniffer ###\n# CodeSniffer\nphpcs.xml\n\n/vendor/*\n/wpcs/*\n\n# End of https://www.toptal.com/developers/gitignore/api/phpcodesniffer\n","phpstorm":"# Created by https://www.toptal.com/developers/gitignore/api/phpstorm\n# Edit at https://www.toptal.com/developers/gitignore?templates=phpstorm\n\n### PhpStorm ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### PhpStorm Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/phpstorm\n","phpstorm+all":"# Created by https://www.toptal.com/developers/gitignore/api/phpstorm+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=phpstorm+all\n\n### PhpStorm+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### PhpStorm+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/phpstorm+all\n","phpstorm+iml":"# Created by https://www.toptal.com/developers/gitignore/api/phpstorm+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=phpstorm+iml\n\n### PhpStorm+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### PhpStorm+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/phpstorm+iml\n","phpunit":"# Created by https://www.toptal.com/developers/gitignore/api/phpunit\n# Edit at https://www.toptal.com/developers/gitignore?templates=phpunit\n\n### PHPUnit ###\n# Covers PHPUnit\n# Reference: https://phpunit.de/\n\n# Generated files\n.phpunit.result.cache\n.phpunit.cache\n\n# PHPUnit\n/app/phpunit.xml\n/phpunit.xml\n\n# Build data\n/build/\n\n# End of https://www.toptal.com/developers/gitignore/api/phpunit\n","pico8":"# Created by https://www.toptal.com/developers/gitignore/api/pico8\n# Edit at https://www.toptal.com/developers/gitignore?templates=pico8\n\n### pico8 ###\n# exported file types\n*.p8.png\n*.png\n*.wav\n*.js\n*.html\n*.bin\n\n# demos folder\ndemos/\n\n# End of https://www.toptal.com/developers/gitignore/api/pico8\n","pimcore":"# Created by https://www.toptal.com/developers/gitignore/api/pimcore\n# Edit at https://www.toptal.com/developers/gitignore?templates=pimcore\n\n### Pimcore ###\n# gitignore template for Pimcore CMS\n\n# pimcore source files\n/pimcore\n\n# asset files\n/website/var/assets/*\n\n# backups\n/website/var/backup/*\n\n# file cache\n/website/var/cache/*\n\n# generated PHP classes, keep definition files (.psf)\n/website/var/classes/Object*\n!/website/var/classes/objectbricks\n\n# various configuration files\n/website/var/config/system.xml\n/website/var/config/cache.xml\n/website/var/config/robots.txt\n/website/var/config/Geo*\n/website/var/config/object/*\n/website/var/config/portal/*\n/website/var/config/sqlreport/*\n\n# sent e-mail log files\n/website/var/email/*\n\n# log files\n/website/var/log/*.log\n\n# serialized recyclebin files\n/website/var/recyclebin/*\n\n# search plugin\n/website/var/search/*\n\n# various temp files\n/website/var/system/*\n/website/var/tmp/*\n\n# serialized version files\n/website/var/versions/asset/*\n/website/var/versions/document/*\n/website/var/versions/object/*\n\n# user profile images\n/website/var/user-image/*\n\n# keep .dummy files\n!.dummy\n\n# End of https://www.toptal.com/developers/gitignore/api/pimcore\n","pimcore4":"# Created by https://www.toptal.com/developers/gitignore/api/pimcore4\n# Edit at https://www.toptal.com/developers/gitignore?templates=pimcore4\n\n### Pimcore4 ###\n# pimcore source files\n/pimcore\n\n# asset files\n/website/var/assets/*\n\n# backups\n/website/var/backup/*\n\n# file cache\n/website/var/cache/*\n\n# generated PHP classes, keep definition files (.psf)\n/website/var/classes/Object*\n!/website/var/classes/objectbricks\n\n# various configuration files\n/website/var/config/system.xml\n/website/var/config/cache.xml\n/website/var/config/robots.txt\n/website/var/config/Geo*\n/website/var/config/object/*\n/website/var/config/portal/*\n/website/var/config/sqlreport/*\n\n# sent e-mail log files\n/website/var/email/*\n\n# log files\n/website/var/log/*.log\n\n# serialized recyclebin files\n/website/var/recyclebin/*\n\n# search plugin\n/website/var/search/*\n\n# various temp files\n/website/var/system/*\n/website/var/tmp/*\n\n# serialized version files\n/website/var/versions/asset/*\n/website/var/versions/document/*\n/website/var/versions/object/*\n\n# user profile images\n/website/var/user-image/*\n\n# keep .dummy files\n!.dummy\n\n# End of https://www.toptal.com/developers/gitignore/api/pimcore4\n","pimcore5":"# Created by https://www.toptal.com/developers/gitignore/api/pimcore5\n# Edit at https://www.toptal.com/developers/gitignore?templates=pimcore5\n\n### Pimcore5 ###\n/var/*\n\n# keep directories\n!/var/cache/.gitkeep\n!/var/logs/.gitkeep\n!/var/sessions/.gitkeep\n\n# keep generated classes in Git to have auto-completion\n!/var/classes\n\n# keep configuration\n!/var/config/\n\n# Auto-generated by bin/console assets:install\n/web/bundles/\n/web/var/\n\n!/var/SymfonyRequirements.php\n\n# End of https://www.toptal.com/developers/gitignore/api/pimcore5\n","pinegrow":"# Created by https://www.toptal.com/developers/gitignore/api/pinegrow\n# Edit at https://www.toptal.com/developers/gitignore?templates=pinegrow\n\n### PineGrow ###\n#Pinegrow backup files\n_pgbackup/*\n\n#Pinegrow setting file\npinegrow.json\n\n# End of https://www.toptal.com/developers/gitignore/api/pinegrow\n","platformio":"# Created by https://www.toptal.com/developers/gitignore/api/platformio\n# Edit at https://www.toptal.com/developers/gitignore?templates=platformio\n\n### PlatformIO ###\n.pioenvs\n.piolibdeps\n.clang_complete\n.gcc-flags.json\n.pio\n\n# End of https://www.toptal.com/developers/gitignore/api/platformio\n","playframework":"# Created by https://www.toptal.com/developers/gitignore/api/playframework\n# Edit at https://www.toptal.com/developers/gitignore?templates=playframework\n\n### PlayFramework ###\n# Ignore Play! working directory #\nbin/\n/db\n.eclipse\n/lib/\n/logs/\n/modules\n/project/project\n/project/target\n/target\ntmp/\ntest-result\nserver.pid\n*.eml\n/dist/\n.cache\n\n# End of https://www.toptal.com/developers/gitignore/api/playframework\n","plone":"# Created by https://www.toptal.com/developers/gitignore/api/plone\n# Edit at https://www.toptal.com/developers/gitignore?templates=plone\n\n### Plone ###\n*.pyc\n*.pyo\n*.tmp*\n*.mo\n*.egg\n*.EGG\n*.egg-info\n*.EGG-INFO\n.*.cfg\nbin/\nbuild/\ndevelop-eggs/\ndownloads/\neggs/\nfake-eggs/\nparts/\ndist/\nvar/\n\n# End of https://www.toptal.com/developers/gitignore/api/plone\n","polymer":"# Created by https://www.toptal.com/developers/gitignore/api/polymer\n# Edit at https://www.toptal.com/developers/gitignore?templates=polymer\n\n### Polymer ###\n#Installed components via bower\nbower_components/\n\n#Installed modules via npm\nnode_modules/\n\n#Polymer-cli builds\nbuild/\n\n# End of https://www.toptal.com/developers/gitignore/api/polymer\n","powershell":"# Created by https://www.toptal.com/developers/gitignore/api/powershell\n# Edit at https://www.toptal.com/developers/gitignore?templates=powershell\n\n### PowerShell ###\n# Exclude packaged modules\n*.zip\n\n# Exclude .NET assemblies from source\n*.dll\n\n# End of https://www.toptal.com/developers/gitignore/api/powershell\n","premake-gmake":"# Created by https://www.toptal.com/developers/gitignore/api/premake-gmake\n# Edit at https://www.toptal.com/developers/gitignore?templates=premake-gmake\n\n### premake-gmake ###\nMakefile\n*.make\nobj/\n\n# End of https://www.toptal.com/developers/gitignore/api/premake-gmake\n","prepros":"# Created by https://www.toptal.com/developers/gitignore/api/prepros\n# Edit at https://www.toptal.com/developers/gitignore?templates=prepros\n\n### Prepros ###\n# Config Prepros files to ignore\nprepros.cfg\nprepros-6.config\nprepros.config\n\n# End of https://www.toptal.com/developers/gitignore/api/prepros\n","prestashop":"# Created by https://www.toptal.com/developers/gitignore/api/prestashop\n# Edit at https://www.toptal.com/developers/gitignore?templates=prestashop\n\n### Prestashop ###\n# Cache, temp and personal files\n\n/.htaccess\n*.log\n\n# Cache\n/cache/*\n!/cache/.htaccess\n!/cache/cachefs/index.php\n!/cache/deprecated.txt\n!/cache/index.php\n!/cache/purifier/index.php\n!/cache/push/activity\n!/cache/push/index.php\n!/cache/push/trends\n!/cache/sandbox/index.php\n!/cache/smarty/cache/index.php\n!/cache/smarty/compile/index.php\n!/cache/smarty/index.php\n!/cache/tcpdf/index.php\n\n# Download\n/download/*\n!/download/.htaccess\n!/download/index.php\n\n# Images\n/img/*\n!/img/.htaccess\n!/img/index.php\n!/img/404.gif\n!/img/bg_500.png\n!/img/bg_loader.png\n!/img/favicon.ico\n!/img/loader.gif\n!/img/loadingAnimation.gif\n!/img/logo.jpg\n!/img/logo.png\n!/img/logo_invoice.jpg\n!/img/logo_stores.png\n!/img/macFFBgHack.png\n!/img/prestashop-avatar.png\n!/img/prestashop@2x.png\n!/img/preston-login-wink@2x.png\n!/img/preston-login@2x.png\n!/img/questionmark.png\n!/img/genders/index.php\n!/img/admin/index.php\n!/img/c/index.php\n!/img/cms/index.php\n!/img/co/index.php\n!/img/jquery-ui\n!/img/l/index.php\n!/img/m/index.php\n!/img/os/index.php\n!/img/p/index.php\n!/img/s/index.php\n!/img/scenes\n!/img/st/index.php\n!/img/su/index.php\n!/img/t/index.php\n!/img/tmp/index.php\n\n# Upload\n/upload/*\n!/upload/.htaccess\n\n/vendor/*\n/docs/phpdoc-sf/\n/composer.lock\n*.hot-update.js\n*.hot-update.json\n\n\n/admin-dev/autoupgrade/*\n!/admin-dev/autoupgrade/index.php\n!/admin-dev/autoupgrade/backup/index.php\n\n/admin-dev/backups/*\n!/admin-dev/backups/.htaccess\n\n/admin-dev/import/*\n!/admin-dev/import/.htaccess\n!/admin-dev/import/index.php\n\n/admin-dev/export/*\n!/admin-dev/export/.htaccess\n!/admin-dev/export/index.php\n\n# Downloaded RTL files\n/admin-dev/themes/default/css/bundle/default_rtl.css\n/admin-dev/themes/default/css/bundle/shared_rtl.css\n/admin-dev/themes/default/css/font_rtl.css\n/admin-dev/themes/default/css/overrides_rtl.css\n/admin-dev/themes/default/css/vendor/font-awesome/font-awesome_rtl.css\n/admin-dev/themes/default/css/vendor/nv.d3_rtl.css\n/admin-dev/themes/default/css/vendor/titatoggle-min_rtl.css\n/admin-dev/themes/default/public/theme_rtl.css\n/admin-dev/themes/new-theme/css/module/drop_rtl.css\n/admin-dev/themes/new-theme/css/right-sidebar_rtl.css\n\nthemes/*/cache/*\n\n# Config\n\nconfig/settings.inc.php\nconfig/settings.old.php\nconfig/xml/*\nconfig/themes/*\n!config/xml/themes/default.xml\nthemes/*/config/settings_*.json\napp/config/parameters.old.yml\napp/config/config.php\n\n# Themes, modules and overrides\n\nmodules/*\noverride/*\nthemes/*/\n!themes/classic\n!themes/_core\n!themes/_libraries\n\n# Vendors and dependencies\n\nbower_components/\nnode_modules/\ncomposer.phar\nphp-cs-fixer\n.grunt/*\n\n# Translations and emails templates\n\ntranslations/*\nmails/*\n!mails/themes/\n!mails/_partials/\nthemes/default-bootstrap/lang/*\nthemes/default-bootstrap/modules/*/translations/*.php\nthemes/default-bootstrap/mails/*\n!themes/default-bootstrap/mails/en/\nthemes/default-bootstrap/modules/*/mails/*\n!themes/default-bootstrap/modules/*/mails/en\n\n# MISC\n\n*sitemap.xml\n/robots.txt\n\n# Symfony\n\n/bin/\n/app/Resources/geoip/GeoLite2-City.mmdb\n/app/Resources/translations/*\n!/app/Resources/translations/default\n/app/config/parameters.yml\n/app/config/parameters.php\n/build/\n/phpunit.xml\n/var/*\n!/var/cache\n/var/cache/*\n!var/cache/.gitkeep\n!/var/logs\n/var/logs/*\n!var/logs/.gitkeep\n!/var/sessions\n/var/sessions/*\n!var/sessions/.gitkeep\n!var/SymfonyRequirements.php\n/vendor/\n/web/bundles/\n\n\n# End of https://www.toptal.com/developers/gitignore/api/prestashop\n","processing":"# Created by https://www.toptal.com/developers/gitignore/api/processing\n# Edit at https://www.toptal.com/developers/gitignore?templates=processing\n\n### Processing ###\n.DS_Store\napplet\napplication.linux-arm64\napplication.linux-armv6hf\napplication.linux32\napplication.linux64\napplication.windows32\napplication.windows64\napplication.macosx\nout\n\n# End of https://www.toptal.com/developers/gitignore/api/processing\n","progressabl":"# Created by https://www.toptal.com/developers/gitignore/api/progressabl\n# Edit at https://www.toptal.com/developers/gitignore?templates=progressabl\n\n### ProgressABL ###\n# Developer Studio\n/.dbconnection\n/.project\n/.propath\n/debugger.pref\n/protrace*\n*.r\n*.pl\n\n# DataDigger\nDataDigger/Cache\nDataDigger/backup\nDataDigger/DataDigger-*.ini\nDataDigger/DataDigger*.log\n\n# General folders\n/old/\n/debug/\n\n# General files\n.gitignore\n*.tmp\n*.bak\n*.bck\n*.old\n*- Copy.*\n*- Copy (*).*\n*.log\n\n# End of https://www.toptal.com/developers/gitignore/api/progressabl\n","psoccreator":"# Created by https://www.toptal.com/developers/gitignore/api/psoccreator\n# Edit at https://www.toptal.com/developers/gitignore?templates=psoccreator\n\n### PSoCCreator ###\n# Project Settings\n*.cywrk.*\n*.cyprj.*\n\n# Generated Assets and Resources\nDebug/\nRelease/\nExport/\n*/codegentemp\n*/Generated_Source\n*_datasheet.pdf\n*_timing.html\n*.cycdx\n*.cyfit\n*.rpt\n*.svd\n*.log\n*.zip\n\n# End of https://www.toptal.com/developers/gitignore/api/psoccreator\n","pulumi":"# Created by https://www.toptal.com/developers/gitignore/api/pulumi\n# Edit at https://www.toptal.com/developers/gitignore?templates=pulumi\n\n### Pulumi ###\n# Ignore temp build directory for Pulumi\n# Info: https://www.pulumi.com/docs/\n\n.pulumi/\n\n# End of https://www.toptal.com/developers/gitignore/api/pulumi\n","pulumi+stacks":"# Created by https://www.toptal.com/developers/gitignore/api/pulumi+stacks\n# Edit at https://www.toptal.com/developers/gitignore?templates=pulumi+stacks\n\n### Pulumi+stacks ###\n# Ignore temp build directory for Pulumi\n# Info: https://www.pulumi.com/docs/\n\n.pulumi/\n\n### Pulumi+stacks Patch ###\n# Pulumi stacks configuration files\n# See https://www.pulumi.com/docs/intro/concepts/project/#stack-settings-file\n\nPulumi.*.yaml\n\n# End of https://www.toptal.com/developers/gitignore/api/pulumi+stacks\n","puppet":"# Created by https://www.toptal.com/developers/gitignore/api/puppet\n# Edit at https://www.toptal.com/developers/gitignore?templates=puppet\n\n### Puppet ###\n# gitignore template for Puppet modules\n# website: https://forge.puppet.com/\n\n# Built packages\npkg/*\n\n# Should run on multiple platforms so don't check in\nGemfile.lock\n\n# Tests\nspec/fixtures/*\ncoverage/*\n\n# Third-party\nvendor/*\n.bundle/*\n\n# End of https://www.toptal.com/developers/gitignore/api/puppet\n","puppet-librarian":"# Created by https://www.toptal.com/developers/gitignore/api/puppet-librarian\n# Edit at https://www.toptal.com/developers/gitignore?templates=puppet-librarian\n\n### puppet-librarian ###\n# puppet-librarian - https://github.com/rodjek/librarian-puppet\n.tmp/\nmodules/\n\n# End of https://www.toptal.com/developers/gitignore/api/puppet-librarian\n","purebasic":"# Created by https://www.toptal.com/developers/gitignore/api/purebasic\n# Edit at https://www.toptal.com/developers/gitignore?templates=purebasic\n\n### PureBasic ###\n# PureBasic\n\n# Settings files\n*.pb.cfg\n*.pbi.cfg\nproject.cfg\n\n# Shared libraries\n*.so\n*.dylib\n*.dll\n\n# Binary executables\n*.exe\n*.app\n*.out\n\n# End of https://www.toptal.com/developers/gitignore/api/purebasic\n","purescript":"# Created by https://www.toptal.com/developers/gitignore/api/purescript\n# Edit at https://www.toptal.com/developers/gitignore?templates=purescript\n\n### PureScript ###\n# Dependencies\n.psci_modules\n.spago\nbower_components\nnode_modules\n\n# Generated files\n.psci\noutput\n\n# End of https://www.toptal.com/developers/gitignore/api/purescript\n","putty":"# Created by https://www.toptal.com/developers/gitignore/api/putty\n# Edit at https://www.toptal.com/developers/gitignore?templates=putty\n\n### PuTTY ###\n# Private key\n*.ppk\n\n# End of https://www.toptal.com/developers/gitignore/api/putty\n","pvs":"# Created by https://www.toptal.com/developers/gitignore/api/pvs\n# Edit at https://www.toptal.com/developers/gitignore?templates=pvs\n\n### PVS ###\n# Ignore binary files\npvsbin/\nbin/\nlib/\n*.bin\n.pvscontext\n# Ignore orphaned and old proofs\norphaned-proofs.prf\n*.prf~\n# Ignore compiled Lisp files\n*fasl\n# Ignore PVS dump files\n*.dmp\n# Ignore PVS log files\n*.log\n\n# End of https://www.toptal.com/developers/gitignore/api/pvs\n","pycharm":"# Created by https://www.toptal.com/developers/gitignore/api/pycharm\n# Edit at https://www.toptal.com/developers/gitignore?templates=pycharm\n\n### PyCharm ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### PyCharm Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/pycharm\n","pycharm+all":"# Created by https://www.toptal.com/developers/gitignore/api/pycharm+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=pycharm+all\n\n### PyCharm+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### PyCharm+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/pycharm+all\n","pycharm+iml":"# Created by https://www.toptal.com/developers/gitignore/api/pycharm+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=pycharm+iml\n\n### PyCharm+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### PyCharm+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/pycharm+iml\n","pydev":"# Created by https://www.toptal.com/developers/gitignore/api/pydev\n# Edit at https://www.toptal.com/developers/gitignore?templates=pydev\n\n### pydev ###\n.pydevproject\n\n# End of https://www.toptal.com/developers/gitignore/api/pydev\n","python":"# Created by https://www.toptal.com/developers/gitignore/api/python\n# Edit at https://www.toptal.com/developers/gitignore?templates=python\n\n### Python ###\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n# Usually these files are written by a python script from a template\n# before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n# For a library or package, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n# However, in case of collaboration, if having platform-specific dependencies or dependencies\n# having no cross-platform support, pipenv may install dependencies that don't work, or not\n# install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n# This is especially recommended for binary packages to ensure reproducibility, and is more\n# commonly ignored for libraries.\n# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n# in version control.\n# https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n# JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n# and can be added to the global gitignore or merged into this file. For a more nuclear\n# option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n### Python Patch ###\n# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration\npoetry.toml\n\n# ruff\n.ruff_cache/\n\n# LSP config files\npyrightconfig.json\n\n# End of https://www.toptal.com/developers/gitignore/api/python\n","pythonvanilla":"# Created by https://www.toptal.com/developers/gitignore/api/pythonvanilla\n# Edit at https://www.toptal.com/developers/gitignore?templates=pythonvanilla\n\n### PythonVanilla ###\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# pyenv\n# For a library or package, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n# However, in case of collaboration, if having platform-specific dependencies or dependencies\n# having no cross-platform support, pipenv may install dependencies that don't work, or not\n# install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n\n# End of https://www.toptal.com/developers/gitignore/api/pythonvanilla\n","qml":"# Created by https://www.toptal.com/developers/gitignore/api/qml\n# Edit at https://www.toptal.com/developers/gitignore?templates=qml\n\n### QML ###\n# Cached binary representations of QML and JS files\n*.qmlc\n*.jsc\n\n# End of https://www.toptal.com/developers/gitignore/api/qml\n","qooxdoo":"# Created by https://www.toptal.com/developers/gitignore/api/qooxdoo\n# Edit at https://www.toptal.com/developers/gitignore?templates=qooxdoo\n\n### Qooxdoo ###\ncache\ncache-downloads\ninspector\napi\nsource/inspector.html\n\n# End of https://www.toptal.com/developers/gitignore/api/qooxdoo\n","qt":"# Created by https://www.toptal.com/developers/gitignore/api/qt\n# Edit at https://www.toptal.com/developers/gitignore?templates=qt\n\n### Qt ###\n# C++ objects and libs\n*.slo\n*.lo\n*.o\n*.a\n*.la\n*.lai\n*.so\n*.so.*\n*.dll\n*.dylib\n\n# Qt-es\nobject_script.*.Release\nobject_script.*.Debug\n*_plugin_import.cpp\n/.qmake.cache\n/.qmake.stash\n*.pro.user\n*.pro.user.*\n*.qbs.user\n*.qbs.user.*\n*.moc\nmoc_*.cpp\nmoc_*.h\nqrc_*.cpp\nui_*.h\n*.qmlc\n*.jsc\nMakefile*\n*build-*\n*.qm\n*.prl\n\n# Qt unit tests\ntarget_wrapper.*\n\n# QtCreator\n*.autosave\n\n# QtCreator Qml\n*.qmlproject.user\n*.qmlproject.user.*\n\n# QtCreator CMake\nCMakeLists.txt.user*\n\n# QtCreator 4.8< compilation database\ncompile_commands.json\n\n# QtCreator local machine specific files for imported projects\n*creator.user*\n\n*_qmlcache.qrc\n\n# End of https://www.toptal.com/developers/gitignore/api/qt\n","qtcreator":"# Created by https://www.toptal.com/developers/gitignore/api/qtcreator\n# Edit at https://www.toptal.com/developers/gitignore?templates=qtcreator\n\n### QtCreator ###\n# gitignore for Qt Creator like IDE for pure C/C++ project without Qt\n#\n# Reference: http://doc.qt.io/qtcreator/creator-project-generic.html\n\n\n\n# Qt Creator autogenerated files\n\n\n# A listing of all the files included in the project\n*.files\n\n# Include directories\n*.includes\n\n# Project configuration settings like predefined Macros\n*.config\n\n# Qt Creator settings\n*.creator\n\n# User project settings\n*.creator.user*\n\n# Qt Creator backups\n*.autosave\n\n# Flags for Clang Code Model\n*.cxxflags\n*.cflags\n\n\n# End of https://www.toptal.com/developers/gitignore/api/qtcreator\n","r":"# Created by https://www.toptal.com/developers/gitignore/api/r\n# Edit at https://www.toptal.com/developers/gitignore?templates=r\n\n### R ###\n# History files\n.Rhistory\n.Rapp.history\n\n# Session Data files\n.RData\n.RDataTmp\n\n# User-specific files\n.Ruserdata\n\n# Example code in package build process\n*-Ex.R\n\n# Output files from R CMD build\n/*.tar.gz\n\n# Output files from R CMD check\n/*.Rcheck/\n\n# RStudio files\n.Rproj.user/\n\n# produced vignettes\nvignettes/*.html\nvignettes/*.pdf\n\n# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3\n.httr-oauth\n\n# knitr and R markdown default cache directories\n*_cache/\n/cache/\n\n# Temporary files created by R markdown\n*.utf8.md\n*.knit.md\n\n# R Environment Variables\n.Renviron\n\n# pkgdown site\ndocs/\n\n# translation temp files\npo/*~\n\n# RStudio Connect folder\nrsconnect/\n\n### R.Bookdown Stack ###\n# R package: bookdown caching files\n/*_files/\n\n# End of https://www.toptal.com/developers/gitignore/api/r\n","racket":"# Created by https://www.toptal.com/developers/gitignore/api/racket\n# Edit at https://www.toptal.com/developers/gitignore?templates=racket\n\n### Racket ###\n# gitignore template for the Racket language\n# website: http://www.racket-lang.org/\n\n# DrRacket autosave files\n*.rkt~\n*.rkt.bak\n\\#*.rkt#\n\\#*.rkt#*#\n\n# Compiled racket bytecode\ncompiled/\n*.zo\n\n# Dependency tracking files\n*.dep\n\n# End of https://www.toptal.com/developers/gitignore/api/racket\n","rails":"# Created by https://www.toptal.com/developers/gitignore/api/rails\n# Edit at https://www.toptal.com/developers/gitignore?templates=rails\n\n### Rails ###\n*.rbc\ncapybara-*.html\n.rspec\n/db/*.sqlite3\n/db/*.sqlite3-journal\n/db/*.sqlite3-[0-9]*\n/public/system\n/coverage/\n/spec/tmp\n*.orig\nrerun.txt\npickle-email-*.html\n\n# Ignore all logfiles and tempfiles.\n/log/*\n/tmp/*\n!/log/.keep\n!/tmp/.keep\n\n# TODO Comment out this rule if you are OK with secrets being uploaded to the repo\nconfig/initializers/secret_token.rb\nconfig/master.key\n\n# Only include if you have production secrets in this file, which is no longer a Rails default\n# config/secrets.yml\n\n# dotenv, dotenv-rails\n# TODO Comment out these rules if environment variables can be committed\n.env\n.env*.local\n\n## Environment normalization:\n/.bundle\n/vendor/bundle\n\n# these should all be checked in to normalize the environment:\n# Gemfile.lock, .ruby-version, .ruby-gemset\n\n# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:\n.rvmrc\n\n# if using bower-rails ignore default bower_components path bower.json files\n/vendor/assets/bower_components\n*.bowerrc\nbower.json\n\n# Ignore pow environment settings\n.powenv\n\n# Ignore Byebug command history file.\n.byebug_history\n\n# Ignore node_modules\nnode_modules/\n\n# Ignore precompiled javascript packs\n/public/packs\n/public/packs-test\n/public/assets\n\n# Ignore yarn files\n/yarn-error.log\nyarn-debug.log*\n.yarn-integrity\n\n# Ignore uploaded files in development\n/storage/*\n!/storage/.keep\n/public/uploads\n\n# End of https://www.toptal.com/developers/gitignore/api/rails\n","react":"# Created by https://www.toptal.com/developers/gitignore/api/react\n# Edit at https://www.toptal.com/developers/gitignore?templates=react\n\n### react ###\n.DS_*\n*.log\nlogs\n**/*.backup.*\n**/*.back.*\n\nnode_modules\nbower_components\n\n*.sublime*\n\npsd\nthumb\nsketch\n\n# End of https://www.toptal.com/developers/gitignore/api/react\n","reactnative":"# Created by https://www.toptal.com/developers/gitignore/api/reactnative\n# Edit at https://www.toptal.com/developers/gitignore?templates=reactnative\n\n### ReactNative ###\n# React Native Stack Base\n\n.expo\n__generated__\n\n### ReactNative.Buck Stack ###\nbuck-out/\n.buckconfig.local\n.buckd/\n.buckversion\n.fakebuckversion\n\n### ReactNative.Node Stack ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n### ReactNative.Gradle Stack ###\n.gradle\n**/build/\n!src/**/build/\n\n# Ignore Gradle GUI config\ngradle-app.setting\n\n# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)\n!gradle-wrapper.jar\n\n# Avoid ignore Gradle wrappper properties\n!gradle-wrapper.properties\n\n# Cache of project\n.gradletasknamecache\n\n# Eclipse Gradle plugin generated files\n# Eclipse Core\n.project\n# JDT-specific (Eclipse Java Development Tools)\n.classpath\n\n### ReactNative.Android Stack ###\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Log/OS Files\n\n# Android Studio generated files and folders\ncaptures/\n.externalNativeBuild/\n.cxx/\n*.apk\noutput.json\n\n# IntelliJ\n*.iml\n.idea/\nmisc.xml\ndeploymentTargetDropDown.xml\nrender.experimental.xml\n\n# Keystore files\n*.jks\n*.keystore\n\n# Google Services (e.g. APIs or Firebase)\ngoogle-services.json\n\n# Android Profiling\n*.hprof\n\n### ReactNative.Linux Stack ###\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n### ReactNative.Xcode Stack ###\n## User settings\nxcuserdata/\n\n## Xcode 8 and earlier\n*.xcscmblueprint\n*.xccheckout\n\n### ReactNative.macOS Stack ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\r\r\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# End of https://www.toptal.com/developers/gitignore/api/reactnative\n","reasonml":"# Created by https://www.toptal.com/developers/gitignore/api/reasonml\n# Edit at https://www.toptal.com/developers/gitignore?templates=reasonml\n\n### Reasonml ###\n/lib\n\n# End of https://www.toptal.com/developers/gitignore/api/reasonml\n","red":"# Created by https://www.toptal.com/developers/gitignore/api/red\n# Edit at https://www.toptal.com/developers/gitignore?templates=red\n\n### Red ###\n# gitignore template for Red programming language\n# website: http://www.red-lang.org/\n\n# Red Compiled code\n*.red\n\n# Libraries\ncrush.dll\ncrush.dylib\ncrush.so\n\n# Files generated during test\nquick-test/quick-test.log\nquick-test/runnable/\nsystem/tests/source/units/auto-tests/\ntests/source/units/auto-tests/\n\n# End of https://www.toptal.com/developers/gitignore/api/red\n","redcar":"# Created by https://www.toptal.com/developers/gitignore/api/redcar\n# Edit at https://www.toptal.com/developers/gitignore?templates=redcar\n\n### Redcar ###\n.redcar\n\n# End of https://www.toptal.com/developers/gitignore/api/redcar\n","redis":"# Created by https://www.toptal.com/developers/gitignore/api/redis\n# Edit at https://www.toptal.com/developers/gitignore?templates=redis\n\n### Redis ###\n# Ignore redis binary dump (dump.rdb) files\n\n*.rdb\n\n# End of https://www.toptal.com/developers/gitignore/api/redis\n","remix":"# Created by https://www.toptal.com/developers/gitignore/api/remix\n# Edit at https://www.toptal.com/developers/gitignore?templates=remix\n\n### Remix ###\nnode_modules\n/.cache\n/build\n/public/build\n.env\n\n# End of https://www.toptal.com/developers/gitignore/api/remix\n","remix+arc":"# Created by https://www.toptal.com/developers/gitignore/api/remix+arc\n# Edit at https://www.toptal.com/developers/gitignore?templates=remix+arc\n\n### Remix+Arc ###\n/server/index.js\npreferences.arc\nsam.json\nsam.yaml\n\n# End of https://www.toptal.com/developers/gitignore/api/remix+arc\n","remix+cloudflarepages":"# Created by https://www.toptal.com/developers/gitignore/api/remix+cloudflarepages\n# Edit at https://www.toptal.com/developers/gitignore?templates=remix+cloudflarepages\n\n### Remix+CloudflarePages ###\n/functions/\\[\\[path\\]\\].js\n/functions/\\[\\[path\\]\\].js.map\n\n# End of https://www.toptal.com/developers/gitignore/api/remix+cloudflarepages\n","remix+cloudflareworkers":"# Created by https://www.toptal.com/developers/gitignore/api/remix+cloudflareworkers\n# Edit at https://www.toptal.com/developers/gitignore?templates=remix+cloudflareworkers\n\n### Remix+CloudflareWorkers ###\n/dist\n/.mf\n\n# End of https://www.toptal.com/developers/gitignore/api/remix+cloudflareworkers\n","remix+netlify":"# Created by https://www.toptal.com/developers/gitignore/api/remix+netlify\n# Edit at https://www.toptal.com/developers/gitignore?templates=remix+netlify\n\n### Remix+Netlify ###\n/.netlify\n\n# End of https://www.toptal.com/developers/gitignore/api/remix+netlify\n","remix+vercel":"# Created by https://www.toptal.com/developers/gitignore/api/remix+vercel\n# Edit at https://www.toptal.com/developers/gitignore?templates=remix+vercel\n\n### Remix+Vercel ###\n.vercel\n.output\n/api/index.js\n/api/index.js.map\n\n# End of https://www.toptal.com/developers/gitignore/api/remix+vercel\n","renpy":"# Created by https://www.toptal.com/developers/gitignore/api/renpy\n# Edit at https://www.toptal.com/developers/gitignore?templates=renpy\n\n### Renpy ###\n# Don't track content of these folders #\n########################################\ncache/\nsaves/\n\n\n# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n*.rpyc\n*.rpymc\n\n\n# The log file and other misc things #\n######################################\nlog.txt\nerrors.txt\ntraceback.txt\nfiles.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/renpy\n","replit":"# Created by https://www.toptal.com/developers/gitignore/api/replit\n# Edit at https://www.toptal.com/developers/gitignore?templates=replit\n\n### replit ###\n# Replit config file\n.replit\n\n# Replit Nix config file\nreplit.nix\n\n# End of https://www.toptal.com/developers/gitignore/api/replit\n","retool":"# Created by https://www.toptal.com/developers/gitignore/api/retool\n# Edit at https://www.toptal.com/developers/gitignore?templates=retool\n\n### retool ###\n_tools\n\n# End of https://www.toptal.com/developers/gitignore/api/retool\n","rhodesrhomobile":"# Created by https://www.toptal.com/developers/gitignore/api/rhodesrhomobile\n# Edit at https://www.toptal.com/developers/gitignore?templates=rhodesrhomobile\n\n### RhodesRhomobile ###\nrholog-*\nsim-*\nbin/libs\nbin/RhoBundle\nbin/tmp\nbin/target\nbin/*.ap_\n*.o\n*.jar\n\n# End of https://www.toptal.com/developers/gitignore/api/rhodesrhomobile\n","rider":"# Created by https://www.toptal.com/developers/gitignore/api/rider\n# Edit at https://www.toptal.com/developers/gitignore?templates=rider\n\n### Rider ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n# End of https://www.toptal.com/developers/gitignore/api/rider\n","robotframework":"# Created by https://www.toptal.com/developers/gitignore/api/robotframework\n# Edit at https://www.toptal.com/developers/gitignore?templates=robotframework\n\n### robotframework ###\nlog.html\noutput.xml\nreport.html\nselenium-screenshot-*.png\n\n# End of https://www.toptal.com/developers/gitignore/api/robotframework\n","root":"# Created by https://www.toptal.com/developers/gitignore/api/root\n# Edit at https://www.toptal.com/developers/gitignore?templates=root\n\n### ROOT ###\n# ROOT Home Page : https://root.cern.ch/\n# ROOT Used by Experimental Physicists, not necessarily HEP\n# ROOT based on C++\n\n# Files generated by ROOT, observed with v6.xy\n\n*.pcm\n*.so\n*.d\n\n\n\n# End of https://www.toptal.com/developers/gitignore/api/root\n","ros":"# Created by https://www.toptal.com/developers/gitignore/api/ros\n# Edit at https://www.toptal.com/developers/gitignore?templates=ros\n\n### ROS ###\ndevel/\nlogs/\nbuild/\nbin/\nlib/\nmsg_gen/\nsrv_gen/\nmsg/*Action.msg\nmsg/*ActionFeedback.msg\nmsg/*ActionGoal.msg\nmsg/*ActionResult.msg\nmsg/*Feedback.msg\nmsg/*Goal.msg\nmsg/*Result.msg\nmsg/_*.py\nbuild_isolated/\ndevel_isolated/\n\n# Generated by dynamic reconfigure\n*.cfgc\n/cfg/cpp/\n/cfg/*.py\n\n# Ignore generated docs\n*.dox\n*.wikidoc\n\n# eclipse stuff\n.project\n.cproject\n\n# qcreator stuff\nCMakeLists.txt.user\n\nsrv/_*.py\n*.pcd\n*.pyc\nqtcreator-*\n*.user\n\n/planning/cfg\n/planning/docs\n/planning/src\n\n*~\n\n# Emacs\n.#*\n\n# Catkin custom files\nCATKIN_IGNORE\n\n# End of https://www.toptal.com/developers/gitignore/api/ros\n","ros2":"# Created by https://www.toptal.com/developers/gitignore/api/ros2\n# Edit at https://www.toptal.com/developers/gitignore?templates=ros2\n\n### ROS2 ###\ninstall/\nlog/\nbuild/\n\n# Ignore generated docs\n*.dox\n*.wikidoc\n\n# eclipse stuff\n.project\n.cproject\n\n# qcreator stuff\nCMakeLists.txt.user\n\nsrv/_*.py\n*.pcd\n*.pyc\nqtcreator-*\n*.user\n\n*~\n\n# Emacs\n.#*\n\n# Colcon custom files\nCOLCON_IGNORE\nAMENT_IGNORE\n\n# End of https://www.toptal.com/developers/gitignore/api/ros2\n","ruby":"# Created by https://www.toptal.com/developers/gitignore/api/ruby\n# Edit at https://www.toptal.com/developers/gitignore?templates=ruby\n\n### Ruby ###\n*.gem\n*.rbc\n/.config\n/coverage/\n/InstalledFiles\n/pkg/\n/spec/reports/\n/spec/examples.txt\n/test/tmp/\n/test/version_tmp/\n/tmp/\n\n# Used by dotenv library to load environment variables.\n# .env\n\n# Ignore Byebug command history file.\n.byebug_history\n\n## Specific to RubyMotion:\n.dat*\n.repl_history\nbuild/\n*.bridgesupport\nbuild-iPhoneOS/\nbuild-iPhoneSimulator/\n\n## Specific to RubyMotion (use of CocoaPods):\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n# vendor/Pods/\n\n## Documentation cache and generated files:\n/.yardoc/\n/_yardoc/\n/doc/\n/rdoc/\n\n## Environment normalization:\n/.bundle/\n/vendor/bundle\n/lib/bundler/man/\n\n# for a library or gem, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# Gemfile.lock\n# .ruby-version\n# .ruby-gemset\n\n# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:\n.rvmrc\n\n# Used by RuboCop. Remote config files pulled in from inherit_from directive.\n# .rubocop-https?--*\n\n# End of https://www.toptal.com/developers/gitignore/api/ruby\n","rubymine":"# Created by https://www.toptal.com/developers/gitignore/api/rubymine\n# Edit at https://www.toptal.com/developers/gitignore?templates=rubymine\n\n### RubyMine ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### RubyMine Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/rubymine\n","rubymine+all":"# Created by https://www.toptal.com/developers/gitignore/api/rubymine+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=rubymine+all\n\n### RubyMine+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### RubyMine+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/rubymine+all\n","rubymine+iml":"# Created by https://www.toptal.com/developers/gitignore/api/rubymine+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=rubymine+iml\n\n### RubyMine+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### RubyMine+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/rubymine+iml\n","rust":"# Created by https://www.toptal.com/developers/gitignore/api/rust\n# Edit at https://www.toptal.com/developers/gitignore?templates=rust\n\n### Rust ###\n# Generated by Cargo\n# will have compiled files and executables\ndebug/\ntarget/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n# MSVC Windows builds of rustc generate these, which store debugging information\n*.pdb\n\n# End of https://www.toptal.com/developers/gitignore/api/rust\n","rust-analyzer":"# Created by https://www.toptal.com/developers/gitignore/api/rust-analyzer\n# Edit at https://www.toptal.com/developers/gitignore?templates=rust-analyzer\n\n### rust-analyzer ###\n# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules) \nrust-project.json\n\n\n# End of https://www.toptal.com/developers/gitignore/api/rust-analyzer\n","salesforce":"# Created by https://www.toptal.com/developers/gitignore/api/salesforce\n# Edit at https://www.toptal.com/developers/gitignore?templates=salesforce\n\n### Salesforce ###\n# GitIgnore for Salesforce Projects\n# Project Settings and MetaData\n.project\n.settings/\n.metadata\nbuild.properties\nconfig\n\n# Apex Log as optional\napex-scripts/log\n\n# Eclipse specific\nsalesforce.schema\nReferenced Packages\nbin/\ntmp/\nconfig/\n*.tmp\n*.bak\nlocal.properties\n.settings\n.loadpath\n.classpath\n*.cache\n\n# Illuminated Cloud (IntelliJ IDEA)\nIlluminatedCloud\nout\n.idea\n*.iml\n\n# Mavensmate\n*.sublime-project\n*.sublime-settings\n*.sublime-workspace\nmm.log\n\n# Haoide SublimeText\n.config\n.deploy\n.history\n\n# OSX-specific exclusions\n.[dD][sS]_[sS]tore\n\n# The Welkin Suite specific\n**/.localHistory\n\n*.sfuo\n\nTestCache.xml\n\nTestsResultsCache.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/salesforce\n","salesforcedx":"# Created by https://www.toptal.com/developers/gitignore/api/salesforcedx\n# Edit at https://www.toptal.com/developers/gitignore?templates=salesforcedx\n\n### SalesforceDX ###\n# GitIgnore for Salesforce Projects\n# Project Settings and MetaData\n.project\n.settings/\n.metadata\nbuild.properties\nconfig\n\n# Apex Log as optional\napex-scripts/log\n\n# Eclipse specific\nsalesforce.schema\nReferenced Packages\nbin/\ntmp/\nconfig/\n*.tmp\n*.bak\nlocal.properties\n.settings\n.loadpath\n.classpath\n*.cache\n\n# Illuminated Cloud (IntelliJ IDEA)\nIlluminatedCloud\nout\n.idea\n*.iml\n\n# Mavensmate\n*.sublime-project\n*.sublime-settings\n*.sublime-workspace\nmm.log\n\n# Haoide SublimeText\n.config\n.deploy\n.history\n\n# OSX-specific exclusions\n.[dD][sS]_[sS]tore\n\n# The Welkin Suite specific\n**/.localHistory\n\n*.sfuo\n\nTestCache.xml\n\nTestsResultsCache.xml\n\n### SalesforceDX Patch ###\n.sfdx\n\n# End of https://www.toptal.com/developers/gitignore/api/salesforcedx\n","sam":"# Created by https://www.toptal.com/developers/gitignore/api/sam\n# Edit at https://www.toptal.com/developers/gitignore?templates=sam\n\n### SAM ###\n# Ignore build directories for the AWS Serverless Application Model (SAM)\n# Info: https://aws.amazon.com/serverless/sam/\n# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html\n\n**/.aws-sam\n\n# End of https://www.toptal.com/developers/gitignore/api/sam\n","sam+config":"# Created by https://www.toptal.com/developers/gitignore/api/sam+config\n# Edit at https://www.toptal.com/developers/gitignore?templates=sam+config\n\n### SAM+config ###\n# Ignore build directories for the AWS Serverless Application Model (SAM)\n# Info: https://aws.amazon.com/serverless/sam/\n# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html\n\n**/.aws-sam\n\n### SAM+config Patch ###\n# SAM config - exclude this file if sharing publicly\nsamconfig.toml\n\n# End of https://www.toptal.com/developers/gitignore/api/sam+config\n","sas":"# Created by https://www.toptal.com/developers/gitignore/api/sas\n# Edit at https://www.toptal.com/developers/gitignore?templates=sas\n\n### SAS ###\n# Binary data formats\n*.sas7baud\n*.sas7bdat\n*.sas7bvew\n*.sas7bndx\n*.sas7bcat\n*.sas7bacs\n*.sas7bfdb\n*.sas7bmdb\n*.sas7bdmd\n*.sas7bitm\n*.sas7butl\n*.sas7bput\n*.sas7bbak\n\n# End of https://www.toptal.com/developers/gitignore/api/sas\n","sass":"# Created by https://www.toptal.com/developers/gitignore/api/sass\n# Edit at https://www.toptal.com/developers/gitignore?templates=sass\n\n### Sass ###\n.sass-cache/\n*.css.map\n*.sass.map\n*.scss.map\n\n# End of https://www.toptal.com/developers/gitignore/api/sass\n","sbt":"# Created by https://www.toptal.com/developers/gitignore/api/sbt\n# Edit at https://www.toptal.com/developers/gitignore?templates=sbt\n\n### SBT ###\n# Simple Build Tool\n# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control\n\ndist/*\ntarget/\nlib_managed/\nsrc_managed/\nproject/boot/\nproject/plugins/project/\n.history\n.cache\n.lib/\n\n### SBT Patch ###\n.bsp/\n\n# End of https://www.toptal.com/developers/gitignore/api/sbt\n","scala":"# Created by https://www.toptal.com/developers/gitignore/api/scala\n# Edit at https://www.toptal.com/developers/gitignore?templates=scala\n\n### Scala ###\n*.class\n*.log\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n\n# End of https://www.toptal.com/developers/gitignore/api/scala\n","scheme":"# Created by https://www.toptal.com/developers/gitignore/api/scheme\n# Edit at https://www.toptal.com/developers/gitignore?templates=scheme\n\n### Scheme ###\n*.ss~\n*.ss#*\n.#*.ss\n\n*.scm~\n*.scm#*\n.#*.scm\n\n# End of https://www.toptal.com/developers/gitignore/api/scheme\n","scons":"# Created by https://www.toptal.com/developers/gitignore/api/scons\n# Edit at https://www.toptal.com/developers/gitignore?templates=scons\n\n### SCons ###\n# for projects that use SCons for building: http://http://www.scons.org/\n.sconsign.dblite\n\n# When configure fails, SCons outputs these\nconfig.log\n.sconf_temp\n\n# End of https://www.toptal.com/developers/gitignore/api/scons\n","scrivener":"# Created by https://www.toptal.com/developers/gitignore/api/scrivener\n# Edit at https://www.toptal.com/developers/gitignore?templates=scrivener\n\n### Scrivener ###\n*/Files/binder.autosave\n*/Files/binder.backup\n*/Files/search.indexes\n*/Files/user.lock\n*/Files/Docs/docs.checksum\n*/Files/Data/docs.checksum\n*/QuickLook/\n*/Settings/ui.plist\n\n# End of https://www.toptal.com/developers/gitignore/api/scrivener\n","sdcc":"# Created by https://www.toptal.com/developers/gitignore/api/sdcc\n# Edit at https://www.toptal.com/developers/gitignore?templates=sdcc\n\n### Sdcc ###\n# SDCC stuff\n*.lnk\n*.lst\n*.map\n*.mem\n*.rel\n*.rst\n*.sym\n\n# End of https://www.toptal.com/developers/gitignore/api/sdcc\n","seamgen":"# Created by https://www.toptal.com/developers/gitignore/api/seamgen\n# Edit at https://www.toptal.com/developers/gitignore?templates=seamgen\n\n### SeamGen ###\n/bootstrap/data\n/bootstrap/tmp\n/classes/\n/dist/\n/exploded-archives/\n/test-build/\n/test-output/\n/test-report/\n/target/\ntemp-testng-customsuite.xml\n\n# based on http://stackoverflow.com/a/8865858/422476 I am removing inline comments\n\n#/classes/ \t\t all class files\n#/dist/ contains generated war files for deployment\n#/exploded-archives/\t\t war content generation during deploy (or explode)\n#/test-build/ test compilation (ant target for Seam)\n#/test-output/ test results\n#/test-report/ test report generation for, e.g., Hudson\n#/target/ maven output folder\n#temp-testng-customsuite.xml\tgenerated when running test cases under Eclipse\n\n# Thanks to @VonC and @kraftan for their helpful answers on a related question\n# on StackOverflow.com:\n# http://stackoverflow.com/questions/4176687\n# /what-is-the-recommended-source-control-ignore-pattern-for-seam-projects\n\n# End of https://www.toptal.com/developers/gitignore/api/seamgen\n","senchatouch":"# Created by https://www.toptal.com/developers/gitignore/api/senchatouch\n# Edit at https://www.toptal.com/developers/gitignore?templates=senchatouch\n\n### SenchaTouch ###\n# Build and Release Folders\narchive/\nbuild/\n\n# sass cache folder\nresources/sass/.sass-cache/\n\n# End of https://www.toptal.com/developers/gitignore/api/senchatouch\n","serverless":"# Created by https://www.toptal.com/developers/gitignore/api/serverless\n# Edit at https://www.toptal.com/developers/gitignore?templates=serverless\n\n### Serverless ###\n# Ignore build directory\n.serverless\n\n# End of https://www.toptal.com/developers/gitignore/api/serverless\n","shopware":"# Created by https://www.toptal.com/developers/gitignore/api/shopware\n# Edit at https://www.toptal.com/developers/gitignore?templates=shopware\n\n### Shopware ###\n# PhpStorm-Project\n.idea/\n\n# Cache and logs\n/var/cache/*\n/var/log/*\n!var/cache/clear_cache.sh\n!var/log/clear_cache.sh\n\n/web/cache\n!web/cache/.gitkeep\n\n/bin/*\n!bin/console\n/vendor/\n\n# User provided content\n/media/archive/\n/media/image/\n/media/music/\n/media/pdf/\n/media/temp/\n/media/unknown/\n/media/video/\n\n/files/documents/\n/files/downloads/\n\n# Caches/Proxies\n/tests/Shopware/TempFiles/\n/cache/\n\n# OS generated files\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nIcon?\nehthumbs.db\nThumbs.db\n\n# SASS/SCSS cache\n.sass-cache/\n\n# End of https://www.toptal.com/developers/gitignore/api/shopware\n","silverstripe":"# Created by https://www.toptal.com/developers/gitignore/api/silverstripe\n# Edit at https://www.toptal.com/developers/gitignore?templates=silverstripe\n\n### Silverstripe ###\nassets/_combinedfiles/\nassets/_resampled/\nassets/Uploads/\nsilverstripe-cache/\n.env\nvendor/\nresources/\nthemes/simple/\npublic/resources/\n\n# End of https://www.toptal.com/developers/gitignore/api/silverstripe\n","sketchup":"# Created by https://www.toptal.com/developers/gitignore/api/sketchup\n# Edit at https://www.toptal.com/developers/gitignore?templates=sketchup\n\n### SketchUp ###\n*.skb\n\n# End of https://www.toptal.com/developers/gitignore/api/sketchup\n","slickedit":"# Created by https://www.toptal.com/developers/gitignore/api/slickedit\n# Edit at https://www.toptal.com/developers/gitignore?templates=slickedit\n\n### SlickEdit ###\n# SlickEdit workspace and project files are ignored by default because\n# typically they are considered to be developer-specific and not part of a\n# project.\n*.vpw\n*.vpj\n\n# SlickEdit workspace history and tag files always contain user-specific\n# data so they should not be stored in a repository.\n*.vpwhistu\n*.vpwhist\n*.vtg\n\n# End of https://www.toptal.com/developers/gitignore/api/slickedit\n","smalltalk":"# Created by https://www.toptal.com/developers/gitignore/api/smalltalk\n# Edit at https://www.toptal.com/developers/gitignore?templates=smalltalk\n\n### Smalltalk ###\n# changes file\n*.changes\n*.chg\n\n# system image\n*.image\n*.img7\n*.img\n\n# Pharo Smalltalk Debug log file\nPharoDebug.log\n\n# Squeak Smalltalk Debug log file\nSqueakDebug.log\n\n# Dolphin Smalltalk source file\n*.sml\n\n# Dolphin Smalltalk error file\n*.errors\n\n# Monticello package cache\n/package-cache\n\n# playground cache\n/play-cache\n/play-stash\n\n# Metacello-github cache\n/github-cache\ngithub-*.zip\n\n# End of https://www.toptal.com/developers/gitignore/api/smalltalk\n","snap":"# Created by https://www.toptal.com/developers/gitignore/api/snap\n# Edit at https://www.toptal.com/developers/gitignore?templates=snap\n\n### Snap ###\n# gitginore template for creating Snap packages\n# website: https://snapcraft.io/\n\nparts/\nprime/\nstage/\n*.snap\n\n# Snapcraft global state tracking data(automatically generated)\n# https://forum.snapcraft.io/t/location-to-save-global-state/768\n/snap/.snapcraft/\n\n# Source archive packed by `snapcraft cleanbuild` before pushing to the LXD container\n/*_source.tar.bz2\n\n# End of https://www.toptal.com/developers/gitignore/api/snap\n","snapcraft":"# Created by https://www.toptal.com/developers/gitignore/api/snapcraft\n# Edit at https://www.toptal.com/developers/gitignore?templates=snapcraft\n\n### Snapcraft ###\n/parts/\n/stage/\n/prime/\n/*.snap\n\n# Snapcraft global state tracking data(automatically generated)\n# https://forum.snapcraft.io/t/location-to-save-global-state/768\n/snap/.snapcraft/\n\n# Source archive packed by `snapcraft cleanbuild` before pushing to the LXD container\n/*_source.tar.bz2\n\n# End of https://www.toptal.com/developers/gitignore/api/snapcraft\n","snyk":"# Created by https://www.toptal.com/developers/gitignore/api/snyk\n# Edit at https://www.toptal.com/developers/gitignore?templates=snyk\n\n### Snyk ###\n# DeepCode\n.dccache\n\n# End of https://www.toptal.com/developers/gitignore/api/snyk\n","solidity":"# Created by https://www.toptal.com/developers/gitignore/api/solidity\n# Edit at https://www.toptal.com/developers/gitignore?templates=solidity\n\n### Solidity ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# End of https://www.toptal.com/developers/gitignore/api/solidity\n","soliditytruffle":"# Created by https://www.toptal.com/developers/gitignore/api/soliditytruffle\n# Edit at https://www.toptal.com/developers/gitignore?templates=soliditytruffle\n\n### SolidityTruffle ###\n# depedencies\nnode_modules\n\n# testing\ncoverage\n\n# production\nbuild\nbuild_webpack\n\n# misc\n.DS_Store\n.env\nnpm-debug.log\n.truffle-solidity-loader\n.vagrant/**\nblockchain/geth/**\nblockchain/keystore/**\nblockchain/history\n\n#truffle\n.tern-port\nyarn.lock\npackage-lock.json\n\n# End of https://www.toptal.com/developers/gitignore/api/soliditytruffle\n","sonar":"# Created by https://www.toptal.com/developers/gitignore/api/sonar\n# Edit at https://www.toptal.com/developers/gitignore?templates=sonar\n\n### Sonar ###\n#Sonar generated dir\n/.sonar/\n\n# End of https://www.toptal.com/developers/gitignore/api/sonar\n","sonarqube":"# Created by https://www.toptal.com/developers/gitignore/api/sonarqube\n# Edit at https://www.toptal.com/developers/gitignore?templates=sonarqube\n\n### SonarQube ###\n# SonarQube ignore files.\n#\n# https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner\n# Sonar Scanner working directories\n.sonar/\n.sonarqube/\n.scannerwork/\n\n# http://www.sonarlint.org/commandline/\n# SonarLint working directories, configuration files (including credentials)\n.sonarlint/\n\n# End of https://www.toptal.com/developers/gitignore/api/sonarqube\n","sourcepawn":"# Created by https://www.toptal.com/developers/gitignore/api/sourcepawn\n# Edit at https://www.toptal.com/developers/gitignore?templates=sourcepawn\n\n### SourcePawn ###\n# SourcePawn\n*.dll # extensions\n*.so # extensions\n*.smx # plugins\n\n# End of https://www.toptal.com/developers/gitignore/api/sourcepawn\n","spark":"# Created by https://www.toptal.com/developers/gitignore/api/spark\n# Edit at https://www.toptal.com/developers/gitignore?templates=spark\n\n### Spark ###\n*#*#\n*.#*\n*.iml\n*.ipr\n*.iws\n*.pyc\n*.pyo\n*.swp\n*~\n.DS_Store\n.cache\n.classpath\n.ensime\n.ensime_cache/\n.ensime_lucene\n.generated-mima*\n.idea/\n.idea_modules/\n.project\n.pydevproject\n.scala_dependencies\n.settings\n/lib/\nR-unit-tests.log\nR/unit-tests.out\nR/cran-check.out\nR/pkg/vignettes/sparkr-vignettes.html\nR/pkg/tests/fulltests/Rplots.pdf\nbuild/*.jar\nbuild/apache-maven*\nbuild/scala*\nbuild/zinc*\ncache\ncheckpoint\nconf/*.cmd\nconf/*.conf\nconf/*.properties\nconf/*.sh\nconf/*.xml\nconf/java-opts\nconf/slaves\ndependency-reduced-pom.xml\nderby.log\ndev/create-release/*final\ndev/create-release/*txt\ndev/pr-deps/\ndist/\ndocs/_site\ndocs/api\nsql/docs\nsql/site\nlib_managed/\nlint-r-report.log\nlog/\nlogs/\nout/\nproject/boot/\nproject/build/target/\nproject/plugins/lib_managed/\nproject/plugins/project/build.properties\nproject/plugins/src_managed/\nproject/plugins/target/\npython/lib/pyspark.zip\npython/deps\npython/test_coverage/coverage_data\npython/test_coverage/htmlcov\npython/pyspark/python\nreports/\nscalastyle-on-compile.generated.xml\nscalastyle-output.xml\nscalastyle.txt\nspark-*-bin-*.tgz\nspark-tests.log\nsrc_managed/\nstreaming-tests.log\ntarget/\nunit-tests.log\nwork/\ndocs/.jekyll-metadata\n\n# For Hive\nTempStatsStore/\nmetastore/\nmetastore_db/\nsql/hive-thriftserver/test_warehouses\nwarehouse/\nspark-warehouse/\n\n# For R session data\n.RData\n.RHistory\n.Rhistory\n*.Rproj\n*.Rproj.*\n\n.Rproj.user\n\n# For SBT\n.jvmopts\n\n\n# End of https://www.toptal.com/developers/gitignore/api/spark\n","specflow":"# Created by https://www.toptal.com/developers/gitignore/api/specflow\n# Edit at https://www.toptal.com/developers/gitignore?templates=specflow\n\n### SpecFlow ###\n# Feature Files\n*.feature.cs\n*.feature.xlsx.*\n\n# End of https://www.toptal.com/developers/gitignore/api/specflow\n","splunk":"# Created by https://www.toptal.com/developers/gitignore/api/splunk\n# Edit at https://www.toptal.com/developers/gitignore?templates=splunk\n\n### Splunk ###\n# gitignore template for Splunk apps\n# documentation: http://docs.splunk.com/Documentation/Splunk/6.2.3/admin/Defaultmetaconf\n\n# Splunk local meta file\nlocal.meta\n\n# Splunk local folder\nlocal\n\n# End of https://www.toptal.com/developers/gitignore/api/splunk\n","spreadsheet":"# Created by https://www.toptal.com/developers/gitignore/api/spreadsheet\n# Edit at https://www.toptal.com/developers/gitignore?templates=spreadsheet\n\n### Spreadsheet ###\n*.xlr\n*.xls\n*.xlsx\n\n# End of https://www.toptal.com/developers/gitignore/api/spreadsheet\n","ssh":"# Created by https://www.toptal.com/developers/gitignore/api/ssh\n# Edit at https://www.toptal.com/developers/gitignore?templates=ssh\n\n### SSH ###\n**/.ssh/id_*\n**/.ssh/*_id_*\n**/.ssh/known_hosts\n\n# End of https://www.toptal.com/developers/gitignore/api/ssh\n","standardml":"# Created by https://www.toptal.com/developers/gitignore/api/standardml\n# Edit at https://www.toptal.com/developers/gitignore?templates=standardml\n\n### StandardML ###\n## Ignore all SML/NJ Compiler Manager build directories\n.cm/\n\n# End of https://www.toptal.com/developers/gitignore/api/standardml\n","stata":"# Created by https://www.toptal.com/developers/gitignore/api/stata\n# Edit at https://www.toptal.com/developers/gitignore?templates=stata\n\n### Stata ###\n# .gitignore file for git projects containing Stata files\n# Commercial statistical software: http://www.stata.com\n\n# Stata dataset and output files\n*.dta\n*.gph\n*.log\n*.smcl\n*.stpr\n*.stsem\n\n# Graphic export files from Stata\n# Stata command graph export: http://www.stata.com/manuals14/g-2graphexport.pdf\n#\n# You may add graphic export files to your .gitignore. However you should be\n# aware that this will exclude all image files from this main directory\n# and subdirectories.\n# *.ps\n# *.eps\n# *.wmf\n# *.emf\n# *.pdf\n# *.png\n# *.tif\n\n# End of https://www.toptal.com/developers/gitignore/api/stata\n","stdlib":"# Created by https://www.toptal.com/developers/gitignore/api/stdlib\n# Edit at https://www.toptal.com/developers/gitignore?templates=stdlib\n\n### StdLib ###\n/node_modules\nenv.json\n\n# End of https://www.toptal.com/developers/gitignore/api/stdlib\n","stella":"# Created by https://www.toptal.com/developers/gitignore/api/stella\n# Edit at https://www.toptal.com/developers/gitignore?templates=stella\n\n### Stella ###\n# Atari 2600 (Stella) support for multiple assemblers\n# - DASM\n# - CC65\n\n# Assembled binaries and object directories\nobj/\na.out\n*.bin\n*.a26\n\n# Add in special Atari 7800-based binaries for good measure\n*.a78\n\n# End of https://www.toptal.com/developers/gitignore/api/stella\n","stellar":"# Created by https://www.toptal.com/developers/gitignore/api/stellar\n# Edit at https://www.toptal.com/developers/gitignore?templates=stellar\n\n### Stellar ###\nstellar.yaml\n\n# End of https://www.toptal.com/developers/gitignore/api/stellar\n","storybookjs":"# Created by https://www.toptal.com/developers/gitignore/api/storybookjs\n# Edit at https://www.toptal.com/developers/gitignore?templates=storybookjs\n\n### StorybookJs ###\n# gitignore template for the Storybook, UI guide for front apps\n# website: https://storybook.js.org/\n\nstorybook-static/\n\n# End of https://www.toptal.com/developers/gitignore/api/storybookjs\n","strapi":"# Created by https://www.toptal.com/developers/gitignore/api/strapi\n# Edit at https://www.toptal.com/developers/gitignore?templates=strapi\n\n### Strapi ###\n\n.env\nlicense.txt\nexports\n*.cache\nbuild\n.strapi-updater.json\n\n############################\n# OS X\n\n.DS_Store\n.AppleDouble\n.LSOverride\nIcon\n.Spotlight-V100\n.Trashes\n._*\n\n# Linux\n\n*~\n\n\n# Windows\n\nThumbs.db\nehthumbs.db\nDesktop.ini\n$RECYCLE.BIN/\n*.cab\n*.msi\n*.msm\n*.msp\n\n\n# Packages\n\n*.7z\n*.csv\n*.dat\n*.dmg\n*.gz\n*.iso\n*.jar\n*.rar\n*.tar\n*.zip\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.seed\n*.so\n*.swo\n*.swp\n*.swn\n*.swm\n*.out\n*.pid\n\n# Logs and databases\n\n.tmp\n*.log\n*.sql\n*.sqlite\n*.sqlite3\n\n\n# Misc.\n\n*#\nssl\n.idea\nnbproject\npublic/uploads/*\n!public/uploads/.gitkeep\n\n# Node.js\n\nlib-cov\nlcov.info\npids\nlogs\nresults\nnode_modules\n.node_history\n\n# Tests\n\ntestApp\ncoverage\n\n# End of https://www.toptal.com/developers/gitignore/api/strapi\n","stylus":"# Created by https://www.toptal.com/developers/gitignore/api/stylus\n# Edit at https://www.toptal.com/developers/gitignore?templates=stylus\n\n### Stylus ###\n*.css\n\n# End of https://www.toptal.com/developers/gitignore/api/stylus\n","sublimetext":"# Created by https://www.toptal.com/developers/gitignore/api/sublimetext\n# Edit at https://www.toptal.com/developers/gitignore?templates=sublimetext\n\n### SublimeText ###\n# Cache files for Sublime Text\n*.tmlanguage.cache\n*.tmPreferences.cache\n*.stTheme.cache\n\n# Workspace files are user-specific\n*.sublime-workspace\n\n# Project files should be checked into the repository, unless a significant\n# proportion of contributors will probably not be using Sublime Text\n# *.sublime-project\n\n# SFTP configuration file\nsftp-config.json\nsftp-config-alt*.json\n\n# Package control specific files\nPackage Control.last-run\nPackage Control.ca-list\nPackage Control.ca-bundle\nPackage Control.system-ca-bundle\nPackage Control.cache/\nPackage Control.ca-certs/\nPackage Control.merged-ca-bundle\nPackage Control.user-ca-bundle\noscrypto-ca-bundle.crt\nbh_unicode_properties.cache\n\n# Sublime-github package stores a github token in this file\n# https://packagecontrol.io/packages/sublime-github\nGitHub.sublime-settings\n\n# End of https://www.toptal.com/developers/gitignore/api/sublimetext\n","sugarcrm":"# Created by https://www.toptal.com/developers/gitignore/api/sugarcrm\n# Edit at https://www.toptal.com/developers/gitignore?templates=sugarcrm\n\n### SugarCRM ###\n## SugarCRM\n# Ignore custom .htaccess stuff.\n/.htaccess\n# Ignore the cache directory completely.\n# This will break the current behaviour. Which was often leading to\n# the misuse of the repository as backup replacement.\n# For development the cache directory can be safely ignored and\n# therefore it is ignored.\n/cache/*\n!/cache/index.html\n# Ignore some files and directories from the custom directory.\n/custom/history/\n/custom/modulebuilder/\n/custom/working/\n/custom/modules/*/Ext/\n/custom/application/Ext/\n# Custom configuration should also be ignored.\n/config.php\n/config_override.php\n# The silent upgrade scripts aren't needed.\n/silentUpgrade*.php\n# Logs files can safely be ignored.\n*.log\n# Ignore the new upload directories.\n/upload/*\n!/upload/index.html\n/upload_backup/\n\n# End of https://www.toptal.com/developers/gitignore/api/sugarcrm\n","svelte":"# Created by https://www.toptal.com/developers/gitignore/api/svelte\n# Edit at https://www.toptal.com/developers/gitignore?templates=svelte\n\n### Svelte ###\n# gitignore template for the SvelteKit, frontend web component framework\n# website: https://kit.svelte.dev/\n\n.svelte-kit/\npackage\n\n# End of https://www.toptal.com/developers/gitignore/api/svelte\n","svn":"# Created by https://www.toptal.com/developers/gitignore/api/svn\n# Edit at https://www.toptal.com/developers/gitignore?templates=svn\n\n### SVN ###\n.svn/\n\n# End of https://www.toptal.com/developers/gitignore/api/svn\n","swift":"# Created by https://www.toptal.com/developers/gitignore/api/swift\n# Edit at https://www.toptal.com/developers/gitignore?templates=swift\n\n### Swift ###\n# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n## User settings\nxcuserdata/\n\n## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)\n*.xcscmblueprint\n*.xccheckout\n\n## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)\nbuild/\nDerivedData/\n*.moved-aside\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\n\n## Obj-C/Swift specific\n*.hmap\n\n## App packaging\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n## Playgrounds\ntimeline.xctimeline\nplayground.xcworkspace\n\n# Swift Package Manager\n# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.\n# Packages/\n# Package.pins\n# Package.resolved\n# *.xcodeproj\n# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata\n# hence it is not needed unless you have added a package configuration file to your project\n# .swiftpm\n\n.build/\n\n# CocoaPods\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n# Pods/\n# Add this line if you want to avoid checking in source code from the Xcode workspace\n# *.xcworkspace\n\n# Carthage\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build/\n\n# Accio dependency management\nDependencies/\n.accio/\n\n# fastlane\n# It is recommended to not store the screenshots in the git repo.\n# Instead, use fastlane to re-generate the screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots/**/*.png\nfastlane/test_output\n\n# Code Injection\n# After new code Injection tools there's a generated folder /iOSInjectionProject\n# https://github.com/johnno1962/injectionforxcode\n\niOSInjectionProject/\n\n# End of https://www.toptal.com/developers/gitignore/api/swift\n","swiftpackagemanager":"# Created by https://www.toptal.com/developers/gitignore/api/swiftpackagemanager\n# Edit at https://www.toptal.com/developers/gitignore?templates=swiftpackagemanager\n\n### SwiftPackageManager ###\nPackages\n.build/\nxcuserdata\nDerivedData/\n*.xcodeproj\n\n\n# End of https://www.toptal.com/developers/gitignore/api/swiftpackagemanager\n","swiftpm":"# Created by https://www.toptal.com/developers/gitignore/api/swiftpm\n# Edit at https://www.toptal.com/developers/gitignore?templates=swiftpm\n\n### SwiftPM ###\nPackages\n.build/\nxcuserdata\nDerivedData/\n*.xcodeproj\n\n\n# End of https://www.toptal.com/developers/gitignore/api/swiftpm\n","symfony":"# Created by https://www.toptal.com/developers/gitignore/api/symfony\n# Edit at https://www.toptal.com/developers/gitignore?templates=symfony\n\n### Symfony ###\n# Cache and logs (Symfony2)\n/app/cache/*\n/app/logs/*\n!app/cache/.gitkeep\n!app/logs/.gitkeep\n\n# Email spool folder\n/app/spool/*\n\n# Cache, session files and logs (Symfony3)\n/var/cache/*\n/var/logs/*\n/var/sessions/*\n!var/cache/.gitkeep\n!var/logs/.gitkeep\n!var/sessions/.gitkeep\n\n# Logs (Symfony4)\n/var/log/*\n!var/log/.gitkeep\n\n# Parameters\n/app/config/parameters.yml\n/app/config/parameters.ini\n\n# Managed by Composer\n/app/bootstrap.php.cache\n/var/bootstrap.php.cache\n/bin/*\n!bin/console\n!bin/symfony_requirements\n/vendor/\n\n# Assets and user uploads\n/web/bundles/\n/web/uploads/\n\n# PHPUnit\n/app/phpunit.xml\n/phpunit.xml\n\n# Build data\n/build/\n\n# Composer PHAR\n/composer.phar\n\n# Backup entities generated with doctrine:generate:entities command\n**/Entity/*~\n\n# Embedded web-server pid file\n/.web-server-pid\n\n### Symfony Patch ###\n/web/css/\n/web/js/\n\n# End of https://www.toptal.com/developers/gitignore/api/symfony\n","symphonycms":"# Created by https://www.toptal.com/developers/gitignore/api/symphonycms\n# Edit at https://www.toptal.com/developers/gitignore?templates=symphonycms\n\n### SymphonyCMS ###\nmanifest/cache/\nmanifest/logs/\nmanifest/tmp/\nsymphony/\nworkspace/uploads/\ninstall-log.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/symphonycms\n","synology":"# Created by https://www.toptal.com/developers/gitignore/api/synology\n# Edit at https://www.toptal.com/developers/gitignore?templates=synology\n\n### Synology ###\n# Thumbnails\n@eaDir\n# Recycle bin\n\\#recycle\n\n# End of https://www.toptal.com/developers/gitignore/api/synology\n","synopsysvcs":"# Created by https://www.toptal.com/developers/gitignore/api/synopsysvcs\n# Edit at https://www.toptal.com/developers/gitignore?templates=synopsysvcs\n\n### SynopsysVCS ###\n# Waveform formats\n*.vcd\n*.vpd\n*.evcd\n*.fsdb\n\n# Default name of the simulation executable. A different name can be\n# specified with this switch (the associated daidir database name is\n# also taken from here): -o /\nsimv\n\n# Generated for Verilog and VHDL top configs\nsimv.daidir/\nsimv.db.dir/\n\n# Infrastructure necessary to co-simulate SystemC models with\n# Verilog/VHDL models. An alternate directory may be specified with this\n# switch: -Mdir=\ncsrc/\n\n# Log file - the following switch allows to specify the file that will be\n# used to write all messages from simulation: -l \n*.log\n\n# Coverage results (generated with urg) and database location. The\n# following switch can also be used: urg -dir .vdb\nsimv.vdb/\nurgReport/\n\n# DVE and UCLI related files.\nDVEfiles/\nucli.key\n\n# When the design is elaborated for DirectC, the following file is created\n# with declarations for C/C++ functions.\nvc_hdrs.h\n\n# End of https://www.toptal.com/developers/gitignore/api/synopsysvcs\n","tags":"# Created by https://www.toptal.com/developers/gitignore/api/tags\n# Edit at https://www.toptal.com/developers/gitignore?templates=tags\n\n### Tags ###\n# Ignore tags created by etags, ctags, gtags (GNU global) and cscope\nTAGS\n.TAGS\n!TAGS/\ntags\n.tags\n!tags/\ngtags.files\nGTAGS\nGRTAGS\nGPATH\nGSYMS\ncscope.files\ncscope.out\ncscope.in.out\ncscope.po.out\n\n\n# End of https://www.toptal.com/developers/gitignore/api/tags\n","tarmainstallmate":"# Created by https://www.toptal.com/developers/gitignore/api/tarmainstallmate\n# Edit at https://www.toptal.com/developers/gitignore?templates=tarmainstallmate\n\n### TarmaInstallMate ###\n# Workspace files\n*.iw7\n*.iw9\n\n# Build log files\n*.log\n\n# End of https://www.toptal.com/developers/gitignore/api/tarmainstallmate\n","terraform":"# Created by https://www.toptal.com/developers/gitignore/api/terraform\n# Edit at https://www.toptal.com/developers/gitignore?templates=terraform\n\n### Terraform ###\n# Local .terraform directories\n**/.terraform/*\n\n# .tfstate files\n*.tfstate\n*.tfstate.*\n\n# Crash log files\ncrash.log\ncrash.*.log\n\n# Exclude all .tfvars files, which are likely to contain sensitive data, such as\n# password, private keys, and other secrets. These should not be part of version\n# control as they are data points which are potentially sensitive and subject\n# to change depending on the environment.\n*.tfvars\n*.tfvars.json\n\n# Ignore override files as they are usually used to override resources locally and so\n# are not checked in\noverride.tf\noverride.tf.json\n*_override.tf\n*_override.tf.json\n\n# Include override files you do wish to add to version control using negated pattern\n# !example_override.tf\n\n# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan\n# example: *tfplan*\n\n# Ignore CLI configuration files\n.terraformrc\nterraform.rc\n\n# End of https://www.toptal.com/developers/gitignore/api/terraform\n","terragrunt":"# Created by https://www.toptal.com/developers/gitignore/api/terragrunt\n# Edit at https://www.toptal.com/developers/gitignore?templates=terragrunt\n\n### Terragrunt ###\n# terragrunt cache directories\n**/.terragrunt-cache/*\n\n# Terragrunt debug output file (when using `--terragrunt-debug` option)\n# See: https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-debug\nterragrunt-debug.tfvars.json\n\n# End of https://www.toptal.com/developers/gitignore/api/terragrunt\n","test":"# Created by https://www.toptal.com/developers/gitignore/api/test\n# Edit at https://www.toptal.com/developers/gitignore?templates=test\n\n### Test ###\n### Ignore all files that could be used to test your code and\n### you wouldn't want to push\n\n# Reference https://en.wikipedia.org/wiki/Metasyntactic_variable\n\n# Most common\n*foo\n*bar\n*fubar\n*foobar\n*baz\n\n# Less common\n*qux\n*quux\n*bongo\n*bazola\n*ztesch\n\n# UK, Australia\n*wibble\n*wobble\n*wubble\n*flob\n*blep\n*blah\n*boop\n*beep\n\n# Japanese\n*hoge\n*piyo\n*fuga\n*hogera\n*hogehoge\n\n# Portugal, Spain\n*fulano\n*sicrano\n*beltrano\n*mengano\n*perengano\n*zutano\n\n# France, Italy, the Netherlands\n*toto\n*titi\n*tata\n*tutu\n*pipppo\n*pluto\n*paperino\n*aap\n*noot\n*mies\n\n# Other names that would make sense\n*tests\n*testsdir\n*testsfile\n*testsfiles\n*test\n*testdir\n*testfile\n*testfiles\n*testing\n*testingdir\n*testingfile\n*testingfiles\n*temp\n*tempdir\n*tempfile\n*tempfiles\n*tmp\n*tmpdir\n*tmpfile\n*tmpfiles\n*lol\n\n# End of https://www.toptal.com/developers/gitignore/api/test\n","testcomplete":"# Created by https://www.toptal.com/developers/gitignore/api/testcomplete\n# Edit at https://www.toptal.com/developers/gitignore?templates=testcomplete\n\n### TestComplete ###\n# Test Complete ignore files: https://support.smartbear.com/viewarticle/68002/\n\n# Tester-specific Settings\n*.tcCFGExtender\n*.tcLS\n\n# Type library declarations\n*.tlb\n\n# Log files\n*.tcLogs\n\n# Backup files\n*.bak\n\n# End of https://www.toptal.com/developers/gitignore/api/testcomplete\n","testinfra":"# Created by https://www.toptal.com/developers/gitignore/api/testinfra\n# Edit at https://www.toptal.com/developers/gitignore?templates=testinfra\n\n### Testinfra ###\n__pycache__/\n.pytest_cache/\n\n# End of https://www.toptal.com/developers/gitignore/api/testinfra\n","tex":"# Created by https://www.toptal.com/developers/gitignore/api/tex\n# Edit at https://www.toptal.com/developers/gitignore?templates=tex\n\n### TeX ###\n## Core latex/pdflatex auxiliary files:\n*.aux\n*.lof\n*.log\n*.lot\n*.fls\n*.out\n*.toc\n*.fmt\n*.fot\n*.cb\n*.cb2\n.*.lb\n\n## Intermediate documents:\n*.dvi\n*.xdv\n*-converted-to.*\n# these rules might exclude image files for figures etc.\n# *.ps\n# *.eps\n# *.pdf\n\n## Generated if empty string is given at \"Please type another file name for output:\"\n.pdf\n\n## Bibliography auxiliary files (bibtex/biblatex/biber):\n*.bbl\n*.bcf\n*.blg\n*-blx.aux\n*-blx.bib\n*.run.xml\n\n## Build tool auxiliary files:\n*.fdb_latexmk\n*.synctex\n*.synctex(busy)\n*.synctex.gz\n*.synctex.gz(busy)\n*.pdfsync\n\n## Build tool directories for auxiliary files\n# latexrun\nlatex.out/\n\n## Auxiliary and intermediate files from other packages:\n# algorithms\n*.alg\n*.loa\n\n# achemso\nacs-*.bib\n\n# amsthm\n*.thm\n\n# beamer\n*.nav\n*.pre\n*.snm\n*.vrb\n\n# changes\n*.soc\n\n# comment\n*.cut\n\n# cprotect\n*.cpt\n\n# elsarticle (documentclass of Elsevier journals)\n*.spl\n\n# endnotes\n*.ent\n\n# fixme\n*.lox\n\n# feynmf/feynmp\n*.mf\n*.mp\n*.t[1-9]\n*.t[1-9][0-9]\n*.tfm\n\n#(r)(e)ledmac/(r)(e)ledpar\n*.end\n*.?end\n*.[1-9]\n*.[1-9][0-9]\n*.[1-9][0-9][0-9]\n*.[1-9]R\n*.[1-9][0-9]R\n*.[1-9][0-9][0-9]R\n*.eledsec[1-9]\n*.eledsec[1-9]R\n*.eledsec[1-9][0-9]\n*.eledsec[1-9][0-9]R\n*.eledsec[1-9][0-9][0-9]\n*.eledsec[1-9][0-9][0-9]R\n\n# glossaries\n*.acn\n*.acr\n*.glg\n*.glo\n*.gls\n*.glsdefs\n*.lzo\n*.lzs\n*.slg\n*.slo\n*.sls\n\n# uncomment this for glossaries-extra (will ignore makeindex's style files!)\n# *.ist\n\n# gnuplot\n*.gnuplot\n*.table\n\n# gnuplottex\n*-gnuplottex-*\n\n# gregoriotex\n*.gaux\n*.glog\n*.gtex\n\n# htlatex\n*.4ct\n*.4tc\n*.idv\n*.lg\n*.trc\n*.xref\n\n# hyperref\n*.brf\n\n# knitr\n*-concordance.tex\n# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files\n# *.tikz\n*-tikzDictionary\n\n# listings\n*.lol\n\n# luatexja-ruby\n*.ltjruby\n\n# makeidx\n*.idx\n*.ilg\n*.ind\n\n# minitoc\n*.maf\n*.mlf\n*.mlt\n*.mtc[0-9]*\n*.slf[0-9]*\n*.slt[0-9]*\n*.stc[0-9]*\n\n# minted\n_minted*\n*.pyg\n\n# morewrites\n*.mw\n\n# newpax\n*.newpax\n\n# nomencl\n*.nlg\n*.nlo\n*.nls\n\n# pax\n*.pax\n\n# pdfpcnotes\n*.pdfpc\n\n# sagetex\n*.sagetex.sage\n*.sagetex.py\n*.sagetex.scmd\n\n# scrwfile\n*.wrt\n\n# svg\nsvg-inkscape/\n\n# sympy\n*.sout\n*.sympy\nsympy-plots-for-*.tex/\n\n# pdfcomment\n*.upa\n*.upb\n\n# pythontex\n*.pytxcode\npythontex-files-*/\n\n# tcolorbox\n*.listing\n\n# thmtools\n*.loe\n\n# TikZ & PGF\n*.dpth\n*.md5\n*.auxlock\n\n# titletoc\n*.ptc\n\n# todonotes\n*.tdo\n\n# vhistory\n*.hst\n*.ver\n\n# easy-todo\n*.lod\n\n# xcolor\n*.xcp\n\n# xmpincl\n*.xmpi\n\n# xindy\n*.xdy\n\n# xypic precompiled matrices and outlines\n*.xyc\n*.xyd\n\n# endfloat\n*.ttt\n*.fff\n\n# Latexian\nTSWLatexianTemp*\n\n## Editors:\n# WinEdt\n*.bak\n*.sav\n\n# Texpad\n.texpadtmp\n\n# LyX\n*.lyx~\n\n# Kile\n*.backup\n\n# gummi\n.*.swp\n\n# KBibTeX\n*~[0-9]*\n\n# TeXnicCenter\n*.tps\n\n# auto folder when using emacs and auctex\n./auto/*\n*.el\n\n# expex forward references with \\gathertags\n*-tags.tex\n\n# standalone packages\n*.sta\n\n# Makeindex log files\n*.lpz\n\n# xwatermark package\n*.xwm\n\n# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib\n# option is specified. Footnotes are the stored in a file with suffix Notes.bib.\n# Uncomment the next line to have this generated file ignored.\n#*Notes.bib\n\n### TeX Patch ###\n# LIPIcs / OASIcs\n*.vtc\n\n# glossaries\n*.glstex\n\n# End of https://www.toptal.com/developers/gitignore/api/tex\n","text":"# Created by https://www.toptal.com/developers/gitignore/api/text\n# Edit at https://www.toptal.com/developers/gitignore?templates=text\n\n### Text ###\n*.doc\n*.docx\n*.log\n*.msg\n*.pages\n*.rtf\n*.txt\n*.wpd\n*.wps\n\n# End of https://www.toptal.com/developers/gitignore/api/text\n","textmate":"# Created by https://www.toptal.com/developers/gitignore/api/textmate\n# Edit at https://www.toptal.com/developers/gitignore?templates=textmate\n\n### TextMate ###\n*.tmproj\n*.tmproject\ntmtags\n\n# End of https://www.toptal.com/developers/gitignore/api/textmate\n","textpattern":"# Created by https://www.toptal.com/developers/gitignore/api/textpattern\n# Edit at https://www.toptal.com/developers/gitignore?templates=textpattern\n\n### Textpattern ###\n.htaccess\ncss.php\nrpc/\nsites/site*/admin/\nsites/site*/private/\nsites/site*/public/admin/\nsites/site*/public/setup/\nsites/site*/public/theme/\ntextpattern/\nHISTORY.txt\nREADME.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/textpattern\n","theos-tweak":"# Created by https://www.toptal.com/developers/gitignore/api/theos-tweak\n# Edit at https://www.toptal.com/developers/gitignore?templates=theos-tweak\n\n### THEOS-Tweak ###\n._*\n*.deb\n.debmake\n_\nobj\n.theos\n.DS_Store\n\n# End of https://www.toptal.com/developers/gitignore/api/theos-tweak\n","thinkphp":"# Created by https://www.toptal.com/developers/gitignore/api/thinkphp\n# Edit at https://www.toptal.com/developers/gitignore?templates=thinkphp\n\n### ThinkPHP ###\n# gitignore template for ThinkPHP v3.2.3\n# website: http://www.thinkphp.cn/\n\n# Logs and Cache files\n/Application/Runtime/\n\n# Common configure file\n/Application/Common/Conf/config.php\n\n# End of https://www.toptal.com/developers/gitignore/api/thinkphp\n","tla+":"# Created by https://www.toptal.com/developers/gitignore/api/tla+\n# Edit at https://www.toptal.com/developers/gitignore?templates=tla+\n\n### TLA+ ###\n# Reference: https://github.com/jameshfisher/tlaplus/blob/master/.gitignore\n\n# TLA+ Toolbox\n# Exclude all files related to TLA+ Toolbox\n# *.toolbox/**\n\n*.toolbox/*.launch\n*.toolbox/*.pmap\n\n# TLA+ Toolbox models\n*.toolbox/**/*.tla\n*.toolbox/**/*.out\n*.toolbox/**/*.cfg\n\n# PlusCal\n*.old\n\n# LaTex output\n*.toolbox/*aux\n*.toolbox/*.log\n*.toolbox/*.pdf\n*.toolbox/*.tex\n*aux\n*.log\n*.pdf\n*.tex\n\n# Vim backup file\n*~\n\n# End of https://www.toptal.com/developers/gitignore/api/tla+\n","toit":"# Created by https://www.toptal.com/developers/gitignore/api/toit\n# Edit at https://www.toptal.com/developers/gitignore?templates=toit\n\n### Toit ###\n# Package downloads\n.packages/\n\n# End of https://www.toptal.com/developers/gitignore/api/toit\n","tortoisegit":"# Created by https://www.toptal.com/developers/gitignore/api/tortoisegit\n# Edit at https://www.toptal.com/developers/gitignore?templates=tortoisegit\n\n### TortoiseGit ###\n# Project-level settings\n/.tgitconfig\n\n# End of https://www.toptal.com/developers/gitignore/api/tortoisegit\n","tower":"# Created by https://www.toptal.com/developers/gitignore/api/tower\n# Edit at https://www.toptal.com/developers/gitignore?templates=tower\n\n### Tower ###\n# Tower.app - http://www.git-tower.com/\nIcon.png\n\n# End of https://www.toptal.com/developers/gitignore/api/tower\n","turbo":"# Created by https://www.toptal.com/developers/gitignore/api/turbo\n# Edit at https://www.toptal.com/developers/gitignore?templates=turbo\n\n### Turbo ###\n# Turborepo task cache\n.turbo\n\n# End of https://www.toptal.com/developers/gitignore/api/turbo\n","turbogears2":"# Created by https://www.toptal.com/developers/gitignore/api/turbogears2\n# Edit at https://www.toptal.com/developers/gitignore?templates=turbogears2\n\n### TurboGears2 ###\n*.py[co]\n\n# Default development database\ndevdata.db\n\n# Default data directory\ndata/*\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\n\n# End of https://www.toptal.com/developers/gitignore/api/turbogears2\n","twincat3":"# Created by https://www.toptal.com/developers/gitignore/api/twincat3\n# Edit at https://www.toptal.com/developers/gitignore?templates=twincat3\n\n### TwinCAT3 ###\n# TwinCAT3 Stack Base\n\n# TwinCAT 3.1\n\n# include suggestions from GitHub: https://github.com/github/gitignore/pull/3092\n#\n# website: https://www.beckhoff.com/twincat3/\n\n# TwinCAT3 PLC\n*.plcproj.bak\n*.plcproj.orig\n*.tpy\n*.tclrs\n*.compiled-library\n*.library\n*.compileinfo\nLineIDs.dbg\nLineIDs.dbg.bak\n\n# Don't include the tmc-file rule if either of the following is true:\n# 1. You've got TwinCAT C++ projects, as the information in the TMC-file is created manually for the C++ projects (in that case, only (manually) ignore the tmc-files for the PLC projects)\n# 2. You've created a standalone PLC-project and added events to it, as these are stored in the TMC-file.\n# 3. shared tmc files are used to exchange data types that should be persisted in the project\n*.tmc\n*.tmcRefac\n\n# TwinCAT3 project files\n*.tsproj.bak\n*.tsproj.orig\n*.xti.bak\n*.xti.orig\n*.project.~u\n\n# folders that should be excluded\n_Boot/\n_CompileInfo/\n_Libraries/\n_ModuleInstall/\n_Deployment/\n_Repository/\n\n### TwinCAT3.VisualStudio Stack ###\nVisualStudio.gitignore\n\n\n### TwinCAT3.OpenFrameworks Stack ###\nOpenFrameworks.gitignore\n\n\n# End of https://www.toptal.com/developers/gitignore/api/twincat3\n","tye":"# Created by https://www.toptal.com/developers/gitignore/api/tye\n# Edit at https://www.toptal.com/developers/gitignore?templates=tye\n\n### Tye ###\n# Tye | https://github.com/dotnet/tye/\n.tye/*\n\n# End of https://www.toptal.com/developers/gitignore/api/tye\n","typings":"# Created by https://www.toptal.com/developers/gitignore/api/typings\n# Edit at https://www.toptal.com/developers/gitignore?templates=typings\n\n### Typings ###\n## Ignore downloaded typings\ntypings\n\n# End of https://www.toptal.com/developers/gitignore/api/typings\n","typo3":"# Created by https://www.toptal.com/developers/gitignore/api/typo3\n# Edit at https://www.toptal.com/developers/gitignore?templates=typo3\n\n### Typo3 ###\n## TYPO3 v6.2\n# Ignore several upload and file directories.\n/fileadmin/user_upload/\n/fileadmin/_temp_/\n/fileadmin/_processed_/\n/uploads/\n# Ignore cache\n/typo3conf/temp_CACHED*\n/typo3conf/temp_fieldInfo.php\n/typo3conf/deprecation_*.log\n/typo3conf/ENABLE_INSTALL_TOOL\n/typo3conf/realurl_autoconf.php\n/FIRST_INSTALL\n# Ignore system folders, you should have them symlinked.\n# If not comment out the following entries.\n/typo3\n/typo3_src\n/typo3_src-*\n/Packages\n/.htaccess\n/index.php\n# Ignore temp directory.\n/typo3temp/\n\n# End of https://www.toptal.com/developers/gitignore/api/typo3\n","typo3-composer":"# Created by https://www.toptal.com/developers/gitignore/api/typo3-composer\n# Edit at https://www.toptal.com/developers/gitignore?templates=typo3-composer\n\n### TYPO3-composer ###\n/vendor\n/web/index.php\n/web/fileadmin/\n/web/FIRST_INSTALL\n/web/typo3\n/web/typo3conf/ENABLE_INSTALL_TOOL\n/web/typo3conf/deprecation*\n/web/typo3conf/PackageStates.php\n/web/typo3conf/ext/\n/web/typo3conf/l10n/\n/web/typo3conf/realurl_autoconf.php\n/web/typo3temp\n/web/uploads/\n\n# End of https://www.toptal.com/developers/gitignore/api/typo3-composer\n","umbraco":"# Created by https://www.toptal.com/developers/gitignore/api/umbraco\n# Edit at https://www.toptal.com/developers/gitignore?templates=umbraco\n\n### Umbraco ###\n## Ignore Umbraco files/folders generated for each instance\n##\n## Get latest from https://github.com/github/gitignore/blob/main/Umbraco.gitignore\n\n# Note: VisualStudio gitignore rules may also be relevant\n\n# Umbraco\n# Ignore unimportant folders generated by Umbraco\n**/App_Data/Logs/\n**/App_Data/[Pp]review/\n**/App_Data/TEMP/\n**/App_Data/NuGetBackup/\n\n# Ignore Umbraco content cache file\n**/App_Data/umbraco.config\n\n## this [Uu]mbraco/ folder should be created by cmd like `Install-Package UmbracoCms -Version 8.5.3`\n## you can find your Umbraco version in your Web.config. (i.e. )\n## Uncomment this line if you think it fits the way you work on your project.\n## **/[Uu]mbraco/\n\n## The [Mm]edia/ folder contains content. Content may vary by environment and should therefore not be added to source control.\n## **/[Mm]edia/\n\n# Don't ignore Umbraco packages (VisualStudio.gitignore mistakes this for a NuGet packages folder)\n# Make sure to include details from VisualStudio.gitignore BEFORE this\n!**/App_Data/[Pp]ackages/*\n!**/[Uu]mbraco/[Dd]eveloper/[Pp]ackages/*\n!**/[Uu]mbraco/[Vv]iews/[Pp]ackages/*\n\n# ImageProcessor DiskCache\n**/App_Data/cache/\n\n# Ignore the Models Builder models out of date flag\n**/ood.flag\n\n# NEW for version 9 .Net 5 (Core)\n#ignore umbraco backoffice assest from wwwroot\n**/wwwroot/umbraco/\n\n# SQLite files\n*.sqlite.db*\n\n#ignore umbraco data/views/settings\n**/umbraco/\n\n#include default location for modelsbuilder output\n!**/umbraco/models\n\n#include default location for packages\n!**/umbraco/Data/packages\n\n# End of https://www.toptal.com/developers/gitignore/api/umbraco\n","unity":"# Created by https://www.toptal.com/developers/gitignore/api/unity\n# Edit at https://www.toptal.com/developers/gitignore?templates=unity\n\n### Unity ###\n# This .gitignore file should be placed at the root of your Unity project directory\n#\n# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore\n/[Ll]ibrary/\n/[Tt]emp/\n/[Oo]bj/\n/[Bb]uild/\n/[Bb]uilds/\n/[Ll]ogs/\n/[Uu]ser[Ss]ettings/\n\n# MemoryCaptures can get excessive in size.\n# They also could contain extremely sensitive data\n/[Mm]emoryCaptures/\n\n# Recordings can get excessive in size\n/[Rr]ecordings/\n\n# Uncomment this line if you wish to ignore the asset store tools plugin\n# /[Aa]ssets/AssetStoreTools*\n\n# Autogenerated Jetbrains Rider plugin\n/[Aa]ssets/Plugins/Editor/JetBrains*\n\n# Visual Studio cache directory\n.vs/\n\n# Gradle cache directory\n.gradle/\n\n# Autogenerated VS/MD/Consulo solution and project files\nExportedObj/\n.consulo/\n*.csproj\n*.unityproj\n*.sln\n*.suo\n*.tmp\n*.user\n*.userprefs\n*.pidb\n*.booproj\n*.svd\n*.pdb\n*.mdb\n*.opendb\n*.VC.db\n\n# Unity3D generated meta files\n*.pidb.meta\n*.pdb.meta\n*.mdb.meta\n\n# Unity3D generated file on crash reports\nsysinfo.txt\n\n# Builds\n*.apk\n*.aab\n*.unitypackage\n*.app\n\n# Crashlytics generated file\ncrashlytics-build.properties\n\n# Packed Addressables\n/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*\n\n# Temporary auto-generated Android Assets\n/[Aa]ssets/[Ss]treamingAssets/aa.meta\n/[Aa]ssets/[Ss]treamingAssets/aa/*\n\n# End of https://www.toptal.com/developers/gitignore/api/unity\n","unrealengine":"# Created by https://www.toptal.com/developers/gitignore/api/unrealengine\n# Edit at https://www.toptal.com/developers/gitignore?templates=unrealengine\n\n### UnrealEngine ###\n# Visual Studio 2015 user specific files\n.vs/\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n*.ipa\n\n# These project files can be generated by the engine\n*.xcodeproj\n*.xcworkspace\n*.sln\n*.suo\n*.opensdf\n*.sdf\n*.VC.db\n*.VC.opendb\n\n# Precompiled Assets\nSourceArt/**/*.png\nSourceArt/**/*.tga\n\n# Binary Files\nBinaries/*\nPlugins/*/Binaries/*\n\n# Builds\nBuild/*\n\n# Whitelist PakBlacklist-.txt files\n!Build/*/\nBuild/*/**\n!Build/*/PakBlacklist*.txt\n\n# Don't ignore icon files in Build\n!Build/**/*.ico\n\n# Built data for maps\n*_BuiltData.uasset\n\n# Configuration files generated by the Editor\nSaved/*\n\n# Compiled source files for the engine to use\nIntermediate/*\nPlugins/*/Intermediate/*\n\n# Cache files for the editor to use\nDerivedDataCache/*\n\n### UnrealEngine Patch ###\n# Don't ignore icon and splash images for mobile app\n!Build/IOS/Resources/\nBuild/IOS/Resources/*\n!Build/IOS/Resources/Graphics/\nBuild/IOS/Resources/Graphics/*\n!Build/IOS/Resources/Graphics/*.png\n!Build/Android/res/\nBuild/Android/res/*\n!Build/Android/res/*/\nBuild/Android/res/*/*\n!Build/Android/res/*/*.png\n# Ignore plugins binaries on deep subfolders\nPlugins/**/Binaries/*\nPlugins/**/Intermediate/*\n\n# End of https://www.toptal.com/developers/gitignore/api/unrealengine\n","vaadin":"# Created by https://www.toptal.com/developers/gitignore/api/vaadin\n# Edit at https://www.toptal.com/developers/gitignore?templates=vaadin\n\n### Vaadin ###\n.vaadin-designer\n.designer\n\n# End of https://www.toptal.com/developers/gitignore/api/vaadin\n","vagrant":"# Created by https://www.toptal.com/developers/gitignore/api/vagrant\n# Edit at https://www.toptal.com/developers/gitignore?templates=vagrant\n\n### Vagrant ###\n# General\n.vagrant/\n\n# Log files (if you are creating logs in debug mode, uncomment this)\n# *.log\n\n### Vagrant Patch ###\n*.box\n\n# End of https://www.toptal.com/developers/gitignore/api/vagrant\n","valgrind":"# Created by https://www.toptal.com/developers/gitignore/api/valgrind\n# Edit at https://www.toptal.com/developers/gitignore?templates=valgrind\n\n### Valgrind ###\n# Callgrind output files\ncallgrind.out\ncallgrind.out.*\n\n# Cachegrind output files\ncachegrind.out\ncachegrind.out.*\n\n# Massif output files\nmassif.out\nmassif.out.*\n\n# BBV output files\nbb.out\nbb.out.*\n\n\n\n# End of https://www.toptal.com/developers/gitignore/api/valgrind\n","vapor":"# Created by https://www.toptal.com/developers/gitignore/api/vapor\n# Edit at https://www.toptal.com/developers/gitignore?templates=vapor\n\n### Vapor ###\nConfig/secrets\n\n### Vapor Patch ###\nPackages\n.build/\nxcuserdata\nDerivedData/\n*.xcodeproj\n\n\n# End of https://www.toptal.com/developers/gitignore/api/vapor\n","vcpkg":"# Created by https://www.toptal.com/developers/gitignore/api/vcpkg\n# Edit at https://www.toptal.com/developers/gitignore?templates=vcpkg\n\n### vcpkg ###\n# Vcpkg\n\nvcpkg-manifest-install.log\n\n## Manifest Mode\nvcpkg_installed/\n\n# End of https://www.toptal.com/developers/gitignore/api/vcpkg\n","venv":"# Created by https://www.toptal.com/developers/gitignore/api/venv\n# Edit at https://www.toptal.com/developers/gitignore?templates=venv\n\n### venv ###\n# Virtualenv\n# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/\n.Python\n[Bb]in\n[Ii]nclude\n[Ll]ib\n[Ll]ib64\n[Ll]ocal\n[Ss]cripts\npyvenv.cfg\n.venv\npip-selfcheck.json\n\n# End of https://www.toptal.com/developers/gitignore/api/venv\n","vercel":"# Created by https://www.toptal.com/developers/gitignore/api/vercel\n# Edit at https://www.toptal.com/developers/gitignore?templates=vercel\n\n### Vercel ###\n.vercel\n\n# End of https://www.toptal.com/developers/gitignore/api/vercel\n","vertx":"# Created by https://www.toptal.com/developers/gitignore/api/vertx\n# Edit at https://www.toptal.com/developers/gitignore?templates=vertx\n\n### Vertx ###\n# Vertx cache directory\n.vertx\n\n# End of https://www.toptal.com/developers/gitignore/api/vertx\n","video":"# Created by https://www.toptal.com/developers/gitignore/api/video\n# Edit at https://www.toptal.com/developers/gitignore?templates=video\n\n### Video ###\n*.3g2\n*.3gp\n*.asf\n*.asx\n*.avi\n*.flv\n*.mkv\n*.mov\n*.mp4\n*.mpg\n*.ogv\n*.rm\n*.swf\n*.vob\n*.wmv\n*.webm\n\n# End of https://www.toptal.com/developers/gitignore/api/video\n","vim":"# Created by https://www.toptal.com/developers/gitignore/api/vim\n# Edit at https://www.toptal.com/developers/gitignore?templates=vim\n\n### Vim ###\n# Swap\n[._]*.s[a-v][a-z]\n!*.svg # comment out if you don't need vector files\n[._]*.sw[a-p]\n[._]s[a-rt-v][a-z]\n[._]ss[a-gi-z]\n[._]sw[a-p]\n\n# Session\nSession.vim\nSessionx.vim\n\n# Temporary\n.netrwhist\n*~\n# Auto-generated tag files\ntags\n# Persistent undo\n[._]*.un~\n\n# End of https://www.toptal.com/developers/gitignore/api/vim\n","virtualenv":"# Created by https://www.toptal.com/developers/gitignore/api/virtualenv\n# Edit at https://www.toptal.com/developers/gitignore?templates=virtualenv\n\n### VirtualEnv ###\n# Virtualenv\n# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/\n.Python\n[Bb]in\n[Ii]nclude\n[Ll]ib\n[Ll]ib64\n[Ll]ocal\n[Ss]cripts\npyvenv.cfg\n.venv\npip-selfcheck.json\n\n# End of https://www.toptal.com/developers/gitignore/api/virtualenv\n","virtuoso":"# Created by https://www.toptal.com/developers/gitignore/api/virtuoso\n# Edit at https://www.toptal.com/developers/gitignore?templates=virtuoso\n\n### Virtuoso ###\n# Gitignore for Cadence Virtuoso\n################################################################\n\n# Log files\n*.log\npanic*.log.*\n\n# OpenAccess database lock files\n*.cdslck*\n\n# Run directories for layout vs. schematic and design rule check\nlvsRunDir/*\ndrcRunDir/*\n\n# Abstract generation tool\nabstract.log*\nabstract.record*\n\n\n# End of https://www.toptal.com/developers/gitignore/api/virtuoso\n","visualbasic":"# Created by https://www.toptal.com/developers/gitignore/api/visualbasic\n# Edit at https://www.toptal.com/developers/gitignore?templates=visualbasic\n\n### VisualBasic ###\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n.DS_Store\n.Trashes\n*.vbw\n*.csi\n*.exp\n*.lib\n*.lvw\n*.dca\n*.scc\n*.tmp\n*.exe\n*.bat\n*.bak\n*.zip\n*.old\n*.enc\n*.key\n# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# =========================\n# Operating System Files\n# =========================\n\n# OSX\n# =========================\n\n.AppleDouble\n.LSOverride\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.VolumeIcon.icns\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# End of https://www.toptal.com/developers/gitignore/api/visualbasic\n","visualstudio":"# Created by https://www.toptal.com/developers/gitignore/api/visualstudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio\n\n### VisualStudio ###\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.tlog\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio 6 auto-generated project file (contains which files were open etc.)\n*.vbp\n\n# Visual Studio 6 workspace and project file (working project files containing files to include in project)\n*.dsw\n*.dsp\n\n# Visual Studio 6 technical files\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# Visual Studio History (VSHistory) files\n.vshistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd\n\n# VS Code files for those working on multiple tools\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# Local History for Visual Studio Code\n.history/\n\n# Windows Installer files from build outputs\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# JetBrains Rider\n*.sln.iml\n\n### VisualStudio Patch ###\n# Additional files built by Visual Studio\n\n# End of https://www.toptal.com/developers/gitignore/api/visualstudio\n","visualstudiocode":"# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode\n# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode\n\n### VisualStudioCode ###\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n!.vscode/*.code-snippets\n\n# Local History for Visual Studio Code\n.history/\n\n# Built Visual Studio Code Extensions\n*.vsix\n\n### VisualStudioCode Patch ###\n# Ignore all local history of files\n.history\n.ionide\n\n# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode\n","vivado":"# Created by https://www.toptal.com/developers/gitignore/api/vivado\n# Edit at https://www.toptal.com/developers/gitignore?templates=vivado\n\n### Vivado ###\n#########################################################################################################\n##\tThis is an example .gitignore file for Vivado, please treat it as an example as\n##\tit might not be complete. In addition, XAPP 1165 should be followed.\n#########\n#Exclude all\n*\n!*/\n!.gitignore\n###########################################################################\n##\tVIVADO\n#Source files:\n#Do NOT ignore VHDL, Verilog, block diagrams or EDIF files.\n!*.vhd\n!*.v\n!*.sv\n!*.bd\n!*.edif\n#IP files\n#.xci: synthesis and implemented not possible - you need to return back to the previous version to generate output products\n#.xci + .dcp: implementation possible but not re-synthesis\n#*.xci(www.spiritconsortium.org)\n!*.xci\n#.xcix: Core container file\n#.xcix: https://www.xilinx.com/support/documentation/sw_manuals/xilinx2016_2/ug896-vivado-ip.pdf (Page 41)\n!*.xcix\n#*.dcp(checkpoint files)\n!*.dcp\n!*.vds\n!*.pb\n#All bd comments and layout coordinates are stored within .ui\n!*.ui\n!*.ooc\n#System Generator\n!*.mdl\n!*.slx\n!*.bxml\n#Simulation logic analyzer\n!*.wcfg\n!*.coe\n#MIG\n!*.prj\n!*.mem\n#Project files\n#XPR + *.XML ? XPR (Files are merged into a single XPR file for 2014.1 version)\n#Do NOT ignore *.xpr files\n!*.xpr\n#Include *.xml files for 2013.4 or earlier version\n!*.xml\n#Constraint files\n#Do NOT ignore *.xdc files\n!*.xdc\n#TCL - files\n!*.tcl\n#Journal - files\n!*.jou\n#Reports\n!*.rpt\n!*.txt\n!*.vdi\n#C-files\n!*.c\n!*.h\n!*.elf\n!*.bmm\n!*.xmp\n\n# End of https://www.toptal.com/developers/gitignore/api/vivado\n","vlab":"# Created by https://www.toptal.com/developers/gitignore/api/vlab\n# Edit at https://www.toptal.com/developers/gitignore?templates=vlab\n\n### VLab ###\n# VLab/Lstudio files\ninput.i\nlsys.dll\nlsys.so\nlsys.exp\nlsys.i\nlsys.ii\nlsys.lib\nlsys.obj\nlsys.o\nlpfg.log\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n\n# End of https://www.toptal.com/developers/gitignore/api/vlab\n","vrealizeorchestrator":"# Created by https://www.toptal.com/developers/gitignore/api/vrealizeorchestrator\n# Edit at https://www.toptal.com/developers/gitignore?templates=vrealizeorchestrator\n\n### vRealizeOrchestrator ###\n# Default Resource Elements\nResources/Library/*\n\n# Library Workflows\nWorkflows/Library\n\n# Environment Specific variables\nConfigurations/*\n\n# Multi-Node Workflows\nWorkflows/VCO@*\n\n# All Packages\n**/Packages/\n\n# Test actions\nActions/test/*\n\n# All packages, resources, configurations, policies and Workflows\n**/Resources/\n**/Configurations/\n**/Workflows/\n**/Policy Templates/\n\n# End of https://www.toptal.com/developers/gitignore/api/vrealizeorchestrator\n","vs":"# Created by https://www.toptal.com/developers/gitignore/api/vs\n# Edit at https://www.toptal.com/developers/gitignore?templates=vs\n\n### vs ###\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*[.json, .xml, .info]\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# End of https://www.toptal.com/developers/gitignore/api/vs\n","vue":"# Created by https://www.toptal.com/developers/gitignore/api/vue\n# Edit at https://www.toptal.com/developers/gitignore?templates=vue\n\n### Vue ###\n# gitignore template for Vue.js projects\n#\n# Recommended template: Node.gitignore\n\n# TODO: where does this rule come from?\ndocs/_book\n\n# TODO: where does this rule come from?\ntest/\n\n# End of https://www.toptal.com/developers/gitignore/api/vue\n","vuejs":"# Created by https://www.toptal.com/developers/gitignore/api/vuejs\n# Edit at https://www.toptal.com/developers/gitignore?templates=vuejs\n\n### Vuejs ###\n# Recommended template: Node.gitignore\n\nnode_modules/\ndist/\nnpm-debug.log\nyarn-error.log\n\n# End of https://www.toptal.com/developers/gitignore/api/vuejs\n","vvvv":"# Created by https://www.toptal.com/developers/gitignore/api/vvvv\n# Edit at https://www.toptal.com/developers/gitignore?templates=vvvv\n\n### VVVV ###\n\n# .v4p backup files\n*~.xml\n\n# Dynamic plugins .dll\nbin/\n\n# End of https://www.toptal.com/developers/gitignore/api/vvvv\n","waf":"# Created by https://www.toptal.com/developers/gitignore/api/waf\n# Edit at https://www.toptal.com/developers/gitignore?templates=waf\n\n### Waf ###\n# For projects that use the Waf build system: https://waf.io/\n# Dot-hidden on Unix-like systems\n.waf-*-*/\n.waf3-*-*/\n# Hidden directory on Windows (no dot)\nwaf-*-*/\nwaf3-*-*/\n# Lockfile\n.lock-waf_*_build\n\n# End of https://www.toptal.com/developers/gitignore/api/waf\n","wakanda":"# Created by https://www.toptal.com/developers/gitignore/api/wakanda\n# Edit at https://www.toptal.com/developers/gitignore?templates=wakanda\n\n### Wakanda ###\n# Wakanda studio preferences\n*.waPreferences\n\n# Wakanda log\n*.waLog\n*/Logs\nstudio_log*\n\n# Wakanda code symbols\n*.waSym\n*.waSymData\n\n# Wakanda index\n*.waIndx\n*.Match\n\n# Wakanda data\n*.waData\n*(compacting)*\n\n# Wakanda User and Group\n*.cacheUAG\n*.waDirectory\n\n# Wakanda backup\n*.waBackup\n*.breakpoints\n\n# End of https://www.toptal.com/developers/gitignore/api/wakanda\n","web":"# Created by https://www.toptal.com/developers/gitignore/api/web\n# Edit at https://www.toptal.com/developers/gitignore?templates=web\n\n### Web ###\n*.asp\n*.cer\n*.csr\n*.css\n*.htm\n*.html\n*.js\n*.jsp\n*.php\n*.rss\n*.wasm\n*.wat\n*.xhtml\n\n# End of https://www.toptal.com/developers/gitignore/api/web\n","webmethods":"# Created by https://www.toptal.com/developers/gitignore/api/webmethods\n# Edit at https://www.toptal.com/developers/gitignore?templates=webmethods\n\n### WebMethods ###\n**/IntegrationServer/datastore/\n**/IntegrationServer/db/\n**/IntegrationServer/DocumentStore/\n**/IntegrationServer/lib/\n**/IntegrationServer/logs/\n**/IntegrationServer/replicate/\n**/IntegrationServer/sdk/\n**/IntegrationServer/support/\n**/IntegrationServer/update/\n**/IntegrationServer/userFtpRoot/\n**/IntegrationServer/web/\n**/IntegrationServer/WmRepository4/\n**/IntegrationServer/XAStore/\n**/IntegrationServer/packages/Wm*/\n\n# End of https://www.toptal.com/developers/gitignore/api/webmethods\n","webstorm":"# Created by https://www.toptal.com/developers/gitignore/api/webstorm\n# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm\n\n### WebStorm ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### WebStorm Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n# https://plugins.jetbrains.com/plugin/7973-sonarlint\n.idea/**/sonarlint/\n\n# SonarQube Plugin\n# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin\n.idea/**/sonarIssues.xml\n\n# Markdown Navigator plugin\n# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced\n.idea/**/markdown-navigator.xml\n.idea/**/markdown-navigator-enh.xml\n.idea/**/markdown-navigator/\n\n# Cache file creation bug\n# See https://youtrack.jetbrains.com/issue/JBR-2257\n.idea/$CACHE_FILE$\n\n# CodeStream plugin\n# https://plugins.jetbrains.com/plugin/12206-codestream\n.idea/codestream.xml\n\n# Azure Toolkit for IntelliJ plugin\n# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij\n.idea/**/azureSettings.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/webstorm\n","webstorm+all":"# Created by https://www.toptal.com/developers/gitignore/api/webstorm+all\n# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm+all\n\n### WebStorm+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### WebStorm+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n# End of https://www.toptal.com/developers/gitignore/api/webstorm+all\n","webstorm+iml":"# Created by https://www.toptal.com/developers/gitignore/api/webstorm+iml\n# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm+iml\n\n### WebStorm+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn. Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### WebStorm+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n# End of https://www.toptal.com/developers/gitignore/api/webstorm+iml\n","werckercli":"# Created by https://www.toptal.com/developers/gitignore/api/werckercli\n# Edit at https://www.toptal.com/developers/gitignore?templates=werckercli\n\n### WerckerCLI ###\n_steps\n_builds\n_cache\n_projects\n\n# End of https://www.toptal.com/developers/gitignore/api/werckercli\n","windows":"# Created by https://www.toptal.com/developers/gitignore/api/windows\n# Edit at https://www.toptal.com/developers/gitignore?templates=windows\n\n### Windows ###\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# End of https://www.toptal.com/developers/gitignore/api/windows\n","wintersmith":"# Created by https://www.toptal.com/developers/gitignore/api/wintersmith\n# Edit at https://www.toptal.com/developers/gitignore?templates=wintersmith\n\n### Wintersmith ###\ncontents/\nnode_modules/\nplugins/\ntemplates/\nconfig.json\npackage.json\n\n\n# End of https://www.toptal.com/developers/gitignore/api/wintersmith\n","wordpress":"# Created by https://www.toptal.com/developers/gitignore/api/wordpress\n# Edit at https://www.toptal.com/developers/gitignore?templates=wordpress\n\n### WordPress ###\n# Core\n#\n# Note: if you want to stage/commit WP core files\n# you can delete this whole section/until Configuration.\n/wp-admin/\n/wp-content/index.php\n/wp-content/languages\n/wp-content/plugins/index.php\n/wp-content/themes/index.php\n/wp-includes/\n/index.php\n/license.txt\n/readme.html\n/wp-*.php\n/xmlrpc.php\n\n# Configuration\nwp-config.php\n\n# Example themes\n/wp-content/themes/twenty*/\n\n# Example plugin\n/wp-content/plugins/hello.php\n\n# Uploads\n/wp-content/uploads/\n\n# Log files\n*.log\n\n# htaccess\n/.htaccess\n\n# All plugins\n# Note: If you wish to whitelist plugins,\n# uncomment the next line\n#/wp-content/plugins\n\n# All themes\n# Note: If you wish to whitelist themes,\n# uncomment the next line\n#/wp-content/themes\n\n\n# End of https://www.toptal.com/developers/gitignore/api/wordpress\n","wyam":"# Created by https://www.toptal.com/developers/gitignore/api/wyam\n# Edit at https://www.toptal.com/developers/gitignore?templates=wyam\n\n### Wyam ###\n# Wyam Ignore - Based on https://wyam.io/docs/advanced/what_to_exclude_from_source_control\noutput/*\nconfig.wyam.dll\nconfig.wyam.hash\n*packages.xml\n\n# End of https://www.toptal.com/developers/gitignore/api/wyam\n","xamarinstudio":"# Created by https://www.toptal.com/developers/gitignore/api/xamarinstudio\n# Edit at https://www.toptal.com/developers/gitignore?templates=xamarinstudio\n\n### XamarinStudio ###\nbin/\nobj/\n*.userprefs\n.packages\n\n# End of https://www.toptal.com/developers/gitignore/api/xamarinstudio\n","xcode":"# Created by https://www.toptal.com/developers/gitignore/api/xcode\n# Edit at https://www.toptal.com/developers/gitignore?templates=xcode\n\n### Xcode ###\n## User settings\nxcuserdata/\n\n## Xcode 8 and earlier\n*.xcscmblueprint\n*.xccheckout\n\n### Xcode Patch ###\n*.xcodeproj/*\n!*.xcodeproj/project.pbxproj\n!*.xcodeproj/xcshareddata/\n!*.xcodeproj/project.xcworkspace/\n!*.xcworkspace/contents.xcworkspacedata\n/*.gcno\n**/xcshareddata/WorkspaceSettings.xcsettings\n\n# End of https://www.toptal.com/developers/gitignore/api/xcode\n","xcodeinjection":"# Created by https://www.toptal.com/developers/gitignore/api/xcodeinjection\n# Edit at https://www.toptal.com/developers/gitignore?templates=xcodeinjection\n\n### XcodeInjection ###\n# Code Injection\n#\n# After new code Injection tools there's a generated folder /iOSInjectionProject\n# https://github.com/johnno1962/injectionforxcode\n\niOSInjectionProject/\n\n# End of https://www.toptal.com/developers/gitignore/api/xcodeinjection\n","xilinx":"# Created by https://www.toptal.com/developers/gitignore/api/xilinx\n# Edit at https://www.toptal.com/developers/gitignore?templates=xilinx\n\n### Xilinx ###\n# gitignore template for Xilinx Vivado Design Suite\n# website: https://www.xilinx.com/support/download.html\n\n# [home]\n*.jou\n*.log\n*.debug\n*.str\n*.zip\n*.tmp\n*.rst\n*.os\n*.js\n*.pb\n*.dcp\n*.hwdef\n*.vds\n*.veo\n*.wdf\n*.vdi\n*.dmp\n*.rpx\n*.rpt\n*_stub.v\n*_stub.vhdl\n*_funcsim.v\n*_funcsim.vhdl\n.project\n\n# [dir]\n*.cache\n.metadata\n*.data\n*.ipdefs\n.Xil\n*.sdk\n*.hw\n*.ip_user_files\n\n### IP synth\n*_synth_*\n\n.jobs\n\n### project synth\n*/*.runs/synth*/*.xml\n*/*.runs/synth*/*.txt\n*/*.runs/synth*/*.sh\n*/*.runs/synth*/*.tcl\n*/*.runs/synth*/*.bat\n*/*.runs/synth*/*.xdc\n!*/*.runs/synth*/*utilization*.rpt\n\n*.runs/synth*/*.xml\n*.runs/synth*/*.txt\n*.runs/synth*/*.sh\n*.runs/synth*/*.tcl\n*.runs/synth*/*.bat\n*.runs/synth*/*.xdc\n!*.runs/synth*/*utilization*.rpt\n\n### project impl\n*/*.runs/impl*/*.xml\n*/*.runs/impl*/*.html\n*/*.runs/impl*/*.txt\n*/*.runs/impl*/*.sh\n*/*.runs/impl*/*.tcl\n*/*.runs/impl*/*.bat\n!*/*.runs/impl*/*utilization*.rpt\n\n*.runs/impl*/*.xml\n*.runs/impl*/*.html\n*.runs/impl*/*.txt\n*.runs/impl*/*.sh\n*.runs/impl*/*.tcl\n*.runs/impl*/*.bat\n!*.runs/impl*/*utilization*.rpt\n\n### block design\n*/*/bd/*/hdl\n*/*/*/bd/*/hdl\n\n*/*/bd/*/*.xdc\n*/*/*/bd/*/*.xdc\n\n*/*/bd/*/ip/*/*.xdc\n*/*/*/bd/*/ip/*/*.xdc\n\n*/*/bd/*/ip/*/*/\n*/*/*/bd/*/ip/*/*/\n\n*/*/bd/*/ip/*/*.vhd\n*/*/*/bd/*/ip/*/*.vhd\n\n*/*/bd/*/ip/*/*.xml\n*/*/*/bd/*/ip/*/*.xml\n\n*.c\n*.h\n*.vho\n*.html\n*/*/bd/*/ip/*/*.tcl\n*/*/*/bd/*/ip/*/*.tcl\nhw_handoff\nipshared\n\n# End of https://www.toptal.com/developers/gitignore/api/xilinx\n","xilinxise":"# Created by https://www.toptal.com/developers/gitignore/api/xilinxise\n# Edit at https://www.toptal.com/developers/gitignore?templates=xilinxise\n\n### XilinxISE ###\n# intermediate build files\n*.bgn\n*.bit\n*.bld\n*.cmd_log\n*.drc\n*.ll\n*.lso\n*.msd\n*.msk\n*.ncd\n*.ngc\n*.ngd\n*.ngr\n*.pad\n*.par\n*.pcf\n*.prj\n*.ptwx\n*.rbb\n*.rbd\n*.stx\n*.syr\n*.twr\n*.twx\n*.unroutes\n*.ut\n*.xpi\n*.xst\n*_bitgen.xwbt\n*_envsettings.html\n*_map.map\n*_map.mrp\n*_map.ngm\n*_map.xrpt\n*_ngdbuild.xrpt\n*_pad.csv\n*_pad.txt\n*_par.xrpt\n*_summary.html\n*_summary.xml\n*_usage.xml\n*_xst.xrpt\n\n# iMPACT generated files\n_impactbatch.log\nimpact.xsl\nimpact_impact.xwbt\nise_impact.cmd\nwebtalk_impact.xml\n\n# Core Generator generated files\nxaw2verilog.log\n\n# project-wide generated files\n*.gise\npar_usage_statistics.html\nusage_statistics_webtalk.html\nwebtalk.log\nwebtalk_pn.xml\n\n# generated folders\niseconfig/\nxlnx_auto_0_xdb/\nxst/\n_ngo/\n_xmsgs/\n\n# End of https://www.toptal.com/developers/gitignore/api/xilinxise\n","xilinxvivado":"# Created by https://www.toptal.com/developers/gitignore/api/xilinxvivado\n# Edit at https://www.toptal.com/developers/gitignore?templates=xilinxvivado\n\n### XilinxVivado ###\n#########################################################################################################\n##\tThis is an example .gitignore file for Vivado, please treat it as an example as\n##\tit might not be complete. In addition, XAPP 1165 should be followed.\n#########\n#Exclude all\n*\n!*/\n!.gitignore\n###########################################################################\n##\tVIVADO\n#Source files:\n#Do NOT ignore VHDL, Verilog, block diagrams or EDIF files.\n!*.vhd\n!*.v\n!*.bd\n!*.edif\n#IP files\n#.xci: IP-core property file with core container disabled\n!*.xci\n#*.dcp (checkpoint files: better be ignored!)\n#!*.dcp\n!*.vds\n!*.pb\n#All bd comments and layout coordinates are stored within .ui\n!*.ui\n!*.ooc\n#System Generator\n!*.mdl\n!*.slx\n!*.bxml\n#Simulation logic analyzer\n!*.wcfg\n!*.coe\n#MIG\n!*.prj\n!*.mem\n#Project files\n#XPR + *.XML ? XPR (Files are merged into a single XPR file for 2014.1 version)\n#Do NOT ignore *.xpr files\n!*.xpr\n#Include *.xml files for 2013.4 or earlier version\n!*.xml\n#Constraint files\n#Do NOT ignore *.xdc files\n!*.xdc\n#TCL - files\n!*.tcl\n#Journal - files\n!*.jou\n#Reports\n!*.rpt\n!*.txt\n!*.vdi\n#C-files\n!*.c\n!*.h\n!*.elf\n!*.bmm\n!*.xmp\n\n# End of https://www.toptal.com/developers/gitignore/api/xilinxvivado\n","xill":"# Created by https://www.toptal.com/developers/gitignore/api/xill\n# Edit at https://www.toptal.com/developers/gitignore?templates=xill\n\n### Xill ###\n.project\n.xlib\ntestit-reports/\n.generated-tests/\ngenerated-tests/\nxill.properties\n\n# End of https://www.toptal.com/developers/gitignore/api/xill\n","xmake":"# Created by https://www.toptal.com/developers/gitignore/api/xmake\n# Edit at https://www.toptal.com/developers/gitignore?templates=xmake\n\n### xmake ###\n# Xmake cache\n.xmake/\nbuild/\n\n# End of https://www.toptal.com/developers/gitignore/api/xmake\n","xojo":"# Created by https://www.toptal.com/developers/gitignore/api/xojo\n# Edit at https://www.toptal.com/developers/gitignore?templates=xojo\n\n### Xojo ###\n# Xojo (formerly REALbasic and Real Studio)\n\nBuilds*\n*.debug\n*.debug.app\nDebug*.exe\nDebug*/Debug*.exe\nDebug*/Debug*\\ Libs\n*.rbuistate\n*.xojo_uistate\n*.obsolete*\n\n# End of https://www.toptal.com/developers/gitignore/api/xojo\n","xtext":"# Created by https://www.toptal.com/developers/gitignore/api/xtext\n# Edit at https://www.toptal.com/developers/gitignore?templates=xtext\n\n### XText ###\n# Those source folders can be generated by maven and Eclipse\ngenerated/\nsrc-gen/\nxtend-gen/\n\n# Ignore plugin.xml_gen and feature.xml_gen files, when build Eclipse Plugins.\n*.xml_gen\n\n# End of https://www.toptal.com/developers/gitignore/api/xtext\n","y86":"# Created by https://www.toptal.com/developers/gitignore/api/y86\n# Edit at https://www.toptal.com/developers/gitignore?templates=y86\n\n### Y86 ###\n# Object File\n*.yo\n\n# End of https://www.toptal.com/developers/gitignore/api/y86\n","yalc":"# Created by https://www.toptal.com/developers/gitignore/api/yalc\n# Edit at https://www.toptal.com/developers/gitignore?templates=yalc\n\n### yalc ###\n# yalc\n# https://www.npmjs.com/package/yalc/v/1.0.0-pre.17#keep-it-out-of-git\n\n.yalc\nyalc.lock\n\n# End of https://www.toptal.com/developers/gitignore/api/yalc\n","yarn":"# Created by https://www.toptal.com/developers/gitignore/api/yarn\n# Edit at https://www.toptal.com/developers/gitignore?templates=yarn\n\n### yarn ###\n# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored\n\n.yarn/*\n!.yarn/releases\n!.yarn/patches\n!.yarn/plugins\n!.yarn/sdks\n!.yarn/versions\n\n# if you are NOT using Zero-installs, then:\n# comment the following lines\n!.yarn/cache\n\n# and uncomment the following lines\n# .pnp.*\n\n# End of https://www.toptal.com/developers/gitignore/api/yarn\n","yeoman":"# Created by https://www.toptal.com/developers/gitignore/api/yeoman\n# Edit at https://www.toptal.com/developers/gitignore?templates=yeoman\n\n### Yeoman ###\nnode_modules/\nbower_components/\n*.log\n\nbuild/\ndist/\n\n# End of https://www.toptal.com/developers/gitignore/api/yeoman\n","yii":"# Created by https://www.toptal.com/developers/gitignore/api/yii\n# Edit at https://www.toptal.com/developers/gitignore?templates=yii\n\n### Yii ###\nassets/*\n!assets/.gitignore\nprotected/runtime/*\n!protected/runtime/.gitignore\nprotected/data/*.db\nthemes/classic/views/\n\n# End of https://www.toptal.com/developers/gitignore/api/yii\n","yii2":"# Created by https://www.toptal.com/developers/gitignore/api/yii2\n# Edit at https://www.toptal.com/developers/gitignore?templates=yii2\n\n### Yii2 ###\n# yii console commands\n/yii\n/yii_test\n/yii_test.bat\n\n# phpstorm project files\n.idea\n\n# netbeans project files\nnbproject\n\n# zend studio for eclipse project files\n.buildpath\n.project\n.settings\n\n# windows thumbnail cache\nThumbs.db\n\n# composer vendor dir\n/vendor\n\n# composer itself is not needed\ncomposer.phar\n\n# Mac DS_Store Files\n.DS_Store\n\n# phpunit itself is not needed\nphpunit.phar\n# local phpunit config\n/phpunit.xml\n\n# vagrant runtime\n/.vagrant\n\n# ignore generated files\n/frontend/web/index.php\n/frontend/web/index-test.php\n/frontend/web/robots.txt\n/backend/web/index.php\n/backend/web/index-test.php\n/backend/web/robots.txt\n\n# End of https://www.toptal.com/developers/gitignore/api/yii2\n","zendframework":"# Created by https://www.toptal.com/developers/gitignore/api/zendframework\n# Edit at https://www.toptal.com/developers/gitignore?templates=zendframework\n\n### ZendFramework ###\n# Composer files\ncomposer.phar\nvendor/\n\n# Local configs\nconfig/autoload/*.local.php\n\n# Binary gettext files\n*.mo\n\n# Data\ndata/logs/\ndata/cache/\ndata/sessions/\ndata/tmp/\ntemp/\n\n#Doctrine 2\ndata/DoctrineORMModule/Proxy/\ndata/DoctrineORMModule/cache/\n\n# Legacy ZF1\ndemos/\nextras/documentation\n\n# End of https://www.toptal.com/developers/gitignore/api/zendframework\n","zephir":"# Created by https://www.toptal.com/developers/gitignore/api/zephir\n# Edit at https://www.toptal.com/developers/gitignore?templates=zephir\n\n### Zephir ###\n# Cache files, generates by Zephir\n.temp/\n.libs/\n\n# Object files, generates by linker\n*.lo\n*.la\n*.o\n*.loT\n\n# Files generated by configure and Zephir,\n# not required for extension compilation.\next/build/\next/modules/\next/Makefile*\next/config*\next/acinclude.m4\next/aclocal.m4\next/autom4te*\next/install-sh\next/ltmain.sh\next/missing\next/mkinstalldirs\next/run-tests.php\next/.deps\next/libtool\n\n# End of https://www.toptal.com/developers/gitignore/api/zephir\n","zig":"# Created by https://www.toptal.com/developers/gitignore/api/zig\n# Edit at https://www.toptal.com/developers/gitignore?templates=zig\n\n### zig ###\n# Zig programming language\n\nzig-cache/\nzig-out/\nbuild/\nbuild-*/\ndocgen_tmp/\n\n# End of https://www.toptal.com/developers/gitignore/api/zig\n","zsh":"# Created by https://www.toptal.com/developers/gitignore/api/zsh\n# Edit at https://www.toptal.com/developers/gitignore?templates=zsh\n\n### Zsh ###\n# Zsh compiled script + zrecompile backup\n*.zwc\n*.zwc.old\n\n# Zsh completion-optimization dumpfile\n*zcompdump*\n\n# Zsh history\n.zsh_history\n\n# Zsh sessions\n.zsh_sessions\n\n# Zsh zcalc history\n.zcalc_history\n\n# A popular plugin manager's files\n._zinit\n.zinit_lstupd\n\n# zdharma/zshelldoc tool's files\nzsdoc/data\n\n# robbyrussell/oh-my-zsh/plugins/per-directory-history plugin's files\n# (when set-up to store the history in the local directory)\n.directory_history\n\n# MichaelAquilina/zsh-autoswitch-virtualenv plugin's files\n# (for Zsh plugins using Python)\n.venv\n\n# Zunit tests' output\n/tests/_output/*\n!/tests/_output/.gitkeep\n\n# End of https://www.toptal.com/developers/gitignore/api/zsh\n","zukencr8000":"# Created by https://www.toptal.com/developers/gitignore/api/zukencr8000\n# Edit at https://www.toptal.com/developers/gitignore?templates=zukencr8000\n\n### ZukenCR8000 ###\n## Zuken Global\n\n# log files\n*.log\n\n\n## Schematic\n\n# Schematic lock / edit in progress files\n*.s_t\n\n# Schematic Sheet backups\n*.shtb\n*.shtq\n\n# Schematic Design backups\n*undo.dsgn_\n\n# Schematic ERC/DRC results\n**/*template.cir/drc*\n\n# Schematic Frame\n**/*template.cir/frame*\n\n# Schematic log\n**/*template.cir/log*\n\n# Schematic exported netlist readme\n**/*template.cir/ext/readme.txt\n\n\n## Schematic <-> Layout communication\n\n# forward annotation files:\n*.coh\n*.loh\n*.coe\n\n\n## Layout\n\n# Layout Backups\n*.dsgn.bk\n\n# Layout Lockfiles\n*.dsgn.lk\n\n# Layout .crdb folder\n**/.crdb/\n\n# Layout .df_cache folder\n**/.df_cache/\n\n# Layout Automatic backup folders\n**/*dsgn.autobk/\n\n# Layout DRC result folder\n**/*.chk/\n\n# Layout Parameter file folders\n**/*.dsgn.prm/\n\n# Layout Signal Integrity result folders\n**/*.si/\n\n# Layout Power Integrity / EMI result folders\n**/*.emc/\n\n\n## DFM\n\n# DFM backups\n*.mdgn.bk\n\n# DFM lockfiles\n*.mdgn.lk\n\n# End of https://www.toptal.com/developers/gitignore/api/zukencr8000\n"}} diff --git a/apps/desktop-ui/public/globe.svg b/apps/desktop-ui/public/globe.svg new file mode 100644 index 00000000..567f17b0 --- /dev/null +++ b/apps/desktop-ui/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-ui/public/icon-192x192.png b/apps/desktop-ui/public/icon-192x192.png new file mode 100644 index 00000000..ca3a5232 Binary files /dev/null and b/apps/desktop-ui/public/icon-192x192.png differ diff --git a/apps/desktop-ui/public/icon-512x512.png b/apps/desktop-ui/public/icon-512x512.png new file mode 100644 index 00000000..ca3a5232 Binary files /dev/null and b/apps/desktop-ui/public/icon-512x512.png differ diff --git a/apps/desktop-ui/public/images/Encrypt -decrypt-text/Encrypt -decrypt-text-black.png b/apps/desktop-ui/public/images/Encrypt -decrypt-text/Encrypt -decrypt-text-black.png new file mode 100644 index 00000000..1420fecf Binary files /dev/null and b/apps/desktop-ui/public/images/Encrypt -decrypt-text/Encrypt -decrypt-text-black.png differ diff --git a/apps/desktop-ui/public/images/Encrypt -decrypt-text/Encrypt -decrypt-text-white.png b/apps/desktop-ui/public/images/Encrypt -decrypt-text/Encrypt -decrypt-text-white.png new file mode 100644 index 00000000..da34a2e5 Binary files /dev/null and b/apps/desktop-ui/public/images/Encrypt -decrypt-text/Encrypt -decrypt-text-white.png differ diff --git a/apps/desktop-ui/public/images/OTP-Code-Generator/OTP-Code-Generator-black.png b/apps/desktop-ui/public/images/OTP-Code-Generator/OTP-Code-Generator-black.png new file mode 100644 index 00000000..35636a14 Binary files /dev/null and b/apps/desktop-ui/public/images/OTP-Code-Generator/OTP-Code-Generator-black.png differ diff --git a/apps/desktop-ui/public/images/OTP-Code-Generator/OTP-Code-Generator-white.png b/apps/desktop-ui/public/images/OTP-Code-Generator/OTP-Code-Generator-white.png new file mode 100644 index 00000000..c7b4c753 Binary files /dev/null and b/apps/desktop-ui/public/images/OTP-Code-Generator/OTP-Code-Generator-white.png differ diff --git a/apps/desktop-ui/public/images/Token-generator/Token-generator-black.png b/apps/desktop-ui/public/images/Token-generator/Token-generator-black.png new file mode 100644 index 00000000..b9a2a803 Binary files /dev/null and b/apps/desktop-ui/public/images/Token-generator/Token-generator-black.png differ diff --git a/apps/desktop-ui/public/images/Token-generator/Token-generator-light.png b/apps/desktop-ui/public/images/Token-generator/Token-generator-light.png new file mode 100644 index 00000000..b05624e4 Binary files /dev/null and b/apps/desktop-ui/public/images/Token-generator/Token-generator-light.png differ diff --git a/apps/desktop-ui/public/images/UUIDs generator/UUIDs generator-black.png b/apps/desktop-ui/public/images/UUIDs generator/UUIDs generator-black.png new file mode 100644 index 00000000..318b44ac Binary files /dev/null and b/apps/desktop-ui/public/images/UUIDs generator/UUIDs generator-black.png differ diff --git a/apps/desktop-ui/public/images/UUIDs generator/UUIDs generator-light.png b/apps/desktop-ui/public/images/UUIDs generator/UUIDs generator-light.png new file mode 100644 index 00000000..9e6ed2b7 Binary files /dev/null and b/apps/desktop-ui/public/images/UUIDs generator/UUIDs generator-light.png differ diff --git a/apps/desktop-ui/public/images/bookmark/bookmark-screenshot-black.png b/apps/desktop-ui/public/images/bookmark/bookmark-screenshot-black.png new file mode 100644 index 00000000..a53e4c0d Binary files /dev/null and b/apps/desktop-ui/public/images/bookmark/bookmark-screenshot-black.png differ diff --git a/apps/desktop-ui/public/images/bookmark/bookmark-screenshot-light.png b/apps/desktop-ui/public/images/bookmark/bookmark-screenshot-light.png new file mode 100644 index 00000000..7367d2cc Binary files /dev/null and b/apps/desktop-ui/public/images/bookmark/bookmark-screenshot-light.png differ diff --git a/apps/desktop-ui/public/images/dashboard-dark.png b/apps/desktop-ui/public/images/dashboard-dark.png new file mode 100644 index 00000000..56ecadb4 Binary files /dev/null and b/apps/desktop-ui/public/images/dashboard-dark.png differ diff --git a/apps/desktop-ui/public/images/dashboard-light.png b/apps/desktop-ui/public/images/dashboard-light.png new file mode 100644 index 00000000..6e1c5544 Binary files /dev/null and b/apps/desktop-ui/public/images/dashboard-light.png differ diff --git a/apps/desktop-ui/public/logo-dark.png b/apps/desktop-ui/public/logo-dark.png new file mode 100644 index 00000000..ca3a5232 Binary files /dev/null and b/apps/desktop-ui/public/logo-dark.png differ diff --git a/apps/desktop-ui/public/logo-full-dark.png b/apps/desktop-ui/public/logo-full-dark.png new file mode 100644 index 00000000..f2632e08 Binary files /dev/null and b/apps/desktop-ui/public/logo-full-dark.png differ diff --git a/apps/desktop-ui/public/logo-full-light.png b/apps/desktop-ui/public/logo-full-light.png new file mode 100644 index 00000000..5d9d45d0 Binary files /dev/null and b/apps/desktop-ui/public/logo-full-light.png differ diff --git a/apps/desktop-ui/public/logo-light.png b/apps/desktop-ui/public/logo-light.png new file mode 100644 index 00000000..7cb8a480 Binary files /dev/null and b/apps/desktop-ui/public/logo-light.png differ diff --git a/apps/desktop-ui/public/logo-text-dark.png b/apps/desktop-ui/public/logo-text-dark.png new file mode 100644 index 00000000..a2217646 Binary files /dev/null and b/apps/desktop-ui/public/logo-text-dark.png differ diff --git a/apps/desktop-ui/public/logo-text-light.png b/apps/desktop-ui/public/logo-text-light.png new file mode 100644 index 00000000..5b9beb80 Binary files /dev/null and b/apps/desktop-ui/public/logo-text-light.png differ diff --git a/apps/desktop-ui/public/manifest.json b/apps/desktop-ui/public/manifest.json new file mode 100644 index 00000000..453c7334 --- /dev/null +++ b/apps/desktop-ui/public/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "MyDevTools", + "short_name": "MyDevTools", + "description": "A collection of developer tools for everyday use.", + "start_url": "/", + "display": "standalone", + "background_color": "#0a0b12", + "icons": [ + { + "src": "/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "orientation": "portrait-primary" +} \ No newline at end of file diff --git a/apps/desktop-ui/public/next.svg b/apps/desktop-ui/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/apps/desktop-ui/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-ui/public/oui.json b/apps/desktop-ui/public/oui.json new file mode 100644 index 00000000..a0d34702 --- /dev/null +++ b/apps/desktop-ui/public/oui.json @@ -0,0 +1,10 @@ +{ + "203706": "Cisco Systems, Inc", + "000A95": "Apple, Inc", + "D850E6": "Samsung Electronics Co., Ltd", + "F8E903": "Huawei Technologies Co., Ltd", + "F00DBA": "Intel Corporation", + "AC1F6B": "Xiaomi Communications Co., Ltd", + "B827EB": "Raspberry Pi Foundation" + } + \ No newline at end of file diff --git a/apps/desktop-ui/public/placeholder.svg b/apps/desktop-ui/public/placeholder.svg new file mode 100644 index 00000000..b4ad4422 --- /dev/null +++ b/apps/desktop-ui/public/placeholder.svg @@ -0,0 +1,4 @@ + + + ? + \ No newline at end of file diff --git a/apps/desktop-ui/public/vercel.svg b/apps/desktop-ui/public/vercel.svg new file mode 100644 index 00000000..77053960 --- /dev/null +++ b/apps/desktop-ui/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-ui/public/window.svg b/apps/desktop-ui/public/window.svg new file mode 100644 index 00000000..b2b2a44f --- /dev/null +++ b/apps/desktop-ui/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-ui/scripts/apply-dashboard-translations.mjs b/apps/desktop-ui/scripts/apply-dashboard-translations.mjs new file mode 100644 index 00000000..3f60f8ae --- /dev/null +++ b/apps/desktop-ui/scripts/apply-dashboard-translations.mjs @@ -0,0 +1,999 @@ +/** + * Applies localized strings for Dashboard.signIn, tabs, analytics, ui to each messages/*.json (except en). + * Run: node scripts/apply-dashboard-translations.mjs + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const messagesDir = path.join(__dirname, "..", "messages"); + +/** @type {Record; analytics: Record; ui: Record }>} */ +const T = { + es: { + signIn: "Iniciar sesión", + tabs: { apps: "Aplicaciones", analytics: "Análisis", ui: "Interfaz" }, + analytics: { + title: "Tus datos de un vistazo", + subtitle: "Recuentos sincronizados con tu cuenta en el servidor.", + loadError: "No se pudieron cargar las estadísticas.", + retry: "Reintentar", + loginHint: "Inicia sesión para ver el análisis de uso y las preferencias de interfaz.", + passwordEntries: "Entradas de contraseñas", + bookmarks: "Marcadores", + bookmarkFolders: "Carpetas de marcadores", + tasksTotal: "Tareas", + tasksCompleted: "Completadas", + tasksOngoing: "En curso", + tasksNotStarted: "Sin empezar", + projects: "Proyectos", + nosqlConnections: "Conexiones a BD", + notes: "Notas", + apiClientCollections: "Colecciones API", + apiClientEnvironments: "Entornos API", + apiClientHistory: "Entradas del historial API", + jsonFormatterDocuments: "Documentos del formateador JSON", + }, + ui: { + title: "Preferencias de interfaz", + subtitle: "Tema, idioma y herramientas visibles en la barra lateral — desde la configuración de tu cuenta.", + loadError: "No se pudieron cargar las preferencias.", + loginHint: "Inicia sesión para ver tus preferencias de interfaz guardadas.", + theme: "Tema", + accent: "Color de acento", + locale: "Idioma", + enabledTools: "Herramientas visibles", + favoriteTools: "Herramientas favoritas", + toolsWithUsage: "Herramientas con uso registrado", + }, + }, + fr: { + signIn: "Se connecter", + tabs: { apps: "Applications", analytics: "Analytique", ui: "Interface" }, + analytics: { + title: "Vos données en un coup d’œil", + subtitle: "Compteurs synchronisés avec votre compte sur le serveur.", + loadError: "Impossible de charger les statistiques.", + retry: "Réessayer", + loginHint: "Connectez-vous pour voir les analyses d’utilisation et les préférences d’interface.", + passwordEntries: "Entrées de mots de passe", + bookmarks: "Signets", + bookmarkFolders: "Dossiers de signets", + tasksTotal: "Tâches", + tasksCompleted: "Terminées", + tasksOngoing: "En cours", + tasksNotStarted: "Non démarrées", + projects: "Projets", + nosqlConnections: "Connexions BD", + notes: "Notes", + apiClientCollections: "Collections API", + apiClientEnvironments: "Environnements API", + apiClientHistory: "Entrées d’historique API", + jsonFormatterDocuments: "Documents du formateur JSON", + }, + ui: { + title: "Préférences d’interface", + subtitle: "Thème, langue et outils visibles dans la barre latérale — depuis les paramètres du compte.", + loadError: "Impossible de charger les préférences.", + loginHint: "Connectez-vous pour voir vos préférences d’interface enregistrées.", + theme: "Thème", + accent: "Couleur d’accent", + locale: "Langue", + enabledTools: "Outils visibles", + favoriteTools: "Outils favoris", + toolsWithUsage: "Outils avec suivi d’utilisation", + }, + }, + de: { + signIn: "Anmelden", + tabs: { apps: "Apps", analytics: "Analysen", ui: "Oberfläche" }, + analytics: { + title: "Ihre Daten auf einen Blick", + subtitle: "Zählerstand mit Ihrem Konto auf dem Server synchronisiert.", + loadError: "Analysen konnten nicht geladen werden.", + retry: "Erneut versuchen", + loginHint: "Melden Sie sich an, um Nutzungsanalysen und Oberflächeneinstellungen zu sehen.", + passwordEntries: "Passwort-Einträge", + bookmarks: "Lesezeichen", + bookmarkFolders: "Lesezeichen-Ordner", + tasksTotal: "Aufgaben", + tasksCompleted: "Erledigt", + tasksOngoing: "In Bearbeitung", + tasksNotStarted: "Nicht gestartet", + projects: "Projekte", + nosqlConnections: "DB-Verbindungen", + notes: "Notizen", + apiClientCollections: "API-Sammlungen", + apiClientEnvironments: "API-Umgebungen", + apiClientHistory: "API-Verlaufseinträge", + jsonFormatterDocuments: "JSON-Formatierer-Dokumente", + }, + ui: { + title: "Oberflächeneinstellungen", + subtitle: "Design, Sprache und sichtbare Tools in der Seitenleiste — aus Ihren Kontoeinstellungen.", + loadError: "Einstellungen konnten nicht geladen werden.", + loginHint: "Melden Sie sich an, um Ihre gespeicherten Oberflächeneinstellungen zu sehen.", + theme: "Design", + accent: "Akzentfarbe", + locale: "Sprache", + enabledTools: "Sichtbare Tools", + favoriteTools: "Favoriten-Tools", + toolsWithUsage: "Tools mit Nutzungsstatistik", + }, + }, + it: { + signIn: "Accedi", + tabs: { apps: "App", analytics: "Analisi", ui: "Interfaccia" }, + analytics: { + title: "I tuoi dati a colpo d’occhio", + subtitle: "Conteggi sincronizzati con il tuo account sul server.", + loadError: "Impossibile caricare le statistiche.", + retry: "Riprova", + loginHint: "Accedi per vedere analisi d’uso e preferenze di interfaccia.", + passwordEntries: "Voci password", + bookmarks: "Segnalibri", + bookmarkFolders: "Cartelle segnalibri", + tasksTotal: "Attività", + tasksCompleted: "Completate", + tasksOngoing: "In corso", + tasksNotStarted: "Non iniziate", + projects: "Progetti", + nosqlConnections: "Connessioni DB", + notes: "Note", + apiClientCollections: "Raccolte API", + apiClientEnvironments: "Ambienti API", + apiClientHistory: "Voci cronologia API", + jsonFormatterDocuments: "Documenti formattatore JSON", + }, + ui: { + title: "Preferenze interfaccia", + subtitle: "Tema, lingua e strumenti visibili nella barra laterale — dalle impostazioni account.", + loadError: "Impossibile caricare le preferenze.", + loginHint: "Accedi per vedere le preferenze di interfaccia salvate.", + theme: "Tema", + accent: "Colore accento", + locale: "Lingua", + enabledTools: "Strumenti visibili", + favoriteTools: "Strumenti preferiti", + toolsWithUsage: "Strumenti con utilizzo tracciato", + }, + }, + pt: { + signIn: "Iniciar sessão", + tabs: { apps: "Aplicações", analytics: "Análise", ui: "Interface" }, + analytics: { + title: "Os seus dados num relance", + subtitle: "Contagens sincronizadas com a sua conta no servidor.", + loadError: "Não foi possível carregar as estatísticas.", + retry: "Tentar novamente", + loginHint: "Inicie sessão para ver análises de utilização e preferências de interface.", + passwordEntries: "Entradas de palavras-passe", + bookmarks: "Marcadores", + bookmarkFolders: "Pastas de marcadores", + tasksTotal: "Tarefas", + tasksCompleted: "Concluídas", + tasksOngoing: "Em curso", + tasksNotStarted: "Não iniciadas", + projects: "Projetos", + nosqlConnections: "Ligações à BD", + notes: "Notas", + apiClientCollections: "Coleções API", + apiClientEnvironments: "Ambientes API", + apiClientHistory: "Entradas do histórico API", + jsonFormatterDocuments: "Documentos do formatador JSON", + }, + ui: { + title: "Preferências de interface", + subtitle: "Tema, idioma e ferramentas visíveis na barra lateral — nas definições da conta.", + loadError: "Não foi possível carregar as preferências.", + loginHint: "Inicie sessão para ver as preferências de interface guardadas.", + theme: "Tema", + accent: "Cor de destaque", + locale: "Idioma", + enabledTools: "Ferramentas visíveis", + favoriteTools: "Ferramentas favoritas", + toolsWithUsage: "Ferramentas com utilização registada", + }, + }, + "pt-BR": { + signIn: "Entrar", + tabs: { apps: "Aplicativos", analytics: "Análises", ui: "Interface" }, + analytics: { + title: "Seus dados em resumo", + subtitle: "Contagens sincronizadas com sua conta no servidor.", + loadError: "Não foi possível carregar as análises.", + retry: "Tentar de novo", + loginHint: "Entre para ver análises de uso e preferências de interface.", + passwordEntries: "Entradas de senhas", + bookmarks: "Favoritos", + bookmarkFolders: "Pastas de favoritos", + tasksTotal: "Tarefas", + tasksCompleted: "Concluídas", + tasksOngoing: "Em andamento", + tasksNotStarted: "Não iniciadas", + projects: "Projetos", + nosqlConnections: "Conexões de BD", + notes: "Notas", + apiClientCollections: "Coleções da API", + apiClientEnvironments: "Ambientes da API", + apiClientHistory: "Entradas do histórico da API", + jsonFormatterDocuments: "Documentos do formatador JSON", + }, + ui: { + title: "Preferências de interface", + subtitle: "Tema, idioma e ferramentas visíveis na barra lateral — nas configurações da conta.", + loadError: "Não foi possível carregar as preferências.", + loginHint: "Entre para ver suas preferências de interface salvas.", + theme: "Tema", + accent: "Cor de destaque", + locale: "Idioma", + enabledTools: "Ferramentas visíveis", + favoriteTools: "Ferramentas favoritas", + toolsWithUsage: "Ferramentas com uso rastreado", + }, + }, + nl: { + signIn: "Inloggen", + tabs: { apps: "Apps", analytics: "Analyse", ui: "Interface" }, + analytics: { + title: "Jouw data in één oogopslag", + subtitle: "Tellingen gesynchroniseerd met je account op de server.", + loadError: "Kan statistieken niet laden.", + retry: "Opnieuw", + loginHint: "Log in om gebruiksanalyses en interfacevoorkeuren te zien.", + passwordEntries: "Wachtwoorditems", + bookmarks: "Bladwijzers", + bookmarkFolders: "Bladwijzermappen", + tasksTotal: "Taken", + tasksCompleted: "Voltooid", + tasksOngoing: "Bezig", + tasksNotStarted: "Niet gestart", + projects: "Projecten", + nosqlConnections: "DB-verbindingen", + notes: "Notities", + apiClientCollections: "API-collecties", + apiClientEnvironments: "API-omgevingen", + apiClientHistory: "API-geschiedenisitems", + jsonFormatterDocuments: "JSON-formatteerdocumenten", + }, + ui: { + title: "Interfacevoorkeuren", + subtitle: "Thema, taal en zichtbare tools in de zijbalk — vanuit je accountinstellingen.", + loadError: "Kan voorkeuren niet laden.", + loginHint: "Log in om je opgeslagen interfacevoorkeuren te zien.", + theme: "Thema", + accent: "Accentkleur", + locale: "Taal", + enabledTools: "Zichtbare tools", + favoriteTools: "Favoriete tools", + toolsWithUsage: "Tools met gebruiksstatistieken", + }, + }, + pl: { + signIn: "Zaloguj się", + tabs: { apps: "Aplikacje", analytics: "Analityka", ui: "Interfejs" }, + analytics: { + title: "Twoje dane w skrócie", + subtitle: "Liczby zsynchronizowane z kontem na serwerze.", + loadError: "Nie udało się wczytać statystyk.", + retry: "Ponów", + loginHint: "Zaloguj się, aby zobaczyć analitykę i preferencje interfejsu.", + passwordEntries: "Wpisy haseł", + bookmarks: "Zakładki", + bookmarkFolders: "Foldery zakładek", + tasksTotal: "Zadania", + tasksCompleted: "Ukończone", + tasksOngoing: "W toku", + tasksNotStarted: "Nierozpoczęte", + projects: "Projekty", + nosqlConnections: "Połączenia z BD", + notes: "Notatki", + apiClientCollections: "Kolekcje API", + apiClientEnvironments: "Środowiska API", + apiClientHistory: "Wpisy historii API", + jsonFormatterDocuments: "Dokumenty formatu JSON", + }, + ui: { + title: "Preferencje interfejsu", + subtitle: "Motyw, język i widoczne narzędzia na pasku bocznym — z ustawień konta.", + loadError: "Nie udało się wczytać preferencji.", + loginHint: "Zaloguj się, aby zobaczyć zapisane preferencje interfejsu.", + theme: "Motyw", + accent: "Kolor akcentu", + locale: "Język", + enabledTools: "Widoczne narzędzia", + favoriteTools: "Ulubione narzędzia", + toolsWithUsage: "Narzędzia ze statystyką użycia", + }, + }, + ru: { + signIn: "Войти", + tabs: { apps: "Приложения", analytics: "Аналитика", ui: "Интерфейс" }, + analytics: { + title: "Ваши данные кратко", + subtitle: "Счётчики синхронизированы с вашей учётной записью на сервере.", + loadError: "Не удалось загрузить аналитику.", + retry: "Повторить", + loginHint: "Войдите, чтобы видеть аналитику использования и настройки интерфейса.", + passwordEntries: "Записей паролей", + bookmarks: "Закладки", + bookmarkFolders: "Папки закладок", + tasksTotal: "Задачи", + tasksCompleted: "Выполнено", + tasksOngoing: "В работе", + tasksNotStarted: "Не начато", + projects: "Проекты", + nosqlConnections: "Подключения к БД", + notes: "Заметки", + apiClientCollections: "Коллекции API", + apiClientEnvironments: "Окружения API", + apiClientHistory: "Записи истории API", + jsonFormatterDocuments: "Документы JSON-форматера", + }, + ui: { + title: "Настройки интерфейса", + subtitle: "Тема, язык и видимые инструменты на боковой панели — из настроек аккаунта.", + loadError: "Не удалось загрузить настройки.", + loginHint: "Войдите, чтобы видеть сохранённые настройки интерфейса.", + theme: "Тема", + accent: "Цвет акцента", + locale: "Язык", + enabledTools: "Видимые инструменты", + favoriteTools: "Избранные инструменты", + toolsWithUsage: "Инструменты со статистикой", + }, + }, + uk: { + signIn: "Увійти", + tabs: { apps: "Застосунки", analytics: "Аналітика", ui: "Інтерфейс" }, + analytics: { + title: "Ваші дані коротко", + subtitle: "Лічильники синхронізовано з обліковим записом на сервері.", + loadError: "Не вдалося завантажити аналітику.", + retry: "Повторити", + loginHint: "Увійдіть, щоб бачити аналітику та налаштування інтерфейсу.", + passwordEntries: "Записів паролів", + bookmarks: "Закладки", + bookmarkFolders: "Папки закладок", + tasksTotal: "Завдання", + tasksCompleted: "Виконано", + tasksOngoing: "У процесі", + tasksNotStarted: "Не розпочато", + projects: "Проєкти", + nosqlConnections: "Підключення до БД", + notes: "Нотатки", + apiClientCollections: "Колекції API", + apiClientEnvironments: "Оточення API", + apiClientHistory: "Записи історії API", + jsonFormatterDocuments: "Документи JSON-форматера", + }, + ui: { + title: "Налаштування інтерфейсу", + subtitle: "Тема, мова та видимі інструменти на бічній панелі — з налаштувань облікового запису.", + loadError: "Не вдалося завантажити налаштування.", + loginHint: "Увійдіть, щоб бачити збережені налаштування інтерфейсу.", + theme: "Тема", + accent: "Колір акценту", + locale: "Мова", + enabledTools: "Видимі інструменти", + favoriteTools: "Обрані інструменти", + toolsWithUsage: "Інструменти зі статистикою", + }, + }, + tr: { + signIn: "Giriş yap", + tabs: { apps: "Uygulamalar", analytics: "Analitik", ui: "Arayüz" }, + analytics: { + title: "Verilerinize genel bakış", + subtitle: "Sunucudaki hesabınızla senkronize sayılar.", + loadError: "Analitik yüklenemedi.", + retry: "Yeniden dene", + loginHint: "Kullanım analitiği ve arayüz tercihleri için giriş yapın.", + passwordEntries: "Parola kayıtları", + bookmarks: "Yer imleri", + bookmarkFolders: "Yer imi klasörleri", + tasksTotal: "Görevler", + tasksCompleted: "Tamamlanan", + tasksOngoing: "Devam eden", + tasksNotStarted: "Başlanmadı", + projects: "Projeler", + nosqlConnections: "Veritabanı bağlantıları", + notes: "Notlar", + apiClientCollections: "API koleksiyonları", + apiClientEnvironments: "API ortamları", + apiClientHistory: "API geçmişi kayıtları", + jsonFormatterDocuments: "JSON biçimleyici belgeleri", + }, + ui: { + title: "Arayüz tercihleri", + subtitle: "Tema, dil ve kenar çubuğundaki araçlar — hesap ayarlarınızdan.", + loadError: "Tercihler yüklenemedi.", + loginHint: "Kayıtlı arayüz tercihlerinizi görmek için giriş yapın.", + theme: "Tema", + accent: "Vurgu rengi", + locale: "Dil", + enabledTools: "Görünür araçlar", + favoriteTools: "Favori araçlar", + toolsWithUsage: "Kullanımı izlenen araçlar", + }, + }, + sv: { + signIn: "Logga in", + tabs: { apps: "Appar", analytics: "Analys", ui: "Gränssnitt" }, + analytics: { + title: "Dina data i korthet", + subtitle: "Antal synkroniserade med ditt konto på servern.", + loadError: "Kunde inte ladda analys.", + retry: "Försök igen", + loginHint: "Logga in för att se användningsanalys och gränssnittsinställningar.", + passwordEntries: "Lösenordsposter", + bookmarks: "Bokmärken", + bookmarkFolders: "Bokmärkesmappar", + tasksTotal: "Uppgifter", + tasksCompleted: "Klara", + tasksOngoing: "Pågående", + tasksNotStarted: "Ej påbörjade", + projects: "Projekt", + nosqlConnections: "DB-anslutningar", + notes: "Anteckningar", + apiClientCollections: "API-samlingar", + apiClientEnvironments: "API-miljöer", + apiClientHistory: "API-historikposter", + jsonFormatterDocuments: "JSON-formateringsdokument", + }, + ui: { + title: "Gränssnittsinställningar", + subtitle: "Tema, språk och synliga verktyg i sidofältet — från dina kontoinställningar.", + loadError: "Kunde inte ladda inställningar.", + loginHint: "Logga in för att se dina sparade gränssnittsinställningar.", + theme: "Tema", + accent: "Accentfärg", + locale: "Språk", + enabledTools: "Synliga verktyg", + favoriteTools: "Favoritverktyg", + toolsWithUsage: "Verktyg med användningsstatistik", + }, + }, + nb: { + signIn: "Logg inn", + tabs: { apps: "Apper", analytics: "Analyse", ui: "Grensesnitt" }, + analytics: { + title: "Dataene dine i et øyeblikk", + subtitle: "Tall synkronisert med kontoen din på serveren.", + loadError: "Kunne ikke laste analyse.", + retry: "Prøv igjen", + loginHint: "Logg inn for å se bruksanalyse og grensesnittpreferanser.", + passwordEntries: "Passordoppføringer", + bookmarks: "Bokmerker", + bookmarkFolders: "Bokmerkemapper", + tasksTotal: "Oppgaver", + tasksCompleted: "Fullført", + tasksOngoing: "Pågående", + tasksNotStarted: "Ikke startet", + projects: "Prosjekter", + nosqlConnections: "DB-tilkoblinger", + notes: "Notater", + apiClientCollections: "API-samlinger", + apiClientEnvironments: "API-miljøer", + apiClientHistory: "API-historikkoppføringer", + jsonFormatterDocuments: "JSON-formateringsdokumenter", + }, + ui: { + title: "Grensesnittpreferanser", + subtitle: "Tema, språk og synlige verktøy i sidefeltet — fra kontoinnstillingene.", + loadError: "Kunne ikke laste preferanser.", + loginHint: "Logg inn for å se lagrede grensesnittpreferanser.", + theme: "Tema", + accent: "Aksentfarge", + locale: "Språk", + enabledTools: "Synlige verktøy", + favoriteTools: "Favorittverktøy", + toolsWithUsage: "Verktøy med bruksstatistikk", + }, + }, + da: { + signIn: "Log ind", + tabs: { apps: "Apps", analytics: "Analyse", ui: "Brugerflade" }, + analytics: { + title: "Dine data på et øjeblik", + subtitle: "Antal synkroniseret med din konto på serveren.", + loadError: "Kunne ikke indlæse analyse.", + retry: "Prøv igen", + loginHint: "Log ind for at se brugsanalyse og grænsefladeindstillinger.", + passwordEntries: "Adgangskodeposter", + bookmarks: "Bogmærker", + bookmarkFolders: "Bogmærkemapper", + tasksTotal: "Opgaver", + tasksCompleted: "Fuldført", + tasksOngoing: "I gang", + tasksNotStarted: "Ikke startet", + projects: "Projekter", + nosqlConnections: "DB-forbindelser", + notes: "Noter", + apiClientCollections: "API-samlinger", + apiClientEnvironments: "API-miljøer", + apiClientHistory: "API-historikposter", + jsonFormatterDocuments: "JSON-formatdokumenter", + }, + ui: { + title: "Brugerfladeindstillinger", + subtitle: "Tema, sprog og synlige værktøjer i sidebjælken — fra dine kontoindstillinger.", + loadError: "Kunne ikke indlæse indstillinger.", + loginHint: "Log ind for at se dine gemte grænsefladeindstillinger.", + theme: "Tema", + accent: "Accentfarve", + locale: "Sprog", + enabledTools: "Synlige værktøjer", + favoriteTools: "Foretrukne værktøjer", + toolsWithUsage: "Værktøjer med brugsstatistik", + }, + }, + cs: { + signIn: "Přihlásit se", + tabs: { apps: "Aplikace", analytics: "Analytika", ui: "Rozhraní" }, + analytics: { + title: "Vaše data na první pohled", + subtitle: "Počty synchronizované s vaším účtem na serveru.", + loadError: "Nelze načíst analytiku.", + retry: "Zkusit znovu", + loginHint: "Přihlaste se pro zobrazení analytiky a předvoleb rozhraní.", + passwordEntries: "Záznamů hesel", + bookmarks: "Záložky", + bookmarkFolders: "Složky záložek", + tasksTotal: "Úkoly", + tasksCompleted: "Dokončené", + tasksOngoing: "Probíhající", + tasksNotStarted: "Nespuštěné", + projects: "Projekty", + nosqlConnections: "Připojení k DB", + notes: "Poznámky", + apiClientCollections: "Kolekce API", + apiClientEnvironments: "Prostředí API", + apiClientHistory: "Záznamy historie API", + jsonFormatterDocuments: "Dokumenty JSON formátovače", + }, + ui: { + title: "Předvolby rozhraní", + subtitle: "Motiv, jazyk a viditelné nástroje v postranním panelu — z nastavení účtu.", + loadError: "Nelze načíst předvolby.", + loginHint: "Přihlaste se pro zobrazení uložených předvoleb rozhraní.", + theme: "Motiv", + accent: "Barva zvýraznění", + locale: "Jazyk", + enabledTools: "Viditelné nástroje", + favoriteTools: "Oblíbené nástroje", + toolsWithUsage: "Nástroje se sledováním použití", + }, + }, + el: { + signIn: "Σύνδεση", + tabs: { apps: "Εφαρμογές", analytics: "Αναλυτικά", ui: "Διεπαφή" }, + analytics: { + title: "Τα δεδομένα σας με μια ματιά", + subtitle: "Μετρήσεις συγχρονισμένες με τον λογαριασμό σας στον διακομιστή.", + loadError: "Δεν ήταν δυνατή η φόρτωση αναλυτικών.", + retry: "Επανάληψη", + loginHint: "Συνδεθείτε για να δείτε αναλυτικά χρήσης και προτιμήσεις διεπαφής.", + passwordEntries: "Καταχωρήσεις κωδικών", + bookmarks: "Σελιδοδείκτες", + bookmarkFolders: "Φάκελοι σελιδοδεικτών", + tasksTotal: "Εργασίες", + tasksCompleted: "Ολοκληρωμένες", + tasksOngoing: "Σε εξέλιξη", + tasksNotStarted: "Δεν ξεκίνησαν", + projects: "Έργα", + nosqlConnections: "Συνδέσεις ΒΔ", + notes: "Σημειώσεις", + apiClientCollections: "Συλλογές API", + apiClientEnvironments: "Περιβάλλοντα API", + apiClientHistory: "Καταχωρήσεις ιστορικού API", + jsonFormatterDocuments: "Έγγραφα μορφοποιητή JSON", + }, + ui: { + title: "Προτιμήσεις διεπαφής", + subtitle: "Θέμα, γλώσσα και ορατά εργαλεία στην πλευρική γραμμή — από τις ρυθμίσεις λογαριασμού.", + loadError: "Δεν ήταν δυνατή η φόρτωση προτιμήσεων.", + loginHint: "Συνδεθείτε για να δείτε τις αποθηκευμένες προτιμήσεις διεπαφής.", + theme: "Θέμα", + accent: "Χρώμα έμφασης", + locale: "Γλώσσα", + enabledTools: "Ορατά εργαλεία", + favoriteTools: "Αγαπημένα εργαλεία", + toolsWithUsage: "Εργαλεία με παρακολούθηση χρήσης", + }, + }, + ar: { + signIn: "تسجيل الدخول", + tabs: { apps: "التطبيقات", analytics: "التحليلات", ui: "الواجهة" }, + analytics: { + title: "بياناتك في لمحة", + subtitle: "أعداد متزامنة مع حسابك على الخادم.", + loadError: "تعذّر تحميل التحليلات.", + retry: "إعادة المحاولة", + loginHint: "سجّل الدخول لعرض تحليلات الاستخدام وتفضيلات الواجهة.", + passwordEntries: "إدخالات كلمات المرور", + bookmarks: "الإشارات المرجعية", + bookmarkFolders: "مجلدات الإشارات", + tasksTotal: "المهام", + tasksCompleted: "مكتملة", + tasksOngoing: "قيد التنفيذ", + tasksNotStarted: "لم تبدأ", + projects: "المشاريع", + nosqlConnections: "اتصالات قاعدة البيانات", + notes: "الملاحظات", + apiClientCollections: "مجموعات API", + apiClientEnvironments: "بيئات API", + apiClientHistory: "سجلات سجل API", + jsonFormatterDocuments: "مستندات منسّق JSON", + }, + ui: { + title: "تفضيلات الواجهة", + subtitle: "المظهر واللغة والأدوات الظاهرة في الشريط الجانبي — من إعدادات الحساب.", + loadError: "تعذّر تحميل التفضيلات.", + loginHint: "سجّل الدخول لعرض تفضيلات الواجهة المحفوظة.", + theme: "المظهر", + accent: "لون التمييز", + locale: "اللغة", + enabledTools: "الأدوات الظاهرة", + favoriteTools: "الأدوات المفضلة", + toolsWithUsage: "أدوات مع تتبع الاستخدام", + }, + }, + fa: { + signIn: "ورود", + tabs: { apps: "برنامه‌ها", analytics: "تحلیل", ui: "رابط کاربری" }, + analytics: { + title: "داده‌های شما در یک نگاه", + subtitle: "شمارش‌ها همگام‌شده با حساب شما روی سرور.", + loadError: "بارگذاری تحلیل ممکن نشد.", + retry: "تلاش دوباره", + loginHint: "برای دیدن تحلیل استفاده و ترجیحات رابط وارد شوید.", + passwordEntries: "ورودی‌های گذرواژه", + bookmarks: "نشانک‌ها", + bookmarkFolders: "پوشه‌های نشانک", + tasksTotal: "وظایف", + tasksCompleted: "تکمیل‌شده", + tasksOngoing: "در حال انجام", + tasksNotStarted: "شروع‌نشده", + projects: "پروژه‌ها", + nosqlConnections: "اتصالات پایگاه داده", + notes: "یادداشت‌ها", + apiClientCollections: "مجموعه‌های API", + apiClientEnvironments: "محیط‌های API", + apiClientHistory: "ورودی‌های تاریخچه API", + jsonFormatterDocuments: "اسناد قالب‌ده JSON", + }, + ui: { + title: "ترجیحات رابط", + subtitle: "پوسته، زبان و ابزارهای نوار کناری — از تنظیمات حساب.", + loadError: "بارگذاری ترجیحات ممکن نشد.", + loginHint: "برای دیدن ترجیحات ذخیره‌شده وارد شوید.", + theme: "پوسته", + accent: "رنگ تأکید", + locale: "زبان", + enabledTools: "ابزارهای قابل مشاهده", + favoriteTools: "ابزارهای مورد علاقه", + toolsWithUsage: "ابزارها با آمار استفاده", + }, + }, + af: { + signIn: "Meld aan", + tabs: { apps: "Apps", analytics: "Ontleding", ui: "Koppelvlak" }, + analytics: { + title: "Jou data in ’n oogopslag", + subtitle: "Tellinge gesinkroniseer met jou rekening op die bediener.", + loadError: "Kon nie ontleding laai nie.", + retry: "Probeer weer", + loginHint: "Meld aan om gebruik-ontleding en koppelvlakvoorkeure te sien.", + passwordEntries: "Wagwoord-inskrywings", + bookmarks: "Boekmerke", + bookmarkFolders: "Boekmerkvouers", + tasksTotal: "Take", + tasksCompleted: "Voltooi", + tasksOngoing: "Aan die gang", + tasksNotStarted: "Nie begin nie", + projects: "Projekte", + nosqlConnections: "DB-verbindings", + notes: "Notas", + apiClientCollections: "API-versamelings", + apiClientEnvironments: "API-omgewings", + apiClientHistory: "API-geskiedenisinskrywings", + jsonFormatterDocuments: "JSON-formateerderdokumente", + }, + ui: { + title: "Koppelvlakvoorkeure", + subtitle: "Tema, taal en sigbare gereedskap in die sybalk — vanaf jou rekeninginstellings.", + loadError: "Kon nie voorkeure laai nie.", + loginHint: "Meld aan om gestoorde koppelvlakvoorkeure te sien.", + theme: "Tema", + accent: "Aksentkleur", + locale: "Taal", + enabledTools: "Sigbare gereedskap", + favoriteTools: "Gunsteling-gereedskap", + toolsWithUsage: "Gereedskap met gebruikstatistiek", + }, + }, + id: { + signIn: "Masuk", + tabs: { apps: "Aplikasi", analytics: "Analitik", ui: "Antarmuka" }, + analytics: { + title: "Data Anda sekilas", + subtitle: "Jumlah disinkronkan dengan akun Anda di server.", + loadError: "Tidak dapat memuat analitik.", + retry: "Coba lagi", + loginHint: "Masuk untuk melihat analitik penggunaan dan preferensi antarmuka.", + passwordEntries: "Entri kata sandi", + bookmarks: "Penanda", + bookmarkFolders: "Folder penanda", + tasksTotal: "Tugas", + tasksCompleted: "Selesai", + tasksOngoing: "Berjalan", + tasksNotStarted: "Belum dimulai", + projects: "Proyek", + nosqlConnections: "Koneksi basis data", + notes: "Catatan", + apiClientCollections: "Koleksi API", + apiClientEnvironments: "Lingkungan API", + apiClientHistory: "Entri riwayat API", + jsonFormatterDocuments: "Dokumen pemformat JSON", + }, + ui: { + title: "Preferensi antarmuka", + subtitle: "Tema, bahasa, dan alat yang terlihat di bilah sisi — dari pengaturan akun.", + loadError: "Tidak dapat memuat preferensi.", + loginHint: "Masuk untuk melihat preferensi antarmuka yang disimpan.", + theme: "Tema", + accent: "Warna aksen", + locale: "Bahasa", + enabledTools: "Alat terlihat", + favoriteTools: "Alat favorit", + toolsWithUsage: "Alat dengan pelacakan penggunaan", + }, + }, + ms: { + signIn: "Log masuk", + tabs: { apps: "Aplikasi", analytics: "Analitik", ui: "Antara muka" }, + analytics: { + title: "Data anda sekilas", + subtitle: "Kiraan disegerakkan dengan akaun anda pada pelayan.", + loadError: "Tidak dapat memuatkan analitik.", + retry: "Cuba lagi", + loginHint: "Log masuk untuk melihat analitik penggunaan dan pilihan antara muka.", + passwordEntries: "Entri kata laluan", + bookmarks: "Penanda halaman", + bookmarkFolders: "Folder penanda", + tasksTotal: "Tugas", + tasksCompleted: "Selesai", + tasksOngoing: "Sedang berjalan", + tasksNotStarted: "Belum dimulakan", + projects: "Projek", + nosqlConnections: "Sambungan pangkalan data", + notes: "Nota", + apiClientCollections: "Koleksi API", + apiClientEnvironments: "Persekitaran API", + apiClientHistory: "Entri sejarah API", + jsonFormatterDocuments: "Dokumen pemformat JSON", + }, + ui: { + title: "Pilihan antara muka", + subtitle: "Tema, bahasa dan alat yang kelihatan di bar sisi — daripada tetapan akaun.", + loadError: "Tidak dapat memuatkan pilihan.", + loginHint: "Log masuk untuk melihat pilihan antara muka yang disimpan.", + theme: "Tema", + accent: "Warna aksen", + locale: "Bahasa", + enabledTools: "Alat kelihatan", + favoriteTools: "Alat kegemaran", + toolsWithUsage: "Alat dengan penjejakan penggunaan", + }, + }, + ja: { + signIn: "ログイン", + tabs: { apps: "アプリ", analytics: "分析", ui: "インターフェース" }, + analytics: { + title: "データの概要", + subtitle: "サーバー上のアカウントと同期された件数です。", + loadError: "分析を読み込めませんでした。", + retry: "再試行", + loginHint: "ログインすると利用状況の分析とインターフェース設定を表示できます。", + passwordEntries: "パスワード項目", + bookmarks: "ブックマーク", + bookmarkFolders: "ブックマークフォルダ", + tasksTotal: "タスク", + tasksCompleted: "完了", + tasksOngoing: "進行中", + tasksNotStarted: "未着手", + projects: "プロジェクト", + nosqlConnections: "DB接続", + notes: "ノート", + apiClientCollections: "APIコレクション", + apiClientEnvironments: "API環境", + apiClientHistory: "API履歴エントリ", + jsonFormatterDocuments: "JSONフォーマッターのドキュメント", + }, + ui: { + title: "インターフェース設定", + subtitle: "テーマ・言語・サイドバーに表示するツール — アカウント設定から。", + loadError: "設定を読み込めませんでした。", + loginHint: "ログインすると保存したインターフェース設定を表示できます。", + theme: "テーマ", + accent: "アクセントカラー", + locale: "言語", + enabledTools: "表示中のツール", + favoriteTools: "お気に入りのツール", + toolsWithUsage: "利用状況が記録されたツール", + }, + }, + ko: { + signIn: "로그인", + tabs: { apps: "앱", analytics: "분석", ui: "인터페이스" }, + analytics: { + title: "데이터 한눈에 보기", + subtitle: "서버의 계정과 동기화된 개수입니다.", + loadError: "분석을 불러올 수 없습니다.", + retry: "다시 시도", + loginHint: "로그인하면 사용 분석과 인터페이스 설정을 볼 수 있습니다.", + passwordEntries: "비밀번호 항목", + bookmarks: "북마크", + bookmarkFolders: "북마크 폴더", + tasksTotal: "작업", + tasksCompleted: "완료", + tasksOngoing: "진행 중", + tasksNotStarted: "시작 전", + projects: "프로젝트", + nosqlConnections: "DB 연결", + notes: "노트", + apiClientCollections: "API 컬렉션", + apiClientEnvironments: "API 환경", + apiClientHistory: "API 기록 항목", + jsonFormatterDocuments: "JSON 포맷터 문서", + }, + ui: { + title: "인터페이스 설정", + subtitle: "테마, 언어, 사이드바에 표시할 도구 — 계정 설정에서.", + loadError: "설정을 불러올 수 없습니다.", + loginHint: "로그인하면 저장된 인터페이스 설정을 볼 수 있습니다.", + theme: "테마", + accent: "강조 색", + locale: "언어", + enabledTools: "표시 중인 도구", + favoriteTools: "즐겨찾는 도구", + toolsWithUsage: "사용량이 추적된 도구", + }, + }, + zh: { + signIn: "登录", + tabs: { apps: "应用", analytics: "分析", ui: "界面" }, + analytics: { + title: "数据一览", + subtitle: "与服务器账户同步的计数。", + loadError: "无法加载分析数据。", + retry: "重试", + loginHint: "登录后可查看使用分析与界面偏好。", + passwordEntries: "密码条目", + bookmarks: "书签", + bookmarkFolders: "书签文件夹", + tasksTotal: "任务", + tasksCompleted: "已完成", + tasksOngoing: "进行中", + tasksNotStarted: "未开始", + projects: "项目", + nosqlConnections: "数据库连接", + notes: "笔记", + apiClientCollections: "API 集合", + apiClientEnvironments: "API 环境", + apiClientHistory: "API 历史记录", + jsonFormatterDocuments: "JSON 格式化器文档", + }, + ui: { + title: "界面偏好", + subtitle: "主题、语言与侧边栏显示的工具 — 来自账户设置。", + loadError: "无法加载偏好设置。", + loginHint: "登录后可查看已保存的界面偏好。", + theme: "主题", + accent: "强调色", + locale: "语言", + enabledTools: "显示的工具", + favoriteTools: "收藏的工具", + toolsWithUsage: "已记录使用情况的工具", + }, + }, + vi: { + signIn: "Đăng nhập", + tabs: { apps: "Ứng dụng", analytics: "Phân tích", ui: "Giao diện" }, + analytics: { + title: "Dữ liệu của bạn trong nháy mắt", + subtitle: "Số liệu đồng bộ với tài khoản trên máy chủ.", + loadError: "Không tải được phân tích.", + retry: "Thử lại", + loginHint: "Đăng nhập để xem phân tích sử dụng và tùy chọn giao diện.", + passwordEntries: "Mục mật khẩu", + bookmarks: "Dấu trang", + bookmarkFolders: "Thư mục dấu trang", + tasksTotal: "Nhiệm vụ", + tasksCompleted: "Hoàn thành", + tasksOngoing: "Đang làm", + tasksNotStarted: "Chưa bắt đầu", + projects: "Dự án", + nosqlConnections: "Kết nối CSDL", + notes: "Ghi chú", + apiClientCollections: "Bộ sưu tập API", + apiClientEnvironments: "Môi trường API", + apiClientHistory: "Mục lịch sử API", + jsonFormatterDocuments: "Tài liệu định dạng JSON", + }, + ui: { + title: "Tùy chọn giao diện", + subtitle: "Chủ đề, ngôn ngữ và công cụ hiển thị trên thanh bên — từ cài đặt tài khoản.", + loadError: "Không tải được tùy chọn.", + loginHint: "Đăng nhập để xem tùy chọn giao diện đã lưu.", + theme: "Chủ đề", + accent: "Màu nhấn", + locale: "Ngôn ngữ", + enabledTools: "Công cụ hiển thị", + favoriteTools: "Công cụ yêu thích", + toolsWithUsage: "Công cụ có theo dõi sử dụng", + }, + }, + ca: { + signIn: "Inicia la sessió", + tabs: { apps: "Aplicacions", analytics: "Anàlisi", ui: "Interfície" }, + analytics: { + title: "Les teves dades d’un cop d’ull", + subtitle: "Recomptes sincronitzats amb el teu compte al servidor.", + loadError: "No s’han pogut carregar les estadístiques.", + retry: "Torna-ho a provar", + loginHint: "Inicia la sessió per veure l’anàlisi d’ús i les preferències d’interfície.", + passwordEntries: "Entrades de contrasenyes", + bookmarks: "Marcadors", + bookmarkFolders: "Carpetes de marcadors", + tasksTotal: "Tasques", + tasksCompleted: "Completades", + tasksOngoing: "En curs", + tasksNotStarted: "No iniciades", + projects: "Projectes", + nosqlConnections: "Connexions a BD", + notes: "Notes", + apiClientCollections: "Col·leccions API", + apiClientEnvironments: "Entorns API", + apiClientHistory: "Entrades de l’historial API", + jsonFormatterDocuments: "Documents del formatador JSON", + }, + ui: { + title: "Preferències d’interfície", + subtitle: "Tema, idioma i eines visibles a la barra lateral — des de la configuració del compte.", + loadError: "No s’han pogut carregar les preferències.", + loginHint: "Inicia la sessió per veure les preferències d’interfície desades.", + theme: "Tema", + accent: "Color d’accent", + locale: "Idioma", + enabledTools: "Eines visibles", + favoriteTools: "Eines favorites", + toolsWithUsage: "Eines amb ús registrat", + }, + }, +}; + +function main() { + const locales = Object.keys(T); + for (const locale of locales) { + const file = path.join(messagesDir, `${locale}.json`); + if (!fs.existsSync(file)) { + console.warn("skip missing", file); + continue; + } + const raw = fs.readFileSync(file, "utf8"); + const data = JSON.parse(raw); + if (!data.Dashboard) data.Dashboard = {}; + const tr = T[locale]; + data.Dashboard.signIn = tr.signIn; + data.Dashboard.tabs = { ...tr.tabs }; + data.Dashboard.analytics = { ...tr.analytics }; + data.Dashboard.ui = { ...tr.ui }; + fs.writeFileSync(file, JSON.stringify(data, null, 2) + "\n"); + console.log("updated", locale); + } +} + +main(); diff --git a/apps/desktop-ui/scripts/apply-json-schema-i18n.cjs b/apps/desktop-ui/scripts/apply-json-schema-i18n.cjs new file mode 100644 index 00000000..8e9684cd --- /dev/null +++ b/apps/desktop-ui/scripts/apply-json-schema-i18n.cjs @@ -0,0 +1,35 @@ +/** + * Merges json-schema-i18n.json into apps/web/messages/.json + * Run: node apps/web/scripts/apply-json-schema-i18n.cjs + */ +const fs = require('fs'); +const path = require('path'); + +const dataPath = path.join(__dirname, 'json-schema-i18n.json'); +const messagesDir = path.join(__dirname, '../messages'); + +const byLocale = JSON.parse(fs.readFileSync(dataPath, 'utf8')); + +for (const file of fs.readdirSync(messagesDir)) { + if (!file.endsWith('.json')) continue; + const loc = file.replace(/\.json$/, ''); + const patch = byLocale[loc]; + if (!patch) continue; + + const fp = path.join(messagesDir, file); + const j = JSON.parse(fs.readFileSync(fp, 'utf8')); + + if (patch.Navigation?.jsonSchemaGenerator) { + j.Navigation.jsonSchemaGenerator = patch.Navigation.jsonSchemaGenerator; + } + if (patch.Dashboard?.tools?.jsonSchemaGenerator) { + j.Dashboard.tools.jsonSchemaGenerator = patch.Dashboard.tools.jsonSchemaGenerator; + } + if (patch.JsonSchemaGenerator) { + j.JsonSchemaGenerator = patch.JsonSchemaGenerator; + } + + fs.writeFileSync(fp, JSON.stringify(j, null, 2) + '\n'); +} + +console.log('Updated locales:', Object.keys(byLocale).join(', ')); diff --git a/apps/desktop-ui/scripts/apply-pdf-compare-locales.cjs b/apps/desktop-ui/scripts/apply-pdf-compare-locales.cjs new file mode 100644 index 00000000..e431ad57 --- /dev/null +++ b/apps/desktop-ui/scripts/apply-pdf-compare-locales.cjs @@ -0,0 +1,720 @@ +/** + * Updates Navigation.pdfCompare, Dashboard.tools.pdfCompare, and PdfCompare + * in all apps/web/messages/*.json files. PdfCompare invalid/password/generic + * messages reuse PdfToJpg in the same file when present. + */ +const fs = require("fs"); +const path = require("path"); + +const MESSAGES_DIR = path.join(__dirname, "../messages"); + +/** @typedef {{ nav: string, dashTitle: string, dashDesc: string } & Record} Tr */ + +/** @type {Record} */ +const T = { + en: { + nav: "Compare PDF", + dashTitle: "Compare PDF", + dashDesc: + "Easily display the differences between two similar files. Text is extracted with PDF.js and compared locally — nothing is uploaded.", + title: "Compare PDF", + subtitle: + "Upload two PDFs to compare their text layer side by side. Runs locally in your browser with PDF.js.", + privacyNote: + "Your files never leave this browser. The PDF.js worker loads from a CDN the first time you compare.", + firstPdf: "First PDF", + secondPdf: "Second PDF", + chooseFile: "Choose file", + removeFile: "Remove", + clearAll: "Clear all", + extracting: "Extracting text…", + comparisonLabel: "Text diff", + emptyState: "Choose two PDFs to see additions and removals side by side.", + truncatedWarning: "Showing first {max} rows.", + limitExceeded: "Extracted text is limited to {max} characters per file.", + noTextHint: "Little or no text was found in one or both PDFs (common for scanned pages).", + mobileTabFiles: "PDFs", + mobileTabDiff: "Diff", + }, + af: { + nav: "Vergelyk PDF", + dashTitle: "Vergelyk PDF", + dashDesc: + "Wys maklik die verskille tussen twee soortgelyke lêers. Teks word met PDF.js plaaslik onttrek en vergelyk — niks word opgelaai nie.", + title: "Vergelyk PDF", + subtitle: + "Laai twee PDF's op om hul tekslaag sy aan sy te vergelyk. Gebeur plaaslik in jou blaaier met PDF.js.", + privacyNote: + "Jou lêers verlaat nie hierdie blaaier nie. Die PDF.js-werker laai die eerste keer vanaf 'n CDN.", + firstPdf: "Eerste PDF", + secondPdf: "Tweede PDF", + chooseFile: "Kies lêer", + removeFile: "Verwyder", + clearAll: "Maak alles skoon", + extracting: "Onttrek teks…", + comparisonLabel: "Teksverskil", + emptyState: "Kies twee PDF's om byvoegings en verwyderings sy aan sy te sien.", + truncatedWarning: "Wys eerste {max} rye.", + limitExceeded: "Onttrekte teks is beperk tot {max} karakters per lêer.", + noTextHint: "Min of geen teks in een of beide PDF's (gewoon by geskandeerde bladsye).", + mobileTabFiles: "PDF's", + mobileTabDiff: "Verskil", + }, + ar: { + nav: "مقارنة PDF", + dashTitle: "مقارنة PDF", + dashDesc: + "اعرض الاختلافات بين ملفين متشابهين بسهولة. يُستخرج النص محليًا باستخدام PDF.js ويُقارن دون رفع الملفات إلى خادم.", + title: "مقارنة PDF", + subtitle: + "ارفع ملفي PDF لمقارنة طبقة النص جنبًا إلى جنب. يعمل محليًا في المتصفح عبر PDF.js.", + privacyNote: + "ملفاتك لا تغادر هذا المتصفح. يُحمّل عامل PDF.js من شبكة CDN عند أول مقارنة.", + firstPdf: "ملف PDF الأول", + secondPdf: "ملف PDF الثاني", + chooseFile: "اختر ملفًا", + removeFile: "إزالة", + clearAll: "مسح الكل", + extracting: "جارٍ استخراج النص…", + comparisonLabel: "فرق النص", + emptyState: "اختر ملفي PDF لعرض الإضافات والحذف جنبًا إلى جنب.", + truncatedWarning: "عرض أول {max} صفًا.", + limitExceeded: "النص المستخرج محدود بـ {max} حرفًا لكل ملف.", + noTextHint: "لم يُعثر على نص تقريبًا في أحد الملفين أو كليهما (شائع في المستندات الممسوحة ضوئيًا).", + mobileTabFiles: "ملفات PDF", + mobileTabDiff: "الفروقات", + }, + ca: { + nav: "Comparar PDF", + dashTitle: "Comparar PDF", + dashDesc: + "Mostra fàcilment les diferències entre dos fitxers similars. El text s'extreu amb PDF.js i es compara localment — res no es puja.", + title: "Comparar PDF", + subtitle: + "Puja dos PDF per comparar la capa de text costat per costat. S'executa localment al navegador amb PDF.js.", + privacyNote: + "Els fitxers no surten d'aquest navegador. El worker de PDF.js es carrega des d'un CDN la primera vegada.", + firstPdf: "Primer PDF", + secondPdf: "Segon PDF", + chooseFile: "Trieu fitxer", + removeFile: "Elimina", + clearAll: "Neteja-ho tot", + extracting: "Extraient text…", + comparisonLabel: "Diferència de text", + emptyState: "Trieu dos PDF per veure afegits i eliminacions costat per costat.", + truncatedWarning: "Es mostren les primeres {max} files.", + limitExceeded: "El text extret es limita a {max} caràcters per fitxer.", + noTextHint: "Poc o cap text en un o tots dos PDF (freqüent en pàgines escanejades).", + mobileTabFiles: "PDF", + mobileTabDiff: "Diferències", + }, + cs: { + nav: "Porovnat PDF", + dashTitle: "Porovnat PDF", + dashDesc: + "Snadno ukažte rozdíly mezi dvěma podobnými soubory. Text se extrahuje pomocí PDF.js a porovnává lokálně — nic se nenahrává.", + title: "Porovnat PDF", + subtitle: + "Nahrajte dvě PDF a porovnejte jejich textovou vrstvu vedle sebe. Běží lokálně v prohlížeči s PDF.js.", + privacyNote: + "Soubory neopouštějí tento prohlížeč. Worker PDF.js se při prvním porovnání načte z CDN.", + firstPdf: "První PDF", + secondPdf: "Druhé PDF", + chooseFile: "Vybrat soubor", + removeFile: "Odebrat", + clearAll: "Vymazat vše", + extracting: "Extrahuji text…", + comparisonLabel: "Textový rozdíl", + emptyState: "Vyberte dvě PDF a uvidíte přidané a odebrané řádky vedle sebe.", + truncatedWarning: "Zobrazuji prvních {max} řádků.", + limitExceeded: "Extrahovaný text je omezen na {max} znaků na soubor.", + noTextHint: "V jednom nebo obou PDF je málo nebo žádný text (běžné u skenů).", + mobileTabFiles: "PDF", + mobileTabDiff: "Rozdíl", + }, + da: { + nav: "Sammenlign PDF", + dashTitle: "Sammenlign PDF", + dashDesc: + "Vis nemt forskelle mellem to lignende filer. Tekst udtrækkes med PDF.js og sammenlignes lokalt — intet uploades.", + title: "Sammenlign PDF", + subtitle: + "Upload to PDF-filer for at sammenligne tekstlag side om side. Kører lokalt i din browser med PDF.js.", + privacyNote: + "Dine filer forlader ikke denne browser. PDF.js-worker indlæses fra et CDN første gang.", + firstPdf: "Første PDF", + secondPdf: "Anden PDF", + chooseFile: "Vælg fil", + removeFile: "Fjern", + clearAll: "Ryd alt", + extracting: "Udtrækker tekst…", + comparisonLabel: "Tekstdiff", + emptyState: "Vælg to PDF-filer for at se tilføjelser og fjernelser side om side.", + truncatedWarning: "Viser de første {max} rækker.", + limitExceeded: "Udtrukket tekst er begrænset til {max} tegn pr. fil.", + noTextHint: "Der blev fundet lidt eller ingen tekst i en eller begge PDF'er (almindeligt ved scanninger).", + mobileTabFiles: "PDF'er", + mobileTabDiff: "Diff", + }, + de: { + nav: "PDF vergleichen", + dashTitle: "PDF vergleichen", + dashDesc: + "Unterschiede zwischen zwei ähnlichen Dateien einfach anzeigen. Text wird mit PDF.js lokal extrahiert und verglichen — kein Upload.", + title: "PDF vergleichen", + subtitle: + "Zwei PDFs hochladen und die Textebene nebeneinander vergleichen. Läuft lokal im Browser mit PDF.js.", + privacyNote: + "Ihre Dateien verlassen diesen Browser nicht. Der PDF.js-Worker wird beim ersten Vergleich von einem CDN geladen.", + firstPdf: "Erstes PDF", + secondPdf: "Zweites PDF", + chooseFile: "Datei wählen", + removeFile: "Entfernen", + clearAll: "Alles löschen", + extracting: "Text wird extrahiert…", + comparisonLabel: "Textdiff", + emptyState: "Wählen Sie zwei PDFs, um Hinzufügungen und Entfernungen nebeneinander zu sehen.", + truncatedWarning: "Es werden die ersten {max} Zeilen angezeigt.", + limitExceeded: "Extrahierter Text ist auf {max} Zeichen pro Datei begrenzt.", + noTextHint: "In einem oder beiden PDFs wurde wenig oder kein Text gefunden (häufig bei Scans).", + mobileTabFiles: "PDFs", + mobileTabDiff: "Diff", + }, + el: { + nav: "Σύγκριση PDF", + dashTitle: "Σύγκριση PDF", + dashDesc: + "Εμφανίστε εύκολα τις διαφορές μεταξύ δύο παρόμοιων αρχείων. Το κείμενο εξάγεται τοπικά με PDF.js και συγκρίνεται — χωρίς αποστολή στον διακομιστή.", + title: "Σύγκριση PDF", + subtitle: + "Ανεβάστε δύο PDF για να συγκρίνετε το επίπεδο κειμένου πλευρικά. Εκτελείται τοπικά στον browser με PDF.js.", + privacyNote: + "Τα αρχεία σας δεν εγκαταλείπουν αυτόν τον browser. Ο worker PDF.js φορτώνεται από CDN την πρώτη φορά.", + firstPdf: "Πρώτο PDF", + secondPdf: "Δεύτερο PDF", + chooseFile: "Επιλογή αρχείου", + removeFile: "Αφαίρεση", + clearAll: "Εκκαθάριση όλων", + extracting: "Εξαγωγή κειμένου…", + comparisonLabel: "Διαφορά κειμένου", + emptyState: "Επιλέξτε δύο PDF για να δείτε προσθήκες και αφαιρέσεις πλευρικά.", + truncatedWarning: "Εμφάνιση των πρώτων {max} γραμμών.", + limitExceeded: "Το εξαγόμενο κείμενο περιορίζεται σε {max} χαρακτήρες ανά αρχείο.", + noTextHint: "Βρέθηκε ελάχιστο ή καθόλου κείμενο σε ένα ή και τα δύο PDF (συχνά σε σαρωμένες σελίδες).", + mobileTabFiles: "PDF", + mobileTabDiff: "Διαφορά", + }, + es: { + nav: "Comparar PDF", + dashTitle: "Comparar PDF", + dashDesc: + "Muestra fácilmente las diferencias entre dos archivos similares. El texto se extrae con PDF.js y se compara en local: no se sube nada.", + title: "Comparar PDF", + subtitle: + "Sube dos PDF para comparar su capa de texto lado a lado. Se ejecuta en local en el navegador con PDF.js.", + privacyNote: + "Tus archivos no salen de este navegador. El worker de PDF.js se carga desde un CDN la primera vez.", + firstPdf: "Primer PDF", + secondPdf: "Segundo PDF", + chooseFile: "Elegir archivo", + removeFile: "Quitar", + clearAll: "Borrar todo", + extracting: "Extrayendo texto…", + comparisonLabel: "Diff de texto", + emptyState: "Elige dos PDF para ver adiciones y eliminaciones lado a lado.", + truncatedWarning: "Mostrando las primeras {max} filas.", + limitExceeded: "El texto extraído está limitado a {max} caracteres por archivo.", + noTextHint: "Poco o ningún texto en uno o ambos PDF (frecuente en páginas escaneadas).", + mobileTabFiles: "PDF", + mobileTabDiff: "Diff", + }, + fa: { + nav: "مقایسه PDF", + dashTitle: "مقایسه PDF", + dashDesc: + "تفاوت‌های دو فایل مشابه را به‌سادگی نمایش دهید. متن با PDF.js به‌صورت محلی استخراج و مقایسه می‌شود — چیزی آپلود نمی‌شود.", + title: "مقایسه PDF", + subtitle: + "دو فایل PDF بارگذاری کنید تا لایهٔ متن را کنار هم مقایسه کنید. به‌صورت محلی در مرورگر با PDF.js اجرا می‌شود.", + privacyNote: + "فایل‌های شما این مرورگر را ترک نمی‌کنند. worker مربوط به PDF.js بار اول از CDN بارگذاری می‌شود.", + firstPdf: "PDF اول", + secondPdf: "PDF دوم", + chooseFile: "انتخاب فایل", + removeFile: "حذف", + clearAll: "پاک‌سازی همه", + extracting: "در حال استخراج متن…", + comparisonLabel: "تفاوت متن", + emptyState: "دو PDF انتخاب کنید تا افزوده‌ها و حذف‌ها را کنار هم ببینید.", + truncatedWarning: "نمایش {max} سطر اول.", + limitExceeded: "متن استخراج‌شده به {max} نویسه در هر فایل محدود است.", + noTextHint: "در یک یا هر دو PDF متن کمی یا اصلاً یافت نشد (در اسکن‌ها رایج است).", + mobileTabFiles: "فایل‌های PDF", + mobileTabDiff: "تفاوت", + }, + fr: { + nav: "Comparer des PDF", + dashTitle: "Comparer des PDF", + dashDesc: + "Affichez facilement les différences entre deux fichiers similaires. Le texte est extrait avec PDF.js et comparé localement — rien n’est envoyé au serveur.", + title: "Comparer des PDF", + subtitle: + "Importez deux PDF pour comparer leur couche texte côte à côte. S’exécute localement dans le navigateur avec PDF.js.", + privacyNote: + "Vos fichiers ne quittent pas ce navigateur. Le worker PDF.js est chargé depuis un CDN lors de la première comparaison.", + firstPdf: "Premier PDF", + secondPdf: "Second PDF", + chooseFile: "Choisir un fichier", + removeFile: "Retirer", + clearAll: "Tout effacer", + extracting: "Extraction du texte…", + comparisonLabel: "Diff texte", + emptyState: "Choisissez deux PDF pour voir les ajouts et suppressions côte à côte.", + truncatedWarning: "Affichage des {max} premières lignes.", + limitExceeded: "Le texte extrait est limité à {max} caractères par fichier.", + noTextHint: "Peu ou pas de texte dans un ou les deux PDF (fréquent pour les scans).", + mobileTabFiles: "PDF", + mobileTabDiff: "Diff", + }, + id: { + nav: "Bandingkan PDF", + dashTitle: "Bandingkan PDF", + dashDesc: + "Tampilkan perbedaan antara dua file serupa dengan mudah. Teks diekstrak dengan PDF.js dan dibandingkan secara lokal — tidak ada unggahan.", + title: "Bandingkan PDF", + subtitle: + "Unggah dua PDF untuk membandingkan lapisan teks berdampingan. Berjalan lokal di browser dengan PDF.js.", + privacyNote: + "File Anda tidak meninggalkan browser ini. Worker PDF.js dimuat dari CDN saat pertama kali membandingkan.", + firstPdf: "PDF pertama", + secondPdf: "PDF kedua", + chooseFile: "Pilih file", + removeFile: "Hapus", + clearAll: "Bersihkan semua", + extracting: "Mengekstrak teks…", + comparisonLabel: "Diff teks", + emptyState: "Pilih dua PDF untuk melihat penambahan dan penghapusan berdampingan.", + truncatedWarning: "Menampilkan {max} baris pertama.", + limitExceeded: "Teks yang diekstrak dibatasi hingga {max} karakter per file.", + noTextHint: "Sedikit atau tidak ada teks di salah satu atau kedua PDF (umum pada hasil pindaian).", + mobileTabFiles: "PDF", + mobileTabDiff: "Diff", + }, + it: { + nav: "Confronta PDF", + dashTitle: "Confronta PDF", + dashDesc: + "Mostra facilmente le differenze tra due file simili. Il testo viene estratto con PDF.js e confrontato in locale — nulla viene caricato.", + title: "Confronta PDF", + subtitle: + "Carica due PDF per confrontare il livello testo affiancato. Funziona in locale nel browser con PDF.js.", + privacyNote: + "I tuoi file non lasciano questo browser. Il worker PDF.js viene caricato da un CDN la prima volta.", + firstPdf: "Primo PDF", + secondPdf: "Secondo PDF", + chooseFile: "Scegli file", + removeFile: "Rimuovi", + clearAll: "Cancella tutto", + extracting: "Estrazione testo…", + comparisonLabel: "Diff testo", + emptyState: "Scegli due PDF per vedere aggiunte e rimozioni affiancate.", + truncatedWarning: "Prime {max} righe mostrate.", + limitExceeded: "Il testo estratto è limitato a {max} caratteri per file.", + noTextHint: "Poco o nessun testo in uno o entrambi i PDF (frequente con pagine scansionate).", + mobileTabFiles: "PDF", + mobileTabDiff: "Diff", + }, + ja: { + nav: "PDF を比較", + dashTitle: "PDF を比較", + dashDesc: + "似た 2 つのファイルの違いを簡単に表示します。PDF.js でテキストをローカル抽出・比較します。アップロードはありません。", + title: "PDF を比較", + subtitle: + "2 つの PDF を読み込み、テキストレイヤーを並べて比較します。PDF.js でブラウザ内のみで処理します。", + privacyNote: + "ファイルはこのブラウザの外に出ません。初回比較時に PDF.js のワーカーが CDN から読み込まれます。", + firstPdf: "1 つ目の PDF", + secondPdf: "2 つ目の PDF", + chooseFile: "ファイルを選択", + removeFile: "削除", + clearAll: "すべてクリア", + extracting: "テキストを抽出しています…", + comparisonLabel: "テキスト差分", + emptyState: "2 つの PDF を選ぶと、追加・削除が並んで表示されます。", + truncatedWarning: "先頭 {max} 行を表示しています。", + limitExceeded: "抽出テキストはファイルごとに {max} 文字までです。", + noTextHint: "どちらかまたは両方の PDF にテキストがほとんどありません(スキャン PDF でよくあります)。", + mobileTabFiles: "PDF", + mobileTabDiff: "差分", + }, + ko: { + nav: "PDF 비교", + dashTitle: "PDF 비교", + dashDesc: + "비슷한 두 파일의 차이를 쉽게 확인하세요. PDF.js로 텍스트를 로컬에서 추출·비교하며 업로드되지 않습니다.", + title: "PDF 비교", + subtitle: + "두 개의 PDF를 업로드하여 텍스트 레이어를 나란히 비교합니다. PDF.js로 브라우저에서만 실행됩니다.", + privacyNote: + "파일은 이 브라우저를 벗어나지 않습니다. 처음 비교할 때 PDF.js 워커가 CDN에서 로드됩니다.", + firstPdf: "첫 번째 PDF", + secondPdf: "두 번째 PDF", + chooseFile: "파일 선택", + removeFile: "제거", + clearAll: "모두 지우기", + extracting: "텍스트 추출 중…", + comparisonLabel: "텍스트 diff", + emptyState: "두 개의 PDF를 선택하면 추가·삭제를 나란히 볼 수 있습니다.", + truncatedWarning: "처음 {max}줄만 표시합니다.", + limitExceeded: "추출된 텍스트는 파일당 {max}자로 제한됩니다.", + noTextHint: "한쪽 또는 양쪽 PDF에서 텍스트가 거의 없습니다(스캔 문서에서 흔함).", + mobileTabFiles: "PDF", + mobileTabDiff: "Diff", + }, + ms: { + nav: "Bandingkan PDF", + dashTitle: "Bandingkan PDF", + dashDesc: + "Paparkan perbezaan antara dua fail serupa dengan mudah. Teks diekstrak dengan PDF.js dan dibandingkan secara setempat — tiada muat naik.", + title: "Bandingkan PDF", + subtitle: + "Muat naik dua PDF untuk membandingkan lapisan teks bersebelahan. Berjalan secara setempat dalam pelayar dengan PDF.js.", + privacyNote: + "Fail anda tidak meninggalkan pelayar ini. Pekerja PDF.js dimuat dari CDN kali pertama anda membandingkan.", + firstPdf: "PDF pertama", + secondPdf: "PDF kedua", + chooseFile: "Pilih fail", + removeFile: "Buang", + clearAll: "Kosongkan semua", + extracting: "Mengekstrak teks…", + comparisonLabel: "Diff teks", + emptyState: "Pilih dua PDF untuk melihat tambahan dan pembuangan bersebelahan.", + truncatedWarning: "Menunjukkan {max} baris pertama.", + limitExceeded: "Teks yang diekstrak terhad kepada {max} aksara setiap fail.", + noTextHint: "Sedikit atau tiada teks dalam satu atau kedua-dua PDF (biasa pada imbasan).", + mobileTabFiles: "PDF", + mobileTabDiff: "Diff", + }, + nb: { + nav: "Sammenlign PDF", + dashTitle: "Sammenlign PDF", + dashDesc: + "Vis enkelt forskjeller mellom to lignende filer. Tekst hentes ut med PDF.js og sammenlignes lokalt — ingenting lastes opp.", + title: "Sammenlign PDF", + subtitle: + "Last opp to PDF-filer for å sammenligne tekstlag side ved side. Kjører lokalt i nettleseren med PDF.js.", + privacyNote: + "Filene dine forlater ikke denne nettleseren. PDF.js-arbeider lastes fra et CDN første gang.", + firstPdf: "Første PDF", + secondPdf: "Andre PDF", + chooseFile: "Velg fil", + removeFile: "Fjern", + clearAll: "Tøm alt", + extracting: "Henter ut tekst…", + comparisonLabel: "Tekstdiff", + emptyState: "Velg to PDF-filer for å se tillegg og slettinger side ved side.", + truncatedWarning: "Viser de første {max} radene.", + limitExceeded: "Uthentet tekst er begrenset til {max} tegn per fil.", + noTextHint: "Lite eller ingen tekst i én eller begge PDF-ene (vanlig ved skanning).", + mobileTabFiles: "PDF-filer", + mobileTabDiff: "Diff", + }, + nl: { + nav: "PDF vergelijken", + dashTitle: "PDF vergelijken", + dashDesc: + "Toon eenvoudig de verschillen tussen twee vergelijkbare bestanden. Tekst wordt met PDF.js lokaal geëxtraheerd en vergeleken — er wordt niets geüpload.", + title: "PDF vergelijken", + subtitle: + "Upload twee PDF’s om de tekstlaag naast elkaar te vergelijken. Draait lokaal in je browser met PDF.js.", + privacyNote: + "Je bestanden verlaten deze browser niet. De PDF.js-worker wordt de eerste keer vanaf een CDN geladen.", + firstPdf: "Eerste PDF", + secondPdf: "Tweede PDF", + chooseFile: "Bestand kiezen", + removeFile: "Verwijderen", + clearAll: "Alles wissen", + extracting: "Tekst extraheren…", + comparisonLabel: "Tekstverschil", + emptyState: "Kies twee PDF’s om toevoegingen en verwijderingen naast elkaar te zien.", + truncatedWarning: "Eerste {max} rijen worden getoond.", + limitExceeded: "Geëxtraheerde tekst is beperkt tot {max} tekens per bestand.", + noTextHint: "Weinig of geen tekst in een of beide PDF’s (gebruikelijk bij scans).", + mobileTabFiles: "PDF’s", + mobileTabDiff: "Diff", + }, + pl: { + nav: "Porównaj PDF", + dashTitle: "Porównaj PDF", + dashDesc: + "Łatwo pokaż różnice między dwoma podobnymi plikami. Tekst jest wyciągany przez PDF.js i porównywany lokalnie — nic nie jest wysyłane.", + title: "Porównaj PDF", + subtitle: + "Prześlij dwa pliki PDF, aby porównać warstwę tekstową obok siebie. Działa lokalnie w przeglądarce z PDF.js.", + privacyNote: + "Twoje pliki nie opuszczają tej przeglądarki. Worker PDF.js ładuje się z CDN przy pierwszym porównaniu.", + firstPdf: "Pierwszy PDF", + secondPdf: "Drugi PDF", + chooseFile: "Wybierz plik", + removeFile: "Usuń", + clearAll: "Wyczyść wszystko", + extracting: "Wyciąganie tekstu…", + comparisonLabel: "Różnice tekstu", + emptyState: "Wybierz dwa pliki PDF, aby zobaczyć dodania i usunięcia obok siebie.", + truncatedWarning: "Wyświetlane są pierwsze {max} wierszy.", + limitExceeded: "Wyciągnięty tekst jest ograniczony do {max} znaków na plik.", + noTextHint: "W jednym lub obu plikach PDF jest mało lub brak tekstu (częste przy skanach).", + mobileTabFiles: "PDF", + mobileTabDiff: "Różnice", + }, + pt: { + nav: "Comparar PDF", + dashTitle: "Comparar PDF", + dashDesc: + "Mostre facilmente as diferenças entre dois ficheiros semelhantes. O texto é extraído com PDF.js e comparado localmente — nada é enviado.", + title: "Comparar PDF", + subtitle: + "Carregue dois PDFs para comparar a camada de texto lado a lado. Corre localmente no navegador com PDF.js.", + privacyNote: + "Os seus ficheiros não saem deste navegador. O worker PDF.js é carregado a partir de um CDN na primeira vez.", + firstPdf: "Primeiro PDF", + secondPdf: "Segundo PDF", + chooseFile: "Escolher ficheiro", + removeFile: "Remover", + clearAll: "Limpar tudo", + extracting: "A extrair texto…", + comparisonLabel: "Diff de texto", + emptyState: "Escolha dois PDFs para ver adições e remoções lado a lado.", + truncatedWarning: "A mostrar as primeiras {max} linhas.", + limitExceeded: "O texto extraído está limitado a {max} caracteres por ficheiro.", + noTextHint: "Pouco ou nenhum texto num ou em ambos os PDFs (comum em digitalizações).", + mobileTabFiles: "PDFs", + mobileTabDiff: "Diff", + }, + "pt-BR": { + nav: "Comparar PDF", + dashTitle: "Comparar PDF", + dashDesc: + "Mostre facilmente as diferenças entre dois arquivos semelhantes. O texto é extraído com PDF.js e comparado localmente — nada é enviado.", + title: "Comparar PDF", + subtitle: + "Envie dois PDFs para comparar a camada de texto lado a lado. Roda localmente no navegador com PDF.js.", + privacyNote: + "Seus arquivos não saem deste navegador. O worker do PDF.js é carregado de um CDN na primeira comparação.", + firstPdf: "Primeiro PDF", + secondPdf: "Segundo PDF", + chooseFile: "Escolher arquivo", + removeFile: "Remover", + clearAll: "Limpar tudo", + extracting: "Extraindo texto…", + comparisonLabel: "Diff de texto", + emptyState: "Escolha dois PDFs para ver adições e remoções lado a lado.", + truncatedWarning: "Mostrando as primeiras {max} linhas.", + limitExceeded: "O texto extraído é limitado a {max} caracteres por arquivo.", + noTextHint: "Pouco ou nenhum texto em um ou ambos os PDFs (comum em digitalizações).", + mobileTabFiles: "PDFs", + mobileTabDiff: "Diff", + }, + ru: { + nav: "Сравнить PDF", + dashTitle: "Сравнить PDF", + dashDesc: + "Наглядно покажите отличия между двумя похожими файлами. Текст извлекается с помощью PDF.js и сравнивается локально — ничего не загружается на сервер.", + title: "Сравнить PDF", + subtitle: + "Загрузите два PDF, чтобы сравнить текстовый слой рядом. Работает локально в браузере с PDF.js.", + privacyNote: + "Ваши файлы не покидают этот браузер. Воркер PDF.js подгружается с CDN при первом сравнении.", + firstPdf: "Первый PDF", + secondPdf: "Второй PDF", + chooseFile: "Выбрать файл", + removeFile: "Убрать", + clearAll: "Очистить всё", + extracting: "Извлечение текста…", + comparisonLabel: "Текстовые отличия", + emptyState: "Выберите два PDF, чтобы увидеть добавления и удаления рядом.", + truncatedWarning: "Показаны первые {max} строк.", + limitExceeded: "Извлечённый текст ограничен {max} символами на файл.", + noTextHint: "В одном или обоих PDF мало текста (часто у отсканированных страниц).", + mobileTabFiles: "PDF", + mobileTabDiff: "Отличия", + }, + sv: { + nav: "Jämför PDF", + dashTitle: "Jämför PDF", + dashDesc: + "Visa enkelt skillnaderna mellan två liknande filer. Text extraheras med PDF.js och jämförs lokalt — inget laddas upp.", + title: "Jämför PDF", + subtitle: + "Ladda upp två PDF-filer för att jämföra textlagret sida vid sida. Körs lokalt i webbläsaren med PDF.js.", + privacyNote: + "Dina filer lämnar inte den här webbläsaren. PDF.js-arbetaren laddas från ett CDN första gången.", + firstPdf: "Första PDF", + secondPdf: "Andra PDF", + chooseFile: "Välj fil", + removeFile: "Ta bort", + clearAll: "Rensa allt", + extracting: "Extraherar text…", + comparisonLabel: "Textdiff", + emptyState: "Välj två PDF-filer för att se tillägg och borttagningar sida vid sida.", + truncatedWarning: "Visar de första {max} raderna.", + limitExceeded: "Extraherad text är begränsad till {max} tecken per fil.", + noTextHint: "Lite eller ingen text i en eller båda PDF:erna (vanligt vid skanning).", + mobileTabFiles: "PDF", + mobileTabDiff: "Diff", + }, + tr: { + nav: "PDF karşılaştır", + dashTitle: "PDF karşılaştır", + dashDesc: + "İki benzer dosya arasındaki farkları kolayca gösterin. Metin PDF.js ile yerel olarak çıkarılır ve karşılaştırılır — hiçbir şey yüklenmez.", + title: "PDF karşılaştır", + subtitle: + "Metin katmanını yan yana karşılaştırmak için iki PDF yükleyin. PDF.js ile tarayıcıda yerel çalışır.", + privacyNote: + "Dosyalarınız bu tarayıcıdan çıkmaz. PDF.js worker ilk karşılaştırmada bir CDN’den yüklenir.", + firstPdf: "Birinci PDF", + secondPdf: "İkinci PDF", + chooseFile: "Dosya seç", + removeFile: "Kaldır", + clearAll: "Tümünü temizle", + extracting: "Metin çıkarılıyor…", + comparisonLabel: "Metin farkı", + emptyState: "Eklemeleri ve silmeleri yan yana görmek için iki PDF seçin.", + truncatedWarning: "İlk {max} satır gösteriliyor.", + limitExceeded: "Çıkarılan metin dosya başına {max} karakterle sınırlıdır.", + noTextHint: "Bir veya her iki PDF’de az veya hiç metin yok (taranmış sayfalarda yaygın).", + mobileTabFiles: "PDF’ler", + mobileTabDiff: "Fark", + }, + uk: { + nav: "Порівняти PDF", + dashTitle: "Порівняти PDF", + dashDesc: + "Легко покажіть відмінності між двома схожими файлами. Текст витягується за допомогою PDF.js і порівнюється локально — нічого не завантажується.", + title: "Порівняти PDF", + subtitle: + "Завантажте два PDF, щоб порівняти текстовий шар поруч. Працює локально в браузері з PDF.js.", + privacyNote: + "Ваші файли не залишають цей браузер. Worker PDF.js завантажується з CDN під час першого порівняння.", + firstPdf: "Перший PDF", + secondPdf: "Другий PDF", + chooseFile: "Вибрати файл", + removeFile: "Прибрати", + clearAll: "Очистити все", + extracting: "Витяг тексту…", + comparisonLabel: "Текстові відмінності", + emptyState: "Оберіть два PDF, щоб побачити додавання й вилучення поруч.", + truncatedWarning: "Показано перші {max} рядків.", + limitExceeded: "Витягнутий текст обмежений {max} символами на файл.", + noTextHint: "Мало або немає тексту в одному або обох PDF (часто для відсканованих сторінок).", + mobileTabFiles: "PDF", + mobileTabDiff: "Відмінності", + }, + vi: { + nav: "So sánh PDF", + dashTitle: "So sánh PDF", + dashDesc: + "Dễ dàng hiển thị khác biệt giữa hai tệp tương tự. Văn bản được trích xuất bằng PDF.js và so sánh cục bộ — không tải lên máy chủ.", + title: "So sánh PDF", + subtitle: + "Tải lên hai PDF để so sánh lớp văn bản cạnh nhau. Chạy cục bộ trong trình duyệt với PDF.js.", + privacyNote: + "Tệp của bạn không rời khỏi trình duyệt này. Worker PDF.js được tải từ CDN lần đầu so sánh.", + firstPdf: "PDF thứ nhất", + secondPdf: "PDF thứ hai", + chooseFile: "Chọn tệp", + removeFile: "Gỡ", + clearAll: "Xóa tất cả", + extracting: "Đang trích xuất văn bản…", + comparisonLabel: "Khác biệt văn bản", + emptyState: "Chọn hai PDF để xem phần thêm và phần xóa cạnh nhau.", + truncatedWarning: "Hiển thị {max} dòng đầu tiên.", + limitExceeded: "Văn bản trích xuất giới hạn {max} ký tự mỗi tệp.", + noTextHint: "Ít hoặc không có văn bản trong một hoặc cả hai PDF (thường gặp với trang quét).", + mobileTabFiles: "PDF", + mobileTabDiff: "Khác biệt", + }, + zh: { + nav: "比较 PDF", + dashTitle: "比较 PDF", + dashDesc: + "轻松展示两个相似文件之间的差异。使用 PDF.js 在本地提取并比较文本 — 不会上传任何内容。", + title: "比较 PDF", + subtitle: "上传两个 PDF,并排比较文本层。在浏览器中通过 PDF.js 本地运行。", + privacyNote: "您的文件不会离开本浏览器。首次比较时从 CDN 加载 PDF.js 工作线程。", + firstPdf: "第一个 PDF", + secondPdf: "第二个 PDF", + chooseFile: "选择文件", + removeFile: "移除", + clearAll: "全部清除", + extracting: "正在提取文本…", + comparisonLabel: "文本差异", + emptyState: "选择两个 PDF 以并排查看新增与删除。", + truncatedWarning: "仅显示前 {max} 行。", + limitExceeded: "每个文件提取的文本最多 {max} 个字符。", + noTextHint: "其中一个或两个 PDF 几乎没有可提取文本(扫描件常见)。", + mobileTabFiles: "PDF", + mobileTabDiff: "差异", + }, +}; + +const enFallback = T.en; + +const PDF_ERR_FALLBACK_EN = { + invalidPdf: "Could not read this file as a PDF.", + passwordRequired: + "This PDF needs a password. Unlock it with PDF Unlocker first, then try again.", + genericError: "Could not process this PDF. Try a different file.", +}; + +function buildPdfCompare(locale, messages) { + const tr = T[locale] || enFallback; + const ptj = messages.PdfToJpg || {}; + return { + title: tr.title, + subtitle: tr.subtitle, + privacyNote: tr.privacyNote, + firstPdf: tr.firstPdf, + secondPdf: tr.secondPdf, + chooseFile: tr.chooseFile, + removeFile: tr.removeFile, + clearAll: tr.clearAll, + extracting: tr.extracting, + comparisonLabel: tr.comparisonLabel, + emptyState: tr.emptyState, + truncatedWarning: tr.truncatedWarning, + limitExceeded: tr.limitExceeded, + noTextHint: tr.noTextHint, + mobileTabFiles: tr.mobileTabFiles, + mobileTabDiff: tr.mobileTabDiff, + invalidPdf: ptj.invalidPdf || PDF_ERR_FALLBACK_EN.invalidPdf, + passwordRequired: ptj.passwordRequired || PDF_ERR_FALLBACK_EN.passwordRequired, + genericError: ptj.genericError || PDF_ERR_FALLBACK_EN.genericError, + }; +} + +for (const file of fs.readdirSync(MESSAGES_DIR).filter((f) => f.endsWith(".json"))) { + const fp = path.join(MESSAGES_DIR, file); + const locale = file.replace(/\.json$/i, ""); + const j = JSON.parse(fs.readFileSync(fp, "utf8")); + const tr = T[locale] || enFallback; + + if (!j.Navigation) j.Navigation = {}; + j.Navigation.pdfCompare = tr.nav; + + if (!j.Dashboard) j.Dashboard = {}; + if (!j.Dashboard.tools) j.Dashboard.tools = {}; + j.Dashboard.tools.pdfCompare = { + title: tr.dashTitle, + description: tr.dashDesc, + }; + + j.PdfCompare = buildPdfCompare(locale, j); + + fs.writeFileSync(fp, JSON.stringify(j, null, 2) + "\n"); +} + +console.log("Updated PdfCompare strings in", fs.readdirSync(MESSAGES_DIR).filter((f) => f.endsWith(".json")).length, "locale files."); + diff --git a/apps/desktop-ui/scripts/apply-pdfa-locale-translations.mjs b/apps/desktop-ui/scripts/apply-pdfa-locale-translations.mjs new file mode 100644 index 00000000..aa19fb9b --- /dev/null +++ b/apps/desktop-ui/scripts/apply-pdfa-locale-translations.mjs @@ -0,0 +1,725 @@ +/** + * One-off script: fills Navigation, Dashboard.tools, and PdfToPdfA for each locale + * with proper translations. Reuses PdfCompressor (chooseFile, reset, pageCount, + * invalidPdf, encryptedPdf) and PdfWatermark.working for consistency. + * + * Run: node scripts/apply-pdfa-locale-translations.mjs + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const messagesDir = path.join(__dirname, "../messages"); + +/** @typedef {{ nav: string, dashboardTitle: string, dashboardDesc: string, intro: string, privacyNote: string, disclaimer: string, levelLabel: string, levelHint: string, download: string, done: string, convertFailed: string, level: Record }} Spec */ + +/** @type {Record} */ +const STRINGS = { + en: { + nav: "PDF to PDF/A", + dashboardTitle: "PDF to PDF/A", + dashboardDesc: + "Convert PDFs to PDF/A for archival preservation and pick an ISO conformance level — all in your browser.", + intro: + "Convert PDF documents to PDF/A for archiving and long-term preservation.", + privacyNote: "Your file never leaves this browser.", + disclaimer: + "This rebuilds your PDF with PDF/A identification (XMP pdfaid, sRGB output intent, and MarkInfo). Complex PDFs may still fail strict ISO validators (fonts, transparency, attachments). Use dedicated archival software if you need guaranteed compliance.", + levelLabel: "PDF/A conformance", + levelHint: + 'PDF/A-1b disables object streams for older archival expectations. PDF/A-2 and PDF/A-3 target modern PDF 1.7 workflows; "U" declares Unicode text assumptions.', + download: "Convert & download", + done: "PDF/A-tagged file downloaded.", + convertFailed: "Could not convert this PDF. Try a different file.", + level: { + "1b": "PDF/A-1b (part 1, conformance B)", + "2b": "PDF/A-2b (part 2, conformance B)", + "2u": "PDF/A-2u (part 2, conformance U)", + "3b": "PDF/A-3b (part 3, conformance B)", + "3u": "PDF/A-3u (part 3, conformance U)", + }, + }, + de: { + nav: "PDF nach PDF/A", + dashboardTitle: "PDF nach PDF/A", + dashboardDesc: + "Wandeln Sie PDFs für Archivierung und Langzeitbewahrung in PDF/A um und wählen Sie die ISO-Konformitätsstufe – alles in Ihrem Browser.", + intro: + "Wandeln Sie PDF-Dokumente für Archivierung und Langzeitbewahrung in PDF/A um.", + privacyNote: "Ihre Datei verlässt diesen Browser nicht.", + disclaimer: + "Dies erstellt die PDF mit PDF/A-Kennzeichnung (XMP pdfaid, sRGB-Ausgabeintent und MarkInfo) neu. Komplexe PDFs können strenge ISO-Prüfungen dennoch nicht bestehen (Schriftarten, Transparenz, Anhänge). Für garantierte Konformität nutzen Sie spezialisierte Archivsoftware.", + levelLabel: "PDF/A-Konformität", + levelHint: + 'PDF/A-1b deaktiviert Objektstreams für ältere Archiverwartungen. PDF/A-2 und PDF/A-3 richten sich an moderne PDF-1.7-Workflows; "U" steht für Unicode-Textannahmen.', + download: "Konvertieren & herunterladen", + done: "Die mit PDF/A gekennzeichnete Datei wurde heruntergeladen.", + convertFailed: "Dieses PDF konnte nicht konvertiert werden. Versuchen Sie eine andere Datei.", + level: { + "1b": "PDF/A-1b (Teil 1, Konformität B)", + "2b": "PDF/A-2b (Teil 2, Konformität B)", + "2u": "PDF/A-2u (Teil 2, Konformität U)", + "3b": "PDF/A-3b (Teil 3, Konformität B)", + "3u": "PDF/A-3u (Teil 3, Konformität U)", + }, + }, + fr: { + nav: "PDF vers PDF/A", + dashboardTitle: "PDF vers PDF/A", + dashboardDesc: + "Convertissez des PDF en PDF/A pour l’archivage et la conservation longue durée, avec un niveau de conformité ISO au choix — le tout dans votre navigateur.", + intro: + "Convertissez des documents PDF en PDF/A pour l’archivage et la conservation à long terme.", + privacyNote: "Votre fichier ne quitte jamais ce navigateur.", + disclaimer: + "Cette opération reconstruit le PDF avec les marqueurs PDF/A (XMP pdfaid, intention de sortie sRGB et MarkInfo). Les PDF complexes peuvent encore échouer aux validateurs ISO stricts (polices, transparence, pièces jointes). Utilisez un logiciel d’archivage dédié pour une conformité garantie.", + levelLabel: "Conformité PDF/A", + levelHint: + 'PDF/A-1b désactive les flux d’objets pour les attentes d’archivage plus anciennes. PDF/A-2 et PDF/A-3 ciblent les workflows PDF 1.7 modernes ; « U » indique des hypothèses de texte Unicode.', + download: "Convertir et télécharger", + done: "Fichier balisé PDF/A téléchargé.", + convertFailed: "Impossible de convertir ce PDF. Essayez un autre fichier.", + level: { + "1b": "PDF/A-1b (partie 1, conformité B)", + "2b": "PDF/A-2b (partie 2, conformité B)", + "2u": "PDF/A-2u (partie 2, conformité U)", + "3b": "PDF/A-3b (partie 3, conformité B)", + "3u": "PDF/A-3u (partie 3, conformité U)", + }, + }, + es: { + nav: "PDF a PDF/A", + dashboardTitle: "PDF a PDF/A", + dashboardDesc: + "Convierte PDF a PDF/A para archivo y conservación a largo plazo, con el nivel de conformidad ISO que elijas — todo en tu navegador.", + intro: + "Convierte documentos PDF a PDF/A para archivo y conservación a largo plazo.", + privacyNote: "Tu archivo no sale nunca de este navegador.", + disclaimer: + "Esto reconstruye el PDF con identificación PDF/A (XMP pdfaid, intención de salida sRGB y MarkInfo). Los PDF complejos pueden seguir fallando validadores ISO estrictos (fuentes, transparencia, adjuntos). Usa software de archivo dedicado si necesitas conformidad garantizada.", + levelLabel: "Conformidad PDF/A", + levelHint: + 'PDF/A-1b desactiva flujos de objetos para expectativas de archivo más antiguas. PDF/A-2 y PDF/A-3 apuntan a flujos de trabajo PDF 1.7 modernos; « U » declara suposiciones de texto Unicode.', + download: "Convertir y descargar", + done: "Archivo etiquetado como PDF/A descargado.", + convertFailed: "No se pudo convertir este PDF. Prueba con otro archivo.", + level: { + "1b": "PDF/A-1b (parte 1, conformidad B)", + "2b": "PDF/A-2b (parte 2, conformidad B)", + "2u": "PDF/A-2u (parte 2, conformidad U)", + "3b": "PDF/A-3b (parte 3, conformidad B)", + "3u": "PDF/A-3u (parte 3, conformidad U)", + }, + }, + it: { + nav: "PDF in PDF/A", + dashboardTitle: "PDF in PDF/A", + dashboardDesc: + "Converti i PDF in PDF/A per archiviazione e conservazione a lungo termine, con il livello di conformità ISO desiderato — tutto nel browser.", + intro: + "Converti documenti PDF in PDF/A per archiviazione e conservazione a lungo termine.", + privacyNote: "Il tuo file non lascia mai questo browser.", + disclaimer: + "Questo ricostruisce il PDF con identificazione PDF/A (XMP pdfaid, output intent sRGB e MarkInfo). I PDF complessi possono ancora non superare validatori ISO rigidi (font, trasparenza, allegati). Usa software di archiviazione dedicato per una conformità garantita.", + levelLabel: "Conformità PDF/A", + levelHint: + 'PDF/A-1b disabilita gli object stream per aspettative di archiviazione più datate. PDF/A-2 e PDF/A-3 sono per flussi di lavoro PDF 1.7 moderni; «U» indica assunzioni sul testo Unicode.', + download: "Converti e scarica", + done: "File contrassegnato PDF/A scaricato.", + convertFailed: "Impossibile convertire questo PDF. Prova un altro file.", + level: { + "1b": "PDF/A-1b (parte 1, conformità B)", + "2b": "PDF/A-2b (parte 2, conformità B)", + "2u": "PDF/A-2u (parte 2, conformità U)", + "3b": "PDF/A-3b (parte 3, conformità B)", + "3u": "PDF/A-3u (parte 3, conformità U)", + }, + }, + pt: { + nav: "PDF para PDF/A", + dashboardTitle: "PDF para PDF/A", + dashboardDesc: + "Converta PDFs para PDF/A para arquivo e preservação a longo prazo, com o nível de conformidade ISO que preferir — tudo no seu navegador.", + intro: + "Converta documentos PDF em PDF/A para arquivo e preservação a longo prazo.", + privacyNote: "O seu ficheiro nunca sai deste navegador.", + disclaimer: + "Isto reconstrói o PDF com identificação PDF/A (XMP pdfaid, intenção de saída sRGB e MarkInfo). PDFs complexos podem ainda falhar validadores ISO rigorosos (tipos de letra, transparência, anexos). Use software de arquivo dedicado se precisar de conformidade garantida.", + levelLabel: "Conformidade PDF/A", + levelHint: + 'PDF/A-1b desativa fluxos de objeto para expectativas de arquivo mais antigas. PDF/A-2 e PDF/A-3 visam fluxos PDF 1.7 modernos; «U» declara pressupostos de texto Unicode.', + download: "Converter e transferir", + done: "Ficheiro etiquetado PDF/A transferido.", + convertFailed: "Não foi possível converter este PDF. Tente outro ficheiro.", + level: { + "1b": "PDF/A-1b (parte 1, conformidade B)", + "2b": "PDF/A-2b (parte 2, conformidade B)", + "2u": "PDF/A-2u (parte 2, conformidade U)", + "3b": "PDF/A-3b (parte 3, conformidade B)", + "3u": "PDF/A-3u (parte 3, conformidade U)", + }, + }, + "pt-BR": { + nav: "PDF para PDF/A", + dashboardTitle: "PDF para PDF/A", + dashboardDesc: + "Converta PDFs para PDF/A para arquivamento e preservação a longo prazo, com o nível de conformidade ISO desejado — tudo no seu navegador.", + intro: + "Converta documentos PDF em PDF/A para arquivamento e preservação a longo prazo.", + privacyNote: "Seu arquivo nunca sai deste navegador.", + disclaimer: + "Isso reconstrói o PDF com identificação PDF/A (XMP pdfaid, output intent sRGB e MarkInfo). PDFs complexos ainda podem falhar em validadores ISO rigorosos (fontes, transparência, anexos). Use software de arquivamento dedicado se precisar de conformidade garantida.", + levelLabel: "Conformidade PDF/A", + levelHint: + 'PDF/A-1b desativa object streams para expectativas de arquivo mais antigas. PDF/A-2 e PDF/A-3 visam fluxos PDF 1.7 modernos; «U» declara suposições de texto Unicode.', + download: "Converter e baixar", + done: "Arquivo marcado como PDF/A baixado.", + convertFailed: "Não foi possível converter este PDF. Tente outro arquivo.", + level: { + "1b": "PDF/A-1b (parte 1, conformidade B)", + "2b": "PDF/A-2b (parte 2, conformidade B)", + "2u": "PDF/A-2u (parte 2, conformidade U)", + "3b": "PDF/A-3b (parte 3, conformidade B)", + "3u": "PDF/A-3u (parte 3, conformidade U)", + }, + }, + nl: { + nav: "PDF naar PDF/A", + dashboardTitle: "PDF naar PDF/A", + dashboardDesc: + "Zet PDF’s om naar PDF/A voor archivering en langetermijnbewaring, met het gewenste ISO-conformiteitsniveau — volledig in je browser.", + intro: "Zet PDF-documenten om naar PDF/A voor archivering en langetermijnbewaring.", + privacyNote: "Je bestand verlaat deze browser nooit.", + disclaimer: + "Dit bouwt de PDF opnieuw op met PDF/A-markering (XMP pdfaid, sRGB-outputintent en MarkInfo). Complexe PDF’s kunnen nog steeds falen voor strikte ISO-validators (lettertypen, transparantie, bijlagen). Gebruik gespecialiseerde archiveringssoftware voor gegarandeerde conformiteit.", + levelLabel: "PDF/A-conformiteit", + levelHint: + 'PDF/A-1b schakelt objectstreams uit voor oudere archiefverwachtingen. PDF/A-2 en PDF/A-3 richten zich op moderne PDF 1.7-workflows; "U" betekent Unicode-tekstveronderstellingen.', + download: "Converteren en downloaden", + done: "PDF/A-gemarkeerd bestand gedownload.", + convertFailed: "Deze PDF kon niet worden geconverteerd. Probeer een ander bestand.", + level: { + "1b": "PDF/A-1b (deel 1, conformiteit B)", + "2b": "PDF/A-2b (deel 2, conformiteit B)", + "2u": "PDF/A-2u (deel 2, conformiteit U)", + "3b": "PDF/A-3b (deel 3, conformiteit B)", + "3u": "PDF/A-3u (deel 3, conformiteit U)", + }, + }, + sv: { + nav: "PDF till PDF/A", + dashboardTitle: "PDF till PDF/A", + dashboardDesc: + "Konvertera PDF till PDF/A för arkivering och långtidsbevarande med valfri ISO-konformitetsnivå — allt i din webbläsare.", + intro: "Konvertera PDF-dokument till PDF/A för arkivering och långtidsbevarande.", + privacyNote: "Din fil lämnar aldrig den här webbläsaren.", + disclaimer: + "Detta bygger om PDF:en med PDF/A-markering (XMP pdfaid, sRGB-utdataavsikt och MarkInfo). Komplexa PDF:er kan fortfarande fallera mot strikta ISO-validerare (typsnitt, transparens, bilagor). Använd dedikerad arkivprogramvara om du behöver garanterad efterlevnad.", + levelLabel: "PDF/A-konformitet", + levelHint: + 'PDF/A-1b inaktiverar objektströmmar för äldre arkivförväntningar. PDF/A-2 och PDF/A-3 riktar sig mot moderna PDF 1.7-arbetsflöden; "U" anger Unicode-textantaganden.', + download: "Konvertera och ladda ner", + done: "PDF/A-märkt fil nedladdad.", + convertFailed: "Det gick inte att konvertera den här PDF:en. Prova en annan fil.", + level: { + "1b": "PDF/A-1b (del 1, konformitet B)", + "2b": "PDF/A-2b (del 2, konformitet B)", + "2u": "PDF/A-2u (del 2, konformitet U)", + "3b": "PDF/A-3b (del 3, konformitet B)", + "3u": "PDF/A-3u (del 3, konformitet U)", + }, + }, + da: { + nav: "PDF til PDF/A", + dashboardTitle: "PDF til PDF/A", + dashboardDesc: + "Konvertér PDF-filer til PDF/A til arkivering og langtidsbevaring med valgfrit ISO-overensstemmelsesniveau — alt i din browser.", + intro: "Konvertér PDF-dokumenter til PDF/A til arkivering og langtidsbevaring.", + privacyNote: "Din fil forlader aldrig denne browser.", + disclaimer: + "Dette genopbygger PDF’en med PDF/A-markering (XMP pdfaid, sRGB-outputintent og MarkInfo). Komplekse PDF’er kan stadig fejle over for strenge ISO-validatorer (skrifttyper, gennemsigtighed, vedhæftninger). Brug dedikeret arkiveringssoftware, hvis du skal have garanteret overensstemmelse.", + levelLabel: "PDF/A-overensstemmelse", + levelHint: + 'PDF/A-1b deaktiverer objektstreams til ældre arkivforventninger. PDF/A-2 og PDF/A-3 sigter mod moderne PDF 1.7-workflows; "U" angiver antagelser om Unicode-tekst.', + download: "Konvertér og download", + done: "PDF/A-mærket fil downloadet.", + convertFailed: "Denne PDF kunne ikke konverteres. Prøv en anden fil.", + level: { + "1b": "PDF/A-1b (del 1, overensstemmelse B)", + "2b": "PDF/A-2b (del 2, overensstemmelse B)", + "2u": "PDF/A-2u (del 2, overensstemmelse U)", + "3b": "PDF/A-3b (del 3, overensstemmelse B)", + "3u": "PDF/A-3u (del 3, overensstemmelse U)", + }, + }, + nb: { + nav: "PDF til PDF/A", + dashboardTitle: "PDF til PDF/A", + dashboardDesc: + "Konverter PDF-filer til PDF/A for arkivering og langtidsbevaring med ønsket ISO-samsvarsnivå — alt i nettleseren din.", + intro: "Konverter PDF-dokumenter til PDF/A for arkivering og langtidsbevaring.", + privacyNote: "Filen din forlater aldri denne nettleseren.", + disclaimer: + "Dette bygger PDF-en på nytt med PDF/A-merking (XMP pdfaid, sRGB-utdatahensikt og MarkInfo). Komplekse PDF-er kan fortsatt feile mot strenge ISO-validerere (fonter, gjennomsiktighet, vedlegg). Bruk dedikert arkiveringsprogramvare hvis du trenger garantert samsvar.", + levelLabel: "PDF/A-samsvar", + levelHint: + 'PDF/A-1b slår av objektstrømmer for eldre arkivforventninger. PDF/A-2 og PDF/A-3 retter seg mot moderne PDF 1.7-arbeidsflyter; «U» angir antakelser om Unicode-tekst.', + download: "Konverter og last ned", + done: "PDF/A-merket fil lastet ned.", + convertFailed: "Kunne ikke konvertere denne PDF-en. Prøv en annen fil.", + level: { + "1b": "PDF/A-1b (del 1, samsvar B)", + "2b": "PDF/A-2b (del 2, samsvar B)", + "2u": "PDF/A-2u (del 2, samsvar U)", + "3b": "PDF/A-3b (del 3, samsvar B)", + "3u": "PDF/A-3u (del 3, samsvar U)", + }, + }, + ca: { + nav: "PDF a PDF/A", + dashboardTitle: "PDF a PDF/A", + dashboardDesc: + "Converteix PDF a PDF/A per a arxivatge i preservació a llarg termini, amb el nivell de conformitat ISO que triïs — tot al navegador.", + intro: + "Converteix documents PDF a PDF/A per a arxivatge i preservació a llarg termini.", + privacyNote: "El teu fitxer no surt mai d’aquest navegador.", + disclaimer: + "Això reconstrueix el PDF amb identificació PDF/A (XMP pdfaid, intenció de sortida sRGB i MarkInfo). Els PDF complexos poden seguir fallant validadors ISO estricts (tipus de lletra, transparència, adjunts). Fes servir programari d’arxivament dedicat si necessites conformitat garantida.", + levelLabel: "Conformitat PDF/A", + levelHint: + 'PDF/A-1b desactiva fluxos d’objecte per a expectatives d’arxivament més antigues. PDF/A-2 i PDF/A-3 apunten a fluxos de treball PDF 1.7 moderns; «U» declara suposicions de text Unicode.', + download: "Converteix i descarrega", + done: "Fitxer etiquetat com a PDF/A descarregat.", + convertFailed: "No s’ha pogut convertir aquest PDF. Prova un altre fitxer.", + level: { + "1b": "PDF/A-1b (part 1, conformitat B)", + "2b": "PDF/A-2b (part 2, conformitat B)", + "2u": "PDF/A-2u (part 2, conformitat U)", + "3b": "PDF/A-3b (part 3, conformitat B)", + "3u": "PDF/A-3u (part 3, conformitat U)", + }, + }, + pl: { + nav: "PDF do PDF/A", + dashboardTitle: "PDF do PDF/A", + dashboardDesc: + "Konwertuj pliki PDF do PDF/A w celu archiwizacji i długotrwałego przechowywania, wybierając poziom zgodności ISO — wszystko w przeglądarce.", + intro: + "Konwertuj dokumenty PDF do formatu PDF/A w celu archiwizacji i długotrwałego przechowywania.", + privacyNote: "Twój plik nigdy nie opuszcza tej przeglądarki.", + disclaimer: + "To odbudowuje plik PDF ze znacznikami PDF/A (XMP pdfaid, intencja wyjścia sRGB i MarkInfo). Złożone pliki PDF mogą nadal nie przejść ścisłych walidatorów ISO (czcionki, przezroczystość, załączniki). Użyj dedykowanego oprogramowania archiwizacyjnego, jeśli potrzebujesz gwarantowanej zgodności.", + levelLabel: "Zgodność PDF/A", + levelHint: + 'PDF/A-1b wyłącza strumienie obiektów pod starsze oczekiwania archiwalne. PDF/A-2 i PDF/A-3 są dla nowoczesnych przepływów PDF 1.7; „U” oznacza założenia dotyczące tekstu Unicode.', + download: "Konwertuj i pobierz", + done: "Oznaczony plik PDF/A został pobrany.", + convertFailed: "Nie udało się przekonwertować tego pliku PDF. Spróbuj innego pliku.", + level: { + "1b": "PDF/A-1b (część 1, zgodność B)", + "2b": "PDF/A-2b (część 2, zgodność B)", + "2u": "PDF/A-2u (część 2, zgodność U)", + "3b": "PDF/A-3b (część 3, zgodność B)", + "3u": "PDF/A-3u (część 3, zgodność U)", + }, + }, + cs: { + nav: "PDF do PDF/A", + dashboardTitle: "PDF do PDF/A", + dashboardDesc: + "Převeďte PDF do PDF/A pro archivaci a dlouhodobou ochranu, s úrovní shody ISO dle výběru — vše v prohlížeči.", + intro: "Převeďte dokumenty PDF do PDF/A pro archivaci a dlouhodobou ochranu.", + privacyNote: "Váš soubor tento prohlížeč nikdy neopustí.", + disclaimer: + "Tím se PDF znovu sestaví s identifikací PDF/A (XMP pdfaid, výstupní záměr sRGB a MarkInfo). Složitá PDF mohou stále selhat u přísných ISO validátorů (písma, průhlednost, přílohy). Pro zaručenou shodu použijte specializovaný archivační software.", + levelLabel: "Shoda PDF/A", + levelHint: + 'PDF/A-1b vypíná objektové streamy kvůli starším archivním očekáváním. PDF/A-2 a PDF/A-3 cílí na moderní pracovní postupy PDF 1.7; „U“ označuje předpoklady Unicode textu.', + download: "Převést a stáhnout", + done: "Soubor označený jako PDF/A byl stažen.", + convertFailed: "Tento PDF soubor se nepodařilo převést. Zkuste jiný soubor.", + level: { + "1b": "PDF/A-1b (část 1, shoda B)", + "2b": "PDF/A-2b (část 2, shoda B)", + "2u": "PDF/A-2u (část 2, shoda U)", + "3b": "PDF/A-3b (část 3, shoda B)", + "3u": "PDF/A-3u (část 3, shoda U)", + }, + }, + ru: { + nav: "PDF в PDF/A", + dashboardTitle: "PDF в PDF/A", + dashboardDesc: + "Конвертируйте PDF в PDF/A для архивного хранения и долговременного сохранения с выбранным уровнем соответствия ISO — всё в браузере.", + intro: + "Конвертируйте PDF-документы в PDF/A для архивирования и долговременного хранения.", + privacyNote: "Ваш файл никогда не покидает этот браузер.", + disclaimer: + "PDF пересобирается с маркировкой PDF/A (XMP pdfaid, выходной профиль sRGB и MarkInfo). Сложные PDF всё ещё могут не проходить строгие ISO-валидаторы (шрифты, прозрачность, вложения). Для гарантированного соответствия используйте специализированное архивное ПО.", + levelLabel: "Соответствие PDF/A", + levelHint: + 'PDF/A-1b отключает потоки объектов под старые архивные требования. PDF/A-2 и PDF/A-3 ориентированы на современные сценарии PDF 1.7; «U» задаёт предположения о Unicode-тексте.', + download: "Конвертировать и скачать", + done: "Файл с меткой PDF/A скачан.", + convertFailed: "Не удалось конвертировать этот PDF. Попробуйте другой файл.", + level: { + "1b": "PDF/A-1b (часть 1, соответствие B)", + "2b": "PDF/A-2b (часть 2, соответствие B)", + "2u": "PDF/A-2u (часть 2, соответствие U)", + "3b": "PDF/A-3b (часть 3, соответствие B)", + "3u": "PDF/A-3u (часть 3, соответствие U)", + }, + }, + uk: { + nav: "PDF у PDF/A", + dashboardTitle: "PDF у PDF/A", + dashboardDesc: + "Конвертуйте PDF у PDF/A для архівного зберігання та довготривалого збереження з обраним рівнем відповідності ISO — усе в браузері.", + intro: + "Конвертуйте PDF-документи у PDF/A для архівування та довготривалого збереження.", + privacyNote: "Ваш файл ніколи не залишає цей браузер.", + disclaimer: + "PDF збирається знову з маркуванням PDF/A (XMP pdfaid, вихідний намір sRGB та MarkInfo). Складні PDF можуть не пройти суворі ISO-валідатори (шрифти, прозорість, вкладення). Для гарантованої відповідності використовуйте спеціалізоване архівне ПЗ.", + levelLabel: "Відповідність PDF/A", + levelHint: + 'PDF/A-1b вимикає потоки об’єктів для старіших архівних очікувань. PDF/A-2 і PDF/A-3 — для сучасних сценаріїв PDF 1.7; «U» позначає припущення щодо Unicode-тексту.', + download: "Конвертувати й завантажити", + done: "Файл із позначкою PDF/A завантажено.", + convertFailed: "Не вдалося конвертувати цей PDF. Спробуйте інший файл.", + level: { + "1b": "PDF/A-1b (частина 1, відповідність B)", + "2b": "PDF/A-2b (частина 2, відповідність B)", + "2u": "PDF/A-2u (частина 2, відповідність U)", + "3b": "PDF/A-3b (частина 3, відповідність B)", + "3u": "PDF/A-3u (частина 3, відповідність U)", + }, + }, + el: { + nav: "PDF σε PDF/A", + dashboardTitle: "PDF σε PDF/A", + dashboardDesc: + "Μετατρέψτε PDF σε PDF/A για αρχειοθέτηση και μακροχρόνια διατήρηση, με επίπεδο συμμόρφωσης ISO της επιλογής σας — όλα στον browser σας.", + intro: + "Μετατρέψτε έγγραφα PDF σε PDF/A για αρχειοθέτηση και μακροχρόνια διατήρηση.", + privacyNote: "Το αρχείο σας δεν φεύγει ποτέ από αυτόν τον browser.", + disclaimer: + "Αυτό ξαναχτίζει το PDF με αναγνώριση PDF/A (XMP pdfaid, πρόθεση εξόδου sRGB και MarkInfo). Σύνθετα PDF μπορεί ακόμη να αποτυγχάνουν σε αυστηρούς επικυρωτές ISO (γραμματοσειρές, διαφάνεια, συνημμένα). Χρησιμοποιήστε εξειδικευμένο λογισμικό αρχειοθέτησης για εγγυημένη συμμόρφωση.", + levelLabel: "Συμμόρφωση PDF/A", + levelHint: + 'Το PDF/A-1b απενεργοποιεί ροές αντικειμένων για παλαιότερες προσδοκίες αρχειοθέτησης. PDF/A-2 και PDF/A-3 στοχεύουν σε σύγχρονες ροές PDF 1.7· το «U» δηλώνει υποθέσεις Unicode κειμένου.', + download: "Μετατροπή και λήψη", + done: "Λήφθηκε αρχείο με επισήμανση PDF/A.", + convertFailed: "Δεν ήταν δυνατή η μετατροπή αυτού του PDF. Δοκιμάστε άλλο αρχείο.", + level: { + "1b": "PDF/A-1b (μέρος 1, συμμόρφωση B)", + "2b": "PDF/A-2b (μέρος 2, συμμόρφωση B)", + "2u": "PDF/A-2u (μέρος 2, συμμόρφωση U)", + "3b": "PDF/A-3b (μέρος 3, συμμόρφωση B)", + "3u": "PDF/A-3u (μέρος 3, συμμόρφωση U)", + }, + }, + tr: { + nav: "PDF’den PDF/A’ya", + dashboardTitle: "PDF’den PDF/A’ya", + dashboardDesc: + "Arşivleme ve uzun süreli saklama için PDF’leri PDF/A’ya dönüştürün; istediğiniz ISO uyumluluk düzeyi — hepsi tarayıcınızda.", + intro: + "Arşivleme ve uzun süreli saklama için PDF belgelerini PDF/A formatına dönüştürün.", + privacyNote: "Dosyanız bu tarayıcıdan asla çıkmaz.", + disclaimer: + "Bu işlem, PDF’yi PDF/A tanımlaması (XMP pdfaid, sRGB çıktı niyeti ve MarkInfo) ile yeniden oluşturur. Karmaşık PDF’ler katı ISO doğrulayıcılarında hâlâ başarısız olabilir (yazı tipleri, şeffaflık, ekler). Garanti uyumluluk için özel arşiv yazılımı kullanın.", + levelLabel: "PDF/A uyumluluğu", + levelHint: + 'PDF/A-1b, eski arşiv beklentileri için nesne akışlarını kapatır. PDF/A-2 ve PDF/A-3 modern PDF 1.7 iş akışları içindir; «U» Unicode metin varsayımlarını belirtir.', + download: "Dönüştür ve indir", + done: "PDF/A etiketli dosya indirildi.", + convertFailed: "Bu PDF dönüştürülemedi. Başka bir dosya deneyin.", + level: { + "1b": "PDF/A-1b (bölüm 1, B uyumluluğu)", + "2b": "PDF/A-2b (bölüm 2, B uyumluluğu)", + "2u": "PDF/A-2u (bölüm 2, U uyumluluğu)", + "3b": "PDF/A-3b (bölüm 3, B uyumluluğu)", + "3u": "PDF/A-3u (bölüm 3, U uyumluluğu)", + }, + }, + id: { + nav: "PDF ke PDF/A", + dashboardTitle: "PDF ke PDF/A", + dashboardDesc: + "Ubah PDF ke PDF/A untuk arsip dan pelestarian jangka panjang, dengan tingkat kesesuaian ISO pilihan Anda — semuanya di browser.", + intro: + "Ubah dokumen PDF ke PDF/A untuk pengarsipan dan pelestarian jangka panjang.", + privacyNote: "Berkas Anda tidak pernah meninggalkan browser ini.", + disclaimer: + "Ini membangun ulang PDF dengan identifikasi PDF/A (XMP pdfaid, output intent sRGB, dan MarkInfo). PDF kompleks mungkin masih gagal pada validator ISO ketat (font, transparansi, lampiran). Gunakan perangkat lunak arsip khusus jika Anda membutuhkan kepatuhan terjamin.", + levelLabel: "Kesesuaian PDF/A", + levelHint: + 'PDF/A-1b menonaktifkan aliran objek untuk ekspektasi arsip lama. PDF/A-2 dan PDF/A-3 untuk alur kerja PDF 1.7 modern; «U» menyatakan asumsi teks Unicode.', + download: "Konversi & unduh", + done: "Berkas bertanda PDF/A telah diunduh.", + convertFailed: "Tidak dapat mengonversi PDF ini. Coba berkas lain.", + level: { + "1b": "PDF/A-1b (bagian 1, kesesuaian B)", + "2b": "PDF/A-2b (bagian 2, kesesuaian B)", + "2u": "PDF/A-2u (bagian 2, kesesuaian U)", + "3b": "PDF/A-3b (bagian 3, kesesuaian B)", + "3u": "PDF/A-3u (bagian 3, kesesuaian U)", + }, + }, + vi: { + nav: "PDF sang PDF/A", + dashboardTitle: "PDF sang PDF/A", + dashboardDesc: + "Chuyển PDF sang PDF/A để lưu trữ và bảo quản lâu dài, chọn mức tuân thủ ISO — hoàn toàn trong trình duyệt.", + intro: + "Chuyển tài liệu PDF sang PDF/A để lưu trữ và bảo quản lâu dài.", + privacyNote: "Tệp của bạn không bao giờ rời khỏi trình duyệt này.", + disclaimer: + "Thao tác này tái tạo PDF với định danh PDF/A (XMP pdfaid, output intent sRGB và MarkInfo). PDF phức tạp vẫn có thể không qua được bộ kiểm tra ISO nghiêm ngặt (phông chữ, độ trong suốt, tệp đính kèm). Hãy dùng phần mềm lưu trữ chuyên dụng nếu cần tuân thủ chắc chắn.", + levelLabel: "Tuân thủ PDF/A", + levelHint: + 'PDF/A-1b tắt luồng đối tượng cho kỳ vọng lưu trữ cũ hơn. PDF/A-2 và PDF/A-3 hướng tới quy trình PDF 1.7 hiện đại; «U» khai báo giả định văn bản Unicode.', + download: "Chuyển đổi & tải xuống", + done: "Đã tải xuống tệp gắn nhãn PDF/A.", + convertFailed: "Không thể chuyển đổi PDF này. Hãy thử tệp khác.", + level: { + "1b": "PDF/A-1b (phần 1, tuân thủ B)", + "2b": "PDF/A-2b (phần 2, tuân thủ B)", + "2u": "PDF/A-2u (phần 2, tuân thủ U)", + "3b": "PDF/A-3b (phần 3, tuân thủ B)", + "3u": "PDF/A-3u (phần 3, tuân thủ U)", + }, + }, + ms: { + nav: "PDF ke PDF/A", + dashboardTitle: "PDF ke PDF/A", + dashboardDesc: + "Tukar PDF ke PDF/A untuk arkib dan pemeliharaan jangka panjang, dengan tahap pematuhan ISO pilihan anda — semuanya dalam pelayar.", + intro: + "Tukar dokumen PDF ke PDF/A untuk arkib dan pemeliharaan jangka panjang.", + privacyNote: "Fail anda tidak pernah meninggalkan pelayar ini.", + disclaimer: + "Ini membina semula PDF dengan pengenalan PDF/A (XMP pdfaid, niat output sRGB dan MarkInfo). PDF kompleks mungkin masih gagal pada pengesah ISO yang ketat (fon, ketelusan, lampiran). Gunakan perisian arkib khusus jika anda memerlukan pematuhan terjamin.", + levelLabel: "Pematuhan PDF/A", + levelHint: + 'PDF/A-1b melumpuhkan strim objek untuk jangkaan arkib lama. PDF/A-2 dan PDF/A-3 untuk aliran kerja PDF 1.7 moden; «U» menyatakan andaian teks Unicode.', + download: "Tukar & muat turun", + done: "Fail berlabel PDF/A dimuat turun.", + convertFailed: "PDF ini tidak dapat ditukar. Cuba fail lain.", + level: { + "1b": "PDF/A-1b (bahagian 1, pematuhan B)", + "2b": "PDF/A-2b (bahagian 2, pematuhan B)", + "2u": "PDF/A-2u (bahagian 2, pematuhan U)", + "3b": "PDF/A-3b (bahagian 3, pematuhan B)", + "3u": "PDF/A-3u (bahagian 3, pematuhan U)", + }, + }, + ar: { + nav: "PDF إلى PDF/A", + dashboardTitle: "PDF إلى PDF/A", + dashboardDesc: + "حوّل ملفات PDF إلى PDF/A للأرشفة والحفظ طويل الأمد مع مستوى مطابقة ISO الذي تختاره — كله داخل المتصفح.", + intro: "حوّل مستندات PDF إلى PDF/A للأرشفة والحفظ طويل الأمد.", + privacyNote: "ملفك لا يغادر هذا المتصفح أبدًا.", + disclaimer: + "يعيد هذا بناء ملف PDF مع تعريف PDF/A (XMP pdfaid ونية الإخراج sRGB وMarkInfo). قد تفشل ملفات PDF المعقدة في مراجعي ISO الصارمين (الخطوط، الشفافية، المرفقات). استخدم برامج أرشفة متخصصة إذا كنت بحاجة إلى مطابقة مضمونة.", + levelLabel: "مطابقة PDF/A", + levelHint: + 'يعطّل PDF/A-1b تدفقات الكائنات لتوقعات الأرشفة الأقدم. يستهدف PDF/A-2 وPDF/A-3 سير عمل PDF 1.7 الحديثة؛ «U» يعلن عن افتراضات نص يونيكود.', + download: "تحويل وتنزيل", + done: "تم تنزيل الملف الموسوم بـ PDF/A.", + convertFailed: "تعذر تحويل ملف PDF هذا. جرّب ملفًا آخر.", + level: { + "1b": "PDF/A-1b (الجزء 1، مطابقة B)", + "2b": "PDF/A-2b (الجزء 2، مطابقة B)", + "2u": "PDF/A-2u (الجزء 2، مطابقة U)", + "3b": "PDF/A-3b (الجزء 3، مطابقة B)", + "3u": "PDF/A-3u (الجزء 3، مطابقة U)", + }, + }, + fa: { + nav: "PDF به PDF/A", + dashboardTitle: "PDF به PDF/A", + dashboardDesc: + "PDFها را برای بایگانی و حفظ بلندمدت به PDF/A تبدیل کنید و سطح انطباق ISO را انتخاب کنید — همه در مرورگر.", + intro: "اسناد PDF را برای بایگانی و حفظ بلندمدت به PDF/A تبدیل کنید.", + privacyNote: "فایل شما هرگز این مرورگر را ترک نمی‌کند.", + disclaimer: + "این کار PDF را با شناسایی PDF/A (XMP pdfaid، قصد خروجی sRGB و MarkInfo) بازسازی می‌کند. PDFهای پیچیده ممکن است در اعتبارسنج‌های سخت‌گیر ISO رد شوند (فونت‌ها، شفافیت، پیوست‌ها). برای انطباق تضمین‌شده از نرم‌افزار بایگانی تخصصی استفاده کنید.", + levelLabel: "انطباق PDF/A", + levelHint: + 'PDF/A-1b جریان‌های شیء را برای انتظارات بایگانی قدیمی‌تر غیرفعال می‌کند. PDF/A-2 و PDF/A-3 برای گردش کار مدرن PDF 1.7 هستند؛ «U» فرضیات متن یونیکد را اعلام می‌کند.', + download: "تبدیل و بارگیری", + done: "فایل برچسب‌دار PDF/A بارگیری شد.", + convertFailed: "تبدیل این PDF ممکن نشد. فایل دیگری امتحان کنید.", + level: { + "1b": "PDF/A-1b (بخش ۱، انطباق B)", + "2b": "PDF/A-2b (بخش ۲، انطباق B)", + "2u": "PDF/A-2u (بخش ۲، انطباق U)", + "3b": "PDF/A-3b (بخش ۳، انطباق B)", + "3u": "PDF/A-3u (بخش ۳، انطباق U)", + }, + }, + zh: { + nav: "PDF 转 PDF/A", + dashboardTitle: "PDF 转 PDF/A", + dashboardDesc: + "将 PDF 转为 PDF/A,用于归档与长期保存,并可选择 ISO 符合性级别 — 全部在浏览器中完成。", + intro: "将 PDF 文档转换为 PDF/A,用于归档与长期保存。", + privacyNote: "您的文件不会离开本浏览器。", + disclaimer: + "此操作会使用 PDF/A 标识(XMP pdfaid、sRGB 输出意图和 MarkInfo)重建 PDF。复杂的 PDF 仍可能无法通过严格的 ISO 校验(字体、透明度、附件)。若需保证符合性,请使用专业归档软件。", + levelLabel: "PDF/A 符合性", + levelHint: + "PDF/A-1b 关闭对象流以适配较旧的归档预期。PDF/A-2 与 PDF/A-3 面向现代 PDF 1.7 流程;“U”表示 Unicode 文本假设。", + download: "转换并下载", + done: "已下载带 PDF/A 标记的文件。", + convertFailed: "无法转换此 PDF。请尝试其他文件。", + level: { + "1b": "PDF/A-1b(第 1 部分,符合性 B)", + "2b": "PDF/A-2b(第 2 部分,符合性 B)", + "2u": "PDF/A-2u(第 2 部分,符合性 U)", + "3b": "PDF/A-3b(第 3 部分,符合性 B)", + "3u": "PDF/A-3u(第 3 部分,符合性 U)", + }, + }, + ja: { + nav: "PDFをPDF/Aへ", + dashboardTitle: "PDFをPDF/Aへ", + dashboardDesc: + "アーカイブと長期保存のため、PDFをPDF/Aに変換。ISO適合レベルを選択 — すべてブラウザ内で処理。", + intro: "アーカイブと長期保存のため、PDFドキュメントをPDF/Aに変換します。", + privacyNote: "ファイルはこのブラウザの外に送信されません。", + disclaimer: + "PDF/A識別情報(XMP pdfaid、sRGB出力インテント、MarkInfo)を付与してPDFを再構築します。複雑なPDFは、厳格なISOバリデータ(フォント、透明度、添付ファイル)で不合格になる場合があります。確実な適合が必要な場合は専用のアーカイブソフトを使用してください。", + levelLabel: "PDF/A適合", + levelHint: + "PDF/A-1bは旧来のアーカイブ想定のためオブジェクトストリームを無効化します。PDF/A-2およびPDF/A-3は現行のPDF 1.7ワークフロー向けです。「U」はUnicodeテキストの前提を示します。", + download: "変換してダウンロード", + done: "PDF/Aタグ付きファイルをダウンロードしました。", + convertFailed: "このPDFを変換できませんでした。別のファイルを試してください。", + level: { + "1b": "PDF/A-1b(パート1、適合B)", + "2b": "PDF/A-2b(パート2、適合B)", + "2u": "PDF/A-2u(パート2、適合U)", + "3b": "PDF/A-3b(パート3、適合B)", + "3u": "PDF/A-3u(パート3、適合U)", + }, + }, + ko: { + nav: "PDF를 PDF/A로", + dashboardTitle: "PDF를 PDF/A로", + dashboardDesc: + "보관 및 장기 보존을 위해 PDF를 PDF/A로 변환하고 ISO 적합 수준을 선택하세요 — 모두 브라우저에서 처리됩니다.", + intro: "보관 및 장기 보존을 위해 PDF 문서를 PDF/A로 변환합니다.", + privacyNote: "파일은 이 브라우저를 벗어나지 않습니다.", + disclaimer: + "PDF/A 식별(XMP pdfaid, sRGB 출력 인텐트, MarkInfo)을 추가하여 PDF를 다시 구성합니다. 복잡한 PDF는 엄격한 ISO 검증(글꼴, 투명도, 첨부 파일)에서 여전히 실패할 수 있습니다. 보장된 적합이 필요하면 전용 보관 소프트웨어를 사용하세요.", + levelLabel: "PDF/A 적합", + levelHint: + 'PDF/A-1b는 이전 보관 환경을 위해 객체 스트림을 사용하지 않습니다. PDF/A-2와 PDF/A-3은 최신 PDF 1.7 워크플로를 대상으로 합니다. "U"는 유니코드 텍스트 가정을 나타냅니다.', + download: "변환 후 다운로드", + done: "PDF/A로 태그된 파일이 다운로드되었습니다.", + convertFailed: "이 PDF를 변환할 수 없습니다. 다른 파일을 시도하세요.", + level: { + "1b": "PDF/A-1b(파트 1, 적합 B)", + "2b": "PDF/A-2b(파트 2, 적합 B)", + "2u": "PDF/A-2u(파트 2, 적합 U)", + "3b": "PDF/A-3b(파트 3, 적합 B)", + "3u": "PDF/A-3u(파트 3, 적합 U)", + }, + }, + af: { + nav: "PDF na PDF/A", + dashboardTitle: "PDF na PDF/A", + dashboardDesc: + "Skakel PDF’s om na PDF/A vir argivering en langtermynbewaring, met die ISO-voldoeningsvlak van jou keuse — alles in jou blaaier.", + intro: "Skakel PDF-dokumente om na PDF/A vir argivering en langtermynbewaring.", + privacyNote: "Jou lêer verlaat nooit hierdie blaaier nie.", + disclaimer: + "Hierdie bou die PDF weer op met PDF/A-identifikasie (XMP pdfaid, sRGB-uitsetbedoeling en MarkInfo). Komplekse PDF’s kan steeds misluk by streng ISO-valideerders (lettertipes, deursigtigheid, aanhegsels). Gebruik toegewyde argiveringsprogrammatuur as jy gewaarborgde voldoening benodig.", + levelLabel: "PDF/A-voldoening", + levelHint: + 'PDF/A-1b skakel voorwerpstrome af vir ouer argiveringsverwagtinge. PDF/A-2 en PDF/A-3 teiken moderne PDF 1.7-werksvloei; "U" verklaar Unicode-teksaanames.', + download: "Skakel om en laai af", + done: "PDF/A-gemerkte lêer afgelaai.", + convertFailed: "Kon nie hierdie PDF omskakel nie. Probeer ’n ander lêer.", + level: { + "1b": "PDF/A-1b (deel 1, voldoening B)", + "2b": "PDF/A-2b (deel 2, voldoening B)", + "2u": "PDF/A-2u (deel 2, voldoening U)", + "3b": "PDF/A-3b (deel 3, voldoening B)", + "3u": "PDF/A-3u (deel 3, voldoening U)", + }, + }, +}; + +const LOCALES = [ + "en", + "fr", + "es", + "ar", + "ca", + "zh", + "cs", + "el", + "de", + "da", + "af", + "id", + "fa", + "ru", + "it", + "ja", + "ko", + "ms", + "nb", + "nl", + "sv", + "pl", + "tr", + "pt", + "pt-BR", + "vi", + "uk", +]; + +for (const locale of LOCALES) { + const p = path.join(messagesDir, `${locale}.json`); + const j = JSON.parse(fs.readFileSync(p, "utf8")); + const pc = j.PdfCompressor; + const pw = j.PdfWatermark; + if (!pc || !pw) { + throw new Error(`${locale}.json: missing PdfCompressor or PdfWatermark`); + } + const sp = STRINGS[locale] ?? STRINGS.en; + + j.Navigation.pdfToPdfA = sp.nav; + j.Dashboard.tools.pdfToPdfA = { + title: sp.dashboardTitle, + description: sp.dashboardDesc, + }; + + j.PdfToPdfA = { + intro: sp.intro, + privacyNote: sp.privacyNote, + disclaimer: sp.disclaimer, + chooseFile: pc.chooseFile, + reset: pc.reset, + pageCount: pc.pageCount, + levelLabel: sp.levelLabel, + levelHint: sp.levelHint, + download: sp.download, + working: pw.working, + done: sp.done, + invalidPdf: pc.invalidPdf, + encryptedPdf: pc.encryptedPdf, + convertFailed: sp.convertFailed, + level: { ...sp.level }, + }; + + fs.writeFileSync(p, `${JSON.stringify(j, null, 2)}\n`); +} + +console.log(`Updated PdfToPdfA strings in ${LOCALES.length} locale files.`); diff --git a/apps/desktop-ui/scripts/apply-snippet-dashboard-i18n.py b/apps/desktop-ui/scripts/apply-snippet-dashboard-i18n.py new file mode 100644 index 00000000..2527fd7a --- /dev/null +++ b/apps/desktop-ui/scripts/apply-snippet-dashboard-i18n.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +"""Patch SnippetManager + Dashboard.analytics codeSnippets strings in all locale JSON files.""" + +import json +from pathlib import Path + +MESSAGES = Path(__file__).resolve().parent.parent / "messages" + +# Subtitle: browser storage + optional account sync + same feature list as EN. +# Toasts: server load/sync errors. +# codeSnippets: dashboard metric label. +LOCALE_PATCHES: dict[str, dict[str, str]] = { + "en": { + "subtitle": "Store snippets in your browser, or sign in to sync them with your account. Switch between view and edit, copy in one click, pick a language or use auto-detect, and format JSON, SQL, or Monaco-supported code.", + "toastLoadFailed": "Could not load snippets from the server.", + "toastSyncFailed": "Could not sync with the server.", + "codeSnippets": "Code snippets", + }, + "fr": { + "subtitle": "Enregistrez des extraits dans votre navigateur ou connectez-vous pour les synchroniser avec votre compte. Passez de la consultation à l’édition, copiez en un clic, choisissez un langage ou la détection automatique, et formatez JSON, SQL ou le code pris en charge par Monaco.", + "toastLoadFailed": "Impossible de charger les extraits depuis le serveur.", + "toastSyncFailed": "Impossible de synchroniser avec le serveur.", + "codeSnippets": "Extraits de code", + }, + "es": { + "subtitle": "Guarda fragmentos en tu navegador o inicia sesión para sincronizarlos con tu cuenta. Cambia entre ver y editar, copia con un clic, elige idioma o detección automática y formatea JSON, SQL o código compatible con Monaco.", + "toastLoadFailed": "No se pudieron cargar los fragmentos desde el servidor.", + "toastSyncFailed": "No se pudo sincronizar con el servidor.", + "codeSnippets": "Fragmentos de código", + }, + "de": { + "subtitle": "Speichern Sie Snippets im Browser oder melden Sie sich an, um sie mit Ihrem Konto zu synchronisieren. Wechseln Sie zwischen Ansicht und Bearbeitung, kopieren Sie mit einem Klick, wählen Sie eine Sprache oder Auto-Erkennung und formatieren Sie JSON, SQL oder von Monaco unterstützten Code.", + "toastLoadFailed": "Snippets konnten nicht vom Server geladen werden.", + "toastSyncFailed": "Synchronisation mit dem Server fehlgeschlagen.", + "codeSnippets": "Code-Snippets", + }, + "it": { + "subtitle": "Conserva gli snippet nel browser o accedi per sincronizzarli con il tuo account. Passa da visualizzazione a modifica, copia con un clic, scegli la lingua o il rilevamento automatico e formatta JSON, SQL o codice supportato da Monaco.", + "toastLoadFailed": "Impossibile caricare gli snippet dal server.", + "toastSyncFailed": "Impossibile sincronizzare con il server.", + "codeSnippets": "Snippet di codice", + }, + "pt": { + "subtitle": "Guarde excertos no navegador ou inicie sessão para os sincronizar com a sua conta. Alterne entre ver e editar, copie com um clique, escolha a linguagem ou deteção automática e formate JSON, SQL ou código suportado pelo Monaco.", + "toastLoadFailed": "Não foi possível carregar os excertos do servidor.", + "toastSyncFailed": "Não foi possível sincronizar com o servidor.", + "codeSnippets": "Excertos de código", + }, + "pt-BR": { + "subtitle": "Armazene trechos no navegador ou entre para sincronizá-los com sua conta. Alterne entre ver e editar, copie com um clique, escolha a linguagem ou detecção automática e formate JSON, SQL ou código suportado pelo Monaco.", + "toastLoadFailed": "Não foi possível carregar os trechos do servidor.", + "toastSyncFailed": "Não foi possível sincronizar com o servidor.", + "codeSnippets": "Trechos de código", + }, + "nl": { + "subtitle": "Bewaar snippets in je browser of log in om ze met je account te synchroniseren. Wissel tussen bekijken en bewerken, kopieer met één klik, kies een taal of automatische detectie en formatteer JSON, SQL of door Monaco ondersteunde code.", + "toastLoadFailed": "Snippets konden niet van de server worden geladen.", + "toastSyncFailed": "Synchroniseren met de server is mislukt.", + "codeSnippets": "Codefragmenten", + }, + "pl": { + "subtitle": "Przechowuj fragmenty kodu w przeglądarce lub zaloguj się, aby synchronizować je z kontem. Przełączaj widok i edycję, kopiuj jednym kliknięciem, wybierz język lub autowykrywanie i formatuj JSON, SQL lub kod obsługiwany przez Monaco.", + "toastLoadFailed": "Nie udało się wczytać fragmentów z serwera.", + "toastSyncFailed": "Nie udało się zsynchronizować z serwerem.", + "codeSnippets": "Fragmenty kodu", + }, + "ru": { + "subtitle": "Храните сниппеты в браузере или войдите, чтобы синхронизировать их с аккаунтом. Переключайтесь между просмотром и правкой, копируйте в один клик, выбирайте язык или автоопределение и форматируйте JSON, SQL или код, поддерживаемый Monaco.", + "toastLoadFailed": "Не удалось загрузить сниппеты с сервера.", + "toastSyncFailed": "Не удалось синхронизировать с сервером.", + "codeSnippets": "Фрагменты кода", + }, + "uk": { + "subtitle": "Зберігайте фрагменти коду в браузері або увійдіть, щоб синхронізувати їх з обліковим записом. Перемикайте перегляд і редагування, копіюйте одним кліком, обирайте мову або авто-визначення та форматуйте JSON, SQL або код, який підтримує Monaco.", + "toastLoadFailed": "Не вдалося завантажити фрагменти з сервера.", + "toastSyncFailed": "Не вдалося синхронізувати з сервером.", + "codeSnippets": "Фрагменти коду", + }, + "tr": { + "subtitle": "Parçacıkları tarayıcıda saklayın veya hesabınızla eşitlemek için oturum açın. Görüntüleme ve düzenleme arasında geçiş yapın, tek tıkla kopyalayın, dil seçin veya otomatik algılayın ve JSON, SQL veya Monaco destekli kodu biçimlendirin.", + "toastLoadFailed": "Parçacıklar sunucudan yüklenemedi.", + "toastSyncFailed": "Sunucu ile eşitleme başarısız oldu.", + "codeSnippets": "Kod parçacıkları", + }, + "ar": { + "subtitle": "خزّن المقاطع في المتصفح أو سجّل الدخول لمزامنتها مع حسابك. بدّل بين العرض والتحرير، انسخ بنقرة واحدة، اختر لغة أو كشفًا تلقائيًا، ونسّق JSON أو SQL أو أكواد يدعمها Monaco.", + "toastLoadFailed": "تعذّر تحميل المقاطع من الخادم.", + "toastSyncFailed": "تعذّرت المزامنة مع الخادم.", + "codeSnippets": "مقاطع برمجية", + }, + "fa": { + "subtitle": "قطعه‌کدها را در مرورگر نگه دارید یا برای همگام‌سازی با حساب وارد شوید. بین مشاهده و ویرایش جابه‌جا شوید، با یک کلیک کپی کنید، زبان را انتخاب یا خودکار تشخیص دهید و JSON، SQL یا کد پشتیبانی‌شده توسط Monaco را قالب‌بندی کنید.", + "toastLoadFailed": "بارگیری قطعه‌کدها از سرور انجام نشد.", + "toastSyncFailed": "همگام‌سازی با سرور انجام نشد.", + "codeSnippets": "قطعه‌کدهای برنامه", + }, + "zh": { + "subtitle": "在浏览器中保存代码片段,或登录以同步到账户。在查看与编辑间切换、一键复制、选择语言或使用自动检测,并格式化 JSON、SQL 或 Monaco 支持的代码。", + "toastLoadFailed": "无法从服务器加载代码片段。", + "toastSyncFailed": "无法与服务器同步。", + "codeSnippets": "代码片段", + }, + "ja": { + "subtitle": "ブラウザにスニペットを保存するか、サインインしてアカウントと同期します。表示と編集の切り替え、ワンクリックコピー、言語の選択または自動検出、JSON・SQL・Monaco 対応コードの整形ができます。", + "toastLoadFailed": "サーバーからスニペットを読み込めませんでした。", + "toastSyncFailed": "サーバーと同期できませんでした。", + "codeSnippets": "コードスニペット", + }, + "ko": { + "subtitle": "브라우저에 스니펫을 저장하거나 로그인해 계정과 동기화하세요. 보기와 편집 전환, 한 번의 클릭으로 복사, 언어 선택 또는 자동 감지, JSON·SQL·Monaco 지원 코드 포맷을 사용할 수 있습니다.", + "toastLoadFailed": "서버에서 스니펫을 불러오지 못했습니다.", + "toastSyncFailed": "서버와 동기화하지 못했습니다.", + "codeSnippets": "코드 스니펫", + }, + "vi": { + "subtitle": "Lưu đoạn mã trong trình duyệt hoặc đăng nhập để đồng bộ với tài khoản. Chuyển giữa xem và sửa, sao chép một cú nhấp, chọn ngôn ngữ hoặc tự động nhận diện, và định dạng JSON, SQL hoặc mã được Monaco hỗ trợ.", + "toastLoadFailed": "Không thể tải đoạn mã từ máy chủ.", + "toastSyncFailed": "Không thể đồng bộ với máy chủ.", + "codeSnippets": "Đoạn mã", + }, + "id": { + "subtitle": "Simpan cuplikan di browser atau masuk untuk menyinkronkannya dengan akun Anda. Beralih antara lihat dan sunting, salin sekali klik, pilih bahasa atau deteksi otomatis, dan format JSON, SQL, atau kode yang didukung Monaco.", + "toastLoadFailed": "Tidak dapat memuat cuplikan dari server.", + "toastSyncFailed": "Tidak dapat menyinkronkan dengan server.", + "codeSnippets": "Cuplikan kode", + }, + "ms": { + "subtitle": "Simpan petikan dalam pelayar atau log masuk untuk menyelaraskannya dengan akaun anda. Tukar antara lihat dan sunting, salin satu klik, pilih bahasa atau auto kesan, dan format JSON, SQL atau kod yang disokong Monaco.", + "toastLoadFailed": "Tidak dapat memuatkan petikan dari pelayan.", + "toastSyncFailed": "Tidak dapat menyelaraskan dengan pelayan.", + "codeSnippets": "Petikan kod", + }, + "cs": { + "subtitle": "Ukládejte úryvky v prohlížeči nebo se přihlaste a synchronizujte je s účtem. Přepínejte zobrazení a úpravy, kopírujte jedním kliknutím, volte jazyk nebo automatickou detekci a formátujte JSON, SQL nebo kód podporovaný Monacem.", + "toastLoadFailed": "Úryvky se nepodařilo načíst ze serveru.", + "toastSyncFailed": "Synchronizace se serverem se nezdařila.", + "codeSnippets": "Úryvky kódu", + }, + "el": { + "subtitle": "Αποθηκεύστε αποσπάσματα στον browser ή συνδεθείτε για συγχρονισμό με τον λογαριασμό σας. Εναλλαγή προβολής και επεξεργασίας, αντιγραφή με ένα κλικ, επιλογή γλώσσας ή αυτόματη ανίχνευση και μορφοποίηση JSON, SQL ή κώδικα που υποστηρίζει το Monaco.", + "toastLoadFailed": "Δεν ήταν δυνατή η φόρτωση αποσπασμάτων από τον διακομιστή.", + "toastSyncFailed": "Δεν ήταν δυνατός ο συγχρονισμός με τον διακομιστή.", + "codeSnippets": "Αποσπάσματα κώδικα", + }, + "da": { + "subtitle": "Gem snippets i browseren eller log ind for at synkronisere med din konto. Skift mellem visning og redigering, kopiér med ét klik, vælg sprog eller autodetektér, og formatér JSON, SQL eller Monaco-understøttet kode.", + "toastLoadFailed": "Kunne ikke indlæse snippets fra serveren.", + "toastSyncFailed": "Kunne ikke synkronisere med serveren.", + "codeSnippets": "Kode-snippets", + }, + "nb": { + "subtitle": "Lagre utdrag i nettleseren eller logg inn for å synkronisere med kontoen din. Bytt mellom visning og redigering, kopier med ett klikk, velg språk eller automatisk gjenkjenning, og formater JSON, SQL eller Monaco-støttet kode.", + "toastLoadFailed": "Kunne ikke laste utdrag fra serveren.", + "toastSyncFailed": "Kunne ikke synkronisere med serveren.", + "codeSnippets": "Kodeutdrag", + }, + "sv": { + "subtitle": "Spara utdrag i webbläsaren eller logga in för att synka med ditt konto. Växla mellan visning och redigering, kopiera med ett klick, välj språk eller autoupptäckt, och formatera JSON, SQL eller Monaco-stödd kod.", + "toastLoadFailed": "Kunde inte läsa in utdrag från servern.", + "toastSyncFailed": "Kunde inte synkronisera med servern.", + "codeSnippets": "Kodutdrag", + }, + "af": { + "subtitle": "Bewaar snippette in jou blaaier of meld aan om dit met jou rekening te sinkroniseer. Wissel tussen bekyk en wysig, kopieer met een klik, kies taal of outo-opsporing, en formatteer JSON, SQL of Monaco-ondersteunde kode.", + "toastLoadFailed": "Kon nie snippette van die bediener laai nie.", + "toastSyncFailed": "Kon nie met die bediener sinkroniseer nie.", + "codeSnippets": "Kodesnippette", + }, + "ca": { + "subtitle": "Emmagatzema fragments al navegador o inicia sessió per sincronitzar-los amb el teu compte. Canvia entre veure i editar, copia amb un clic, tria idioma o detecció automàtica i formata JSON, SQL o codi compatible amb Monaco.", + "toastLoadFailed": "No s'han pogut carregar els fragments des del servidor.", + "toastSyncFailed": "No s'ha pogut sincronitzar amb el servidor.", + "codeSnippets": "Fragments de codi", + }, +} + + +def main() -> None: + for path in sorted(MESSAGES.glob("*.json")): + stem = path.stem + patch = LOCALE_PATCHES.get(stem, LOCALE_PATCHES["en"]) + data = json.loads(path.read_text(encoding="utf-8")) + + dash = data.setdefault("Dashboard", {}).setdefault("analytics", {}) + dash["codeSnippets"] = patch["codeSnippets"] + + sm = data.setdefault("SnippetManager", {}) + sm["subtitle"] = patch["subtitle"] + sm["toastLoadFailed"] = patch["toastLoadFailed"] + sm["toastSyncFailed"] = patch["toastSyncFailed"] + + path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(path.name) + + +if __name__ == "__main__": + main() diff --git a/apps/desktop-ui/scripts/build-tauri.mjs b/apps/desktop-ui/scripts/build-tauri.mjs new file mode 100644 index 00000000..9e991686 --- /dev/null +++ b/apps/desktop-ui/scripts/build-tauri.mjs @@ -0,0 +1,69 @@ +#!/usr/bin/env node +/** + * Tauri desktop static-export build (apps/desktop-ui — desktop-only frontend, + * output: 'export' is hard-coded in next.config.ts). + * + * Requires Node >= 20.19 (older 20.x fails the export with a misleading + * `util.markAsUncloneable` error). + * + * Usage: node scripts/build-tauri.mjs (from apps/desktop-ui or repo root) + * Output: apps/desktop-ui/out/ + */ +import { execSync, spawnSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const uiRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +// --- Node version gate ----------------------------------------------------- +const [major, minor] = process.versions.node.split(".").map(Number); +if (major < 20 || (major === 20 && minor < 19)) { + console.error( + `build-tauri: Node ${process.versions.node} is too old — the static export fails on it ` + + `(red-herring 'util.markAsUncloneable' error). Use Node >= 20.19, e.g. 'nvm use 20.19.5'.` + ); + process.exit(1); +} + +// Activation is mandatory: a desktop build without real Firebase credentials +// produces an app nobody can activate. Hard-fail rather than ship it. +// Next loads .env files itself; this check just guards CI/misconfigured shells. +const hasFirebaseKey = + !!process.env.NEXT_PUBLIC_FIREBASE_API_KEY || + [".env", ".env.local", ".env.production"].some((f) => { + try { + return fs.readFileSync(path.join(uiRoot, f), "utf8").includes("NEXT_PUBLIC_FIREBASE_API_KEY="); + } catch { + return false; + } + }); +if (!hasFirebaseKey) { + console.error( + "build-tauri: NEXT_PUBLIC_FIREBASE_API_KEY is not set (env or .env file). Desktop " + + "activation requires real Firebase credentials baked into the bundle — export the " + + "NEXT_PUBLIC_FIREBASE_* vars before building." + ); + process.exit(1); +} + +const env = { + ...process.env, + TAURI_BUILD: "1", + NEXT_PUBLIC_TAURI: "1", +}; + +const result = spawnSync("npx", ["next", "build"], { + cwd: uiRoot, + env, + stdio: "inherit", +}); + +if (result.status !== 0) { + console.error("build-tauri: next build failed."); + process.exit(result.status ?? 1); +} + +const outDir = path.join(uiRoot, "out"); +const pageCount = execSync(`find ${JSON.stringify(outDir)} -name '*.html' | wc -l`).toString().trim(); +console.log(`build-tauri: OK — ${pageCount} HTML pages in apps/desktop-ui/out/`); diff --git a/apps/desktop-ui/scripts/csv-excel-json-i18n-data.json b/apps/desktop-ui/scripts/csv-excel-json-i18n-data.json new file mode 100644 index 00000000..1009b4ba --- /dev/null +++ b/apps/desktop-ui/scripts/csv-excel-json-i18n-data.json @@ -0,0 +1,1016 @@ +{ + "fr": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Convertir des fichiers CSV ou Excel en JSON et exporter vers CSV ou XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Importez un fichier CSV ou Excel, modifiez le JSON, puis téléchargez en CSV ou XLSX. Données tabulaires uniquement ; les dates Excel deviennent des horodatages ISO dans le JSON.", + "dropTitle": "Déposez un fichier CSV ou Excel", + "dropHint": "La première feuille est utilisée pour .xlsx / .xls. Le CSV doit comporter une ligne d’en-têtes.", + "chooseFile": "Choisir un fichier", + "jsonLabel": "JSON (tableau d’objets)", + "jsonPlaceholder": "Collez un tableau JSON d’objets ou importez CSV / Excel…", + "loadSample": "Charger un exemple", + "actions": { + "format": "Formater le JSON", + "clear": "Effacer", + "copy": "Copier", + "copied": "Copié", + "downloadCsv": "Télécharger CSV", + "downloadXlsx": "Télécharger Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Données", + "toastLoadedCsv": "CSV chargé dans l’éditeur", + "toastLoadedExcel": "Fichier Excel chargé dans l’éditeur", + "toastFormatted": "JSON formaté", + "toastExportedCsv": "CSV téléchargé", + "toastExportedXlsx": "Fichier Excel téléchargé", + "toastCopied": "Copié dans le presse-papiers", + "footnote": "Tout s’exécute dans votre navigateur. Le JSON doit être un tableau non vide d’objets. Les valeurs imbriquées sont sérialisées en JSON dans les cellules CSV.", + "errors": { + "unsupportedFile": "Utilisez un fichier .csv, .xlsx ou .xls.", + "parseFailed": "Impossible d’analyser ce fichier.", + "invalidJson": "JSON invalide", + "exportFailed": "Échec de l’export", + "copyFailed": "Impossible de copier" + } + } + }, + "de": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "CSV- oder Excel-Dateien in JSON umwandeln und nach CSV oder XLSX exportieren.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV- oder Excel-Datei hochladen, JSON bearbeiten, dann als CSV oder XLSX herunterladen. Nur Tabellendaten; Excel-Datumswerte werden im JSON als ISO-Zeitstempel ausgegeben.", + "dropTitle": "CSV- oder Excel-Datei hier ablegen", + "dropHint": "Für .xlsx / .xls wird das erste Arbeitsblatt verwendet. CSV benötigt eine Kopfzeile.", + "chooseFile": "Datei wählen", + "jsonLabel": "JSON (Array von Objekten)", + "jsonPlaceholder": "JSON-Array einfügen oder CSV / Excel importieren…", + "loadSample": "Beispiel laden", + "actions": { + "format": "JSON formatieren", + "clear": "Leeren", + "copy": "Kopieren", + "copied": "Kopiert", + "downloadCsv": "CSV herunterladen", + "downloadXlsx": "Excel herunterladen" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Daten", + "toastLoadedCsv": "CSV in den Editor geladen", + "toastLoadedExcel": "Excel-Datei in den Editor geladen", + "toastFormatted": "JSON formatiert", + "toastExportedCsv": "CSV heruntergeladen", + "toastExportedXlsx": "Excel-Datei heruntergeladen", + "toastCopied": "In die Zwischenablage kopiert", + "footnote": "Alles läuft im Browser. JSON muss ein nicht leeres Array von Objekten sein. Verschachtelte Werte werden in CSV-Zellen als JSON-String gespeichert.", + "errors": { + "unsupportedFile": "Bitte .csv-, .xlsx- oder .xls-Datei verwenden.", + "parseFailed": "Diese Datei konnte nicht gelesen werden.", + "invalidJson": "Ungültiges JSON", + "exportFailed": "Export fehlgeschlagen", + "copyFailed": "Kopieren fehlgeschlagen" + } + } + }, + "es": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Convierte archivos CSV o Excel a JSON y exporta de vuelta a CSV o XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Sube un CSV o Excel, edita el JSON y descarga CSV o XLSX. Solo datos tabulares; las fechas de Excel pasan a marcas de tiempo ISO en JSON.", + "dropTitle": "Suelta un CSV o Excel", + "dropHint": "Se usa la primera hoja en .xlsx / .xls. El CSV debe tener fila de cabecera.", + "chooseFile": "Elegir archivo", + "jsonLabel": "JSON (matriz de objetos)", + "jsonPlaceholder": "Pega un array JSON de objetos o importa CSV / Excel…", + "loadSample": "Cargar ejemplo", + "actions": { + "format": "Formatear JSON", + "clear": "Borrar", + "copy": "Copiar", + "copied": "Copiado", + "downloadCsv": "Descargar CSV", + "downloadXlsx": "Descargar Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Datos", + "toastLoadedCsv": "CSV cargado en el editor", + "toastLoadedExcel": "Excel cargado en el editor", + "toastFormatted": "JSON formateado", + "toastExportedCsv": "CSV descargado", + "toastExportedXlsx": "Excel descargado", + "toastCopied": "Copiado al portapapeles", + "footnote": "Todo se ejecuta en tu navegador. El JSON debe ser un array no vacío de objetos. Los valores anidados se serializan como JSON en las celdas CSV.", + "errors": { + "unsupportedFile": "Usa un archivo .csv, .xlsx o .xls.", + "parseFailed": "No se pudo analizar el archivo.", + "invalidJson": "JSON no válido", + "exportFailed": "Error al exportar", + "copyFailed": "No se pudo copiar" + } + } + }, + "it": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Converti file CSV o Excel in JSON ed esporta in CSV o XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Carica CSV o Excel, modifica il JSON, poi scarica CSV o XLSX. Solo dati tabulari; le date Excel diventano timestamp ISO nel JSON.", + "dropTitle": "Trascina un CSV o Excel", + "dropHint": "Per .xlsx / .xls si usa il primo foglio. Il CSV deve avere una riga di intestazione.", + "chooseFile": "Scegli file", + "jsonLabel": "JSON (array di oggetti)", + "jsonPlaceholder": "Incolla un array JSON di oggetti o importa CSV / Excel…", + "loadSample": "Carica esempio", + "actions": { + "format": "Formatta JSON", + "clear": "Cancella", + "copy": "Copia", + "copied": "Copiato", + "downloadCsv": "Scarica CSV", + "downloadXlsx": "Scarica Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Dati", + "toastLoadedCsv": "CSV caricato nell’editor", + "toastLoadedExcel": "File Excel caricato nell’editor", + "toastFormatted": "JSON formattato", + "toastExportedCsv": "CSV scaricato", + "toastExportedXlsx": "Excel scaricato", + "toastCopied": "Copiato negli appunti", + "footnote": "Tutto nel browser. Il JSON deve essere un array non vuoto di oggetti. I valori annidati sono serializzati in JSON nelle celle CSV.", + "errors": { + "unsupportedFile": "Usa un file .csv, .xlsx o .xls.", + "parseFailed": "Impossibile analizzare il file.", + "invalidJson": "JSON non valido", + "exportFailed": "Esportazione non riuscita", + "copyFailed": "Impossibile copiare" + } + } + }, + "pt": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Converta ficheiros CSV ou Excel para JSON e exporte para CSV ou XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Carregue CSV ou Excel, edite o JSON e descarregue CSV ou XLSX. Apenas dados tabulares; as datas do Excel passam a ISO no JSON.", + "dropTitle": "Largue um ficheiro CSV ou Excel", + "dropHint": "Para .xlsx / .xls usa-se a primeira folha. O CSV precisa de linha de cabeçalho.", + "chooseFile": "Escolher ficheiro", + "jsonLabel": "JSON (array de objetos)", + "jsonPlaceholder": "Cole um array JSON de objetos ou importe CSV / Excel…", + "loadSample": "Carregar exemplo", + "actions": { + "format": "Formatar JSON", + "clear": "Limpar", + "copy": "Copiar", + "copied": "Copiado", + "downloadCsv": "Descarregar CSV", + "downloadXlsx": "Descarregar Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Dados", + "toastLoadedCsv": "CSV carregado no editor", + "toastLoadedExcel": "Excel carregado no editor", + "toastFormatted": "JSON formatado", + "toastExportedCsv": "CSV descarregado", + "toastExportedXlsx": "Excel descarregado", + "toastCopied": "Copiado para a área de transferência", + "footnote": "Tudo corre no navegador. O JSON deve ser um array não vazio de objetos. Valores aninhados são serializados em JSON nas células CSV.", + "errors": { + "unsupportedFile": "Use um ficheiro .csv, .xlsx ou .xls.", + "parseFailed": "Não foi possível analisar o ficheiro.", + "invalidJson": "JSON inválido", + "exportFailed": "Falha na exportação", + "copyFailed": "Não foi possível copiar" + } + } + }, + "pt-BR": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Converta arquivos CSV ou Excel em JSON e exporte de volta para CSV ou XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Envie CSV ou Excel, edite o JSON e baixe CSV ou XLSX. Apenas dados tabulares; datas do Excel viram ISO no JSON.", + "dropTitle": "Solte um arquivo CSV ou Excel", + "dropHint": "Para .xlsx / .xls usa-se a primeira planilha. O CSV precisa de linha de cabeçalho.", + "chooseFile": "Escolher arquivo", + "jsonLabel": "JSON (array de objetos)", + "jsonPlaceholder": "Cole um array JSON de objetos ou importe CSV / Excel…", + "loadSample": "Carregar exemplo", + "actions": { + "format": "Formatar JSON", + "clear": "Limpar", + "copy": "Copiar", + "copied": "Copiado", + "downloadCsv": "Baixar CSV", + "downloadXlsx": "Baixar Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Dados", + "toastLoadedCsv": "CSV carregado no editor", + "toastLoadedExcel": "Excel carregado no editor", + "toastFormatted": "JSON formatado", + "toastExportedCsv": "CSV baixado", + "toastExportedXlsx": "Excel baixado", + "toastCopied": "Copiado para a área de transferência", + "footnote": "Tudo roda no navegador. O JSON deve ser um array não vazio de objetos. Valores aninhados viram JSON nas células CSV.", + "errors": { + "unsupportedFile": "Use um arquivo .csv, .xlsx ou .xls.", + "parseFailed": "Não foi possível analisar o arquivo.", + "invalidJson": "JSON inválido", + "exportFailed": "Falha na exportação", + "copyFailed": "Não foi possível copiar" + } + } + }, + "nl": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Zet CSV- of Excel-bestanden om naar JSON en exporteer terug naar CSV of XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Upload CSV of Excel, bewerk JSON en download CSV of XLSX. Alleen tabelgegevens; Excel-datums worden ISO-tijdstempels in JSON.", + "dropTitle": "Sleep een CSV- of Excel-bestand hierheen", + "dropHint": "Voor .xlsx / .xls wordt het eerste werkblad gebruikt. CSV moet een kopregel hebben.", + "chooseFile": "Bestand kiezen", + "jsonLabel": "JSON (array van objecten)", + "jsonPlaceholder": "Plak een JSON-array van objecten of importeer CSV / Excel…", + "loadSample": "Voorbeeld laden", + "actions": { + "format": "JSON formatteren", + "clear": "Wissen", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "downloadCsv": "CSV downloaden", + "downloadXlsx": "Excel downloaden" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Gegevens", + "toastLoadedCsv": "CSV geladen in editor", + "toastLoadedExcel": "Excel-bestand geladen in editor", + "toastFormatted": "JSON geformatteerd", + "toastExportedCsv": "CSV gedownload", + "toastExportedXlsx": "Excel gedownload", + "toastCopied": "Gekopieerd naar klembord", + "footnote": "Alles draait in je browser. JSON moet een niet-lege array van objecten zijn. Geneste waarden worden als JSON in CSV-cellen gezet.", + "errors": { + "unsupportedFile": "Gebruik een .csv-, .xlsx- of .xls-bestand.", + "parseFailed": "Dit bestand kon niet worden gelezen.", + "invalidJson": "Ongeldige JSON", + "exportFailed": "Exporteren mislukt", + "copyFailed": "Kopiëren mislukt" + } + } + }, + "pl": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Konwertuj pliki CSV lub Excel na JSON i eksportuj z powrotem do CSV lub XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Wgraj CSV lub Excel, edytuj JSON, pobierz CSV lub XLSX. Tylko dane tabelaryczne; daty z Excela stają się znacznikami ISO w JSON.", + "dropTitle": "Upuść plik CSV lub Excel", + "dropHint": "Dla .xlsx / .xls używany jest pierwszy arkusz. CSV musi mieć wiersz nagłówka.", + "chooseFile": "Wybierz plik", + "jsonLabel": "JSON (tablica obiektów)", + "jsonPlaceholder": "Wklej tablicę JSON obiektów lub zaimportuj CSV / Excel…", + "loadSample": "Załaduj przykład", + "actions": { + "format": "Formatuj JSON", + "clear": "Wyczyść", + "copy": "Kopiuj", + "copied": "Skopiowano", + "downloadCsv": "Pobierz CSV", + "downloadXlsx": "Pobierz Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Dane", + "toastLoadedCsv": "CSV wczytano do edytora", + "toastLoadedExcel": "Plik Excel wczytano do edytora", + "toastFormatted": "Sformatowano JSON", + "toastExportedCsv": "Pobrano CSV", + "toastExportedXlsx": "Pobrano Excel", + "toastCopied": "Skopiowano do schowka", + "footnote": "Wszystko w przeglądarce. JSON musi być niepustą tablicą obiektów. Zagnieżdżone wartości są serializowane do JSON w komórkach CSV.", + "errors": { + "unsupportedFile": "Użyj pliku .csv, .xlsx lub .xls.", + "parseFailed": "Nie można przetworzyć pliku.", + "invalidJson": "Nieprawidłowy JSON", + "exportFailed": "Eksport nie powiódł się", + "copyFailed": "Nie można skopiować" + } + } + }, + "uk": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Перетворюйте CSV або Excel на JSON і експортуйте назад у CSV чи XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Завантажте CSV або Excel, відредагуйте JSON, потім завантажте CSV або XLSX. Лише табличні дані; дати Excel стають ISO-мітками часу в JSON.", + "dropTitle": "Перетягніть файл CSV або Excel", + "dropHint": "Для .xlsx / .xls використовується перший аркуш. CSV має містити рядок заголовків.", + "chooseFile": "Вибрати файл", + "jsonLabel": "JSON (масив об’єктів)", + "jsonPlaceholder": "Вставте JSON-масив об’єктів або імпортуйте CSV / Excel…", + "loadSample": "Завантажити приклад", + "actions": { + "format": "Форматувати JSON", + "clear": "Очистити", + "copy": "Копіювати", + "copied": "Скопійовано", + "downloadCsv": "Завантажити CSV", + "downloadXlsx": "Завантажити Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Дані", + "toastLoadedCsv": "CSV завантажено в редактор", + "toastLoadedExcel": "Excel завантажено в редактор", + "toastFormatted": "JSON відформатовано", + "toastExportedCsv": "CSV завантажено", + "toastExportedXlsx": "Excel завантажено", + "toastCopied": "Скопійовано в буфер", + "footnote": "Усе працює у браузері. JSON має бути непустим масивом об’єктів. Вкладені значення серіалізуються в JSON у комірках CSV.", + "errors": { + "unsupportedFile": "Використайте файл .csv, .xlsx або .xls.", + "parseFailed": "Не вдалося розібрати файл.", + "invalidJson": "Некоректний JSON", + "exportFailed": "Помилка експорту", + "copyFailed": "Не вдалося скопіювати" + } + } + }, + "ru": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Конвертируйте CSV или Excel в JSON и экспортируйте обратно в CSV или XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Загрузите CSV или Excel, отредактируйте JSON, затем скачайте CSV или XLSX. Только табличные данные; даты Excel становятся ISO-временными метками в JSON.", + "dropTitle": "Перетащите файл CSV или Excel", + "dropHint": "Для .xlsx / .xls используется первый лист. В CSV должна быть строка заголовков.", + "chooseFile": "Выбрать файл", + "jsonLabel": "JSON (массив объектов)", + "jsonPlaceholder": "Вставьте JSON-массив объектов или импортируйте CSV / Excel…", + "loadSample": "Загрузить пример", + "actions": { + "format": "Форматировать JSON", + "clear": "Очистить", + "copy": "Копировать", + "copied": "Скопировано", + "downloadCsv": "Скачать CSV", + "downloadXlsx": "Скачать Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Данные", + "toastLoadedCsv": "CSV загружен в редактор", + "toastLoadedExcel": "Excel загружен в редактор", + "toastFormatted": "JSON отформатирован", + "toastExportedCsv": "CSV скачан", + "toastExportedXlsx": "Excel скачан", + "toastCopied": "Скопировано в буфер", + "footnote": "Всё выполняется в браузере. JSON должен быть непустым массивом объектов. Вложенные значения сериализуются в JSON в ячейках CSV.", + "errors": { + "unsupportedFile": "Используйте файл .csv, .xlsx или .xls.", + "parseFailed": "Не удалось разобрать файл.", + "invalidJson": "Некорректный JSON", + "exportFailed": "Ошибка экспорта", + "copyFailed": "Не удалось скопировать" + } + } + }, + "ja": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "CSV や Excel を JSON に変換し、CSV や XLSX に再エクスポートします。", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV または Excel をアップロードし、JSON を編集してから CSV または XLSX をダウンロード。表形式データのみ。Excel の日付セルは JSON では ISO 形式になります。", + "dropTitle": "CSV または Excel ファイルをドロップ", + "dropHint": ".xlsx / .xls は先頭のシートを使用します。CSV はヘッダー行が必要です。", + "chooseFile": "ファイルを選択", + "jsonLabel": "JSON(オブジェクトの配列)", + "jsonPlaceholder": "オブジェクトの JSON 配列を貼り付けるか、CSV / Excel を取り込み…", + "loadSample": "サンプルを読み込む", + "actions": { + "format": "JSON を整形", + "clear": "クリア", + "copy": "コピー", + "copied": "コピーしました", + "downloadCsv": "CSV をダウンロード", + "downloadXlsx": "Excel をダウンロード" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "データ", + "toastLoadedCsv": "CSV をエディターに読み込みました", + "toastLoadedExcel": "Excel をエディターに読み込みました", + "toastFormatted": "JSON を整形しました", + "toastExportedCsv": "CSV をダウンロードしました", + "toastExportedXlsx": "Excel をダウンロードしました", + "toastCopied": "クリップボードにコピーしました", + "footnote": "すべてブラウザ内で処理されます。JSON は空でないオブジェクトの配列である必要があります。ネストした値は CSV セル内で JSON 文字列化されます。", + "errors": { + "unsupportedFile": ".csv、.xlsx、または .xls ファイルを使用してください。", + "parseFailed": "このファイルを解析できませんでした。", + "invalidJson": "無効な JSON", + "exportFailed": "エクスポートに失敗しました", + "copyFailed": "コピーできませんでした" + } + } + }, + "ko": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "CSV 또는 Excel 파일을 JSON으로 변환하고 CSV 또는 XLSX로 다시보냅니다.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV 또는 Excel을 업로드하고 JSON을 편집한 뒤 CSV 또는 XLSX로 다운로드합니다. 표 형식 데이터만 지원합니다. Excel 날짜 셀은 JSON에서 ISO 타임스탬프로 변환됩니다.", + "dropTitle": "CSV 또는 Excel 파일을 놓으세요", + "dropHint": ".xlsx / .xls는 첫 번째 시트를 사용합니다. CSV에는 헤더 행이 있어야 합니다.", + "chooseFile": "파일 선택", + "jsonLabel": "JSON(객체 배열)", + "jsonPlaceholder": "객체의 JSON 배열을 붙여넣거나 CSV / Excel을 가져오기…", + "loadSample": "샘플 불러오기", + "actions": { + "format": "JSON 정렬", + "clear": "지우기", + "copy": "복사", + "copied": "복사됨", + "downloadCsv": "CSV 다운로드", + "downloadXlsx": "Excel 다운로드" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "데이터", + "toastLoadedCsv": "CSV를 편집기에 불러왔습니다", + "toastLoadedExcel": "Excel 파일을 편집기에 불러왔습니다", + "toastFormatted": "JSON이 정렬되었습니다", + "toastExportedCsv": "CSV를 다운로드했습니다", + "toastExportedXlsx": "Excel을 다운로드했습니다", + "toastCopied": "클립보드에 복사했습니다", + "footnote": "모든 처리는 브라우저에서 이루어집니다. JSON은 비어 있지 않은 객체 배열이어야 합니다. 중첩 값은 CSV 셀에 JSON 문자열로 직렬화됩니다.", + "errors": { + "unsupportedFile": ".csv, .xlsx 또는 .xls 파일을 사용하세요.", + "parseFailed": "이 파일을 분석할 수 없습니다.", + "invalidJson": "잘못된 JSON", + "exportFailed": "보내기 실패", + "copyFailed": "복사할 수 없습니다" + } + } + }, + "zh": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "将 CSV 或 Excel 转为 JSON,并可导出为 CSV 或 XLSX。", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "上传 CSV 或 Excel,编辑 JSON,再下载 CSV 或 XLSX。仅支持表格数据;Excel 日期单元格在 JSON 中为 ISO 时间戳。", + "dropTitle": "拖放 CSV 或 Excel 文件", + "dropHint": ".xlsx / .xls 使用第一个工作表。CSV 需包含标题行。", + "chooseFile": "选择文件", + "jsonLabel": "JSON(对象数组)", + "jsonPlaceholder": "粘贴对象组成的 JSON 数组,或导入 CSV / Excel…", + "loadSample": "加载示例", + "actions": { + "format": "格式化 JSON", + "clear": "清空", + "copy": "复制", + "copied": "已复制", + "downloadCsv": "下载 CSV", + "downloadXlsx": "下载 Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "数据", + "toastLoadedCsv": "已将 CSV 加载到编辑器", + "toastLoadedExcel": "已将 Excel 加载到编辑器", + "toastFormatted": "已格式化 JSON", + "toastExportedCsv": "已下载 CSV", + "toastExportedXlsx": "已下载 Excel", + "toastCopied": "已复制到剪贴板", + "footnote": "全部在浏览器中完成。JSON 必须是非空对象数组。嵌套值在 CSV 单元格中会序列化为 JSON。", + "errors": { + "unsupportedFile": "请使用 .csv、.xlsx 或 .xls 文件。", + "parseFailed": "无法解析此文件。", + "invalidJson": "无效的 JSON", + "exportFailed": "导出失败", + "copyFailed": "无法复制" + } + } + }, + "ar": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "حوّل ملفات CSV أو Excel إلى JSON وصدّر مرة أخرى إلى CSV أو XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "ارفع CSV أو Excel، عدّل JSON، ثم نزّل CSV أو XLSX. بيانات جدولية فقط؛ خلايا التاريخ في Excel تصبح طوابع ISO في JSON.", + "dropTitle": "أسقط ملف CSV أو Excel", + "dropHint": "يُستخدم الورقة الأولى لملفات .xlsx / .xls. يجب أن يحتوي CSV على صف عناوين.", + "chooseFile": "اختر ملفًا", + "jsonLabel": "JSON (مصفوفة كائنات)", + "jsonPlaceholder": "الصق مصفوفة JSON من الكائنات أو استورد CSV / Excel…", + "loadSample": "تحميل مثال", + "actions": { + "format": "تنسيق JSON", + "clear": "مسح", + "copy": "نسخ", + "copied": "تم النسخ", + "downloadCsv": "تنزيل CSV", + "downloadXlsx": "تنزيل Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "بيانات", + "toastLoadedCsv": "تم تحميل CSV في المحرر", + "toastLoadedExcel": "تم تحميل Excel في المحرر", + "toastFormatted": "تم تنسيق JSON", + "toastExportedCsv": "تم تنزيل CSV", + "toastExportedXlsx": "تم تنزيل Excel", + "toastCopied": "تم النسخ إلى الحافظة", + "footnote": "كل شيء يعمل في المتصفح. يجب أن يكون JSON مصفوفة غير فارغة من الكائنات. القيم المتداخلة تُسلسل كـ JSON داخل خلايا CSV.", + "errors": { + "unsupportedFile": "استخدم ملف .csv أو .xlsx أو .xls.", + "parseFailed": "تعذّر تحليل هذا الملف.", + "invalidJson": "JSON غير صالح", + "exportFailed": "فشل التصدير", + "copyFailed": "تعذّر النسخ" + } + } + }, + "ca": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Converteix fitxers CSV o Excel a JSON i exporta de nou a CSV o XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Puja CSV o Excel, edita el JSON i descarrega CSV o XLSX. Només dades tabulars; les dates d’Excel passen a ISO al JSON.", + "dropTitle": "Deixa anar un CSV o Excel", + "dropHint": "Per a .xlsx / .xls s’usa el primer full. El CSV ha de tenir fila de capçalera.", + "chooseFile": "Trieu un fitxer", + "jsonLabel": "JSON (matriu d’objectes)", + "jsonPlaceholder": "Enganxa una matriu JSON d’objectes o importa CSV / Excel…", + "loadSample": "Carregar exemple", + "actions": { + "format": "Formatejar JSON", + "clear": "Netejar", + "copy": "Copiar", + "copied": "Copiat", + "downloadCsv": "Descarregar CSV", + "downloadXlsx": "Descarregar Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Dades", + "toastLoadedCsv": "CSV carregat a l’editor", + "toastLoadedExcel": "Excel carregat a l’editor", + "toastFormatted": "JSON formatat", + "toastExportedCsv": "CSV descarregat", + "toastExportedXlsx": "Excel descarregat", + "toastCopied": "Copiat al porta-retalls", + "footnote": "Tot s’executa al navegador. El JSON ha de ser una matriu no buida d’objectes. Els valors imbricats es serialitzen com a JSON a les cel·les CSV.", + "errors": { + "unsupportedFile": "Utilitza un fitxer .csv, .xlsx o .xls.", + "parseFailed": "No s’ha pogut analitzar el fitxer.", + "invalidJson": "JSON no vàlid", + "exportFailed": "Ha fallat l’exportació", + "copyFailed": "No s’ha pogut copiar" + } + } + }, + "cs": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Převádějte soubory CSV nebo Excel do JSON a exportujte zpět do CSV nebo XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Nahrajte CSV nebo Excel, upravte JSON a stáhněte CSV nebo XLSX. Pouze tabulková data; data z Excelu budou v JSON jako ISO časové značky.", + "dropTitle": "Přetáhněte soubor CSV nebo Excel", + "dropHint": "U .xlsx / .xls se použije první list. CSV musí mít řádek záhlaví.", + "chooseFile": "Vybrat soubor", + "jsonLabel": "JSON (pole objektů)", + "jsonPlaceholder": "Vložte JSON pole objektů nebo importujte CSV / Excel…", + "loadSample": "Načíst ukázku", + "actions": { + "format": "Formátovat JSON", + "clear": "Vymazat", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "downloadCsv": "Stáhnout CSV", + "downloadXlsx": "Stáhnout Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Data", + "toastLoadedCsv": "CSV načteno do editoru", + "toastLoadedExcel": "Excel načten do editoru", + "toastFormatted": "JSON naformátováno", + "toastExportedCsv": "CSV staženo", + "toastExportedXlsx": "Excel stažen", + "toastCopied": "Zkopírováno do schránky", + "footnote": "Vše probíhá v prohlížeči. JSON musí být neprázdné pole objektů. Vnořené hodnoty se v buňkách CSV serializují jako JSON.", + "errors": { + "unsupportedFile": "Použijte soubor .csv, .xlsx nebo .xls.", + "parseFailed": "Soubor nelze zpracovat.", + "invalidJson": "Neplatný JSON", + "exportFailed": "Export se nezdařil", + "copyFailed": "Kopírování se nezdařilo" + } + } + }, + "da": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Konverter CSV- eller Excel-filer til JSON og eksporter tilbage til CSV eller XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Upload CSV eller Excel, rediger JSON, og download CSV eller XLSX. Kun tabeldata; Excel-datoer bliver ISO-tidsstempler i JSON.", + "dropTitle": "Slip en CSV- eller Excel-fil", + "dropHint": "Første ark bruges til .xlsx / .xls. CSV skal have en header-række.", + "chooseFile": "Vælg fil", + "jsonLabel": "JSON (array af objekter)", + "jsonPlaceholder": "Indsæt et JSON-array af objekter, eller importér CSV / Excel…", + "loadSample": "Indlæs eksempel", + "actions": { + "format": "Formatér JSON", + "clear": "Ryd", + "copy": "Kopiér", + "copied": "Kopieret", + "downloadCsv": "Download CSV", + "downloadXlsx": "Download Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Data", + "toastLoadedCsv": "CSV indlæst i editoren", + "toastLoadedExcel": "Excel indlæst i editoren", + "toastFormatted": "JSON formateret", + "toastExportedCsv": "CSV downloadet", + "toastExportedXlsx": "Excel downloadet", + "toastCopied": "Kopieret til udklipsholder", + "footnote": "Alt kører i din browser. JSON skal være et ikke-tomt array af objekter. Indlejrede værdier serialiseres som JSON i CSV-celler.", + "errors": { + "unsupportedFile": "Brug en .csv-, .xlsx- eller .xls-fil.", + "parseFailed": "Kunne ikke læse filen.", + "invalidJson": "Ugyldig JSON", + "exportFailed": "Eksport mislykkedes", + "copyFailed": "Kunne ikke kopiere" + } + } + }, + "el": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Μετατρέψτε αρχεία CSV ή Excel σε JSON και εξάγετε ξανά σε CSV ή XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Ανεβάστε CSV ή Excel, επεξεργαστείτε JSON και κατεβάστε CSV ή XLSX. Μόνο πίνακας· οι ημερομηνίες Excel γίνονται ISO στο JSON.", + "dropTitle": "Αφήστε αρχείο CSV ή Excel", + "dropHint": "Για .xlsx / .xls χρησιμοποιείται το πρώτο φύλλο. Το CSV χρειάζεται γραμμή κεφαλίδας.", + "chooseFile": "Επιλογή αρχείου", + "jsonLabel": "JSON (πίνακας αντικειμένων)", + "jsonPlaceholder": "Επικολλήστε πίνακα JSON αντικειμένων ή εισάγετε CSV / Excel…", + "loadSample": "Φόρτωση δείγματος", + "actions": { + "format": "Μορφοποίηση JSON", + "clear": "Εκκαθάριση", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "downloadCsv": "Λήψη CSV", + "downloadXlsx": "Λήψη Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Δεδομένα", + "toastLoadedCsv": "Το CSV φορτώθηκε στον επεξεργαστή", + "toastLoadedExcel": "Το Excel φορτώθηκε στον επεξεργαστή", + "toastFormatted": "Το JSON μορφοποιήθηκε", + "toastExportedCsv": "Το CSV λήφθηκε", + "toastExportedXlsx": "Το Excel λήφθηκε", + "toastCopied": "Αντιγράφηκε στο πρόχειρο", + "footnote": "Όλα τρέχουν στον browser. Το JSON πρέπει να είναι μη κενός πίνακας αντικειμένων. Ένθετες τιμές σειριοποιούνται ως JSON στα κελιά CSV.", + "errors": { + "unsupportedFile": "Χρησιμοποιήστε αρχείο .csv, .xlsx ή .xls.", + "parseFailed": "Δεν ήταν δυνατή η ανάλυση του αρχείου.", + "invalidJson": "Μη έγκυρο JSON", + "exportFailed": "Η εξαγωγή απέτυχε", + "copyFailed": "Δεν ήταν δυνατή η αντιγραφή" + } + } + }, + "id": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Ubah file CSV atau Excel menjadi JSON dan ekspor kembali ke CSV atau XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Unggah CSV atau Excel, edit JSON, lalu unduh CSV atau XLSX. Hanya data tabular; tanggal Excel menjadi stempel waktu ISO di JSON.", + "dropTitle": "Letakkan file CSV atau Excel", + "dropHint": "Untuk .xlsx / .xls digunakan lembar pertama. CSV harus punya baris header.", + "chooseFile": "Pilih file", + "jsonLabel": "JSON (array objek)", + "jsonPlaceholder": "Tempel array JSON objek atau impor CSV / Excel…", + "loadSample": "Muat contoh", + "actions": { + "format": "Format JSON", + "clear": "Hapus", + "copy": "Salin", + "copied": "Disalin", + "downloadCsv": "Unduh CSV", + "downloadXlsx": "Unduh Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Data", + "toastLoadedCsv": "CSV dimuat ke editor", + "toastLoadedExcel": "Excel dimuat ke editor", + "toastFormatted": "JSON diformat", + "toastExportedCsv": "CSV diunduh", + "toastExportedXlsx": "Excel diunduh", + "toastCopied": "Disalin ke papan klip", + "footnote": "Semua berjalan di browser. JSON harus berupa array objek yang tidak kosong. Nilai bersarang diserialisasi sebagai JSON di sel CSV.", + "errors": { + "unsupportedFile": "Gunakan file .csv, .xlsx, atau .xls.", + "parseFailed": "Tidak dapat mengurai file ini.", + "invalidJson": "JSON tidak valid", + "exportFailed": "Ekspor gagal", + "copyFailed": "Tidak dapat menyalin" + } + } + }, + "ms": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Tukar fail CSV atau Excel kepada JSON dan eksport semula ke CSV atau XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Muat naik CSV atau Excel, edit JSON, kemudian muat turun CSV atau XLSX. Data jadual sahaja; tarikh Excel menjadi cap masa ISO dalam JSON.", + "dropTitle": "Lepaskan fail CSV atau Excel", + "dropHint": "Untuk .xlsx / .xls, helaian pertama digunakan. CSV mesti ada baris pengepala.", + "chooseFile": "Pilih fail", + "jsonLabel": "JSON (tatasusunan objek)", + "jsonPlaceholder": "Tampal tatasusunan JSON objek atau import CSV / Excel…", + "loadSample": "Muat contoh", + "actions": { + "format": "Format JSON", + "clear": "Kosongkan", + "copy": "Salin", + "copied": "Disalin", + "downloadCsv": "Muat turun CSV", + "downloadXlsx": "Muat turun Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Data", + "toastLoadedCsv": "CSV dimuatkan ke editor", + "toastLoadedExcel": "Excel dimuatkan ke editor", + "toastFormatted": "JSON diformat", + "toastExportedCsv": "CSV dimuat turun", + "toastExportedXlsx": "Excel dimuat turun", + "toastCopied": "Disalin ke papan keratan", + "footnote": "Semua berjalan dalam pelayar. JSON mesti tatasusunan objek yang tidak kosong. Nilai bersarang disirikan sebagai JSON dalam sel CSV.", + "errors": { + "unsupportedFile": "Guna fail .csv, .xlsx atau .xls.", + "parseFailed": "Tidak dapat menghuraikan fail ini.", + "invalidJson": "JSON tidak sah", + "exportFailed": "Eksport gagal", + "copyFailed": "Tidak dapat menyalin" + } + } + }, + "nb": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Konverter CSV- eller Excel-filer til JSON og eksporter tilbake til CSV eller XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Last opp CSV eller Excel, rediger JSON, og last ned CSV eller XLSX. Kun tabell data; Excel-datoer blir ISO-tidsstempler i JSON.", + "dropTitle": "Slipp en CSV- eller Excel-fil", + "dropHint": "Første ark brukes for .xlsx / .xls. CSV må ha en overskriftsrad.", + "chooseFile": "Velg fil", + "jsonLabel": "JSON (array av objekter)", + "jsonPlaceholder": "Lim inn JSON-array av objekter, eller importer CSV / Excel…", + "loadSample": "Last inn eksempel", + "actions": { + "format": "Formater JSON", + "clear": "Tøm", + "copy": "Kopier", + "copied": "Kopiert", + "downloadCsv": "Last ned CSV", + "downloadXlsx": "Last ned Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Data", + "toastLoadedCsv": "CSV lastet inn i redigeringsprogrammet", + "toastLoadedExcel": "Excel lastet inn i redigeringsprogrammet", + "toastFormatted": "JSON formatert", + "toastExportedCsv": "CSV lastet ned", + "toastExportedXlsx": "Excel lastet ned", + "toastCopied": "Kopiert til utklippstavle", + "footnote": "Alt skjer i nettleseren. JSON må være en ikke-tom array av objekter. Nestede verdier serialiseres som JSON i CSV-celler.", + "errors": { + "unsupportedFile": "Bruk en .csv-, .xlsx- eller .xls-fil.", + "parseFailed": "Kunne ikke lese filen.", + "invalidJson": "Ugyldig JSON", + "exportFailed": "Eksport mislyktes", + "copyFailed": "Kunne ikke kopiere" + } + } + }, + "sv": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Konvertera CSV- eller Excel-filer till JSON och exportera tillbaka till CSV eller XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Ladda upp CSV eller Excel, redigera JSON och ladda ner CSV eller XLSX. Endast tabell data; Excel-datum blir ISO-tidsstämplar i JSON.", + "dropTitle": "Släpp en CSV- eller Excel-fil", + "dropHint": "Första arbetsbladet används för .xlsx / .xls. CSV måste ha en rubrikrad.", + "chooseFile": "Välj fil", + "jsonLabel": "JSON (array av objekt)", + "jsonPlaceholder": "Klistra in JSON-array av objekt eller importera CSV / Excel…", + "loadSample": "Ladda exempel", + "actions": { + "format": "Formatera JSON", + "clear": "Rensa", + "copy": "Kopiera", + "copied": "Kopierat", + "downloadCsv": "Ladda ner CSV", + "downloadXlsx": "Ladda ner Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Data", + "toastLoadedCsv": "CSV laddad i redigeraren", + "toastLoadedExcel": "Excel laddad i redigeraren", + "toastFormatted": "JSON formaterat", + "toastExportedCsv": "CSV nedladdad", + "toastExportedXlsx": "Excel nedladdad", + "toastCopied": "Kopierat till urklipp", + "footnote": "Allt körs i webbläsaren. JSON måste vara en icke-tom array av objekt. Kapslade värden serialiseras som JSON i CSV-celler.", + "errors": { + "unsupportedFile": "Använd en .csv-, .xlsx- eller .xls-fil.", + "parseFailed": "Kunde inte tolka filen.", + "invalidJson": "Ogiltig JSON", + "exportFailed": "Export misslyckades", + "copyFailed": "Kunde inte kopiera" + } + } + }, + "tr": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "CSV veya Excel dosyalarını JSON’a dönüştürün ve tekrar CSV veya XLSX olarak dışa aktarın.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV veya Excel yükleyin, JSON’u düzenleyin, ardından CSV veya XLSX indirin. Yalnızca tablo verisi; Excel tarih hücreleri JSON’da ISO zaman damgası olur.", + "dropTitle": "CSV veya Excel dosyasını bırakın", + "dropHint": ".xlsx / .xls için ilk çalışma sayfası kullanılır. CSV’de başlık satırı olmalıdır.", + "chooseFile": "Dosya seç", + "jsonLabel": "JSON (nesne dizisi)", + "jsonPlaceholder": "Nesnelerden oluşan JSON dizisini yapıştırın veya CSV / Excel içe aktarın…", + "loadSample": "Örnek yükle", + "actions": { + "format": "JSON’u biçimlendir", + "clear": "Temizle", + "copy": "Kopyala", + "copied": "Kopyalandı", + "downloadCsv": "CSV indir", + "downloadXlsx": "Excel indir" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Veri", + "toastLoadedCsv": "CSV düzenleyiciye yüklendi", + "toastLoadedExcel": "Excel düzenleyiciye yüklendi", + "toastFormatted": "JSON biçimlendirildi", + "toastExportedCsv": "CSV indirildi", + "toastExportedXlsx": "Excel indirildi", + "toastCopied": "Panoya kopyalandı", + "footnote": "Her şey tarayıcıda çalışır. JSON boş olmayan bir nesne dizisi olmalıdır. İç içe değerler CSV hücrelerinde JSON olarak serileştirilir.", + "errors": { + "unsupportedFile": ".csv, .xlsx veya .xls dosyası kullanın.", + "parseFailed": "Bu dosya ayrıştırılamadı.", + "invalidJson": "Geçersiz JSON", + "exportFailed": "Dışa aktarma başarısız", + "copyFailed": "Kopyalanamadı" + } + } + }, + "vi": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Chuyển file CSV hoặc Excel sang JSON và xuất lại CSV hoặc XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Tải CSV hoặc Excel, chỉnh JSON, rồi tải CSV hoặc XLSX. Chỉ dữ liệu dạng bảng; ô ngày trong Excel thành dấu thời gian ISO trong JSON.", + "dropTitle": "Thả file CSV hoặc Excel", + "dropHint": "Với .xlsx / .xls dùng sheet đầu tiên. CSV cần có dòng tiêu đề.", + "chooseFile": "Chọn file", + "jsonLabel": "JSON (mảng đối tượng)", + "jsonPlaceholder": "Dán mảng JSON đối tượng hoặc nhập CSV / Excel…", + "loadSample": "Tải mẫu", + "actions": { + "format": "Định dạng JSON", + "clear": "Xóa", + "copy": "Sao chép", + "copied": "Đã sao chép", + "downloadCsv": "Tải CSV", + "downloadXlsx": "Tải Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Dữ liệu", + "toastLoadedCsv": "Đã tải CSV vào trình sửa", + "toastLoadedExcel": "Đã tải Excel vào trình sửa", + "toastFormatted": "Đã định dạng JSON", + "toastExportedCsv": "Đã tải CSV", + "toastExportedXlsx": "Đã tải Excel", + "toastCopied": "Đã sao chép vào clipboard", + "footnote": "Mọi thứ chạy trong trình duyệt. JSON phải là mảng đối tượng không rỗng. Giá trị lồng nhau được chuỗi hóa JSON trong ô CSV.", + "errors": { + "unsupportedFile": "Dùng file .csv, .xlsx hoặc .xls.", + "parseFailed": "Không thể phân tích file này.", + "invalidJson": "JSON không hợp lệ", + "exportFailed": "Xuất thất bại", + "copyFailed": "Không thể sao chép" + } + } + }, + "fa": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "فایل‌های CSV یا Excel را به JSON تبدیل کنید و دوباره به CSV یا XLSX صادر کنید.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "CSV یا Excel را بارگذاری کنید، JSON را ویرایش کنید، سپس CSV یا XLSX را دانلود کنید. فقط داده جدولی؛ سلول‌های تاریخ Excel در JSON به برچسب زمانی ISO تبدیل می‌شوند.", + "dropTitle": "فایل CSV یا Excel را رها کنید", + "dropHint": "برای .xlsx / .xls از اولین برگه استفاده می‌شود. CSV باید سطر سرستون داشته باشد.", + "chooseFile": "انتخاب فایل", + "jsonLabel": "JSON (آرایه‌ای از شیءها)", + "jsonPlaceholder": "آرایه JSON از شیءها را بچسبانید یا CSV / Excel وارد کنید…", + "loadSample": "بارگذاری نمونه", + "actions": { + "format": "قالب‌بندی JSON", + "clear": "پاک کردن", + "copy": "کپی", + "copied": "کپی شد", + "downloadCsv": "دانلود CSV", + "downloadXlsx": "دانلود Excel" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "داده", + "toastLoadedCsv": "CSV در ویرایشگر بارگذاری شد", + "toastLoadedExcel": "Excel در ویرایشگر بارگذاری شد", + "toastFormatted": "JSON قالب‌بندی شد", + "toastExportedCsv": "CSV دانلود شد", + "toastExportedXlsx": "Excel دانلود شد", + "toastCopied": "در کلیپ‌بورد کپی شد", + "footnote": "همه‌چیز در مرورگر اجرا می‌شود. JSON باید آرایه‌ای غیرخالی از شیءها باشد. مقادیر تودرتو در سلول‌های CSV به صورت JSON سریال می‌شوند.", + "errors": { + "unsupportedFile": "از فایل .csv، .xlsx یا .xls استفاده کنید.", + "parseFailed": "این فایل قابل تجزیه نیست.", + "invalidJson": "JSON نامعتبر", + "exportFailed": "صادرات ناموفق", + "copyFailed": "کپی نشد" + } + } + }, + "af": { + "nav": "CSV / Excel ↔ JSON", + "dTitle": "CSV / Excel ↔ JSON", + "dDesc": "Skakel CSV- of Excel-lêers na JSON en voer terug uit na CSV of XLSX.", + "tool": { + "title": "CSV / Excel ↔ JSON", + "subtitle": "Laai CSV of Excel op, wysig JSON, laai dan CSV of XLSX af. Slegs tabeldata; Excel-datums word ISO-tydstempels in JSON.", + "dropTitle": "Los ’n CSV- of Excel-lêer hier", + "dropHint": "Eerste werkblad word vir .xlsx / .xls gebruik. CSV moet ’n opskrifry hê.", + "chooseFile": "Kies lêer", + "jsonLabel": "JSON (skikking van voorwerpe)", + "jsonPlaceholder": "Plak ’n JSON-skikking van voorwerpe of voer CSV / Excel in…", + "loadSample": "Laai voorbeeld", + "actions": { + "format": "Formateer JSON", + "clear": "Maak skoon", + "copy": "Kopieer", + "copied": "Gekopieer", + "downloadCsv": "Laai CSV af", + "downloadXlsx": "Laai Excel af" + }, + "filenames": { "csv": "data.csv", "xlsx": "data.xlsx" }, + "sheetName": "Data", + "toastLoadedCsv": "CSV in redigeerder gelaai", + "toastLoadedExcel": "Excel in redigeerder gelaai", + "toastFormatted": "JSON geformateer", + "toastExportedCsv": "CSV afgelaai", + "toastExportedXlsx": "Excel afgelaai", + "toastCopied": "Na knipbord gekopieer", + "footnote": "Alles loop in jou blaaier. JSON moet ’n nie-leë skikking van voorwerpe wees. Geneste waardes word as JSON in CSV-selle gestoor.", + "errors": { + "unsupportedFile": "Gebruik ’n .csv-, .xlsx- of .xls-lêer.", + "parseFailed": "Kon nie hierdie lêer ontleed nie.", + "invalidJson": "Ongeldige JSON", + "exportFailed": "Uitvoer het misluk", + "copyFailed": "Kon nie kopieer nie" + } + } + } +} diff --git a/apps/desktop-ui/scripts/generate_snippet_manager_patches.py b/apps/desktop-ui/scripts/generate_snippet_manager_patches.py new file mode 100644 index 00000000..6df2ddfb --- /dev/null +++ b/apps/desktop-ui/scripts/generate_snippet_manager_patches.py @@ -0,0 +1,1490 @@ +#!/usr/bin/env python3 +"""Writes snippet-manager-patches.json for merge-snippet-manager-i18n.mjs (full SnippetManager UI).""" + +import json +from pathlib import Path + +OUT = Path(__file__).resolve().parent / "snippet-manager-patches.json" +OUT_NAV_DASH = Path(__file__).resolve().parent / "snippet-manager-nav-dash-patches.json" + +# Navigation.snippetManager + Dashboard.tools.snippetManager (card on dashboard) +NAV_DASH: dict[str, dict[str, str]] = { + "fr": { + "navigation": "Extraits de code", + "title": "Extraits de code", + "description": "Enregistrez des extraits avec coloration syntaxique, détection automatique du langage et formatage.", + }, + "de": { + "navigation": "Code-Snippets", + "title": "Code-Snippets", + "description": "Snippets mit Syntaxhervorhebung, automatischer Spracherkennung und Formatierung speichern.", + }, + "es": { + "navigation": "Fragmentos de código", + "title": "Fragmentos de código", + "description": "Guarda fragmentos con resaltado de sintaxis, detección automática de idioma y formato.", + }, + "it": { + "navigation": "Snippet di codice", + "title": "Snippet di codice", + "description": "Salva snippet con evidenziazione sintattica, rilevamento automatico della lingua e formattazione.", + }, + "pt": { + "navigation": "Excertos de código", + "title": "Excertos de código", + "description": "Guarde excertos com realce de sintaxe, deteção automática de linguagem e formatação.", + }, + "pt-BR": { + "navigation": "Trechos de código", + "title": "Trechos de código", + "description": "Salve trechos com realce de sintaxe, detecção automática de linguagem e formatação.", + }, + "nl": { + "navigation": "Codefragmenten", + "title": "Codefragmenten", + "description": "Bewaar fragmenten met syntaxisaccentuering, automatische taaldetectie en opmaak.", + }, + "pl": { + "navigation": "Fragmenty kodu", + "title": "Fragmenty kodu", + "description": "Zapisuj fragmenty z podświetlaniem składni, autowykrywaniem języka i formatowaniem.", + }, + "ru": { + "navigation": "Фрагменты кода", + "title": "Фрагменты кода", + "description": "Сохраняйте сниппеты с подсветкой синтаксиса, автоопределением языка и форматированием.", + }, + "uk": { + "navigation": "Фрагменти коду", + "title": "Фрагменти коду", + "description": "Зберігайте фрагменти з підсвічуванням синтаксису, авто-визначенням мови та форматуванням.", + }, + "tr": { + "navigation": "Kod parçacıkları", + "title": "Kod parçacıkları", + "description": "Sözdizimi vurgulama, otomatik dil algılama ve biçimlendirme ile parçacıkları kaydedin.", + }, + "ar": { + "navigation": "مقاطع الشيفرة", + "title": "مقاطع الشيفرة", + "description": "احفظ المقاطع مع تمييز بناء الجملة وكشف اللغة التلقائي والتنسيق.", + }, + "fa": { + "navigation": "قطعه‌کدها", + "title": "قطعه‌کدها", + "description": "قطعه‌کدها را با برجسته‌سازی نحو، تشخیص خودکار زبان و قالب‌بندی ذخیره کنید.", + }, + "zh": { + "navigation": "代码片段", + "title": "代码片段", + "description": "保存代码片段,支持语法高亮、自动检测语言和格式化。", + }, + "ja": { + "navigation": "コードスニペット", + "title": "コードスニペット", + "description": "シンタックスハイライト、自動言語検出、整形でスニペットを保存します。", + }, + "ko": { + "navigation": "코드 스니펫", + "title": "코드 스니펫", + "description": "구문 강조, 자동 언어 감지 및 서식으로 스니펫을 저장하세요.", + }, + "vi": { + "navigation": "Đoạn mã", + "title": "Đoạn mã", + "description": "Lưu đoạn mã với tô sáng cú pháp, tự động nhận diện ngôn ngữ và định dạng.", + }, + "id": { + "navigation": "Cuplikan kode", + "title": "Cuplikan kode", + "description": "Simpan cuplikan dengan penyorotan sintaks, deteksi bahasa otomatis, dan pemformatan.", + }, + "ms": { + "navigation": "Petikan kod", + "title": "Petikan kod", + "description": "Simpan petikan dengan sorotan sintaks, pengesanan bahasa automatik dan pemformatan.", + }, + "cs": { + "navigation": "Úryvky kódu", + "title": "Úryvky kódu", + "description": "Ukládejte úryvky se zvýrazněním syntaxe, automatickou detekcí jazyka a formátováním.", + }, + "el": { + "navigation": "Αποσπάσματα κώδικα", + "title": "Αποσπάσματα κώδικα", + "description": "Αποθηκεύστε αποσπάσματα με τονισμό σύνταξης, αυτόματη ανίχνευση γλώσσας και μορφοποίηση.", + }, + "da": { + "navigation": "Kodestykker", + "title": "Kodestykker", + "description": "Gem kodestykker med syntaksfremhævelse, automatisk sprogdetektion og formatering.", + }, + "nb": { + "navigation": "Kodeutdrag", + "title": "Kodeutdrag", + "description": "Lagre utdrag med syntaksutheving, automatisk språkgjenkjenning og formatering.", + }, + "sv": { + "navigation": "Kodutdrag", + "title": "Kodutdrag", + "description": "Spara utdrag med syntaxmarkering, automatisk språkupptäckt och formatering.", + }, + "af": { + "navigation": "Kodesnippette", + "title": "Kodesnippette", + "description": "Bewaar snippette met sintaks-beklemtoning, outomatiese taalopsporing en formatering.", + }, + "ca": { + "navigation": "Fragments de codi", + "title": "Fragments de codi", + "description": "Desa fragments amb ressaltat de sintaxi, detecció automàtica d’idioma i format.", + }, +} + +# Programming-language ids stay English; only translate user-facing UI. +LANG = { + "en": ("Auto-detect", "Plain text"), + "fr": ("Détection automatique", "Texte brut"), + "es": ("Detección automática", "Texto sin formato"), + "de": ("Automatisch erkennen", "Nur Text"), + "it": ("Rilevamento automatico", "Testo normale"), + "pt": ("Deteção automática", "Texto simples"), + "pt-BR": ("Detecção automática", "Texto simples"), + "nl": ("Automatisch detecteren", "Platte tekst"), + "pl": ("Wykrywanie automatyczne", "Zwykły tekst"), + "ru": ("Автоопределение", "Простой текст"), + "uk": ("Автовизначення", "Звичайний текст"), + "tr": ("Otomatik algıla", "Düz metin"), + "ar": ("اكتشاف تلقائي", "نص عادي"), + "fa": ("تشخیص خودکار", "متن ساده"), + "zh": ("自动检测", "纯文本"), + "ja": ("自動検出", "プレーンテキスト"), + "ko": ("자동 감지", "일반 텍스트"), + "vi": ("Tự động nhận diện", "Văn bản thuần"), + "id": ("Deteksi otomatis", "Teks biasa"), + "ms": ("Auto kesan", "Teks biasa"), + "cs": ("Automatická detekce", "Prostý text"), + "el": ("Αυτόματη ανίχνευση", "Απλό κείμενο"), + "da": ("Automatisk registrering", "Almindelig tekst"), + "nb": ("Automatisk gjenkjenning", "Ren tekst"), + "sv": ("Automatisk avkänning", "Oformaterad text"), + "af": ("Outomatiese opsporing", "Gewone teks"), + "ca": ("Detecció automàtica", "Text sense format"), +} + + +def code_sample(l1: str, l2: str) -> str: + return ( + f"{l1}\n{l2}\n" + "function greet(name) {\n" + " return `Hello, ${name}!`;\n" + "}" + ) + + +def pack( + loc: str, + *, + title: str, + snippets: str, + new_snippet: str, + new_title: str, + welcome: str, + c1: str, + c2: str, + search_ph: str, + search_ar: str, + empty: str, + st: str, + st_ph: str, + lang: str, + det: str, + badge: str, + view: str, + edit: str, + copy: str, + copied: str, + fmt: str, + cancel: str, + del_t: str, + del_d: str, + del_ok: str, + ed_ar: str, + ed_ld: str, + tc: str, + tcf: str, + tnt: str, + tfm: str, + tff: str, + tfj: str, + tfu: str, + tdel: str, + tlf: str, + tsf: str, + sub: str, +) -> dict: + auto, plain = LANG[loc] + return { + "title": title, + "subtitle": sub, + "snippets": snippets, + "newSnippet": new_snippet, + "newSnippetTitle": new_title, + "defaultSnippetTitle": welcome, + "defaultSnippetCode": code_sample(c1, c2), + "searchPlaceholder": search_ph, + "searchAria": search_ar, + "emptySearch": empty, + "snippetTitle": st, + "snippetTitlePlaceholder": st_ph, + "language": lang, + "detectedHint": det, + "badgeAuto": badge, + "modeView": view, + "modeEdit": edit, + "copy": copy, + "copied": copied, + "format": fmt, + "cancel": cancel, + "deleteTitle": del_t, + "deleteDescription": del_d, + "deleteConfirm": del_ok, + "editorAria": ed_ar, + "editorLoading": ed_ld, + "toastCopied": tc, + "toastCopyFailed": tcf, + "toastNothingToCopy": tnt, + "toastFormatted": tfm, + "toastFormatFailed": tff, + "toastFormatJsonInvalid": tfj, + "toastFormatUnsupported": tfu, + "toastDeleted": tdel, + "toastLoadFailed": tlf, + "toastSyncFailed": tsf, + "languages": {"auto": auto, "plaintext": plain}, + } + + +# Subtitles / server toasts / dashboard label — keep aligned with apply-snippet-dashboard-i18n.py +SUB = { + "fr": ( + "Enregistrez des extraits dans votre navigateur ou connectez-vous pour les synchroniser avec votre compte. Passez de la consultation à l’édition, copiez en un clic, choisissez un langage ou la détection automatique, et formatez JSON, SQL ou le code pris en charge par Monaco.", + "Impossible de charger les extraits depuis le serveur.", + "Impossible de synchroniser avec le serveur.", + ), + "de": ( + "Speichern Sie Snippets im Browser oder melden Sie sich an, um sie mit Ihrem Konto zu synchronisieren. Wechseln Sie zwischen Ansicht und Bearbeitung, kopieren Sie mit einem Klick, wählen Sie eine Sprache oder Auto-Erkennung und formatieren Sie JSON, SQL oder von Monaco unterstützten Code.", + "Snippets konnten nicht vom Server geladen werden.", + "Synchronisation mit dem Server fehlgeschlagen.", + ), + "es": ( + "Guarda fragmentos en tu navegador o inicia sesión para sincronizarlos con tu cuenta. Cambia entre ver y editar, copia con un clic, elige idioma o detección automática y formatea JSON, SQL o código compatible con Monaco.", + "No se pudieron cargar los fragmentos desde el servidor.", + "No se pudo sincronizar con el servidor.", + ), + "it": ( + "Conserva gli snippet nel browser o accedi per sincronizzarli con il tuo account. Passa da visualizzazione a modifica, copia con un clic, scegli la lingua o il rilevamento automatico e formatta JSON, SQL o codice supportato da Monaco.", + "Impossibile caricare gli snippet dal server.", + "Impossibile sincronizzare con il server.", + ), + "pt": ( + "Guarde excertos no navegador ou inicie sessão para os sincronizar com a sua conta. Alterne entre ver e editar, copie com um clique, escolha a linguagem ou deteção automática e formate JSON, SQL ou código suportado pelo Monaco.", + "Não foi possível carregar os excertos do servidor.", + "Não foi possível sincronizar com o servidor.", + ), + "pt-BR": ( + "Armazene trechos no navegador ou entre para sincronizá-los com sua conta. Alterne entre ver e editar, copie com um clique, escolha a linguagem ou detecção automática e formate JSON, SQL ou código suportado pelo Monaco.", + "Não foi possível carregar os trechos do servidor.", + "Não foi possível sincronizar com o servidor.", + ), + "nl": ( + "Bewaar snippets in je browser of log in om ze met je account te synchroniseren. Wissel tussen bekijken en bewerken, kopieer met één klik, kies een taal of automatische detectie en formatteer JSON, SQL of door Monaco ondersteunde code.", + "Snippets konden niet van de server worden geladen.", + "Synchroniseren met de server is mislukt.", + ), + "pl": ( + "Przechowuj fragmenty kodu w przeglądarce lub zaloguj się, aby synchronizować je z kontem. Przełączaj widok i edycję, kopiuj jednym kliknięciem, wybierz język lub autowykrywanie i formatuj JSON, SQL lub kod obsługiwany przez Monaco.", + "Nie udało się wczytać fragmentów z serwera.", + "Nie udało się zsynchronizować z serwerem.", + ), + "ru": ( + "Храните сниппеты в браузере или войдите, чтобы синхронизировать их с аккаунтом. Переключайтесь между просмотром и правкой, копируйте в один клик, выбирайте язык или автоопределение и форматируйте JSON, SQL или код, поддерживаемый Monaco.", + "Не удалось загрузить сниппеты с сервера.", + "Не удалось синхронизировать с сервером.", + ), + "uk": ( + "Зберігайте фрагменти коду в браузері або увійдіть, щоб синхронізувати їх з обліковим записом. Перемикайте перегляд і редагування, копіюйте одним кліком, обирайте мову або авто-визначення та форматуйте JSON, SQL або код, який підтримує Monaco.", + "Не вдалося завантажити фрагменти з сервера.", + "Не вдалося синхронізувати з сервером.", + ), + "tr": ( + "Parçacıkları tarayıcıda saklayın veya hesabınızla eşitlemek için oturum açın. Görüntüleme ve düzenleme arasında geçiş yapın, tek tıkla kopyalayın, dil seçin veya otomatik algılayın ve JSON, SQL veya Monaco destekli kodu biçimlendirin.", + "Parçacıklar sunucudan yüklenemedi.", + "Sunucu ile eşitleme başarısız oldu.", + ), + "ar": ( + "خزّن المقاطع في المتصفح أو سجّل الدخول لمزامنتها مع حسابك. بدّل بين العرض والتحرير، انسخ بنقرة واحدة، اختر لغة أو كشفًا تلقائيًا، ونسّق JSON أو SQL أو أكواد يدعمها Monaco.", + "تعذّر تحميل المقاطع من الخادم.", + "تعذّرت المزامنة مع الخادم.", + ), + "fa": ( + "قطعه‌کدها را در مرورگر نگه دارید یا برای همگام‌سازی با حساب وارد شوید. بین مشاهده و ویرایش جابه‌جا شوید، با یک کلیک کپی کنید، زبان را انتخاب یا خودکار تشخیص دهید و JSON، SQL یا کد پشتیبانی‌شده توسط Monaco را قالب‌بندی کنید.", + "بارگیری قطعه‌کدها از سرور انجام نشد.", + "همگام‌سازی با سرور انجام نشد.", + ), + "zh": ( + "在浏览器中保存代码片段,或登录以同步到账户。在查看与编辑间切换、一键复制、选择语言或使用自动检测,并格式化 JSON、SQL 或 Monaco 支持的代码。", + "无法从服务器加载代码片段。", + "无法与服务器同步。", + ), + "ja": ( + "ブラウザにスニペットを保存するか、サインインしてアカウントと同期します。表示と編集の切り替え、ワンクリックコピー、言語の選択または自動検出、JSON・SQL・Monaco 対応コードの整形ができます。", + "サーバーからスニペットを読み込めませんでした。", + "サーバーと同期できませんでした。", + ), + "ko": ( + "브라우저에 스니펫을 저장하거나 로그인해 계정과 동기화하세요. 보기와 편집 전환, 한 번의 클릭으로 복사, 언어 선택 또는 자동 감지, JSON·SQL·Monaco 지원 코드 포맷을 사용할 수 있습니다.", + "서버에서 스니펫을 불러오지 못했습니다.", + "서버와 동기화하지 못했습니다.", + ), + "vi": ( + "Lưu đoạn mã trong trình duyệt hoặc đăng nhập để đồng bộ với tài khoản. Chuyển giữa xem và sửa, sao chép một cú nhấp, chọn ngôn ngữ hoặc tự động nhận diện, và định dạng JSON, SQL hoặc mã được Monaco hỗ trợ.", + "Không thể tải đoạn mã từ máy chủ.", + "Không thể đồng bộ với máy chủ.", + ), + "id": ( + "Simpan cuplikan di browser atau masuk untuk menyinkronkannya dengan akun Anda. Beralih antara lihat dan sunting, salin sekali klik, pilih bahasa atau deteksi otomatis, dan format JSON, SQL, atau kode yang didukung Monaco.", + "Tidak dapat memuat cuplikan dari server.", + "Tidak dapat menyinkronkan dengan server.", + ), + "ms": ( + "Simpan petikan dalam pelayar atau log masuk untuk menyelaraskannya dengan akaun anda. Tukar antara lihat dan sunting, salin satu klik, pilih bahasa atau auto kesan, dan format JSON, SQL atau kod yang disokong Monaco.", + "Tidak dapat memuatkan petikan dari pelayan.", + "Tidak dapat menyelaraskan dengan pelayan.", + ), + "cs": ( + "Ukládejte úryvky v prohlížeči nebo se přihlaste a synchronizujte je s účtem. Přepínejte zobrazení a úpravy, kopírujte jedním kliknutím, volte jazyk nebo automatickou detekci a formátujte JSON, SQL nebo kód podporovaný Monacem.", + "Úryvky se nepodařilo načíst ze serveru.", + "Synchronizace se serverem se nezdařila.", + ), + "el": ( + "Αποθηκεύστε αποσπάσματα στον browser ή συνδεθείτε για συγχρονισμό με τον λογαριασμό σας. Εναλλαγή προβολής και επεξεργασίας, αντιγραφή με ένα κλικ, επιλογή γλώσσας ή αυτόματη ανίχνευση και μορφοποίηση JSON, SQL ή κώδικα που υποστηρίζει το Monaco.", + "Δεν ήταν δυνατή η φόρτωση αποσπασμάτων από τον διακομιστή.", + "Δεν ήταν δυνατός ο συγχρονισμός με τον διακομιστή.", + ), + "da": ( + "Gem snippets i browseren eller log ind for at synkronisere med din konto. Skift mellem visning og redigering, kopiér med ét klik, vælg sprog eller autodetektér, og formatér JSON, SQL eller Monaco-understøttet kode.", + "Kunne ikke indlæse snippets fra serveren.", + "Kunne ikke synkronisere med serveren.", + ), + "nb": ( + "Lagre utdrag i nettleseren eller logg inn for å synkronisere med kontoen din. Bytt mellom visning og redigering, kopier med ett klikk, velg språk eller automatisk gjenkjenning, og formater JSON, SQL eller Monaco-støttet kode.", + "Kunne ikke laste utdrag fra serveren.", + "Kunne ikke synkronisere med serveren.", + ), + "sv": ( + "Spara utdrag i webbläsaren eller logga in för att synka med ditt konto. Växla mellan visning och redigering, kopiera med ett klick, välj språk eller autoupptäckt, och formatera JSON, SQL eller Monaco-stödd kod.", + "Kunde inte läsa in utdrag från servern.", + "Kunde inte synkronisera med servern.", + ), + "af": ( + "Bewaar snippette in jou blaaier of meld aan om dit met jou rekening te sinkroniseer. Wissel tussen bekyk en wysig, kopieer met een klik, kies taal of outo-opsporing, en formatteer JSON, SQL of Monaco-ondersteunde kode.", + "Kon nie snippette van die bediener laai nie.", + "Kon nie met die bediener sinkroniseer nie.", + ), + "ca": ( + "Emmagatzema fragments al navegador o inicia sessió per sincronitzar-los amb el teu compte. Canvia entre veure i editar, copia amb un clic, tria idioma o detecció automàtica i formata JSON, SQL o codi compatible amb Monaco.", + "No s'han pogut carregar els fragments des del servidor.", + "No s'ha pogut sincronitzar amb el servidor.", + ), +} + + +def eu_fr( + loc: str, + *, + title: str, + snippets: str, + new_sn: str, + new_t: str, + welcome: str, + c1: str, + c2: str, + sph: str, + sar: str, + emp: str, + st: str, + stp: str, + lng: str, + det: str, + bdg: str, + vw: str, + ed: str, + cp: str, + cpd: str, + fm: str, + cn: str, + dt: str, + dd: str, + dok: str, + ear: str, + eld: str, + t_cpd: str, + t_cpf: str, + t_ntc: str, + t_fmt: str, + t_ff: str, + t_fj: str, + t_fu: str, + t_del: str, +) -> dict: + s0, s1, s2 = SUB[loc] + return pack( + loc, + title=title, + snippets=snippets, + new_snippet=new_sn, + new_title=new_t, + welcome=welcome, + c1=c1, + c2=c2, + search_ph=sph, + search_ar=sar, + empty=emp, + st=st, + st_ph=stp, + lang=lng, + det=det, + badge=bdg, + view=vw, + edit=ed, + copy=cp, + copied=cpd, + fmt=fm, + cancel=cn, + del_t=dt, + del_d=dd, + del_ok=dok, + ed_ar=ear, + ed_ld=eld, + tc=t_cpd, + tcf=t_cpf, + tnt=t_ntc, + tfm=t_fmt, + tff=t_ff, + tfj=t_fj, + tfu=t_fu, + tdel=t_del, + tlf=s1, + tsf=s2, + sub=s0, + ) + + +PATCHES: dict[str, dict] = {} + +PATCHES["fr"] = eu_fr( + "fr", + title="Gestionnaire d'extraits de code", + snippets="Extraits", + new_sn="Nouvel extrait", + new_t="Nouvel extrait", + welcome="Bienvenue", + c1="// Bienvenue dans Extraits de code", + c2="// Essayez Affichage / Édition, Copier, Formater et langage auto", + sph="Rechercher des extraits…", + sar="Rechercher des extraits", + emp="Aucun extrait ne correspond à votre recherche.", + st="Titre", + stp="Nom de l'extrait", + lng="Langage", + det="Coloration syntaxique : {lang}", + bdg="Auto · {lang}", + vw="Lecture", + ed="Édition", + cp="Copier", + cpd="Copié", + fm="Formater", + cn="Annuler", + dt="Supprimer l'extrait ?", + dd="« {title} » sera supprimé de ce navigateur.", + dok="Supprimer", + ear="Éditeur de code de l'extrait", + eld="Chargement de l'éditeur…", + t_cpd="Copié dans le presse-papiers", + t_cpf="Impossible de copier", + t_ntc="Rien à copier", + t_fmt="Formaté", + t_ff="Impossible de formater", + t_fj="JSON invalide — corrigez la syntaxe d'abord", + t_fu="Pas de formateur pour ce langage — essayez JSON ou SQL, ou passez en mode édition", + t_del="Extrait supprimé", +) + +PATCHES["de"] = eu_fr( + "de", + title="Code-Snippet-Manager", + snippets="Snippets", + new_sn="Neues Snippet", + new_t="Neues Snippet", + welcome="Willkommen", + c1="// Willkommen bei Code-Snippets", + c2="// Testen Sie Ansicht vs. Bearbeiten, Kopieren, Formatieren und Auto-Sprache", + sph="Snippets durchsuchen…", + sar="Snippets durchsuchen", + emp="Keine Snippets entsprechen Ihrer Suche.", + st="Titel", + stp="Snippet-Name", + lng="Sprache", + det="Hervorhebung als {lang}", + bdg="Auto · {lang}", + vw="Ansicht", + ed="Bearbeiten", + cp="Kopieren", + cpd="Kopiert", + fm="Formatieren", + cn="Abbrechen", + dt="Snippet löschen?", + dd="„{title}“ wird aus diesem Browser entfernt.", + dok="Löschen", + ear="Snippet-Code-Editor", + eld="Editor wird geladen…", + t_cpd="In die Zwischenablage kopiert", + t_cpf="Kopieren fehlgeschlagen", + t_ntc="Nichts zu kopieren", + t_fmt="Formatiert", + t_ff="Formatierung fehlgeschlagen", + t_fj="Ungültiges JSON — zuerst Syntax beheben", + t_fu="Kein Formatierer für diese Sprache — JSON oder SQL versuchen oder Bearbeiten", + t_del="Snippet gelöscht", +) + +PATCHES["es"] = eu_fr( + "es", + title="Administrador de fragmentos de código", + snippets="Fragmentos", + new_sn="Nuevo fragmento", + new_t="Nuevo fragmento", + welcome="Bienvenido", + c1="// Bienvenido a Fragmentos de código", + c2="// Prueba Ver vs Editar, Copiar, Formatear y idioma automático", + sph="Buscar fragmentos…", + sar="Buscar fragmentos", + emp="Ningún fragmento coincide con tu búsqueda.", + st="Título", + stp="Nombre del fragmento", + lng="Lenguaje", + det="Resaltado como {lang}", + bdg="Auto · {lang}", + vw="Ver", + ed="Editar", + cp="Copiar", + cpd="Copiado", + fm="Formatear", + cn="Cancelar", + dt="¿Eliminar fragmento?", + dd="«{title}» se eliminará de este navegador.", + dok="Eliminar", + ear="Editor de código del fragmento", + eld="Cargando editor…", + t_cpd="Copiado al portapapeles", + t_cpf="No se pudo copiar", + t_ntc="Nada que copiar", + t_fmt="Formateado", + t_ff="No se pudo formatear", + t_fj="JSON no válido — corrige la sintaxis primero", + t_fu="Sin formateador para este lenguaje — prueba JSON o SQL, o modo edición", + t_del="Fragmento eliminado", +) + +PATCHES["it"] = eu_fr( + "it", + title="Gestore snippet di codice", + snippets="Snippet", + new_sn="Nuovo snippet", + new_t="Nuovo snippet", + welcome="Benvenuto", + c1="// Benvenuto negli snippet di codice", + c2="// Prova Visualizza vs Modifica, Copia, Formatta e lingua automatica", + sph="Cerca snippet…", + sar="Cerca snippet", + emp="Nessuno snippet corrisponde alla ricerca.", + st="Titolo", + stp="Nome snippet", + lng="Linguaggio", + det="Evidenziazione come {lang}", + bdg="Auto · {lang}", + vw="Visualizza", + ed="Modifica", + cp="Copia", + cpd="Copiato", + fm="Formatta", + cn="Annulla", + dt="Eliminare lo snippet?", + dd="«{title}» verrà rimosso da questo browser.", + dok="Elimina", + ear="Editor codice snippet", + eld="Caricamento editor…", + t_cpd="Copiato negli appunti", + t_cpf="Impossibile copiare", + t_ntc="Niente da copiare", + t_fmt="Formattato", + t_ff="Impossibile formattare", + t_fj="JSON non valido — correggi prima la sintassi", + t_fu="Nessun formattatore per questo linguaggio — prova JSON o SQL o la modalità modifica", + t_del="Snippet eliminato", +) + +PATCHES["nl"] = eu_fr( + "nl", + title="Codefragmentbeheer", + snippets="Fragmenten", + new_sn="Nieuw fragment", + new_t="Nieuw fragment", + welcome="Welkom", + c1="// Welkom bij codefragmenten", + c2="// Probeer bekijken vs bewerken, kopiëren, formatteren en automatische taal", + sph="Fragmenten zoeken…", + sar="Fragmenten zoeken", + emp="Geen fragmenten komen overeen met je zoekopdracht.", + st="Titel", + stp="Fragmentnaam", + lng="Taal", + det="Markering als {lang}", + bdg="Auto · {lang}", + vw="Bekijken", + ed="Bewerken", + cp="Kopiëren", + cpd="Gekopieerd", + fm="Formatteren", + cn="Annuleren", + dt="Fragment verwijderen?", + dd="‘{title}’ wordt uit deze browser verwijderd.", + dok="Verwijderen", + ear="Fragmentcode-editor", + eld="Editor laden…", + t_cpd="Gekopieerd naar klembord", + t_cpf="Kopiëren mislukt", + t_ntc="Niets om te kopiëren", + t_fmt="Geformatteerd", + t_ff="Formatteren mislukt", + t_fj="Ongeldige JSON — los eerst de syntaxis op", + t_fu="Geen formatter voor deze taal — probeer JSON of SQL, of bewerkingsmodus", + t_del="Fragment verwijderd", +) + +PATCHES["pl"] = eu_fr( + "pl", + title="Menedżer fragmentów kodu", + snippets="Fragmenty", + new_sn="Nowy fragment", + new_t="Nowy fragment", + welcome="Witamy", + c1="// Witamy we fragmentach kodu", + c2="// Wypróbuj widok vs edycja, kopiowanie, formatowanie i auto-język", + sph="Szukaj fragmentów…", + sar="Szukaj fragmentów", + emp="Brak fragmentów pasujących do wyszukiwania.", + st="Tytuł", + stp="Nazwa fragmentu", + lng="Język", + det="Podświetlanie: {lang}", + bdg="Auto · {lang}", + vw="Podgląd", + ed="Edycja", + cp="Kopiuj", + cpd="Skopiowano", + fm="Formatuj", + cn="Anuluj", + dt="Usunąć fragment?", + dd="„{title}” zostanie usunięty z tej przeglądarki.", + dok="Usuń", + ear="Edytor kodu fragmentu", + eld="Ładowanie edytora…", + t_cpd="Skopiowano do schowka", + t_cpf="Nie udało się skopiować", + t_ntc="Nic do skopiowania", + t_fmt="Sformatowano", + t_ff="Nie udało się sformatować", + t_fj="Nieprawidłowy JSON — najpierw popraw składnię", + t_fu="Brak formatu dla tego języka — spróbuj JSON lub SQL albo tryb edycji", + t_del="Fragment usunięty", +) + +PATCHES["pt"] = eu_fr( + "pt", + title="Gestor de excertos de código", + snippets="Excertos", + new_sn="Novo excerto", + new_t="Novo excerto", + welcome="Bem-vindo", + c1="// Bem-vindo aos excertos de código", + c2="// Experimente Ver vs Editar, Copiar, Formatar e idioma automático", + sph="Pesquisar excertos…", + sar="Pesquisar excertos", + emp="Nenhum excerto corresponde à pesquisa.", + st="Título", + stp="Nome do excerto", + lng="Linguagem", + det="Realce como {lang}", + bdg="Auto · {lang}", + vw="Ver", + ed="Editar", + cp="Copiar", + cpd="Copiado", + fm="Formatar", + cn="Cancelar", + dt="Eliminar excerto?", + dd="«{title}» será removido deste navegador.", + dok="Eliminar", + ear="Editor de código do excerto", + eld="A carregar editor…", + t_cpd="Copiado para a área de transferência", + t_cpf="Não foi possível copiar", + t_ntc="Nada para copiar", + t_fmt="Formatado", + t_ff="Não foi possível formatar", + t_fj="JSON inválido — corrija a sintaxe primeiro", + t_fu="Sem formatador para esta linguagem — tente JSON ou SQL, ou modo de edição", + t_del="Excerto eliminado", +) + +PATCHES["pt-BR"] = eu_fr( + "pt-BR", + title="Gerenciador de trechos de código", + snippets="Trechos", + new_sn="Novo trecho", + new_t="Novo trecho", + welcome="Bem-vindo", + c1="// Bem-vindo aos trechos de código", + c2="// Experimente Ver vs Editar, Copiar, Formatar e idioma automático", + sph="Buscar trechos…", + sar="Buscar trechos", + emp="Nenhum trecho corresponde à busca.", + st="Título", + stp="Nome do trecho", + lng="Linguagem", + det="Realce como {lang}", + bdg="Auto · {lang}", + vw="Ver", + ed="Editar", + cp="Copiar", + cpd="Copiado", + fm="Formatar", + cn="Cancelar", + dt="Excluir trecho?", + dd="“{title}” será removido deste navegador.", + dok="Excluir", + ear="Editor de código do trecho", + eld="Carregando editor…", + t_cpd="Copiado para a área de transferência", + t_cpf="Não foi possível copiar", + t_ntc="Nada para copiar", + t_fmt="Formatado", + t_ff="Não foi possível formatar", + t_fj="JSON inválido — corrija a sintaxe primeiro", + t_fu="Sem formatador para esta linguagem — tente JSON ou SQL, ou modo de edição", + t_del="Trecho excluído", +) + +PATCHES["ru"] = eu_fr( + "ru", + title="Менеджер фрагментов кода", + snippets="Сниппеты", + new_sn="Новый сниппет", + new_t="Новый сниппет", + welcome="Добро пожаловать", + c1="// Добро пожаловать в фрагменты кода", + c2="// Попробуйте просмотр и правку, копирование, форматирование и автоязык", + sph="Поиск сниппетов…", + sar="Поиск сниппетов", + emp="Нет сниппетов по запросу.", + st="Заголовок", + stp="Имя сниппета", + lng="Язык", + det="Подсветка: {lang}", + bdg="Авто · {lang}", + vw="Просмотр", + ed="Правка", + cp="Копировать", + cpd="Скопировано", + fm="Форматировать", + cn="Отмена", + dt="Удалить сниппет?", + dd="«{title}» будет удалён из этого браузера.", + dok="Удалить", + ear="Редактор кода сниппета", + eld="Загрузка редактора…", + t_cpd="Скопировано в буфер", + t_cpf="Не удалось скопировать", + t_ntc="Нечего копировать", + t_fmt="Отформатировано", + t_ff="Не удалось отформатировать", + t_fj="Неверный JSON — сначала исправьте синтаксис", + t_fu="Нет форматтера для этого языка — попробуйте JSON или SQL или режим правки", + t_del="Сниппет удалён", +) + +PATCHES["uk"] = eu_fr( + "uk", + title="Менеджер фрагментів коду", + snippets="Фрагменти", + new_sn="Новий фрагмент", + new_t="Новий фрагмент", + welcome="Ласкаво просимо", + c1="// Ласкаво просимо до фрагментів коду", + c2="// Спробуйте перегляд і редагування, копіювання, форматування та авто-мову", + sph="Пошук фрагментів…", + sar="Пошук фрагментів", + emp="Немає фрагментів за запитом.", + st="Заголовок", + stp="Назва фрагмента", + lng="Мова", + det="Підсвічування: {lang}", + bdg="Авто · {lang}", + vw="Перегляд", + ed="Редагування", + cp="Копіювати", + cpd="Скопійовано", + fm="Форматувати", + cn="Скасувати", + dt="Видалити фрагмент?", + dd="«{title}» буде вилучено з цього браузера.", + dok="Видалити", + ear="Редактор коду фрагмента", + eld="Завантаження редактора…", + t_cpd="Скопійовано в буфер", + t_cpf="Не вдалося скопіювати", + t_ntc="Нічого копіювати", + t_fmt="Відформатовано", + t_ff="Не вдалося відформатувати", + t_fj="Некоректний JSON — спочатку виправте синтаксис", + t_fu="Немає форматера для цієї мови — спробуйте JSON або SQL або режим редагування", + t_del="Фрагмент видалено", +) + +PATCHES["tr"] = eu_fr( + "tr", + title="Kod parçacığı yöneticisi", + snippets="Parçacıklar", + new_sn="Yeni parçacık", + new_t="Yeni parçacık", + welcome="Hoş geldiniz", + c1="// Kod parçacıklarına hoş geldiniz", + c2="// Görüntüle vs düzenle, kopyala, biçimlendir ve otomatik dil deneyin", + sph="Parçacık ara…", + sar="Parçacık ara", + emp="Aramanızla eşleşen parçacık yok.", + st="Başlık", + stp="Parçacık adı", + lng="Dil", + det="Vurgulama: {lang}", + bdg="Otomatik · {lang}", + vw="Görüntüle", + ed="Düzenle", + cp="Kopyala", + cpd="Kopyalandı", + fm="Biçimlendir", + cn="İptal", + dt="Parçacık silinsin mi?", + dd="“{title}” bu tarayıcıdan kaldırılacak.", + dok="Sil", + ear="Parçacık kod düzenleyici", + eld="Düzenleyici yükleniyor…", + t_cpd="Panoya kopyalandı", + t_cpf="Kopyalanamadı", + t_ntc="Kopyalanacak bir şey yok", + t_fmt="Biçimlendirildi", + t_ff="Biçimlendirilemedi", + t_fj="Geçersiz JSON — önce sözdizimini düzeltin", + t_fu="Bu dil için biçimlendirici yok — JSON veya SQL deneyin ya da düzenleme modunu kullanın", + t_del="Parçacık silindi", +) + +PATCHES["cs"] = eu_fr( + "cs", + title="Správce úryvků kódu", + snippets="Úryvky", + new_sn="Nový úryvek", + new_t="Nový úryvek", + welcome="Vítejte", + c1="// Vítejte u úryvků kódu", + c2="// Zkuste zobrazení vs úpravy, kopírování, formátování a automatický jazyk", + sph="Hledat úryvky…", + sar="Hledat úryvky", + emp="Žádný úryvek neodpovídá hledání.", + st="Název", + stp="Název úryvku", + lng="Jazyk", + det="Zvýraznění: {lang}", + bdg="Auto · {lang}", + vw="Zobrazit", + ed="Upravit", + cp="Kopírovat", + cpd="Zkopírováno", + fm="Formátovat", + cn="Zrušit", + dt="Smazat úryvek?", + dd="„{title}“ bude odebrán z tohoto prohlížeče.", + dok="Smazat", + ear="Editor kódu úryvku", + eld="Načítání editoru…", + t_cpd="Zkopírováno do schránky", + t_cpf="Kopírování se nezdařilo", + t_ntc="Nic ke kopírování", + t_fmt="Naformátováno", + t_ff="Formátování se nezdařilo", + t_fj="Neplatné JSON — nejdřív opravte syntaxi", + t_fu="Pro tento jazyk není formátovač — zkuste JSON nebo SQL nebo režim úprav", + t_del="Úryvek smazán", +) + +PATCHES["el"] = eu_fr( + "el", + title="Διαχειριστής αποσπασμάτων κώδικα", + snippets="Αποσπάσματα", + new_sn="Νέο απόσπασμα", + new_t="Νέο απόσπασμα", + welcome="Καλώς ήρθατε", + c1="// Καλώς ήρθατε στα αποσπάσματα κώδικα", + c2="// Δοκιμάστε προβολή vs επεξεργασία, αντιγραφή, μορφοποίηση και αυτόματη γλώσσα", + sph="Αναζήτηση αποσπασμάτων…", + sar="Αναζήτηση αποσπασμάτων", + emp="Κανένα απόσπασμα δεν ταιριάζει στην αναζήτηση.", + st="Τίτλος", + stp="Όνομα αποσπάσματος", + lng="Γλώσσα", + det="Τονισμός ως {lang}", + bdg="Αυτόματο · {lang}", + vw="Προβολή", + ed="Επεξεργασία", + cp="Αντιγραφή", + cpd="Αντιγράφηκε", + fm="Μορφοποίηση", + cn="Άκυρο", + dt="Διαγραφή αποσπάσματος;", + dd="Το «{title}» θα αφαιρεθεί από αυτόν τον browser.", + dok="Διαγραφή", + ear="Επεξεργαστής κώδικα αποσπάσματος", + eld="Φόρτωση επεξεργαστή…", + t_cpd="Αντιγράφηκε στο πρόχειρο", + t_cpf="Αδυναμία αντιγραφής", + t_ntc="Τίποτα για αντιγραφή", + t_fmt="Μορφοποιήθηκε", + t_ff="Αδυναμία μορφοποίησης", + t_fj="Μη έγκυρο JSON — διορθώστε πρώτα τη σύνταξη", + t_fu="Δεν υπάρχει μορφοποιητής για αυτή τη γλώσσα — δοκιμάστε JSON ή SQL ή λειτουργία επεξεργασίας", + t_del="Το απόσπασμα διαγράφηκε", +) + +PATCHES["da"] = eu_fr( + "da", + title="Kodestykke-manager", + snippets="Snippets", + new_sn="Nyt snippet", + new_t="Nyt snippet", + welcome="Velkommen", + c1="// Velkommen til kodestykker", + c2="// Prøv visning vs redigering, kopiér, formatér og automatisk sprog", + sph="Søg i snippets…", + sar="Søg i snippets", + emp="Ingen snippets matcher din søgning.", + st="Titel", + stp="Snippet-navn", + lng="Sprog", + det="Fremhævning som {lang}", + bdg="Auto · {lang}", + vw="Vis", + ed="Rediger", + cp="Kopiér", + cpd="Kopieret", + fm="Formatér", + cn="Annuller", + dt="Slet snippet?", + dd="«{title}» fjernes fra denne browser.", + dok="Slet", + ear="Snippet-kodeeditor", + eld="Indlæser editor…", + t_cpd="Kopieret til udklipsholder", + t_cpf="Kunne ikke kopiere", + t_ntc="Intet at kopiere", + t_fmt="Formateret", + t_ff="Kunne ikke formatere", + t_fj="Ugyldig JSON — ret syntaksen først", + t_fu="Ingen formatter til dette sprog — prøv JSON eller SQL eller redigeringstilstand", + t_del="Snippet slettet", +) + +PATCHES["nb"] = eu_fr( + "nb", + title="Kodeutklipp-administrator", + snippets="Utdrag", + new_sn="Nytt utdrag", + new_t="Nytt utdrag", + welcome="Velkommen", + c1="// Velkommen til kodeutdrag", + c2="// Prøv visning vs redigering, kopier, formater og automatisk språk", + sph="Søk i utdrag…", + sar="Søk i utdrag", + emp="Ingen utdrag samsvarer med søket.", + st="Tittel", + stp="Navn på utdrag", + lng="Språk", + det="Utheving som {lang}", + bdg="Auto · {lang}", + vw="Vis", + ed="Rediger", + cp="Kopier", + cpd="Kopiert", + fm="Formater", + cn="Avbryt", + dt="Slette utdrag?", + dd="«{title}» fjernes fra denne nettleseren.", + dok="Slett", + ear="Kodeeditor for utdrag", + eld="Laster editor…", + t_cpd="Kopiert til utklippstavle", + t_cpf="Kunne ikke kopiere", + t_ntc="Ingenting å kopiere", + t_fmt="Formatert", + t_ff="Kunne ikke formatere", + t_fj="Ugyldig JSON — rett syntaksen først", + t_fu="Ingen formattering for dette språket — prøv JSON eller SQL eller redigeringsmodus", + t_del="Utdrag slettet", +) + +PATCHES["sv"] = eu_fr( + "sv", + title="Hanterare för kodutdrag", + snippets="Utdrag", + new_sn="Nytt utdrag", + new_t="Nytt utdrag", + welcome="Välkommen", + c1="// Välkommen till kodutdrag", + c2="// Prova visning vs redigera, kopiera, formatera och automatiskt språk", + sph="Sök utdrag…", + sar="Sök utdrag", + emp="Inga utdrag matchar sökningen.", + st="Titel", + stp="Utdragsnamn", + lng="Språk", + det="Syntaxmarkering som {lang}", + bdg="Auto · {lang}", + vw="Visa", + ed="Redigera", + cp="Kopiera", + cpd="Kopierat", + fm="Formatera", + cn="Avbryt", + dt="Ta bort utdrag?", + dd="”{title}” tas bort från den här webbläsaren.", + dok="Ta bort", + ear="Kodredigerare för utdrag", + eld="Laddar redigerare…", + t_cpd="Kopierat till urklipp", + t_cpf="Kunde inte kopiera", + t_ntc="Inget att kopiera", + t_fmt="Formaterat", + t_ff="Kunde inte formatera", + t_fj="Ogiltig JSON — åtgärda syntaxen först", + t_fu="Ingen formaterare för detta språk — prova JSON eller SQL eller redigeringsläge", + t_del="Utdrag borttaget", +) + +PATCHES["af"] = eu_fr( + "af", + title="Kodesnippet-bestuurder", + snippets="Snippette", + new_sn="Nuwe snippet", + new_t="Nuwe snippet", + welcome="Welkom", + c1="// Welkom by kodesnippette", + c2="// Probeer bekyk vs wysig, kopieer, formateer en outo-taal", + sph="Soek snippette…", + sar="Soek snippette", + emp="Geen snippette pas by jou soektog nie.", + st="Titel", + stp="Snippett-naam", + lng="Taal", + det="Beklemtoning as {lang}", + bdg="Outo · {lang}", + vw="Bekyk", + ed="Wysig", + cp="Kopieer", + cpd="Gekopieer", + fm="Formateer", + cn="Kanselleer", + dt="Snippett uitvee?", + dd="“{title}” sal uit hierdie blaaier verwyder word.", + dok="Vee uit", + ear="Snippett-kode-redigeerder", + eld="Laai redigeerder…", + t_cpd="Gekopieer na knipbord", + t_cpf="Kon nie kopieer nie", + t_ntc="Niks om te kopieer nie", + t_fmt="Geformatteer", + t_ff="Kon nie formateer nie", + t_fj="Ongeldige JSON — herstel eers sintaksis", + t_fu="Geen formateerder vir hierdie taal nie — probeer JSON of SQL of wysig-modus", + t_del="Snippett uitgevee", +) + +PATCHES["ca"] = eu_fr( + "ca", + title="Gestor de fragments de codi", + snippets="Fragments", + new_sn="Fragment nou", + new_t="Fragment nou", + welcome="Benvingut", + c1="// Benvingut als fragments de codi", + c2="// Prova veure vs editar, copia, formata i idioma automàtic", + sph="Cerca fragments…", + sar="Cerca fragments", + emp="Cap fragment no coincideix amb la cerca.", + st="Títol", + stp="Nom del fragment", + lng="Llenguatge", + det="Ressaltat com a {lang}", + bdg="Auto · {lang}", + vw="Veure", + ed="Editar", + cp="Copiar", + cpd="Copiat", + fm="Formatejar", + cn="Cancel·la", + dt="Eliminar el fragment?", + dd="«{title}» se suprimirà d'aquest navegador.", + dok="Eliminar", + ear="Editor de codi del fragment", + eld="Carregant l'editor…", + t_cpd="Copiat al porta-retalls", + t_cpf="No s'ha pogut copiar", + t_ntc="Res a copiar", + t_fmt="Formatejat", + t_ff="No s'ha pogut formatejar", + t_fj="JSON no vàlid — corregeix la sintaxi primer", + t_fu="Sense formatador per a aquest llenguatge — prova JSON o SQL o mode edició", + t_del="Fragment eliminat", +) + +# Arabic, Persian, CJK, Vietnamese, Indonesian, Malay — same eu_fr pattern with loc +PATCHES["ar"] = eu_fr( + "ar", + title="مدير مقاطع الشيفرة", + snippets="المقاطع", + new_sn="مقطع جديد", + new_t="مقطع جديد", + welcome="مرحبًا", + c1="// مرحبًا بك في مقاطع الشيفرة", + c2="// جرّب العرض مقابل التحرير، النسخ، التنسيق واللغة التلقائية", + sph="البحث في المقاطع…", + sar="البحث في المقاطع", + emp="لا توجد مقاطع تطابق بحثك.", + st="العنوان", + stp="اسم المقطع", + lng="اللغة", + det="تمييز بناء الجملة: {lang}", + bdg="تلقائي · {lang}", + vw="عرض", + ed="تحرير", + cp="نسخ", + cpd="تم النسخ", + fm="تنسيق", + cn="إلغاء", + dt="حذف المقطع؟", + dd="سيتم إزالة «{title}» من هذا المتصفح.", + dok="حذف", + ear="محرر شيفرة المقطع", + eld="جاري تحميل المحرر…", + t_cpd="تم النسخ إلى الحافظة", + t_cpf="تعذّر النسخ", + t_ntc="لا يوجد شيء للنسخ", + t_fmt="تم التنسيق", + t_ff="تعذّر التنسيق", + t_fj="JSON غير صالح — صحّح الصياغة أولًا", + t_fu="لا يوجد مُنسّق لهذه اللغة — جرّب JSON أو SQL أو وضع التحرير", + t_del="تم حذف المقطع", +) + +PATCHES["fa"] = eu_fr( + "fa", + title="مدیریت قطعه‌کد", + snippets="قطعه‌کدها", + new_sn="قطعه‌کد جدید", + new_t="قطعه‌کد جدید", + welcome="خوش آمدید", + c1="// به قطعه‌کدها خوش آمدید", + c2="// حالت مشاهده در برابر ویرایش، رونوشت، قالب‌بندی و زبان خودکار را امتحان کنید", + sph="جستجوی قطعه‌کدها…", + sar="جستجوی قطعه‌کدها", + emp="هیچ قطعه‌ای با جستجو هم‌خوان نیست.", + st="عنوان", + stp="نام قطعه", + lng="زبان", + det="برجسته‌سازی به‌صورت {lang}", + bdg="خودکار · {lang}", + vw="مشاهده", + ed="ویرایش", + cp="رونوشت", + cpd="رونوشت شد", + fm="قالب‌بندی", + cn="لغو", + dt="قطعه‌کد حذف شود؟", + dd="«{title}» از این مرورگر حذف می‌شود.", + dok="حذف", + ear="ویرایشگر کد قطعه", + eld="در حال بارگذاری ویرایشگر…", + t_cpd="در بریده‌دان رونوشت شد", + t_cpf="رونوشت ممکن نشد", + t_ntc="چیزی برای رونوشت نیست", + t_fmt="قالب‌بندی شد", + t_ff="قالب‌بندی ممکن نشد", + t_fj="JSON نامعتبر — نخست نحو را درست کنید", + t_fu="قالب‌بند برای این زبان نیست — JSON یا SQL یا حالت ویرایش را امتحان کنید", + t_del="قطعه حذف شد", +) + +PATCHES["zh"] = eu_fr( + "zh", + title="代码片段管理器", + snippets="片段", + new_sn="新建片段", + new_t="新建片段", + welcome="欢迎", + c1="// 欢迎使用代码片段", + c2="// 试试查看/编辑、复制、格式化与自动语言", + sph="搜索片段…", + sar="搜索片段", + emp="没有匹配的片段。", + st="标题", + stp="片段名称", + lng="语言", + det="高亮为 {lang}", + bdg="自动 · {lang}", + vw="查看", + ed="编辑", + cp="复制", + cpd="已复制", + fm="格式化", + cn="取消", + dt="删除片段?", + dd="将从此浏览器移除「{title}」。", + dok="删除", + ear="片段代码编辑器", + eld="正在加载编辑器…", + t_cpd="已复制到剪贴板", + t_cpf="无法复制", + t_ntc="没有可复制的内容", + t_fmt="已格式化", + t_ff="无法格式化", + t_fj="JSON 无效 — 请先修正语法", + t_fu="此语言没有格式化程序 — 请尝试 JSON 或 SQL,或使用编辑模式", + t_del="片段已删除", +) + +PATCHES["ja"] = eu_fr( + "ja", + title="コードスニペットマネージャー", + snippets="スニペット", + new_sn="新しいスニペット", + new_t="新しいスニペット", + welcome="ようこそ", + c1="// コードスニペットへようこそ", + c2="// 表示と編集、コピー、整形、自動言語をお試しください", + sph="スニペットを検索…", + sar="スニペットを検索", + emp="一致するスニペットがありません。", + st="タイトル", + stp="スニペット名", + lng="言語", + det="ハイライト: {lang}", + bdg="自動 · {lang}", + vw="表示", + ed="編集", + cp="コピー", + cpd="コピーしました", + fm="整形", + cn="キャンセル", + dt="スニペットを削除しますか?", + dd="「{title}」はこのブラウザから削除されます。", + dok="削除", + ear="スニペットコードエディター", + eld="エディターを読み込み中…", + t_cpd="クリップボードにコピーしました", + t_cpf="コピーできませんでした", + t_ntc="コピーする内容がありません", + t_fmt="整形しました", + t_ff="整形できませんでした", + t_fj="無効な JSON — 先に構文を修正してください", + t_fu="この言語用の整形はありません — JSON か SQL、または編集モードをお試しください", + t_del="スニペットを削除しました", +) + +PATCHES["ko"] = eu_fr( + "ko", + title="코드 스니펫 관리자", + snippets="스니펫", + new_sn="새 스니펫", + new_t="새 스니펫", + welcome="환영합니다", + c1="// 코드 스니펫에 오신 것을 환영합니다", + c2="// 보기/편집, 복사, 서식 및 자동 언어를 사용해 보세요", + sph="스니펫 검색…", + sar="스니펫 검색", + emp="검색과 일치하는 스니펫이 없습니다.", + st="제목", + stp="스니펫 이름", + lng="언어", + det="강조: {lang}", + bdg="자동 · {lang}", + vw="보기", + ed="편집", + cp="복사", + cpd="복사됨", + fm="서식", + cn="취소", + dt="스니펫을 삭제할까요?", + dd="‘{title}’이(가) 이 브라우저에서 제거됩니다.", + dok="삭제", + ear="스니펫 코드 편집기", + eld="편집기 로드 중…", + t_cpd="클립보드에 복사됨", + t_cpf="복사할 수 없습니다", + t_ntc="복사할 내용이 없습니다", + t_fmt="서식이 적용되었습니다", + t_ff="서식을 적용할 수 없습니다", + t_fj="잘못된 JSON — 먼저 구문을 수정하세요", + t_fu="이 언어용 서식 도구가 없습니다 — JSON 또는 SQL 또는 편집 모드를 사용해 보세요", + t_del="스니펫이 삭제되었습니다", +) + +PATCHES["vi"] = eu_fr( + "vi", + title="Trình quản lý đoạn mã", + snippets="Đoạn mã", + new_sn="Đoạn mới", + new_t="Đoạn mới", + welcome="Chào mừng", + c1="// Chào mừng đến với đoạn mã", + c2="// Thử xem so với sửa, sao chép, định dạng và ngôn ngữ tự động", + sph="Tìm đoạn mã…", + sar="Tìm đoạn mã", + emp="Không có đoạn mã khớp tìm kiếm.", + st="Tiêu đề", + stp="Tên đoạn", + lng="Ngôn ngữ", + det="Tô sáng dạng {lang}", + bdg="Tự động · {lang}", + vw="Xem", + ed="Sửa", + cp="Sao chép", + cpd="Đã sao chép", + fm="Định dạng", + cn="Hủy", + dt="Xóa đoạn mã?", + dd="“{title}” sẽ bị xóa khỏi trình duyệt này.", + dok="Xóa", + ear="Trình sửa mã đoạn", + eld="Đang tải trình sửa…", + t_cpd="Đã sao chép vào bảng tạm", + t_cpf="Không thể sao chép", + t_ntc="Không có gì để sao chép", + t_fmt="Đã định dạng", + t_ff="Không thể định dạng", + t_fj="JSON không hợp lệ — sửa cú pháp trước", + t_fu="Không có bộ định dạng cho ngôn ngữ này — thử JSON hoặc SQL hoặc chế độ sửa", + t_del="Đã xóa đoạn mã", +) + +PATCHES["id"] = eu_fr( + "id", + title="Pengelola cuplikan kode", + snippets="Cuplikan", + new_sn="Cuplikan baru", + new_t="Cuplikan baru", + welcome="Selamat datang", + c1="// Selamat datang di cuplikan kode", + c2="// Coba lihat vs sunting, salin, format, dan bahasa otomatis", + sph="Cari cuplikan…", + sar="Cari cuplikan", + emp="Tidak ada cuplikan yang cocok.", + st="Judul", + stp="Nama cuplikan", + lng="Bahasa", + det="Sorotan sebagai {lang}", + bdg="Otomatis · {lang}", + vw="Lihat", + ed="Sunting", + cp="Salin", + cpd="Disalin", + fm="Format", + cn="Batal", + dt="Hapus cuplikan?", + dd="“{title}” akan dihapus dari browser ini.", + dok="Hapus", + ear="Editor kode cuplikan", + eld="Memuat editor…", + t_cpd="Disalin ke papan klip", + t_cpf="Tidak dapat menyalin", + t_ntc="Tidak ada yang disalin", + t_fmt="Sudah diformat", + t_ff="Tidak dapat memformat", + t_fj="JSON tidak valid — perbaiki sintaks dulu", + t_fu="Tidak ada pemformat untuk bahasa ini — coba JSON atau SQL atau mode sunting", + t_del="Cuplikan dihapus", +) + +PATCHES["ms"] = eu_fr( + "ms", + title="Pengurus petikan kod", + snippets="Petikan", + new_sn="Petikan baharu", + new_t="Petikan baharu", + welcome="Selamat datang", + c1="// Selamat datang ke petikan kod", + c2="// Cuba lihat vs sunting, salin, format dan bahasa automatik", + sph="Cari petikan…", + sar="Cari petikan", + emp="Tiada petikan sepadan.", + st="Tajuk", + stp="Nama petikan", + lng="Bahasa", + det="Sorotan sebagai {lang}", + bdg="Auto · {lang}", + vw="Lihat", + ed="Sunting", + cp="Salin", + cpd="Disalin", + fm="Format", + cn="Batal", + dt="Padam petikan?", + dd="“{title}” akan dibuang daripada pelayar ini.", + dok="Padam", + ear="Editor kod petikan", + eld="Memuatkan editor…", + t_cpd="Disalin ke papan keratan", + t_cpf="Tidak dapat menyalin", + t_ntc="Tiada untuk disalin", + t_fmt="Telah diformat", + t_ff="Tidak dapat memformat", + t_fj="JSON tidak sah — betulkan sintaks dahulu", + t_fu="Tiada pemformat untuk bahasa ini — cuba JSON atau SQL atau mod sunting", + t_del="Petikan dipadam", +) + + +def main() -> None: + OUT.write_text( + json.dumps(PATCHES, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + OUT_NAV_DASH.write_text( + json.dumps(NAV_DASH, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + print("Wrote", OUT, "locales:", len(PATCHES)) + print("Wrote", OUT_NAV_DASH, "locales:", len(NAV_DASH)) + + +if __name__ == "__main__": + main() diff --git a/apps/desktop-ui/scripts/json-schema-i18n.json b/apps/desktop-ui/scripts/json-schema-i18n.json new file mode 100644 index 00000000..5d496fdd --- /dev/null +++ b/apps/desktop-ui/scripts/json-schema-i18n.json @@ -0,0 +1,1016 @@ +{ + "fr": { + "Navigation": { + "jsonSchemaGenerator": "Générateur de schéma JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Générateur de schéma JSON", + "description": "Déduisez un schéma JSON et des modèles typés à partir d'un exemple JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Générateur de schéma JSON", + "subtitle": "Collez du JSON valide pour générer un schéma JSON ou des modèles typés. Tout s'exécute dans votre navigateur.", + "languageLabel": "Langue de sortie", + "inputLabel": "Entrée JSON", + "inputPlaceholder": "Collez le JSON ici…", + "outputLabel": "Résultat généré", + "outputPlaceholder": "Le schéma ou les types générés s'afficheront ici…", + "parseError": "JSON invalide", + "clear": "Effacer", + "copy": "Copier", + "copied": "Copié dans le presse-papiers", + "copyFailed": "Impossible de copier", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Schéma JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Chargement de l’éditeur…" + } + }, + "es": { + "Navigation": { + "jsonSchemaGenerator": "Generador de esquema JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Generador de esquema JSON", + "description": "Obtén un esquema JSON y modelos tipados a partir de una muestra JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Generador de esquema JSON", + "subtitle": "Pega JSON válido para generar un esquema JSON o modelos tipados. Todo se ejecuta en tu navegador.", + "languageLabel": "Idioma de salida", + "inputLabel": "Entrada JSON", + "inputPlaceholder": "Pega el JSON aquí…", + "outputLabel": "Salida generada", + "outputPlaceholder": "El esquema o los tipos generados aparecerán aquí…", + "parseError": "JSON no válido", + "clear": "Borrar", + "copy": "Copiar", + "copied": "Copiado al portapapeles", + "copyFailed": "No se pudo copiar", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Esquema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Cargando el editor…" + } + }, + "de": { + "Navigation": { + "jsonSchemaGenerator": "JSON-Schema-Generator" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON-Schema-Generator", + "description": "Leiten Sie ein JSON-Schema und typisierte Modelle aus Beispiel-JSON ab." + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON-Schema-Generator", + "subtitle": "Fügen Sie gültiges JSON ein, um ein JSON-Schema oder typisierte Modelle zu erzeugen. Alles läuft im Browser.", + "languageLabel": "Ausgabesprache", + "inputLabel": "JSON-Eingabe", + "inputPlaceholder": "JSON hier einfügen…", + "outputLabel": "Generierte Ausgabe", + "outputPlaceholder": "Schema oder generierte Typen erscheinen hier…", + "parseError": "Ungültiges JSON", + "clear": "Leeren", + "copy": "Kopieren", + "copied": "In die Zwischenablage kopiert", + "copyFailed": "Kopieren fehlgeschlagen", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-Schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Editor wird geladen…" + } + }, + "it": { + "Navigation": { + "jsonSchemaGenerator": "Generatore di schema JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Generatore di schema JSON", + "description": "Ricava uno schema JSON e modelli tipizzati da un campione JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Generatore di schema JSON", + "subtitle": "Incolla JSON valido per generare uno schema JSON o modelli tipizzati. Tutto viene eseguito nel browser.", + "languageLabel": "Linguaggio di output", + "inputLabel": "Input JSON", + "inputPlaceholder": "Incolla il JSON qui…", + "outputLabel": "Output generato", + "outputPlaceholder": "Lo schema o i tipi generati appariranno qui…", + "parseError": "JSON non valido", + "clear": "Cancella", + "copy": "Copia", + "copied": "Copiato negli appunti", + "copyFailed": "Impossibile copiare", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Schema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Caricamento editor…" + } + }, + "pt": { + "Navigation": { + "jsonSchemaGenerator": "Gerador de esquema JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Gerador de esquema JSON", + "description": "Infira um esquema JSON e modelos tipados a partir de uma amostra JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Gerador de esquema JSON", + "subtitle": "Cole JSON válido para gerar um esquema JSON ou modelos tipados. Tudo corre no seu navegador.", + "languageLabel": "Idioma de saída", + "inputLabel": "Entrada JSON", + "inputPlaceholder": "Cole o JSON aqui…", + "outputLabel": "Saída gerada", + "outputPlaceholder": "O esquema ou os tipos gerados aparecerão aqui…", + "parseError": "JSON inválido", + "clear": "Limpar", + "copy": "Copiar", + "copied": "Copiado para a área de transferência", + "copyFailed": "Não foi possível copiar", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Esquema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "A carregar o editor…" + } + }, + "pt-BR": { + "Navigation": { + "jsonSchemaGenerator": "Gerador de esquema JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Gerador de esquema JSON", + "description": "Infira um esquema JSON e modelos tipados a partir de uma amostra JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Gerador de esquema JSON", + "subtitle": "Cole JSON válido para gerar um esquema JSON ou modelos tipados. Tudo roda no seu navegador.", + "languageLabel": "Idioma de saída", + "inputLabel": "Entrada JSON", + "inputPlaceholder": "Cole o JSON aqui…", + "outputLabel": "Saída gerada", + "outputPlaceholder": "O esquema ou os tipos gerados aparecerão aqui…", + "parseError": "JSON inválido", + "clear": "Limpar", + "copy": "Copiar", + "copied": "Copiado para a área de transferência", + "copyFailed": "Não foi possível copiar", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Esquema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Carregando o editor…" + } + }, + "nl": { + "Navigation": { + "jsonSchemaGenerator": "JSON-schema-generator" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON-schema-generator", + "description": "Leid een JSON-schema en getypeerde modellen af van voorbeeld-JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON-schema-generator", + "subtitle": "Plak geldige JSON om een JSON-schema of getypeerde modellen te genereren. Alles draait in je browser.", + "languageLabel": "Uitvoertaal", + "inputLabel": "JSON-invoer", + "inputPlaceholder": "Plak JSON hier…", + "outputLabel": "Gegenereerde uitvoer", + "outputPlaceholder": "Het schema of de gegenereerde typen verschijnen hier…", + "parseError": "Ongeldige JSON", + "clear": "Wissen", + "copy": "Kopiëren", + "copied": "Gekopieerd naar klembord", + "copyFailed": "Kopiëren mislukt", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Editor laden…" + } + }, + "sv": { + "Navigation": { + "jsonSchemaGenerator": "JSON-schemagenerator" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON-schemagenerator", + "description": "Härled ett JSON-schema och typade modeller från exempel-JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON-schemagenerator", + "subtitle": "Klistra in giltig JSON för att generera JSON-schema eller typade modeller. Allt körs i din webbläsare.", + "languageLabel": "Utdataspråk", + "inputLabel": "JSON-indata", + "inputPlaceholder": "Klistra in JSON här…", + "outputLabel": "Genererad utdata", + "outputPlaceholder": "Schemat eller de genererade typerna visas här…", + "parseError": "Ogiltig JSON", + "clear": "Rensa", + "copy": "Kopiera", + "copied": "Kopierat till urklipp", + "copyFailed": "Kunde inte kopiera", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Läser in redigeraren…" + } + }, + "nb": { + "Navigation": { + "jsonSchemaGenerator": "JSON-skjemagenerator" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON-skjemagenerator", + "description": "Utled et JSON-skjema og typede modeller fra eksempel-JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON-skjemagenerator", + "subtitle": "Lim inn gyldig JSON for å generere JSON-skjema eller typede modeller. Alt kjører i nettleseren.", + "languageLabel": "Utgangsspråk", + "inputLabel": "JSON-inndata", + "inputPlaceholder": "Lim inn JSON her…", + "outputLabel": "Generert utdata", + "outputPlaceholder": "Skjemaet eller de genererte typene vises her…", + "parseError": "Ugyldig JSON", + "clear": "Tøm", + "copy": "Kopier", + "copied": "Kopiert til utklippstavlen", + "copyFailed": "Kunne ikke kopiere", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-skjema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Laster inn redigeringsprogrammet…" + } + }, + "da": { + "Navigation": { + "jsonSchemaGenerator": "JSON-skemagenerator" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON-skemagenerator", + "description": "Udled et JSON-skema og typede modeller fra eksempel-JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON-skemagenerator", + "subtitle": "Indsæt gyldig JSON for at generere JSON-skema eller typede modeller. Alt kører i din browser.", + "languageLabel": "Outputsprog", + "inputLabel": "JSON-input", + "inputPlaceholder": "Indsæt JSON her…", + "outputLabel": "Genereret output", + "outputPlaceholder": "Skemaet eller de genererede typer vises her…", + "parseError": "Ugyldig JSON", + "clear": "Ryd", + "copy": "Kopiér", + "copied": "Kopieret til udklipsholder", + "copyFailed": "Kunne ikke kopiere", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-skema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Indlæser editor…" + } + }, + "af": { + "Navigation": { + "jsonSchemaGenerator": "JSON-skemagenerator" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON-skemagenerator", + "description": "Lei 'n JSON-skema en getiketteerde modelle uit voorbeeld-JSON af." + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON-skemagenerator", + "subtitle": "Plak geldige JSON om 'n JSON-skema of getiketteerde modelle te genereer. Alles loop in jou blaaier.", + "languageLabel": "Uittaal", + "inputLabel": "JSON-invoer", + "inputPlaceholder": "Plak JSON hier…", + "outputLabel": "Gegenereerde uitset", + "outputPlaceholder": "Die skema of gegenereerde tipes verskyn hier…", + "parseError": "Ongeldige JSON", + "clear": "Maak skoon", + "copy": "Kopieer", + "copied": "Na knipbord gekopieer", + "copyFailed": "Kon nie kopieer nie", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-skema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Laai redigeerder…" + } + }, + "cs": { + "Navigation": { + "jsonSchemaGenerator": "Generátor JSON schématu" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Generátor JSON schématu", + "description": "Odvození JSON schématu a typovaných modelů z ukázkového JSONu." + } + } + }, + "JsonSchemaGenerator": { + "title": "Generátor JSON schématu", + "subtitle": "Vložte platný JSON a vygenerujte JSON schéma nebo typované modely. Vše běží v prohlížeči.", + "languageLabel": "Jazyk výstupu", + "inputLabel": "Vstup JSON", + "inputPlaceholder": "Vložte JSON sem…", + "outputLabel": "Vygenerovaný výstup", + "outputPlaceholder": "Schéma nebo vygenerované typy se zobrazí zde…", + "parseError": "Neplatný JSON", + "clear": "Vymazat", + "copy": "Kopírovat", + "copied": "Zkopírováno do schránky", + "copyFailed": "Kopírování se nezdařilo", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON schéma (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Načítání editoru…" + } + }, + "pl": { + "Navigation": { + "jsonSchemaGenerator": "Generator schematu JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Generator schematu JSON", + "description": "Wywnioskuj schemat JSON i modele typowane z przykładowego JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Generator schematu JSON", + "subtitle": "Wklej prawidłowy JSON, aby wygenerować schemat JSON lub modele typowane. Wszystko działa w przeglądarce.", + "languageLabel": "Język wyjścia", + "inputLabel": "Dane wejściowe JSON", + "inputPlaceholder": "Wklej JSON tutaj…", + "outputLabel": "Wygenerowany wynik", + "outputPlaceholder": "Schemat lub wygenerowane typy pojawią się tutaj…", + "parseError": "Nieprawidłowy JSON", + "clear": "Wyczyść", + "copy": "Kopiuj", + "copied": "Skopiowano do schowka", + "copyFailed": "Nie udało się skopiować", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Schemat JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Ładowanie edytora…" + } + }, + "uk": { + "Navigation": { + "jsonSchemaGenerator": "Генератор JSON-схеми" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Генератор JSON-схеми", + "description": "Виведіть JSON-схему та типізовані моделі з прикладу JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Генератор JSON-схеми", + "subtitle": "Вставте коректний JSON, щоб згенерувати JSON-схему або типізовані моделі. Усе працює у вашому браузері.", + "languageLabel": "Мова виводу", + "inputLabel": "Вхід JSON", + "inputPlaceholder": "Вставте JSON сюди…", + "outputLabel": "Згенерований вивід", + "outputPlaceholder": "Схема або згенеровані типи з’являться тут…", + "parseError": "Некоректний JSON", + "clear": "Очистити", + "copy": "Копіювати", + "copied": "Скопійовано в буфер обміну", + "copyFailed": "Не вдалося скопіювати", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-схема (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Завантаження редактора…" + } + }, + "ru": { + "Navigation": { + "jsonSchemaGenerator": "Генератор JSON-схемы" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Генератор JSON-схемы", + "description": "Получите JSON-схему и типизированные модели из примера JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Генератор JSON-схемы", + "subtitle": "Вставьте корректный JSON, чтобы сгенерировать JSON-схему или типизированные модели. Всё выполняется в браузере.", + "languageLabel": "Язык вывода", + "inputLabel": "Ввод JSON", + "inputPlaceholder": "Вставьте JSON сюда…", + "outputLabel": "Сгенерированный вывод", + "outputPlaceholder": "Схема или сгенерированные типы появятся здесь…", + "parseError": "Некорректный JSON", + "clear": "Очистить", + "copy": "Копировать", + "copied": "Скопировано в буфер обмена", + "copyFailed": "Не удалось скопировать", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON-схема (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Загрузка редактора…" + } + }, + "el": { + "Navigation": { + "jsonSchemaGenerator": "Γεννήτρια JSON Schema" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Γεννήτρια JSON Schema", + "description": "Συμπεράνετε ένα JSON Schema και τυποποιημένα μοντέλα από δείγμα JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Γεννήτρια JSON Schema", + "subtitle": "Επικολλήστε έγκυρο JSON για να δημιουργήσετε JSON Schema ή τυποποιημένα μοντέλα. Όλα εκτελούνται στον browser σας.", + "languageLabel": "Γλώσσα εξόδου", + "inputLabel": "Είσοδος JSON", + "inputPlaceholder": "Επικολλήστε JSON εδώ…", + "outputLabel": "Δημιουργημένη έξοδος", + "outputPlaceholder": "Το σχήμα ή οι τύποι θα εμφανιστούν εδώ…", + "parseError": "Μη έγκυρο JSON", + "clear": "Εκκαθάριση", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε στο πρόχειρο", + "copyFailed": "Αποτυχία αντιγραφής", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON Schema (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Φόρτωση επεξεργαστή…" + } + }, + "tr": { + "Navigation": { + "jsonSchemaGenerator": "JSON Şema Oluşturucu" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON Şema Oluşturucu", + "description": "Örnek JSON’dan JSON Şeması ve tipli modeller türetin." + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON Şema Oluşturucu", + "subtitle": "JSON Şeması veya tipli modeller üretmek için geçerli JSON yapıştırın. Her şey tarayıcınızda çalışır.", + "languageLabel": "Çıktı dili", + "inputLabel": "JSON girdisi", + "inputPlaceholder": "JSON’u buraya yapıştırın…", + "outputLabel": "Üretilen çıktı", + "outputPlaceholder": "Üretilen şema veya türler burada görünecek…", + "parseError": "Geçersiz JSON", + "clear": "Temizle", + "copy": "Kopyala", + "copied": "Panoya kopyalandı", + "copyFailed": "Kopyalanamadı", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON Şeması (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Editör yükleniyor…" + } + }, + "ar": { + "Navigation": { + "jsonSchemaGenerator": "مولّد مخطط JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "مولّد مخطط JSON", + "description": "استنتج مخطط JSON ونماذج مُنمّطة من عيّنة JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "مولّد مخطط JSON", + "subtitle": "الصق JSON صالحًا لتوليد مخطط JSON أو نماذج مُنمّطة. كل شيء يعمل في المتصفح.", + "languageLabel": "لغة المخرجات", + "inputLabel": "إدخال JSON", + "inputPlaceholder": "الصق JSON هنا…", + "outputLabel": "المخرجات المُولَّدة", + "outputPlaceholder": "يظهر المخطط أو الأنواع المُولَّدة هنا…", + "parseError": "JSON غير صالح", + "clear": "مسح", + "copy": "نسخ", + "copied": "تم النسخ إلى الحافظة", + "copyFailed": "تعذّر النسخ", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "مخطط JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "جاري تحميل المحرر…" + } + }, + "fa": { + "Navigation": { + "jsonSchemaGenerator": "تولیدکنندهٔ طرح JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "تولیدکنندهٔ طرح JSON", + "description": "از نمونهٔ JSON، طرح JSON و مدل‌های نوع‌دار استنتاج کنید." + } + } + }, + "JsonSchemaGenerator": { + "title": "تولیدکنندهٔ طرح JSON", + "subtitle": "JSON معتبر بچسبانید تا طرح JSON یا مدل‌های نوع‌دار بسازید. همه‌چیز در مرورگر شما اجرا می‌شود.", + "languageLabel": "زبان خروجی", + "inputLabel": "ورودی JSON", + "inputPlaceholder": "JSON را اینجا بچسبانید…", + "outputLabel": "خروجی تولیدشده", + "outputPlaceholder": "طرح یا انواع تولیدشده اینجا نمایش داده می‌شود…", + "parseError": "JSON نامعتبر", + "clear": "پاک کردن", + "copy": "رونوشت", + "copied": "در بریده‌دان رونوشت شد", + "copyFailed": "رونوشت ممکن نشد", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "طرح JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "در حال بارگذاری ویرایشگر…" + } + }, + "id": { + "Navigation": { + "jsonSchemaGenerator": "Generator Skema JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Generator Skema JSON", + "description": "Turunkan skema JSON dan model bertipe dari contoh JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Generator Skema JSON", + "subtitle": "Tempel JSON yang valid untuk menghasilkan skema JSON atau model bertipe. Semua berjalan di peramban Anda.", + "languageLabel": "Bahasa keluaran", + "inputLabel": "Masukan JSON", + "inputPlaceholder": "Tempel JSON di sini…", + "outputLabel": "Keluaran yang dihasilkan", + "outputPlaceholder": "Skema atau tipe yang dihasilkan akan muncul di sini…", + "parseError": "JSON tidak valid", + "clear": "Bersihkan", + "copy": "Salin", + "copied": "Disalin ke papan klip", + "copyFailed": "Tidak dapat menyalin", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Skema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Memuat editor…" + } + }, + "ms": { + "Navigation": { + "jsonSchemaGenerator": "Penjana Skema JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Penjana Skema JSON", + "description": "Inferens skema JSON dan model berjenis daripada sampel JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Penjana Skema JSON", + "subtitle": "Tampal JSON yang sah untuk menjana skema JSON atau model berjenis. Semuanya berjalan dalam pelayar anda.", + "languageLabel": "Bahasa output", + "inputLabel": "Input JSON", + "inputPlaceholder": "Tampal JSON di sini…", + "outputLabel": "Output yang dijana", + "outputPlaceholder": "Skema atau jenis yang dijana akan dipaparkan di sini…", + "parseError": "JSON tidak sah", + "clear": "Kosongkan", + "copy": "Salin", + "copied": "Disalin ke papan keratan", + "copyFailed": "Tidak dapat menyalin", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Skema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Memuatkan editor…" + } + }, + "vi": { + "Navigation": { + "jsonSchemaGenerator": "Trình tạo lược đồ JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Trình tạo lược đồ JSON", + "description": "Suy ra lược đồ JSON và mô hình có kiểu từ mẫu JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Trình tạo lược đồ JSON", + "subtitle": "Dán JSON hợp lệ để tạo lược đồ JSON hoặc mô hình có kiểu. Mọi thứ chạy trong trình duyệt.", + "languageLabel": "Ngôn ngữ đầu ra", + "inputLabel": "Đầu vào JSON", + "inputPlaceholder": "Dán JSON vào đây…", + "outputLabel": "Đầu ra đã tạo", + "outputPlaceholder": "Lược đồ hoặc kiểu đã tạo sẽ hiển thị ở đây…", + "parseError": "JSON không hợp lệ", + "clear": "Xóa", + "copy": "Sao chép", + "copied": "Đã sao chép vào bảng tạm", + "copyFailed": "Không thể sao chép", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Lược đồ JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "Đang tải trình soạn thảo…" + } + }, + "ja": { + "Navigation": { + "jsonSchemaGenerator": "JSONスキーマジェネレーター" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSONスキーマジェネレーター", + "description": "サンプルJSONからJSONスキーマと型付きモデルを推論します。" + } + } + }, + "JsonSchemaGenerator": { + "title": "JSONスキーマジェネレーター", + "subtitle": "有効なJSONを貼り付けてJSONスキーマや型付きモデルを生成します。すべてブラウザ内で処理されます。", + "languageLabel": "出力言語", + "inputLabel": "JSON入力", + "inputPlaceholder": "JSONをここに貼り付け…", + "outputLabel": "生成された出力", + "outputPlaceholder": "生成されたスキーマや型がここに表示されます…", + "parseError": "無効なJSON", + "clear": "クリア", + "copy": "コピー", + "copied": "クリップボードにコピーしました", + "copyFailed": "コピーできませんでした", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSONスキーマ(Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "エディタを読み込み中…" + } + }, + "ko": { + "Navigation": { + "jsonSchemaGenerator": "JSON 스키마 생성기" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON 스키마 생성기", + "description": "샘플 JSON에서 JSON 스키마와 타입이 지정된 모델을 추론합니다." + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON 스키마 생성기", + "subtitle": "유효한 JSON을 붙여넣어 JSON 스키마 또는 타입 모델을 생성합니다. 모든 처리는 브라우저에서 이루어집니다.", + "languageLabel": "출력 언어", + "inputLabel": "JSON 입력", + "inputPlaceholder": "JSON을 여기에 붙여넣기…", + "outputLabel": "생성된 출력", + "outputPlaceholder": "생성된 스키마나 타입이 여기에 표시됩니다…", + "parseError": "잘못된 JSON", + "clear": "지우기", + "copy": "복사", + "copied": "클립보드에 복사됨", + "copyFailed": "복사하지 못했습니다", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON 스키마(Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "편집기 로드 중…" + } + }, + "zh": { + "Navigation": { + "jsonSchemaGenerator": "JSON 模式生成器" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "JSON 模式生成器", + "description": "从示例 JSON 推断 JSON 模式与类型化模型。" + } + } + }, + "JsonSchemaGenerator": { + "title": "JSON 模式生成器", + "subtitle": "粘贴有效的 JSON 以生成 JSON 模式或类型化模型。全部在浏览器中完成。", + "languageLabel": "输出语言", + "inputLabel": "JSON 输入", + "inputPlaceholder": "在此粘贴 JSON…", + "outputLabel": "生成结果", + "outputPlaceholder": "生成的模式或类型将显示在此…", + "parseError": "无效的 JSON", + "clear": "清空", + "copy": "复制", + "copied": "已复制到剪贴板", + "copyFailed": "无法复制", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "JSON 模式(Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "正在加载编辑器…" + } + }, + "ca": { + "Navigation": { + "jsonSchemaGenerator": "Generador d'esquema JSON" + }, + "Dashboard": { + "tools": { + "jsonSchemaGenerator": { + "title": "Generador d'esquema JSON", + "description": "Extreu un esquema JSON i models tipats a partir d'una mostra JSON." + } + } + }, + "JsonSchemaGenerator": { + "title": "Generador d'esquema JSON", + "subtitle": "Enganxa JSON vàlid per generar un esquema JSON o models tipats. Tot s'executa al navegador.", + "languageLabel": "Idioma de sortida", + "inputLabel": "Entrada JSON", + "inputPlaceholder": "Enganxa el JSON aquí…", + "outputLabel": "Sortida generada", + "outputPlaceholder": "L'esquema o els tipus generats apareixeran aquí…", + "parseError": "JSON no vàlid", + "clear": "Neteja", + "copy": "Copia", + "copied": "Copiat al porta-retalls", + "copyFailed": "No s'ha pogut copiar", + "languages": { + "python": "Python (Pydantic v2)", + "typescript": "TypeScript", + "jsonSchema": "Esquema JSON (Draft 2020-12)", + "go": "Go (encoding/json)", + "rust": "Rust (serde_json)", + "java": "Java (Jackson)", + "csharp": "C# (System.Text.Json)", + "dart": "Dart", + "swift": "Swift (Codable)" + }, + "editorLoading": "S'està carregant l'editor…" + } + } +} diff --git a/apps/desktop-ui/scripts/merge-csv-excel-json-translations.mjs b/apps/desktop-ui/scripts/merge-csv-excel-json-translations.mjs new file mode 100644 index 00000000..fc235289 --- /dev/null +++ b/apps/desktop-ui/scripts/merge-csv-excel-json-translations.mjs @@ -0,0 +1,35 @@ +/** + * Merges Navigation, Dashboard.tools, and CsvExcelJson from + * csv-excel-json-i18n-data.json into each locale messages/*.json (except en). + * Run from repo root: node apps/web/scripts/merge-csv-excel-json-translations.mjs + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const dataPath = path.join(__dirname, "csv-excel-json-i18n-data.json"); +const messagesDir = path.join(__dirname, "../messages"); + +const data = JSON.parse(fs.readFileSync(dataPath, "utf8")); + +for (const [locale, payload] of Object.entries(data)) { + const filePath = path.join(messagesDir, `${locale}.json`); + if (!fs.existsSync(filePath)) { + console.warn("skip missing", locale); + continue; + } + const j = JSON.parse(fs.readFileSync(filePath, "utf8")); + j.Navigation = j.Navigation ?? {}; + j.Navigation.csvExcelJson = payload.nav; + j.Dashboard = j.Dashboard ?? {}; + j.Dashboard.tools = j.Dashboard.tools ?? {}; + j.Dashboard.tools.csvExcelJson = { + title: payload.dTitle, + description: payload.dDesc, + }; + j.CsvExcelJson = payload.tool; + fs.writeFileSync(filePath, JSON.stringify(j, null, 2) + "\n"); +} + +console.log("Updated", Object.keys(data).length, "locale files"); diff --git a/apps/desktop-ui/scripts/merge-dashboard-i18n.mjs b/apps/desktop-ui/scripts/merge-dashboard-i18n.mjs new file mode 100644 index 00000000..468f59d4 --- /dev/null +++ b/apps/desktop-ui/scripts/merge-dashboard-i18n.mjs @@ -0,0 +1,30 @@ +/** + * One-off: copy Dashboard.tabs, Dashboard.analytics, Dashboard.ui from en.json into all other locale files. + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const messagesDir = path.join(__dirname, "..", "messages"); +const enPath = path.join(messagesDir, "en.json"); +const en = JSON.parse(fs.readFileSync(enPath, "utf8")); +const tabs = en.Dashboard?.tabs; +const analytics = en.Dashboard?.analytics; +const ui = en.Dashboard?.ui; +if (!tabs || !analytics || !ui) { + console.error("en.json missing Dashboard.tabs/analytics/ui"); + process.exit(1); +} + +for (const f of fs.readdirSync(messagesDir)) { + if (!f.endsWith(".json") || f === "en.json") continue; + const p = path.join(messagesDir, f); + const j = JSON.parse(fs.readFileSync(p, "utf8")); + j.Dashboard = j.Dashboard || {}; + j.Dashboard.tabs = { ...tabs }; + j.Dashboard.analytics = { ...analytics }; + j.Dashboard.ui = { ...ui }; + fs.writeFileSync(p, JSON.stringify(j, null, 2) + "\n"); +} +console.log("Merged Dashboard tabs/analytics/ui into all non-en locales."); diff --git a/apps/desktop-ui/scripts/merge-jpg-to-pdf-translations.mjs b/apps/desktop-ui/scripts/merge-jpg-to-pdf-translations.mjs new file mode 100644 index 00000000..f5b1a64d --- /dev/null +++ b/apps/desktop-ui/scripts/merge-jpg-to-pdf-translations.mjs @@ -0,0 +1,842 @@ +/** + * Applies localized JPG→PDF strings to messages/*.json (all locales except en). + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const messagesDir = path.join(__dirname, "../messages"); + +/** @typedef {{ nav: string, dashTitle: string, dashDesc: string, panel: Record }} Bundle */ + +/** @type {Record} */ +const BUNDLES = { + fr: { + nav: "JPG vers PDF", + dashTitle: "JPG vers PDF", + dashDesc: + "Convertissez des images JPG en PDF en quelques secondes. Ajustez facilement l'orientation et les marges.", + panel: { + intro: + "Convertissez des images JPG en PDF en quelques secondes. Ajustez facilement l'orientation et les marges.", + privacyNote: "Vos fichiers ne quittent jamais ce navigateur.", + orderHint: + "Les fichiers en haut deviennent les premières pages du PDF. Utilisez les flèches pour réordonner.", + rotationLabel: "Rotation (toutes les pages)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Marge uniforme", + marginValue: "{mm} mm", + addImages: "Ajouter des JPEG", + clearAll: "Effacer la liste", + downloadPdf: "Créer le PDF et télécharger", + building: "Création du PDF…", + moveUpAria: "Monter", + moveDownAria: "Descendre", + removeFileAria: "Retirer de la liste", + minOneJpeg: "Ajoutez au moins un fichier JPEG.", + invalidJpeg: "Ce fichier n'est pas un JPEG (JPG) valide.", + buildFailed: + "Impossible de créer le PDF. Retirez les fichiers non pris en charge et réessayez.", + }, + }, + es: { + nav: "JPG a PDF", + dashTitle: "JPG a PDF", + dashDesc: + "Convierte imágenes JPG a PDF en segundos. Ajusta fácilmente la orientación y los márgenes.", + panel: { + intro: + "Convierte imágenes JPG a PDF en segundos. Ajusta fácilmente la orientación y los márgenes.", + privacyNote: "Tus archivos nunca salen de este navegador.", + orderHint: + "Los archivos de arriba serán las primeras páginas del PDF. Usa las flechas para reordenar.", + rotationLabel: "Rotación (todas las páginas)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Margen uniforme", + marginValue: "{mm} mm", + addImages: "Añadir JPEG", + clearAll: "Vaciar lista", + downloadPdf: "Crear PDF y descargar", + building: "Creando el PDF…", + moveUpAria: "Subir", + moveDownAria: "Bajar", + removeFileAria: "Quitar de la lista", + minOneJpeg: "Añade al menos un archivo JPEG.", + invalidJpeg: "Este archivo no es un JPEG (JPG) válido.", + buildFailed: + "No se pudo crear el PDF. Quita archivos no compatibles e inténtalo de nuevo.", + }, + }, + de: { + nav: "JPG zu PDF", + dashTitle: "JPG zu PDF", + dashDesc: + "Wandeln Sie JPG-Bilder in Sekunden in PDF um. Ausrichtung und Ränder lassen sich einfach anpassen.", + panel: { + intro: + "Wandeln Sie JPG-Bilder in Sekunden in PDF um. Ausrichtung und Ränder lassen sich einfach anpassen.", + privacyNote: "Ihre Dateien verlassen diesen Browser nie.", + orderHint: + "Dateien oben werden die ersten Seiten im PDF. Mit den Pfeilen sortieren.", + rotationLabel: "Drehung (alle Seiten)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Einheitlicher Rand", + marginValue: "{mm} mm", + addImages: "JPEGs hinzufügen", + clearAll: "Liste leeren", + downloadPdf: "PDF erstellen & herunterladen", + building: "PDF wird erstellt…", + moveUpAria: "Nach oben", + moveDownAria: "Nach unten", + removeFileAria: "Aus Liste entfernen", + minOneJpeg: "Fügen Sie mindestens eine JPEG-Datei hinzu.", + invalidJpeg: "Diese Datei ist kein gültiges JPEG (JPG).", + buildFailed: + "PDF konnte nicht erstellt werden. Nicht unterstützte Dateien entfernen und erneut versuchen.", + }, + }, + it: { + nav: "JPG in PDF", + dashTitle: "JPG in PDF", + dashDesc: + "Converti immagini JPG in PDF in pochi secondi. Regola facilmente orientazione e margini.", + panel: { + intro: + "Converti immagini JPG in PDF in pochi secondi. Regola facilmente orientazione e margini.", + privacyNote: "I tuoi file non lasciano mai questo browser.", + orderHint: + "I file in alto diventano le prime pagine del PDF. Usa le frecce per riordinare.", + rotationLabel: "Rotazione (tutte le pagine)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Margine uniforme", + marginValue: "{mm} mm", + addImages: "Aggiungi JPEG", + clearAll: "Svuota elenco", + downloadPdf: "Crea PDF e scarica", + building: "Creazione PDF…", + moveUpAria: "Sposta su", + moveDownAria: "Sposta giù", + removeFileAria: "Rimuovi dall'elenco", + minOneJpeg: "Aggiungi almeno un file JPEG.", + invalidJpeg: "Questo file non è un JPEG (JPG) valido.", + buildFailed: + "Impossibile creare il PDF. Rimuovi i file non supportati e riprova.", + }, + }, + pt: { + nav: "JPG para PDF", + dashTitle: "JPG para PDF", + dashDesc: + "Converta imagens JPG em PDF em segundos. Ajuste facilmente a orientação e as margens.", + panel: { + intro: + "Converta imagens JPG em PDF em segundos. Ajuste facilmente a orientação e as margens.", + privacyNote: "Os seus ficheiros nunca saem deste navegador.", + orderHint: + "Os ficheiros no topo passam a ser as primeiras páginas do PDF. Use as setas para reordenar.", + rotationLabel: "Rotação (todas as páginas)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Margem uniforme", + marginValue: "{mm} mm", + addImages: "Adicionar JPEG", + clearAll: "Limpar lista", + downloadPdf: "Criar PDF e transferir", + building: "A criar o PDF…", + moveUpAria: "Mover para cima", + moveDownAria: "Mover para baixo", + removeFileAria: "Remover da lista", + minOneJpeg: "Adicione pelo menos um ficheiro JPEG.", + invalidJpeg: "Este ficheiro não é um JPEG (JPG) válido.", + buildFailed: + "Não foi possível criar o PDF. Remova ficheiros não suportados e tente novamente.", + }, + }, + "pt-BR": { + nav: "JPG para PDF", + dashTitle: "JPG para PDF", + dashDesc: + "Converta imagens JPG para PDF em segundos. Ajuste facilmente a orientação e as margens.", + panel: { + intro: + "Converta imagens JPG para PDF em segundos. Ajuste facilmente a orientação e as margens.", + privacyNote: "Seus arquivos nunca saem deste navegador.", + orderHint: + "Os arquivos no topo viram as primeiras páginas do PDF. Use as setas para reordenar.", + rotationLabel: "Rotação (todas as páginas)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Margem uniforme", + marginValue: "{mm} mm", + addImages: "Adicionar JPEG", + clearAll: "Limpar lista", + downloadPdf: "Criar PDF e baixar", + building: "Gerando o PDF…", + moveUpAria: "Mover para cima", + moveDownAria: "Mover para baixo", + removeFileAria: "Remover da lista", + minOneJpeg: "Adicione pelo menos um arquivo JPEG.", + invalidJpeg: "Este arquivo não é um JPEG (JPG) válido.", + buildFailed: + "Não foi possível criar o PDF. Remova arquivos não suportados e tente novamente.", + }, + }, + nl: { + nav: "JPG naar PDF", + dashTitle: "JPG naar PDF", + dashDesc: + "Zet JPG-afbeeldingen in enkele seconden om naar PDF. Pas eenvoudig oriëntatie en marges aan.", + panel: { + intro: + "Zet JPG-afbeeldingen in enkele seconden om naar PDF. Pas eenvoudig oriëntatie en marges aan.", + privacyNote: "Uw bestanden verlaten deze browser nooit.", + orderHint: + "Bestanden bovenaan worden de eerste pagina's van de PDF. Gebruik de pijlen om te herschikken.", + rotationLabel: "Rotatie (alle pagina's)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Uniforme marge", + marginValue: "{mm} mm", + addImages: "JPEG's toevoegen", + clearAll: "Lijst wissen", + downloadPdf: "PDF maken en downloaden", + building: "PDF wordt gemaakt…", + moveUpAria: "Omhoog", + moveDownAria: "Omlaag", + removeFileAria: "Uit lijst verwijderen", + minOneJpeg: "Voeg minstens één JPEG-bestand toe.", + invalidJpeg: "Dit bestand is geen geldige JPEG (JPG).", + buildFailed: + "Kan de PDF niet maken. Verwijder niet-ondersteunde bestanden en probeer opnieuw.", + }, + }, + pl: { + nav: "JPG do PDF", + dashTitle: "JPG do PDF", + dashDesc: + "Konwertuj obrazy JPG na PDF w kilka sekund. Łatwo dostosuj orientację i marginesy.", + panel: { + intro: + "Konwertuj obrazy JPG na PDF w kilka sekund. Łatwo dostosuj orientację i marginesy.", + privacyNote: "Twoje pliki nigdy nie opuszczają tej przeglądarki.", + orderHint: + "Pliki u góry stają się pierwszymi stronami PDF. Strzałkami zmienisz kolejność.", + rotationLabel: "Obrót (wszystkie strony)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Jednolity margines", + marginValue: "{mm} mm", + addImages: "Dodaj pliki JPEG", + clearAll: "Wyczyść listę", + downloadPdf: "Utwórz PDF i pobierz", + building: "Tworzenie PDF…", + moveUpAria: "W górę", + moveDownAria: "W dół", + removeFileAria: "Usuń z listy", + minOneJpeg: "Dodaj co najmniej jeden plik JPEG.", + invalidJpeg: "Ten plik nie jest prawidłowym JPEG (JPG).", + buildFailed: + "Nie można utworzyć PDF. Usuń nieobsługiwane pliki i spróbuj ponownie.", + }, + }, + cs: { + nav: "JPG do PDF", + dashTitle: "JPG do PDF", + dashDesc: + "Převádějte obrázky JPG do PDF během několika sekund. Snadno upravíte orientaci a okraje.", + panel: { + intro: + "Převádějte obrázky JPG do PDF během několika sekund. Snadno upravíte orientaci a okraje.", + privacyNote: "Vaše soubory tento prohlížeč nikdy neopustí.", + orderHint: + "Soubory nahoře se stanou prvními stránkami PDF. Šipkami je přeuspořádejte.", + rotationLabel: "Otočení (všechny stránky)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Stejné okraje", + marginValue: "{mm} mm", + addImages: "Přidat JPEG", + clearAll: "Vymazat seznam", + downloadPdf: "Vytvořit PDF a stáhnout", + building: "Vytváření PDF…", + moveUpAria: "Nahoru", + moveDownAria: "Dolů", + removeFileAria: "Odebrat ze seznamu", + minOneJpeg: "Přidejte alespoň jeden soubor JPEG.", + invalidJpeg: "Tento soubor není platný JPEG (JPG).", + buildFailed: + "PDF se nepodařilo vytvořit. Odstraňte nepodporované soubory a zkuste to znovu.", + }, + }, + sv: { + nav: "JPG till PDF", + dashTitle: "JPG till PDF", + dashDesc: + "Konvertera JPG-bilder till PDF på några sekunder. Justera enkelt orientering och marginaler.", + panel: { + intro: + "Konvertera JPG-bilder till PDF på några sekunder. Justera enkelt orientering och marginaler.", + privacyNote: "Dina filer lämnar aldrig den här webbläsaren.", + orderHint: + "Filer högst upp blir de första sidorna i PDF:en. Använd pilarna för att ändra ordning.", + rotationLabel: "Rotation (alla sidor)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Enhetlig marginal", + marginValue: "{mm} mm", + addImages: "Lägg till JPEG-filer", + clearAll: "Rensa listan", + downloadPdf: "Skapa PDF och ladda ner", + building: "Skapar PDF…", + moveUpAria: "Flytta upp", + moveDownAria: "Flytta ned", + removeFileAria: "Ta bort från listan", + minOneJpeg: "Lägg till minst en JPEG-fil.", + invalidJpeg: "Den här filen är inte en giltig JPEG (JPG).", + buildFailed: + "Det gick inte att skapa PDF:en. Ta bort filer som inte stöds och försök igen.", + }, + }, + da: { + nav: "JPG til PDF", + dashTitle: "JPG til PDF", + dashDesc: + "Konverter JPG-billeder til PDF på få sekunder. Juster nemt orientering og margener.", + panel: { + intro: + "Konverter JPG-billeder til PDF på få sekunder. Juster nemt orientering og margener.", + privacyNote: "Dine filer forlader aldrig denne browser.", + orderHint: + "Filer øverst bliver de første sider i PDF'en. Brug pilene til at omarrangere.", + rotationLabel: "Rotation (alle sider)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Ensartet margin", + marginValue: "{mm} mm", + addImages: "Tilføj JPEG-filer", + clearAll: "Ryd listen", + downloadPdf: "Opret PDF og download", + building: "Opretter PDF…", + moveUpAria: "Flyt op", + moveDownAria: "Flyt ned", + removeFileAria: "Fjern fra listen", + minOneJpeg: "Tilføj mindst én JPEG-fil.", + invalidJpeg: "Denne fil er ikke en gyldig JPEG (JPG).", + buildFailed: + "PDF'en kunne ikke oprettes. Fjern filer, der ikke understøttes, og prøv igen.", + }, + }, + nb: { + nav: "JPG til PDF", + dashTitle: "JPG til PDF", + dashDesc: + "Konverter JPG-bilder til PDF på noen sekunder. Juster retning og marginer enkelt.", + panel: { + intro: + "Konverter JPG-bilder til PDF på noen sekunder. Juster retning og marginer enkelt.", + privacyNote: "Filene dine forlater aldri denne nettleseren.", + orderHint: + "Filer øverst blir de første sidene i PDF-en. Bruk pilene for å sortere om.", + rotationLabel: "Rotasjon (alle sider)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Lik margin", + marginValue: "{mm} mm", + addImages: "Legg til JPEG-filer", + clearAll: "Tøm listen", + downloadPdf: "Lag PDF og last ned", + building: "Lager PDF…", + moveUpAria: "Flytt opp", + moveDownAria: "Flytt ned", + removeFileAria: "Fjern fra listen", + minOneJpeg: "Legg til minst én JPEG-fil.", + invalidJpeg: "Denne filen er ikke en gyldig JPEG (JPG).", + buildFailed: + "Kunne ikke lage PDF-en. Fjern filer som ikke støttes, og prøv igjen.", + }, + }, + tr: { + nav: "JPG'den PDF'e", + dashTitle: "JPG'den PDF'e", + dashDesc: + "JPG görsellerini saniyeler içinde PDF'ye dönüştürün. Yönelimi ve kenar boşluklarını kolayca ayarlayın.", + panel: { + intro: + "JPG görsellerini saniyeler içinde PDF'ye dönüştürün. Yönelimi ve kenar boşluklarını kolayca ayarlayın.", + privacyNote: "Dosyalarınız bu tarayıcıdan hiç çıkmaz.", + orderHint: + "Üstteki dosyalar PDF'teki ilk sayfalar olur. Sıralamayı değiştirmek için okları kullanın.", + rotationLabel: "Döndürme (tüm sayfalar)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Tek tip kenar boşluğu", + marginValue: "{mm} mm", + addImages: "JPEG ekle", + clearAll: "Listeyi temizle", + downloadPdf: "PDF oluştur ve indir", + building: "PDF oluşturuluyor…", + moveUpAria: "Yukarı taşı", + moveDownAria: "Aşağı taşı", + removeFileAria: "Listeden kaldır", + minOneJpeg: "En az bir JPEG dosyası ekleyin.", + invalidJpeg: "Bu dosya geçerli bir JPEG (JPG) değil.", + buildFailed: + "PDF oluşturulamadı. Desteklenmeyen dosyaları kaldırıp yeniden deneyin.", + }, + }, + ru: { + nav: "JPG в PDF", + dashTitle: "JPG в PDF", + dashDesc: + "Конвертируйте изображения JPG в PDF за секунды. Легко настройте ориентацию и поля.", + panel: { + intro: + "Конвертируйте изображения JPG в PDF за секунды. Легко настройте ориентацию и поля.", + privacyNote: "Ваши файлы не покидают этот браузер.", + orderHint: + "Файлы сверху станут первыми страницами PDF. Стрелками меняйте порядок.", + rotationLabel: "Поворот (все страницы)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Одинаковые поля", + marginValue: "{mm} мм", + addImages: "Добавить JPEG", + clearAll: "Очистить список", + downloadPdf: "Создать PDF и скачать", + building: "Создание PDF…", + moveUpAria: "Вверх", + moveDownAria: "Вниз", + removeFileAria: "Удалить из списка", + minOneJpeg: "Добавьте хотя бы один файл JPEG.", + invalidJpeg: "Этот файл не является допустимым JPEG (JPG).", + buildFailed: + "Не удалось создать PDF. Удалите неподдерживаемые файлы и повторите попытку.", + }, + }, + uk: { + nav: "JPG у PDF", + dashTitle: "JPG у PDF", + dashDesc: + "Конвертуйте зображення JPG у PDF за секунди. Легко налаштуйте орієнтацію та поля.", + panel: { + intro: + "Конвертуйте зображення JPG у PDF за секунди. Легко налаштуйте орієнтацію та поля.", + privacyNote: "Ваші файли ніколи не залишають цей браузер.", + orderHint: + "Файли зверху стануть першими сторінками PDF. Стрілками змінюйте порядок.", + rotationLabel: "Поворот (усі сторінки)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Однакові поля", + marginValue: "{mm} мм", + addImages: "Додати JPEG", + clearAll: "Очистити список", + downloadPdf: "Створити PDF і завантажити", + building: "Створення PDF…", + moveUpAria: "Вгору", + moveDownAria: "Вниз", + removeFileAria: "Прибрати зі списку", + minOneJpeg: "Додайте принаймні один файл JPEG.", + invalidJpeg: "Цей файл не є дійсним JPEG (JPG).", + buildFailed: + "Не вдалося створити PDF. Приберіть непідтримувані файли й спробуйте знову.", + }, + }, + el: { + nav: "JPG σε PDF", + dashTitle: "JPG σε PDF", + dashDesc: + "Μετατρέψτε εικόνες JPG σε PDF σε δευτερόλεπτα. Προσαρμόστε εύκολα προσανατολισμό και περιθώρια.", + panel: { + intro: + "Μετατρέψτε εικόνες JPG σε PDF σε δευτερόλεπτα. Προσαρμόστε εύκολα προσανατολισμό και περιθώρια.", + privacyNote: "Τα αρχεία σας δεν φεύγουν ποτέ από αυτόν τον περιηγητή.", + orderHint: + "Τα αρχεία στην κορυφή γίνονται οι πρώτες σελίδες του PDF. Χρησιμοποιήστε τα βέλη για αναδιάταξη.", + rotationLabel: "Περιστροφή (όλες οι σελίδες)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Ομοιόμορφα περιθώρια", + marginValue: "{mm} mm", + addImages: "Προσθήκη JPEG", + clearAll: "Εκκαθάριση λίστας", + downloadPdf: "Δημιουργία PDF και λήψη", + building: "Δημιουργία PDF…", + moveUpAria: "Μετακίνηση πάνω", + moveDownAria: "Μετακίνηση κάτω", + removeFileAria: "Αφαίρεση από τη λίστα", + minOneJpeg: "Προσθέστε τουλάχιστον ένα αρχείο JPEG.", + invalidJpeg: "Αυτό το αρχείο δεν είναι έγκυρο JPEG (JPG).", + buildFailed: + "Δεν ήταν δυνατή η δημιουργία PDF. Αφαιρέστε μη υποστηριζόμενα αρχεία και δοκιμάστε ξανά.", + }, + }, + vi: { + nav: "JPG sang PDF", + dashTitle: "JPG sang PDF", + dashDesc: + "Chuyển ảnh JPG sang PDF trong vài giây. Điều chỉnh hướng và lề dễ dàng.", + panel: { + intro: + "Chuyển ảnh JPG sang PDF trong vài giây. Điều chỉnh hướng và lề dễ dàng.", + privacyNote: "Tệp của bạn không bao giờ rời khỏi trình duyệt này.", + orderHint: + "Tệp ở trên cùng sẽ thành các trang đầu của PDF. Dùng mũi tên để sắp xếp lại.", + rotationLabel: "Xoay (mọi trang)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Lề đều", + marginValue: "{mm} mm", + addImages: "Thêm JPEG", + clearAll: "Xóa danh sách", + downloadPdf: "Tạo PDF và tải xuống", + building: "Đang tạo PDF…", + moveUpAria: "Lên trên", + moveDownAria: "Xuống dưới", + removeFileAria: "Xóa khỏi danh sách", + minOneJpeg: "Thêm ít nhất một tệp JPEG.", + invalidJpeg: "Tệp này không phải JPEG (JPG) hợp lệ.", + buildFailed: + "Không thể tạo PDF. Hãy bỏ các tệp không được hỗ trợ và thử lại.", + }, + }, + id: { + nav: "JPG ke PDF", + dashTitle: "JPG ke PDF", + dashDesc: + "Ubah gambar JPG menjadi PDF dalam hitungan detik. Atur orientasi dan margin dengan mudah.", + panel: { + intro: + "Ubah gambar JPG menjadi PDF dalam hitungan detik. Atur orientasi dan margin dengan mudah.", + privacyNote: "Berkas Anda tidak pernah meninggalkan browser ini.", + orderHint: + "Berkas di atas menjadi halaman pertama PDF. Gunakan panah untuk mengurutkan ulang.", + rotationLabel: "Rotasi (semua halaman)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Margin seragam", + marginValue: "{mm} mm", + addImages: "Tambah JPEG", + clearAll: "Kosongkan daftar", + downloadPdf: "Buat PDF & unduh", + building: "Membuat PDF…", + moveUpAria: "Pindah ke atas", + moveDownAria: "Pindah ke bawah", + removeFileAria: "Hapus dari daftar", + minOneJpeg: "Tambahkan setidaknya satu berkas JPEG.", + invalidJpeg: "Berkas ini bukan JPEG (JPG) yang valid.", + buildFailed: + "Tidak dapat membuat PDF. Hapus berkas yang tidak didukung dan coba lagi.", + }, + }, + ms: { + nav: "JPG ke PDF", + dashTitle: "JPG ke PDF", + dashDesc: + "Tukar imej JPG kepada PDF dalam beberapa saat. Laraskan orientasi dan margin dengan mudah.", + panel: { + intro: + "Tukar imej JPG kepada PDF dalam beberapa saat. Laraskan orientasi dan margin dengan mudah.", + privacyNote: "Fail anda tidak pernah meninggalkan pelayar ini.", + orderHint: + "Fail di atas menjadi halaman pertama PDF. Gunakan anak panah untuk menyusun semula.", + rotationLabel: "Putaran (semua halaman)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Margin sekata", + marginValue: "{mm} mm", + addImages: "Tambah JPEG", + clearAll: "Kosongkan senarai", + downloadPdf: "Cipta PDF & muat turun", + building: "Mencipta PDF…", + moveUpAria: "Alih ke atas", + moveDownAria: "Alih ke bawah", + removeFileAria: "Buang daripada senarai", + minOneJpeg: "Tambah sekurang-kurangnya satu fail JPEG.", + invalidJpeg: "Fail ini bukan JPEG (JPG) yang sah.", + buildFailed: + "Tidak dapat mencipta PDF. Buang fail yang tidak disokong dan cuba lagi.", + }, + }, + ja: { + nav: "JPGをPDFへ", + dashTitle: "JPGをPDFへ", + dashDesc: + "JPG画像を数秒でPDFに変換。向きと余白を簡単に調整できます。", + panel: { + intro: + "JPG画像を数秒でPDFに変換。向きと余白を簡単に調整できます。", + privacyNote: "ファイルがこのブラウザの外に出ることはありません。", + orderHint: + "上にあるファイルがPDFの先頭ページになります。矢印で並べ替えられます。", + rotationLabel: "回転(すべてのページ)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "四辺の余白(同一)", + marginValue: "{mm} mm", + addImages: "JPEGを追加", + clearAll: "一覧をクリア", + downloadPdf: "PDFを作成してダウンロード", + building: "PDFを作成中…", + moveUpAria: "上へ", + moveDownAria: "下へ", + removeFileAria: "一覧から削除", + minOneJpeg: "JPEGファイルを1つ以上追加してください。", + invalidJpeg: "有効なJPEG(JPG)ではありません。", + buildFailed: + "PDFを作成できませんでした。対応していないファイルを外して再度お試しください。", + }, + }, + ko: { + nav: "JPG를 PDF로", + dashTitle: "JPG를 PDF로", + dashDesc: + "JPG 이미지를 몇 초 만에 PDF로 변환합니다. 방향과 여백을 쉽게 조정하세요.", + panel: { + intro: + "JPG 이미지를 몇 초 만에 PDF로 변환합니다. 방향과 여백을 쉽게 조정하세요.", + privacyNote: "파일은 이 브라우저를 벗어나지 않습니다.", + orderHint: + "맨 위 파일이 PDF의 앞쪽 페이지가 됩니다. 화살표로 순서를 바꿀 수 있습니다.", + rotationLabel: "회전(모든 페이지)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "동일 여백", + marginValue: "{mm} mm", + addImages: "JPEG 추가", + clearAll: "목록 지우기", + downloadPdf: "PDF 만들기 및 다운로드", + building: "PDF 만드는 중…", + moveUpAria: "위로 이동", + moveDownAria: "아래로 이동", + removeFileAria: "목록에서 제거", + minOneJpeg: "JPEG 파일을 하나 이상 추가하세요.", + invalidJpeg: "올바른 JPEG(JPG) 파일이 아닙니다.", + buildFailed: + "PDF를 만들 수 없습니다. 지원되지 않는 파일을 제거한 뒤 다시 시도하세요.", + }, + }, + zh: { + nav: "JPG 转 PDF", + dashTitle: "JPG 转 PDF", + dashDesc: "几秒内将 JPG 图片转为 PDF。轻松调整方向和页边距。", + panel: { + intro: "几秒内将 JPG 图片转为 PDF。轻松调整方向和页边距。", + privacyNote: "您的文件不会离开本浏览器。", + orderHint: "列表顶部的文件将成为 PDF 的前几页。使用箭头可调整顺序。", + rotationLabel: "旋转(应用于所有页面)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "统一页边距", + marginValue: "{mm} mm", + addImages: "添加 JPEG", + clearAll: "清空列表", + downloadPdf: "生成 PDF 并下载", + building: "正在生成 PDF…", + moveUpAria: "上移", + moveDownAria: "下移", + removeFileAria: "从列表移除", + minOneJpeg: "请至少添加一个 JPEG 文件。", + invalidJpeg: "该文件不是有效的 JPEG(JPG)。", + buildFailed: "无法生成 PDF。请移除不支持的文件后重试。", + }, + }, + ar: { + nav: "من JPG إلى PDF", + dashTitle: "من JPG إلى PDF", + dashDesc: + "حوّل صور JPG إلى PDF في ثوانٍ. اضبط الاتجاه والهوامش بسهولة.", + panel: { + intro: + "حوّل صور JPG إلى PDF في ثوانٍ. اضبط الاتجاه والهوامش بسهولة.", + privacyNote: "ملفاتك لا تغادر هذا المتصفح أبدًا.", + orderHint: + "الملفات في الأعلى تصبح الصفحات الأولى في ملف PDF. استخدم الأسهم لإعادة الترتيب.", + rotationLabel: "الاستدارة (كل الصفحات)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "هامش موحّد", + marginValue: "{mm} مم", + addImages: "إضافة ملفات JPEG", + clearAll: "مسح القائمة", + downloadPdf: "إنشاء PDF وتنزيله", + building: "جارٍ إنشاء PDF…", + moveUpAria: "تحريك لأعلى", + moveDownAria: "تحريك لأسفل", + removeFileAria: "إزالة من القائمة", + minOneJpeg: "أضف ملف JPEG واحدًا على الأقل.", + invalidJpeg: "هذا الملف ليس JPEG (JPG) صالحًا.", + buildFailed: + "تعذّر إنشاء ملف PDF. أزل الملفات غير المدعومة وحاول مرة أخرى.", + }, + }, + fa: { + nav: "JPG به PDF", + dashTitle: "JPG به PDF", + dashDesc: + "تصاویر JPG را در چند ثانیه به PDF تبدیل کنید. جهت‌گیری و حاشیه را به‌راحتی تنظیم کنید.", + panel: { + intro: + "تصاویر JPG را در چند ثانیه به PDF تبدیل کنید. جهت‌گیری و حاشیه را به‌راحتی تنظیم کنید.", + privacyNote: "فایل‌های شما هرگز این مرورگر را ترک نمی‌کنند.", + orderHint: + "فایل‌های بالای فهرست، صفحات اول PDF می‌شوند. با فلش‌ها ترتیب را عوض کنید.", + rotationLabel: "چرخش (همهٔ صفحات)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "حاشیه یکنواخت", + marginValue: "{mm} mm", + addImages: "افزودن JPEG", + clearAll: "پاک‌کردن فهرست", + downloadPdf: "ایجاد PDF و بارگیری", + building: "در حال ساخت PDF…", + moveUpAria: "بالا بردن", + moveDownAria: "پایین بردن", + removeFileAria: "حذف از فهرست", + minOneJpeg: "حداقل یک فایل JPEG اضافه کنید.", + invalidJpeg: "این فایل JPEG (JPG) معتبری نیست.", + buildFailed: + "ساخت PDF ممکن نشد. فایل‌های پشتیبانی‌نشده را بردارید و دوباره امتحان کنید.", + }, + }, + ca: { + nav: "JPG a PDF", + dashTitle: "JPG a PDF", + dashDesc: + "Converteix imatges JPG a PDF en segons. Ajusta fàcilment l'orientació i els marges.", + panel: { + intro: + "Converteix imatges JPG a PDF en segons. Ajusta fàcilment l'orientació i els marges.", + privacyNote: "Els teus fitxers no surten mai d'aquest navegador.", + orderHint: + "Els fitxers de dalt es converteixen en les primeres pàgines del PDF. Fletxes per reordenar.", + rotationLabel: "Rotació (totes les pàgines)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Marge uniforme", + marginValue: "{mm} mm", + addImages: "Afegeix JPEG", + clearAll: "Buida la llista", + downloadPdf: "Crea el PDF i descarrega", + building: "S'està creant el PDF…", + moveUpAria: "Puja", + moveDownAria: "Baixa", + removeFileAria: "Treu de la llista", + minOneJpeg: "Afegeix com a mínim un fitxer JPEG.", + invalidJpeg: "Aquest fitxer no és un JPEG (JPG) vàlid.", + buildFailed: + "No s'ha pogut crear el PDF. Elimina fitxers no compatibles i torna-ho a provar.", + }, + }, + af: { + nav: "JPG na PDF", + dashTitle: "JPG na PDF", + dashDesc: + "Skakel JPG-beelde na PDF in sekondes. Pas oriëntasie en kante maklik aan.", + panel: { + intro: + "Skakel JPG-beelde na PDF in sekondes. Pas oriëntasie en kante maklik aan.", + privacyNote: "Jou lêers verlaat nooit hierdie blaaier nie.", + orderHint: + "Lêers bo word die eerste bladsye in die PDF. Gebruik pyle om te herskik.", + rotationLabel: "Rotasie (alle bladsye)", + rotation0: "0°", + rotation90: "90°", + rotation180: "180°", + rotation270: "270°", + marginLabel: "Eenvormige kant", + marginValue: "{mm} mm", + addImages: "Voeg JPEG's by", + clearAll: "Maak lys skoon", + downloadPdf: "Skep PDF en laai af", + building: "Skep PDF…", + moveUpAria: "Skuif op", + moveDownAria: "Skuif af", + removeFileAria: "Verwyder uit lys", + minOneJpeg: "Voeg minstens een JPEG-lêer by.", + invalidJpeg: "Hierdie lêer is nie 'n geldige JPEG (JPG) nie.", + buildFailed: + "Kon nie die PDF skep nie. Verwyder nie-ondersteunde lêers en probeer weer.", + }, + }, +}; + +for (const [locale, bundle] of Object.entries(BUNDLES)) { + const filePath = path.join(messagesDir, `${locale}.json`); + if (!fs.existsSync(filePath)) { + console.warn("skip missing", locale); + continue; + } + const raw = fs.readFileSync(filePath, "utf8"); + const data = JSON.parse(raw); + if (!data.Navigation) data.Navigation = {}; + if (!data.Dashboard?.tools) { + console.warn("skip bad shape", locale); + continue; + } + data.Navigation.jpgToPdf = bundle.nav; + data.Dashboard.tools.jpgToPdf = { + title: bundle.dashTitle, + description: bundle.dashDesc, + }; + data.JpgToPdf = { ...bundle.panel }; + fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`); + console.log("updated", locale); +} + +console.log("Done. en.json unchanged."); diff --git a/apps/desktop-ui/scripts/merge-snippet-manager-i18n.mjs b/apps/desktop-ui/scripts/merge-snippet-manager-i18n.mjs new file mode 100644 index 00000000..7b332b5d --- /dev/null +++ b/apps/desktop-ui/scripts/merge-snippet-manager-i18n.mjs @@ -0,0 +1,68 @@ +/** + * Merges snippet-manager i18n: tool page (SnippetManager), sidebar/nav label, and + * dashboard tool card (Navigation.snippetManager + Dashboard.tools.snippetManager). + * Run from repo root (after `python3 apps/web/scripts/generate_snippet_manager_patches.py`): + * node apps/web/scripts/merge-snippet-manager-i18n.mjs + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const messagesDir = path.join(__dirname, "../messages"); +const patchesPath = path.join(__dirname, "snippet-manager-patches.json"); +const navDashPath = path.join(__dirname, "snippet-manager-nav-dash-patches.json"); + +function deepMerge(a, b) { + const out = { ...a }; + for (const [k, v] of Object.entries(b)) { + if ( + v != null && + typeof v === "object" && + !Array.isArray(v) && + typeof out[k] === "object" && + out[k] !== null && + !Array.isArray(out[k]) + ) { + out[k] = deepMerge(out[k], v); + } else { + out[k] = v; + } + } + return out; +} + +const en = JSON.parse(fs.readFileSync(path.join(messagesDir, "en.json"), "utf8")); +const base = structuredClone(en.SnippetManager); +const patches = JSON.parse(fs.readFileSync(patchesPath, "utf8")); +const navDashPatches = fs.existsSync(navDashPath) + ? JSON.parse(fs.readFileSync(navDashPath, "utf8")) + : {}; + +let n = 0; +for (const [locale, patch] of Object.entries(patches)) { + if (locale === "en") continue; + const fp = path.join(messagesDir, `${locale}.json`); + if (!fs.existsSync(fp)) { + console.warn("skip missing", locale); + continue; + } + const j = JSON.parse(fs.readFileSync(fp, "utf8")); + j.SnippetManager = deepMerge(structuredClone(base), patch); + + const nd = navDashPatches[locale]; + if (nd) { + j.Navigation = j.Navigation ?? {}; + j.Navigation.snippetManager = nd.navigation; + j.Dashboard = j.Dashboard ?? {}; + j.Dashboard.tools = j.Dashboard.tools ?? {}; + j.Dashboard.tools.snippetManager = { + title: nd.title, + description: nd.description, + }; + } + + fs.writeFileSync(fp, JSON.stringify(j, null, 2) + "\n"); + n += 1; +} +console.log("Updated SnippetManager + nav/dashboard snippet card in", n, "locale files"); diff --git a/apps/desktop-ui/scripts/patch-excel-to-pdf-locales.mjs b/apps/desktop-ui/scripts/patch-excel-to-pdf-locales.mjs new file mode 100644 index 00000000..0fd8fbc2 --- /dev/null +++ b/apps/desktop-ui/scripts/patch-excel-to-pdf-locales.mjs @@ -0,0 +1,750 @@ +/** + * One-off / maintenance: set localized strings for Excel → PDF across all message files. + * Run from repo root: node apps/web/scripts/patch-excel-to-pdf-locales.mjs + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const messagesDir = path.join(__dirname, "../messages"); + +/** @typedef {{ nav: string, dashboardTitle: string, dashboardDesc: string, panel: Record }} LocaleBundle */ + +/** @type {Record} */ +const T = { + en: { + nav: "Excel to PDF", + dashboardTitle: "Excel to PDF", + dashboardDesc: + "Make Excel spreadsheets easy to read by converting them to PDF in your browser.", + panel: { + intro: + "Make Excel spreadsheets easy to read by converting them to PDF.", + privacyNote: "Your file never leaves this browser.", + includeSheets: "Sheets to include", + allSheets: "All sheets", + firstSheetOnly: "First sheet only", + chooseFile: "Choose Excel file", + clearFile: "Clear", + downloadPdf: "Download PDF", + building: "Building PDF…", + noFile: "Choose an Excel file first.", + emptyWorkbook: + "No sheet in this workbook had table data we could export.", + unsupportedFile: "Use a .xlsx or .xls file.", + readFailed: "Could not read this spreadsheet.", + buildFailed: + "Could not build the PDF. Try a smaller workbook or fewer sheets.", + sheetHeading: "Sheet: {name}", + }, + }, + fr: { + nav: "Excel vers PDF", + dashboardTitle: "Excel vers PDF", + dashboardDesc: + "Rendez vos feuilles Excel plus lisibles en les convertissant en PDF dans le navigateur.", + panel: { + intro: + "Rendez vos feuilles Excel plus lisibles en les convertissant en PDF.", + privacyNote: "Votre fichier ne quitte jamais ce navigateur.", + includeSheets: "Feuilles à inclure", + allSheets: "Toutes les feuilles", + firstSheetOnly: "Première feuille uniquement", + chooseFile: "Choisir un fichier Excel", + clearFile: "Effacer", + downloadPdf: "Télécharger le PDF", + building: "Génération du PDF…", + noFile: "Choisissez d'abord un fichier Excel.", + emptyWorkbook: + "Aucune feuille de ce classeur ne contenait de données tabulaires exportables.", + unsupportedFile: "Utilisez un fichier .xlsx ou .xls.", + readFailed: "Impossible de lire ce classeur.", + buildFailed: + "Impossible de créer le PDF. Essayez un classeur plus petit ou moins de feuilles.", + sheetHeading: "Feuille : {name}", + }, + }, + es: { + nav: "Excel a PDF", + dashboardTitle: "Excel a PDF", + dashboardDesc: + "Haga que las hojas de Excel sean fáciles de leer convirtiéndolas a PDF en el navegador.", + panel: { + intro: + "Haga que las hojas de Excel sean fáciles de leer convirtiéndolas a PDF.", + privacyNote: "Su archivo no sale nunca de este navegador.", + includeSheets: "Hojas a incluir", + allSheets: "Todas las hojas", + firstSheetOnly: "Solo la primera hoja", + chooseFile: "Elegir archivo de Excel", + clearFile: "Borrar", + downloadPdf: "Descargar PDF", + building: "Generando PDF…", + noFile: "Elija primero un archivo de Excel.", + emptyWorkbook: + "Ninguna hoja de este libro tenía datos de tabla que pudiéramos exportar.", + unsupportedFile: "Use un archivo .xlsx o .xls.", + readFailed: "No se pudo leer esta hoja de cálculo.", + buildFailed: + "No se pudo generar el PDF. Pruebe con un libro más pequeño o menos hojas.", + sheetHeading: "Hoja: {name}", + }, + }, + de: { + nav: "Excel zu PDF", + dashboardTitle: "Excel zu PDF", + dashboardDesc: + "Machen Sie Excel-Tabellen leichter lesbar, indem Sie sie im Browser in PDF umwandeln.", + panel: { + intro: + "Machen Sie Excel-Tabellen leichter lesbar, indem Sie sie in PDF umwandeln.", + privacyNote: "Ihre Datei verlässt diesen Browser nie.", + includeSheets: "Einzubeziehende Blätter", + allSheets: "Alle Blätter", + firstSheetOnly: "Nur erstes Blatt", + chooseFile: "Excel-Datei wählen", + clearFile: "Zurücksetzen", + downloadPdf: "PDF herunterladen", + building: "PDF wird erstellt…", + noFile: "Wählen Sie zuerst eine Excel-Datei.", + emptyWorkbook: + "Kein Blatt in dieser Arbeitsmappe enthielt exportierbare Tabellendaten.", + unsupportedFile: "Verwenden Sie eine .xlsx- oder .xls-Datei.", + readFailed: "Diese Tabelle konnte nicht gelesen werden.", + buildFailed: + "PDF konnte nicht erstellt werden. Versuchen Sie eine kleinere Datei oder weniger Blätter.", + sheetHeading: "Blatt: {name}", + }, + }, + it: { + nav: "Excel in PDF", + dashboardTitle: "Excel in PDF", + dashboardDesc: + "Rendi i fogli Excel più leggibili convertendoli in PDF nel browser.", + panel: { + intro: "Rendi i fogli Excel più leggibili convertendoli in PDF.", + privacyNote: "Il file non lascia mai questo browser.", + includeSheets: "Fogli da includere", + allSheets: "Tutti i fogli", + firstSheetOnly: "Solo il primo foglio", + chooseFile: "Scegli file Excel", + clearFile: "Cancella", + downloadPdf: "Scarica PDF", + building: "Creazione PDF…", + noFile: "Scegli prima un file Excel.", + emptyWorkbook: + "Nessun foglio di questa cartella conteneva dati tabulari esportabili.", + unsupportedFile: "Usa un file .xlsx o .xls.", + readFailed: "Impossibile leggere questo foglio di calcolo.", + buildFailed: + "Impossibile creare il PDF. Prova una cartella più piccola o meno fogli.", + sheetHeading: "Foglio: {name}", + }, + }, + pt: { + nav: "Excel para PDF", + dashboardTitle: "Excel para PDF", + dashboardDesc: + "Torne as folhas Excel mais fáceis de ler convertendo-as para PDF no navegador.", + panel: { + intro: "Torne as folhas Excel mais fáceis de ler convertendo-as para PDF.", + privacyNote: "O ficheiro nunca sai deste navegador.", + includeSheets: "Folhas a incluir", + allSheets: "Todas as folhas", + firstSheetOnly: "Apenas a primeira folha", + chooseFile: "Escolher ficheiro Excel", + clearFile: "Limpar", + downloadPdf: "Descarregar PDF", + building: "A criar PDF…", + noFile: "Escolha primeiro um ficheiro Excel.", + emptyWorkbook: + "Nenhuma folha deste livro continha dados de tabela exportáveis.", + unsupportedFile: "Use um ficheiro .xlsx ou .xls.", + readFailed: "Não foi possível ler esta folha de cálculo.", + buildFailed: + "Não foi possível criar o PDF. Tente um livro mais pequeno ou menos folhas.", + sheetHeading: "Folha: {name}", + }, + }, + "pt-BR": { + nav: "Excel para PDF", + dashboardTitle: "Excel para PDF", + dashboardDesc: + "Deixe planilhas Excel mais fáceis de ler convertendo-as para PDF no navegador.", + panel: { + intro: "Deixe planilhas Excel mais fáceis de ler convertendo-as para PDF.", + privacyNote: "Seu arquivo nunca sai deste navegador.", + includeSheets: "Planilhas a incluir", + allSheets: "Todas as planilhas", + firstSheetOnly: "Somente a primeira planilha", + chooseFile: "Escolher arquivo Excel", + clearFile: "Limpar", + downloadPdf: "Baixar PDF", + building: "Gerando PDF…", + noFile: "Escolha um arquivo Excel primeiro.", + emptyWorkbook: + "Nenhuma planilha desta pasta continha dados de tabela exportáveis.", + unsupportedFile: "Use um arquivo .xlsx ou .xls.", + readFailed: "Não foi possível ler esta planilha.", + buildFailed: + "Não foi possível gerar o PDF. Tente uma pasta menor ou menos planilhas.", + sheetHeading: "Planilha: {name}", + }, + }, + nl: { + nav: "Excel naar PDF", + dashboardTitle: "Excel naar PDF", + dashboardDesc: + "Maak Excel-spreadsheets makkelijker leesbaar door ze in de browser naar PDF te zetten.", + panel: { + intro: + "Maak Excel-spreadsheets makkelijker leesbaar door ze naar PDF te zetten.", + privacyNote: "Uw bestand verlaat deze browser nooit.", + includeSheets: "Te verwerken werkbladen", + allSheets: "Alle werkbladen", + firstSheetOnly: "Alleen eerste werkblad", + chooseFile: "Excelbestand kiezen", + clearFile: "Wissen", + downloadPdf: "PDF downloaden", + building: "PDF wordt gemaakt…", + noFile: "Kies eerst een Excelbestand.", + emptyWorkbook: + "Geen werkblad in deze map bevatte exporteerbare tabelgegevens.", + unsupportedFile: "Gebruik een .xlsx- of .xls-bestand.", + readFailed: "Kon deze spreadsheet niet lezen.", + buildFailed: + "Kon de PDF niet maken. Probeer een kleinere map of minder werkbladen.", + sheetHeading: "Werkblad: {name}", + }, + }, + pl: { + nav: "Excel do PDF", + dashboardTitle: "Excel do PDF", + dashboardDesc: + "Ułatwiaj czytanie arkuszy Excel, konwertując je do PDF w przeglądarce.", + panel: { + intro: "Ułatwiaj czytanie arkuszy Excel, konwertując je do PDF.", + privacyNote: "Plik nigdy nie opuszcza tej przeglądarki.", + includeSheets: "Arkusze do uwzględnienia", + allSheets: "Wszystkie arkusze", + firstSheetOnly: "Tylko pierwszy arkusz", + chooseFile: "Wybierz plik Excel", + clearFile: "Wyczyść", + downloadPdf: "Pobierz PDF", + building: "Tworzenie PDF…", + noFile: "Najpierw wybierz plik Excel.", + emptyWorkbook: + "Żaden arkusz w tym skoroszycie nie zawierał danych tabelarycznych do eksportu.", + unsupportedFile: "Użyj pliku .xlsx lub .xls.", + readFailed: "Nie można odczytać tego arkusza.", + buildFailed: + "Nie można utworzyć PDF. Spróbuj mniejszego skoroszytu lub mniej arkuszy.", + sheetHeading: "Arkusz: {name}", + }, + }, + ru: { + nav: "Excel в PDF", + dashboardTitle: "Excel в PDF", + dashboardDesc: + "Сделайте таблицы Excel удобнее для чтения, конвертируя их в PDF в браузере.", + panel: { + intro: "Сделайте таблицы Excel удобнее для чтения, конвертируя их в PDF.", + privacyNote: "Файл не покидает этот браузер.", + includeSheets: "Листы для включения", + allSheets: "Все листы", + firstSheetOnly: "Только первый лист", + chooseFile: "Выбрать файл Excel", + clearFile: "Очистить", + downloadPdf: "Скачать PDF", + building: "Создание PDF…", + noFile: "Сначала выберите файл Excel.", + emptyWorkbook: + "Ни на одном листе книги не было табличных данных для экспорта.", + unsupportedFile: "Используйте файл .xlsx или .xls.", + readFailed: "Не удалось прочитать эту таблицу.", + buildFailed: + "Не удалось создать PDF. Попробуйте меньшую книгу или меньше листов.", + sheetHeading: "Лист: {name}", + }, + }, + uk: { + nav: "Excel у PDF", + dashboardTitle: "Excel у PDF", + dashboardDesc: + "Зробіть таблиці Excel зручнішими для читання, конвертуючи їх у PDF у браузері.", + panel: { + intro: "Зробіть таблиці Excel зручнішими для читання, конвертуючи їх у PDF.", + privacyNote: "Файл не покидає цей браузер.", + includeSheets: "Аркуші для включення", + allSheets: "Усі аркуші", + firstSheetOnly: "Лише перший аркуш", + chooseFile: "Вибрати файл Excel", + clearFile: "Очистити", + downloadPdf: "Завантажити PDF", + building: "Створення PDF…", + noFile: "Спочатку виберіть файл Excel.", + emptyWorkbook: + "Жоден аркуш у цій книзі не містив табличних даних для експорту.", + unsupportedFile: "Використовуйте файл .xlsx або .xls.", + readFailed: "Не вдалося прочитати цю таблицю.", + buildFailed: + "Не вдалося створити PDF. Спробуйте меншу книгу або менше аркушів.", + sheetHeading: "Аркуш: {name}", + }, + }, + cs: { + nav: "Excel do PDF", + dashboardTitle: "Excel do PDF", + dashboardDesc: + "Udělejte tabulky Excel snáze čitelné jejich převodem na PDF v prohlížeči.", + panel: { + intro: "Udělejte tabulky Excel snáze čitelné převodem na PDF.", + privacyNote: "Soubor tento prohlížeč nikdy neopustí.", + includeSheets: "Listy k zahrnutí", + allSheets: "Všechny listy", + firstSheetOnly: "Pouze první list", + chooseFile: "Vybrat soubor Excel", + clearFile: "Vymazat", + downloadPdf: "Stáhnout PDF", + building: "Vytváření PDF…", + noFile: "Nejprve vyberte soubor Excel.", + emptyWorkbook: + "Žádný list v tomto sešitu neobsahoval exportovatelná tabulková data.", + unsupportedFile: "Použijte soubor .xlsx nebo .xls.", + readFailed: "Tento sešit nelze přečíst.", + buildFailed: + "PDF se nepodařilo vytvořit. Zkuste menší sešit nebo méně listů.", + sheetHeading: "List: {name}", + }, + }, + da: { + nav: "Excel til PDF", + dashboardTitle: "Excel til PDF", + dashboardDesc: + "Gør Excel-regneark lettere at læse ved at konvertere dem til PDF i browseren.", + panel: { + intro: "Gør Excel-regneark lettere at læse ved at konvertere dem til PDF.", + privacyNote: "Din fil forlader aldrig denne browser.", + includeSheets: "Ark der skal medtages", + allSheets: "Alle ark", + firstSheetOnly: "Kun første ark", + chooseFile: "Vælg Excel-fil", + clearFile: "Ryd", + downloadPdf: "Download PDF", + building: "Opretter PDF…", + noFile: "Vælg først en Excel-fil.", + emptyWorkbook: + "Intet ark i denne projektmappe indeholdt tabeldata, vi kunne eksportere.", + unsupportedFile: "Brug en .xlsx- eller .xls-fil.", + readFailed: "Kunne ikke læse dette regneark.", + buildFailed: + "Kunne ikke oprette PDF. Prøv en mindre projektmappe eller færre ark.", + sheetHeading: "Ark: {name}", + }, + }, + nb: { + nav: "Excel til PDF", + dashboardTitle: "Excel til PDF", + dashboardDesc: + "Gjør Excel-ark lettere å lese ved å konvertere dem til PDF i nettleseren.", + panel: { + intro: "Gjør Excel-ark lettere å lese ved å konvertere dem til PDF.", + privacyNote: "Filen forlater aldri denne nettleseren.", + includeSheets: "Ark som skal tas med", + allSheets: "Alle ark", + firstSheetOnly: "Kun første ark", + chooseFile: "Velg Excel-fil", + clearFile: "Tøm", + downloadPdf: "Last ned PDF", + building: "Bygger PDF…", + noFile: "Velg en Excel-fil først.", + emptyWorkbook: + "Ingen ark i denne arbeidsboken inneholdt tabelldata vi kunne eksportere.", + unsupportedFile: "Bruk en .xlsx- eller .xls-fil.", + readFailed: "Kunne ikke lese dette regnearket.", + buildFailed: + "Kunne ikke bygge PDF. Prøv en mindre arbeidsbok eller færre ark.", + sheetHeading: "Ark: {name}", + }, + }, + sv: { + nav: "Excel till PDF", + dashboardTitle: "Excel till PDF", + dashboardDesc: + "Gör Excel-kalkylblad lättare att läsa genom att konvertera dem till PDF i webbläsaren.", + panel: { + intro: "Gör Excel-kalkylblad lättare att läsa genom att konvertera dem till PDF.", + privacyNote: "Din fil lämnar aldrig den här webbläsaren.", + includeSheets: "Blad att inkludera", + allSheets: "Alla blad", + firstSheetOnly: "Endast första bladet", + chooseFile: "Välj Excel-fil", + clearFile: "Rensa", + downloadPdf: "Ladda ner PDF", + building: "Skapar PDF…", + noFile: "Välj en Excel-fil först.", + emptyWorkbook: + "Inget blad i den här arbetsboken innehöll tabelldata som vi kunde exportera.", + unsupportedFile: "Använd en .xlsx- eller .xls-fil.", + readFailed: "Kunde inte läsa detta kalkylblad.", + buildFailed: + "Kunde inte skapa PDF. Prova en mindre arbetsbok eller färre blad.", + sheetHeading: "Blad: {name}", + }, + }, + tr: { + nav: "Excel'den PDF", + dashboardTitle: "Excel'den PDF'ye", + dashboardDesc: + "Excel sayfalarını tarayıcıda PDF'ye dönüştürerek okunmasını kolaylaştırın.", + panel: { + intro: "Excel sayfalarını PDF'ye dönüştürerek okunmasını kolaylaştırın.", + privacyNote: "Dosyanız bu tarayıcıdan hiç ayrılmaz.", + includeSheets: "Dahil edilecek sayfalar", + allSheets: "Tüm sayfalar", + firstSheetOnly: "Yalnızca ilk sayfa", + chooseFile: "Excel dosyası seç", + clearFile: "Temizle", + downloadPdf: "PDF indir", + building: "PDF oluşturuluyor…", + noFile: "Önce bir Excel dosyası seçin.", + emptyWorkbook: + "Bu çalışma kitabındaki hiçbir sayfada dışa aktarılabilir tablo verisi yoktu.", + unsupportedFile: ".xlsx veya .xls dosyası kullanın.", + readFailed: "Bu e-tablo okunamadı.", + buildFailed: + "PDF oluşturulamadı. Daha küçük bir çalışma kitabı veya daha az sayfa deneyin.", + sheetHeading: "Sayfa: {name}", + }, + }, + vi: { + nav: "Excel sang PDF", + dashboardTitle: "Excel sang PDF", + dashboardDesc: + "Giúp bảng Excel dễ đọc hơn bằng cách chuyển sang PDF ngay trên trình duyệt.", + panel: { + intro: "Giúp bảng Excel dễ đọc hơn bằng cách chuyển sang PDF.", + privacyNote: "Tệp của bạn không bao giờ rời khỏi trình duyệt này.", + includeSheets: "Trang tính cần gồm", + allSheets: "Tất cả trang tính", + firstSheetOnly: "Chỉ trang tính đầu tiên", + chooseFile: "Chọn tệp Excel", + clearFile: "Xóa", + downloadPdf: "Tải PDF", + building: "Đang tạo PDF…", + noFile: "Hãy chọn tệp Excel trước.", + emptyWorkbook: + "Không trang tính nào trong sổ làm việc này có dữ liệu bảng để xuất.", + unsupportedFile: "Dùng tệp .xlsx hoặc .xls.", + readFailed: "Không đọc được bảng tính này.", + buildFailed: + "Không tạo được PDF. Hãy thử sổ làm việc nhỏ hơn hoặc ít trang tính hơn.", + sheetHeading: "Trang tính: {name}", + }, + }, + zh: { + nav: "Excel 转 PDF", + dashboardTitle: "Excel 转 PDF", + dashboardDesc: "在浏览器中将 Excel 表格转为 PDF,阅读更轻松。", + panel: { + intro: "将 Excel 表格转为 PDF,让内容更易读。", + privacyNote: "您的文件不会离开本浏览器。", + includeSheets: "包含的工作表", + allSheets: "所有工作表", + firstSheetOnly: "仅第一个工作表", + chooseFile: "选择 Excel 文件", + clearFile: "清除", + downloadPdf: "下载 PDF", + building: "正在生成 PDF…", + noFile: "请先选择 Excel 文件。", + emptyWorkbook: "此工作簿中没有可导出的表格数据。", + unsupportedFile: "请使用 .xlsx 或 .xls 文件。", + readFailed: "无法读取此电子表格。", + buildFailed: "无法生成 PDF。请尝试更小的工作簿或更少的工作表。", + sheetHeading: "工作表:{name}", + }, + }, + ja: { + nav: "Excel から PDF", + dashboardTitle: "Excel から PDF", + dashboardDesc: + "ブラウザ上で Excel を PDF に変換し、表を読みやすくします。", + panel: { + intro: "Excel を PDF に変換して、表を読みやすくします。", + privacyNote: "ファイルはこのブラウザから外部に送信されません。", + includeSheets: "含めるシート", + allSheets: "すべてのシート", + firstSheetOnly: "最初のシートのみ", + chooseFile: "Excel ファイルを選択", + clearFile: "クリア", + downloadPdf: "PDF をダウンロード", + building: "PDF を作成中…", + noFile: "先に Excel ファイルを選択してください。", + emptyWorkbook: + "このブックのどのシートにもエクスポート可能な表データがありませんでした。", + unsupportedFile: ".xlsx または .xls ファイルを使用してください。", + readFailed: "このスプレッドシートを読み取れませんでした。", + buildFailed: + "PDF を作成できませんでした。ブックを小さくするか、シート数を減らしてお試しください。", + sheetHeading: "シート: {name}", + }, + }, + ko: { + nav: "Excel을 PDF로", + dashboardTitle: "Excel을 PDF로", + dashboardDesc: + "브라우저에서 Excel을 PDF로 변환해 스프레드시트를 더 읽기 쉽게 만듭니다.", + panel: { + intro: "Excel을 PDF로 변환해 스프레드시트를 더 읽기 쉽게 만듭니다.", + privacyNote: "파일은 이 브라우저를 벗어나지 않습니다.", + includeSheets: "포함할 시트", + allSheets: "모든 시트", + firstSheetOnly: "첫 번째 시트만", + chooseFile: "Excel 파일 선택", + clearFile: "지우기", + downloadPdf: "PDF 다운로드", + building: "PDF 생성 중…", + noFile: "먼저 Excel 파일을 선택하세요.", + emptyWorkbook: + "이 통합 문서에서 보낼 수 있는 표 형식 데이터가 있는 시트를 찾을 수 없습니다.", + unsupportedFile: ".xlsx 또는 .xls 파일을 사용하세요.", + readFailed: "이 스프레드시트를 읽을 수 없습니다.", + buildFailed: + "PDF를 만들 수 없습니다. 더 작은 통합 문서나 시트 수를 줄여 보세요.", + sheetHeading: "시트: {name}", + }, + }, + ar: { + nav: "Excel إلى PDF", + dashboardTitle: "Excel إلى PDF", + dashboardDesc: + "اجعل جداول Excel أسهل للقراءة بتحويلها إلى PDF داخل المتصفح.", + panel: { + intro: "اجعل جداول Excel أسهل للقراءة بتحويلها إلى PDF.", + privacyNote: "ملفك لا يغادر هذا المتصفح أبدًا.", + includeSheets: "الأوراق المضمّنة", + allSheets: "كل الأوراق", + firstSheetOnly: "الورقة الأولى فقط", + chooseFile: "اختر ملف Excel", + clearFile: "مسح", + downloadPdf: "تنزيل PDF", + building: "جارٍ إنشاء PDF…", + noFile: "اختر ملف Excel أولًا.", + emptyWorkbook: "لم يحتوِ أي ورقة في هذا المصنف على بيانات جدول يمكن تصديرها.", + unsupportedFile: "استخدم ملف .xlsx أو .xls.", + readFailed: "تعذّر قراءة هذا الجدول.", + buildFailed: + "تعذّر إنشاء PDF. جرّب مصنفًا أصغر أو عددًا أقل من الأوراق.", + sheetHeading: "الورقة: {name}", + }, + }, + ca: { + nav: "Excel a PDF", + dashboardTitle: "Excel a PDF", + dashboardDesc: + "Feu que els fulls de càlcul Excel siguin més fàcils de llegir convertint-los a PDF al navegador.", + panel: { + intro: + "Feu que els fulls de càlcul Excel siguin més fàcils de llegir convertint-los a PDF.", + privacyNote: "El fitxer no surt mai d’aquest navegador.", + includeSheets: "Fulls a incloure", + allSheets: "Tots els fulls", + firstSheetOnly: "Només el primer full", + chooseFile: "Trieu un fitxer Excel", + clearFile: "Neteja", + downloadPdf: "Descarrega el PDF", + building: "S’està generant el PDF…", + noFile: "Trieu primer un fitxer Excel.", + emptyWorkbook: + "Cap full d’aquest llibre tenia dades de taula que poguéssim exportar.", + unsupportedFile: "Utilitzeu un fitxer .xlsx o .xls.", + readFailed: "No s’ha pogut llegir aquest full de càlcul.", + buildFailed: + "No s’ha pogut crear el PDF. Proveu un llibre més petit o menys fulls.", + sheetHeading: "Full: {name}", + }, + }, + el: { + nav: "Excel σε PDF", + dashboardTitle: "Excel σε PDF", + dashboardDesc: + "Κάντε τα φύλλα Excel πιο ευανάγνωστα μετατρέποντάς τα σε PDF στον browser.", + panel: { + intro: "Κάντε τα φύλλα Excel πιο ευανάγνωστα μετατρέποντάς τα σε PDF.", + privacyNote: "Το αρχείο σας δεν φεύγει ποτέ από αυτόν τον browser.", + includeSheets: "Φύλλα προς συμπερίληψη", + allSheets: "Όλα τα φύλλα", + firstSheetOnly: "Μόνο το πρώτο φύλλο", + chooseFile: "Επιλέξτε αρχείο Excel", + clearFile: "Καθαρισμός", + downloadPdf: "Λήψη PDF", + building: "Δημιουργία PDF…", + noFile: "Επιλέξτε πρώτα αρχείο Excel.", + emptyWorkbook: + "Κανένα φύλλο αυτού του βιβλίου δεν είχε δεδομένα πίνακα για εξαγωγή.", + unsupportedFile: "Χρησιμοποιήστε αρχείο .xlsx ή .xls.", + readFailed: "Δεν ήταν δυνατή η ανάγνωση αυτού του υπολογιστικού φύλλου.", + buildFailed: + "Δεν ήταν δυνατή η δημιουργία PDF. Δοκιμάστε μικρότερο βιβλίο ή λιγότερα φύλλα.", + sheetHeading: "Φύλλο: {name}", + }, + }, + af: { + nav: "Excel na PDF", + dashboardTitle: "Excel na PDF", + dashboardDesc: + "Maak Excel-spruitstukke makliker om te lees deur dit in die blaaier na PDF om te skakel.", + panel: { + intro: "Maak Excel-spruitstukke makliker om te lees deur dit na PDF om te skakel.", + privacyNote: "Jou lêer verlaat nooit hierdie blaaier nie.", + includeSheets: "Blaaie om in te sluit", + allSheets: "Alle blaaie", + firstSheetOnly: "Slegs eerste blad", + chooseFile: "Kies Excel-lêer", + clearFile: "Maak skoon", + downloadPdf: "Laai PDF af", + building: "Bou PDF…", + noFile: "Kies eers ’n Excel-lêer.", + emptyWorkbook: + "Geen blad in hierdie werkboek het tabeldata gehad wat ons kon uitvoer nie.", + unsupportedFile: "Gebruik ’n .xlsx- of .xls-lêer.", + readFailed: "Kon nie hierdie sigblad lees nie.", + buildFailed: + "Kon nie die PDF bou nie. Probeer ’n kleiner werkboek of minder blaaie.", + sheetHeading: "Blad: {name}", + }, + }, + id: { + nav: "Excel ke PDF", + dashboardTitle: "Excel ke PDF", + dashboardDesc: + "Buat lembar Excel lebih mudah dibaca dengan mengonversinya ke PDF di browser.", + panel: { + intro: "Buat lembar Excel lebih mudah dibaca dengan mengonversinya ke PDF.", + privacyNote: "Berkas Anda tidak pernah meninggalkan browser ini.", + includeSheets: "Lembar yang disertakan", + allSheets: "Semua lembar", + firstSheetOnly: "Hanya lembar pertama", + chooseFile: "Pilih berkas Excel", + clearFile: "Hapus", + downloadPdf: "Unduh PDF", + building: "Membuat PDF…", + noFile: "Pilih berkas Excel terlebih dahulu.", + emptyWorkbook: + "Tidak ada lembar dalam buku kerja ini yang berisi data tabel untuk diekspor.", + unsupportedFile: "Gunakan berkas .xlsx atau .xls.", + readFailed: "Tidak dapat membaca spreadsheet ini.", + buildFailed: + "Tidak dapat membuat PDF. Coba buku kerja yang lebih kecil atau lebih sedikit lembar.", + sheetHeading: "Lembar: {name}", + }, + }, + fa: { + nav: "Excel به PDF", + dashboardTitle: "Excel به PDF", + dashboardDesc: + "صفحه‌های Excel را با تبدیل به PDF در مرورگر راحت‌تر بخوانید.", + panel: { + intro: "صفحه‌های Excel را با تبدیل به PDF راحت‌تر بخوانید.", + privacyNote: "فایل شما هرگز این مرورگر را ترک نمی‌کند.", + includeSheets: "برگه‌های شامل", + allSheets: "همه برگه‌ها", + firstSheetOnly: "فقط اولین برگه", + chooseFile: "انتخاب فایل Excel", + clearFile: "پاک کردن", + downloadPdf: "دانلود PDF", + building: "در حال ساخت PDF…", + noFile: "ابتدا یک فایل Excel انتخاب کنید.", + emptyWorkbook: + "هیچ برگه‌ای در این کتاب دادهٔ جدولی برای خروجی نداشت.", + unsupportedFile: "از فایل .xlsx یا .xls استفاده کنید.", + readFailed: "خواندن این صفحه گسترده ممکن نشد.", + buildFailed: + "ساخت PDF ممکن نشد. کتاب کوچک‌تر یا برگه‌های کمتر امتحان کنید.", + sheetHeading: "برگه: {name}", + }, + }, + ms: { + nav: "Excel ke PDF", + dashboardTitle: "Excel ke PDF", + dashboardDesc: + "Jadikan helaian Excel lebih mudah dibaca dengan menukarkannya kepada PDF dalam pelayar.", + panel: { + intro: "Jadikan helaian Excel lebih mudah dibaca dengan menukarkannya kepada PDF.", + privacyNote: "Fail anda tidak pernah meninggalkan pelayar ini.", + includeSheets: "Helaian untuk disertakan", + allSheets: "Semua helaian", + firstSheetOnly: "Helaian pertama sahaja", + chooseFile: "Pilih fail Excel", + clearFile: "Kosongkan", + downloadPdf: "Muat turun PDF", + building: "Membina PDF…", + noFile: "Pilih fail Excel dahulu.", + emptyWorkbook: + "Tiada helaian dalam buku kerja ini mengandungi data jadual untuk dieksport.", + unsupportedFile: "Gunakan fail .xlsx atau .xls.", + readFailed: "Tidak dapat membaca helaian ini.", + buildFailed: + "Tidak dapat membina PDF. Cuba buku kerja yang lebih kecil atau kurang helaian.", + sheetHeading: "Helaian: {name}", + }, + }, +}; + +const expectedLocales = [ + "en", + "fr", + "es", + "ar", + "ca", + "zh", + "cs", + "el", + "de", + "da", + "af", + "id", + "fa", + "ru", + "it", + "ja", + "ko", + "ms", + "nb", + "nl", + "sv", + "pl", + "tr", + "pt", + "pt-BR", + "vi", + "uk", +]; +const keys = Object.keys(T).sort(); +if (keys.length !== expectedLocales.length || !expectedLocales.every((l) => T[l])) { + console.error("Locale set mismatch", keys); + process.exit(1); +} + +for (const [locale, bundle] of Object.entries(T)) { + const fp = path.join(messagesDir, `${locale}.json`); + if (!fs.existsSync(fp)) { + console.warn("missing file", fp); + continue; + } + const raw = fs.readFileSync(fp, "utf8"); + const data = JSON.parse(raw); + if (!data.Navigation || !data.Dashboard?.tools || !data.ExcelToPdf) { + console.error("unexpected structure", locale); + process.exit(1); + } + data.Navigation.excelToPdf = bundle.nav; + data.Dashboard.tools.excelToPdf = { + title: bundle.dashboardTitle, + description: bundle.dashboardDesc, + }; + Object.assign(data.ExcelToPdf, bundle.panel); + fs.writeFileSync(fp, JSON.stringify(data, null, 2) + "\n"); + console.log("updated", locale); +} diff --git a/apps/desktop-ui/scripts/patch-json-schema-editor-loading.cjs b/apps/desktop-ui/scripts/patch-json-schema-editor-loading.cjs new file mode 100644 index 00000000..43f9dc9f --- /dev/null +++ b/apps/desktop-ui/scripts/patch-json-schema-editor-loading.cjs @@ -0,0 +1,51 @@ +/** + * Adds JsonSchemaGenerator.editorLoading to every messages locale. + * Run: node apps/web/scripts/patch-json-schema-editor-loading.cjs + */ +const fs = require('fs'); +const path = require('path'); + +const messagesDir = path.join(__dirname, '../messages'); + +const editorLoading = { + en: 'Loading editor…', + fr: 'Chargement de l’éditeur…', + es: 'Cargando el editor…', + de: 'Editor wird geladen…', + it: 'Caricamento editor…', + pt: 'A carregar o editor…', + 'pt-BR': 'Carregando o editor…', + nl: 'Editor laden…', + sv: 'Läser in redigeraren…', + nb: 'Laster inn redigeringsprogrammet…', + da: 'Indlæser editor…', + af: 'Laai redigeerder…', + cs: 'Načítání editoru…', + pl: 'Ładowanie edytora…', + uk: 'Завантаження редактора…', + ru: 'Загрузка редактора…', + el: 'Φόρτωση επεξεργαστή…', + tr: 'Editör yükleniyor…', + ar: 'جاري تحميل المحرر…', + fa: 'در حال بارگذاری ویرایشگر…', + id: 'Memuat editor…', + ms: 'Memuatkan editor…', + vi: 'Đang tải trình soạn thảo…', + ja: 'エディタを読み込み中…', + ko: '편집기 로드 중…', + zh: '正在加载编辑器…', + ca: "S'està carregant l'editor…", +}; + +for (const file of fs.readdirSync(messagesDir)) { + if (!file.endsWith('.json')) continue; + const loc = file.replace(/\.json$/, ''); + const fp = path.join(messagesDir, file); + const j = JSON.parse(fs.readFileSync(fp, 'utf8')); + if (!j.JsonSchemaGenerator) continue; + j.JsonSchemaGenerator.editorLoading = + editorLoading[loc] || editorLoading.en; + fs.writeFileSync(fp, JSON.stringify(j, null, 2) + '\n'); +} + +console.log('Patched editorLoading for', Object.keys(editorLoading).length, 'locales (+ defaults)'); diff --git a/apps/desktop-ui/scripts/snippet-manager-nav-dash-patches.json b/apps/desktop-ui/scripts/snippet-manager-nav-dash-patches.json new file mode 100644 index 00000000..1d0ee54b --- /dev/null +++ b/apps/desktop-ui/scripts/snippet-manager-nav-dash-patches.json @@ -0,0 +1,132 @@ +{ + "fr": { + "navigation": "Extraits de code", + "title": "Extraits de code", + "description": "Enregistrez des extraits avec coloration syntaxique, détection automatique du langage et formatage." + }, + "de": { + "navigation": "Code-Snippets", + "title": "Code-Snippets", + "description": "Snippets mit Syntaxhervorhebung, automatischer Spracherkennung und Formatierung speichern." + }, + "es": { + "navigation": "Fragmentos de código", + "title": "Fragmentos de código", + "description": "Guarda fragmentos con resaltado de sintaxis, detección automática de idioma y formato." + }, + "it": { + "navigation": "Snippet di codice", + "title": "Snippet di codice", + "description": "Salva snippet con evidenziazione sintattica, rilevamento automatico della lingua e formattazione." + }, + "pt": { + "navigation": "Excertos de código", + "title": "Excertos de código", + "description": "Guarde excertos com realce de sintaxe, deteção automática de linguagem e formatação." + }, + "pt-BR": { + "navigation": "Trechos de código", + "title": "Trechos de código", + "description": "Salve trechos com realce de sintaxe, detecção automática de linguagem e formatação." + }, + "nl": { + "navigation": "Codefragmenten", + "title": "Codefragmenten", + "description": "Bewaar fragmenten met syntaxisaccentuering, automatische taaldetectie en opmaak." + }, + "pl": { + "navigation": "Fragmenty kodu", + "title": "Fragmenty kodu", + "description": "Zapisuj fragmenty z podświetlaniem składni, autowykrywaniem języka i formatowaniem." + }, + "ru": { + "navigation": "Фрагменты кода", + "title": "Фрагменты кода", + "description": "Сохраняйте сниппеты с подсветкой синтаксиса, автоопределением языка и форматированием." + }, + "uk": { + "navigation": "Фрагменти коду", + "title": "Фрагменти коду", + "description": "Зберігайте фрагменти з підсвічуванням синтаксису, авто-визначенням мови та форматуванням." + }, + "tr": { + "navigation": "Kod parçacıkları", + "title": "Kod parçacıkları", + "description": "Sözdizimi vurgulama, otomatik dil algılama ve biçimlendirme ile parçacıkları kaydedin." + }, + "ar": { + "navigation": "مقاطع الشيفرة", + "title": "مقاطع الشيفرة", + "description": "احفظ المقاطع مع تمييز بناء الجملة وكشف اللغة التلقائي والتنسيق." + }, + "fa": { + "navigation": "قطعه‌کدها", + "title": "قطعه‌کدها", + "description": "قطعه‌کدها را با برجسته‌سازی نحو، تشخیص خودکار زبان و قالب‌بندی ذخیره کنید." + }, + "zh": { + "navigation": "代码片段", + "title": "代码片段", + "description": "保存代码片段,支持语法高亮、自动检测语言和格式化。" + }, + "ja": { + "navigation": "コードスニペット", + "title": "コードスニペット", + "description": "シンタックスハイライト、自動言語検出、整形でスニペットを保存します。" + }, + "ko": { + "navigation": "코드 스니펫", + "title": "코드 스니펫", + "description": "구문 강조, 자동 언어 감지 및 서식으로 스니펫을 저장하세요." + }, + "vi": { + "navigation": "Đoạn mã", + "title": "Đoạn mã", + "description": "Lưu đoạn mã với tô sáng cú pháp, tự động nhận diện ngôn ngữ và định dạng." + }, + "id": { + "navigation": "Cuplikan kode", + "title": "Cuplikan kode", + "description": "Simpan cuplikan dengan penyorotan sintaks, deteksi bahasa otomatis, dan pemformatan." + }, + "ms": { + "navigation": "Petikan kod", + "title": "Petikan kod", + "description": "Simpan petikan dengan sorotan sintaks, pengesanan bahasa automatik dan pemformatan." + }, + "cs": { + "navigation": "Úryvky kódu", + "title": "Úryvky kódu", + "description": "Ukládejte úryvky se zvýrazněním syntaxe, automatickou detekcí jazyka a formátováním." + }, + "el": { + "navigation": "Αποσπάσματα κώδικα", + "title": "Αποσπάσματα κώδικα", + "description": "Αποθηκεύστε αποσπάσματα με τονισμό σύνταξης, αυτόματη ανίχνευση γλώσσας και μορφοποίηση." + }, + "da": { + "navigation": "Kodestykker", + "title": "Kodestykker", + "description": "Gem kodestykker med syntaksfremhævelse, automatisk sprogdetektion og formatering." + }, + "nb": { + "navigation": "Kodeutdrag", + "title": "Kodeutdrag", + "description": "Lagre utdrag med syntaksutheving, automatisk språkgjenkjenning og formatering." + }, + "sv": { + "navigation": "Kodutdrag", + "title": "Kodutdrag", + "description": "Spara utdrag med syntaxmarkering, automatisk språkupptäckt och formatering." + }, + "af": { + "navigation": "Kodesnippette", + "title": "Kodesnippette", + "description": "Bewaar snippette met sintaks-beklemtoning, outomatiese taalopsporing en formatering." + }, + "ca": { + "navigation": "Fragments de codi", + "title": "Fragments de codi", + "description": "Desa fragments amb ressaltat de sintaxi, detecció automàtica d’idioma i format." + } +} diff --git a/apps/desktop-ui/scripts/snippet-manager-patches.json b/apps/desktop-ui/scripts/snippet-manager-patches.json new file mode 100644 index 00000000..7f656b8e --- /dev/null +++ b/apps/desktop-ui/scripts/snippet-manager-patches.json @@ -0,0 +1,1094 @@ +{ + "fr": { + "title": "Gestionnaire d'extraits de code", + "subtitle": "Enregistrez des extraits dans votre navigateur ou connectez-vous pour les synchroniser avec votre compte. Passez de la consultation à l’édition, copiez en un clic, choisissez un langage ou la détection automatique, et formatez JSON, SQL ou le code pris en charge par Monaco.", + "snippets": "Extraits", + "newSnippet": "Nouvel extrait", + "newSnippetTitle": "Nouvel extrait", + "defaultSnippetTitle": "Bienvenue", + "defaultSnippetCode": "// Bienvenue dans Extraits de code\n// Essayez Affichage / Édition, Copier, Formater et langage auto\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Rechercher des extraits…", + "searchAria": "Rechercher des extraits", + "emptySearch": "Aucun extrait ne correspond à votre recherche.", + "snippetTitle": "Titre", + "snippetTitlePlaceholder": "Nom de l'extrait", + "language": "Langage", + "detectedHint": "Coloration syntaxique : {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Lecture", + "modeEdit": "Édition", + "copy": "Copier", + "copied": "Copié", + "format": "Formater", + "cancel": "Annuler", + "deleteTitle": "Supprimer l'extrait ?", + "deleteDescription": "« {title} » sera supprimé de ce navigateur.", + "deleteConfirm": "Supprimer", + "editorAria": "Éditeur de code de l'extrait", + "editorLoading": "Chargement de l'éditeur…", + "toastCopied": "Copié dans le presse-papiers", + "toastCopyFailed": "Impossible de copier", + "toastNothingToCopy": "Rien à copier", + "toastFormatted": "Formaté", + "toastFormatFailed": "Impossible de formater", + "toastFormatJsonInvalid": "JSON invalide — corrigez la syntaxe d'abord", + "toastFormatUnsupported": "Pas de formateur pour ce langage — essayez JSON ou SQL, ou passez en mode édition", + "toastDeleted": "Extrait supprimé", + "toastLoadFailed": "Impossible de charger les extraits depuis le serveur.", + "toastSyncFailed": "Impossible de synchroniser avec le serveur.", + "languages": { + "auto": "Détection automatique", + "plaintext": "Texte brut" + } + }, + "de": { + "title": "Code-Snippet-Manager", + "subtitle": "Speichern Sie Snippets im Browser oder melden Sie sich an, um sie mit Ihrem Konto zu synchronisieren. Wechseln Sie zwischen Ansicht und Bearbeitung, kopieren Sie mit einem Klick, wählen Sie eine Sprache oder Auto-Erkennung und formatieren Sie JSON, SQL oder von Monaco unterstützten Code.", + "snippets": "Snippets", + "newSnippet": "Neues Snippet", + "newSnippetTitle": "Neues Snippet", + "defaultSnippetTitle": "Willkommen", + "defaultSnippetCode": "// Willkommen bei Code-Snippets\n// Testen Sie Ansicht vs. Bearbeiten, Kopieren, Formatieren und Auto-Sprache\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Snippets durchsuchen…", + "searchAria": "Snippets durchsuchen", + "emptySearch": "Keine Snippets entsprechen Ihrer Suche.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Snippet-Name", + "language": "Sprache", + "detectedHint": "Hervorhebung als {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Ansicht", + "modeEdit": "Bearbeiten", + "copy": "Kopieren", + "copied": "Kopiert", + "format": "Formatieren", + "cancel": "Abbrechen", + "deleteTitle": "Snippet löschen?", + "deleteDescription": "„{title}“ wird aus diesem Browser entfernt.", + "deleteConfirm": "Löschen", + "editorAria": "Snippet-Code-Editor", + "editorLoading": "Editor wird geladen…", + "toastCopied": "In die Zwischenablage kopiert", + "toastCopyFailed": "Kopieren fehlgeschlagen", + "toastNothingToCopy": "Nichts zu kopieren", + "toastFormatted": "Formatiert", + "toastFormatFailed": "Formatierung fehlgeschlagen", + "toastFormatJsonInvalid": "Ungültiges JSON — zuerst Syntax beheben", + "toastFormatUnsupported": "Kein Formatierer für diese Sprache — JSON oder SQL versuchen oder Bearbeiten", + "toastDeleted": "Snippet gelöscht", + "toastLoadFailed": "Snippets konnten nicht vom Server geladen werden.", + "toastSyncFailed": "Synchronisation mit dem Server fehlgeschlagen.", + "languages": { + "auto": "Automatisch erkennen", + "plaintext": "Nur Text" + } + }, + "es": { + "title": "Administrador de fragmentos de código", + "subtitle": "Guarda fragmentos en tu navegador o inicia sesión para sincronizarlos con tu cuenta. Cambia entre ver y editar, copia con un clic, elige idioma o detección automática y formatea JSON, SQL o código compatible con Monaco.", + "snippets": "Fragmentos", + "newSnippet": "Nuevo fragmento", + "newSnippetTitle": "Nuevo fragmento", + "defaultSnippetTitle": "Bienvenido", + "defaultSnippetCode": "// Bienvenido a Fragmentos de código\n// Prueba Ver vs Editar, Copiar, Formatear y idioma automático\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Buscar fragmentos…", + "searchAria": "Buscar fragmentos", + "emptySearch": "Ningún fragmento coincide con tu búsqueda.", + "snippetTitle": "Título", + "snippetTitlePlaceholder": "Nombre del fragmento", + "language": "Lenguaje", + "detectedHint": "Resaltado como {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Ver", + "modeEdit": "Editar", + "copy": "Copiar", + "copied": "Copiado", + "format": "Formatear", + "cancel": "Cancelar", + "deleteTitle": "¿Eliminar fragmento?", + "deleteDescription": "«{title}» se eliminará de este navegador.", + "deleteConfirm": "Eliminar", + "editorAria": "Editor de código del fragmento", + "editorLoading": "Cargando editor…", + "toastCopied": "Copiado al portapapeles", + "toastCopyFailed": "No se pudo copiar", + "toastNothingToCopy": "Nada que copiar", + "toastFormatted": "Formateado", + "toastFormatFailed": "No se pudo formatear", + "toastFormatJsonInvalid": "JSON no válido — corrige la sintaxis primero", + "toastFormatUnsupported": "Sin formateador para este lenguaje — prueba JSON o SQL, o modo edición", + "toastDeleted": "Fragmento eliminado", + "toastLoadFailed": "No se pudieron cargar los fragmentos desde el servidor.", + "toastSyncFailed": "No se pudo sincronizar con el servidor.", + "languages": { + "auto": "Detección automática", + "plaintext": "Texto sin formato" + } + }, + "it": { + "title": "Gestore snippet di codice", + "subtitle": "Conserva gli snippet nel browser o accedi per sincronizzarli con il tuo account. Passa da visualizzazione a modifica, copia con un clic, scegli la lingua o il rilevamento automatico e formatta JSON, SQL o codice supportato da Monaco.", + "snippets": "Snippet", + "newSnippet": "Nuovo snippet", + "newSnippetTitle": "Nuovo snippet", + "defaultSnippetTitle": "Benvenuto", + "defaultSnippetCode": "// Benvenuto negli snippet di codice\n// Prova Visualizza vs Modifica, Copia, Formatta e lingua automatica\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Cerca snippet…", + "searchAria": "Cerca snippet", + "emptySearch": "Nessuno snippet corrisponde alla ricerca.", + "snippetTitle": "Titolo", + "snippetTitlePlaceholder": "Nome snippet", + "language": "Linguaggio", + "detectedHint": "Evidenziazione come {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Visualizza", + "modeEdit": "Modifica", + "copy": "Copia", + "copied": "Copiato", + "format": "Formatta", + "cancel": "Annulla", + "deleteTitle": "Eliminare lo snippet?", + "deleteDescription": "«{title}» verrà rimosso da questo browser.", + "deleteConfirm": "Elimina", + "editorAria": "Editor codice snippet", + "editorLoading": "Caricamento editor…", + "toastCopied": "Copiato negli appunti", + "toastCopyFailed": "Impossibile copiare", + "toastNothingToCopy": "Niente da copiare", + "toastFormatted": "Formattato", + "toastFormatFailed": "Impossibile formattare", + "toastFormatJsonInvalid": "JSON non valido — correggi prima la sintassi", + "toastFormatUnsupported": "Nessun formattatore per questo linguaggio — prova JSON o SQL o la modalità modifica", + "toastDeleted": "Snippet eliminato", + "toastLoadFailed": "Impossibile caricare gli snippet dal server.", + "toastSyncFailed": "Impossibile sincronizzare con il server.", + "languages": { + "auto": "Rilevamento automatico", + "plaintext": "Testo normale" + } + }, + "nl": { + "title": "Codefragmentbeheer", + "subtitle": "Bewaar snippets in je browser of log in om ze met je account te synchroniseren. Wissel tussen bekijken en bewerken, kopieer met één klik, kies een taal of automatische detectie en formatteer JSON, SQL of door Monaco ondersteunde code.", + "snippets": "Fragmenten", + "newSnippet": "Nieuw fragment", + "newSnippetTitle": "Nieuw fragment", + "defaultSnippetTitle": "Welkom", + "defaultSnippetCode": "// Welkom bij codefragmenten\n// Probeer bekijken vs bewerken, kopiëren, formatteren en automatische taal\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Fragmenten zoeken…", + "searchAria": "Fragmenten zoeken", + "emptySearch": "Geen fragmenten komen overeen met je zoekopdracht.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Fragmentnaam", + "language": "Taal", + "detectedHint": "Markering als {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Bekijken", + "modeEdit": "Bewerken", + "copy": "Kopiëren", + "copied": "Gekopieerd", + "format": "Formatteren", + "cancel": "Annuleren", + "deleteTitle": "Fragment verwijderen?", + "deleteDescription": "‘{title}’ wordt uit deze browser verwijderd.", + "deleteConfirm": "Verwijderen", + "editorAria": "Fragmentcode-editor", + "editorLoading": "Editor laden…", + "toastCopied": "Gekopieerd naar klembord", + "toastCopyFailed": "Kopiëren mislukt", + "toastNothingToCopy": "Niets om te kopiëren", + "toastFormatted": "Geformatteerd", + "toastFormatFailed": "Formatteren mislukt", + "toastFormatJsonInvalid": "Ongeldige JSON — los eerst de syntaxis op", + "toastFormatUnsupported": "Geen formatter voor deze taal — probeer JSON of SQL, of bewerkingsmodus", + "toastDeleted": "Fragment verwijderd", + "toastLoadFailed": "Snippets konden niet van de server worden geladen.", + "toastSyncFailed": "Synchroniseren met de server is mislukt.", + "languages": { + "auto": "Automatisch detecteren", + "plaintext": "Platte tekst" + } + }, + "pl": { + "title": "Menedżer fragmentów kodu", + "subtitle": "Przechowuj fragmenty kodu w przeglądarce lub zaloguj się, aby synchronizować je z kontem. Przełączaj widok i edycję, kopiuj jednym kliknięciem, wybierz język lub autowykrywanie i formatuj JSON, SQL lub kod obsługiwany przez Monaco.", + "snippets": "Fragmenty", + "newSnippet": "Nowy fragment", + "newSnippetTitle": "Nowy fragment", + "defaultSnippetTitle": "Witamy", + "defaultSnippetCode": "// Witamy we fragmentach kodu\n// Wypróbuj widok vs edycja, kopiowanie, formatowanie i auto-język\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Szukaj fragmentów…", + "searchAria": "Szukaj fragmentów", + "emptySearch": "Brak fragmentów pasujących do wyszukiwania.", + "snippetTitle": "Tytuł", + "snippetTitlePlaceholder": "Nazwa fragmentu", + "language": "Język", + "detectedHint": "Podświetlanie: {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Podgląd", + "modeEdit": "Edycja", + "copy": "Kopiuj", + "copied": "Skopiowano", + "format": "Formatuj", + "cancel": "Anuluj", + "deleteTitle": "Usunąć fragment?", + "deleteDescription": "„{title}” zostanie usunięty z tej przeglądarki.", + "deleteConfirm": "Usuń", + "editorAria": "Edytor kodu fragmentu", + "editorLoading": "Ładowanie edytora…", + "toastCopied": "Skopiowano do schowka", + "toastCopyFailed": "Nie udało się skopiować", + "toastNothingToCopy": "Nic do skopiowania", + "toastFormatted": "Sformatowano", + "toastFormatFailed": "Nie udało się sformatować", + "toastFormatJsonInvalid": "Nieprawidłowy JSON — najpierw popraw składnię", + "toastFormatUnsupported": "Brak formatu dla tego języka — spróbuj JSON lub SQL albo tryb edycji", + "toastDeleted": "Fragment usunięty", + "toastLoadFailed": "Nie udało się wczytać fragmentów z serwera.", + "toastSyncFailed": "Nie udało się zsynchronizować z serwerem.", + "languages": { + "auto": "Wykrywanie automatyczne", + "plaintext": "Zwykły tekst" + } + }, + "pt": { + "title": "Gestor de excertos de código", + "subtitle": "Guarde excertos no navegador ou inicie sessão para os sincronizar com a sua conta. Alterne entre ver e editar, copie com um clique, escolha a linguagem ou deteção automática e formate JSON, SQL ou código suportado pelo Monaco.", + "snippets": "Excertos", + "newSnippet": "Novo excerto", + "newSnippetTitle": "Novo excerto", + "defaultSnippetTitle": "Bem-vindo", + "defaultSnippetCode": "// Bem-vindo aos excertos de código\n// Experimente Ver vs Editar, Copiar, Formatar e idioma automático\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Pesquisar excertos…", + "searchAria": "Pesquisar excertos", + "emptySearch": "Nenhum excerto corresponde à pesquisa.", + "snippetTitle": "Título", + "snippetTitlePlaceholder": "Nome do excerto", + "language": "Linguagem", + "detectedHint": "Realce como {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Ver", + "modeEdit": "Editar", + "copy": "Copiar", + "copied": "Copiado", + "format": "Formatar", + "cancel": "Cancelar", + "deleteTitle": "Eliminar excerto?", + "deleteDescription": "«{title}» será removido deste navegador.", + "deleteConfirm": "Eliminar", + "editorAria": "Editor de código do excerto", + "editorLoading": "A carregar editor…", + "toastCopied": "Copiado para a área de transferência", + "toastCopyFailed": "Não foi possível copiar", + "toastNothingToCopy": "Nada para copiar", + "toastFormatted": "Formatado", + "toastFormatFailed": "Não foi possível formatar", + "toastFormatJsonInvalid": "JSON inválido — corrija a sintaxe primeiro", + "toastFormatUnsupported": "Sem formatador para esta linguagem — tente JSON ou SQL, ou modo de edição", + "toastDeleted": "Excerto eliminado", + "toastLoadFailed": "Não foi possível carregar os excertos do servidor.", + "toastSyncFailed": "Não foi possível sincronizar com o servidor.", + "languages": { + "auto": "Deteção automática", + "plaintext": "Texto simples" + } + }, + "pt-BR": { + "title": "Gerenciador de trechos de código", + "subtitle": "Armazene trechos no navegador ou entre para sincronizá-los com sua conta. Alterne entre ver e editar, copie com um clique, escolha a linguagem ou detecção automática e formate JSON, SQL ou código suportado pelo Monaco.", + "snippets": "Trechos", + "newSnippet": "Novo trecho", + "newSnippetTitle": "Novo trecho", + "defaultSnippetTitle": "Bem-vindo", + "defaultSnippetCode": "// Bem-vindo aos trechos de código\n// Experimente Ver vs Editar, Copiar, Formatar e idioma automático\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Buscar trechos…", + "searchAria": "Buscar trechos", + "emptySearch": "Nenhum trecho corresponde à busca.", + "snippetTitle": "Título", + "snippetTitlePlaceholder": "Nome do trecho", + "language": "Linguagem", + "detectedHint": "Realce como {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Ver", + "modeEdit": "Editar", + "copy": "Copiar", + "copied": "Copiado", + "format": "Formatar", + "cancel": "Cancelar", + "deleteTitle": "Excluir trecho?", + "deleteDescription": "“{title}” será removido deste navegador.", + "deleteConfirm": "Excluir", + "editorAria": "Editor de código do trecho", + "editorLoading": "Carregando editor…", + "toastCopied": "Copiado para a área de transferência", + "toastCopyFailed": "Não foi possível copiar", + "toastNothingToCopy": "Nada para copiar", + "toastFormatted": "Formatado", + "toastFormatFailed": "Não foi possível formatar", + "toastFormatJsonInvalid": "JSON inválido — corrija a sintaxe primeiro", + "toastFormatUnsupported": "Sem formatador para esta linguagem — tente JSON ou SQL, ou modo de edição", + "toastDeleted": "Trecho excluído", + "toastLoadFailed": "Não foi possível carregar os trechos do servidor.", + "toastSyncFailed": "Não foi possível sincronizar com o servidor.", + "languages": { + "auto": "Detecção automática", + "plaintext": "Texto simples" + } + }, + "ru": { + "title": "Менеджер фрагментов кода", + "subtitle": "Храните сниппеты в браузере или войдите, чтобы синхронизировать их с аккаунтом. Переключайтесь между просмотром и правкой, копируйте в один клик, выбирайте язык или автоопределение и форматируйте JSON, SQL или код, поддерживаемый Monaco.", + "snippets": "Сниппеты", + "newSnippet": "Новый сниппет", + "newSnippetTitle": "Новый сниппет", + "defaultSnippetTitle": "Добро пожаловать", + "defaultSnippetCode": "// Добро пожаловать в фрагменты кода\n// Попробуйте просмотр и правку, копирование, форматирование и автоязык\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Поиск сниппетов…", + "searchAria": "Поиск сниппетов", + "emptySearch": "Нет сниппетов по запросу.", + "snippetTitle": "Заголовок", + "snippetTitlePlaceholder": "Имя сниппета", + "language": "Язык", + "detectedHint": "Подсветка: {lang}", + "badgeAuto": "Авто · {lang}", + "modeView": "Просмотр", + "modeEdit": "Правка", + "copy": "Копировать", + "copied": "Скопировано", + "format": "Форматировать", + "cancel": "Отмена", + "deleteTitle": "Удалить сниппет?", + "deleteDescription": "«{title}» будет удалён из этого браузера.", + "deleteConfirm": "Удалить", + "editorAria": "Редактор кода сниппета", + "editorLoading": "Загрузка редактора…", + "toastCopied": "Скопировано в буфер", + "toastCopyFailed": "Не удалось скопировать", + "toastNothingToCopy": "Нечего копировать", + "toastFormatted": "Отформатировано", + "toastFormatFailed": "Не удалось отформатировать", + "toastFormatJsonInvalid": "Неверный JSON — сначала исправьте синтаксис", + "toastFormatUnsupported": "Нет форматтера для этого языка — попробуйте JSON или SQL или режим правки", + "toastDeleted": "Сниппет удалён", + "toastLoadFailed": "Не удалось загрузить сниппеты с сервера.", + "toastSyncFailed": "Не удалось синхронизировать с сервером.", + "languages": { + "auto": "Автоопределение", + "plaintext": "Простой текст" + } + }, + "uk": { + "title": "Менеджер фрагментів коду", + "subtitle": "Зберігайте фрагменти коду в браузері або увійдіть, щоб синхронізувати їх з обліковим записом. Перемикайте перегляд і редагування, копіюйте одним кліком, обирайте мову або авто-визначення та форматуйте JSON, SQL або код, який підтримує Monaco.", + "snippets": "Фрагменти", + "newSnippet": "Новий фрагмент", + "newSnippetTitle": "Новий фрагмент", + "defaultSnippetTitle": "Ласкаво просимо", + "defaultSnippetCode": "// Ласкаво просимо до фрагментів коду\n// Спробуйте перегляд і редагування, копіювання, форматування та авто-мову\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Пошук фрагментів…", + "searchAria": "Пошук фрагментів", + "emptySearch": "Немає фрагментів за запитом.", + "snippetTitle": "Заголовок", + "snippetTitlePlaceholder": "Назва фрагмента", + "language": "Мова", + "detectedHint": "Підсвічування: {lang}", + "badgeAuto": "Авто · {lang}", + "modeView": "Перегляд", + "modeEdit": "Редагування", + "copy": "Копіювати", + "copied": "Скопійовано", + "format": "Форматувати", + "cancel": "Скасувати", + "deleteTitle": "Видалити фрагмент?", + "deleteDescription": "«{title}» буде вилучено з цього браузера.", + "deleteConfirm": "Видалити", + "editorAria": "Редактор коду фрагмента", + "editorLoading": "Завантаження редактора…", + "toastCopied": "Скопійовано в буфер", + "toastCopyFailed": "Не вдалося скопіювати", + "toastNothingToCopy": "Нічого копіювати", + "toastFormatted": "Відформатовано", + "toastFormatFailed": "Не вдалося відформатувати", + "toastFormatJsonInvalid": "Некоректний JSON — спочатку виправте синтаксис", + "toastFormatUnsupported": "Немає форматера для цієї мови — спробуйте JSON або SQL або режим редагування", + "toastDeleted": "Фрагмент видалено", + "toastLoadFailed": "Не вдалося завантажити фрагменти з сервера.", + "toastSyncFailed": "Не вдалося синхронізувати з сервером.", + "languages": { + "auto": "Автовизначення", + "plaintext": "Звичайний текст" + } + }, + "tr": { + "title": "Kod parçacığı yöneticisi", + "subtitle": "Parçacıkları tarayıcıda saklayın veya hesabınızla eşitlemek için oturum açın. Görüntüleme ve düzenleme arasında geçiş yapın, tek tıkla kopyalayın, dil seçin veya otomatik algılayın ve JSON, SQL veya Monaco destekli kodu biçimlendirin.", + "snippets": "Parçacıklar", + "newSnippet": "Yeni parçacık", + "newSnippetTitle": "Yeni parçacık", + "defaultSnippetTitle": "Hoş geldiniz", + "defaultSnippetCode": "// Kod parçacıklarına hoş geldiniz\n// Görüntüle vs düzenle, kopyala, biçimlendir ve otomatik dil deneyin\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Parçacık ara…", + "searchAria": "Parçacık ara", + "emptySearch": "Aramanızla eşleşen parçacık yok.", + "snippetTitle": "Başlık", + "snippetTitlePlaceholder": "Parçacık adı", + "language": "Dil", + "detectedHint": "Vurgulama: {lang}", + "badgeAuto": "Otomatik · {lang}", + "modeView": "Görüntüle", + "modeEdit": "Düzenle", + "copy": "Kopyala", + "copied": "Kopyalandı", + "format": "Biçimlendir", + "cancel": "İptal", + "deleteTitle": "Parçacık silinsin mi?", + "deleteDescription": "“{title}” bu tarayıcıdan kaldırılacak.", + "deleteConfirm": "Sil", + "editorAria": "Parçacık kod düzenleyici", + "editorLoading": "Düzenleyici yükleniyor…", + "toastCopied": "Panoya kopyalandı", + "toastCopyFailed": "Kopyalanamadı", + "toastNothingToCopy": "Kopyalanacak bir şey yok", + "toastFormatted": "Biçimlendirildi", + "toastFormatFailed": "Biçimlendirilemedi", + "toastFormatJsonInvalid": "Geçersiz JSON — önce sözdizimini düzeltin", + "toastFormatUnsupported": "Bu dil için biçimlendirici yok — JSON veya SQL deneyin ya da düzenleme modunu kullanın", + "toastDeleted": "Parçacık silindi", + "toastLoadFailed": "Parçacıklar sunucudan yüklenemedi.", + "toastSyncFailed": "Sunucu ile eşitleme başarısız oldu.", + "languages": { + "auto": "Otomatik algıla", + "plaintext": "Düz metin" + } + }, + "cs": { + "title": "Správce úryvků kódu", + "subtitle": "Ukládejte úryvky v prohlížeči nebo se přihlaste a synchronizujte je s účtem. Přepínejte zobrazení a úpravy, kopírujte jedním kliknutím, volte jazyk nebo automatickou detekci a formátujte JSON, SQL nebo kód podporovaný Monacem.", + "snippets": "Úryvky", + "newSnippet": "Nový úryvek", + "newSnippetTitle": "Nový úryvek", + "defaultSnippetTitle": "Vítejte", + "defaultSnippetCode": "// Vítejte u úryvků kódu\n// Zkuste zobrazení vs úpravy, kopírování, formátování a automatický jazyk\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Hledat úryvky…", + "searchAria": "Hledat úryvky", + "emptySearch": "Žádný úryvek neodpovídá hledání.", + "snippetTitle": "Název", + "snippetTitlePlaceholder": "Název úryvku", + "language": "Jazyk", + "detectedHint": "Zvýraznění: {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Zobrazit", + "modeEdit": "Upravit", + "copy": "Kopírovat", + "copied": "Zkopírováno", + "format": "Formátovat", + "cancel": "Zrušit", + "deleteTitle": "Smazat úryvek?", + "deleteDescription": "„{title}“ bude odebrán z tohoto prohlížeče.", + "deleteConfirm": "Smazat", + "editorAria": "Editor kódu úryvku", + "editorLoading": "Načítání editoru…", + "toastCopied": "Zkopírováno do schránky", + "toastCopyFailed": "Kopírování se nezdařilo", + "toastNothingToCopy": "Nic ke kopírování", + "toastFormatted": "Naformátováno", + "toastFormatFailed": "Formátování se nezdařilo", + "toastFormatJsonInvalid": "Neplatné JSON — nejdřív opravte syntaxi", + "toastFormatUnsupported": "Pro tento jazyk není formátovač — zkuste JSON nebo SQL nebo režim úprav", + "toastDeleted": "Úryvek smazán", + "toastLoadFailed": "Úryvky se nepodařilo načíst ze serveru.", + "toastSyncFailed": "Synchronizace se serverem se nezdařila.", + "languages": { + "auto": "Automatická detekce", + "plaintext": "Prostý text" + } + }, + "el": { + "title": "Διαχειριστής αποσπασμάτων κώδικα", + "subtitle": "Αποθηκεύστε αποσπάσματα στον browser ή συνδεθείτε για συγχρονισμό με τον λογαριασμό σας. Εναλλαγή προβολής και επεξεργασίας, αντιγραφή με ένα κλικ, επιλογή γλώσσας ή αυτόματη ανίχνευση και μορφοποίηση JSON, SQL ή κώδικα που υποστηρίζει το Monaco.", + "snippets": "Αποσπάσματα", + "newSnippet": "Νέο απόσπασμα", + "newSnippetTitle": "Νέο απόσπασμα", + "defaultSnippetTitle": "Καλώς ήρθατε", + "defaultSnippetCode": "// Καλώς ήρθατε στα αποσπάσματα κώδικα\n// Δοκιμάστε προβολή vs επεξεργασία, αντιγραφή, μορφοποίηση και αυτόματη γλώσσα\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Αναζήτηση αποσπασμάτων…", + "searchAria": "Αναζήτηση αποσπασμάτων", + "emptySearch": "Κανένα απόσπασμα δεν ταιριάζει στην αναζήτηση.", + "snippetTitle": "Τίτλος", + "snippetTitlePlaceholder": "Όνομα αποσπάσματος", + "language": "Γλώσσα", + "detectedHint": "Τονισμός ως {lang}", + "badgeAuto": "Αυτόματο · {lang}", + "modeView": "Προβολή", + "modeEdit": "Επεξεργασία", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε", + "format": "Μορφοποίηση", + "cancel": "Άκυρο", + "deleteTitle": "Διαγραφή αποσπάσματος;", + "deleteDescription": "Το «{title}» θα αφαιρεθεί από αυτόν τον browser.", + "deleteConfirm": "Διαγραφή", + "editorAria": "Επεξεργαστής κώδικα αποσπάσματος", + "editorLoading": "Φόρτωση επεξεργαστή…", + "toastCopied": "Αντιγράφηκε στο πρόχειρο", + "toastCopyFailed": "Αδυναμία αντιγραφής", + "toastNothingToCopy": "Τίποτα για αντιγραφή", + "toastFormatted": "Μορφοποιήθηκε", + "toastFormatFailed": "Αδυναμία μορφοποίησης", + "toastFormatJsonInvalid": "Μη έγκυρο JSON — διορθώστε πρώτα τη σύνταξη", + "toastFormatUnsupported": "Δεν υπάρχει μορφοποιητής για αυτή τη γλώσσα — δοκιμάστε JSON ή SQL ή λειτουργία επεξεργασίας", + "toastDeleted": "Το απόσπασμα διαγράφηκε", + "toastLoadFailed": "Δεν ήταν δυνατή η φόρτωση αποσπασμάτων από τον διακομιστή.", + "toastSyncFailed": "Δεν ήταν δυνατός ο συγχρονισμός με τον διακομιστή.", + "languages": { + "auto": "Αυτόματη ανίχνευση", + "plaintext": "Απλό κείμενο" + } + }, + "da": { + "title": "Kodestykke-manager", + "subtitle": "Gem snippets i browseren eller log ind for at synkronisere med din konto. Skift mellem visning og redigering, kopiér med ét klik, vælg sprog eller autodetektér, og formatér JSON, SQL eller Monaco-understøttet kode.", + "snippets": "Snippets", + "newSnippet": "Nyt snippet", + "newSnippetTitle": "Nyt snippet", + "defaultSnippetTitle": "Velkommen", + "defaultSnippetCode": "// Velkommen til kodestykker\n// Prøv visning vs redigering, kopiér, formatér og automatisk sprog\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Søg i snippets…", + "searchAria": "Søg i snippets", + "emptySearch": "Ingen snippets matcher din søgning.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Snippet-navn", + "language": "Sprog", + "detectedHint": "Fremhævning som {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Vis", + "modeEdit": "Rediger", + "copy": "Kopiér", + "copied": "Kopieret", + "format": "Formatér", + "cancel": "Annuller", + "deleteTitle": "Slet snippet?", + "deleteDescription": "«{title}» fjernes fra denne browser.", + "deleteConfirm": "Slet", + "editorAria": "Snippet-kodeeditor", + "editorLoading": "Indlæser editor…", + "toastCopied": "Kopieret til udklipsholder", + "toastCopyFailed": "Kunne ikke kopiere", + "toastNothingToCopy": "Intet at kopiere", + "toastFormatted": "Formateret", + "toastFormatFailed": "Kunne ikke formatere", + "toastFormatJsonInvalid": "Ugyldig JSON — ret syntaksen først", + "toastFormatUnsupported": "Ingen formatter til dette sprog — prøv JSON eller SQL eller redigeringstilstand", + "toastDeleted": "Snippet slettet", + "toastLoadFailed": "Kunne ikke indlæse snippets fra serveren.", + "toastSyncFailed": "Kunne ikke synkronisere med serveren.", + "languages": { + "auto": "Automatisk registrering", + "plaintext": "Almindelig tekst" + } + }, + "nb": { + "title": "Kodeutklipp-administrator", + "subtitle": "Lagre utdrag i nettleseren eller logg inn for å synkronisere med kontoen din. Bytt mellom visning og redigering, kopier med ett klikk, velg språk eller automatisk gjenkjenning, og formater JSON, SQL eller Monaco-støttet kode.", + "snippets": "Utdrag", + "newSnippet": "Nytt utdrag", + "newSnippetTitle": "Nytt utdrag", + "defaultSnippetTitle": "Velkommen", + "defaultSnippetCode": "// Velkommen til kodeutdrag\n// Prøv visning vs redigering, kopier, formater og automatisk språk\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Søk i utdrag…", + "searchAria": "Søk i utdrag", + "emptySearch": "Ingen utdrag samsvarer med søket.", + "snippetTitle": "Tittel", + "snippetTitlePlaceholder": "Navn på utdrag", + "language": "Språk", + "detectedHint": "Utheving som {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Vis", + "modeEdit": "Rediger", + "copy": "Kopier", + "copied": "Kopiert", + "format": "Formater", + "cancel": "Avbryt", + "deleteTitle": "Slette utdrag?", + "deleteDescription": "«{title}» fjernes fra denne nettleseren.", + "deleteConfirm": "Slett", + "editorAria": "Kodeeditor for utdrag", + "editorLoading": "Laster editor…", + "toastCopied": "Kopiert til utklippstavle", + "toastCopyFailed": "Kunne ikke kopiere", + "toastNothingToCopy": "Ingenting å kopiere", + "toastFormatted": "Formatert", + "toastFormatFailed": "Kunne ikke formatere", + "toastFormatJsonInvalid": "Ugyldig JSON — rett syntaksen først", + "toastFormatUnsupported": "Ingen formattering for dette språket — prøv JSON eller SQL eller redigeringsmodus", + "toastDeleted": "Utdrag slettet", + "toastLoadFailed": "Kunne ikke laste utdrag fra serveren.", + "toastSyncFailed": "Kunne ikke synkronisere med serveren.", + "languages": { + "auto": "Automatisk gjenkjenning", + "plaintext": "Ren tekst" + } + }, + "sv": { + "title": "Hanterare för kodutdrag", + "subtitle": "Spara utdrag i webbläsaren eller logga in för att synka med ditt konto. Växla mellan visning och redigering, kopiera med ett klick, välj språk eller autoupptäckt, och formatera JSON, SQL eller Monaco-stödd kod.", + "snippets": "Utdrag", + "newSnippet": "Nytt utdrag", + "newSnippetTitle": "Nytt utdrag", + "defaultSnippetTitle": "Välkommen", + "defaultSnippetCode": "// Välkommen till kodutdrag\n// Prova visning vs redigera, kopiera, formatera och automatiskt språk\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Sök utdrag…", + "searchAria": "Sök utdrag", + "emptySearch": "Inga utdrag matchar sökningen.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Utdragsnamn", + "language": "Språk", + "detectedHint": "Syntaxmarkering som {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Visa", + "modeEdit": "Redigera", + "copy": "Kopiera", + "copied": "Kopierat", + "format": "Formatera", + "cancel": "Avbryt", + "deleteTitle": "Ta bort utdrag?", + "deleteDescription": "”{title}” tas bort från den här webbläsaren.", + "deleteConfirm": "Ta bort", + "editorAria": "Kodredigerare för utdrag", + "editorLoading": "Laddar redigerare…", + "toastCopied": "Kopierat till urklipp", + "toastCopyFailed": "Kunde inte kopiera", + "toastNothingToCopy": "Inget att kopiera", + "toastFormatted": "Formaterat", + "toastFormatFailed": "Kunde inte formatera", + "toastFormatJsonInvalid": "Ogiltig JSON — åtgärda syntaxen först", + "toastFormatUnsupported": "Ingen formaterare för detta språk — prova JSON eller SQL eller redigeringsläge", + "toastDeleted": "Utdrag borttaget", + "toastLoadFailed": "Kunde inte läsa in utdrag från servern.", + "toastSyncFailed": "Kunde inte synkronisera med servern.", + "languages": { + "auto": "Automatisk avkänning", + "plaintext": "Oformaterad text" + } + }, + "af": { + "title": "Kodesnippet-bestuurder", + "subtitle": "Bewaar snippette in jou blaaier of meld aan om dit met jou rekening te sinkroniseer. Wissel tussen bekyk en wysig, kopieer met een klik, kies taal of outo-opsporing, en formatteer JSON, SQL of Monaco-ondersteunde kode.", + "snippets": "Snippette", + "newSnippet": "Nuwe snippet", + "newSnippetTitle": "Nuwe snippet", + "defaultSnippetTitle": "Welkom", + "defaultSnippetCode": "// Welkom by kodesnippette\n// Probeer bekyk vs wysig, kopieer, formateer en outo-taal\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Soek snippette…", + "searchAria": "Soek snippette", + "emptySearch": "Geen snippette pas by jou soektog nie.", + "snippetTitle": "Titel", + "snippetTitlePlaceholder": "Snippett-naam", + "language": "Taal", + "detectedHint": "Beklemtoning as {lang}", + "badgeAuto": "Outo · {lang}", + "modeView": "Bekyk", + "modeEdit": "Wysig", + "copy": "Kopieer", + "copied": "Gekopieer", + "format": "Formateer", + "cancel": "Kanselleer", + "deleteTitle": "Snippett uitvee?", + "deleteDescription": "“{title}” sal uit hierdie blaaier verwyder word.", + "deleteConfirm": "Vee uit", + "editorAria": "Snippett-kode-redigeerder", + "editorLoading": "Laai redigeerder…", + "toastCopied": "Gekopieer na knipbord", + "toastCopyFailed": "Kon nie kopieer nie", + "toastNothingToCopy": "Niks om te kopieer nie", + "toastFormatted": "Geformatteer", + "toastFormatFailed": "Kon nie formateer nie", + "toastFormatJsonInvalid": "Ongeldige JSON — herstel eers sintaksis", + "toastFormatUnsupported": "Geen formateerder vir hierdie taal nie — probeer JSON of SQL of wysig-modus", + "toastDeleted": "Snippett uitgevee", + "toastLoadFailed": "Kon nie snippette van die bediener laai nie.", + "toastSyncFailed": "Kon nie met die bediener sinkroniseer nie.", + "languages": { + "auto": "Outomatiese opsporing", + "plaintext": "Gewone teks" + } + }, + "ca": { + "title": "Gestor de fragments de codi", + "subtitle": "Emmagatzema fragments al navegador o inicia sessió per sincronitzar-los amb el teu compte. Canvia entre veure i editar, copia amb un clic, tria idioma o detecció automàtica i formata JSON, SQL o codi compatible amb Monaco.", + "snippets": "Fragments", + "newSnippet": "Fragment nou", + "newSnippetTitle": "Fragment nou", + "defaultSnippetTitle": "Benvingut", + "defaultSnippetCode": "// Benvingut als fragments de codi\n// Prova veure vs editar, copia, formata i idioma automàtic\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Cerca fragments…", + "searchAria": "Cerca fragments", + "emptySearch": "Cap fragment no coincideix amb la cerca.", + "snippetTitle": "Títol", + "snippetTitlePlaceholder": "Nom del fragment", + "language": "Llenguatge", + "detectedHint": "Ressaltat com a {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Veure", + "modeEdit": "Editar", + "copy": "Copiar", + "copied": "Copiat", + "format": "Formatejar", + "cancel": "Cancel·la", + "deleteTitle": "Eliminar el fragment?", + "deleteDescription": "«{title}» se suprimirà d'aquest navegador.", + "deleteConfirm": "Eliminar", + "editorAria": "Editor de codi del fragment", + "editorLoading": "Carregant l'editor…", + "toastCopied": "Copiat al porta-retalls", + "toastCopyFailed": "No s'ha pogut copiar", + "toastNothingToCopy": "Res a copiar", + "toastFormatted": "Formatejat", + "toastFormatFailed": "No s'ha pogut formatejar", + "toastFormatJsonInvalid": "JSON no vàlid — corregeix la sintaxi primer", + "toastFormatUnsupported": "Sense formatador per a aquest llenguatge — prova JSON o SQL o mode edició", + "toastDeleted": "Fragment eliminat", + "toastLoadFailed": "No s'han pogut carregar els fragments des del servidor.", + "toastSyncFailed": "No s'ha pogut sincronitzar amb el servidor.", + "languages": { + "auto": "Detecció automàtica", + "plaintext": "Text sense format" + } + }, + "ar": { + "title": "مدير مقاطع الشيفرة", + "subtitle": "خزّن المقاطع في المتصفح أو سجّل الدخول لمزامنتها مع حسابك. بدّل بين العرض والتحرير، انسخ بنقرة واحدة، اختر لغة أو كشفًا تلقائيًا، ونسّق JSON أو SQL أو أكواد يدعمها Monaco.", + "snippets": "المقاطع", + "newSnippet": "مقطع جديد", + "newSnippetTitle": "مقطع جديد", + "defaultSnippetTitle": "مرحبًا", + "defaultSnippetCode": "// مرحبًا بك في مقاطع الشيفرة\n// جرّب العرض مقابل التحرير، النسخ، التنسيق واللغة التلقائية\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "البحث في المقاطع…", + "searchAria": "البحث في المقاطع", + "emptySearch": "لا توجد مقاطع تطابق بحثك.", + "snippetTitle": "العنوان", + "snippetTitlePlaceholder": "اسم المقطع", + "language": "اللغة", + "detectedHint": "تمييز بناء الجملة: {lang}", + "badgeAuto": "تلقائي · {lang}", + "modeView": "عرض", + "modeEdit": "تحرير", + "copy": "نسخ", + "copied": "تم النسخ", + "format": "تنسيق", + "cancel": "إلغاء", + "deleteTitle": "حذف المقطع؟", + "deleteDescription": "سيتم إزالة «{title}» من هذا المتصفح.", + "deleteConfirm": "حذف", + "editorAria": "محرر شيفرة المقطع", + "editorLoading": "جاري تحميل المحرر…", + "toastCopied": "تم النسخ إلى الحافظة", + "toastCopyFailed": "تعذّر النسخ", + "toastNothingToCopy": "لا يوجد شيء للنسخ", + "toastFormatted": "تم التنسيق", + "toastFormatFailed": "تعذّر التنسيق", + "toastFormatJsonInvalid": "JSON غير صالح — صحّح الصياغة أولًا", + "toastFormatUnsupported": "لا يوجد مُنسّق لهذه اللغة — جرّب JSON أو SQL أو وضع التحرير", + "toastDeleted": "تم حذف المقطع", + "toastLoadFailed": "تعذّر تحميل المقاطع من الخادم.", + "toastSyncFailed": "تعذّرت المزامنة مع الخادم.", + "languages": { + "auto": "اكتشاف تلقائي", + "plaintext": "نص عادي" + } + }, + "fa": { + "title": "مدیریت قطعه‌کد", + "subtitle": "قطعه‌کدها را در مرورگر نگه دارید یا برای همگام‌سازی با حساب وارد شوید. بین مشاهده و ویرایش جابه‌جا شوید، با یک کلیک کپی کنید، زبان را انتخاب یا خودکار تشخیص دهید و JSON، SQL یا کد پشتیبانی‌شده توسط Monaco را قالب‌بندی کنید.", + "snippets": "قطعه‌کدها", + "newSnippet": "قطعه‌کد جدید", + "newSnippetTitle": "قطعه‌کد جدید", + "defaultSnippetTitle": "خوش آمدید", + "defaultSnippetCode": "// به قطعه‌کدها خوش آمدید\n// حالت مشاهده در برابر ویرایش، رونوشت، قالب‌بندی و زبان خودکار را امتحان کنید\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "جستجوی قطعه‌کدها…", + "searchAria": "جستجوی قطعه‌کدها", + "emptySearch": "هیچ قطعه‌ای با جستجو هم‌خوان نیست.", + "snippetTitle": "عنوان", + "snippetTitlePlaceholder": "نام قطعه", + "language": "زبان", + "detectedHint": "برجسته‌سازی به‌صورت {lang}", + "badgeAuto": "خودکار · {lang}", + "modeView": "مشاهده", + "modeEdit": "ویرایش", + "copy": "رونوشت", + "copied": "رونوشت شد", + "format": "قالب‌بندی", + "cancel": "لغو", + "deleteTitle": "قطعه‌کد حذف شود؟", + "deleteDescription": "«{title}» از این مرورگر حذف می‌شود.", + "deleteConfirm": "حذف", + "editorAria": "ویرایشگر کد قطعه", + "editorLoading": "در حال بارگذاری ویرایشگر…", + "toastCopied": "در بریده‌دان رونوشت شد", + "toastCopyFailed": "رونوشت ممکن نشد", + "toastNothingToCopy": "چیزی برای رونوشت نیست", + "toastFormatted": "قالب‌بندی شد", + "toastFormatFailed": "قالب‌بندی ممکن نشد", + "toastFormatJsonInvalid": "JSON نامعتبر — نخست نحو را درست کنید", + "toastFormatUnsupported": "قالب‌بند برای این زبان نیست — JSON یا SQL یا حالت ویرایش را امتحان کنید", + "toastDeleted": "قطعه حذف شد", + "toastLoadFailed": "بارگیری قطعه‌کدها از سرور انجام نشد.", + "toastSyncFailed": "همگام‌سازی با سرور انجام نشد.", + "languages": { + "auto": "تشخیص خودکار", + "plaintext": "متن ساده" + } + }, + "zh": { + "title": "代码片段管理器", + "subtitle": "在浏览器中保存代码片段,或登录以同步到账户。在查看与编辑间切换、一键复制、选择语言或使用自动检测,并格式化 JSON、SQL 或 Monaco 支持的代码。", + "snippets": "片段", + "newSnippet": "新建片段", + "newSnippetTitle": "新建片段", + "defaultSnippetTitle": "欢迎", + "defaultSnippetCode": "// 欢迎使用代码片段\n// 试试查看/编辑、复制、格式化与自动语言\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "搜索片段…", + "searchAria": "搜索片段", + "emptySearch": "没有匹配的片段。", + "snippetTitle": "标题", + "snippetTitlePlaceholder": "片段名称", + "language": "语言", + "detectedHint": "高亮为 {lang}", + "badgeAuto": "自动 · {lang}", + "modeView": "查看", + "modeEdit": "编辑", + "copy": "复制", + "copied": "已复制", + "format": "格式化", + "cancel": "取消", + "deleteTitle": "删除片段?", + "deleteDescription": "将从此浏览器移除「{title}」。", + "deleteConfirm": "删除", + "editorAria": "片段代码编辑器", + "editorLoading": "正在加载编辑器…", + "toastCopied": "已复制到剪贴板", + "toastCopyFailed": "无法复制", + "toastNothingToCopy": "没有可复制的内容", + "toastFormatted": "已格式化", + "toastFormatFailed": "无法格式化", + "toastFormatJsonInvalid": "JSON 无效 — 请先修正语法", + "toastFormatUnsupported": "此语言没有格式化程序 — 请尝试 JSON 或 SQL,或使用编辑模式", + "toastDeleted": "片段已删除", + "toastLoadFailed": "无法从服务器加载代码片段。", + "toastSyncFailed": "无法与服务器同步。", + "languages": { + "auto": "自动检测", + "plaintext": "纯文本" + } + }, + "ja": { + "title": "コードスニペットマネージャー", + "subtitle": "ブラウザにスニペットを保存するか、サインインしてアカウントと同期します。表示と編集の切り替え、ワンクリックコピー、言語の選択または自動検出、JSON・SQL・Monaco 対応コードの整形ができます。", + "snippets": "スニペット", + "newSnippet": "新しいスニペット", + "newSnippetTitle": "新しいスニペット", + "defaultSnippetTitle": "ようこそ", + "defaultSnippetCode": "// コードスニペットへようこそ\n// 表示と編集、コピー、整形、自動言語をお試しください\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "スニペットを検索…", + "searchAria": "スニペットを検索", + "emptySearch": "一致するスニペットがありません。", + "snippetTitle": "タイトル", + "snippetTitlePlaceholder": "スニペット名", + "language": "言語", + "detectedHint": "ハイライト: {lang}", + "badgeAuto": "自動 · {lang}", + "modeView": "表示", + "modeEdit": "編集", + "copy": "コピー", + "copied": "コピーしました", + "format": "整形", + "cancel": "キャンセル", + "deleteTitle": "スニペットを削除しますか?", + "deleteDescription": "「{title}」はこのブラウザから削除されます。", + "deleteConfirm": "削除", + "editorAria": "スニペットコードエディター", + "editorLoading": "エディターを読み込み中…", + "toastCopied": "クリップボードにコピーしました", + "toastCopyFailed": "コピーできませんでした", + "toastNothingToCopy": "コピーする内容がありません", + "toastFormatted": "整形しました", + "toastFormatFailed": "整形できませんでした", + "toastFormatJsonInvalid": "無効な JSON — 先に構文を修正してください", + "toastFormatUnsupported": "この言語用の整形はありません — JSON か SQL、または編集モードをお試しください", + "toastDeleted": "スニペットを削除しました", + "toastLoadFailed": "サーバーからスニペットを読み込めませんでした。", + "toastSyncFailed": "サーバーと同期できませんでした。", + "languages": { + "auto": "自動検出", + "plaintext": "プレーンテキスト" + } + }, + "ko": { + "title": "코드 스니펫 관리자", + "subtitle": "브라우저에 스니펫을 저장하거나 로그인해 계정과 동기화하세요. 보기와 편집 전환, 한 번의 클릭으로 복사, 언어 선택 또는 자동 감지, JSON·SQL·Monaco 지원 코드 포맷을 사용할 수 있습니다.", + "snippets": "스니펫", + "newSnippet": "새 스니펫", + "newSnippetTitle": "새 스니펫", + "defaultSnippetTitle": "환영합니다", + "defaultSnippetCode": "// 코드 스니펫에 오신 것을 환영합니다\n// 보기/편집, 복사, 서식 및 자동 언어를 사용해 보세요\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "스니펫 검색…", + "searchAria": "스니펫 검색", + "emptySearch": "검색과 일치하는 스니펫이 없습니다.", + "snippetTitle": "제목", + "snippetTitlePlaceholder": "스니펫 이름", + "language": "언어", + "detectedHint": "강조: {lang}", + "badgeAuto": "자동 · {lang}", + "modeView": "보기", + "modeEdit": "편집", + "copy": "복사", + "copied": "복사됨", + "format": "서식", + "cancel": "취소", + "deleteTitle": "스니펫을 삭제할까요?", + "deleteDescription": "‘{title}’이(가) 이 브라우저에서 제거됩니다.", + "deleteConfirm": "삭제", + "editorAria": "스니펫 코드 편집기", + "editorLoading": "편집기 로드 중…", + "toastCopied": "클립보드에 복사됨", + "toastCopyFailed": "복사할 수 없습니다", + "toastNothingToCopy": "복사할 내용이 없습니다", + "toastFormatted": "서식이 적용되었습니다", + "toastFormatFailed": "서식을 적용할 수 없습니다", + "toastFormatJsonInvalid": "잘못된 JSON — 먼저 구문을 수정하세요", + "toastFormatUnsupported": "이 언어용 서식 도구가 없습니다 — JSON 또는 SQL 또는 편집 모드를 사용해 보세요", + "toastDeleted": "스니펫이 삭제되었습니다", + "toastLoadFailed": "서버에서 스니펫을 불러오지 못했습니다.", + "toastSyncFailed": "서버와 동기화하지 못했습니다.", + "languages": { + "auto": "자동 감지", + "plaintext": "일반 텍스트" + } + }, + "vi": { + "title": "Trình quản lý đoạn mã", + "subtitle": "Lưu đoạn mã trong trình duyệt hoặc đăng nhập để đồng bộ với tài khoản. Chuyển giữa xem và sửa, sao chép một cú nhấp, chọn ngôn ngữ hoặc tự động nhận diện, và định dạng JSON, SQL hoặc mã được Monaco hỗ trợ.", + "snippets": "Đoạn mã", + "newSnippet": "Đoạn mới", + "newSnippetTitle": "Đoạn mới", + "defaultSnippetTitle": "Chào mừng", + "defaultSnippetCode": "// Chào mừng đến với đoạn mã\n// Thử xem so với sửa, sao chép, định dạng và ngôn ngữ tự động\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Tìm đoạn mã…", + "searchAria": "Tìm đoạn mã", + "emptySearch": "Không có đoạn mã khớp tìm kiếm.", + "snippetTitle": "Tiêu đề", + "snippetTitlePlaceholder": "Tên đoạn", + "language": "Ngôn ngữ", + "detectedHint": "Tô sáng dạng {lang}", + "badgeAuto": "Tự động · {lang}", + "modeView": "Xem", + "modeEdit": "Sửa", + "copy": "Sao chép", + "copied": "Đã sao chép", + "format": "Định dạng", + "cancel": "Hủy", + "deleteTitle": "Xóa đoạn mã?", + "deleteDescription": "“{title}” sẽ bị xóa khỏi trình duyệt này.", + "deleteConfirm": "Xóa", + "editorAria": "Trình sửa mã đoạn", + "editorLoading": "Đang tải trình sửa…", + "toastCopied": "Đã sao chép vào bảng tạm", + "toastCopyFailed": "Không thể sao chép", + "toastNothingToCopy": "Không có gì để sao chép", + "toastFormatted": "Đã định dạng", + "toastFormatFailed": "Không thể định dạng", + "toastFormatJsonInvalid": "JSON không hợp lệ — sửa cú pháp trước", + "toastFormatUnsupported": "Không có bộ định dạng cho ngôn ngữ này — thử JSON hoặc SQL hoặc chế độ sửa", + "toastDeleted": "Đã xóa đoạn mã", + "toastLoadFailed": "Không thể tải đoạn mã từ máy chủ.", + "toastSyncFailed": "Không thể đồng bộ với máy chủ.", + "languages": { + "auto": "Tự động nhận diện", + "plaintext": "Văn bản thuần" + } + }, + "id": { + "title": "Pengelola cuplikan kode", + "subtitle": "Simpan cuplikan di browser atau masuk untuk menyinkronkannya dengan akun Anda. Beralih antara lihat dan sunting, salin sekali klik, pilih bahasa atau deteksi otomatis, dan format JSON, SQL, atau kode yang didukung Monaco.", + "snippets": "Cuplikan", + "newSnippet": "Cuplikan baru", + "newSnippetTitle": "Cuplikan baru", + "defaultSnippetTitle": "Selamat datang", + "defaultSnippetCode": "// Selamat datang di cuplikan kode\n// Coba lihat vs sunting, salin, format, dan bahasa otomatis\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Cari cuplikan…", + "searchAria": "Cari cuplikan", + "emptySearch": "Tidak ada cuplikan yang cocok.", + "snippetTitle": "Judul", + "snippetTitlePlaceholder": "Nama cuplikan", + "language": "Bahasa", + "detectedHint": "Sorotan sebagai {lang}", + "badgeAuto": "Otomatis · {lang}", + "modeView": "Lihat", + "modeEdit": "Sunting", + "copy": "Salin", + "copied": "Disalin", + "format": "Format", + "cancel": "Batal", + "deleteTitle": "Hapus cuplikan?", + "deleteDescription": "“{title}” akan dihapus dari browser ini.", + "deleteConfirm": "Hapus", + "editorAria": "Editor kode cuplikan", + "editorLoading": "Memuat editor…", + "toastCopied": "Disalin ke papan klip", + "toastCopyFailed": "Tidak dapat menyalin", + "toastNothingToCopy": "Tidak ada yang disalin", + "toastFormatted": "Sudah diformat", + "toastFormatFailed": "Tidak dapat memformat", + "toastFormatJsonInvalid": "JSON tidak valid — perbaiki sintaks dulu", + "toastFormatUnsupported": "Tidak ada pemformat untuk bahasa ini — coba JSON atau SQL atau mode sunting", + "toastDeleted": "Cuplikan dihapus", + "toastLoadFailed": "Tidak dapat memuat cuplikan dari server.", + "toastSyncFailed": "Tidak dapat menyinkronkan dengan server.", + "languages": { + "auto": "Deteksi otomatis", + "plaintext": "Teks biasa" + } + }, + "ms": { + "title": "Pengurus petikan kod", + "subtitle": "Simpan petikan dalam pelayar atau log masuk untuk menyelaraskannya dengan akaun anda. Tukar antara lihat dan sunting, salin satu klik, pilih bahasa atau auto kesan, dan format JSON, SQL atau kod yang disokong Monaco.", + "snippets": "Petikan", + "newSnippet": "Petikan baharu", + "newSnippetTitle": "Petikan baharu", + "defaultSnippetTitle": "Selamat datang", + "defaultSnippetCode": "// Selamat datang ke petikan kod\n// Cuba lihat vs sunting, salin, format dan bahasa automatik\nfunction greet(name) {\n return `Hello, ${name}!`;\n}", + "searchPlaceholder": "Cari petikan…", + "searchAria": "Cari petikan", + "emptySearch": "Tiada petikan sepadan.", + "snippetTitle": "Tajuk", + "snippetTitlePlaceholder": "Nama petikan", + "language": "Bahasa", + "detectedHint": "Sorotan sebagai {lang}", + "badgeAuto": "Auto · {lang}", + "modeView": "Lihat", + "modeEdit": "Sunting", + "copy": "Salin", + "copied": "Disalin", + "format": "Format", + "cancel": "Batal", + "deleteTitle": "Padam petikan?", + "deleteDescription": "“{title}” akan dibuang daripada pelayar ini.", + "deleteConfirm": "Padam", + "editorAria": "Editor kod petikan", + "editorLoading": "Memuatkan editor…", + "toastCopied": "Disalin ke papan keratan", + "toastCopyFailed": "Tidak dapat menyalin", + "toastNothingToCopy": "Tiada untuk disalin", + "toastFormatted": "Telah diformat", + "toastFormatFailed": "Tidak dapat memformat", + "toastFormatJsonInvalid": "JSON tidak sah — betulkan sintaks dahulu", + "toastFormatUnsupported": "Tiada pemformat untuk bahasa ini — cuba JSON atau SQL atau mod sunting", + "toastDeleted": "Petikan dipadam", + "toastLoadFailed": "Tidak dapat memuatkan petikan dari pelayan.", + "toastSyncFailed": "Tidak dapat menyelaraskan dengan pelayan.", + "languages": { + "auto": "Auto kesan", + "plaintext": "Teks biasa" + } + } +} diff --git a/apps/desktop-ui/scripts/update-docker-compose-all-locales.mjs b/apps/desktop-ui/scripts/update-docker-compose-all-locales.mjs new file mode 100644 index 00000000..3dcdb1bb --- /dev/null +++ b/apps/desktop-ui/scripts/update-docker-compose-all-locales.mjs @@ -0,0 +1,1304 @@ +/** + * One-off / maintenance: merge localized Docker Compose Generator strings into every messages/*.json (except en). + * Run from apps/web: node scripts/update-docker-compose-all-locales.mjs + */ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const messagesDir = path.join(__dirname, '../messages') + +function deepMerge(target, source) { + if (!source || typeof source !== 'object') return target + for (const key of Object.keys(source)) { + const sv = source[key] + const tv = target[key] + if (sv && typeof sv === 'object' && !Array.isArray(sv) && tv && typeof tv === 'object' && !Array.isArray(tv)) { + deepMerge(tv, sv) + } else { + target[key] = sv + } + } + return target +} + +const enPath = path.join(messagesDir, 'en.json') +const en = JSON.parse(fs.readFileSync(enPath, 'utf8')) +const enDcg = en.DockerComposeGenerator +const enDash = en.Dashboard.tools.dockerComposeGenerator + +/** @type {Record }>} */ +const L = { + fr: { + nav: 'Générateur Docker Compose', + dashTitle: 'Générateur Docker Compose', + dashDesc: + 'Choisissez bases de données, caches, files, proxys et observabilité ; exportez un docker-compose.yml pour le dev local.', + dcg: { + title: 'Générateur Docker Compose', + subtitle: + 'Sélectionnez bases de données, caches, files d’attente, proxys inverses et observabilité — copiez un docker-compose.yml prêt pour le développement local.', + searchPlaceholder: 'Rechercher (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Services', + selectedCount: '{count, plural, one {# service} other {# services}}', + copy: 'Copier', + copied: 'Copié !', + download: 'Télécharger', + emptyState: 'Sélectionnez un ou plusieurs services pour générer le YAML Compose.', + hintPorts: + 'Les ports hôte par défaut limitent les conflits ; modifiez-les si Docker signale un port déjà utilisé. Remplacez les mots de passe de démo avant tout environnement partagé.', + categories: { + databases: 'Bases de données et stockages', + cache: 'Cache et mémoire', + queues: 'Files d’attente et messagerie', + search: 'Recherche et journaux', + storage: 'Stockage objet', + proxy: 'Proxys inverses et entrée', + observability: 'Métriques et traçage', + mail: 'E-mail (dev local)', + admin: 'Interfaces d’admin et BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Effacer', + }, + services: { + valkey: 'Valkey (compatible Redis)', + redpanda: 'Redpanda (compatible Kafka)', + minio: 'MinIO (compatible S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (capture SMTP)', + vault: 'HashiCorp Vault (mode dev)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + de: { + nav: 'Docker-Compose-Generator', + dashTitle: 'Docker-Compose-Generator', + dashDesc: + 'Datenbanken, Caches, Warteschlangen, Proxys und Observability wählen; docker-compose.yml für lokale Stacks exportieren.', + dcg: { + title: 'Docker-Compose-Generator', + subtitle: + 'Beliebte Datenbanken, Caches, Messaging, Reverse Proxys und Observability auswählen — fertiges docker-compose.yml für die lokale Entwicklung kopieren.', + searchPlaceholder: 'Suchen (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Dienste', + selectedCount: '{count, plural, one {# Dienst} other {# Dienste}}', + copy: 'Kopieren', + copied: 'Kopiert!', + download: 'Herunterladen', + emptyState: 'Wählen Sie einen oder mehrere Dienste, um Compose-YAML zu erzeugen.', + hintPorts: + 'Standard-Hostports sind verteilt, um Kollisionen zu vermeiden; bei Bind-Fehlern anpassen. Demo-Passwörter vor geteilten Umgebungen ersetzen.', + categories: { + databases: 'Datenbanken & Speicher', + cache: 'Cache & In-Memory', + queues: 'Warteschlangen & Messaging', + search: 'Suche & Logs', + storage: 'Objektspeicher', + proxy: 'Reverse Proxys & Ingress', + observability: 'Metriken & Tracing', + mail: 'E-Mail (lokal)', + admin: 'Admin- & BI-Oberflächen', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Leeren', + }, + services: { + valkey: 'Valkey (Redis-kompatibel)', + redpanda: 'Redpanda (Kafka-kompatibel)', + minio: 'MinIO (S3-kompatibel)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (SMTP-Mitschnitt)', + vault: 'HashiCorp Vault (Dev-Server)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + es: { + nav: 'Generador Docker Compose', + dashTitle: 'Generador Docker Compose', + dashDesc: + 'Elige bases de datos, cachés, colas, proxies e imágenes de observabilidad; exporta un docker-compose.yml para entornos locales.', + dcg: { + title: 'Generador Docker Compose', + subtitle: + 'Selecciona bases de datos, cachés, colas, proxies inversos y observabilidad — copia un docker-compose.yml listo para desarrollo local.', + searchPlaceholder: 'Buscar (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Servicios', + selectedCount: '{count, plural, one {# servicio} other {# servicios}}', + copy: 'Copiar', + copied: '¡Copiado!', + download: 'Descargar', + emptyState: 'Selecciona uno o más servicios para generar el YAML de Compose.', + hintPorts: + 'Los puertos del host por defecto reducen conflictos; cámbialos si Docker indica un puerto en uso. Sustituye las contraseñas de demostración antes de entornos compartidos.', + categories: { + databases: 'Bases de datos y almacenes', + cache: 'Caché y en memoria', + queues: 'Colas y mensajería', + search: 'Búsqueda y registros', + storage: 'Almacenamiento de objetos', + proxy: 'Proxies inversos e ingress', + observability: 'Métricas y trazas', + mail: 'Correo (desarrollo local)', + admin: 'Interfaces de administración y BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Limpiar', + }, + services: { + valkey: 'Valkey (compatible con Redis)', + redpanda: 'Redpanda (compatible con Kafka)', + minio: 'MinIO (compatible con S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (captura SMTP)', + vault: 'HashiCorp Vault (servidor de desarrollo)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + it: { + nav: 'Generatore Docker Compose', + dashTitle: 'Generatore Docker Compose', + dashDesc: + 'Scegli database, cache, code, proxy e immagini di osservabilità; esporta un docker-compose.yml per stack locali.', + dcg: { + title: 'Generatore Docker Compose', + subtitle: + 'Seleziona database, cache, code di messaggi, reverse proxy e osservabilità — copia un docker-compose.yml pronto per lo sviluppo locale.', + searchPlaceholder: 'Cerca (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Servizi', + selectedCount: '{count, plural, one {# servizio} other {# servizi}}', + copy: 'Copia', + copied: 'Copiato!', + download: 'Scarica', + emptyState: 'Seleziona uno o più servizi per generare lo YAML Compose.', + hintPorts: + 'Le porte host predefinite riduono i conflitti; modifiale se Docker segnala un bind in uso. Sostituisci le password demo prima di ambienti condivisi.', + categories: { + databases: 'Database e archivi', + cache: 'Cache e in-memory', + queues: 'Code e messaggistica', + search: 'Ricerca e log', + storage: 'Object storage', + proxy: 'Reverse proxy e ingress', + observability: 'Metriche e tracing', + mail: 'E-mail (sviluppo locale)', + admin: 'Interfacce admin e BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Cancella', + }, + services: { + valkey: 'Valkey (compatibile Redis)', + redpanda: 'Redpanda (compatibile Kafka)', + minio: 'MinIO (compatibile S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (cattura SMTP)', + vault: 'HashiCorp Vault (server di sviluppo)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + pt: { + nav: 'Gerador Docker Compose', + dashTitle: 'Gerador Docker Compose', + dashDesc: + 'Escolha bases de dados, caches, filas, proxies e imagens de observabilidade; exporte um docker-compose.yml para stacks locais.', + dcg: { + title: 'Gerador Docker Compose', + subtitle: + 'Selecione bases de dados, caches, filas de mensagens, proxies inversos e observabilidade — copie um docker-compose.yml pronto para desenvolvimento local.', + searchPlaceholder: 'Pesquisar (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Serviços', + selectedCount: '{count, plural, one {# serviço} other {# serviços}}', + copy: 'Copiar', + copied: 'Copiado!', + download: 'Transferir', + emptyState: 'Selecione um ou mais serviços para gerar o YAML do Compose.', + hintPorts: + 'As portas predefinidas reduzem conflitos; ajuste se o Docker reportar porta em uso. Substitua palavras-passe de demonstração antes de ambientes partilhados.', + categories: { + databases: 'Bases de dados e armazenamento', + cache: 'Cache e em memória', + queues: 'Filas e mensagens', + search: 'Pesquisa e registos', + storage: 'Armazenamento de objetos', + proxy: 'Proxies inversos e ingress', + observability: 'Métricas e tracing', + mail: 'E-mail (dev local)', + admin: 'Interfaces de administração e BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Limpar', + }, + services: { + valkey: 'Valkey (compatível com Redis)', + redpanda: 'Redpanda (compatível com Kafka)', + minio: 'MinIO (compatível com S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (captura SMTP)', + vault: 'HashiCorp Vault (servidor de desenvolvimento)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + 'pt-BR': { + nav: 'Gerador Docker Compose', + dashTitle: 'Gerador Docker Compose', + dashDesc: + 'Escolha bancos de dados, caches, filas, proxies e imagens de observabilidade; exporte um docker-compose.yml para stacks locais.', + dcg: { + title: 'Gerador Docker Compose', + subtitle: + 'Selecione bancos de dados, caches, filas, proxies reversos e observabilidade — copie um docker-compose.yml pronto para desenvolvimento local.', + searchPlaceholder: 'Buscar (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Serviços', + selectedCount: '{count, plural, one {# serviço} other {# serviços}}', + copy: 'Copiar', + copied: 'Copiado!', + download: 'Baixar', + emptyState: 'Selecione um ou mais serviços para gerar o YAML do Compose.', + hintPorts: + 'As portas padrão reduzem conflitos; ajuste se o Docker acusar porta em uso. Troque senhas de demonstração antes de ambientes compartilhados.', + categories: { + databases: 'Bancos de dados e armazenamento', + cache: 'Cache e em memória', + queues: 'Filas e mensageria', + search: 'Busca e logs', + storage: 'Armazenamento de objetos', + proxy: 'Proxies reversos e ingress', + observability: 'Métricas e tracing', + mail: 'E-mail (dev local)', + admin: 'Interfaces de administração e BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Limpar', + }, + services: { + valkey: 'Valkey (compatível com Redis)', + redpanda: 'Redpanda (compatível com Kafka)', + minio: 'MinIO (compatível com S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (captura SMTP)', + vault: 'HashiCorp Vault (servidor de desenvolvimento)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + nl: { + nav: 'Docker Compose-generator', + dashTitle: 'Docker Compose-generator', + dashDesc: + 'Kies databases, caches, wachtrijen, proxy’s en observability-images; exporteer een docker-compose.yml voor lokale stacks.', + dcg: { + title: 'Docker Compose-generator', + subtitle: + 'Kies populaire databases, caches, messaging, reverse proxy’s en observability — kopieer een werkend docker-compose.yml voor lokale ontwikkeling.', + searchPlaceholder: 'Zoeken (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Services', + selectedCount: '{count, plural, one {# service} other {# services}}', + copy: 'Kopiëren', + copied: 'Gekopieerd!', + download: 'Downloaden', + emptyState: 'Selecteer een of meer services om Compose-YAML te genereren.', + hintPorts: + 'Standaard hostpoorten beperken conflicten; pas aan als Docker een poortfout meldt. Vervang demowachtwoorden vóór gedeelde omgevingen.', + categories: { + databases: "Databases & opslag", + cache: 'Cache & in-memory', + queues: 'Wachtrijen & messaging', + search: 'Zoeken & logs', + storage: 'Objectopslag', + proxy: 'Reverse proxy’s & ingress', + observability: 'Metrics & tracing', + mail: 'E-mail (lokaal)', + admin: 'Admin- & BI-interfaces', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Wissen', + }, + services: { + valkey: 'Valkey (Redis-compatibel)', + redpanda: 'Redpanda (Kafka-compatibel)', + minio: 'MinIO (S3-compatibel)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (SMTP-vangst)', + vault: 'HashiCorp Vault (dev-server)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + pl: { + nav: 'Generator Docker Compose', + dashTitle: 'Generator Docker Compose', + dashDesc: + 'Wybierz bazy danych, pamięć podręczną, kolejki, proxy i obserwowalność; eksportuj docker-compose.yml dla lokalnych stacków.', + dcg: { + title: 'Generator Docker Compose', + subtitle: + 'Wybierz popularne bazy, cache, kolejki komunikatów, reverse proxy i obserwowalność — skopiuj działający docker-compose.yml na lokalny development.', + searchPlaceholder: 'Szukaj (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Usługi', + selectedCount: '{count, plural, one {# usługa} few {# usługi} many {# usług} other {# usługi}}', + copy: 'Kopiuj', + copied: 'Skopiowano!', + download: 'Pobierz', + emptyState: 'Wybierz jedną lub więcej usług, aby wygenerować YAML Compose.', + hintPorts: + 'Domyślne porty hosta ograniczają konflikty; zmień je, jeśli Docker zgłosi zajęty port. Zamień hasła demonstracyjne przed środowiskami współdzielonymi.', + categories: { + databases: 'Bazy danych i magazyny', + cache: 'Cache i pamięć operacyjna', + queues: 'Kolejki i messaging', + search: 'Wyszukiwanie i logi', + storage: 'Magazyn obiektów', + proxy: 'Reverse proxy i ingress', + observability: 'Metryki i śledzenie', + mail: 'E-mail (lokalny dev)', + admin: 'Interfejsy administracyjne i BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Wyczyść', + }, + services: { + valkey: 'Valkey (zgodny z Redis)', + redpanda: 'Redpanda (zgodny z Kafka)', + minio: 'MinIO (zgodny z S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (przechwytywanie SMTP)', + vault: 'HashiCorp Vault (serwer developerski)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + ru: { + nav: 'Генератор Docker Compose', + dashTitle: 'Генератор Docker Compose', + dashDesc: + 'Выберите СУБД, кэши, очереди, прокси и образы наблюдаемости; экспортируйте docker-compose.yml для локальных стеков.', + dcg: { + title: 'Генератор Docker Compose', + subtitle: + 'Выберите популярные базы данных, кэши, брокеры сообщений, обратные прокси и наблюдаемость — скопируйте готовый docker-compose.yml для локальной разработки.', + searchPlaceholder: 'Поиск (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Сервисы', + selectedCount: '{count, plural, one {# сервис} few {# сервиса} many {# сервисов} other {# сервисов}}', + copy: 'Копировать', + copied: 'Скопировано!', + download: 'Скачать', + emptyState: 'Выберите один или несколько сервисов, чтобы сгенерировать YAML Compose.', + hintPorts: + 'Порты хоста подобраны, чтобы реже конфликтовать; измените при ошибке занятого порта. Замените демо-пароли перед общими средами.', + categories: { + databases: 'Базы данных и хранилища', + cache: 'Кэш и in-memory', + queues: 'Очереди и обмен сообщениями', + search: 'Поиск и журналы', + storage: 'Объектное хранилище', + proxy: 'Обратные прокси и ingress', + observability: 'Метрики и трассировка', + mail: 'Почта (локальная разработка)', + admin: 'Админ-интерфейсы и BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Очистить', + }, + services: { + valkey: 'Valkey (совместим с Redis)', + redpanda: 'Redpanda (совместим с Kafka)', + minio: 'MinIO (совместим с S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (перехват SMTP)', + vault: 'HashiCorp Vault (режим разработки)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + uk: { + nav: 'Генератор Docker Compose', + dashTitle: 'Генератор Docker Compose', + dashDesc: + 'Оберіть бази даних, кеші, черги, проксі та образи спостережуваності; експортуйте docker-compose.yml для локальних стеків.', + dcg: { + title: 'Генератор Docker Compose', + subtitle: + 'Оберіть популярні бази даних, кеші, брокери повідомлень, зворотні проксі та спостережуваність — скопіюйте готовий docker-compose.yml для локальної розробки.', + searchPlaceholder: 'Пошук (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Сервіси', + selectedCount: '{count, plural, one {# сервіс} few {# сервіси} many {# сервісів} other {# сервісів}}', + copy: 'Копіювати', + copied: 'Скопійовано!', + download: 'Завантажити', + emptyState: 'Оберіть один або кілька сервісів, щоб згенерувати YAML Compose.', + hintPorts: + 'Порти хоста підібрані, щоб рідше конфліктували; змініть за потреби. Замініть демо-паролі перед спільними середовищами.', + categories: { + databases: 'Бази даних і сховища', + cache: 'Кеш і in-memory', + queues: 'Черги та обмін повідомленнями', + search: 'Пошук і журнали', + storage: 'Об’єктне сховище', + proxy: 'Зворотні проксі та ingress', + observability: 'Метрики та трасування', + mail: 'Пошта (локальна розробка)', + admin: 'Адмін-інтерфейси та BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Очистити', + }, + services: { + valkey: 'Valkey (сумісний з Redis)', + redpanda: 'Redpanda (сумісна з Kafka)', + minio: 'MinIO (сумісний з S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (перехоплення SMTP)', + vault: 'HashiCorp Vault (режим розробки)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + cs: { + nav: 'Generátor Docker Compose', + dashTitle: 'Generátor Docker Compose', + dashDesc: + 'Vyberte databáze, cache, fronty, proxy a observabilitu; exportujte docker-compose.yml pro lokální stacky.', + dcg: { + title: 'Generátor Docker Compose', + subtitle: + 'Vyberte populární databáze, cache, fronty zpráv, reverzní proxy a observabilitu — zkopírujte funkční docker-compose.yml pro lokální vývoj.', + searchPlaceholder: 'Hledat (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Služby', + selectedCount: '{count, plural, one {# služba} few {# služby} many {# služeb} other {# služeb}}', + copy: 'Kopírovat', + copied: 'Zkopírováno!', + download: 'Stáhnout', + emptyState: 'Vyberte jednu nebo více služeb pro vygenerování Compose YAML.', + hintPorts: + 'Výchozí porty hostitele snižují konflikty; upravte je, pokud Docker nahlásí obsazený port. Před sdílenými prostředími nahraďte demo hesla.', + categories: { + databases: 'Databáze a úložiště', + cache: 'Cache a in-memory', + queues: 'Fronty a messaging', + search: 'Vyhledávání a logy', + storage: 'Objektové úložiště', + proxy: 'Reverzní proxy a ingress', + observability: 'Metriky a trasování', + mail: 'E-mail (lokální vývoj)', + admin: 'Admin a BI rozhraní', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Vymazat', + }, + services: { + valkey: 'Valkey (kompatibilní s Redis)', + redpanda: 'Redpanda (kompatibilní s Kafka)', + minio: 'MinIO (kompatibilní se S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (zachycení SMTP)', + vault: 'HashiCorp Vault (dev server)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + da: { + nav: 'Docker Compose-generator', + dashTitle: 'Docker Compose-generator', + dashDesc: + 'Vælg databaser, caches, køer, proxyer og observabilitets-images; eksporter docker-compose.yml til lokale stacks.', + dcg: { + title: 'Docker Compose-generator', + subtitle: + 'Vælg populære databaser, caches, beskedkøer, reverse proxyer og observabilitet — kopiér en docker-compose.yml klar til lokal udvikling.', + searchPlaceholder: 'Søg (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Tjenester', + selectedCount: '{count, plural, one {# tjeneste} other {# tjenester}}', + copy: 'Kopiér', + copied: 'Kopieret!', + download: 'Download', + emptyState: 'Vælg én eller flere tjenester for at generere Compose-YAML.', + hintPorts: + 'Standardværtsporte minimerer konflikter; justér hvis Docker melder port i brug. Udskift demo-adgangskoder før delte miljøer.', + categories: { + databases: 'Databaser og lagring', + cache: 'Cache og in-memory', + queues: 'Køer og messaging', + search: 'Søgning og logs', + storage: 'Objektlager', + proxy: 'Reverse proxy og ingress', + observability: 'Metrics og tracing', + mail: 'E-mail (lokal udvikling)', + admin: 'Admin- og BI-grænseflader', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Ryd', + }, + services: { + valkey: 'Valkey (Redis-kompatibel)', + redpanda: 'Redpanda (Kafka-kompatibel)', + minio: 'MinIO (S3-kompatibel)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (SMTP-optagelse)', + vault: 'HashiCorp Vault (dev-server)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + sv: { + nav: 'Docker Compose-generator', + dashTitle: 'Docker Compose-generator', + dashDesc: + 'Välj databaser, cache, köer, proxys och observabilitetsbilder; exportera docker-compose.yml för lokala stackar.', + dcg: { + title: 'Docker Compose-generator', + subtitle: + 'Välj populära databaser, cache, meddelandeköer, omvända proxys och observabilitet — kopiera en docker-compose.yml redo för lokal utveckling.', + searchPlaceholder: 'Sök (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Tjänster', + selectedCount: '{count, plural, one {# tjänst} other {# tjänster}}', + copy: 'Kopiera', + copied: 'Kopierat!', + download: 'Ladda ner', + emptyState: 'Välj en eller flera tjänster för att generera Compose-YAML.', + hintPorts: + 'Standardvärdportar minskar krockar; ändra om Docker rapporterar upptagen port. Byt ut demolösenord före delade miljöer.', + categories: { + databases: 'Databaser och lagring', + cache: 'Cache och in-memory', + queues: 'Köer och meddelanden', + search: 'Sökning och loggar', + storage: 'Objektlagring', + proxy: 'Omvända proxys och ingress', + observability: 'Mätvärden och spårning', + mail: 'E-post (lokal utveckling)', + admin: 'Admin- och BI-gränssnitt', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Rensa', + }, + services: { + valkey: 'Valkey (Redis-kompatibel)', + redpanda: 'Redpanda (Kafka-kompatibel)', + minio: 'MinIO (S3-kompatibel)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (SMTP-insamling)', + vault: 'HashiCorp Vault (dev-server)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + nb: { + nav: 'Docker Compose-generator', + dashTitle: 'Docker Compose-generator', + dashDesc: + 'Velg databaser, hurtigminne, køer, proxyer og observabilitetsbilder; eksporter docker-compose.yml for lokale stakk.', + dcg: { + title: 'Docker Compose-generator', + subtitle: + 'Velg populære databaser, cache, meldingskøer, omvendte proxyer og observabilitet — kopier en docker-compose.yml klar for lokal utvikling.', + searchPlaceholder: 'Søk (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Tjenester', + selectedCount: '{count, plural, one {# tjeneste} other {# tjenester}}', + copy: 'Kopier', + copied: 'Kopiert!', + download: 'Last ned', + emptyState: 'Velg én eller flere tjenester for å generere Compose-YAML.', + hintPorts: + 'Standardvertsporter reduserer konflikter; endre hvis Docker melder opptatt port. Bytt demopassord før delte miljøer.', + categories: { + databases: 'Databaser og lagring', + cache: 'Cache og in-memory', + queues: 'Køer og meldinger', + search: 'Søk og logger', + storage: 'Objektlagring', + proxy: 'Omvendte proxyer og ingress', + observability: 'Målinger og sporing', + mail: 'E-post (lokal utvikling)', + admin: 'Admin- og BI-grensesnitt', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Tøm', + }, + services: { + valkey: 'Valkey (Redis-kompatibel)', + redpanda: 'Redpanda (Kafka-kompatibel)', + minio: 'MinIO (S3-kompatibel)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (SMTP-fangst)', + vault: 'HashiCorp Vault (dev-server)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + el: { + nav: 'Δημιουργός Docker Compose', + dashTitle: 'Δημιουργός Docker Compose', + dashDesc: + 'Επιλέξτε βάσεις δεδομένων, cache, ουρές, proxy και εικόνες παρατηρησιμότητας· εξάγετε docker-compose.yml για τοπικές στοίβες.', + dcg: { + title: 'Δημιουργός Docker Compose', + subtitle: + 'Επιλέξτε δημοφιλείς βάσεις δεδομένων, cache, ουρές μηνυμάτων, reverse proxy και παρατηρησιμότητα — αντιγράψτε ένα docker-compose.yml έτοιμο για τοπική ανάπτυξη.', + searchPlaceholder: 'Αναζήτηση (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Υπηρεσίες', + selectedCount: '{count, plural, one {# υπηρεσία} other {# υπηρεσίες}}', + copy: 'Αντιγραφή', + copied: 'Αντιγράφηκε!', + download: 'Λήψη', + emptyState: 'Επιλέξτε μία ή περισσότερες υπηρεσίες για δημιουργία YAML Compose.', + hintPorts: + 'Οι προεπιλεγμένες θύρες μειώνουν συγκρούσεις· αλλάξτε τις αν το Docker αναφέρει δέσμευση. Αντικαταστήστε κωδικούς επίδειξης πριν από κοινά περιβάλλοντα.', + categories: { + databases: 'Βάσεις δεδομένων και αποθήκευση', + cache: 'Cache και in-memory', + queues: 'Ουρές και ανταλλαγή μηνυμάτων', + search: 'Αναζήτηση και αρχεία καταγραφής', + storage: 'Αποθήκευση αντικειμένων', + proxy: 'Reverse proxy και ingress', + observability: 'Μετρήσεις και ιχνηλάτηση', + mail: 'Ηλεκτρονικό ταχυδρομείο (τοπική ανάπτυξη)', + admin: 'Διαχειριστικές και BI διεπαφές', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Εκκαθάριση', + }, + services: { + valkey: 'Valkey (συμβατό με Redis)', + redpanda: 'Redpanda (συμβατό με Kafka)', + minio: 'MinIO (συμβατό με S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (καταγραφή SMTP)', + vault: 'HashiCorp Vault (διακομιστής ανάπτυξης)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + tr: { + nav: 'Docker Compose Oluşturucu', + dashTitle: 'Docker Compose Oluşturucu', + dashDesc: + 'Veritabanları, önbellek, kuyruklar, proxy ve gözlemlenebilirlik görüntülerini seçin; yerel yığınlar için docker-compose.yml dışa aktarın.', + dcg: { + title: 'Docker Compose Oluşturucu', + subtitle: + 'Popüler veritabanları, önbellek, mesaj kuyrukları, ters proxy ve gözlemlenebilirlik seçin — yerel geliştirme için hazır docker-compose.yml kopyalayın.', + searchPlaceholder: 'Ara (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Hizmetler', + selectedCount: '{count, plural, one {# hizmet} other {# hizmet}}', + copy: 'Kopyala', + copied: 'Kopyalandı!', + download: 'İndir', + emptyState: 'Compose YAML üretmek için bir veya daha fazla hizmet seçin.', + hintPorts: + 'Varsayılan ana bilgisayar bağlantı noktaları çakışmaları azaltır; Docker bağlama hatası verirse değiştirin. Paylaşılan ortamlardan önce demo parolalarını değiştirin.', + categories: { + databases: 'Veritabanları ve depolar', + cache: 'Önbellek ve bellek içi', + queues: 'Kuyruklar ve mesajlaşma', + search: 'Arama ve günlükler', + storage: 'Nesne depolama', + proxy: 'Ters proxy ve ingress', + observability: 'Metrikler ve izleme', + mail: 'E-posta (yerel geliştirme)', + admin: 'Yönetim ve BI arayüzleri', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Temizle', + }, + services: { + valkey: 'Valkey (Redis uyumlu)', + redpanda: 'Redpanda (Kafka uyumlu)', + minio: 'MinIO (S3 uyumlu)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (SMTP yakalama)', + vault: 'HashiCorp Vault (geliştirme sunucusu)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + vi: { + nav: 'Trình tạo Docker Compose', + dashTitle: 'Trình tạo Docker Compose', + dashDesc: + 'Chọn cơ sở dữ liệu, bộ nhớ đệm, hàng đợi, proxy và image quan sát; xuất docker-compose.yml cho stack cục bộ.', + dcg: { + title: 'Trình tạo Docker Compose', + subtitle: + 'Chọn cơ sở dữ liệu, cache, hàng đợi tin nhắn, reverse proxy và quan sát — sao chép docker-compose.yml sẵn sàng cho phát triển cục bộ.', + searchPlaceholder: 'Tìm (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Dịch vụ', + selectedCount: '{count, plural, other {# dịch vụ}}', + copy: 'Sao chép', + copied: 'Đã sao chép!', + download: 'Tải xuống', + emptyState: 'Chọn một hoặc nhiều dịch vụ để tạo YAML Compose.', + hintPorts: + 'Cổng máy chủ mặc định giảm xung đột; chỉnh nếu Docker báo cổng đã dùng. Đổi mật khẩu demo trước môi trường dùng chung.', + categories: { + databases: 'Cơ sở dữ liệu & lưu trữ', + cache: 'Cache & trong bộ nhớ', + queues: 'Hàng đợi & tin nhắn', + search: 'Tìm kiếm & nhật ký', + storage: 'Lưu trữ đối tượng', + proxy: 'Reverse proxy & ingress', + observability: 'Số liệu & tracing', + mail: 'Email (phát triển cục bộ)', + admin: 'Giao diện quản trị & BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Xóa', + }, + services: { + valkey: 'Valkey (tương thích Redis)', + redpanda: 'Redpanda (tương thích Kafka)', + minio: 'MinIO (tương thích S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (bắt SMTP)', + vault: 'HashiCorp Vault (máy chủ dev)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + id: { + nav: 'Generator Docker Compose', + dashTitle: 'Generator Docker Compose', + dashDesc: + 'Pilih basis data, cache, antrean, proxy, dan image observabilitas; ekspor docker-compose.yml untuk stack lokal.', + dcg: { + title: 'Generator Docker Compose', + subtitle: + 'Pilih basis data populer, cache, antrean pesan, reverse proxy, dan observabilitas — salin docker-compose.yml siap pakai untuk pengembangan lokal.', + searchPlaceholder: 'Cari (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Layanan', + selectedCount: '{count, plural, other {# layanan}}', + copy: 'Salin', + copied: 'Disalin!', + download: 'Unduh', + emptyState: 'Pilih satu atau lebih layanan untuk menghasilkan YAML Compose.', + hintPorts: + 'Port host bawaan mengurangi bentrok; ubah jika Docker melaporkan port terpakai. Ganti kata sandi demo sebelum lingkungan bersama.', + categories: { + databases: 'Basis data & penyimpanan', + cache: 'Cache & dalam memori', + queues: 'Antrean & perpesanan', + search: 'Pencarian & log', + storage: 'Penyimpanan objek', + proxy: 'Reverse proxy & ingress', + observability: 'Metrik & pelacakan', + mail: 'Email (pengembangan lokal)', + admin: 'Antarmuka admin & BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Bersihkan', + }, + services: { + valkey: 'Valkey (kompatibel Redis)', + redpanda: 'Redpanda (kompatibel Kafka)', + minio: 'MinIO (kompatibel S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (tangkap SMTP)', + vault: 'HashiCorp Vault (server dev)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + ms: { + nav: 'Penjana Docker Compose', + dashTitle: 'Penjana Docker Compose', + dashDesc: + 'Pilih pangkalan data, cache, baris gilir, proksi dan imej kebolehcerapan; eksport docker-compose.yml untuk setempat.', + dcg: { + title: 'Penjana Docker Compose', + subtitle: + 'Pilih pangkalan data popular, cache, baris gilir mesej, proksi songsang dan kebolehcerapan — salin docker-compose.yml untuk pembangunan setempat.', + searchPlaceholder: 'Cari (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Perkhidmatan', + selectedCount: '{count, plural, other {# perkhidmatan}}', + copy: 'Salin', + copied: 'Disalin!', + download: 'Muat turun', + emptyState: 'Pilih satu atau lebih perkhidmatan untuk menjana YAML Compose.', + hintPorts: + 'Port hos lalai mengurangkan konflik; ubah jika Docker melaporkan port digunakan. Ganti kata laluan demo sebelum persekitaran dikongsi.', + categories: { + databases: 'Pangkalan data & storan', + cache: 'Cache & dalam memori', + queues: 'Baris gilir & pemesejan', + search: 'Carian & log', + storage: 'Storan objek', + proxy: 'Proksi songsang & ingress', + observability: 'Metrik & penjejakan', + mail: 'E-mel (pembangunan setempat)', + admin: 'Antara muka pentadbir & BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Kosongkan', + }, + services: { + valkey: 'Valkey (serasi Redis)', + redpanda: 'Redpanda (serasi Kafka)', + minio: 'MinIO (serasi S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (tangkap SMTP)', + vault: 'HashiCorp Vault (pelayan dev)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + ja: { + nav: 'Docker Compose ジェネレーター', + dashTitle: 'Docker Compose ジェネレーター', + dashDesc: + 'データベース、キャッシュ、キュー、プロキシ、可観測性イメージを選び、ローカル用の docker-compose.yml を出力します。', + dcg: { + title: 'Docker Compose ジェネレーター', + subtitle: + '代表的なデータベース、キャッシュ、メッセージキュー、リバースプロキシ、可観測性を選んで、ローカル開発向けの docker-compose.yml をコピーできます。', + searchPlaceholder: '検索(postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'サービス', + selectedCount: '{count, plural, other {# 件のサービス}}', + copy: 'コピー', + copied: 'コピーしました', + download: 'ダウンロード', + emptyState: 'Compose YAML を生成するサービスを 1 つ以上選んでください。', + hintPorts: + 'デフォルトのホストポートは衝突を避ける配置です。バインドエラー時は変更してください。共有環境ではデモ用パスワードを必ず置き換えてください。', + categories: { + databases: 'データベースとストア', + cache: 'キャッシュ・インメモリ', + queues: 'キューとメッセージング', + search: '検索とログ', + storage: 'オブジェクトストレージ', + proxy: 'リバースプロキシと Ingress', + observability: 'メトリクスとトレース', + mail: 'メール(ローカル開発)', + admin: '管理画面と BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'クリア', + }, + services: { + valkey: 'Valkey(Redis 互換)', + redpanda: 'Redpanda(Kafka 互換)', + minio: 'MinIO(S3 互換)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit(SMTP キャプチャ)', + vault: 'HashiCorp Vault(開発サーバー)', + gotenberg: 'Gotenberg(Chromium / PDF)', + }, + }, + }, + ko: { + nav: 'Docker Compose 생성기', + dashTitle: 'Docker Compose 생성기', + dashDesc: + '데이터베이스, 캐시, 큐, 프록시, 관측 가능성 이미지를 선택하고 로컬 스택용 docker-compose.yml을 보냅니다.', + dcg: { + title: 'Docker Compose 생성기', + subtitle: + '인기 있는 데이터베이스, 캐시, 메시지 큐, 리버스 프록시, 관측 가능성을 선택해 로컬 개발용 docker-compose.yml 을 복사하세요.', + searchPlaceholder: '검색 (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: '서비스', + selectedCount: '{count, plural, other {#개 서비스}}', + copy: '복사', + copied: '복사됨!', + download: '다운로드', + emptyState: 'Compose YAML 을 생성할 서비스를 하나 이상 선택하세요.', + hintPorts: + '기본 호스트 포트는 충돌을 줄이도록 배치되어 있습니다. 바인드 오류 시 변경하세요. 공유 환경 전에 데모 비밀번호를 교체하세요.', + categories: { + databases: '데이터베이스 및 저장소', + cache: '캐시 및 인메모리', + queues: '큐 및 메시징', + search: '검색 및 로그', + storage: '오브젝트 스토리지', + proxy: '리버스 프록시 및 Ingress', + observability: '메트릭 및 추적', + mail: '이메일(로컬 개발)', + admin: '관리 및 BI UI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: '지우기', + }, + services: { + valkey: 'Valkey(Redis 호환)', + redpanda: 'Redpanda(Kafka 호환)', + minio: 'MinIO(S3 호환)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit(SMTP 캡처)', + vault: 'HashiCorp Vault(개발 서버)', + gotenberg: 'Gotenberg(Chromium / PDF)', + }, + }, + }, + zh: { + nav: 'Docker Compose 生成器', + dashTitle: 'Docker Compose 生成器', + dashDesc: '选择数据库、缓存、队列、代理与可观测性镜像;导出用于本地栈的 docker-compose.yml。', + dcg: { + title: 'Docker Compose 生成器', + subtitle: + '选择常用数据库、缓存、消息队列、反向代理与可观测性组件——复制即可用于本地开发的 docker-compose.yml。', + searchPlaceholder: '搜索(postgres、redis、kafka、nginx、grafana…)', + selectedLabel: '服务', + selectedCount: '{count, plural, other {# 个服务}}', + copy: '复制', + copied: '已复制!', + download: '下载', + emptyState: '请选择一个或多个服务以生成 Compose YAML。', + hintPorts: + '默认主机端口已错开以降低冲突;若 Docker 提示端口占用请修改。在共享环境前务必替换演示密码。', + categories: { + databases: '数据库与存储', + cache: '缓存与内存', + queues: '队列与消息', + search: '搜索与日志', + storage: '对象存储', + proxy: '反向代理与入口', + observability: '指标与追踪', + mail: '邮件(本地开发)', + admin: '管理与 BI 界面', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: '清空', + }, + services: { + valkey: 'Valkey(兼容 Redis)', + redpanda: 'Redpanda(兼容 Kafka)', + minio: 'MinIO(兼容 S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit(SMTP 捕获)', + vault: 'HashiCorp Vault(开发模式)', + gotenberg: 'Gotenberg(Chromium / PDF)', + }, + }, + }, + ar: { + nav: 'مولّد Docker Compose', + dashTitle: 'مولّد Docker Compose', + dashDesc: + 'اختر قواعد البيانات والتخزين المؤقت والطوابير والوكلاء وصور المراقبة؛ صدّر docker-compose.yml للمكدسات المحلية.', + dcg: { + title: 'مولّد Docker Compose', + subtitle: + 'اختر قواعد بيانات شائعة وتخزينًا مؤقتًا وطوابير رسائل ووكلاء عكسيين ومراقبة — انسخ docker-compose.yml جاهزًا للتطوير المحلي.', + searchPlaceholder: 'بحث (postgres، redis، kafka، nginx، grafana…)', + selectedLabel: 'الخدمات', + selectedCount: '{count, plural, one {خدمة واحدة} other {# خدمات}}', + copy: 'نسخ', + copied: 'تم النسخ!', + download: 'تنزيل', + emptyState: 'اختر خدمة واحدة أو أكثر لإنشاء YAML لـ Compose.', + hintPorts: + 'تم تباعد منافذ المضيف الافتراضية لتقليل التعارض؛ غيّرها إذا أبلغ Docker عن تعارض. استبدل كلمات مرور العرض قبل البيئات المشتركة.', + categories: { + databases: 'قواعد البيانات والتخزين', + cache: 'التخزين المؤقت والذاكرة', + queues: 'الطوابير والمراسلة', + search: 'البحث والسجلات', + storage: 'تخزين الكائنات', + proxy: 'الوكلاء العكسيون والدخول', + observability: 'المقاييس والتتبع', + mail: 'البريد (تطوير محلي)', + admin: 'واجهات الإدارة وذكاء الأعمال', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'مسح', + }, + services: { + valkey: 'Valkey (متوافق مع Redis)', + redpanda: 'Redpanda (متوافق مع Kafka)', + minio: 'MinIO (متوافق مع S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (التقاط SMTP)', + vault: 'HashiCorp Vault (خادم تطوير)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + fa: { + nav: 'تولیدکننده Docker Compose', + dashTitle: 'تولیدکننده Docker Compose', + dashDesc: + 'پایگاه‌داده، کش، صف، پروکسی و تصاویر قابلیت مشاهده را انتخاب کنید؛ docker-compose.yml را برای پشته‌های محلی صادر کنید.', + dcg: { + title: 'تولیدکننده Docker Compose', + subtitle: + 'پایگاه‌داده‌های رایج، کش، صف پیام، پروکسی معکوس و قابلیت مشاهده را انتخاب کنید — docker-compose.yml آماده توسعه محلی را کپی کنید.', + searchPlaceholder: 'جستجو (postgres، redis، kafka، nginx، grafana…)', + selectedLabel: 'سرویس‌ها', + selectedCount: '{count, plural, one {# سرویس} other {# سرویس}}', + copy: 'کپی', + copied: 'کپی شد!', + download: 'دانلود', + emptyState: 'برای تولید YAML Compose یک یا چند سرویس انتخاب کنید.', + hintPorts: + 'پورت‌های میزبان پیش‌فرض برای کاهش تداخل فاصله دارند؛ در صورت خطای اتصال تغییر دهید. قبل از محیط‌های اشتراکی رمزهای نمایشی را عوض کنید.', + categories: { + databases: 'پایگاه‌داده و ذخیره‌سازی', + cache: 'کش و درون‌حافظه', + queues: 'صف و پیام‌رسانی', + search: 'جستجو و گزارش‌ها', + storage: 'ذخیره‌سازی شیء', + proxy: 'پروکسی معکوس و ingress', + observability: 'معیارها و ردیابی', + mail: 'ایمیل (توسعه محلی)', + admin: 'رابط‌های مدیریت و BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'پاک کردن', + }, + services: { + valkey: 'Valkey (سازگار با Redis)', + redpanda: 'Redpanda (سازگار با Kafka)', + minio: 'MinIO (سازگار با S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (ضبط SMTP)', + vault: 'HashiCorp Vault (سرور توسعه)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + af: { + nav: 'Docker Compose-Generator', + dashTitle: 'Docker Compose-Generator', + dashDesc: + "Kies databasisse, kasse, toue, proxy's en waarneembaarheidbeelde; voer docker-compose.yml uit vir plaaslike stapel.", + dcg: { + title: 'Docker Compose-Generator', + subtitle: + "Kies gewilde databasisse, kasse, boodskaptoue, omgekeerde proxy's en waarneembaarheid — kopieer 'n docker-compose.yml vir plaaslike ontwikkeling.", + searchPlaceholder: 'Soek (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Dienste', + selectedCount: '{count, plural, one {# diens} other {# dienste}}', + copy: 'Kopieer', + copied: 'Gekopieer!', + download: 'Aflaai', + emptyState: "Kies een of meer dienste om Compose-YAML te genereer.", + hintPorts: + "Verstek gasheerpoorte verminder botsings; verander as Docker 'n bindingfout meld. Vervang demo-wagwoorde voor gedeelde omgewings.", + categories: { + databases: 'Databasisse en berging', + cache: 'Kas en in-geheue', + queues: 'Toue en boodskappe', + search: 'Soek en logs', + storage: 'Voorwerpherging', + proxy: "Omgekeerde proxy's en ingress", + observability: 'Metrieke en nasporing', + mail: 'E-pos (plaaslike ontwikkeling)', + admin: 'Admin- en BI-koppelvlakke', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Maak skoon', + }, + services: { + valkey: 'Valkey (Redis-versoenbaar)', + redpanda: 'Redpanda (Kafka-versoenbaar)', + minio: 'MinIO (S3-versoenbaar)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (SMTP-vangs)', + vault: 'HashiCorp Vault (ontwikkelaarbediener)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, + ca: { + nav: 'Generador Docker Compose', + dashTitle: 'Generador Docker Compose', + dashDesc: + 'Trieu bases de dades, memòries cau, cues, proxies i imatges d’observabilitat; exporteu un docker-compose.yml per a piles locals.', + dcg: { + title: 'Generador Docker Compose', + subtitle: + 'Trieu bases de dades populars, memòries cau, cues de missatges, proxies inversos i observabilitat — copieu un docker-compose.yml llest per al desenvolupament local.', + searchPlaceholder: 'Cerca (postgres, redis, kafka, nginx, grafana…)', + selectedLabel: 'Serveis', + selectedCount: '{count, plural, one {# servei} other {# serveis}}', + copy: 'Copia', + copied: 'Copiat!', + download: 'Baixa', + emptyState: 'Seleccioneu un o més serveis per generar el YAML de Compose.', + hintPorts: + 'Els ports per defecte redueixen conflictes; canvieu-los si Docker informa d’un port en ús. Substituïu les contrasenyes de demostració abans d’entorns compartits.', + categories: { + databases: 'Bases de dades i emmagatzematge', + cache: 'Memòria cau i en memòria', + queues: 'Cues i missatgeria', + search: 'Cerca i registres', + storage: 'Emmagatzematge d’objectes', + proxy: 'Proxies inversos i ingress', + observability: 'Mètriques i traçat', + mail: 'Correu (desenvolupament local)', + admin: 'Interfícies d’administració i BI', + }, + presets: { + stackWeb: 'Postgres + Redis + Nginx', + stackMern: 'MongoDB + Redis + Nginx', + stackObserve: 'Prometheus + Grafana + Loki', + stackMessaging: 'RabbitMQ + Redis', + stackSearch: 'Meilisearch + Postgres', + clear: 'Neteja', + }, + services: { + valkey: 'Valkey (compatible amb Redis)', + redpanda: 'Redpanda (compatible amb Kafka)', + minio: 'MinIO (compatible amb S3)', + 'dynamodb-local': 'Amazon DynamoDB Local', + mailpit: 'Mailpit (captura SMTP)', + vault: 'HashiCorp Vault (servidor de desenvolupament)', + gotenberg: 'Gotenberg (Chromium / PDF)', + }, + }, + }, +} + +for (const [locale, pack] of Object.entries(L)) { + const file = path.join(messagesDir, `${locale}.json`) + if (!fs.existsSync(file)) { + console.warn('skip missing', locale) + continue + } + const data = JSON.parse(fs.readFileSync(file, 'utf8')) + data.Navigation = data.Navigation ?? {} + data.Navigation.dockerComposeGenerator = pack.nav + data.Dashboard = data.Dashboard ?? {} + data.Dashboard.tools = data.Dashboard.tools ?? {} + data.Dashboard.tools.dockerComposeGenerator = { + title: pack.dashTitle, + description: pack.dashDesc, + } + const merged = structuredClone(enDcg) + deepMerge(merged, pack.dcg) + data.DockerComposeGenerator = merged + fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n', 'utf8') + console.log('updated', locale) +} + +console.log('done (en unchanged)') diff --git a/apps/desktop-ui/scripts/update-pdf-sign-locales.py b/apps/desktop-ui/scripts/update-pdf-sign-locales.py new file mode 100644 index 00000000..0d5c0d54 --- /dev/null +++ b/apps/desktop-ui/scripts/update-pdf-sign-locales.py @@ -0,0 +1,1006 @@ +#!/usr/bin/env python3 +""" +Merge translated PdfSign + Navigation/Dashboard pdfSign keys into all locale JSON files. +Reuses PdfWatermark strings where they match (invalidPdf, encryptedPdf, positions, etc.). +""" + +from __future__ import annotations + +import json +from pathlib import Path + +MESSAGES_DIR = Path(__file__).resolve().parent.parent / "messages" + +# Keys copied from PdfWatermark when present (same UX copy). +WM_COPY_KEYS = ( + "choosePdf", + "reset", + "pageCount", + "invalidPdf", + "encryptedPdf", + "positionLabel", + "posCenter", + "posTl", + "posTr", + "posBl", + "posBr", + "invalidImage", + "working", +) + +# Per-locale: intro, privacyNote, mainTabSign, mainTabRequest, tabDraw, tabType, tabUpload, +# drawLabel, clearDrawing, typeLabel, typePlaceholder, chooseImage, widthLabel, +# pagesAll, pagesFrom, pagesTo, download, done, signFailed, needDrawing, emptyTypeName, needUpload, +# requestTitle, requestBody, copyTemplate, requestTemplate, copiedTemplate, copyFailed +# Plus navigation_label, dashboard_title, dashboard_description + +T: dict[str, dict[str, str]] = { + "en": { + "navigation_label": "Sign PDF", + "dashboard_title": "Sign PDF", + "dashboard_description": "eSign PDFs in your browser—draw, type, or image—or share a template to collect signatures from others.", + "intro": "Your tool to eSign documents. Sign a document yourself or send a signature request to others.", + "privacyNote": "Your file never leaves this browser. We do not store PDFs or send email for you.", + "mainTabSign": "Sign yourself", + "mainTabRequest": "Request from others", + "tabDraw": "Draw", + "tabType": "Type", + "tabUpload": "Image", + "drawLabel": "Draw your signature", + "clearDrawing": "Clear pad", + "typeLabel": "Your name", + "typePlaceholder": "e.g. Jane Doe", + "chooseImage": "Choose signature image", + "widthLabel": "Signature width ({pct}% of page)", + "pagesAll": "All pages", + "pagesFrom": "First page", + "pagesTo": "Last page", + "download": "Apply signature & download", + "done": "Signed PDF downloaded.", + "signFailed": "Could not sign this PDF. Try a different file.", + "needDrawing": "Draw your signature on the pad first.", + "emptyTypeName": "Enter your name for the typed signature.", + "needUpload": "Choose a PNG or JPEG signature image.", + "requestTitle": "Collect signatures from others", + "requestBody": "MyDevTools never uploads your PDF or sends email on your behalf. Download your file, attach it to a message in Gmail, Outlook, Slack, or any channel you already use, and ask people to sign it here and send a copy back. You can copy a short message below that includes a link to this tool.", + "copyTemplate": "Copy message template", + "requestTemplate": "Hi,\n\nPlease review the attached PDF. You can add a signature using this free in-browser tool (nothing is uploaded to a server from the tool itself):\n{link}\n\nThank you!", + "copiedTemplate": "Message copied to clipboard.", + "copyFailed": "Could not copy. Select and copy the text manually.", + }, + "fr": { + "navigation_label": "Signer un PDF", + "dashboard_title": "Signer un PDF", + "dashboard_description": "Signez des PDF dans le navigateur (dessin, texte ou image) ou partagez un modèle pour recueillir des signatures.", + "intro": "Votre outil pour signer des documents. Signez vous-même ou demandez une signature à d’autres.", + "privacyNote": "Votre fichier ne quitte jamais ce navigateur. Nous ne stockons pas les PDF et n’envoyons pas d’e-mails à votre place.", + "mainTabSign": "Signer moi-même", + "mainTabRequest": "Demander à d’autres", + "tabDraw": "Dessiner", + "tabType": "Texte", + "tabUpload": "Image", + "drawLabel": "Dessinez votre signature", + "clearDrawing": "Effacer le bloc", + "typeLabel": "Votre nom", + "typePlaceholder": "ex. Marie Dupont", + "chooseImage": "Choisir une image de signature", + "widthLabel": "Largeur de la signature ({pct} % de la page)", + "pagesAll": "Toutes les pages", + "pagesFrom": "Première page", + "pagesTo": "Dernière page", + "download": "Appliquer la signature et télécharger", + "done": "PDF signé téléchargé.", + "signFailed": "Impossible de signer ce PDF. Essayez un autre fichier.", + "needDrawing": "Dessinez d’abord votre signature sur le bloc.", + "emptyTypeName": "Saisissez votre nom pour la signature texte.", + "needUpload": "Choisissez une image de signature PNG ou JPEG.", + "requestTitle": "Recueillir des signatures", + "requestBody": "MyDevTools ne téléverse pas votre PDF et n’envoie pas d’e-mails à votre place. Téléchargez le fichier, joignez-le dans Gmail, Outlook, Slack ou tout autre canal, et demandez aux personnes de signer ici puis de vous renvoyer une copie. Vous pouvez copier ci-dessous un court message incluant un lien vers cet outil.", + "copyTemplate": "Copier le modèle de message", + "requestTemplate": "Bonjour,\n\nMerci de consulter le PDF joint. Vous pouvez ajouter une signature avec cet outil gratuit dans le navigateur (rien n’est téléversé vers un serveur depuis l’outil lui-même) :\n{link}\n\nMerci !", + "copiedTemplate": "Message copié dans le presse-papiers.", + "copyFailed": "Copie impossible. Sélectionnez et copiez le texte manuellement.", + }, + "es": { + "navigation_label": "Firmar PDF", + "dashboard_title": "Firmar PDF", + "dashboard_description": "Firma PDF en el navegador (dibujo, texto o imagen) o comparte una plantilla para recoger firmas.", + "intro": "Tu herramienta para firmar documentos. Firma tú mismo o solicita la firma a otras personas.", + "privacyNote": "Tu archivo no sale de este navegador. No almacenamos PDF ni enviamos correos por ti.", + "mainTabSign": "Firmar yo mismo", + "mainTabRequest": "Solicitar a otros", + "tabDraw": "Dibujar", + "tabType": "Escribir", + "tabUpload": "Imagen", + "drawLabel": "Dibuja tu firma", + "clearDrawing": "Borrar lienzo", + "typeLabel": "Tu nombre", + "typePlaceholder": "p. ej. Ana García", + "chooseImage": "Elegir imagen de firma", + "widthLabel": "Ancho de la firma ({pct} % de la página)", + "pagesAll": "Todas las páginas", + "pagesFrom": "Primera página", + "pagesTo": "Última página", + "download": "Aplicar firma y descargar", + "done": "PDF firmado descargado.", + "signFailed": "No se pudo firmar este PDF. Prueba con otro archivo.", + "needDrawing": "Dibuja primero tu firma en el lienzo.", + "emptyTypeName": "Introduce tu nombre para la firma escrita.", + "needUpload": "Elige una imagen de firma PNG o JPEG.", + "requestTitle": "Recoger firmas de otras personas", + "requestBody": "MyDevTools no sube tu PDF ni envía correos por ti. Descarga el archivo, adjúntalo en Gmail, Outlook, Slack o el canal que uses y pide que firmen aquí y te devuelvan una copia. Puedes copiar un mensaje breve con enlace a esta herramienta.", + "copyTemplate": "Copiar plantilla de mensaje", + "requestTemplate": "Hola,\n\nPor favor revisa el PDF adjunto. Puedes añadir una firma con esta herramienta gratuita en el navegador (la herramienta no sube nada a un servidor):\n{link}\n\n¡Gracias!", + "copiedTemplate": "Mensaje copiado al portapapeles.", + "copyFailed": "No se pudo copiar. Selecciona y copia el texto manualmente.", + }, + "de": { + "navigation_label": "PDF signieren", + "dashboard_title": "PDF signieren", + "dashboard_description": "PDFs im Browser signieren (zeichnen, tippen oder Bild) oder eine Vorlage teilen, um Unterschriften einzuholen.", + "intro": "Ihr Tool zum elektronischen Signieren. Signieren Sie selbst oder bitten Sie andere um eine Unterschrift.", + "privacyNote": "Ihre Datei verlässt diesen Browser nicht. Wir speichern keine PDFs und versenden keine E-Mails für Sie.", + "mainTabSign": "Selbst signieren", + "mainTabRequest": "Andere bitten", + "tabDraw": "Zeichnen", + "tabType": "Tippen", + "tabUpload": "Bild", + "drawLabel": "Signatur zeichnen", + "clearDrawing": "Feld leeren", + "typeLabel": "Ihr Name", + "typePlaceholder": "z. B. Max Mustermann", + "chooseImage": "Signaturbild wählen", + "widthLabel": "Signaturbreite ({pct} % der Seite)", + "pagesAll": "Alle Seiten", + "pagesFrom": "Erste Seite", + "pagesTo": "Letzte Seite", + "download": "Signatur anwenden & herunterladen", + "done": "Signiertes PDF heruntergeladen.", + "signFailed": "PDF konnte nicht signiert werden. Andere Datei versuchen.", + "needDrawing": "Zeichnen Sie zuerst Ihre Signatur.", + "emptyTypeName": "Namen für die Textsignatur eingeben.", + "needUpload": "PNG- oder JPEG-Signaturbild wählen.", + "requestTitle": "Unterschriften von anderen einholen", + "requestBody": "MyDevTools lädt Ihr PDF nicht hoch und versendet keine E-Mails. Laden Sie die Datei herunter, hängen Sie sie in Gmail, Outlook, Slack o. Ä. an und bitten Sie andere, hier zu signieren und eine Kopie zurückzusenden. Unten können Sie eine kurze Nachricht mit Link kopieren.", + "copyTemplate": "Nachrichtenvorlage kopieren", + "requestTemplate": "Hallo,\n\nbitte prüfen Sie das angehängte PDF. Eine Signatur können Sie mit diesem kostenlosen Browser-Tool hinzufügen (von der Anwendung selbst wird nichts auf einen Server hochgeladen):\n{link}\n\nVielen Dank!", + "copiedTemplate": "Nachricht in die Zwischenablage kopiert.", + "copyFailed": "Kopieren fehlgeschlagen. Text bitte manuell markieren und kopieren.", + }, + "it": { + "navigation_label": "Firma PDF", + "dashboard_title": "Firma PDF", + "dashboard_description": "Firma i PDF nel browser (disegno, testo o immagine) o condividi un modello per raccogliere firme.", + "intro": "Il tuo strumento per firmare documenti. Firma tu stesso o chiedi la firma ad altri.", + "privacyNote": "Il file non lascia mai questo browser. Non archiviamo PDF né inviamo email al posto tuo.", + "mainTabSign": "Firma da solo", + "mainTabRequest": "Richiedi ad altri", + "tabDraw": "Disegna", + "tabType": "Digita", + "tabUpload": "Immagine", + "drawLabel": "Disegna la tua firma", + "clearDrawing": "Cancella area", + "typeLabel": "Il tuo nome", + "typePlaceholder": "es. Mario Rossi", + "chooseImage": "Scegli immagine firma", + "widthLabel": "Larghezza firma ({pct}% della pagina)", + "pagesAll": "Tutte le pagine", + "pagesFrom": "Prima pagina", + "pagesTo": "Ultima pagina", + "download": "Applica firma e scarica", + "done": "PDF firmato scaricato.", + "signFailed": "Impossibile firmare questo PDF. Prova un altro file.", + "needDrawing": "Disegna prima la firma nell’area.", + "emptyTypeName": "Inserisci il nome per la firma digitata.", + "needUpload": "Scegli un’immagine firma PNG o JPEG.", + "requestTitle": "Raccogli firme da altri", + "requestBody": "MyDevTools non carica il tuo PDF e non invia email per conto tuo. Scarica il file, allegalo in Gmail, Outlook, Slack o altro canale e chiedi di firmare qui e di inviarti una copia. Puoi copiare un breve messaggio con il link a questo strumento.", + "copyTemplate": "Copia modello messaggio", + "requestTemplate": "Ciao,\n\nti chiedo di esaminare il PDF allegato. Puoi aggiungere una firma con questo strumento gratuito nel browser (dallo strumento non viene caricato nulla su un server):\n{link}\n\nGrazie!", + "copiedTemplate": "Messaggio copiato negli appunti.", + "copyFailed": "Impossibile copiare. Seleziona e copia il testo manualmente.", + }, + "pt": { + "navigation_label": "Assinar PDF", + "dashboard_title": "Assinar PDF", + "dashboard_description": "Assine PDFs no navegador (desenho, texto ou imagem) ou partilhe um modelo para recolher assinaturas.", + "intro": "A sua ferramenta para assinar documentos. Assine você mesmo ou peça assinaturas a outras pessoas.", + "privacyNote": "O ficheiro não sai deste navegador. Não armazenamos PDFs nem enviamos e-mail por si.", + "mainTabSign": "Assinar eu mesmo", + "mainTabRequest": "Pedir a outros", + "tabDraw": "Desenhar", + "tabType": "Escrever", + "tabUpload": "Imagem", + "drawLabel": "Desenhe a sua assinatura", + "clearDrawing": "Limpar área", + "typeLabel": "O seu nome", + "typePlaceholder": "ex.: João Silva", + "chooseImage": "Escolher imagem de assinatura", + "widthLabel": "Largura da assinatura ({pct}% da página)", + "pagesAll": "Todas as páginas", + "pagesFrom": "Primeira página", + "pagesTo": "Última página", + "download": "Aplicar assinatura e transferir", + "done": "PDF assinado transferido.", + "signFailed": "Não foi possível assinar este PDF. Tente outro ficheiro.", + "needDrawing": "Desenhe primeiro a assinatura na área.", + "emptyTypeName": "Introduza o nome para a assinatura escrita.", + "needUpload": "Escolha uma imagem PNG ou JPEG da assinatura.", + "requestTitle": "Recolher assinaturas de outros", + "requestBody": "O MyDevTools não carrega o seu PDF nem envia e-mail por si. Transfira o ficheiro, anexe no Gmail, Outlook, Slack ou outro canal e peça para assinarem aqui e devolverem uma cópia. Pode copiar uma mensagem curta com ligação a esta ferramenta.", + "copyTemplate": "Copiar modelo de mensagem", + "requestTemplate": "Olá,\n\nPor favor reveja o PDF em anexo. Pode adicionar uma assinatura com esta ferramenta gratuita no navegador (nada é enviado para um servidor a partir da própria ferramenta):\n{link}\n\nObrigado!", + "copiedTemplate": "Mensagem copiada para a área de transferência.", + "copyFailed": "Não foi possível copiar. Selecione e copie o texto manualmente.", + }, + "pt-BR": { + "navigation_label": "Assinar PDF", + "dashboard_title": "Assinar PDF", + "dashboard_description": "Assine PDFs no navegador (desenho, texto ou imagem) ou compartilhe um modelo para coletar assinaturas.", + "intro": "Sua ferramenta para assinar documentos. Assine você mesmo ou envie uma solicitação de assinatura a outras pessoas.", + "privacyNote": "Seu arquivo não sai deste navegador. Não armazenamos PDFs nem enviamos e-mail por você.", + "mainTabSign": "Assinar eu mesmo", + "mainTabRequest": "Pedir a outros", + "tabDraw": "Desenhar", + "tabType": "Digitar", + "tabUpload": "Imagem", + "drawLabel": "Desenhe sua assinatura", + "clearDrawing": "Limpar quadro", + "typeLabel": "Seu nome", + "typePlaceholder": "ex.: Maria Silva", + "chooseImage": "Escolher imagem da assinatura", + "widthLabel": "Largura da assinatura ({pct}% da página)", + "pagesAll": "Todas as páginas", + "pagesFrom": "Primeira página", + "pagesTo": "Última página", + "download": "Aplicar assinatura e baixar", + "done": "PDF assinado baixado.", + "signFailed": "Não foi possível assinar este PDF. Tente outro arquivo.", + "needDrawing": "Desenhe sua assinatura no quadro primeiro.", + "emptyTypeName": "Digite seu nome para a assinatura em texto.", + "needUpload": "Escolha uma imagem PNG ou JPEG da assinatura.", + "requestTitle": "Coletar assinaturas de outras pessoas", + "requestBody": "O MyDevTools não envia seu PDF para servidores nem envia e-mail por você. Baixe o arquivo, anexe no Gmail, Outlook, Slack ou outro canal e peça para assinarem aqui e devolverem uma cópia. Você pode copiar uma mensagem curta com link para esta ferramenta.", + "copyTemplate": "Copiar modelo de mensagem", + "requestTemplate": "Olá,\n\nPor favor, revise o PDF em anexo. Você pode adicionar uma assinatura com esta ferramenta gratuita no navegador (nada é enviado a um servidor pela própria ferramenta):\n{link}\n\nObrigado!", + "copiedTemplate": "Mensagem copiada para a área de transferência.", + "copyFailed": "Não foi possível copiar. Selecione e copie o texto manualmente.", + }, + "nl": { + "navigation_label": "PDF ondertekenen", + "dashboard_title": "PDF ondertekenen", + "dashboard_description": "PDF’s in de browser ondertekenen (tekenen, typen of afbeelding) of een sjabloon delen om handtekeningen te verzamelen.", + "intro": "Jouw tool voor e‑ondertekening. Onderteken zelf of vraag anderen om een handtekening.", + "privacyNote": "Je bestand verlaat deze browser niet. We slaan geen PDF’s op en sturen geen e‑mail namens jou.", + "mainTabSign": "Zelf ondertekenen", + "mainTabRequest": "Anderen vragen", + "tabDraw": "Tekenen", + "tabType": "Typen", + "tabUpload": "Afbeelding", + "drawLabel": "Teken je handtekening", + "clearDrawing": "Veld wissen", + "typeLabel": "Je naam", + "typePlaceholder": "bijv. Jan Jansen", + "chooseImage": "Handtekeningafbeelding kiezen", + "widthLabel": "Breedte handtekening ({pct}% van pagina)", + "pagesAll": "Alle pagina’s", + "pagesFrom": "Eerste pagina", + "pagesTo": "Laatste pagina", + "download": "Handtekening toepassen & downloaden", + "done": "Ondertekende PDF gedownload.", + "signFailed": "PDF kon niet worden ondertekend. Probeer een ander bestand.", + "needDrawing": "Teken eerst je handtekening op het veld.", + "emptyTypeName": "Vul je naam in voor de getypte handtekening.", + "needUpload": "Kies een PNG- of JPEG-afbeelding van je handtekening.", + "requestTitle": "Handtekeningen van anderen verzamelen", + "requestBody": "MyDevTools uploadt je PDF niet en stuurt geen e‑mail voor je. Download het bestand, voeg het toe in Gmail, Outlook, Slack of een ander kanaal en vraag mensen hier te tekenen en een kopie terug te sturen. Hieronder kun je een kort bericht met link kopiëren.", + "copyTemplate": "Berichtsjabloon kopiëren", + "requestTemplate": "Hallo,\n\nWil je de bijgevoegde PDF bekijken? Je kunt een handtekening toevoegen met deze gratis tool in de browser (de tool zelf uploadt niets naar een server):\n{link}\n\nBedankt!", + "copiedTemplate": "Bericht gekopieerd naar het klembord.", + "copyFailed": "Kopiëren mislukt. Selecteer en kopieer de tekst handmatig.", + }, + "pl": { + "navigation_label": "Podpisz PDF", + "dashboard_title": "Podpisz PDF", + "dashboard_description": "Podpisuj PDF w przeglądarce (rysunek, tekst lub obraz) lub udostępnij szablon, by zebrać podpisy.", + "intro": "Narzędzie do podpisywania dokumentów. Podpisz sam lub poproś innych o podpis.", + "privacyNote": "Plik nie opuszcza tej przeglądarki. Nie przechowujemy PDF ani nie wysyłamy e‑maili w Twoim imieniu.", + "mainTabSign": "Podpisz sam", + "mainTabRequest": "Poproś innych", + "tabDraw": "Rysuj", + "tabType": "Tekst", + "tabUpload": "Obraz", + "drawLabel": "Narysuj podpis", + "clearDrawing": "Wyczyść pole", + "typeLabel": "Twoje imię i nazwisko", + "typePlaceholder": "np. Jan Kowalski", + "chooseImage": "Wybierz obraz podpisu", + "widthLabel": "Szerokość podpisu ({pct}% strony)", + "pagesAll": "Wszystkie strony", + "pagesFrom": "Pierwsza strona", + "pagesTo": "Ostatnia strona", + "download": "Zastosuj podpis i pobierz", + "done": "Podpisany PDF pobrany.", + "signFailed": "Nie udało się podpisać tego PDF. Spróbuj innego pliku.", + "needDrawing": "Najpierw narysuj podpis na polu.", + "emptyTypeName": "Wpisz imię i nazwisko dla podpisu tekstowego.", + "needUpload": "Wybierz obraz PNG lub JPEG podpisu.", + "requestTitle": "Zbieranie podpisów od innych", + "requestBody": "MyDevTools nie przesyła Twojego PDF na serwer ani nie wysyła e‑maili za Ciebie. Pobierz plik, dołącz go w Gmailu, Outlooku, Slacku lub innym kanale i poproś o podpis tutaj i odesłanie kopii. Poniżej możesz skopiować krótką wiadomość z linkiem do tego narzędzia.", + "copyTemplate": "Kopiuj szablon wiadomości", + "requestTemplate": "Cześć,\n\nproszę o zapoznanie się z załączonym PDF. Podpis możesz dodać w tej darmowej przeglądarkowej aplikacji (samo narzędzie nic nie wysyła na serwer):\n{link}\n\nDziękuję!", + "copiedTemplate": "Wiadomość skopiowana do schowka.", + "copyFailed": "Nie udało się skopiować. Zaznacz i skopiuj tekst ręcznie.", + }, + "ru": { + "navigation_label": "Подписать PDF", + "dashboard_title": "Подписать PDF", + "dashboard_description": "Подписывайте PDF в браузере (рисунок, текст или изображение) или делитесь шаблоном для сбора подписей.", + "intro": "Ваш инструмент для электронной подписи документов. Подпишите сами или запросите подпись у других.", + "privacyNote": "Файл не покидает этот браузер. Мы не храним PDF и не отправляем письма за вас.", + "mainTabSign": "Подписать самому", + "mainTabRequest": "Запросить у других", + "tabDraw": "Рисовать", + "tabType": "Текст", + "tabUpload": "Изображение", + "drawLabel": "Нарисуйте подпись", + "clearDrawing": "Очистить поле", + "typeLabel": "Ваше имя", + "typePlaceholder": "напр. Иван Иванов", + "chooseImage": "Выбрать изображение подписи", + "widthLabel": "Ширина подписи ({pct}% страницы)", + "pagesAll": "Все страницы", + "pagesFrom": "Первая страница", + "pagesTo": "Последняя страница", + "download": "Применить подпись и скачать", + "done": "Подписанный PDF скачан.", + "signFailed": "Не удалось подписать PDF. Попробуйте другой файл.", + "needDrawing": "Сначала нарисуйте подпись в поле.", + "emptyTypeName": "Введите имя для текстовой подписи.", + "needUpload": "Выберите изображение подписи PNG или JPEG.", + "requestTitle": "Сбор подписей у других", + "requestBody": "MyDevTools не загружает ваш PDF на сервер и не отправляет почту за вас. Скачайте файл, прикрепите в Gmail, Outlook, Slack или другом канале и попросите подписать здесь и прислать копию. Ниже можно скопировать короткое сообщение со ссылкой на этот инструмент.", + "copyTemplate": "Копировать шаблон сообщения", + "requestTemplate": "Здравствуйте,\n\nпожалуйста, ознакомьтесь с приложенным PDF. Подпись можно добавить в этом бесплатном браузерном инструменте (сам инструмент ничего не загружает на сервер):\n{link}\n\nСпасибо!", + "copiedTemplate": "Сообщение скопировано в буфер обмена.", + "copyFailed": "Не удалось скопировать. Выделите и скопируйте текст вручную.", + }, + "uk": { + "navigation_label": "Підписати PDF", + "dashboard_title": "Підписати PDF", + "dashboard_description": "Підписуйте PDF у браузері (малюнок, текст або зображення) або діліться шаблоном для збору підписів.", + "intro": "Ваш інструмент для електронного підпису. Підпишіть самі або надішліть запит іншим.", + "privacyNote": "Файл не залишає цей браузер. Ми не зберігаємо PDF і не надсилаємо листи за вас.", + "mainTabSign": "Підписати самому", + "mainTabRequest": "Запитати в інших", + "tabDraw": "Малювати", + "tabType": "Текст", + "tabUpload": "Зображення", + "drawLabel": "Намалюйте підпис", + "clearDrawing": "Очистити поле", + "typeLabel": "Ваше ім’я", + "typePlaceholder": "напр. Олена Коваленко", + "chooseImage": "Вибрати зображення підпису", + "widthLabel": "Ширина підпису ({pct}% сторінки)", + "pagesAll": "Усі сторінки", + "pagesFrom": "Перша сторінка", + "pagesTo": "Остання сторінка", + "download": "Застосувати підпис і завантажити", + "done": "Підписаний PDF завантажено.", + "signFailed": "Не вдалося підписати PDF. Спробуйте інший файл.", + "needDrawing": "Спочатку намалюйте підпис у полі.", + "emptyTypeName": "Введіть ім’я для текстового підпису.", + "needUpload": "Виберіть зображення підпису PNG або JPEG.", + "requestTitle": "Збір підписів від інших", + "requestBody": "MyDevTools не завантажує ваш PDF на сервер і не надсилає листи за вас. Завантажте файл, додайте до Gmail, Outlook, Slack або іншого каналу й попросіть підписати тут і надіслати копію. Нижче можна скопіювати коротке повідомлення з посиланням на цей інструмент.", + "copyTemplate": "Копіювати шаблон повідомлення", + "requestTemplate": "Вітаю,\n\nбудь ласка, перегляньте доданий PDF. Підпис можна додати в цій безкоштовній браузерній утиліті (сама утиліта нічого не завантажує на сервер):\n{link}\n\nДякую!", + "copiedTemplate": "Повідомлення скопійовано в буфер обміну.", + "copyFailed": "Не вдалося скопіювати. Виділіть і скопіюйте текст вручну.", + }, + "cs": { + "navigation_label": "Podepsat PDF", + "dashboard_title": "Podepsat PDF", + "dashboard_description": "Podepisujte PDF v prohlížeči (kresba, text nebo obrázek) nebo sdílejte šablonu pro sběr podpisů.", + "intro": "Váš nástroj pro elektronický podpis. Podepište dokument sami nebo požádejte o podpis ostatní.", + "privacyNote": "Soubor neopouští tento prohlížeč. PDF neukládáme a e‑maily za vás neodesíláme.", + "mainTabSign": "Podepsat sám", + "mainTabRequest": "Požádat ostatní", + "tabDraw": "Kreslit", + "tabType": "Psát", + "tabUpload": "Obrázek", + "drawLabel": "Nakreslete podpis", + "clearDrawing": "Vymazat plochu", + "typeLabel": "Vaše jméno", + "typePlaceholder": "např. Jan Novák", + "chooseImage": "Vybrat obrázek podpisu", + "widthLabel": "Šířka podpisu ({pct} % stránky)", + "pagesAll": "Všechny stránky", + "pagesFrom": "První stránka", + "pagesTo": "Poslední stránka", + "download": "Použít podpis a stáhnout", + "done": "Podepsané PDF staženo.", + "signFailed": "PDF se nepodařilo podepsat. Zkuste jiný soubor.", + "needDrawing": "Nejprve nakreslete podpis na plochu.", + "emptyTypeName": "Zadejte jméno pro textový podpis.", + "needUpload": "Vyberte obrázek podpisu PNG nebo JPEG.", + "requestTitle": "Shromáždit podpisy od ostatních", + "requestBody": "MyDevTools nenahrává váš PDF na server ani neodesílá e‑maily za vás. Stáhněte soubor, přiložte ho v Gmailu, Outlooku, Slacku nebo jiném kanálu a požádejte lidi, aby podepsali zde a poslali kopii zpět. Níže můžete zkopírovat krátkou zprávu s odkazem na tento nástroj.", + "copyTemplate": "Kopírovat šablonu zprávy", + "requestTemplate": "Dobrý den,\n\nprosím o kontrolu přiloženého PDF. Podpis můžete přidat v tomto bezplatném nástroji v prohlížeči (samotný nástroj nic na server nenahrává):\n{link}\n\nDěkuji!", + "copiedTemplate": "Zpráva zkopírována do schránky.", + "copyFailed": "Kopírování se nezdařilo. Text vyberte a zkopírujte ručně.", + }, + "da": { + "navigation_label": "Underskriv PDF", + "dashboard_title": "Underskriv PDF", + "dashboard_description": "Underskriv PDF i browseren (tegning, tekst eller billede) eller del en skabelon for at indsamle underskrifter.", + "intro": "Dit værktøj til e‑underskrift. Underskriv selv eller bed andre om underskrift.", + "privacyNote": "Din fil forlader ikke denne browser. Vi gemmer ikke PDF’er og sender ikke e‑mail på dine vegne.", + "mainTabSign": "Underskriv selv", + "mainTabRequest": "Bed andre", + "tabDraw": "Tegn", + "tabType": "Skriv", + "tabUpload": "Billede", + "drawLabel": "Tegn din underskrift", + "clearDrawing": "Ryd felt", + "typeLabel": "Dit navn", + "typePlaceholder": "f.eks. Anna Nielsen", + "chooseImage": "Vælg underskriftsbillede", + "widthLabel": "Underskriftsbredde ({pct}% af siden)", + "pagesAll": "Alle sider", + "pagesFrom": "Første side", + "pagesTo": "Sidste side", + "download": "Anvend underskrift og download", + "done": "Underskrevet PDF downloadet.", + "signFailed": "PDF kunne ikke underskrives. Prøv en anden fil.", + "needDrawing": "Tegn først din underskrift på feltet.", + "emptyTypeName": "Indtast dit navn til den skrevne underskrift.", + "needUpload": "Vælg et PNG‑ eller JPEG‑billede af underskriften.", + "requestTitle": "Indsaml underskrifter fra andre", + "requestBody": "MyDevTools uploader ikke din PDF og sender ikke e‑mail for dig. Download filen, vedhæft den i Gmail, Outlook, Slack eller en anden kanal, og bed folk underskrive her og sende en kopi tilbage. Du kan kopiere en kort besked med link til dette værktøj nedenfor.", + "copyTemplate": "Kopiér beskedskabelon", + "requestTemplate": "Hej,\n\nvenligst gennemgå den vedhæftede PDF. Du kan tilføje en underskrift med dette gratis browser‑værktøj (værktøjet selv uploader intet til en server):\n{link}\n\nTak!", + "copiedTemplate": "Besked kopieret til udklipsholder.", + "copyFailed": "Kunne ikke kopiere. Markér og kopier teksten manuelt.", + }, + "sv": { + "navigation_label": "Signera PDF", + "dashboard_title": "Signera PDF", + "dashboard_description": "Signera PDF i webbläsaren (rita, skriva eller bild) eller dela en mall för att samla in signaturer.", + "intro": "Ditt verktyg för e‑signering. Signera själv eller be andra om signatur.", + "privacyNote": "Din fil lämnar aldrig den här webbläsaren. Vi lagrar inte PDF:er och skickar inte e‑post åt dig.", + "mainTabSign": "Signera själv", + "mainTabRequest": "Be andra", + "tabDraw": "Rita", + "tabType": "Skriv", + "tabUpload": "Bild", + "drawLabel": "Rita din signatur", + "clearDrawing": "Rensa yta", + "typeLabel": "Ditt namn", + "typePlaceholder": "t.ex. Anna Andersson", + "chooseImage": "Välj signaturbild", + "widthLabel": "Signaturbredd ({pct}% av sidan)", + "pagesAll": "Alla sidor", + "pagesFrom": "Första sidan", + "pagesTo": "Sista sidan", + "download": "Använd signatur och ladda ner", + "done": "Signerad PDF nedladdad.", + "signFailed": "Kunde inte signera PDF:en. Prova en annan fil.", + "needDrawing": "Rita först din signatur på ytan.", + "emptyTypeName": "Ange ditt namn för textsignaturen.", + "needUpload": "Välj en PNG‑ eller JPEG‑bild av signaturen.", + "requestTitle": "Samla signaturer från andra", + "requestBody": "MyDevTools laddar inte upp din PDF och skickar inte e‑post åt dig. Ladda ner filen, bifoga i Gmail, Outlook, Slack eller annan kanal och be folk signera här och skicka tillbaka en kopia. Du kan kopiera ett kort meddelande med länk till verktyget nedan.", + "copyTemplate": "Kopiera meddelandemall", + "requestTemplate": "Hej,\n\nvänligen granska bifogad PDF. Du kan lägga till signatur med detta kostnadsfria webbläsarverktyg (själva verktyget laddar inte upp något till en server):\n{link}\n\nTack!", + "copiedTemplate": "Meddelande kopierat till urklipp.", + "copyFailed": "Kunde inte kopiera. Markera och kopiera texten manuellt.", + }, + "nb": { + "navigation_label": "Signer PDF", + "dashboard_title": "Signer PDF", + "dashboard_description": "Signer PDF i nettleseren (tegne, skrive eller bilde) eller del en mal for å samle inn signaturer.", + "intro": "Verktøyet ditt for e‑signering. Signer selv eller be andre om signatur.", + "privacyNote": "Filen din forlater ikke denne nettleseren. Vi lagrer ikke PDF‑er og sender ikke e‑post på dine vegne.", + "mainTabSign": "Signer selv", + "mainTabRequest": "Be andre", + "tabDraw": "Tegn", + "tabType": "Skriv", + "tabUpload": "Bilde", + "drawLabel": "Tegn signaturen din", + "clearDrawing": "Tøm felt", + "typeLabel": "Navnet ditt", + "typePlaceholder": "f.eks. Kari Nordmann", + "chooseImage": "Velg signaturbilde", + "widthLabel": "Signaturbredde ({pct}% av siden)", + "pagesAll": "Alle sider", + "pagesFrom": "Første side", + "pagesTo": "Siste side", + "download": "Bruk signatur og last ned", + "done": "Signert PDF lastet ned.", + "signFailed": "Kunne ikke signere PDF. Prøv en annen fil.", + "needDrawing": "Tegn signaturen i feltet først.", + "emptyTypeName": "Skriv inn navn for tekstsignatur.", + "needUpload": "Velg et PNG‑ eller JPEG‑bilde av signaturen.", + "requestTitle": "Samle signaturer fra andre", + "requestBody": "MyDevTools laster ikke opp PDF‑en din og sender ikke e‑post for deg. Last ned filen, legg den ved i Gmail, Outlook, Slack eller annen kanal, og be folk signere her og sende kopi tilbake. Du kan kopiere en kort melding med lenke til verktøyet nedenfor.", + "copyTemplate": "Kopier meldingsmal", + "requestTemplate": "Hei,\n\nvennligst se gjennom vedlagt PDF. Du kan legge til signatur med dette gratis nettleserverktøyet (selve verktøyet laster ikke opp noe til en server):\n{link}\n\nTakk!", + "copiedTemplate": "Melding kopiert til utklippstavlen.", + "copyFailed": "Kunne ikke kopiere. Marker og kopier teksten manuelt.", + }, + "tr": { + "navigation_label": "PDF imzala", + "dashboard_title": "PDF imzala", + "dashboard_description": "PDF’leri tarayıcıda imzalayın (çizim, yazı veya görsel) veya imza toplamak için şablon paylaşın.", + "intro": "Belgeleri e‑imzalamak için aracınız. Kendiniz imzalayın veya başkalarından imza isteyin.", + "privacyNote": "Dosyanız bu tarayıcıdan çıkmaz. PDF saklamıyoruz ve sizin adınıza e‑posta göndermiyoruz.", + "mainTabSign": "Kendim imzala", + "mainTabRequest": "Başkalarından iste", + "tabDraw": "Çiz", + "tabType": "Yaz", + "tabUpload": "Görsel", + "drawLabel": "İmzanızı çizin", + "clearDrawing": "Alanı temizle", + "typeLabel": "Adınız", + "typePlaceholder": "ör. Ayşe Yılmaz", + "chooseImage": "İmza görseli seçin", + "widthLabel": "İmza genişliği (sayfanın %{pct})", + "pagesAll": "Tüm sayfalar", + "pagesFrom": "İlk sayfa", + "pagesTo": "Son sayfa", + "download": "İmzayı uygula ve indir", + "done": "İmzalı PDF indirildi.", + "signFailed": "Bu PDF imzalanamadı. Başka bir dosya deneyin.", + "needDrawing": "Önce alana imzanızı çizin.", + "emptyTypeName": "Yazılı imza için adınızı girin.", + "needUpload": "PNG veya JPEG imza görseli seçin.", + "requestTitle": "Başkalarından imza toplayın", + "requestBody": "MyDevTools PDF’nizi sunucuya yüklemez ve sizin adınıza e‑posta göndermez. Dosyayı indirip Gmail, Outlook, Slack veya kullandığınız kanala ekleyin; insanlardan burada imzalayıp kopya göndermelerini isteyin. Aşağıdaki kısa mesajı bu araca bağlantıyla kopyalayabilirsiniz.", + "copyTemplate": "Mesaj şablonunu kopyala", + "requestTemplate": "Merhaba,\n\nlütfen ekteki PDF’yi inceleyin. Bu ücretsiz tarayıcı aracıyla imza ekleyebilirsiniz (aracın kendisi sunucuya bir şey yüklemez):\n{link}\n\nTeşekkürler!", + "copiedTemplate": "Mesaj panoya kopyalandı.", + "copyFailed": "Kopyalanamadı. Metni elle seçip kopyalayın.", + }, + "el": { + "navigation_label": "Υπογραφή PDF", + "dashboard_title": "Υπογραφή PDF", + "dashboard_description": "Υπογράψτε PDF στον browser (σχέδιο, κείμενο ή εικόνα) ή μοιραστείτε πρότυπο για συλλογή υπογραφών.", + "intro": "Το εργαλείο σας για ηλεκτρονική υπογραφή. Υπογράψτε μόνοι σας ή ζητήστε υπογραφή από άλλους.", + "privacyNote": "Το αρχείο σας δεν φεύγει ποτέ από αυτόν τον browser. Δεν αποθηκεύουμε PDF ούτε στέλνουμε email για λογαριασμό σας.", + "mainTabSign": "Υπογραφή από εμένα", + "mainTabRequest": "Αίτημα σε άλλους", + "tabDraw": "Σχέδιο", + "tabType": "Πληκτρολόγηση", + "tabUpload": "Εικόνα", + "drawLabel": "Σχεδιάστε την υπογραφή σας", + "clearDrawing": "Εκκαθάριση πεδίου", + "typeLabel": "Το όνομά σας", + "typePlaceholder": "π.χ. Μαρία Παπαδοπούλου", + "chooseImage": "Επιλογή εικόνας υπογραφής", + "widthLabel": "Πλάτος υπογραφής ({pct}% της σελίδας)", + "pagesAll": "Όλες οι σελίδες", + "pagesFrom": "Πρώτη σελίδα", + "pagesTo": "Τελευταία σελίδα", + "download": "Εφαρμογή υπογραφής και λήψη", + "done": "Το υπογεγραμμένο PDF λήφθηκε.", + "signFailed": "Δεν ήταν δυνατή η υπογραφή αυτού του PDF. Δοκιμάστε άλλο αρχείο.", + "needDrawing": "Σχεδιάστε πρώτα την υπογραφή στο πεδίο.", + "emptyTypeName": "Εισάγετε το όνομα για την υπογραφή κειμένου.", + "needUpload": "Επιλέξτε εικόνα υπογραφής PNG ή JPEG.", + "requestTitle": "Συλλογή υπογραφών από άλλους", + "requestBody": "Το MyDevTools δεν ανεβάζει το PDF σας ούτε στέλνει email για λογαριασμό σας. Κατεβάστε το αρχείο, επισυνάψτε το σε Gmail, Outlook, Slack ή άλλο κανάλι και ζητήστε να υπογράψουν εδώ και να σας στείλουν αντίγραφο. Μπορείτε να αντιγράψετε ένα σύντομο μήνυμα με σύνδεσμο σε αυτό το εργαλείο.", + "copyTemplate": "Αντιγραφή προτύπου μηνύματος", + "requestTemplate": "Γεια σας,\n\nπαρακαλώ δείτε το συνημμένο PDF. Μπορείτε να προσθέσετε υπογραφή με αυτό το δωρεάν εργαλείο στον browser (το ίδιο το εργαλείο δεν ανεβάζει τίποτα σε διακομιστή):\n{link}\n\nΕυχαριστώ!", + "copiedTemplate": "Το μήνυμα αντιγράφηκε στο πρόχειρο.", + "copyFailed": "Δεν ήταν δυνατή η αντιγραφή. Επιλέξτε και αντιγράψτε το κείμενο χειροκίνητα.", + }, + "ca": { + "navigation_label": "Signar PDF", + "dashboard_title": "Signar PDF", + "dashboard_description": "Signeu PDF al navegador (dibuix, text o imatge) o compartiu una plantilla per recollir signatures.", + "intro": "La vostra eina per signar documents. Signeu-vos mateixos o sol·liciteu la signatura d’altres.", + "privacyNote": "El fitxer no surt mai d’aquest navegador. No emmagatzemem PDF ni enviem correu per vosaltres.", + "mainTabSign": "Signar jo mateix", + "mainTabRequest": "Demanar a altres", + "tabDraw": "Dibuixar", + "tabType": "Escriure", + "tabUpload": "Imatge", + "drawLabel": "Dibuixeu la vostra signatura", + "clearDrawing": "Esborrar àrea", + "typeLabel": "El vostre nom", + "typePlaceholder": "p. ex. Anna Puig", + "chooseImage": "Trieu imatge de signatura", + "widthLabel": "Amplada de la signatura ({pct}% de la pàgina)", + "pagesAll": "Totes les pàgines", + "pagesFrom": "Primera pàgina", + "pagesTo": "Última pàgina", + "download": "Aplicar signatura i baixar", + "done": "PDF signat baixat.", + "signFailed": "No s’ha pogut signar aquest PDF. Proveu un altre fitxer.", + "needDrawing": "Dibuixeu primer la signatura a l’àrea.", + "emptyTypeName": "Introduïu el nom per a la signatura escrita.", + "needUpload": "Trieu una imatge PNG o JPEG de la signatura.", + "requestTitle": "Recollir signatures d’altres", + "requestBody": "MyDevTools no penja el vostre PDF ni envia correu per vosaltres. Baixeu el fitxer, adjunteu-lo a Gmail, Outlook, Slack o un altre canal i demaneu que signin aquí i us retornin una còpia. Podeu copiar un missatge breu amb l’enllaç a aquesta eina.", + "copyTemplate": "Copiar plantilla de missatge", + "requestTemplate": "Hola,\n\nsi us plau reviseu el PDF adjunt. Podeu afegir una signatura amb aquesta eina gratuïta al navegador (l’eina en si no penja res a cap servidor):\n{link}\n\nGràcies!", + "copiedTemplate": "Missatge copiat al porta-retalls.", + "copyFailed": "No s’ha pogut copiar. Seleccioneu i copieu el text manualment.", + }, + "zh": { + "navigation_label": "签署 PDF", + "dashboard_title": "签署 PDF", + "dashboard_description": "在浏览器中为 PDF 添加签名(手绘、输入文字或图片),或分享模板以收集他人签名。", + "intro": "用于电子签署文档的工具。自行签署或向他人发起签名请求。", + "privacyNote": "您的文件不会离开本浏览器。我们不会存储 PDF,也不会代您发送电子邮件。", + "mainTabSign": "自己签署", + "mainTabRequest": "请他人签署", + "tabDraw": "手绘", + "tabType": "输入", + "tabUpload": "图片", + "drawLabel": "手绘签名", + "clearDrawing": "清除画板", + "typeLabel": "您的姓名", + "typePlaceholder": "例如:张三", + "chooseImage": "选择签名图片", + "widthLabel": "签名宽度(页面宽度的 {pct}%)", + "pagesAll": "全部页面", + "pagesFrom": "起始页", + "pagesTo": "结束页", + "download": "应用签名并下载", + "done": "已下载带签名的 PDF。", + "signFailed": "无法签署此 PDF。请尝试其他文件。", + "needDrawing": "请先在画板上绘制签名。", + "emptyTypeName": "请输入姓名以生成文字签名。", + "needUpload": "请选择 PNG 或 JPEG 格式的签名图片。", + "requestTitle": "向他人收集签名", + "requestBody": "MyDevTools 不会上传您的 PDF,也不会代您发送邮件。请下载文件后通过 Gmail、Outlook、Slack 等渠道发送附件,并请对方在此工具中签署后回传副本。您可复制下方包含工具链接的简短说明。", + "copyTemplate": "复制邮件模板", + "requestTemplate": "您好,\n\n请查阅附件中的 PDF。您可使用以下免费浏览器工具添加签名(工具本身不会将内容上传到服务器):\n{link}\n\n谢谢!", + "copiedTemplate": "已复制到剪贴板。", + "copyFailed": "复制失败,请手动选择并复制文本。", + }, + "ja": { + "navigation_label": "PDF に署名", + "dashboard_title": "PDF に署名", + "dashboard_description": "ブラウザで PDF に署名(手書き・入力・画像)。テンプレートを共有して他者から署名を集められます。", + "intro": "文書の電子署名ツール。自分で署名するか、他者に署名依頼を送れます。", + "privacyNote": "ファイルはこのブラウザから外部に送信されません。PDF を保存せず、メールも代行しません。", + "mainTabSign": "自分で署名", + "mainTabRequest": "他者に依頼", + "tabDraw": "手書き", + "tabType": "入力", + "tabUpload": "画像", + "drawLabel": "署名を手書き", + "clearDrawing": "消去", + "typeLabel": "お名前", + "typePlaceholder": "例:山田太郎", + "chooseImage": "署名画像を選択", + "widthLabel": "署名の幅(ページ幅の {pct}%)", + "pagesAll": "全ページ", + "pagesFrom": "開始ページ", + "pagesTo": "終了ページ", + "download": "署名を適用してダウンロード", + "done": "署名済み PDF をダウンロードしました。", + "signFailed": "この PDF に署名できませんでした。別のファイルをお試しください。", + "needDrawing": "先にパッドに署名を描いてください。", + "emptyTypeName": "テキスト署名用の名前を入力してください。", + "needUpload": "PNG または JPEG の署名画像を選択してください。", + "requestTitle": "他者から署名を集める", + "requestBody": "MyDevTools は PDF をアップロードしたり、代わりにメールを送信したりしません。ファイルをダウンロードし、Gmail・Outlook・Slack などで添付して、ここで署名してコピーを返送するよう依頼してください。下のリンク付き短文をコピーできます。", + "copyTemplate": "メッセージテンプレートをコピー", + "requestTemplate": "こんにちは、\n\n添付の PDF をご確認ください。次の無料ブラウザツールで署名を追加できます(ツール自体はサーバーにアップロードしません):\n{link}\n\nよろしくお願いいたします。", + "copiedTemplate": "クリップボードにコピーしました。", + "copyFailed": "コピーできませんでした。テキストを手動で選択してコピーしてください。", + }, + "ko": { + "navigation_label": "PDF 서명", + "dashboard_title": "PDF 서명", + "dashboard_description": "브라우저에서 PDF에 서명(그리기, 입력, 이미지)하거나 템플릿을 공유해 서명을 받으세요.", + "intro": "문서 전자서명 도구입니다. 직접 서명하거나 다른 사람에게 서명을 요청하세요.", + "privacyNote": "파일은 이 브라우저를 벗어나지 않습니다. PDF를 저장하지 않으며 대신 이메일을 보내지도 않습니다.", + "mainTabSign": "직접 서명", + "mainTabRequest": "다른 사람에게 요청", + "tabDraw": "그리기", + "tabType": "입력", + "tabUpload": "이미지", + "drawLabel": "서명 그리기", + "clearDrawing": "패드 지우기", + "typeLabel": "이름", + "typePlaceholder": "예: 홍길동", + "chooseImage": "서명 이미지 선택", + "widthLabel": "서명 너비(페이지의 {pct}%)", + "pagesAll": "모든 페이지", + "pagesFrom": "첫 페이지", + "pagesTo": "마지막 페이지", + "download": "서명 적용 후 다운로드", + "done": "서명된 PDF를 다운로드했습니다.", + "signFailed": "이 PDF에 서명할 수 없습니다. 다른 파일을 시도하세요.", + "needDrawing": "먼저 패드에 서명을 그리세요.", + "emptyTypeName": "텍스트 서명을 위해 이름을 입력하세요.", + "needUpload": "PNG 또는 JPEG 서명 이미지를 선택하세요.", + "requestTitle": "다른 사람으로부터 서명 받기", + "requestBody": "MyDevTools는 PDF를 업로드하거나 대신 이메일을 보내지 않습니다. 파일을 다운로드한 뒤 Gmail, Outlook, Slack 등에 첨부하고, 여기서 서명한 뒤 사본을 보내 달라고 요청하세요. 아래에서 이 도구 링크가 포함된 짧은 메시지를 복사할 수 있습니다.", + "copyTemplate": "메시지 템플릿 복사", + "requestTemplate": "안녕하세요,\n\n첨부된 PDF를 검토해 주세요. 다음 무료 브라우저 도구로 서명을 추가할 수 있습니다(도구 자체는 서버에 업로드하지 않습니다):\n{link}\n\n감사합니다!", + "copiedTemplate": "클립보드에 복사되었습니다.", + "copyFailed": "복사할 수 없습니다. 텍스트를 직접 선택해 복사하세요.", + }, + "vi": { + "navigation_label": "Ký PDF", + "dashboard_title": "Ký PDF", + "dashboard_description": "Ký PDF trong trình duyệt (vẽ, gõ chữ hoặc ảnh) hoặc chia sẻ mẫu để thu thập chữ ký.", + "intro": "Công cụ ký tài liệu điện tử của bạn. Tự ký hoặc gửi yêu cầu ký cho người khác.", + "privacyNote": "Tệp không rời khỏi trình duyệt này. Chúng tôi không lưu PDF và không gửi email thay bạn.", + "mainTabSign": "Tự ký", + "mainTabRequest": "Nhờ người khác", + "tabDraw": "Vẽ", + "tabType": "Gõ", + "tabUpload": "Ảnh", + "drawLabel": "Vẽ chữ ký của bạn", + "clearDrawing": "Xóa vùng vẽ", + "typeLabel": "Họ tên", + "typePlaceholder": "vd: Nguyễn Văn A", + "chooseImage": "Chọn ảnh chữ ký", + "widthLabel": "Độ rộng chữ ký ({pct}% trang)", + "pagesAll": "Tất cả trang", + "pagesFrom": "Trang đầu", + "pagesTo": "Trang cuối", + "download": "Áp dụng chữ ký và tải xuống", + "done": "Đã tải PDF đã ký.", + "signFailed": "Không thể ký PDF này. Hãy thử tệp khác.", + "needDrawing": "Hãy vẽ chữ ký trên vùng vẽ trước.", + "emptyTypeName": "Nhập tên cho chữ ký dạng chữ.", + "needUpload": "Chọn ảnh chữ ký PNG hoặc JPEG.", + "requestTitle": "Thu thập chữ ký từ người khác", + "requestBody": "MyDevTools không tải PDF của bạn lên máy chủ và không gửi email thay bạn. Tải tệp xuống, đính kèm trong Gmail, Outlook, Slack hoặc kênh bạn dùng và nhờ mọi người ký tại đây rồi gửi lại bản sao. Bạn có thể sao chép tin ngắn kèm liên kết công cụ bên dưới.", + "copyTemplate": "Sao chép mẫu tin nhắn", + "requestTemplate": "Xin chào,\n\nvui lòng xem tệp PDF đính kèm. Bạn có thể thêm chữ ký bằng công cụ miễn phí trong trình duyệt (bản thân công cụ không tải dữ liệu lên máy chủ):\n{link}\n\nCảm ơn!", + "copiedTemplate": "Đã sao chép vào clipboard.", + "copyFailed": "Không thể sao chép. Hãy chọn và sao chép thủ công.", + }, + "id": { + "navigation_label": "Tandatangani PDF", + "dashboard_title": "Tandatangani PDF", + "dashboard_description": "Tanda tangani PDF di browser (gambar, ketik, atau gambar unggah) atau bagikan templat untuk mengumpulkan tanda tangan.", + "intro": "Alat Anda untuk menandatangani dokumen secara elektronik. Tanda tangani sendiri atau minta orang lain menandatangani.", + "privacyNote": "Berkas Anda tidak meninggalkan browser ini. Kami tidak menyimpan PDF dan tidak mengirim email atas nama Anda.", + "mainTabSign": "Tanda tangani sendiri", + "mainTabRequest": "Minta orang lain", + "tabDraw": "Gambar", + "tabType": "Ketik", + "tabUpload": "Gambar", + "drawLabel": "Gambar tanda tangan Anda", + "clearDrawing": "Hapus area", + "typeLabel": "Nama Anda", + "typePlaceholder": "mis. Budi Santoso", + "chooseImage": "Pilih gambar tanda tangan", + "widthLabel": "Lebar tanda tangan ({pct}% halaman)", + "pagesAll": "Semua halaman", + "pagesFrom": "Halaman pertama", + "pagesTo": "Halaman terakhir", + "download": "Terapkan tanda tangan & unduh", + "done": "PDF bertanda tangan diunduh.", + "signFailed": "Tidak dapat menandatangani PDF ini. Coba berkas lain.", + "needDrawing": "Gambar tanda tangan di area terlebih dahulu.", + "emptyTypeName": "Masukkan nama untuk tanda tangan teks.", + "needUpload": "Pilih gambar tanda tangan PNG atau JPEG.", + "requestTitle": "Kumpulkan tanda tangan dari orang lain", + "requestBody": "MyDevTools tidak mengunggah PDF Anda dan tidak mengirim email untuk Anda. Unduh berkas, lampirkan di Gmail, Outlook, Slack, atau saluran lain, dan minta orang menandatangani di sini serta mengirim salinan kembali. Anda dapat menyalin pesan singkat berisi tautan ke alat ini di bawah.", + "copyTemplate": "Salin templat pesan", + "requestTemplate": "Halo,\n\nmohon tinjau PDF terlampir. Anda dapat menambahkan tanda tangan dengan alat gratis di browser ini (alat itu sendiri tidak mengunggah apa pun ke server):\n{link}\n\nTerima kasih!", + "copiedTemplate": "Pesan disalin ke papan klip.", + "copyFailed": "Tidak dapat menyalin. Pilih dan salin teks secara manual.", + }, + "ms": { + "navigation_label": "Tandatangani PDF", + "dashboard_title": "Tandatangani PDF", + "dashboard_description": "Tandatangani PDF dalam pelayar (lukis, taip atau imej) atau kongsi templat untuk mengumpul tandatangan.", + "intro": "Alat anda untuk e-tandatangan dokumen. Tandatangani sendiri atau minta orang lain menandatangani.", + "privacyNote": "Fail anda tidak meninggalkan pelayar ini. Kami tidak menyimpan PDF dan tidak menghantar e-mel bagi pihak anda.", + "mainTabSign": "Tandatangani sendiri", + "mainTabRequest": "Minta orang lain", + "tabDraw": "Lukis", + "tabType": "Taip", + "tabUpload": "Imej", + "drawLabel": "Lukis tandatangan anda", + "clearDrawing": "Kosongkan pad", + "typeLabel": "Nama anda", + "typePlaceholder": "cth. Ahmad bin Ali", + "chooseImage": "Pilih imej tandatangan", + "widthLabel": "Lebar tandatangan ({pct}% halaman)", + "pagesAll": "Semua halaman", + "pagesFrom": "Halaman pertama", + "pagesTo": "Halaman terakhir", + "download": "Gunakan tandatangan & muat turun", + "done": "PDF bertandatangan dimuat turun.", + "signFailed": "Tidak dapat menandatangani PDF ini. Cuba fail lain.", + "needDrawing": "Lukis tandatangan pada pad dahulu.", + "emptyTypeName": "Masukkan nama untuk tandatangan teks.", + "needUpload": "Pilih imej tandatangan PNG atau JPEG.", + "requestTitle": "Kumpul tandatangan daripada orang lain", + "requestBody": "MyDevTools tidak memuat naik PDF anda dan tidak menghantar e-mel bagi pihak anda. Muat turun fail, lampirkan dalam Gmail, Outlook, Slack atau saluran lain, dan minta orang menandatangani di sini serta menghantar salinan balik. Anda boleh menyalin mesej ringkas dengan pautan ke alat ini di bawah.", + "copyTemplate": "Salin templat mesej", + "requestTemplate": "Helo,\n\nsila semak PDF yang dilampirkan. Anda boleh menambah tandatangan menggunakan alat percuma dalam pelayar ini (alat itu sendiri tidak memuat naik apa-apa ke pelayan):\n{link}\n\nTerima kasih!", + "copiedTemplate": "Mesej disalin ke papan keratan.", + "copyFailed": "Tidak dapat menyalin. Pilih dan salin teks secara manual.", + }, + "ar": { + "navigation_label": "توقيع PDF", + "dashboard_title": "توقيع PDF", + "dashboard_description": "وقّع ملفات PDF في المتصفح (رسم أو نص أو صورة) أو شارك قالبًا لجمع التوقيعات.", + "intro": "أداتك لتوقيع المستندات إلكترونيًا. وقّع بنفسك أو أرسل طلب توقيع إلى آخرين.", + "privacyNote": "ملفك لا يغادر هذا المتصفح. لا نخزّن ملفات PDF ولا نرسل بريدًا نيابةً عنك.", + "mainTabSign": "توقيع بنفسي", + "mainTabRequest": "طلب من آخرين", + "tabDraw": "رسم", + "tabType": "كتابة", + "tabUpload": "صورة", + "drawLabel": "ارسم توقيعك", + "clearDrawing": "مسح اللوحة", + "typeLabel": "اسمك", + "typePlaceholder": "مثال: أحمد محمد", + "chooseImage": "اختر صورة التوقيع", + "widthLabel": "عرض التوقيع ({pct}% من الصفحة)", + "pagesAll": "كل الصفحات", + "pagesFrom": "الصفحة الأولى", + "pagesTo": "الصفحة الأخيرة", + "download": "تطبيق التوقيع وتنزيل", + "done": "تم تنزيل PDF موقّع.", + "signFailed": "تعذّر توقيع هذا PDF. جرّب ملفًا آخر.", + "needDrawing": "ارسم توقيعك على اللوحة أولًا.", + "emptyTypeName": "أدخل اسمك للتوقيع النصي.", + "needUpload": "اختر صورة توقيع بصيغة PNG أو JPEG.", + "requestTitle": "جمع توقيعات من الآخرين", + "requestBody": "لا يرفع MyDevTools ملف PDF الخاص بك ولا يرسل بريدًا نيابةً عنك. نزّل الملف وأرفقه في Gmail أو Outlook أو Slack أو أي قناة تستخدمها، واطلب من الأشخاص التوقيع هنا وإعادة نسخة. يمكنك نسخ رسالة قصيرة تتضمن رابطًا لهذه الأداة أدناه.", + "copyTemplate": "نسخ قالب الرسالة", + "requestTemplate": "مرحبًا،\n\nيرجى مراجعة ملف PDF المرفق. يمكنك إضافة توقيع باستخدام هذه الأداة المجانية في المتصفح (الأداة نفسها لا ترفع أي شيء إلى خادم):\n{link}\n\nشكرًا لك!", + "copiedTemplate": "تم نسخ الرسالة إلى الحافظة.", + "copyFailed": "تعذّر النسخ. حدّد النص وانسخه يدويًا.", + }, + "fa": { + "navigation_label": "امضای PDF", + "dashboard_title": "امضای PDF", + "dashboard_description": "امضای PDF در مرورگر (رسم، تایپ یا تصویر) یا اشتراک قالب برای جمع‌آوری امضا.", + "intro": "ابزار شما برای امضای الکترونیکی. خودتان امضا کنید یا از دیگران بخواهید.", + "privacyNote": "فایل شما این مرورگر را ترک نمی‌کند. PDF ذخیره نمی‌کنیم و ایمیل جای شما نمی‌فرستیم.", + "mainTabSign": "امضای خودم", + "mainTabRequest": "درخواست از دیگران", + "tabDraw": "رسم", + "tabType": "تایپ", + "tabUpload": "تصویر", + "drawLabel": "امضای خود را بکشید", + "clearDrawing": "پاک کردن بوم", + "typeLabel": "نام شما", + "typePlaceholder": "مثال: علی رضایی", + "chooseImage": "انتخاب تصویر امضا", + "widthLabel": "عرض امضا ({pct}% صفحه)", + "pagesAll": "همه صفحات", + "pagesFrom": "صفحه اول", + "pagesTo": "صفحه آخر", + "download": "اعمال امضا و دانلود", + "done": "PDF امضاشده دانلود شد.", + "signFailed": "امضای این PDF ممکن نشد. فایل دیگری امتحان کنید.", + "needDrawing": "ابتدا روی بوم امضا بکشید.", + "emptyTypeName": "نام را برای امضای متنی وارد کنید.", + "needUpload": "تصویر امضای PNG یا JPEG انتخاب کنید.", + "requestTitle": "جمع‌آوری امضا از دیگران", + "requestBody": "MyDevTools فایل PDF شما را آپلود نمی‌کند و ایمیل جای شما نمی‌فرستد. فایل را دانلود کنید، در Gmail، Outlook، Slack یا کانال خود ضمیمه کنید و از افراد بخواهید اینجا امضا کنند و نسخه برگردانند. می‌توانید متن کوتاهی با لینک این ابزار را از پایین کپی کنید.", + "copyTemplate": "کپی قالب پیام", + "requestTemplate": "سلام،\n\nلطفاً PDF پیوست را بررسی کنید. می‌توانید با این ابزار رایگان در مرورگر امضا اضافه کنید (خود ابزار چیزی به سرور آپلود نمی‌کند):\n{link}\n\nمتشکرم!", + "copiedTemplate": "پیام در کلیپ‌بورد کپی شد.", + "copyFailed": "کپی نشد. متن را دستی انتخاب و کپی کنید.", + }, + "af": { + "navigation_label": "Teken PDF", + "dashboard_title": "Teken PDF", + "dashboard_description": "Teken PDF’s in die blaaier (teken, tik of beeld) of deel ’n sjabloon om handtekeninge in te samel.", + "intro": "Jou hulpmiddel vir elektroniese ondertekening. Teken self of vra ander om te teken.", + "privacyNote": "Jou lêer verlaat nooit hierdie blaaier nie. Ons stoor nie PDF’s nie en stuur nie e-pos namens jou nie.", + "mainTabSign": "Teken self", + "mainTabRequest": "Vra ander", + "tabDraw": "Teken", + "tabType": "Tik", + "tabUpload": "Beeld", + "drawLabel": "Teken jou handtekening", + "clearDrawing": "Vee oppervlak uit", + "typeLabel": "Jou naam", + "typePlaceholder": "bv. Jan de Vries", + "chooseImage": "Kies handtekeningbeeld", + "widthLabel": "Handtekeningwydte ({pct}% van bladsy)", + "pagesAll": "Alle bladsye", + "pagesFrom": "Eerste bladsy", + "pagesTo": "Laaste bladsy", + "download": "Pas handtekening toe en laai af", + "done": "Getekende PDF afgelaai.", + "signFailed": "Kon nie hierdie PDF teken nie. Probeer ’n ander lêer.", + "needDrawing": "Teken eers jou handtekening op die oppervlak.", + "emptyTypeName": "Voer jou naam vir die getikte handtekening in.", + "needUpload": "Kies ’n PNG- of JPEG-handtekeningbeeld.", + "requestTitle": "Vra handtekeninge van ander", + "requestBody": "MyDevTools laai nie jou PDF op nie en stuur nie e-pos vir jou nie. Laai die lêer af, heg dit in Gmail, Outlook, Slack of ’n ander kanaal aan en vra mense om hier te teken en ’n kopie terug te stuur. Jy kan ’n kort boodskap met ’n skakel na hierdie hulpmiddel hier onder kopieer.", + "copyTemplate": "Kopieer boodskapsjabloon", + "requestTemplate": "Hallo,\n\nbeskou asseblief die aangehegte PDF. Jy kan ’n handtekening byvoeg met hierdie gratis blaaier-hulpmiddel (die hulpmiddel self laai niks na ’n bediener op nie):\n{link}\n\nDankie!", + "copiedTemplate": "Boodskap na knipbord gekopieer.", + "copyFailed": "Kon nie kopieer nie. Merk en kopieer die teks handmatig.", + }, +} + + +def build_pdf_sign(locale: str, wm: dict, wm_en: dict[str, str]) -> dict[str, str]: + tr = T[locale] + out: dict[str, str] = {} + for k in WM_COPY_KEYS: + out[k] = wm.get(k) or wm_en.get(k) or "" + + out.update( + { + "intro": tr["intro"], + "privacyNote": tr["privacyNote"], + "mainTabSign": tr["mainTabSign"], + "mainTabRequest": tr["mainTabRequest"], + "tabDraw": tr["tabDraw"], + "tabType": tr["tabType"], + "tabUpload": tr["tabUpload"], + "drawLabel": tr["drawLabel"], + "clearDrawing": tr["clearDrawing"], + "typeLabel": tr["typeLabel"], + "typePlaceholder": tr["typePlaceholder"], + "chooseImage": tr["chooseImage"], + "widthLabel": tr["widthLabel"], + "pagesAll": tr["pagesAll"], + "pagesFrom": tr["pagesFrom"], + "pagesTo": tr["pagesTo"], + "download": tr["download"], + "done": tr["done"], + "signFailed": tr["signFailed"], + "needDrawing": tr["needDrawing"], + "emptyTypeName": tr["emptyTypeName"], + "needUpload": tr["needUpload"], + "requestTitle": tr["requestTitle"], + "requestBody": tr["requestBody"], + "copyTemplate": tr["copyTemplate"], + "requestTemplate": tr["requestTemplate"], + "copiedTemplate": tr["copiedTemplate"], + "copyFailed": tr["copyFailed"], + } + ) + return out + + +def main() -> None: + locales = sorted(p.name.removesuffix(".json") for p in MESSAGES_DIR.glob("*.json")) + if len(locales) != 27: + raise SystemExit(f"expected 27 locale files, found {len(locales)}: {locales}") + + en_path = MESSAGES_DIR / "en.json" + with en_path.open(encoding="utf-8") as f: + wm_en: dict[str, str] = json.load(f).get("PdfWatermark") or {} + + for loc in locales: + path = MESSAGES_DIR / f"{loc}.json" + with path.open(encoding="utf-8") as f: + data = json.load(f) + + if loc not in T: + raise SystemExit(f"missing translations for locale: {loc}") + + wm = data.get("PdfWatermark") or {} + data["PdfSign"] = build_pdf_sign(loc, wm, wm_en) + data.setdefault("Navigation", {})["pdfSign"] = T[loc]["navigation_label"] + data.setdefault("Dashboard", {}).setdefault("tools", {})["pdfSign"] = { + "title": T[loc]["dashboard_title"], + "description": T[loc]["dashboard_description"], + } + + with path.open("w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + f.write("\n") + + print("Updated PdfSign + Navigation/Dashboard pdfSign in", len(locales), "locales.") + + +if __name__ == "__main__": + main() diff --git a/apps/desktop-ui/scripts/update_pdf_watermark_locales.py b/apps/desktop-ui/scripts/update_pdf_watermark_locales.py new file mode 100644 index 00000000..fa7a207a --- /dev/null +++ b/apps/desktop-ui/scripts/update_pdf_watermark_locales.py @@ -0,0 +1,1075 @@ +#!/usr/bin/env python3 +"""Apply localized Navigation, Dashboard.tools, and PdfWatermark strings for all locales.""" + +from __future__ import annotations + +import json +from pathlib import Path + +MESSAGES_DIR = Path(__file__).resolve().parent.parent / "messages" + +# Font family names stay Latin (PDF standard names) in every locale. +FONT_KEYS = ("fontHelvetica", "fontHelveticaBold", "fontTimes", "fontCourier") + +# Per locale: nav label, dashboard card, then PdfWatermark panel (except pageCount from PdfCompressor). +L: dict[str, dict[str, str]] = { + "en": { + "nav": "PDF Watermark", + "dash_title": "PDF Watermark", + "dash_desc": "Stamp text or a PNG/JPEG on every page with typography, transparency, and position — in your browser.", + "intro": "Stamp an image or text over your PDF in seconds. Choose the typography, transparency and position.", + "privacyNote": "Your file never leaves this browser.", + "choosePdf": "Choose PDF", + "reset": "Another file", + "tabText": "Text", + "tabImage": "Image", + "textLabel": "Watermark text", + "textPlaceholder": "e.g. DRAFT", + "fontLabel": "Font", + "sizeLabel": "Text size", + "rotationLabel": "Rotation", + "colorLabel": "Color", + "chooseImage": "Choose image", + "imageHint": "PNG or JPEG. Transparency in PNGs is preserved.", + "imageWidthLabel": "Width (relative to page)", + "positionLabel": "Position", + "posCenter": "Center", + "posTl": "Top left", + "posTr": "Top right", + "posBl": "Bottom left", + "posBr": "Bottom right", + "opacityLabel": "Opacity", + "download": "Apply watermark & download", + "working": "Working…", + "done": "Watermarked PDF downloaded.", + "invalidPdf": "Could not read this file as a PDF.", + "encryptedPdf": "This PDF is password-protected. Unlock it with PDF Unlocker, then try again.", + "stampFailed": "Could not watermark this PDF. Try a different file.", + "invalidImage": "Use a PNG or JPEG image.", + "emptyText": "Enter some watermark text.", + "needImage": "Choose a PNG or JPEG watermark image.", + }, + "de": { + "nav": "PDF-Wasserzeichen", + "dash_title": "PDF-Wasserzeichen", + "dash_desc": "Text oder PNG/JPEG auf jeder Seite platzieren – Schrift, Transparenz und Position einstellbar, alles im Browser.", + "intro": "Bringen Sie in Sekunden ein Bild oder einen Text als Wasserzeichen auf Ihr PDF. Wählen Sie Typografie, Transparenz und Position.", + "privacyNote": "Ihre Datei verlässt diesen Browser nicht.", + "choosePdf": "PDF auswählen", + "reset": "Andere Datei", + "tabText": "Text", + "tabImage": "Bild", + "textLabel": "Wasserzeichentext", + "textPlaceholder": "z. B. ENTWURF", + "fontLabel": "Schriftart", + "sizeLabel": "Schriftgröße", + "rotationLabel": "Drehung", + "colorLabel": "Farbe", + "chooseImage": "Bild auswählen", + "imageHint": "PNG oder JPEG. Transparenz in PNGs bleibt erhalten.", + "imageWidthLabel": "Breite (relativ zur Seite)", + "positionLabel": "Position", + "posCenter": "Mitte", + "posTl": "Oben links", + "posTr": "Oben rechts", + "posBl": "Unten links", + "posBr": "Unten rechts", + "opacityLabel": "Deckkraft", + "download": "Wasserzeichen anwenden & herunterladen", + "working": "Wird verarbeitet…", + "done": "Wasserzeichen-PDF wurde heruntergeladen.", + "invalidPdf": "Diese Datei konnte nicht als PDF gelesen werden.", + "encryptedPdf": "Dieses PDF ist kennwortgeschützt. Entsperren Sie es zuerst mit „PDF entsperren“, und versuchen Sie es erneut.", + "stampFailed": "Dieses PDF konnte nicht mit einem Wasserzeichen versehen werden. Versuchen Sie eine andere Datei.", + "invalidImage": "Verwenden Sie ein PNG- oder JPEG-Bild.", + "emptyText": "Geben Sie einen Wasserzeichentext ein.", + "needImage": "Wählen Sie ein Wasserzeichenbild (PNG oder JPEG).", + }, + "fr": { + "nav": "Filigrane PDF", + "dash_title": "Filigrane PDF", + "dash_desc": "Texte ou image PNG/JPEG sur chaque page : police, transparence et position — le tout dans le navigateur.", + "intro": "Appliquez une image ou un texte sur votre PDF en quelques secondes. Choisissez la typographie, la transparence et la position.", + "privacyNote": "Votre fichier ne quitte jamais ce navigateur.", + "choosePdf": "Choisir un PDF", + "reset": "Autre fichier", + "tabText": "Texte", + "tabImage": "Image", + "textLabel": "Texte du filigrane", + "textPlaceholder": "ex. BROUILLON", + "fontLabel": "Police", + "sizeLabel": "Taille du texte", + "rotationLabel": "Rotation", + "colorLabel": "Couleur", + "chooseImage": "Choisir une image", + "imageHint": "PNG ou JPEG. La transparence des PNG est conservée.", + "imageWidthLabel": "Largeur (par rapport à la page)", + "positionLabel": "Position", + "posCenter": "Centre", + "posTl": "Haut gauche", + "posTr": "Haut droite", + "posBl": "Bas gauche", + "posBr": "Bas droite", + "opacityLabel": "Opacité", + "download": "Appliquer le filigrane et télécharger", + "working": "Traitement…", + "done": "PDF filigrané téléchargé.", + "invalidPdf": "Impossible de lire ce fichier comme PDF.", + "encryptedPdf": "Ce PDF est protégé par mot de passe. Déverrouillez-le avec Déverrouiller PDF, puis réessayez.", + "stampFailed": "Impossible d'ajouter un filigrane à ce PDF. Essayez un autre fichier.", + "invalidImage": "Utilisez une image PNG ou JPEG.", + "emptyText": "Saisissez du texte de filigrane.", + "needImage": "Choisissez une image de filigrane PNG ou JPEG.", + }, + "es": { + "nav": "Marca de agua PDF", + "dash_title": "Marca de agua PDF", + "dash_desc": "Añada texto o PNG/JPEG en cada página con tipografía, transparencia y posición — todo en el navegador.", + "intro": "Superponga una imagen o texto en su PDF en segundos. Elija la tipografía, la transparencia y la posición.", + "privacyNote": "Su archivo nunca sale de este navegador.", + "choosePdf": "Elegir PDF", + "reset": "Otro archivo", + "tabText": "Texto", + "tabImage": "Imagen", + "textLabel": "Texto de marca de agua", + "textPlaceholder": "p. ej., BORRADOR", + "fontLabel": "Fuente", + "sizeLabel": "Tamaño del texto", + "rotationLabel": "Rotación", + "colorLabel": "Color", + "chooseImage": "Elegir imagen", + "imageHint": "PNG o JPEG. Se conserva la transparencia de los PNG.", + "imageWidthLabel": "Ancho (relativo a la página)", + "positionLabel": "Posición", + "posCenter": "Centro", + "posTl": "Arriba a la izquierda", + "posTr": "Arriba a la derecha", + "posBl": "Abajo a la izquierda", + "posBr": "Abajo a la derecha", + "opacityLabel": "Opacidad", + "download": "Aplicar marca de agua y descargar", + "working": "Trabajando…", + "done": "PDF con marca de agua descargado.", + "invalidPdf": "No se pudo leer este archivo como PDF.", + "encryptedPdf": "Este PDF está protegido con contraseña. Desbloquéelo con Desbloquear PDF e inténtelo de nuevo.", + "stampFailed": "No se pudo añadir la marca de agua a este PDF. Pruebe con otro archivo.", + "invalidImage": "Use una imagen PNG o JPEG.", + "emptyText": "Introduzca texto para la marca de agua.", + "needImage": "Elija una imagen PNG o JPEG para la marca de agua.", + }, + "it": { + "nav": "Filigrana PDF", + "dash_title": "Filigrana PDF", + "dash_desc": "Aggiungi testo o PNG/JPEG su ogni pagina con tipografia, trasparenza e posizione — tutto nel browser.", + "intro": "Applica un'immagine o un testo al PDF in pochi secondi. Scegli tipografia, trasparenza e posizione.", + "privacyNote": "Il file non esce mai da questo browser.", + "choosePdf": "Scegli PDF", + "reset": "Altro file", + "tabText": "Testo", + "tabImage": "Immagine", + "textLabel": "Testo filigrana", + "textPlaceholder": "es. BOZZA", + "fontLabel": "Carattere", + "sizeLabel": "Dimensione testo", + "rotationLabel": "Rotazione", + "colorLabel": "Colore", + "chooseImage": "Scegli immagine", + "imageHint": "PNG o JPEG. La trasparenza dei PNG viene mantenuta.", + "imageWidthLabel": "Larghezza (rispetto alla pagina)", + "positionLabel": "Posizione", + "posCenter": "Centro", + "posTl": "In alto a sinistra", + "posTr": "In alto a destra", + "posBl": "In basso a sinistra", + "posBr": "In basso a destra", + "opacityLabel": "Opacità", + "download": "Applica filigrana e scarica", + "working": "Elaborazione…", + "done": "PDF con filigrana scaricato.", + "invalidPdf": "Impossibile leggere questo file come PDF.", + "encryptedPdf": "Questo PDF è protetto da password. Sbloccalo con Sblocca PDF e riprova.", + "stampFailed": "Impossibile applicare la filigrana a questo PDF. Prova un altro file.", + "invalidImage": "Usa un'immagine PNG o JPEG.", + "emptyText": "Inserisci il testo della filigrana.", + "needImage": "Scegli un'immagine filigrana PNG o JPEG.", + }, + "pt": { + "nav": "Marca d'água PDF", + "dash_title": "Marca d'água PDF", + "dash_desc": "Adicione texto ou PNG/JPEG em cada página com tipografia, transparência e posição — tudo no navegador.", + "intro": "Aplique uma imagem ou texto ao seu PDF em segundos. Escolha a tipografia, a transparência e a posição.", + "privacyNote": "O seu ficheiro não sai deste navegador.", + "choosePdf": "Escolher PDF", + "reset": "Outro ficheiro", + "tabText": "Texto", + "tabImage": "Imagem", + "textLabel": "Texto da marca d'água", + "textPlaceholder": "ex.: RASCUNHO", + "fontLabel": "Tipo de letra", + "sizeLabel": "Tamanho do texto", + "rotationLabel": "Rotação", + "colorLabel": "Cor", + "chooseImage": "Escolher imagem", + "imageHint": "PNG ou JPEG. A transparência dos PNG é preservada.", + "imageWidthLabel": "Largura (relativa à página)", + "positionLabel": "Posição", + "posCenter": "Centro", + "posTl": "Canto superior esquerdo", + "posTr": "Canto superior direito", + "posBl": "Canto inferior esquerdo", + "posBr": "Canto inferior direito", + "opacityLabel": "Opacidade", + "download": "Aplicar marca d'água e transferir", + "working": "A processar…", + "done": "PDF com marca d'água transferido.", + "invalidPdf": "Não foi possível ler este ficheiro como PDF.", + "encryptedPdf": "Este PDF está protegido por palavra-passe. Desbloqueie-o com Desbloquear PDF e tente novamente.", + "stampFailed": "Não foi possível aplicar marca d'água a este PDF. Tente outro ficheiro.", + "invalidImage": "Utilize uma imagem PNG ou JPEG.", + "emptyText": "Introduza o texto da marca d'água.", + "needImage": "Escolha uma imagem PNG ou JPEG para a marca d'água.", + }, + "pt-BR": { + "nav": "Marca d'água em PDF", + "dash_title": "Marca d'água em PDF", + "dash_desc": "Adicione texto ou PNG/JPEG em cada página com tipografia, transparência e posição — tudo no navegador.", + "intro": "Aplique uma imagem ou texto ao seu PDF em segundos. Escolha tipografia, transparência e posição.", + "privacyNote": "Seu arquivo não sai deste navegador.", + "choosePdf": "Escolher PDF", + "reset": "Outro arquivo", + "tabText": "Texto", + "tabImage": "Imagem", + "textLabel": "Texto da marca d'água", + "textPlaceholder": "ex.: RASCUNHO", + "fontLabel": "Fonte", + "sizeLabel": "Tamanho do texto", + "rotationLabel": "Rotação", + "colorLabel": "Cor", + "chooseImage": "Escolher imagem", + "imageHint": "PNG ou JPEG. A transparência dos PNG é preservada.", + "imageWidthLabel": "Largura (em relação à página)", + "positionLabel": "Posição", + "posCenter": "Centro", + "posTl": "Superior esquerdo", + "posTr": "Superior direito", + "posBl": "Inferior esquerdo", + "posBr": "Inferior direito", + "opacityLabel": "Opacidade", + "download": "Aplicar marca d'água e baixar", + "working": "Processando…", + "done": "PDF com marca d'água baixado.", + "invalidPdf": "Não foi possível ler este arquivo como PDF.", + "encryptedPdf": "Este PDF está protegido por senha. Desbloqueie com Desbloquear PDF e tente de novo.", + "stampFailed": "Não foi possível aplicar marca d'água a este PDF. Tente outro arquivo.", + "invalidImage": "Use uma imagem PNG ou JPEG.", + "emptyText": "Digite o texto da marca d'água.", + "needImage": "Escolha uma imagem PNG ou JPEG para a marca d'água.", + }, + "nl": { + "nav": "PDF-watermerk", + "dash_title": "PDF-watermerk", + "dash_desc": "Tekst of PNG/JPEG op elke pagina met lettertype, transparantie en positie — alles in de browser.", + "intro": "Plak in seconden een afbeelding of tekst op uw PDF. Kies typografie, transparantie en positie.", + "privacyNote": "Uw bestand verlaat deze browser niet.", + "choosePdf": "PDF kiezen", + "reset": "Ander bestand", + "tabText": "Tekst", + "tabImage": "Afbeelding", + "textLabel": "Watermerktekst", + "textPlaceholder": "bijv. CONCEPT", + "fontLabel": "Lettertype", + "sizeLabel": "Tekstgrootte", + "rotationLabel": "Rotatie", + "colorLabel": "Kleur", + "chooseImage": "Afbeelding kiezen", + "imageHint": "PNG of JPEG. Transparantie in PNG's blijft behouden.", + "imageWidthLabel": "Breedte (t.o.v. de pagina)", + "positionLabel": "Positie", + "posCenter": "Midden", + "posTl": "Linksboven", + "posTr": "Rechtsboven", + "posBl": "Linksonder", + "posBr": "Rechtsonder", + "opacityLabel": "Dekking", + "download": "Watermerk toepassen & downloaden", + "working": "Bezig…", + "done": "PDF met watermerk gedownload.", + "invalidPdf": "Dit bestand kon niet als PDF worden gelezen.", + "encryptedPdf": "Deze PDF is met een wachtwoord beveiligd. Ontgrendel eerst met PDF ontgrendelen en probeer opnieuw.", + "stampFailed": "Kon geen watermerk op deze PDF plaatsen. Probeer een ander bestand.", + "invalidImage": "Gebruik een PNG- of JPEG-afbeelding.", + "emptyText": "Voer watermerktekst in.", + "needImage": "Kies een PNG- of JPEG-afbeelding voor het watermerk.", + }, + "sv": { + "nav": "PDF-vattenstämpel", + "dash_title": "PDF-vattenstämpel", + "dash_desc": "Lägg text eller PNG/JPEG på varje sida med typografi, transparens och position — allt i webbläsaren.", + "intro": "Stämpla en bild eller text på din PDF på några sekunder. Välj typografi, transparens och position.", + "privacyNote": "Din fil lämnar aldrig den här webbläsaren.", + "choosePdf": "Välj PDF", + "reset": "Annan fil", + "tabText": "Text", + "tabImage": "Bild", + "textLabel": "Vattenstämpeltext", + "textPlaceholder": "t.ex. UTKAST", + "fontLabel": "Teckensnitt", + "sizeLabel": "Textstorlek", + "rotationLabel": "Rotation", + "colorLabel": "Färg", + "chooseImage": "Välj bild", + "imageHint": "PNG eller JPEG. Transparens i PNG behålls.", + "imageWidthLabel": "Bredd (i förhållande till sidan)", + "positionLabel": "Position", + "posCenter": "Mitten", + "posTl": "Övre vänster", + "posTr": "Övre höger", + "posBl": "Nedre vänster", + "posBr": "Nedre höger", + "opacityLabel": "Opacitet", + "download": "Använd vattenstämpel och ladda ner", + "working": "Arbetar…", + "done": "PDF med vattenstämpel nedladdad.", + "invalidPdf": "Kunde inte läsa den här filen som en PDF.", + "encryptedPdf": "Den här PDF:en är lösenordsskyddad. Lås upp den med PDF-upplåsare och försök igen.", + "stampFailed": "Kunde inte lägga vattenstämpel på den här PDF:en. Prova en annan fil.", + "invalidImage": "Använd en PNG- eller JPEG-bild.", + "emptyText": "Ange text för vattenstämpeln.", + "needImage": "Välj en PNG- eller JPEG-bild som vattenstämpel.", + }, + "da": { + "nav": "PDF-vandmærke", + "dash_title": "PDF-vandmærke", + "dash_desc": "Tilføj tekst eller PNG/JPEG på hver side med typografi, gennemsigtighed og placering — alt i browseren.", + "intro": "Stempel et billede eller tekst på din PDF på få sekunder. Vælg typografi, gennemsigtighed og placering.", + "privacyNote": "Din fil forlader aldrig denne browser.", + "choosePdf": "Vælg PDF", + "reset": "Anden fil", + "tabText": "Tekst", + "tabImage": "Billede", + "textLabel": "Vandmærketekst", + "textPlaceholder": "f.eks. KLADDE", + "fontLabel": "Skrifttype", + "sizeLabel": "Tekststørrelse", + "rotationLabel": "Rotation", + "colorLabel": "Farve", + "chooseImage": "Vælg billede", + "imageHint": "PNG eller JPEG. Gennemsigtighed i PNG bevares.", + "imageWidthLabel": "Bredde (i forhold til siden)", + "positionLabel": "Placering", + "posCenter": "Midt", + "posTl": "Øverst til venstre", + "posTr": "Øverst til højre", + "posBl": "Nederst til venstre", + "posBr": "Nederst til højre", + "opacityLabel": "Uigennemsigtighed", + "download": "Anvend vandmærke og download", + "working": "Arbejder…", + "done": "PDF med vandmærke downloadet.", + "invalidPdf": "Kunne ikke læse denne fil som en PDF.", + "encryptedPdf": "Denne PDF er adgangskodebeskyttet. Lås den op med PDF-oplåsning, og prøv igen.", + "stampFailed": "Kunne ikke tilføje vandmærke til denne PDF. Prøv en anden fil.", + "invalidImage": "Brug et PNG- eller JPEG-billede.", + "emptyText": "Indtast vandmærketekst.", + "needImage": "Vælg et PNG- eller JPEG-billede som vandmærke.", + }, + "nb": { + "nav": "PDF-vannmerke", + "dash_title": "PDF-vannmerke", + "dash_desc": "Legg til tekst eller PNG/JPEG på hver side med typografi, gjennomsiktighet og plassering — alt i nettleseren.", + "intro": "Stemple et bilde eller tekst på PDF-en på sekunder. Velg typografi, gjennomsiktighet og plassering.", + "privacyNote": "Filen din forlater aldri denne nettleseren.", + "choosePdf": "Velg PDF", + "reset": "Annen fil", + "tabText": "Tekst", + "tabImage": "Bilde", + "textLabel": "Vannmerketekst", + "textPlaceholder": "f.eks. UTKAST", + "fontLabel": "Skrifttype", + "sizeLabel": "Tekststørrelse", + "rotationLabel": "Rotasjon", + "colorLabel": "Farge", + "chooseImage": "Velg bilde", + "imageHint": "PNG eller JPEG. Gjennomsiktighet i PNG beholdes.", + "imageWidthLabel": "Bredde (relativt til siden)", + "positionLabel": "Plassering", + "posCenter": "Senter", + "posTl": "Øvre venstre", + "posTr": "Øvre høyre", + "posBl": "Nedre venstre", + "posBr": "Nedre høyre", + "opacityLabel": "Opasitet", + "download": "Bruk vannmerke og last ned", + "working": "Arbeider…", + "done": "PDF med vannmerke lastet ned.", + "invalidPdf": "Kunne ikke lese denne filen som en PDF.", + "encryptedPdf": "Denne PDF-en er passordbeskyttet. Lås den opp med PDF-opplåsing, og prøv igjen.", + "stampFailed": "Kunne ikke legge til vannmerke på denne PDF-en. Prøv en annen fil.", + "invalidImage": "Bruk et PNG- eller JPEG-bilde.", + "emptyText": "Skriv inn vannmerketekst.", + "needImage": "Velg et PNG- eller JPEG-bilde som vannmerke.", + }, + "pl": { + "nav": "Znak wodny PDF", + "dash_title": "Znak wodny PDF", + "dash_desc": "Dodaj tekst lub PNG/JPEG na każdej stronie: typografia, przezroczystość i położenie — wszystko w przeglądarce.", + "intro": "Nałóż obraz lub tekst na PDF w kilka sekund. Wybierz typografię, przezroczystość i położenie.", + "privacyNote": "Plik nie opuszcza tej przeglądarki.", + "choosePdf": "Wybierz PDF", + "reset": "Inny plik", + "tabText": "Tekst", + "tabImage": "Obraz", + "textLabel": "Tekst znaku wodnego", + "textPlaceholder": "np. SZKIC", + "fontLabel": "Czcionka", + "sizeLabel": "Rozmiar tekstu", + "rotationLabel": "Obrót", + "colorLabel": "Kolor", + "chooseImage": "Wybierz obraz", + "imageHint": "PNG lub JPEG. Przezroczystość PNG jest zachowana.", + "imageWidthLabel": "Szerokość (względem strony)", + "positionLabel": "Położenie", + "posCenter": "Środek", + "posTl": "Górny lewy", + "posTr": "Górny prawy", + "posBl": "Dolny lewy", + "posBr": "Dolny prawy", + "opacityLabel": "Krycie", + "download": "Zastosuj znak wodny i pobierz", + "working": "Przetwarzanie…", + "done": "Pobrano PDF ze znakiem wodnym.", + "invalidPdf": "Nie można odczytać tego pliku jako PDF.", + "encryptedPdf": "Ten PDF jest chroniony hasłem. Odblokuj go narzędziem Odblokuj PDF i spróbuj ponownie.", + "stampFailed": "Nie można dodać znaku wodnego do tego PDF. Spróbuj innego pliku.", + "invalidImage": "Użyj obrazu PNG lub JPEG.", + "emptyText": "Wpisz tekst znaku wodnego.", + "needImage": "Wybierz obraz PNG lub JPEG jako znak wodny.", + }, + "cs": { + "nav": "Vodoznak PDF", + "dash_title": "Vodoznak PDF", + "dash_desc": "Přidejte na každou stránku text nebo PNG/JPEG s písmem, průhledností a pozicí — vše v prohlížeči.", + "intro": "V několika sekundách překryjte PDF obrázkem nebo textem. Zvolte typografii, průhlednost a pozici.", + "privacyNote": "Soubor z tohoto prohlížeče neodchází.", + "choosePdf": "Vybrat PDF", + "reset": "Jiný soubor", + "tabText": "Text", + "tabImage": "Obrázek", + "textLabel": "Text vodoznaku", + "textPlaceholder": "např. KONCEPT", + "fontLabel": "Písmo", + "sizeLabel": "Velikost textu", + "rotationLabel": "Otočení", + "colorLabel": "Barva", + "chooseImage": "Vybrat obrázek", + "imageHint": "PNG nebo JPEG. Průhlednost u PNG se zachová.", + "imageWidthLabel": "Šířka (vzhledem ke stránce)", + "positionLabel": "Pozice", + "posCenter": "Střed", + "posTl": "Vlevo nahoře", + "posTr": "Vpravo nahoře", + "posBl": "Vlevo dole", + "posBr": "Vpravo dole", + "opacityLabel": "Neprůhlednost", + "download": "Použít vodoznak a stáhnout", + "working": "Zpracování…", + "done": "PDF s vodoznakem staženo.", + "invalidPdf": "Tento soubor nelze přečíst jako PDF.", + "encryptedPdf": "Tento PDF je chráněn heslem. Nejprve jej odemkněte pomocí Odemknout PDF.", + "stampFailed": "Na tento PDF nelze vodoznak aplikovat. Zkuste jiný soubor.", + "invalidImage": "Použijte obrázek PNG nebo JPEG.", + "emptyText": "Zadejte text vodoznaku.", + "needImage": "Vyberte obrázek PNG nebo JPEG jako vodoznak.", + }, + "ru": { + "nav": "Водяной знак PDF", + "dash_title": "Водяной знак PDF", + "dash_desc": "Текст или PNG/JPEG на каждой странице: шрифт, прозрачность и положение — всё в браузере.", + "intro": "Наложите изображение или текст на PDF за секунды. Выберите типографику, прозрачность и положение.", + "privacyNote": "Ваш файл не покидает этот браузер.", + "choosePdf": "Выбрать PDF", + "reset": "Другой файл", + "tabText": "Текст", + "tabImage": "Изображение", + "textLabel": "Текст водяного знака", + "textPlaceholder": "например, ЧЕРНОВИК", + "fontLabel": "Шрифт", + "sizeLabel": "Размер текста", + "rotationLabel": "Поворот", + "colorLabel": "Цвет", + "chooseImage": "Выбрать изображение", + "imageHint": "PNG или JPEG. Прозрачность PNG сохраняется.", + "imageWidthLabel": "Ширина (относительно страницы)", + "positionLabel": "Положение", + "posCenter": "Центр", + "posTl": "Вверху слева", + "posTr": "Вверху справа", + "posBl": "Внизу слева", + "posBr": "Внизу справа", + "opacityLabel": "Непрозрачность", + "download": "Применить водяной знак и скачать", + "working": "Обработка…", + "done": "PDF с водяным знаком скачан.", + "invalidPdf": "Не удалось прочитать этот файл как PDF.", + "encryptedPdf": "Этот PDF защищён паролем. Сначала разблокируйте его с помощью «Разблокировка PDF».", + "stampFailed": "Не удалось добавить водяной знак к этому PDF. Попробуйте другой файл.", + "invalidImage": "Используйте изображение PNG или JPEG.", + "emptyText": "Введите текст водяного знака.", + "needImage": "Выберите изображение PNG или JPEG для водяного знака.", + }, + "uk": { + "nav": "Водяний знак PDF", + "dash_title": "Водяний знак PDF", + "dash_desc": "Текст або PNG/JPEG на кожній сторінці: шрифт, прозорість і положення — усе в браузері.", + "intro": "Накладіть зображення або текст на PDF за секунди. Оберіть типографію, прозорість і положення.", + "privacyNote": "Ваш файл не залишає цей браузер.", + "choosePdf": "Вибрати PDF", + "reset": "Інший файл", + "tabText": "Текст", + "tabImage": "Зображення", + "textLabel": "Текст водяного знака", + "textPlaceholder": "наприклад, ЧЕРНЕТКА", + "fontLabel": "Шрифт", + "sizeLabel": "Розмір тексту", + "rotationLabel": "Поворот", + "colorLabel": "Колір", + "chooseImage": "Вибрати зображення", + "imageHint": "PNG або JPEG. Прозорість PNG зберігається.", + "imageWidthLabel": "Ширина (відносно сторінки)", + "positionLabel": "Положення", + "posCenter": "Центр", + "posTl": "Зверху зліва", + "posTr": "Зверху справа", + "posBl": "Знизу зліва", + "posBr": "Знизу справа", + "opacityLabel": "Непрозорість", + "download": "Застосувати водяний знак і завантажити", + "working": "Обробка…", + "done": "PDF із водяним знаком завантажено.", + "invalidPdf": "Не вдалося прочитати цей файл як PDF.", + "encryptedPdf": "Цей PDF захищено паролем. Спочатку розблокуйте його за допомогою «Розблокування PDF».", + "stampFailed": "Не вдалося додати водяний знак до цього PDF. Спробуйте інший файл.", + "invalidImage": "Використайте зображення PNG або JPEG.", + "emptyText": "Введіть текст водяного знака.", + "needImage": "Оберіть зображення PNG або JPEG для водяного знака.", + }, + "el": { + "nav": "Υδατογράφημα PDF", + "dash_title": "Υδατογράφημα PDF", + "dash_desc": "Προσθέστε κείμενο ή PNG/JPEG σε κάθε σελίδα με τυπογραφία, διαφάνεια και θέση — όλα στον browser.", + "intro": "Επισυνάψτε εικόνα ή κείμενο στο PDF σε δευτερόλεπτα. Επιλέξτε τυπογραφία, διαφάνεια και θέση.", + "privacyNote": "Το αρχείο σας δεν φεύγει ποτέ από αυτόν τον browser.", + "choosePdf": "Επιλογή PDF", + "reset": "Άλλο αρχείο", + "tabText": "Κείμενο", + "tabImage": "Εικόνα", + "textLabel": "Κείμενο υδατογραφήματος", + "textPlaceholder": "π.χ. ΠΡΟΣΧΕΔΙΟ", + "fontLabel": "Γραμματοσειρά", + "sizeLabel": "Μέγεθος κειμένου", + "rotationLabel": "Περιστροφή", + "colorLabel": "Χρώμα", + "chooseImage": "Επιλογή εικόνας", + "imageHint": "PNG ή JPEG. Η διαφάνεια στα PNG διατηρείται.", + "imageWidthLabel": "Πλάτος (σχετικά με τη σελίδα)", + "positionLabel": "Θέση", + "posCenter": "Κέντρο", + "posTl": "Πάνω αριστερά", + "posTr": "Πάνω δεξιά", + "posBl": "Κάτω αριστερά", + "posBr": "Κάτω δεξιά", + "opacityLabel": "Αδιαφάνεια", + "download": "Εφαρμογή υδατογραφήματος & λήψη", + "working": "Επεξεργασία…", + "done": "Λήφθηκε PDF με υδατογράφημα.", + "invalidPdf": "Δεν ήταν δυνατή η ανάγνωση αυτού του αρχείου ως PDF.", + "encryptedPdf": "Αυτό το PDF είναι προστατευμένο με κωδικό. Ξεκλειδώστε το με το Ξεκλείδωμα PDF και δοκιμάστε ξανά.", + "stampFailed": "Δεν ήταν δυνατή η προσθήκη υδατογραφήματος σε αυτό το PDF. Δοκιμάστε άλλο αρχείο.", + "invalidImage": "Χρησιμοποιήστε εικόνα PNG ή JPEG.", + "emptyText": "Εισαγάγετε κείμενο υδατογραφήματος.", + "needImage": "Επιλέξτε εικόνα PNG ή JPEG για υδατογράφημα.", + }, + "tr": { + "nav": "PDF filigranı", + "dash_title": "PDF filigranı", + "dash_desc": "Her sayfaya tipografi, şeffaflık ve konumla metin veya PNG/JPEG ekleyin — hepsi tarayıcıda.", + "intro": "PDF'nize saniyeler içinde görüntü veya metin damgalayın. Tipografi, şeffaflık ve konumu seçin.", + "privacyNote": "Dosyanız bu tarayıcıdan hiç çıkmaz.", + "choosePdf": "PDF seç", + "reset": "Başka dosya", + "tabText": "Metin", + "tabImage": "Görüntü", + "textLabel": "Filigran metni", + "textPlaceholder": "ör. TASLAK", + "fontLabel": "Yazı tipi", + "sizeLabel": "Metin boyutu", + "rotationLabel": "Döndürme", + "colorLabel": "Renk", + "chooseImage": "Görüntü seç", + "imageHint": "PNG veya JPEG. PNG şeffaflığı korunur.", + "imageWidthLabel": "Genişlik (sayfaya göre)", + "positionLabel": "Konum", + "posCenter": "Orta", + "posTl": "Sol üst", + "posTr": "Sağ üst", + "posBl": "Sol alt", + "posBr": "Sağ alt", + "opacityLabel": "Opaklık", + "download": "Filigranı uygula ve indir", + "working": "İşleniyor…", + "done": "Filigranlı PDF indirildi.", + "invalidPdf": "Bu dosya PDF olarak okunamadı.", + "encryptedPdf": "Bu PDF parola korumalı. Önce PDF kilidini açın, sonra tekrar deneyin.", + "stampFailed": "Bu PDF'ye filigran eklenemedi. Başka bir dosya deneyin.", + "invalidImage": "PNG veya JPEG görüntü kullanın.", + "emptyText": "Filigran metni girin.", + "needImage": "Filigran için PNG veya JPEG seçin.", + }, + "ar": { + "nav": "علامة مائية PDF", + "dash_title": "علامة مائية PDF", + "dash_desc": "أضف نصًا أو PNG/JPEG على كل صفحة مع الخط والشفافية والموضع — كل ذلك في المتصفح.", + "intro": "ضع صورة أو نصًا فوق ملف PDF في ثوانٍ. اختر الخط والشفافية والموضع.", + "privacyNote": "ملفك لا يغادر هذا المتصفح أبدًا.", + "choosePdf": "اختر PDF", + "reset": "ملف آخر", + "tabText": "نص", + "tabImage": "صورة", + "textLabel": "نص العلامة المائية", + "textPlaceholder": "مثال: مسودة", + "fontLabel": "الخط", + "sizeLabel": "حجم النص", + "rotationLabel": "الدوران", + "colorLabel": "اللون", + "chooseImage": "اختر صورة", + "imageHint": "PNG أو JPEG. تُحفظ شفافية ملفات PNG.", + "imageWidthLabel": "العرض (نسبة إلى الصفحة)", + "positionLabel": "الموضع", + "posCenter": "الوسط", + "posTl": "أعلى اليسار", + "posTr": "أعلى اليمين", + "posBl": "أسفل اليسار", + "posBr": "أسفل اليمين", + "opacityLabel": "العتامة", + "download": "تطبيق العلامة المائية وتنزيل", + "working": "جارٍ العمل…", + "done": "تم تنزيل PDF مع علامة مائية.", + "invalidPdf": "تعذّر قراءة هذا الملف كملف PDF.", + "encryptedPdf": "هذا PDF محمي بكلمة مرور. افتح قفله باستخدام إلغاء قفل PDF، ثم أعد المحاولة.", + "stampFailed": "تعذّر وضع علامة مائية على هذا PDF. جرّب ملفًا آخر.", + "invalidImage": "استخدم صورة PNG أو JPEG.", + "emptyText": "أدخل نص العلامة المائية.", + "needImage": "اختر صورة PNG أو JPEG للعلامة المائية.", + }, + "fa": { + "nav": "واترمارک PDF", + "dash_title": "واترمارک PDF", + "dash_desc": "متن یا PNG/JPEG را روی هر صفحه با تایپوگرافی، شفافیت و موقعیت اضافه کنید — همه در مرورگر.", + "intro": "در چند ثانیه تصویر یا متن روی PDF بگذارید. تایپوگرافی، شفافیت و موقعیت را انتخاب کنید.", + "privacyNote": "فایل شما هرگز این مرورگر را ترک نمی‌کند.", + "choosePdf": "انتخاب PDF", + "reset": "فایل دیگر", + "tabText": "متن", + "tabImage": "تصویر", + "textLabel": "متن واترمارک", + "textPlaceholder": "مثال: پیش‌نویس", + "fontLabel": "فونت", + "sizeLabel": "اندازه متن", + "rotationLabel": "چرخش", + "colorLabel": "رنگ", + "chooseImage": "انتخاب تصویر", + "imageHint": "PNG یا JPEG. شفافیت PNG حفظ می‌شود.", + "imageWidthLabel": "عرض (نسبت به صفحه)", + "positionLabel": "موقعیت", + "posCenter": "مرکز", + "posTl": "بالا چپ", + "posTr": "بالا راست", + "posBl": "پایین چپ", + "posBr": "پایین راست", + "opacityLabel": "کدورت", + "download": "اعمال واترمارک و دانلود", + "working": "در حال پردازش…", + "done": "PDF واترمارک‌شده دانلود شد.", + "invalidPdf": "خواندن این فایل به‌عنوان PDF ممکن نبود.", + "encryptedPdf": "این PDF با رمز محافظت شده است. ابتدا با باز کردن قفل PDF آن را باز کنید.", + "stampFailed": "اعمال واترمارک روی این PDF ممکن نبود. فایل دیگری امتحان کنید.", + "invalidImage": "از تصویر PNG یا JPEG استفاده کنید.", + "emptyText": "متن واترمارک را وارد کنید.", + "needImage": "یک تصویر PNG یا JPEG برای واترمارک انتخاب کنید.", + }, + "id": { + "nav": "Tanda air PDF", + "dash_title": "Tanda air PDF", + "dash_desc": "Tambahkan teks atau PNG/JPEG di setiap halaman dengan tipografi, transparansi, dan posisi — semua di browser.", + "intro": "Stempel gambar atau teks ke PDF Anda dalam hitungan detik. Pilih tipografi, transparansi, dan posisi.", + "privacyNote": "Berkas Anda tidak pernah meninggalkan browser ini.", + "choosePdf": "Pilih PDF", + "reset": "Berkas lain", + "tabText": "Teks", + "tabImage": "Gambar", + "textLabel": "Teks tanda air", + "textPlaceholder": "mis. DRAF", + "fontLabel": "Font", + "sizeLabel": "Ukuran teks", + "rotationLabel": "Rotasi", + "colorLabel": "Warna", + "chooseImage": "Pilih gambar", + "imageHint": "PNG atau JPEG. Transparansi PNG dipertahankan.", + "imageWidthLabel": "Lebar (relatif ke halaman)", + "positionLabel": "Posisi", + "posCenter": "Tengah", + "posTl": "Kiri atas", + "posTr": "Kanan atas", + "posBl": "Kiri bawah", + "posBr": "Kanan bawah", + "opacityLabel": "Opasitas", + "download": "Terapkan tanda air & unduh", + "working": "Memproses…", + "done": "PDF bertanda air diunduh.", + "invalidPdf": "Tidak dapat membaca berkas ini sebagai PDF.", + "encryptedPdf": "PDF ini dilindungi kata sandi. Buka kuncinya dengan Buka Kunci PDF, lalu coba lagi.", + "stampFailed": "Tidak dapat menambahkan tanda air ke PDF ini. Coba berkas lain.", + "invalidImage": "Gunakan gambar PNG atau JPEG.", + "emptyText": "Masukkan teks tanda air.", + "needImage": "Pilih gambar PNG atau JPEG untuk tanda air.", + }, + "ms": { + "nav": "Tera air PDF", + "dash_title": "Tera air PDF", + "dash_desc": "Tambah teks atau PNG/JPEG pada setiap halaman dengan tipografi, kelegapan dan kedudukan — semuanya dalam pelayar.", + "intro": "Setem gambar atau teks pada PDF anda dalam beberapa saat. Pilih tipografi, kelegapan dan kedudukan.", + "privacyNote": "Fail anda tidak pernah meninggalkan pelayar ini.", + "choosePdf": "Pilih PDF", + "reset": "Fail lain", + "tabText": "Teks", + "tabImage": "Imej", + "textLabel": "Teks tera air", + "textPlaceholder": "cth. DRAF", + "fontLabel": "Fon", + "sizeLabel": "Saiz teks", + "rotationLabel": "Putaran", + "colorLabel": "Warna", + "chooseImage": "Pilih imej", + "imageHint": "PNG atau JPEG. Kelegapan PNG dikekalkan.", + "imageWidthLabel": "Lebar (relatif kepada halaman)", + "positionLabel": "Kedudukan", + "posCenter": "Tengah", + "posTl": "Kiri atas", + "posTr": "Kanan atas", + "posBl": "Kiri bawah", + "posBr": "Kanan bawah", + "opacityLabel": "Kelegapan", + "download": "Gunakan tera air & muat turun", + "working": "Memproses…", + "done": "PDF bertanda air dimuat turun.", + "invalidPdf": "Tidak dapat membaca fail ini sebagai PDF.", + "encryptedPdf": "PDF ini dilindungi kata laluan. Buka kuncinya dengan Buka Kunci PDF, kemudian cuba lagi.", + "stampFailed": "Tidak dapat menambah tera air pada PDF ini. Cuba fail lain.", + "invalidImage": "Gunakan imej PNG atau JPEG.", + "emptyText": "Masukkan teks tera air.", + "needImage": "Pilih imej PNG atau JPEG untuk tera air.", + }, + "vi": { + "nav": "Hình mờ PDF", + "dash_title": "Hình mờ PDF", + "dash_desc": "Thêm chữ hoặc PNG/JPEG vào mỗi trang với kiểu chữ, độ trong suốt và vị trí — tất cả trên trình duyệt.", + "intro": "Đóng dấu ảnh hoặc chữ lên PDF trong vài giây. Chọn kiểu chữ, độ trong suốt và vị trí.", + "privacyNote": "Tệp của bạn không bao giờ rời khỏi trình duyệt này.", + "choosePdf": "Chọn PDF", + "reset": "Tệp khác", + "tabText": "Chữ", + "tabImage": "Ảnh", + "textLabel": "Chữ hình mờ", + "textPlaceholder": "vd: BẢN NHÁP", + "fontLabel": "Phông chữ", + "sizeLabel": "Cỡ chữ", + "rotationLabel": "Xoay", + "colorLabel": "Màu", + "chooseImage": "Chọn ảnh", + "imageHint": "PNG hoặc JPEG. Độ trong suốt của PNG được giữ.", + "imageWidthLabel": "Chiều rộng (so với trang)", + "positionLabel": "Vị trí", + "posCenter": "Giữa", + "posTl": "Trên trái", + "posTr": "Trên phải", + "posBl": "Dưới trái", + "posBr": "Dưới phải", + "opacityLabel": "Độ mờ", + "download": "Áp dụng hình mờ & tải xuống", + "working": "Đang xử lý…", + "done": "Đã tải PDF có hình mờ.", + "invalidPdf": "Không đọc được tệp này dưới dạng PDF.", + "encryptedPdf": "PDF này được bảo vệ bằng mật khẩu. Mở khóa bằng Mở khóa PDF rồi thử lại.", + "stampFailed": "Không thể thêm hình mờ vào PDF này. Hãy thử tệp khác.", + "invalidImage": "Dùng ảnh PNG hoặc JPEG.", + "emptyText": "Nhập chữ hình mờ.", + "needImage": "Chọn ảnh PNG hoặc JPEG làm hình mờ.", + }, + "zh": { + "nav": "PDF 水印", + "dash_title": "PDF 水印", + "dash_desc": "在每一页添加文字或 PNG/JPEG,可设置字体、透明度和位置 — 全部在浏览器中完成。", + "intro": "几秒内为 PDF 叠加图片或文字。选择字体、透明度和位置。", + "privacyNote": "您的文件不会离开本浏览器。", + "choosePdf": "选择 PDF", + "reset": "更换文件", + "tabText": "文字", + "tabImage": "图片", + "textLabel": "水印文字", + "textPlaceholder": "例如:草稿", + "fontLabel": "字体", + "sizeLabel": "文字大小", + "rotationLabel": "旋转", + "colorLabel": "颜色", + "chooseImage": "选择图片", + "imageHint": "PNG 或 JPEG。PNG 的透明度会保留。", + "imageWidthLabel": "宽度(相对页面)", + "positionLabel": "位置", + "posCenter": "居中", + "posTl": "左上", + "posTr": "右上", + "posBl": "左下", + "posBr": "右下", + "opacityLabel": "不透明度", + "download": "应用水印并下载", + "working": "处理中…", + "done": "已下载带水印的 PDF。", + "invalidPdf": "无法将此文件读取为 PDF。", + "encryptedPdf": "此 PDF 受密码保护。请先用 PDF 解锁工具解锁后再试。", + "stampFailed": "无法为此 PDF 添加水印。请尝试其他文件。", + "invalidImage": "请使用 PNG 或 JPEG 图片。", + "emptyText": "请输入水印文字。", + "needImage": "请选择 PNG 或 JPEG 作为水印图片。", + }, + "ja": { + "nav": "PDF 透かし", + "dash_title": "PDF 透かし", + "dash_desc": "各ページにテキストまたは PNG/JPEG を配置。書体・透明度・位置を調整 — すべてブラウザ内で処理。", + "intro": "数秒で PDF に画像やテキストの透かしを重ねます。書体・透明度・位置を選べます。", + "privacyNote": "ファイルはこのブラウザの外に送信されません。", + "choosePdf": "PDF を選択", + "reset": "別のファイル", + "tabText": "テキスト", + "tabImage": "画像", + "textLabel": "透かしテキスト", + "textPlaceholder": "例:ドラフト", + "fontLabel": "フォント", + "sizeLabel": "文字サイズ", + "rotationLabel": "回転", + "colorLabel": "色", + "chooseImage": "画像を選択", + "imageHint": "PNG または JPEG。PNG の透明度は保持されます。", + "imageWidthLabel": "幅(ページに対する割合)", + "positionLabel": "位置", + "posCenter": "中央", + "posTl": "左上", + "posTr": "右上", + "posBl": "左下", + "posBr": "右下", + "opacityLabel": "不透明度", + "download": "透かしを適用してダウンロード", + "working": "処理中…", + "done": "透かし入り PDF をダウンロードしました。", + "invalidPdf": "このファイルを PDF として読み取れませんでした。", + "encryptedPdf": "この PDF はパスワード保護されています。先に「PDF のロック解除」で解除してください。", + "stampFailed": "この PDF に透かしを付けられませんでした。別のファイルをお試しください。", + "invalidImage": "PNG または JPEG 画像を使用してください。", + "emptyText": "透かしテキストを入力してください。", + "needImage": "透かし用の PNG または JPEG 画像を選択してください。", + }, + "ko": { + "nav": "PDF 워터마크", + "dash_title": "PDF 워터마크", + "dash_desc": "각 페이지에 텍스트 또는 PNG/JPEG을 배치하고 글꼴, 투명도, 위치를 설정 — 모두 브라우저에서 처리합니다.", + "intro": "몇 초 만에 PDF에 이미지나 텍스트 워터마크를 넣으세요. 글꼴, 투명도, 위치를 선택할 수 있습니다.", + "privacyNote": "파일은 이 브라우저를 벗어나지 않습니다.", + "choosePdf": "PDF 선택", + "reset": "다른 파일", + "tabText": "텍스트", + "tabImage": "이미지", + "textLabel": "워터마크 텍스트", + "textPlaceholder": "예: 초안", + "fontLabel": "글꼴", + "sizeLabel": "텍스트 크기", + "rotationLabel": "회전", + "colorLabel": "색", + "chooseImage": "이미지 선택", + "imageHint": "PNG 또는 JPEG. PNG 투명도는 유지됩니다.", + "imageWidthLabel": "너비(페이지 대비)", + "positionLabel": "위치", + "posCenter": "가운데", + "posTl": "왼쪽 위", + "posTr": "오른쪽 위", + "posBl": "왼쪽 아래", + "posBr": "오른쪽 아래", + "opacityLabel": "불투명도", + "download": "워터마크 적용 후 다운로드", + "working": "처리 중…", + "done": "워터마크가 적용된 PDF를 다운로드했습니다.", + "invalidPdf": "이 파일을 PDF로 읽을 수 없습니다.", + "encryptedPdf": "이 PDF는 암호로 보호되어 있습니다. PDF 잠금 해제로 먼저 푼 뒤 다시 시도하세요.", + "stampFailed": "이 PDF에 워터마크를 넣을 수 없습니다. 다른 파일을 시도해 보세요.", + "invalidImage": "PNG 또는 JPEG 이미지를 사용하세요.", + "emptyText": "워터마크 텍스트를 입력하세요.", + "needImage": "워터마크로 사용할 PNG 또는 JPEG 이미지를 선택하세요.", + }, + "af": { + "nav": "PDF-watermerk", + "dash_title": "PDF-watermerk", + "dash_desc": "Voeg teks of PNG/JPEG op elke bladsy by met tipografie, deursigtigheid en posisie — alles in die blaaier.", + "intro": "Stempel 'n beeld of teks oor jou PDF in sekondes. Kies tipografie, deursigtigheid en posisie.", + "privacyNote": "Jou lêer verlaat nooit hierdie blaaier nie.", + "choosePdf": "Kies PDF", + "reset": "Ander lêer", + "tabText": "Teks", + "tabImage": "Beeld", + "textLabel": "Watermerkteks", + "textPlaceholder": "bv. KONSEP", + "fontLabel": "Lettertipe", + "sizeLabel": "Teksgrootte", + "rotationLabel": "Rotasie", + "colorLabel": "Kleur", + "chooseImage": "Kies beeld", + "imageHint": "PNG of JPEG. Deursigtigheid in PNG's bly behoue.", + "imageWidthLabel": "Breedte (relatief tot bladsy)", + "positionLabel": "Posisie", + "posCenter": "Middel", + "posTl": "Links bo", + "posTr": "Regs bo", + "posBl": "Links onder", + "posBr": "Regs onder", + "opacityLabel": "Ondeursigtigheid", + "download": "Pas watermerk toe & laai af", + "working": "Besig…", + "done": "PDF met watermerk afgelaai.", + "invalidPdf": "Kon nie hierdie lêer as 'n PDF lees nie.", + "encryptedPdf": "Hierdie PDF is wagwoordbeskerm. Ontsluit dit eers met PDF-ontsluiting en probeer weer.", + "stampFailed": "Kon nie watermerk op hierdie PDF plaas nie. Probeer 'n ander lêer.", + "invalidImage": "Gebruik 'n PNG- of JPEG-beeld.", + "emptyText": "Voer watermerkteks in.", + "needImage": "Kies 'n PNG- of JPEG-beeld vir die watermerk.", + }, + "ca": { + "nav": "Marca d'aigua PDF", + "dash_title": "Marca d'aigua PDF", + "dash_desc": "Afegeix text o PNG/JPEG a cada pàgina amb tipografia, transparència i posició — tot al navegador.", + "intro": "Estampa una imatge o text al PDF en segons. Trieu la tipografia, la transparència i la posició.", + "privacyNote": "El fitxer no surt mai d'aquest navegador.", + "choosePdf": "Trieu PDF", + "reset": "Un altre fitxer", + "tabText": "Text", + "tabImage": "Imatge", + "textLabel": "Text de la marca d'aigua", + "textPlaceholder": "p. ex. ESBORRANY", + "fontLabel": "Tipus de lletra", + "sizeLabel": "Mida del text", + "rotationLabel": "Rotació", + "colorLabel": "Color", + "chooseImage": "Trieu imatge", + "imageHint": "PNG o JPEG. Es conserva la transparència dels PNG.", + "imageWidthLabel": "Amplada (respecte a la pàgina)", + "positionLabel": "Posició", + "posCenter": "Centre", + "posTl": "Dalt a l'esquerra", + "posTr": "Dalt a la dreta", + "posBl": "Baix a l'esquerra", + "posBr": "Baix a la dreta", + "opacityLabel": "Opacitat", + "download": "Aplica la marca d'aigua i descarrega", + "working": "S'està processant…", + "done": "S'ha descarregat el PDF amb marca d'aigua.", + "invalidPdf": "No s'ha pogut llegir aquest fitxer com a PDF.", + "encryptedPdf": "Aquest PDF està protegit amb contrasenya. Desbloqueja'l amb Desbloquejar PDF i torna-ho a provar.", + "stampFailed": "No s'ha pogut afegir la marca d'aigua a aquest PDF. Prova un altre fitxer.", + "invalidImage": "Utilitza una imatge PNG o JPEG.", + "emptyText": "Introdueix el text de la marca d'aigua.", + "needImage": "Tria una imatge PNG o JPEG per a la marca d'aigua.", + }, +} + +FONT_VALUES = { + "fontHelvetica": "Helvetica", + "fontHelveticaBold": "Helvetica Bold", + "fontTimes": "Times Roman", + "fontCourier": "Courier", +} + + +def default_page_count() -> str: + return "{count, plural, one {# page} other {# pages}}" + + +def apply_locale(path: Path, stem: str) -> None: + with path.open(encoding="utf-8") as f: + data = json.load(f) + + tr = L[stem] + page_count = ( + data.get("PdfCompressor", {}).get("pageCount") or default_page_count() + ) + + data.setdefault("Navigation", {})["pdfWatermark"] = tr["nav"] + data.setdefault("Dashboard", {}).setdefault("tools", {})["pdfWatermark"] = { + "title": tr["dash_title"], + "description": tr["dash_desc"], + } + + panel: dict[str, str] = { + "intro": tr["intro"], + "privacyNote": tr["privacyNote"], + "choosePdf": tr["choosePdf"], + "reset": tr["reset"], + "pageCount": page_count, + "tabText": tr["tabText"], + "tabImage": tr["tabImage"], + "textLabel": tr["textLabel"], + "textPlaceholder": tr["textPlaceholder"], + "fontLabel": tr["fontLabel"], + "sizeLabel": tr["sizeLabel"], + "rotationLabel": tr["rotationLabel"], + "colorLabel": tr["colorLabel"], + "chooseImage": tr["chooseImage"], + "imageHint": tr["imageHint"], + "imageWidthLabel": tr["imageWidthLabel"], + "positionLabel": tr["positionLabel"], + "posCenter": tr["posCenter"], + "posTl": tr["posTl"], + "posTr": tr["posTr"], + "posBl": tr["posBl"], + "posBr": tr["posBr"], + "opacityLabel": tr["opacityLabel"], + "download": tr["download"], + "working": tr["working"], + "done": tr["done"], + "invalidPdf": tr["invalidPdf"], + "encryptedPdf": tr["encryptedPdf"], + "stampFailed": tr["stampFailed"], + "invalidImage": tr["invalidImage"], + "emptyText": tr["emptyText"], + "needImage": tr["needImage"], + } + for fk in FONT_KEYS: + panel[fk] = FONT_VALUES[fk] + + data["PdfWatermark"] = panel + + with path.open("w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + f.write("\n") + + +def main() -> None: + missing = [] + for path in sorted(MESSAGES_DIR.glob("*.json")): + stem = path.stem + if stem not in L: + missing.append(stem) + continue + apply_locale(path, stem) + if missing: + raise SystemExit(f"Missing translations for: {missing}") + print(f"Updated {len(L)} locale files under {MESSAGES_DIR}") + + +if __name__ == "__main__": + main() diff --git a/apps/desktop-ui/skills-lock.json b/apps/desktop-ui/skills-lock.json new file mode 100644 index 00000000..a20a4989 --- /dev/null +++ b/apps/desktop-ui/skills-lock.json @@ -0,0 +1,75 @@ +{ + "version": 1, + "skills": { + "accessibility": { + "source": "addyosmani/web-quality-skills", + "sourceType": "autoskills-registry", + "computedHash": "bffe3d08cfe92ebad63699f74ce29e35c19850ebfbf474c1463183cfe34d6a09" + }, + "composition-patterns": { + "source": "vercel-labs/agent-skills", + "sourceType": "autoskills-registry", + "computedHash": "c124390cc3441844903ada94cb5820b23d922f2d0ad34e01e029e410197c8a32" + }, + "frontend-design": { + "source": "anthropics/skills", + "sourceType": "autoskills-registry", + "computedHash": "82fb11a63fb1e35ee2469516ed02d54695f783115b1540c0e783197af4240a3a" + }, + "next-best-practices": { + "source": "vercel-labs/next-skills", + "sourceType": "autoskills-registry", + "computedHash": "22d9bba45864c4ae8acca9f6430290634343b7de91005d090e0ba201bdbbb5da" + }, + "next-cache-components": { + "source": "vercel-labs/next-skills", + "sourceType": "autoskills-registry", + "computedHash": "82a02f95879e9adf1a5a89f9ca2f51813e41ff3bab3c723cc8905f5a926bdd30" + }, + "next-upgrade": { + "source": "vercel-labs/next-skills", + "sourceType": "autoskills-registry", + "computedHash": "265833561c41038b4a626585b46186cff859b7f9b2254ae6b88d01648790a7b4" + }, + "nodejs-backend-patterns": { + "source": "wshobson/agents", + "sourceType": "autoskills-registry", + "computedHash": "710a5e6f83c46e8f6c43356df55c143a7375c4414559a581654ce51709138c55" + }, + "nodejs-best-practices": { + "source": "sickn33/antigravity-awesome-skills", + "sourceType": "autoskills-registry", + "computedHash": "9dbca8bf122ad4b6d8b6686e05e4e434e00ae1dfa5fa412566ae7ee88780a18f" + }, + "react-best-practices": { + "source": "vercel-labs/agent-skills", + "sourceType": "autoskills-registry", + "computedHash": "502eb06700e2c3cb1bd7f710860528f72281320bd5171a2087b05a090b573dc1" + }, + "seo": { + "source": "addyosmani/web-quality-skills", + "sourceType": "autoskills-registry", + "computedHash": "c184da724d1c61ad077f27418ea8e7e88fd54bcdf98165e18be7e4681cbd5e20" + }, + "shadcn": { + "source": "shadcn/ui", + "sourceType": "autoskills-registry", + "computedHash": "1b1287f853d10e344c8e7bf8f1715ec77554b083ad48fff6a4a1c844009626cd" + }, + "tailwind-css-patterns": { + "source": "giuseppe-trisciuoglio/developer-kit", + "sourceType": "autoskills-registry", + "computedHash": "8fd534b64b3f305ade80c207e2f9ba4338d5e409e355af9f724c36764b6709c1" + }, + "tailwind-v4-shadcn": { + "source": "secondsky/claude-skills", + "sourceType": "autoskills-registry", + "computedHash": "ccb851c374e9d60af52bf37d80481dbeec54c3cebf6a9dc67492ea2f8514e283" + }, + "typescript-advanced-types": { + "source": "wshobson/agents", + "sourceType": "autoskills-registry", + "computedHash": "5ca0e177c6aaaba1889255691224daafdb7d71f317cc70bede1590d3907ded42" + } + } +} diff --git a/apps/desktop-ui/src/app/activate/page.tsx b/apps/desktop-ui/src/app/activate/page.tsx new file mode 100644 index 00000000..67285d1c --- /dev/null +++ b/apps/desktop-ui/src/app/activate/page.tsx @@ -0,0 +1,163 @@ +'use client' + +import { useCallback, useEffect, useRef, useState } from 'react' +import { useRouter } from 'next/navigation' +import { useTranslations } from 'next-intl' +import { BadgeCheck, Calendar, Crown, Globe, Loader2, PartyPopper, ShieldCheck } from 'lucide-react' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { completeActivation, getActivation, type ActivationRecord } from '@/lib/desktop/activation' + +/** Signup within this window of activation counts as a brand-new subscription. */ +const FRESH_SIGNUP_WINDOW_MS = 10 * 60 * 1000 + +type Phase = 'idle' | 'waiting' | 'finishing' | 'done' | 'error' + +export default function ActivatePage() { + const t = useTranslations('Activate') + const router = useRouter() + const [phase, setPhase] = useState('idle') + const [record, setRecord] = useState(null) + const [error, setError] = useState(null) + const finishing = useRef(false) + + // Already activated (back-navigation etc.) → straight to the app. + useEffect(() => { + void getActivation().then((r) => { + if (r) router.replace('/dashboard') + }).catch(() => {}) + }, [router]) + + const finish = useCallback(async () => { + if (finishing.current) return + finishing.current = true + setPhase('finishing') + try { + setRecord(await completeActivation()) + setPhase('done') + } catch (e) { + setError(e instanceof Error ? e.message : String(e)) + setPhase('error') + } finally { + finishing.current = false + } + }, []) + + // Deep-link sign-in path (mydevtools://auth) completes outside this page's + // startCloudSignIn call — listen for it and finish the activation here. + useEffect(() => { + const onAuthed = () => void finish() + window.addEventListener('mydevtools:desktop-authed', onAuthed) + return () => window.removeEventListener('mydevtools:desktop-authed', onAuthed) + }, [finish]) + + const signIn = async () => { + setError(null) + setPhase('waiting') + try { + const { startCloudSignIn } = await import('@/lib/desktop/cloud-signin') + await startCloudSignIn() + await finish() + } catch (e) { + if (finishing.current || phase === 'done') return + setError(e instanceof Error ? e.message : String(e)) + setPhase('error') + } + } + + if (phase === 'done' && record) { + const isPro = record.plan === 'pro' + const isEarlyAdopter = record.plan_source === 'early_adopter' + const freshSignup = + record.created_at != null && + record.activated_at != null && + record.activated_at - record.created_at < FRESH_SIGNUP_WINDOW_MS + const grantedAt = record.plan_granted_at + ? new Date(record.plan_granted_at).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }) + : null + + return ( +
+ + +
+ {freshSignup && isPro + ? + : } +
+ + {freshSignup && isPro ? t('subscriptionCreated') : t('authSuccess')} + + + {record.display_name || record.email} + +
+ +
+ + + {t('plan')} + + {isPro ? ( + + {isEarlyAdopter ? t('earlyAdopter') : 'Pro'} + + ) : ( + Free + )} +
+ {isPro && ( +
+ + {t('lifetimeNote')} +
+ )} + {grantedAt && ( +
+ + {t('grantedOn')}: {grantedAt} +
+ )} +
+ + {t('offlineNote')} +
+ +
+
+
+ ) + } + + return ( +
+ + + {t('title')} + {t('subtitle')} + + + + {error &&

{error}

} +

{t('privacyNote')}

+
+
+
+ ) +} diff --git a/apps/web/src/app/app/api-client/layout.tsx b/apps/desktop-ui/src/app/app/api-client/layout.tsx similarity index 100% rename from apps/web/src/app/app/api-client/layout.tsx rename to apps/desktop-ui/src/app/app/api-client/layout.tsx diff --git a/apps/web/src/app/app/api-client/page.tsx b/apps/desktop-ui/src/app/app/api-client/page.tsx similarity index 100% rename from apps/web/src/app/app/api-client/page.tsx rename to apps/desktop-ui/src/app/app/api-client/page.tsx diff --git a/apps/web/src/app/app/api-keys/layout.tsx b/apps/desktop-ui/src/app/app/api-keys/layout.tsx similarity index 100% rename from apps/web/src/app/app/api-keys/layout.tsx rename to apps/desktop-ui/src/app/app/api-keys/layout.tsx diff --git a/apps/web/src/app/app/api-keys/page.tsx b/apps/desktop-ui/src/app/app/api-keys/page.tsx similarity index 100% rename from apps/web/src/app/app/api-keys/page.tsx rename to apps/desktop-ui/src/app/app/api-keys/page.tsx diff --git a/apps/web/src/app/app/app-content.tsx b/apps/desktop-ui/src/app/app/app-content.tsx similarity index 100% rename from apps/web/src/app/app/app-content.tsx rename to apps/desktop-ui/src/app/app/app-content.tsx diff --git a/apps/web/src/app/app/base64/layout.tsx b/apps/desktop-ui/src/app/app/base64/layout.tsx similarity index 100% rename from apps/web/src/app/app/base64/layout.tsx rename to apps/desktop-ui/src/app/app/base64/layout.tsx diff --git a/apps/web/src/app/app/base64/page.tsx b/apps/desktop-ui/src/app/app/base64/page.tsx similarity index 100% rename from apps/web/src/app/app/base64/page.tsx rename to apps/desktop-ui/src/app/app/base64/page.tsx diff --git a/apps/web/src/app/app/bcrypt-generator/layout.tsx b/apps/desktop-ui/src/app/app/bcrypt-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/bcrypt-generator/layout.tsx rename to apps/desktop-ui/src/app/app/bcrypt-generator/layout.tsx diff --git a/apps/web/src/app/app/bcrypt-generator/page.tsx b/apps/desktop-ui/src/app/app/bcrypt-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/bcrypt-generator/page.tsx rename to apps/desktop-ui/src/app/app/bcrypt-generator/page.tsx diff --git a/apps/web/src/app/app/beautify-minify/layout.tsx b/apps/desktop-ui/src/app/app/beautify-minify/layout.tsx similarity index 100% rename from apps/web/src/app/app/beautify-minify/layout.tsx rename to apps/desktop-ui/src/app/app/beautify-minify/layout.tsx diff --git a/apps/web/src/app/app/beautify-minify/page.tsx b/apps/desktop-ui/src/app/app/beautify-minify/page.tsx similarity index 100% rename from apps/web/src/app/app/beautify-minify/page.tsx rename to apps/desktop-ui/src/app/app/beautify-minify/page.tsx diff --git a/apps/web/src/app/app/bookmarks/layout.tsx b/apps/desktop-ui/src/app/app/bookmarks/layout.tsx similarity index 100% rename from apps/web/src/app/app/bookmarks/layout.tsx rename to apps/desktop-ui/src/app/app/bookmarks/layout.tsx diff --git a/apps/web/src/app/app/bookmarks/page.tsx b/apps/desktop-ui/src/app/app/bookmarks/page.tsx similarity index 100% rename from apps/web/src/app/app/bookmarks/page.tsx rename to apps/desktop-ui/src/app/app/bookmarks/page.tsx diff --git a/apps/web/src/app/app/break-room/2048/layout.tsx b/apps/desktop-ui/src/app/app/break-room/2048/layout.tsx similarity index 100% rename from apps/web/src/app/app/break-room/2048/layout.tsx rename to apps/desktop-ui/src/app/app/break-room/2048/layout.tsx diff --git a/apps/web/src/app/app/break-room/2048/page.tsx b/apps/desktop-ui/src/app/app/break-room/2048/page.tsx similarity index 100% rename from apps/web/src/app/app/break-room/2048/page.tsx rename to apps/desktop-ui/src/app/app/break-room/2048/page.tsx diff --git a/apps/web/src/app/app/break-room/minesweeper/layout.tsx b/apps/desktop-ui/src/app/app/break-room/minesweeper/layout.tsx similarity index 100% rename from apps/web/src/app/app/break-room/minesweeper/layout.tsx rename to apps/desktop-ui/src/app/app/break-room/minesweeper/layout.tsx diff --git a/apps/web/src/app/app/break-room/minesweeper/page.tsx b/apps/desktop-ui/src/app/app/break-room/minesweeper/page.tsx similarity index 100% rename from apps/web/src/app/app/break-room/minesweeper/page.tsx rename to apps/desktop-ui/src/app/app/break-room/minesweeper/page.tsx diff --git a/apps/web/src/app/app/break-room/snake/layout.tsx b/apps/desktop-ui/src/app/app/break-room/snake/layout.tsx similarity index 100% rename from apps/web/src/app/app/break-room/snake/layout.tsx rename to apps/desktop-ui/src/app/app/break-room/snake/layout.tsx diff --git a/apps/web/src/app/app/break-room/snake/page.tsx b/apps/desktop-ui/src/app/app/break-room/snake/page.tsx similarity index 100% rename from apps/web/src/app/app/break-room/snake/page.tsx rename to apps/desktop-ui/src/app/app/break-room/snake/page.tsx diff --git a/apps/web/src/app/app/break-room/sudoku/layout.tsx b/apps/desktop-ui/src/app/app/break-room/sudoku/layout.tsx similarity index 100% rename from apps/web/src/app/app/break-room/sudoku/layout.tsx rename to apps/desktop-ui/src/app/app/break-room/sudoku/layout.tsx diff --git a/apps/web/src/app/app/break-room/sudoku/page.tsx b/apps/desktop-ui/src/app/app/break-room/sudoku/page.tsx similarity index 100% rename from apps/web/src/app/app/break-room/sudoku/page.tsx rename to apps/desktop-ui/src/app/app/break-room/sudoku/page.tsx diff --git a/apps/web/src/app/app/break-room/tetris/layout.tsx b/apps/desktop-ui/src/app/app/break-room/tetris/layout.tsx similarity index 100% rename from apps/web/src/app/app/break-room/tetris/layout.tsx rename to apps/desktop-ui/src/app/app/break-room/tetris/layout.tsx diff --git a/apps/web/src/app/app/break-room/tetris/page.tsx b/apps/desktop-ui/src/app/app/break-room/tetris/page.tsx similarity index 100% rename from apps/web/src/app/app/break-room/tetris/page.tsx rename to apps/desktop-ui/src/app/app/break-room/tetris/page.tsx diff --git a/apps/web/src/app/app/certificate-pem-decoder/layout.tsx b/apps/desktop-ui/src/app/app/certificate-pem-decoder/layout.tsx similarity index 100% rename from apps/web/src/app/app/certificate-pem-decoder/layout.tsx rename to apps/desktop-ui/src/app/app/certificate-pem-decoder/layout.tsx diff --git a/apps/web/src/app/app/certificate-pem-decoder/page.tsx b/apps/desktop-ui/src/app/app/certificate-pem-decoder/page.tsx similarity index 100% rename from apps/web/src/app/app/certificate-pem-decoder/page.tsx rename to apps/desktop-ui/src/app/app/certificate-pem-decoder/page.tsx diff --git a/apps/web/src/app/app/chmod-calculator/layout.tsx b/apps/desktop-ui/src/app/app/chmod-calculator/layout.tsx similarity index 100% rename from apps/web/src/app/app/chmod-calculator/layout.tsx rename to apps/desktop-ui/src/app/app/chmod-calculator/layout.tsx diff --git a/apps/web/src/app/app/chmod-calculator/page.tsx b/apps/desktop-ui/src/app/app/chmod-calculator/page.tsx similarity index 100% rename from apps/web/src/app/app/chmod-calculator/page.tsx rename to apps/desktop-ui/src/app/app/chmod-calculator/page.tsx diff --git a/apps/web/src/app/app/code-screenshot/layout.tsx b/apps/desktop-ui/src/app/app/code-screenshot/layout.tsx similarity index 100% rename from apps/web/src/app/app/code-screenshot/layout.tsx rename to apps/desktop-ui/src/app/app/code-screenshot/layout.tsx diff --git a/apps/web/src/app/app/code-screenshot/page.tsx b/apps/desktop-ui/src/app/app/code-screenshot/page.tsx similarity index 100% rename from apps/web/src/app/app/code-screenshot/page.tsx rename to apps/desktop-ui/src/app/app/code-screenshot/page.tsx diff --git a/apps/web/src/app/app/color-picker/layout.tsx b/apps/desktop-ui/src/app/app/color-picker/layout.tsx similarity index 100% rename from apps/web/src/app/app/color-picker/layout.tsx rename to apps/desktop-ui/src/app/app/color-picker/layout.tsx diff --git a/apps/web/src/app/app/color-picker/page.tsx b/apps/desktop-ui/src/app/app/color-picker/page.tsx similarity index 100% rename from apps/web/src/app/app/color-picker/page.tsx rename to apps/desktop-ui/src/app/app/color-picker/page.tsx diff --git a/apps/web/src/app/app/contrast-checker/layout.tsx b/apps/desktop-ui/src/app/app/contrast-checker/layout.tsx similarity index 100% rename from apps/web/src/app/app/contrast-checker/layout.tsx rename to apps/desktop-ui/src/app/app/contrast-checker/layout.tsx diff --git a/apps/web/src/app/app/contrast-checker/page.tsx b/apps/desktop-ui/src/app/app/contrast-checker/page.tsx similarity index 100% rename from apps/web/src/app/app/contrast-checker/page.tsx rename to apps/desktop-ui/src/app/app/contrast-checker/page.tsx diff --git a/apps/web/src/app/app/cron-builder/layout.tsx b/apps/desktop-ui/src/app/app/cron-builder/layout.tsx similarity index 100% rename from apps/web/src/app/app/cron-builder/layout.tsx rename to apps/desktop-ui/src/app/app/cron-builder/layout.tsx diff --git a/apps/web/src/app/app/cron-builder/page.tsx b/apps/desktop-ui/src/app/app/cron-builder/page.tsx similarity index 100% rename from apps/web/src/app/app/cron-builder/page.tsx rename to apps/desktop-ui/src/app/app/cron-builder/page.tsx diff --git a/apps/web/src/app/app/css-generators/layout.tsx b/apps/desktop-ui/src/app/app/css-generators/layout.tsx similarity index 100% rename from apps/web/src/app/app/css-generators/layout.tsx rename to apps/desktop-ui/src/app/app/css-generators/layout.tsx diff --git a/apps/web/src/app/app/css-generators/page.tsx b/apps/desktop-ui/src/app/app/css-generators/page.tsx similarity index 100% rename from apps/web/src/app/app/css-generators/page.tsx rename to apps/desktop-ui/src/app/app/css-generators/page.tsx diff --git a/apps/web/src/app/app/css-gradient-builder/layout.tsx b/apps/desktop-ui/src/app/app/css-gradient-builder/layout.tsx similarity index 100% rename from apps/web/src/app/app/css-gradient-builder/layout.tsx rename to apps/desktop-ui/src/app/app/css-gradient-builder/layout.tsx diff --git a/apps/web/src/app/app/css-gradient-builder/page.tsx b/apps/desktop-ui/src/app/app/css-gradient-builder/page.tsx similarity index 100% rename from apps/web/src/app/app/css-gradient-builder/page.tsx rename to apps/desktop-ui/src/app/app/css-gradient-builder/page.tsx diff --git a/apps/web/src/app/app/csv-excel-json/layout.tsx b/apps/desktop-ui/src/app/app/csv-excel-json/layout.tsx similarity index 100% rename from apps/web/src/app/app/csv-excel-json/layout.tsx rename to apps/desktop-ui/src/app/app/csv-excel-json/layout.tsx diff --git a/apps/web/src/app/app/csv-excel-json/page.tsx b/apps/desktop-ui/src/app/app/csv-excel-json/page.tsx similarity index 100% rename from apps/web/src/app/app/csv-excel-json/page.tsx rename to apps/desktop-ui/src/app/app/csv-excel-json/page.tsx diff --git a/apps/web/src/app/app/curl-to-code/layout.tsx b/apps/desktop-ui/src/app/app/curl-to-code/layout.tsx similarity index 100% rename from apps/web/src/app/app/curl-to-code/layout.tsx rename to apps/desktop-ui/src/app/app/curl-to-code/layout.tsx diff --git a/apps/web/src/app/app/curl-to-code/page.tsx b/apps/desktop-ui/src/app/app/curl-to-code/page.tsx similarity index 100% rename from apps/web/src/app/app/curl-to-code/page.tsx rename to apps/desktop-ui/src/app/app/curl-to-code/page.tsx diff --git a/apps/web/src/app/app/database-explorer/layout.tsx b/apps/desktop-ui/src/app/app/database-explorer/layout.tsx similarity index 100% rename from apps/web/src/app/app/database-explorer/layout.tsx rename to apps/desktop-ui/src/app/app/database-explorer/layout.tsx diff --git a/apps/web/src/app/app/database-explorer/page.tsx b/apps/desktop-ui/src/app/app/database-explorer/page.tsx similarity index 100% rename from apps/web/src/app/app/database-explorer/page.tsx rename to apps/desktop-ui/src/app/app/database-explorer/page.tsx diff --git a/apps/web/src/app/app/diff-checker/layout.tsx b/apps/desktop-ui/src/app/app/diff-checker/layout.tsx similarity index 100% rename from apps/web/src/app/app/diff-checker/layout.tsx rename to apps/desktop-ui/src/app/app/diff-checker/layout.tsx diff --git a/apps/web/src/app/app/diff-checker/page.tsx b/apps/desktop-ui/src/app/app/diff-checker/page.tsx similarity index 100% rename from apps/web/src/app/app/diff-checker/page.tsx rename to apps/desktop-ui/src/app/app/diff-checker/page.tsx diff --git a/apps/web/src/app/app/dns-lookup/layout.tsx b/apps/desktop-ui/src/app/app/dns-lookup/layout.tsx similarity index 100% rename from apps/web/src/app/app/dns-lookup/layout.tsx rename to apps/desktop-ui/src/app/app/dns-lookup/layout.tsx diff --git a/apps/web/src/app/app/dns-lookup/page.tsx b/apps/desktop-ui/src/app/app/dns-lookup/page.tsx similarity index 100% rename from apps/web/src/app/app/dns-lookup/page.tsx rename to apps/desktop-ui/src/app/app/dns-lookup/page.tsx diff --git a/apps/web/src/app/app/docker-compose-generator/layout.tsx b/apps/desktop-ui/src/app/app/docker-compose-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/docker-compose-generator/layout.tsx rename to apps/desktop-ui/src/app/app/docker-compose-generator/layout.tsx diff --git a/apps/web/src/app/app/docker-compose-generator/page.tsx b/apps/desktop-ui/src/app/app/docker-compose-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/docker-compose-generator/page.tsx rename to apps/desktop-ui/src/app/app/docker-compose-generator/page.tsx diff --git a/apps/web/src/app/app/email-validator/layout.tsx b/apps/desktop-ui/src/app/app/email-validator/layout.tsx similarity index 100% rename from apps/web/src/app/app/email-validator/layout.tsx rename to apps/desktop-ui/src/app/app/email-validator/layout.tsx diff --git a/apps/web/src/app/app/email-validator/page.tsx b/apps/desktop-ui/src/app/app/email-validator/page.tsx similarity index 100% rename from apps/web/src/app/app/email-validator/page.tsx rename to apps/desktop-ui/src/app/app/email-validator/page.tsx diff --git a/apps/web/src/app/app/encryption-playground/layout.tsx b/apps/desktop-ui/src/app/app/encryption-playground/layout.tsx similarity index 100% rename from apps/web/src/app/app/encryption-playground/layout.tsx rename to apps/desktop-ui/src/app/app/encryption-playground/layout.tsx diff --git a/apps/web/src/app/app/encryption-playground/page.tsx b/apps/desktop-ui/src/app/app/encryption-playground/page.tsx similarity index 100% rename from apps/web/src/app/app/encryption-playground/page.tsx rename to apps/desktop-ui/src/app/app/encryption-playground/page.tsx diff --git a/apps/web/src/app/app/environment-manager/layout.tsx b/apps/desktop-ui/src/app/app/environment-manager/layout.tsx similarity index 100% rename from apps/web/src/app/app/environment-manager/layout.tsx rename to apps/desktop-ui/src/app/app/environment-manager/layout.tsx diff --git a/apps/web/src/app/app/environment-manager/page.tsx b/apps/desktop-ui/src/app/app/environment-manager/page.tsx similarity index 100% rename from apps/web/src/app/app/environment-manager/page.tsx rename to apps/desktop-ui/src/app/app/environment-manager/page.tsx diff --git a/apps/web/src/app/app/escape-encode/layout.tsx b/apps/desktop-ui/src/app/app/escape-encode/layout.tsx similarity index 100% rename from apps/web/src/app/app/escape-encode/layout.tsx rename to apps/desktop-ui/src/app/app/escape-encode/layout.tsx diff --git a/apps/web/src/app/app/escape-encode/page.tsx b/apps/desktop-ui/src/app/app/escape-encode/page.tsx similarity index 100% rename from apps/web/src/app/app/escape-encode/page.tsx rename to apps/desktop-ui/src/app/app/escape-encode/page.tsx diff --git a/apps/web/src/app/app/exif-viewer/layout.tsx b/apps/desktop-ui/src/app/app/exif-viewer/layout.tsx similarity index 100% rename from apps/web/src/app/app/exif-viewer/layout.tsx rename to apps/desktop-ui/src/app/app/exif-viewer/layout.tsx diff --git a/apps/web/src/app/app/exif-viewer/page.tsx b/apps/desktop-ui/src/app/app/exif-viewer/page.tsx similarity index 100% rename from apps/web/src/app/app/exif-viewer/page.tsx rename to apps/desktop-ui/src/app/app/exif-viewer/page.tsx diff --git a/apps/web/src/app/app/favicon-generator/layout.tsx b/apps/desktop-ui/src/app/app/favicon-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/favicon-generator/layout.tsx rename to apps/desktop-ui/src/app/app/favicon-generator/layout.tsx diff --git a/apps/web/src/app/app/favicon-generator/page.tsx b/apps/desktop-ui/src/app/app/favicon-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/favicon-generator/page.tsx rename to apps/desktop-ui/src/app/app/favicon-generator/page.tsx diff --git a/apps/web/src/app/app/format-converter/layout.tsx b/apps/desktop-ui/src/app/app/format-converter/layout.tsx similarity index 100% rename from apps/web/src/app/app/format-converter/layout.tsx rename to apps/desktop-ui/src/app/app/format-converter/layout.tsx diff --git a/apps/web/src/app/app/format-converter/page.tsx b/apps/desktop-ui/src/app/app/format-converter/page.tsx similarity index 100% rename from apps/web/src/app/app/format-converter/page.tsx rename to apps/desktop-ui/src/app/app/format-converter/page.tsx diff --git a/apps/web/src/app/app/gitignore-generator/layout.tsx b/apps/desktop-ui/src/app/app/gitignore-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/gitignore-generator/layout.tsx rename to apps/desktop-ui/src/app/app/gitignore-generator/layout.tsx diff --git a/apps/web/src/app/app/gitignore-generator/page.tsx b/apps/desktop-ui/src/app/app/gitignore-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/gitignore-generator/page.tsx rename to apps/desktop-ui/src/app/app/gitignore-generator/page.tsx diff --git a/apps/web/src/app/app/graphql-formatter/layout.tsx b/apps/desktop-ui/src/app/app/graphql-formatter/layout.tsx similarity index 100% rename from apps/web/src/app/app/graphql-formatter/layout.tsx rename to apps/desktop-ui/src/app/app/graphql-formatter/layout.tsx diff --git a/apps/web/src/app/app/graphql-formatter/page.tsx b/apps/desktop-ui/src/app/app/graphql-formatter/page.tsx similarity index 100% rename from apps/web/src/app/app/graphql-formatter/page.tsx rename to apps/desktop-ui/src/app/app/graphql-formatter/page.tsx diff --git a/apps/web/src/app/app/hash-generator/layout.tsx b/apps/desktop-ui/src/app/app/hash-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/hash-generator/layout.tsx rename to apps/desktop-ui/src/app/app/hash-generator/layout.tsx diff --git a/apps/web/src/app/app/hash-generator/page.tsx b/apps/desktop-ui/src/app/app/hash-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/hash-generator/page.tsx rename to apps/desktop-ui/src/app/app/hash-generator/page.tsx diff --git a/apps/web/src/app/app/hmac-generator/layout.tsx b/apps/desktop-ui/src/app/app/hmac-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/hmac-generator/layout.tsx rename to apps/desktop-ui/src/app/app/hmac-generator/layout.tsx diff --git a/apps/web/src/app/app/hmac-generator/page.tsx b/apps/desktop-ui/src/app/app/hmac-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/hmac-generator/page.tsx rename to apps/desktop-ui/src/app/app/hmac-generator/page.tsx diff --git a/apps/web/src/app/app/http-status-codes/layout.tsx b/apps/desktop-ui/src/app/app/http-status-codes/layout.tsx similarity index 100% rename from apps/web/src/app/app/http-status-codes/layout.tsx rename to apps/desktop-ui/src/app/app/http-status-codes/layout.tsx diff --git a/apps/web/src/app/app/http-status-codes/page.tsx b/apps/desktop-ui/src/app/app/http-status-codes/page.tsx similarity index 100% rename from apps/web/src/app/app/http-status-codes/page.tsx rename to apps/desktop-ui/src/app/app/http-status-codes/page.tsx diff --git a/apps/web/src/app/app/image-compressor/layout.tsx b/apps/desktop-ui/src/app/app/image-compressor/layout.tsx similarity index 100% rename from apps/web/src/app/app/image-compressor/layout.tsx rename to apps/desktop-ui/src/app/app/image-compressor/layout.tsx diff --git a/apps/web/src/app/app/image-compressor/page.tsx b/apps/desktop-ui/src/app/app/image-compressor/page.tsx similarity index 100% rename from apps/web/src/app/app/image-compressor/page.tsx rename to apps/desktop-ui/src/app/app/image-compressor/page.tsx diff --git a/apps/web/src/app/app/image-to-base64/layout.tsx b/apps/desktop-ui/src/app/app/image-to-base64/layout.tsx similarity index 100% rename from apps/web/src/app/app/image-to-base64/layout.tsx rename to apps/desktop-ui/src/app/app/image-to-base64/layout.tsx diff --git a/apps/web/src/app/app/image-to-base64/page.tsx b/apps/desktop-ui/src/app/app/image-to-base64/page.tsx similarity index 100% rename from apps/web/src/app/app/image-to-base64/page.tsx rename to apps/desktop-ui/src/app/app/image-to-base64/page.tsx diff --git a/apps/web/src/app/app/ip-subnet-calculator/layout.tsx b/apps/desktop-ui/src/app/app/ip-subnet-calculator/layout.tsx similarity index 100% rename from apps/web/src/app/app/ip-subnet-calculator/layout.tsx rename to apps/desktop-ui/src/app/app/ip-subnet-calculator/layout.tsx diff --git a/apps/web/src/app/app/ip-subnet-calculator/page.tsx b/apps/desktop-ui/src/app/app/ip-subnet-calculator/page.tsx similarity index 100% rename from apps/web/src/app/app/ip-subnet-calculator/page.tsx rename to apps/desktop-ui/src/app/app/ip-subnet-calculator/page.tsx diff --git a/apps/web/src/app/app/json-diff/layout.tsx b/apps/desktop-ui/src/app/app/json-diff/layout.tsx similarity index 100% rename from apps/web/src/app/app/json-diff/layout.tsx rename to apps/desktop-ui/src/app/app/json-diff/layout.tsx diff --git a/apps/web/src/app/app/json-diff/page.tsx b/apps/desktop-ui/src/app/app/json-diff/page.tsx similarity index 100% rename from apps/web/src/app/app/json-diff/page.tsx rename to apps/desktop-ui/src/app/app/json-diff/page.tsx diff --git a/apps/web/src/app/app/json-formatter/layout.tsx b/apps/desktop-ui/src/app/app/json-formatter/layout.tsx similarity index 100% rename from apps/web/src/app/app/json-formatter/layout.tsx rename to apps/desktop-ui/src/app/app/json-formatter/layout.tsx diff --git a/apps/web/src/app/app/json-formatter/page.tsx b/apps/desktop-ui/src/app/app/json-formatter/page.tsx similarity index 100% rename from apps/web/src/app/app/json-formatter/page.tsx rename to apps/desktop-ui/src/app/app/json-formatter/page.tsx diff --git a/apps/web/src/app/app/json-schema-generator/layout.tsx b/apps/desktop-ui/src/app/app/json-schema-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/json-schema-generator/layout.tsx rename to apps/desktop-ui/src/app/app/json-schema-generator/layout.tsx diff --git a/apps/web/src/app/app/json-schema-generator/page.tsx b/apps/desktop-ui/src/app/app/json-schema-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/json-schema-generator/page.tsx rename to apps/desktop-ui/src/app/app/json-schema-generator/page.tsx diff --git a/apps/web/src/app/app/json-to-code/layout.tsx b/apps/desktop-ui/src/app/app/json-to-code/layout.tsx similarity index 100% rename from apps/web/src/app/app/json-to-code/layout.tsx rename to apps/desktop-ui/src/app/app/json-to-code/layout.tsx diff --git a/apps/web/src/app/app/json-to-code/page.tsx b/apps/desktop-ui/src/app/app/json-to-code/page.tsx similarity index 100% rename from apps/web/src/app/app/json-to-code/page.tsx rename to apps/desktop-ui/src/app/app/json-to-code/page.tsx diff --git a/apps/web/src/app/app/json-visualizer/layout.tsx b/apps/desktop-ui/src/app/app/json-visualizer/layout.tsx similarity index 100% rename from apps/web/src/app/app/json-visualizer/layout.tsx rename to apps/desktop-ui/src/app/app/json-visualizer/layout.tsx diff --git a/apps/web/src/app/app/json-visualizer/page.tsx b/apps/desktop-ui/src/app/app/json-visualizer/page.tsx similarity index 100% rename from apps/web/src/app/app/json-visualizer/page.tsx rename to apps/desktop-ui/src/app/app/json-visualizer/page.tsx diff --git a/apps/web/src/app/app/jsonpath-playground/layout.tsx b/apps/desktop-ui/src/app/app/jsonpath-playground/layout.tsx similarity index 100% rename from apps/web/src/app/app/jsonpath-playground/layout.tsx rename to apps/desktop-ui/src/app/app/jsonpath-playground/layout.tsx diff --git a/apps/web/src/app/app/jsonpath-playground/page.tsx b/apps/desktop-ui/src/app/app/jsonpath-playground/page.tsx similarity index 100% rename from apps/web/src/app/app/jsonpath-playground/page.tsx rename to apps/desktop-ui/src/app/app/jsonpath-playground/page.tsx diff --git a/apps/web/src/app/app/jwt-decoder/layout.tsx b/apps/desktop-ui/src/app/app/jwt-decoder/layout.tsx similarity index 100% rename from apps/web/src/app/app/jwt-decoder/layout.tsx rename to apps/desktop-ui/src/app/app/jwt-decoder/layout.tsx diff --git a/apps/web/src/app/app/jwt-decoder/page.tsx b/apps/desktop-ui/src/app/app/jwt-decoder/page.tsx similarity index 100% rename from apps/web/src/app/app/jwt-decoder/page.tsx rename to apps/desktop-ui/src/app/app/jwt-decoder/page.tsx diff --git a/apps/web/src/app/app/keycode-inspector/layout.tsx b/apps/desktop-ui/src/app/app/keycode-inspector/layout.tsx similarity index 100% rename from apps/web/src/app/app/keycode-inspector/layout.tsx rename to apps/desktop-ui/src/app/app/keycode-inspector/layout.tsx diff --git a/apps/web/src/app/app/keycode-inspector/page.tsx b/apps/desktop-ui/src/app/app/keycode-inspector/page.tsx similarity index 100% rename from apps/web/src/app/app/keycode-inspector/page.tsx rename to apps/desktop-ui/src/app/app/keycode-inspector/page.tsx diff --git a/apps/web/src/app/app/layout.tsx b/apps/desktop-ui/src/app/app/layout.tsx similarity index 100% rename from apps/web/src/app/app/layout.tsx rename to apps/desktop-ui/src/app/app/layout.tsx diff --git a/apps/web/src/app/app/line-sort-dedupe/layout.tsx b/apps/desktop-ui/src/app/app/line-sort-dedupe/layout.tsx similarity index 100% rename from apps/web/src/app/app/line-sort-dedupe/layout.tsx rename to apps/desktop-ui/src/app/app/line-sort-dedupe/layout.tsx diff --git a/apps/web/src/app/app/line-sort-dedupe/page.tsx b/apps/desktop-ui/src/app/app/line-sort-dedupe/page.tsx similarity index 100% rename from apps/web/src/app/app/line-sort-dedupe/page.tsx rename to apps/desktop-ui/src/app/app/line-sort-dedupe/page.tsx diff --git a/apps/web/src/app/app/lorem-ipsum/layout.tsx b/apps/desktop-ui/src/app/app/lorem-ipsum/layout.tsx similarity index 100% rename from apps/web/src/app/app/lorem-ipsum/layout.tsx rename to apps/desktop-ui/src/app/app/lorem-ipsum/layout.tsx diff --git a/apps/web/src/app/app/lorem-ipsum/page.tsx b/apps/desktop-ui/src/app/app/lorem-ipsum/page.tsx similarity index 100% rename from apps/web/src/app/app/lorem-ipsum/page.tsx rename to apps/desktop-ui/src/app/app/lorem-ipsum/page.tsx diff --git a/apps/web/src/app/app/markdown-preview-html/layout.tsx b/apps/desktop-ui/src/app/app/markdown-preview-html/layout.tsx similarity index 100% rename from apps/web/src/app/app/markdown-preview-html/layout.tsx rename to apps/desktop-ui/src/app/app/markdown-preview-html/layout.tsx diff --git a/apps/web/src/app/app/markdown-preview-html/page.tsx b/apps/desktop-ui/src/app/app/markdown-preview-html/page.tsx similarity index 100% rename from apps/web/src/app/app/markdown-preview-html/page.tsx rename to apps/desktop-ui/src/app/app/markdown-preview-html/page.tsx diff --git a/apps/web/src/app/app/markdown-table-generator/layout.tsx b/apps/desktop-ui/src/app/app/markdown-table-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/markdown-table-generator/layout.tsx rename to apps/desktop-ui/src/app/app/markdown-table-generator/layout.tsx diff --git a/apps/web/src/app/app/markdown-table-generator/page.tsx b/apps/desktop-ui/src/app/app/markdown-table-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/markdown-table-generator/page.tsx rename to apps/desktop-ui/src/app/app/markdown-table-generator/page.tsx diff --git a/apps/web/src/app/app/mime-type-lookup/layout.tsx b/apps/desktop-ui/src/app/app/mime-type-lookup/layout.tsx similarity index 100% rename from apps/web/src/app/app/mime-type-lookup/layout.tsx rename to apps/desktop-ui/src/app/app/mime-type-lookup/layout.tsx diff --git a/apps/web/src/app/app/mime-type-lookup/page.tsx b/apps/desktop-ui/src/app/app/mime-type-lookup/page.tsx similarity index 100% rename from apps/web/src/app/app/mime-type-lookup/page.tsx rename to apps/desktop-ui/src/app/app/mime-type-lookup/page.tsx diff --git a/apps/web/src/app/app/mock-data-generator/layout.tsx b/apps/desktop-ui/src/app/app/mock-data-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/mock-data-generator/layout.tsx rename to apps/desktop-ui/src/app/app/mock-data-generator/layout.tsx diff --git a/apps/web/src/app/app/mock-data-generator/page.tsx b/apps/desktop-ui/src/app/app/mock-data-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/mock-data-generator/page.tsx rename to apps/desktop-ui/src/app/app/mock-data-generator/page.tsx diff --git a/apps/web/src/app/app/notes/context/NotesContext.tsx b/apps/desktop-ui/src/app/app/notes/context/NotesContext.tsx similarity index 68% rename from apps/web/src/app/app/notes/context/NotesContext.tsx rename to apps/desktop-ui/src/app/app/notes/context/NotesContext.tsx index f97a6ca2..fece8a17 100644 --- a/apps/web/src/app/app/notes/context/NotesContext.tsx +++ b/apps/desktop-ui/src/app/app/notes/context/NotesContext.tsx @@ -7,6 +7,8 @@ import { auth } from "@/database/firebase"; import { useTranslations } from "next-intl"; import { fetchAllPages } from "@/lib/fetch-all-pages"; import { proxyJsonAuthed } from "@/lib/backend-auth"; +import { useCipherKey, cipherKeyErrorMessage } from "@/lib/use-cipher-key"; +import { encryptField, decryptField, isEnvelope, type ContentEnvelope } from "@/lib/content-envelope"; const BACKEND_BASE_URL: string = process.env.NEXT_PUBLIC_FASTAPI_BASE_URL || @@ -52,14 +54,49 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { const [activeNoteId, setActiveNoteId] = useState(null); const [focusMode, setFocusMode] = useState(false); const contentLoadedIds = useRef>(new Set()); + // Notes whose content is an envelope we couldn't decrypt (vault locked). Their + // in-state `content` is a null placeholder — writing it back would clobber the + // real body, so content writes are blocked until unlock. + const lockedIds = useRef>(new Set()); + + // Zero-knowledge: note bodies are AES-GCM envelopes on the wire; `notes` state + // always holds decrypted content. Key comes from the master-password gate. + const cipherKey = useCipherKey(); // Refs to read latest state inside stable action callbacks const notesRef = useRef(notes); const activeNoteIdRef = useRef(activeNoteId); const userRef = useRef(user); + const cipherKeyRef = useRef(cipherKey); useEffect(() => { notesRef.current = notes; }, [notes]); useEffect(() => { activeNoteIdRef.current = activeNoteId; }, [activeNoteId]); useEffect(() => { userRef.current = user; }, [user]); + useEffect(() => { cipherKeyRef.current = cipherKey; }, [cipherKey]); + + // Decrypt a note's content envelope into plaintext for in-state use. Legacy + // plaintext notes (pre-encryption) pass through untouched. When locked, the + // body becomes a null placeholder and the note is flagged in `lockedIds`. + const decryptNote = useCallback(async (n: Note): Promise => { + if (!isEnvelope(n.content)) { lockedIds.current.delete(n.id); return n; } + const key = cipherKeyRef.current; + if (!key) { lockedIds.current.add(n.id); return { ...n, content: null }; } + try { + const content = await decryptField(key, n.content as ContentEnvelope); + lockedIds.current.delete(n.id); + return { ...n, content }; + } catch { + lockedIds.current.add(n.id); + return { ...n, content: null }; + } + }, []); + + // Encrypt a plaintext body into an envelope for the wire. Throws when locked + // so callers surface the reason instead of silently persisting an empty body. + const encryptContent = useCallback(async (content: unknown): Promise => { + const key = cipherKeyRef.current; + if (!key) throw new Error(cipherKeyErrorMessage()); + return encryptField(key, content); + }, []); const apiRequest = useCallback( async (method: string, path: string, body?: unknown): Promise => { @@ -86,24 +123,27 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { ), }); contentLoadedIds.current.clear(); - setNotes([...allNotes].sort((a, b) => a.createdAt.localeCompare(b.createdAt))); - }, [apiRequest]); + const decrypted = await Promise.all(allNotes.map(decryptNote)); + setNotes([...decrypted].sort((a, b) => a.createdAt.localeCompare(b.createdAt))); + }, [apiRequest, decryptNote]); useEffect(() => { if (!activeNoteId || contentLoadedIds.current.has(activeNoteId)) return; let cancelled = false; setIsContentLoading(true); apiRequest("GET", `/api/v1/notes/${activeNoteId}`) - .then((full) => { - if (cancelled) return; + .then(async (full) => { if (!full?.id) return; - contentLoadedIds.current.add(activeNoteId); - setNotes((prev) => prev.map((n) => (n.id === full.id ? full : n))); + const dec = await decryptNote(full); + if (cancelled) return; + // Only mark loaded once the body is actually available (unlocked). + if (!lockedIds.current.has(dec.id)) contentLoadedIds.current.add(activeNoteId); + setNotes((prev) => prev.map((n) => (n.id === dec.id ? dec : n))); }) .catch(() => { /* note may have been deleted */ }) .finally(() => { if (!cancelled) setIsContentLoading(false); }); return () => { cancelled = true; }; - }, [activeNoteId, apiRequest]); + }, [activeNoteId, apiRequest, decryptNote]); useEffect(() => { if (!user) { @@ -121,28 +161,38 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { setIsLoading(false); } })(); - }, [refreshNotes, user]); + // cipherKey in deps: re-fetch + decrypt bodies once the vault unlocks. + }, [refreshNotes, user, cipherKey]); const createNote = useCallback(async (parentId: string | null = null) => { if (!userRef.current) throw new Error(t("authRequiredError")); const created = await apiRequest("POST", "/api/v1/notes", { title: t("defaultTitle"), - content: {}, + content: await encryptContent({}), parentId, icon: undefined, }); if (!created?.id) throw new Error("Note create failed"); contentLoadedIds.current.add(created.id); setActiveNoteId(created.id); - setNotes((prev) => [...prev, created].sort((a, b) => a.createdAt.localeCompare(b.createdAt))); + // Server echoes the envelope back; keep decrypted (empty) body in state. + const local: Note = { ...created, content: {} }; + setNotes((prev) => [...prev, local].sort((a, b) => a.createdAt.localeCompare(b.createdAt))); return created.id; - }, [apiRequest, t]); + }, [apiRequest, t, encryptContent]); const updateNote = useCallback(async (id: string, updates: Partial) => { if (!userRef.current) return; - const payload: Partial> = {}; + // Block body writes while locked — persisting would clobber the real + // (undecryptable) content with an empty one. + if (updates.content !== undefined && (lockedIds.current.has(id) || !cipherKeyRef.current)) { + throw new Error(cipherKeyErrorMessage()); + } + + // Plaintext metadata for the wire; encrypted body swapped in below. + const payload: Record = {}; if (updates.title !== undefined) payload.title = updates.title; - if (updates.content !== undefined) payload.content = updates.content; + if (updates.content !== undefined) payload.content = await encryptContent(updates.content); if (updates.parentId !== undefined) payload.parentId = updates.parentId; if (updates.icon !== undefined) payload.icon = updates.icon; if (updates.pinned !== undefined) payload.pinned = updates.pinned; @@ -151,8 +201,11 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { const updated = await apiRequest("PATCH", `/api/v1/notes/${id}`, payload); if (!updated?.id) throw new Error("Update failed: empty response"); if (updates.content !== undefined) contentLoadedIds.current.add(id); - setNotes((prev) => prev.map((n) => (n.id === updated.id ? updated : n))); - }, [apiRequest]); + // Merge server echo but keep the plaintext body we already hold in memory. + setNotes((prev) => prev.map((n) => (n.id === updated.id + ? { ...updated, content: updates.content !== undefined ? updates.content : n.content } + : n))); + }, [apiRequest, encryptContent]); const pinNote = useCallback(async (id: string, pinned: boolean) => { await updateNote(id, { pinned }); @@ -165,13 +218,15 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { if (!contentLoadedIds.current.has(id)) { const full = await apiRequest("GET", `/api/v1/notes/${id}`); if (!full?.id) throw new Error("Note fetch failed"); - contentLoadedIds.current.add(id); - setNotes((prev) => prev.map((n) => (n.id === full.id ? full : n))); - content = full.content; + const dec = await decryptNote(full); + if (!lockedIds.current.has(dec.id)) contentLoadedIds.current.add(id); + setNotes((prev) => prev.map((n) => (n.id === dec.id ? dec : n))); + content = dec.content; } + if (lockedIds.current.has(id) || !cipherKeyRef.current) throw new Error(cipherKeyErrorMessage()); const created = await apiRequest("POST", "/api/v1/notes", { title: `${src.title || t("defaultTitle")} (copy)`, - content, + content: await encryptContent(content), parentId: src.parentId ?? null, icon: src.icon, tags: src.tags, @@ -179,9 +234,10 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { if (!created?.id) throw new Error("Note duplicate failed"); contentLoadedIds.current.add(created.id); setActiveNoteId(created.id); - setNotes((prev) => [...prev, created].sort((a, b) => a.createdAt.localeCompare(b.createdAt))); + const local: Note = { ...created, content }; + setNotes((prev) => [...prev, local].sort((a, b) => a.createdAt.localeCompare(b.createdAt))); return created.id; - }, [apiRequest, t]); + }, [apiRequest, t, decryptNote, encryptContent]); const moveNote = useCallback(async (id: string, newParentId: string | null) => { await updateNote(id, { parentId: newParentId }); diff --git a/apps/web/src/app/app/notes/layout.tsx b/apps/desktop-ui/src/app/app/notes/layout.tsx similarity index 100% rename from apps/web/src/app/app/notes/layout.tsx rename to apps/desktop-ui/src/app/app/notes/layout.tsx diff --git a/apps/web/src/app/app/notes/notes-client-layout.tsx b/apps/desktop-ui/src/app/app/notes/notes-client-layout.tsx similarity index 100% rename from apps/web/src/app/app/notes/notes-client-layout.tsx rename to apps/desktop-ui/src/app/app/notes/notes-client-layout.tsx diff --git a/apps/web/src/app/app/notes/page.tsx b/apps/desktop-ui/src/app/app/notes/page.tsx similarity index 75% rename from apps/web/src/app/app/notes/page.tsx rename to apps/desktop-ui/src/app/app/notes/page.tsx index ce2fcda0..15c3d521 100644 --- a/apps/web/src/app/app/notes/page.tsx +++ b/apps/desktop-ui/src/app/app/notes/page.tsx @@ -4,6 +4,9 @@ import dynamic from "next/dynamic"; import useAuth from "@/utils/useAuth"; import { useTranslations } from "next-intl"; import { Skeleton } from "@/components/ui/skeleton"; +import { useVaultGuard } from "@/hooks/use-vault-guard"; +import { VaultLockedPlaceholder } from "@/components/vault-locked-placeholder"; +import { VaultRestoringSkeleton } from "@/components/vault-restoring-skeleton"; const NotionEditor = dynamic(() => import("@/components/notes/NotionEditor"), { ssr: false, @@ -23,6 +26,7 @@ const NotionEditor = dynamic(() => import("@/components/notes/NotionEditor"), { export default function NotesPage() { const { user, loading } = useAuth(true); + const { isUnlocked, isRestoring } = useVaultGuard(); const t = useTranslations("Notes.page"); if (loading) { @@ -46,5 +50,10 @@ export default function NotesPage() { return null; } + // Zero-knowledge: note bodies are encrypted with the master password. Gate the + // tool until the vault is unlocked (mirrors password-manager / env-manager). + if (isRestoring) return ; + if (!isUnlocked) return ; + return ; } diff --git a/apps/web/src/app/app/notes/types/Note.ts b/apps/desktop-ui/src/app/app/notes/types/Note.ts similarity index 100% rename from apps/web/src/app/app/notes/types/Note.ts rename to apps/desktop-ui/src/app/app/notes/types/Note.ts diff --git a/apps/web/src/app/app/notes/utils/__tests__/noteContentUtils.test.ts b/apps/desktop-ui/src/app/app/notes/utils/__tests__/noteContentUtils.test.ts similarity index 100% rename from apps/web/src/app/app/notes/utils/__tests__/noteContentUtils.test.ts rename to apps/desktop-ui/src/app/app/notes/utils/__tests__/noteContentUtils.test.ts diff --git a/apps/web/src/app/app/notes/utils/noteContentUtils.ts b/apps/desktop-ui/src/app/app/notes/utils/noteContentUtils.ts similarity index 100% rename from apps/web/src/app/app/notes/utils/noteContentUtils.ts rename to apps/desktop-ui/src/app/app/notes/utils/noteContentUtils.ts diff --git a/apps/web/src/app/app/notes/utils/noteTemplates.ts b/apps/desktop-ui/src/app/app/notes/utils/noteTemplates.ts similarity index 100% rename from apps/web/src/app/app/notes/utils/noteTemplates.ts rename to apps/desktop-ui/src/app/app/notes/utils/noteTemplates.ts diff --git a/apps/web/src/app/app/number-base-converter/layout.tsx b/apps/desktop-ui/src/app/app/number-base-converter/layout.tsx similarity index 100% rename from apps/web/src/app/app/number-base-converter/layout.tsx rename to apps/desktop-ui/src/app/app/number-base-converter/layout.tsx diff --git a/apps/web/src/app/app/number-base-converter/page.tsx b/apps/desktop-ui/src/app/app/number-base-converter/page.tsx similarity index 100% rename from apps/web/src/app/app/number-base-converter/page.tsx rename to apps/desktop-ui/src/app/app/number-base-converter/page.tsx diff --git a/apps/web/src/app/app/password-manager/layout.tsx b/apps/desktop-ui/src/app/app/password-manager/layout.tsx similarity index 100% rename from apps/web/src/app/app/password-manager/layout.tsx rename to apps/desktop-ui/src/app/app/password-manager/layout.tsx diff --git a/apps/web/src/app/app/password-manager/page.tsx b/apps/desktop-ui/src/app/app/password-manager/page.tsx similarity index 100% rename from apps/web/src/app/app/password-manager/page.tsx rename to apps/desktop-ui/src/app/app/password-manager/page.tsx diff --git a/apps/web/src/app/app/qr-code-generator/layout.tsx b/apps/desktop-ui/src/app/app/qr-code-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/qr-code-generator/layout.tsx rename to apps/desktop-ui/src/app/app/qr-code-generator/layout.tsx diff --git a/apps/web/src/app/app/qr-code-generator/page.tsx b/apps/desktop-ui/src/app/app/qr-code-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/qr-code-generator/page.tsx rename to apps/desktop-ui/src/app/app/qr-code-generator/page.tsx diff --git a/apps/web/src/app/app/redis-commander/layout.tsx b/apps/desktop-ui/src/app/app/redis-commander/layout.tsx similarity index 100% rename from apps/web/src/app/app/redis-commander/layout.tsx rename to apps/desktop-ui/src/app/app/redis-commander/layout.tsx diff --git a/apps/web/src/app/app/redis-commander/page.tsx b/apps/desktop-ui/src/app/app/redis-commander/page.tsx similarity index 100% rename from apps/web/src/app/app/redis-commander/page.tsx rename to apps/desktop-ui/src/app/app/redis-commander/page.tsx diff --git a/apps/web/src/app/app/regex-tester/layout.tsx b/apps/desktop-ui/src/app/app/regex-tester/layout.tsx similarity index 100% rename from apps/web/src/app/app/regex-tester/layout.tsx rename to apps/desktop-ui/src/app/app/regex-tester/layout.tsx diff --git a/apps/web/src/app/app/regex-tester/page.tsx b/apps/desktop-ui/src/app/app/regex-tester/page.tsx similarity index 100% rename from apps/web/src/app/app/regex-tester/page.tsx rename to apps/desktop-ui/src/app/app/regex-tester/page.tsx diff --git a/apps/web/src/app/app/s3-drive/layout.tsx b/apps/desktop-ui/src/app/app/s3-drive/layout.tsx similarity index 100% rename from apps/web/src/app/app/s3-drive/layout.tsx rename to apps/desktop-ui/src/app/app/s3-drive/layout.tsx diff --git a/apps/web/src/app/app/s3-drive/page.tsx b/apps/desktop-ui/src/app/app/s3-drive/page.tsx similarity index 100% rename from apps/web/src/app/app/s3-drive/page.tsx rename to apps/desktop-ui/src/app/app/s3-drive/page.tsx diff --git a/apps/web/src/app/app/secret-api-key-generator/layout.tsx b/apps/desktop-ui/src/app/app/secret-api-key-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/secret-api-key-generator/layout.tsx rename to apps/desktop-ui/src/app/app/secret-api-key-generator/layout.tsx diff --git a/apps/web/src/app/app/secret-api-key-generator/page.tsx b/apps/desktop-ui/src/app/app/secret-api-key-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/secret-api-key-generator/page.tsx rename to apps/desktop-ui/src/app/app/secret-api-key-generator/page.tsx diff --git a/apps/web/src/app/app/snippet-manager/layout.tsx b/apps/desktop-ui/src/app/app/snippet-manager/layout.tsx similarity index 100% rename from apps/web/src/app/app/snippet-manager/layout.tsx rename to apps/desktop-ui/src/app/app/snippet-manager/layout.tsx diff --git a/apps/desktop-ui/src/app/app/snippet-manager/page.tsx b/apps/desktop-ui/src/app/app/snippet-manager/page.tsx new file mode 100644 index 00000000..b0ee8bdb --- /dev/null +++ b/apps/desktop-ui/src/app/app/snippet-manager/page.tsx @@ -0,0 +1,14 @@ +"use client"; +import { SnippetManagerTool } from "@/components/snippet-manager/snippet-manager-tool"; +import { useVaultGuard } from "@/hooks/use-vault-guard"; +import { VaultLockedPlaceholder } from "@/components/vault-locked-placeholder"; +import { VaultRestoringSkeleton } from "@/components/vault-restoring-skeleton"; + +export default function SnippetManagerPage() { + // Zero-knowledge: snippet code is encrypted with the master password. Gate the + // tool until the vault is unlocked (mirrors password-manager / env-manager). + const { isUnlocked, isRestoring } = useVaultGuard(); + if (isRestoring) return ; + if (!isUnlocked) return ; + return ; +} diff --git a/apps/web/src/app/app/sql-client/layout.tsx b/apps/desktop-ui/src/app/app/sql-client/layout.tsx similarity index 100% rename from apps/web/src/app/app/sql-client/layout.tsx rename to apps/desktop-ui/src/app/app/sql-client/layout.tsx diff --git a/apps/web/src/app/app/sql-client/page.tsx b/apps/desktop-ui/src/app/app/sql-client/page.tsx similarity index 100% rename from apps/web/src/app/app/sql-client/page.tsx rename to apps/desktop-ui/src/app/app/sql-client/page.tsx diff --git a/apps/web/src/app/app/sql-formatter/layout.tsx b/apps/desktop-ui/src/app/app/sql-formatter/layout.tsx similarity index 100% rename from apps/web/src/app/app/sql-formatter/layout.tsx rename to apps/desktop-ui/src/app/app/sql-formatter/layout.tsx diff --git a/apps/web/src/app/app/sql-formatter/page.tsx b/apps/desktop-ui/src/app/app/sql-formatter/page.tsx similarity index 100% rename from apps/web/src/app/app/sql-formatter/page.tsx rename to apps/desktop-ui/src/app/app/sql-formatter/page.tsx diff --git a/apps/web/src/app/app/ssh-key-generator/layout.tsx b/apps/desktop-ui/src/app/app/ssh-key-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/ssh-key-generator/layout.tsx rename to apps/desktop-ui/src/app/app/ssh-key-generator/layout.tsx diff --git a/apps/web/src/app/app/ssh-key-generator/page.tsx b/apps/desktop-ui/src/app/app/ssh-key-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/ssh-key-generator/page.tsx rename to apps/desktop-ui/src/app/app/ssh-key-generator/page.tsx diff --git a/apps/web/src/app/app/string-case-converter/layout.tsx b/apps/desktop-ui/src/app/app/string-case-converter/layout.tsx similarity index 100% rename from apps/web/src/app/app/string-case-converter/layout.tsx rename to apps/desktop-ui/src/app/app/string-case-converter/layout.tsx diff --git a/apps/web/src/app/app/string-case-converter/page.tsx b/apps/desktop-ui/src/app/app/string-case-converter/page.tsx similarity index 100% rename from apps/web/src/app/app/string-case-converter/page.tsx rename to apps/desktop-ui/src/app/app/string-case-converter/page.tsx diff --git a/apps/web/src/app/app/string-inspector/layout.tsx b/apps/desktop-ui/src/app/app/string-inspector/layout.tsx similarity index 100% rename from apps/web/src/app/app/string-inspector/layout.tsx rename to apps/desktop-ui/src/app/app/string-inspector/layout.tsx diff --git a/apps/web/src/app/app/string-inspector/page.tsx b/apps/desktop-ui/src/app/app/string-inspector/page.tsx similarity index 100% rename from apps/web/src/app/app/string-inspector/page.tsx rename to apps/desktop-ui/src/app/app/string-inspector/page.tsx diff --git a/apps/web/src/app/app/svg-optimizer/layout.tsx b/apps/desktop-ui/src/app/app/svg-optimizer/layout.tsx similarity index 100% rename from apps/web/src/app/app/svg-optimizer/layout.tsx rename to apps/desktop-ui/src/app/app/svg-optimizer/layout.tsx diff --git a/apps/web/src/app/app/svg-optimizer/page.tsx b/apps/desktop-ui/src/app/app/svg-optimizer/page.tsx similarity index 100% rename from apps/web/src/app/app/svg-optimizer/page.tsx rename to apps/desktop-ui/src/app/app/svg-optimizer/page.tsx diff --git a/apps/web/src/app/app/timestamp-converter/layout.tsx b/apps/desktop-ui/src/app/app/timestamp-converter/layout.tsx similarity index 100% rename from apps/web/src/app/app/timestamp-converter/layout.tsx rename to apps/desktop-ui/src/app/app/timestamp-converter/layout.tsx diff --git a/apps/web/src/app/app/timestamp-converter/page.tsx b/apps/desktop-ui/src/app/app/timestamp-converter/page.tsx similarity index 100% rename from apps/web/src/app/app/timestamp-converter/page.tsx rename to apps/desktop-ui/src/app/app/timestamp-converter/page.tsx diff --git a/apps/web/src/app/app/timezone-converter/layout.tsx b/apps/desktop-ui/src/app/app/timezone-converter/layout.tsx similarity index 100% rename from apps/web/src/app/app/timezone-converter/layout.tsx rename to apps/desktop-ui/src/app/app/timezone-converter/layout.tsx diff --git a/apps/web/src/app/app/timezone-converter/page.tsx b/apps/desktop-ui/src/app/app/timezone-converter/page.tsx similarity index 100% rename from apps/web/src/app/app/timezone-converter/page.tsx rename to apps/desktop-ui/src/app/app/timezone-converter/page.tsx diff --git a/apps/web/src/app/app/to-do/ExportImportDialog.tsx b/apps/desktop-ui/src/app/app/to-do/ExportImportDialog.tsx similarity index 100% rename from apps/web/src/app/app/to-do/ExportImportDialog.tsx rename to apps/desktop-ui/src/app/app/to-do/ExportImportDialog.tsx diff --git a/apps/web/src/app/app/to-do/GoogleLoginButton.tsx b/apps/desktop-ui/src/app/app/to-do/GoogleLoginButton.tsx similarity index 100% rename from apps/web/src/app/app/to-do/GoogleLoginButton.tsx rename to apps/desktop-ui/src/app/app/to-do/GoogleLoginButton.tsx diff --git a/apps/web/src/app/app/to-do/KanbanBoard.tsx b/apps/desktop-ui/src/app/app/to-do/KanbanBoard.tsx similarity index 100% rename from apps/web/src/app/app/to-do/KanbanBoard.tsx rename to apps/desktop-ui/src/app/app/to-do/KanbanBoard.tsx diff --git a/apps/web/src/app/app/to-do/KanbanCard.tsx b/apps/desktop-ui/src/app/app/to-do/KanbanCard.tsx similarity index 100% rename from apps/web/src/app/app/to-do/KanbanCard.tsx rename to apps/desktop-ui/src/app/app/to-do/KanbanCard.tsx diff --git a/apps/web/src/app/app/to-do/KanbanColumn.tsx b/apps/desktop-ui/src/app/app/to-do/KanbanColumn.tsx similarity index 100% rename from apps/web/src/app/app/to-do/KanbanColumn.tsx rename to apps/desktop-ui/src/app/app/to-do/KanbanColumn.tsx diff --git a/apps/web/src/app/app/to-do/KeyboardShortcutsDialog.tsx b/apps/desktop-ui/src/app/app/to-do/KeyboardShortcutsDialog.tsx similarity index 100% rename from apps/web/src/app/app/to-do/KeyboardShortcutsDialog.tsx rename to apps/desktop-ui/src/app/app/to-do/KeyboardShortcutsDialog.tsx diff --git a/apps/web/src/app/app/to-do/PaginationS.tsx b/apps/desktop-ui/src/app/app/to-do/PaginationS.tsx similarity index 100% rename from apps/web/src/app/app/to-do/PaginationS.tsx rename to apps/desktop-ui/src/app/app/to-do/PaginationS.tsx diff --git a/apps/web/src/app/app/to-do/TaskContainer.tsx b/apps/desktop-ui/src/app/app/to-do/TaskContainer.tsx similarity index 100% rename from apps/web/src/app/app/to-do/TaskContainer.tsx rename to apps/desktop-ui/src/app/app/to-do/TaskContainer.tsx diff --git a/apps/web/src/app/app/to-do/TaskEditDialog.tsx b/apps/desktop-ui/src/app/app/to-do/TaskEditDialog.tsx similarity index 100% rename from apps/web/src/app/app/to-do/TaskEditDialog.tsx rename to apps/desktop-ui/src/app/app/to-do/TaskEditDialog.tsx diff --git a/apps/web/src/app/app/to-do/TaskForm.tsx b/apps/desktop-ui/src/app/app/to-do/TaskForm.tsx similarity index 100% rename from apps/web/src/app/app/to-do/TaskForm.tsx rename to apps/desktop-ui/src/app/app/to-do/TaskForm.tsx diff --git a/apps/web/src/app/app/to-do/TaskItem.tsx b/apps/desktop-ui/src/app/app/to-do/TaskItem.tsx similarity index 100% rename from apps/web/src/app/app/to-do/TaskItem.tsx rename to apps/desktop-ui/src/app/app/to-do/TaskItem.tsx diff --git a/apps/web/src/app/app/to-do/TaskList.tsx b/apps/desktop-ui/src/app/app/to-do/TaskList.tsx similarity index 100% rename from apps/web/src/app/app/to-do/TaskList.tsx rename to apps/desktop-ui/src/app/app/to-do/TaskList.tsx diff --git a/apps/web/src/app/app/to-do/components/AssigneePicker.tsx b/apps/desktop-ui/src/app/app/to-do/components/AssigneePicker.tsx similarity index 100% rename from apps/web/src/app/app/to-do/components/AssigneePicker.tsx rename to apps/desktop-ui/src/app/app/to-do/components/AssigneePicker.tsx diff --git a/apps/web/src/app/app/to-do/components/LazyBoundary.tsx b/apps/desktop-ui/src/app/app/to-do/components/LazyBoundary.tsx similarity index 100% rename from apps/web/src/app/app/to-do/components/LazyBoundary.tsx rename to apps/desktop-ui/src/app/app/to-do/components/LazyBoundary.tsx diff --git a/apps/web/src/app/app/to-do/components/ProjectManagerDialog.tsx b/apps/desktop-ui/src/app/app/to-do/components/ProjectManagerDialog.tsx similarity index 100% rename from apps/web/src/app/app/to-do/components/ProjectManagerDialog.tsx rename to apps/desktop-ui/src/app/app/to-do/components/ProjectManagerDialog.tsx diff --git a/apps/web/src/app/app/to-do/components/TaskCommandPalette.tsx b/apps/desktop-ui/src/app/app/to-do/components/TaskCommandPalette.tsx similarity index 100% rename from apps/web/src/app/app/to-do/components/TaskCommandPalette.tsx rename to apps/desktop-ui/src/app/app/to-do/components/TaskCommandPalette.tsx diff --git a/apps/web/src/app/app/to-do/components/TaskSkeleton.tsx b/apps/desktop-ui/src/app/app/to-do/components/TaskSkeleton.tsx similarity index 100% rename from apps/web/src/app/app/to-do/components/TaskSkeleton.tsx rename to apps/desktop-ui/src/app/app/to-do/components/TaskSkeleton.tsx diff --git a/apps/web/src/app/app/to-do/config/constants.ts b/apps/desktop-ui/src/app/app/to-do/config/constants.ts similarity index 100% rename from apps/web/src/app/app/to-do/config/constants.ts rename to apps/desktop-ui/src/app/app/to-do/config/constants.ts diff --git a/apps/web/src/app/app/to-do/context/ProjectContext.tsx b/apps/desktop-ui/src/app/app/to-do/context/ProjectContext.tsx similarity index 100% rename from apps/web/src/app/app/to-do/context/ProjectContext.tsx rename to apps/desktop-ui/src/app/app/to-do/context/ProjectContext.tsx diff --git a/apps/web/src/app/app/to-do/context/TaskContext.tsx b/apps/desktop-ui/src/app/app/to-do/context/TaskContext.tsx similarity index 100% rename from apps/web/src/app/app/to-do/context/TaskContext.tsx rename to apps/desktop-ui/src/app/app/to-do/context/TaskContext.tsx diff --git a/apps/web/src/app/app/to-do/hooks/useWorkspaceMembers.ts b/apps/desktop-ui/src/app/app/to-do/hooks/useWorkspaceMembers.ts similarity index 100% rename from apps/web/src/app/app/to-do/hooks/useWorkspaceMembers.ts rename to apps/desktop-ui/src/app/app/to-do/hooks/useWorkspaceMembers.ts diff --git a/apps/web/src/app/app/to-do/layout.tsx b/apps/desktop-ui/src/app/app/to-do/layout.tsx similarity index 100% rename from apps/web/src/app/app/to-do/layout.tsx rename to apps/desktop-ui/src/app/app/to-do/layout.tsx diff --git a/apps/web/src/app/app/to-do/login-page.tsx b/apps/desktop-ui/src/app/app/to-do/login-page.tsx similarity index 100% rename from apps/web/src/app/app/to-do/login-page.tsx rename to apps/desktop-ui/src/app/app/to-do/login-page.tsx diff --git a/apps/web/src/app/app/to-do/page.tsx b/apps/desktop-ui/src/app/app/to-do/page.tsx similarity index 100% rename from apps/web/src/app/app/to-do/page.tsx rename to apps/desktop-ui/src/app/app/to-do/page.tsx diff --git a/apps/web/src/app/app/to-do/providers.tsx b/apps/desktop-ui/src/app/app/to-do/providers.tsx similarity index 100% rename from apps/web/src/app/app/to-do/providers.tsx rename to apps/desktop-ui/src/app/app/to-do/providers.tsx diff --git a/apps/web/src/app/app/to-do/types/Project.ts b/apps/desktop-ui/src/app/app/to-do/types/Project.ts similarity index 100% rename from apps/web/src/app/app/to-do/types/Project.ts rename to apps/desktop-ui/src/app/app/to-do/types/Project.ts diff --git a/apps/web/src/app/app/to-do/types/Task.ts b/apps/desktop-ui/src/app/app/to-do/types/Task.ts similarity index 100% rename from apps/web/src/app/app/to-do/types/Task.ts rename to apps/desktop-ui/src/app/app/to-do/types/Task.ts diff --git a/apps/web/src/app/app/to-do/useKeyboardShortcuts.tsx b/apps/desktop-ui/src/app/app/to-do/useKeyboardShortcuts.tsx similarity index 100% rename from apps/web/src/app/app/to-do/useKeyboardShortcuts.tsx rename to apps/desktop-ui/src/app/app/to-do/useKeyboardShortcuts.tsx diff --git a/apps/web/src/app/app/to-do/utils/pendingDeletes.ts b/apps/desktop-ui/src/app/app/to-do/utils/pendingDeletes.ts similarity index 100% rename from apps/web/src/app/app/to-do/utils/pendingDeletes.ts rename to apps/desktop-ui/src/app/app/to-do/utils/pendingDeletes.ts diff --git a/apps/web/src/app/app/to-do/utils/taskDate.ts b/apps/desktop-ui/src/app/app/to-do/utils/taskDate.ts similarity index 100% rename from apps/web/src/app/app/to-do/utils/taskDate.ts rename to apps/desktop-ui/src/app/app/to-do/utils/taskDate.ts diff --git a/apps/web/src/app/app/to-do/utils/taskTimeUtils.ts b/apps/desktop-ui/src/app/app/to-do/utils/taskTimeUtils.ts similarity index 100% rename from apps/web/src/app/app/to-do/utils/taskTimeUtils.ts rename to apps/desktop-ui/src/app/app/to-do/utils/taskTimeUtils.ts diff --git a/apps/web/src/app/app/token-counter/layout.tsx b/apps/desktop-ui/src/app/app/token-counter/layout.tsx similarity index 100% rename from apps/web/src/app/app/token-counter/layout.tsx rename to apps/desktop-ui/src/app/app/token-counter/layout.tsx diff --git a/apps/web/src/app/app/token-counter/page.tsx b/apps/desktop-ui/src/app/app/token-counter/page.tsx similarity index 100% rename from apps/web/src/app/app/token-counter/page.tsx rename to apps/desktop-ui/src/app/app/token-counter/page.tsx diff --git a/apps/web/src/app/app/totp-generator/layout.tsx b/apps/desktop-ui/src/app/app/totp-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/totp-generator/layout.tsx rename to apps/desktop-ui/src/app/app/totp-generator/layout.tsx diff --git a/apps/web/src/app/app/totp-generator/page.tsx b/apps/desktop-ui/src/app/app/totp-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/totp-generator/page.tsx rename to apps/desktop-ui/src/app/app/totp-generator/page.tsx diff --git a/apps/web/src/app/app/unit-converter/layout.tsx b/apps/desktop-ui/src/app/app/unit-converter/layout.tsx similarity index 100% rename from apps/web/src/app/app/unit-converter/layout.tsx rename to apps/desktop-ui/src/app/app/unit-converter/layout.tsx diff --git a/apps/web/src/app/app/unit-converter/page.tsx b/apps/desktop-ui/src/app/app/unit-converter/page.tsx similarity index 100% rename from apps/web/src/app/app/unit-converter/page.tsx rename to apps/desktop-ui/src/app/app/unit-converter/page.tsx diff --git a/apps/web/src/app/app/url-encode/layout.tsx b/apps/desktop-ui/src/app/app/url-encode/layout.tsx similarity index 100% rename from apps/web/src/app/app/url-encode/layout.tsx rename to apps/desktop-ui/src/app/app/url-encode/layout.tsx diff --git a/apps/web/src/app/app/url-encode/page.tsx b/apps/desktop-ui/src/app/app/url-encode/page.tsx similarity index 100% rename from apps/web/src/app/app/url-encode/page.tsx rename to apps/desktop-ui/src/app/app/url-encode/page.tsx diff --git a/apps/web/src/app/app/url-parser/layout.tsx b/apps/desktop-ui/src/app/app/url-parser/layout.tsx similarity index 100% rename from apps/web/src/app/app/url-parser/layout.tsx rename to apps/desktop-ui/src/app/app/url-parser/layout.tsx diff --git a/apps/web/src/app/app/url-parser/page.tsx b/apps/desktop-ui/src/app/app/url-parser/page.tsx similarity index 100% rename from apps/web/src/app/app/url-parser/page.tsx rename to apps/desktop-ui/src/app/app/url-parser/page.tsx diff --git a/apps/web/src/app/app/user-agent-parser/layout.tsx b/apps/desktop-ui/src/app/app/user-agent-parser/layout.tsx similarity index 100% rename from apps/web/src/app/app/user-agent-parser/layout.tsx rename to apps/desktop-ui/src/app/app/user-agent-parser/layout.tsx diff --git a/apps/web/src/app/app/user-agent-parser/page.tsx b/apps/desktop-ui/src/app/app/user-agent-parser/page.tsx similarity index 100% rename from apps/web/src/app/app/user-agent-parser/page.tsx rename to apps/desktop-ui/src/app/app/user-agent-parser/page.tsx diff --git a/apps/web/src/app/app/uuid-generator/layout.tsx b/apps/desktop-ui/src/app/app/uuid-generator/layout.tsx similarity index 100% rename from apps/web/src/app/app/uuid-generator/layout.tsx rename to apps/desktop-ui/src/app/app/uuid-generator/layout.tsx diff --git a/apps/web/src/app/app/uuid-generator/page.tsx b/apps/desktop-ui/src/app/app/uuid-generator/page.tsx similarity index 100% rename from apps/web/src/app/app/uuid-generator/page.tsx rename to apps/desktop-ui/src/app/app/uuid-generator/page.tsx diff --git a/apps/web/src/app/app/websocket-tester/layout.tsx b/apps/desktop-ui/src/app/app/websocket-tester/layout.tsx similarity index 100% rename from apps/web/src/app/app/websocket-tester/layout.tsx rename to apps/desktop-ui/src/app/app/websocket-tester/layout.tsx diff --git a/apps/web/src/app/app/websocket-tester/page.tsx b/apps/desktop-ui/src/app/app/websocket-tester/page.tsx similarity index 100% rename from apps/web/src/app/app/websocket-tester/page.tsx rename to apps/desktop-ui/src/app/app/websocket-tester/page.tsx diff --git a/apps/web/src/app/app/whois-lookup/layout.tsx b/apps/desktop-ui/src/app/app/whois-lookup/layout.tsx similarity index 100% rename from apps/web/src/app/app/whois-lookup/layout.tsx rename to apps/desktop-ui/src/app/app/whois-lookup/layout.tsx diff --git a/apps/desktop-ui/src/app/app/whois-lookup/page.tsx b/apps/desktop-ui/src/app/app/whois-lookup/page.tsx new file mode 100644 index 00000000..19a2eba3 --- /dev/null +++ b/apps/desktop-ui/src/app/app/whois-lookup/page.tsx @@ -0,0 +1,11 @@ +import { WhoisLookupLayout } from '@/components/whois-lookup/whois-lookup-layout' +import { DesktopOnlineGate } from '@/components/desktop/desktop-online-gate' +export default function WhoisLookupPage() { + return ( + +
+ +
+
+ ) +} diff --git a/apps/web/src/app/app/yaml-formatter/layout.tsx b/apps/desktop-ui/src/app/app/yaml-formatter/layout.tsx similarity index 100% rename from apps/web/src/app/app/yaml-formatter/layout.tsx rename to apps/desktop-ui/src/app/app/yaml-formatter/layout.tsx diff --git a/apps/web/src/app/app/yaml-formatter/page.tsx b/apps/desktop-ui/src/app/app/yaml-formatter/page.tsx similarity index 100% rename from apps/web/src/app/app/yaml-formatter/page.tsx rename to apps/desktop-ui/src/app/app/yaml-formatter/page.tsx diff --git a/apps/web/src/app/dashboard/activity/page.tsx b/apps/desktop-ui/src/app/dashboard/activity/page.tsx similarity index 100% rename from apps/web/src/app/dashboard/activity/page.tsx rename to apps/desktop-ui/src/app/dashboard/activity/page.tsx diff --git a/apps/web/src/app/dashboard/dashboard-client-layout.tsx b/apps/desktop-ui/src/app/dashboard/dashboard-client-layout.tsx similarity index 100% rename from apps/web/src/app/dashboard/dashboard-client-layout.tsx rename to apps/desktop-ui/src/app/dashboard/dashboard-client-layout.tsx diff --git a/apps/web/src/app/dashboard/layout.tsx b/apps/desktop-ui/src/app/dashboard/layout.tsx similarity index 100% rename from apps/web/src/app/dashboard/layout.tsx rename to apps/desktop-ui/src/app/dashboard/layout.tsx diff --git a/apps/web/src/app/dashboard/page.tsx b/apps/desktop-ui/src/app/dashboard/page.tsx similarity index 100% rename from apps/web/src/app/dashboard/page.tsx rename to apps/desktop-ui/src/app/dashboard/page.tsx diff --git a/apps/desktop-ui/src/app/error.tsx b/apps/desktop-ui/src/app/error.tsx new file mode 100644 index 00000000..6fa84252 --- /dev/null +++ b/apps/desktop-ui/src/app/error.tsx @@ -0,0 +1,47 @@ +'use client' + +import Link from 'next/link' +import { Home, RotateCw } from 'lucide-react' +import { MdtStatusPage } from '@/components/mdt-status-page' + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string } + reset: () => void +}) { + return ( + + + + + Back home + + + ) +} diff --git a/apps/desktop-ui/src/app/favicon.ico b/apps/desktop-ui/src/app/favicon.ico new file mode 100644 index 00000000..7cb8a480 Binary files /dev/null and b/apps/desktop-ui/src/app/favicon.ico differ diff --git a/apps/desktop-ui/src/app/global-error.tsx b/apps/desktop-ui/src/app/global-error.tsx new file mode 100644 index 00000000..6a22c3fa --- /dev/null +++ b/apps/desktop-ui/src/app/global-error.tsx @@ -0,0 +1,42 @@ +'use client' + +import { GeistSans } from 'geist/font/sans' +import { GeistMono } from 'geist/font/mono' +import { RotateCw } from 'lucide-react' +import { MdtStatusPage } from '@/components/mdt-status-page' +import './globals.css' + +// Catches errors thrown in the root layout itself — must render its own /. +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string } + reset: () => void +}) { + return ( + + + + + + + + ) +} diff --git a/apps/desktop-ui/src/app/globals.css b/apps/desktop-ui/src/app/globals.css new file mode 100644 index 00000000..76fed569 --- /dev/null +++ b/apps/desktop-ui/src/app/globals.css @@ -0,0 +1,1895 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + font-size: 15px; + line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + letter-spacing: -0.01em; +} + +@layer base { + :root { + /* Light "deck-in-daylight": crisp white panels lifting off a soft cool canvas */ + --background: 225 24% 96.5%; + --foreground: 222 32% 11%; + --card: 0 0% 100%; + --card-foreground: 222 32% 11%; + --popover: 0 0% 100%; + --popover-foreground: 222 32% 11%; + --primary: 222 47% 16%; + --primary-foreground: 0 0% 100%; + --secondary: 225 22% 93.5%; + --secondary-foreground: 222 30% 16%; + --muted: 225 22% 93.5%; + --muted-foreground: 222 14% 42%; + --accent: 225 24% 93%; + --accent-foreground: 222 30% 16%; + --destructive: 0 72% 51%; + --destructive-foreground: 0 0% 100%; + --border: 222 18% 88%; + --input: 222 18% 88%; + --ring: 222 47% 16%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --surface-1: 0 0% 100%; + --surface-2: 225 24% 97.5%; + --surface-3: 225 22% 94.5%; + --sidebar-background: 0 0% 100%; + --sidebar-foreground: 222 18% 28%; + --sidebar-primary: 222 47% 16%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 225 26% 94.5%; + --sidebar-accent-foreground: 222 32% 14%; + --sidebar-border: 222 18% 90%; + --sidebar-ring: 222 47% 16%; + --sidebar-width: 247px; + --main-content-width: calc(100% - var(--sidebar-width)); + + /* Mobile Safe Areas */ + --safe-area-top: env(safe-area-inset-top); + --safe-area-bottom: env(safe-area-inset-bottom); + --safe-area-left: env(safe-area-inset-left); + --safe-area-right: env(safe-area-inset-right); + } + + .dark { + /* Minimalist dark theme - shadcn neutral zinc defaults */ + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + /* Chart colors remain balanced */ + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --card: 240 10% 5.5%; + --surface-1: 240 8% 7%; + --surface-2: 240 7% 9%; + --surface-3: 240 6% 12%; + /* Sidebar aligned to the deck blue-black theme (was a desaturated grey) */ + --sidebar-background: 240 10% 5%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 0 0% 98%; + --sidebar-primary-foreground: 240 5.9% 10%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 240 4.9% 83.9%; + } + + /* Color Theme Overrides - Light Mode */ + :root.blue { + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 0 0% 98%; + --ring: 217.2 91.2% 59.8%; + --sidebar-primary: 217.2 91.2% 59.8%; + } + + :root.purple { + --primary: 258.3 89.5% 66.3%; + --primary-foreground: 0 0% 98%; + --ring: 258.3 89.5% 66.3%; + --sidebar-primary: 258.3 89.5% 66.3%; + } + + :root.green { + --primary: 142.1 76.2% 36.3%; + --primary-foreground: 0 0% 98%; + --ring: 142.1 76.2% 36.3%; + --sidebar-primary: 142.1 76.2% 36.3%; + } + + :root.orange { + --primary: 24.6 95% 53.1%; + --primary-foreground: 0 0% 98%; + --ring: 24.6 95% 53.1%; + --sidebar-primary: 24.6 95% 53.1%; + } + + :root.red { + --primary: 0 72.2% 50.6%; + --primary-foreground: 0 0% 98%; + --ring: 0 72.2% 50.6%; + --sidebar-primary: 0 72.2% 50.6%; + } + + :root.pink { + --primary: 340.7 82.1% 52.7%; + --primary-foreground: 0 0% 98%; + --ring: 340.7 82.1% 52.7%; + --sidebar-primary: 340.7 82.1% 52.7%; + } + + :root.cyan { + --primary: 199.1 89.1% 48.2%; + --primary-foreground: 0 0% 98%; + --ring: 199.1 89.1% 48.2%; + --sidebar-primary: 199.1 89.1% 48.2%; + } + + :root.indigo { + --primary: 239.4 83.9% 66.7%; + --primary-foreground: 0 0% 98%; + --ring: 239.4 83.9% 66.7%; + --sidebar-primary: 239.4 83.9% 66.7%; + } + + /* Color Theme Overrides - Dark Mode */ + .dark.blue { + --primary: 217.2 91.2% 59.8%; + --sidebar-primary: 217.2 91.2% 59.8%; + } + + .dark.purple { + --primary: 258.3 89.5% 66.3%; + --sidebar-primary: 258.3 89.5% 66.3%; + } + + .dark.green { + --primary: 142.1 70.6% 45.3%; + --sidebar-primary: 142.1 70.6% 45.3%; + } + + .dark.orange { + --primary: 24.6 95% 53.1%; + --sidebar-primary: 24.6 95% 53.1%; + } + + .dark.red { + --primary: 0 72.2% 50.6%; + --sidebar-primary: 0 72.2% 50.6%; + } + + .dark.pink { + --primary: 340.7 82.1% 52.7%; + --sidebar-primary: 340.7 82.1% 52.7%; + } + + .dark.cyan { + --primary: 199.1 89.1% 48.2%; + --sidebar-primary: 199.1 89.1% 48.2%; + } + + .dark.indigo { + --primary: 239.4 83.9% 66.7%; + --sidebar-primary: 239.4 83.9% 66.7%; + } +} + +/* Custom Scrollbar for Sidebar */ +.sidebar-scrollbar { + scrollbar-width: thin; + scrollbar-color: hsl(var(--muted-foreground) / 0.2) transparent; +} + +.sidebar-scrollbar::-webkit-scrollbar { + width: 4px; +} + +.sidebar-scrollbar::-webkit-scrollbar-track { + background: transparent; + border-radius: 20px; +} + +.sidebar-scrollbar::-webkit-scrollbar-thumb { + background-color: hsl(var(--muted-foreground) / 0.2); + border-radius: 20px; + transition: background-color 0.2s ease; +} + +.sidebar-scrollbar:hover::-webkit-scrollbar-thumb { + background-color: hsl(var(--muted-foreground) / 0.4); +} + +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + } +} + +/* + * vanilla-jsoneditor — map --jse-* tokens to app shadcn theme (light + dark). + * Loaded after base tokens; higher specificity than `.jse-theme-dark` alone. + */ +@layer components { + .jse-app-theme { + --jse-font-family: inherit; + --jse-font-family-mono: var(--font-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, + Consolas, 'Liberation Mono', 'Courier New', monospace; + --jse-font-size: 14px; + --jse-font-size-mono: 13px; + --jse-error-color: hsl(var(--destructive)); + --jse-warning-color: hsl(38 92% 50%); + --jse-info-color: hsl(var(--primary)); + --jse-text-readonly: hsl(var(--muted-foreground)); + } + + /* Light / app background */ + html:not(.dark) .jse-app-theme:not(.jse-theme-dark) { + --jse-theme: light; + --jse-theme-color: hsl(var(--primary)); + --jse-theme-color-highlight: hsl(var(--ring)); + --jse-background-color: hsl(var(--card)); + --jse-text-color: hsl(var(--foreground)); + --jse-text-color-inverse: hsl(var(--primary-foreground)); + --jse-main-border: 1px solid hsl(var(--border)); + --jse-menu-color: hsl(var(--primary-foreground)); + --jse-modal-background: hsl(var(--popover)); + --jse-modal-overlay-background: hsl(var(--foreground) / 0.25); + --jse-modal-code-background: hsl(var(--muted)); + --jse-tooltip-color: hsl(var(--popover-foreground)); + --jse-tooltip-background: hsl(var(--popover)); + --jse-tooltip-border: 1px solid hsl(var(--border)); + --jse-tooltip-action-button-color: hsl(var(--primary-foreground)); + --jse-tooltip-action-button-background: hsl(var(--primary)); + --jse-panel-background: hsl(var(--muted)); + --jse-panel-background-border: 1px solid hsl(var(--border)); + --jse-panel-color: hsl(var(--foreground)); + --jse-panel-color-readonly: hsl(var(--muted-foreground)); + --jse-panel-border: 1px solid hsl(var(--border)); + --jse-panel-button-background-highlight: hsl(var(--secondary)); + --jse-panel-button-color-highlight: hsl(var(--secondary-foreground)); + --jse-navigation-bar-background: hsl(var(--muted)); + --jse-navigation-bar-background-highlight: hsl(var(--accent)); + --jse-navigation-bar-dropdown-color: hsl(var(--muted-foreground)); + --jse-context-menu-background: hsl(var(--popover)); + --jse-context-menu-background-highlight: hsl(var(--accent)); + --jse-context-menu-color: hsl(var(--popover-foreground)); + --jse-context-menu-color-disabled: hsl(var(--muted-foreground)); + --jse-context-menu-separator-color: hsl(var(--border)); + --jse-context-menu-pointer-background: hsl(var(--muted)); + --jse-context-menu-pointer-background-highlight: hsl(var(--accent)); + --jse-context-menu-pointer-color: hsl(var(--popover-foreground)); + --jse-key-color: hsl(var(--primary)); + --jse-value-color: hsl(var(--foreground)); + --jse-value-color-number: hsl(142 76% 30%); + --jse-value-color-boolean: hsl(217 91% 45%); + --jse-value-color-null: hsl(var(--muted-foreground)); + --jse-value-color-string: hsl(24 95% 38%); + --jse-value-color-url: hsl(217 91% 45%); + --jse-delimiter-color: hsl(var(--muted-foreground)); + --jse-edit-outline: 2px solid hsl(var(--ring)); + --jse-contents-background-color: transparent; + --jse-selection-background-color: hsl(var(--accent)); + --jse-selection-background-inactive-color: hsl(var(--muted)); + --jse-hover-background-color: hsl(var(--muted)); + --jse-active-line-background-color: hsl(var(--muted) / 0.55); + --jse-search-match-background-color: hsl(var(--primary) / 0.12); + --jse-collapsed-items-background-color: hsl(var(--muted)); + --jse-collapsed-items-selected-background-color: hsl(var(--accent)); + --jse-collapsed-items-link-color: hsl(var(--muted-foreground)); + --jse-collapsed-items-link-color-highlight: hsl(var(--destructive)); + --jse-search-match-color: hsl(48 96% 53%); + --jse-search-match-outline: 1px solid hsl(var(--primary)); + --jse-search-match-active-color: hsl(38 92% 50%); + --jse-search-match-active-outline: 1px solid hsl(var(--ring)); + --jse-tag-background: hsl(var(--secondary)); + --jse-tag-color: hsl(var(--secondary-foreground)); + --jse-table-header-background: hsl(var(--muted)); + --jse-table-header-background-highlight: hsl(var(--secondary)); + --jse-table-row-odd-background: hsl(var(--muted) / 0.35); + --jse-input-background: hsl(var(--background)); + --jse-input-border: 1px solid hsl(var(--input)); + --jse-button-background: hsl(var(--primary)); + --jse-button-background-highlight: hsl(var(--primary) / 0.88); + --jse-button-color: hsl(var(--primary-foreground)); + --jse-button-secondary-background: hsl(var(--secondary)); + --jse-button-secondary-background-highlight: hsl(var(--accent)); + --jse-button-secondary-background-disabled: hsl(var(--muted)); + --jse-button-secondary-color: hsl(var(--secondary-foreground)); + --jse-a-color: hsl(var(--primary)); + --jse-a-color-highlight: hsl(var(--ring)); + --jse-svelte-select-background: hsl(var(--background)); + --jse-svelte-select-border: 1px solid hsl(var(--border)); + --list-background: hsl(var(--popover)); + --item-hover-bg: hsl(var(--accent)); + --input-color: hsl(var(--foreground)); + --multi-item-bg: hsl(var(--secondary)); + --multi-clear-bg: hsl(var(--muted-foreground)); + --multi-item-clear-icon-color: hsl(var(--background)); + --multi-item-outline: 1px solid hsl(var(--border)); + --list-shadow: 0 4px 12px hsl(var(--foreground) / 0.08); + --jse-color-picker-background: hsl(var(--muted)); + --jse-color-picker-border-box-shadow: hsl(var(--border)) 0 0 0 1px; + } + + /* Dark — override bundled jse-theme-dark to match app .dark palette */ + html.dark .jse-app-theme.jse-theme-dark { + --jse-theme: dark; + --jse-theme-color: hsl(var(--primary)); + --jse-theme-color-highlight: hsl(var(--ring)); + --jse-background-color: hsl(var(--card)); + --jse-text-color: hsl(var(--foreground)); + --jse-text-color-inverse: hsl(var(--primary-foreground)); + --jse-main-border: 1px solid hsl(var(--border)); + --jse-menu-color: hsl(var(--primary-foreground)); + --jse-modal-background: hsl(var(--popover)); + --jse-modal-overlay-background: hsl(0 0% 0% / 0.55); + --jse-modal-code-background: hsl(var(--muted)); + --jse-tooltip-color: hsl(var(--popover-foreground)); + --jse-tooltip-background: hsl(var(--popover)); + --jse-tooltip-border: 1px solid hsl(var(--border)); + --jse-tooltip-action-button-color: hsl(var(--primary-foreground)); + --jse-tooltip-action-button-background: hsl(var(--primary)); + --jse-panel-background: hsl(var(--muted)); + --jse-panel-background-border: 1px solid hsl(var(--border)); + --jse-panel-color: hsl(var(--foreground)); + --jse-panel-color-readonly: hsl(var(--muted-foreground)); + --jse-panel-border: 1px solid hsl(var(--border)); + --jse-panel-button-color-highlight: hsl(var(--foreground)); + --jse-panel-button-background-highlight: hsl(var(--secondary)); + --jse-navigation-bar-background: hsl(var(--muted)); + --jse-navigation-bar-background-highlight: hsl(var(--secondary)); + --jse-navigation-bar-dropdown-color: hsl(var(--foreground)); + --jse-context-menu-background: hsl(var(--popover)); + --jse-context-menu-background-highlight: hsl(var(--accent)); + --jse-context-menu-separator-color: hsl(var(--border)); + --jse-context-menu-color: hsl(var(--popover-foreground)); + --jse-context-menu-pointer-background: hsl(var(--muted)); + --jse-context-menu-pointer-background-highlight: hsl(var(--accent)); + --jse-context-menu-pointer-color: hsl(var(--popover-foreground)); + --jse-key-color: hsl(199 89% 58%); + --jse-value-color: hsl(var(--foreground)); + --jse-value-color-number: hsl(142 55% 55%); + --jse-value-color-boolean: hsl(var(--primary)); + --jse-value-color-null: hsl(var(--primary)); + --jse-value-color-string: hsl(24 95% 68%); + --jse-value-color-url: hsl(24 95% 68%); + --jse-delimiter-color: hsl(var(--muted-foreground)); + --jse-edit-outline: 2px solid hsl(var(--ring)); + --jse-contents-background-color: transparent; + --jse-selection-background-color: hsl(var(--accent)); + --jse-selection-background-inactive-color: hsl(var(--muted)); + --jse-hover-background-color: hsl(var(--secondary)); + --jse-active-line-background-color: hsl(var(--primary) / 0.12); + --jse-search-match-background-color: hsl(var(--muted)); + --jse-collapsed-items-background-color: hsl(var(--muted)); + --jse-collapsed-items-selected-background-color: hsl(var(--accent)); + --jse-collapsed-items-link-color: hsl(var(--muted-foreground)); + --jse-collapsed-items-link-color-highlight: hsl(var(--destructive)); + --jse-search-match-color: hsl(38 92% 45%); + --jse-search-match-outline: 1px solid hsl(var(--primary)); + --jse-search-match-active-color: hsl(48 96% 55%); + --jse-search-match-active-outline: 1px solid hsl(var(--ring)); + --jse-tag-background: hsl(var(--secondary)); + --jse-tag-color: hsl(var(--secondary-foreground)); + --jse-table-header-background: hsl(var(--muted)); + --jse-table-header-background-highlight: hsl(var(--secondary)); + --jse-table-row-odd-background: hsl(var(--foreground) / 0.04); + --jse-input-background: hsl(var(--background)); + --jse-input-border: 1px solid hsl(var(--border)); + --jse-button-background: hsl(var(--primary)); + --jse-button-background-highlight: hsl(var(--ring)); + --jse-button-color: hsl(var(--primary-foreground)); + --jse-button-secondary-background: hsl(var(--secondary)); + --jse-button-secondary-background-highlight: hsl(var(--accent)); + --jse-button-secondary-background-disabled: hsl(var(--muted-foreground)); + --jse-button-secondary-color: hsl(var(--secondary-foreground)); + --jse-a-color: hsl(var(--primary)); + --jse-a-color-highlight: hsl(var(--ring)); + --jse-svelte-select-background: hsl(var(--card)); + --jse-svelte-select-border: 1px solid hsl(var(--border)); + --list-background: hsl(var(--popover)); + --item-hover-bg: hsl(var(--accent)); + --input-color: hsl(var(--foreground)); + --multi-item-bg: hsl(var(--secondary)); + --multi-clear-bg: hsl(var(--muted-foreground)); + --multi-item-clear-icon-color: hsl(var(--background)); + --multi-item-outline: 1px solid hsl(var(--border)); + --list-shadow: 0 4px 16px hsl(0 0% 0% / 0.35); + --jse-color-picker-background: hsl(var(--muted)); + --jse-color-picker-border-box-shadow: hsl(var(--border)) 0 0 0 1px; + } +} + +/* Enhanced blue dark mode styling for elegant, modern visuals */ +@layer utilities { + .dark { + /* Smooth transitions for theme changes */ + transition: background-color 0.3s ease, color 0.3s ease; + } + + /* Enhanced card shadows in dark mode */ + .dark .shadow { + box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.3), 0 1px 2px -1px rgb(0 0 0 / 0.3); + } + + .dark .shadow-md { + box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3); + } + + .dark .shadow-lg { + box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.4); + } + + .dark .shadow-xl { + box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.4); + } + + /* Subtle border glow for interactive elements */ + .dark .border-primary\/20 { + border-color: hsl(var(--primary) / 0.2); + } + + /* Standard neutral background for body */ + .dark body { + background: hsl(var(--background)); + } + + /* Custom scrollbar for tabs */ + .scrollbar-thin { + scrollbar-width: thin; + scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent; + } + + .scrollbar-thin::-webkit-scrollbar { + height: 6px; + } + + .scrollbar-thin::-webkit-scrollbar-track { + background: transparent; + } + + .scrollbar-thin::-webkit-scrollbar-thumb { + background-color: hsl(var(--muted-foreground) / 0.3); + border-radius: 3px; + } + + .scrollbar-thin::-webkit-scrollbar-thumb:hover { + background-color: hsl(var(--muted-foreground) / 0.5); + } + + /* Task animations */ + @keyframes slideInFromTop { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + + @keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.95); + } + + to { + opacity: 1; + transform: scale(1); + } + } + + @keyframes slideInFromLeft { + from { + opacity: 0; + transform: translateX(-10px); + } + + to { + opacity: 1; + transform: translateX(0); + } + } + + .animate-in { + animation: fadeIn 0.3s ease-out; + } + + .animate-slide-in { + animation: slideInFromTop 0.3s ease-out; + } + + .animate-scale-in { + animation: scaleIn 0.2s ease-out; + } + + /* Smooth transitions for task items */ + .task-item-enter { + animation: slideInFromTop 0.3s ease-out; + } + + /* Focus visible improvements */ + *:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; + border-radius: calc(var(--radius) - 2px); + } + + /* Reduced motion support */ + @media (prefers-reduced-motion: reduce) { + + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + } + + /* Landing page background animations - Enhanced for visibility */ + @keyframes float { + + 0%, + 100% { + transform: translate(0, 0) scale(1); + } + + 33% { + transform: translate(60px, -60px) scale(1.15); + } + + 66% { + transform: translate(-50px, 50px) scale(0.85); + } + } + + @keyframes float-delayed { + + 0%, + 100% { + transform: translate(0, 0) scale(1); + } + + 33% { + transform: translate(-55px, 55px) scale(1.1); + } + + 66% { + transform: translate(55px, -55px) scale(0.9); + } + } + + @keyframes pulse-glow { + + 0%, + 100% { + opacity: 0.3; + transform: scale(1); + } + + 50% { + opacity: 0.6; + transform: scale(1.05); + } + } + + @keyframes shimmer { + 0% { + background-position: -1000px 0; + } + + 100% { + background-position: 1000px 0; + } + } + + .animate-float { + animation: float 15s ease-in-out infinite; + } + + .animate-float-delayed { + animation: float-delayed 18s ease-in-out infinite; + } + + .animate-pulse-glow { + animation: pulse-glow 3s ease-in-out infinite; + } + + .animate-shimmer { + animation: shimmer 8s linear infinite; + } + + .skeleton-shimmer { + background-image: linear-gradient( + 90deg, + hsl(var(--muted)) 0%, + hsl(var(--muted-foreground) / 0.08) 50%, + hsl(var(--muted)) 100% + ); + background-size: 1000px 100%; + animation: shimmer 1.6s linear infinite; + } + + @media (prefers-reduced-motion: reduce) { + .skeleton-shimmer { + animation: none; + background: hsl(var(--muted)); + } + } + + /* Animated gradient text for welcome heading */ + @keyframes gradient-shift { + + 0%, + 100% { + background-position: 0% 50%; + } + + 50% { + background-position: 100% 50%; + } + } + + .gradient-text-animated { + background: linear-gradient(135deg, + hsl(var(--foreground)) 0%, + hsl(var(--primary)) 25%, + hsl(var(--foreground)) 50%, + hsl(var(--primary)) 75%, + hsl(var(--foreground)) 100%); + background-size: 300% 300%; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + animation: gradient-shift 8s ease infinite; + } + + /* Gradient border animation for cards */ + @keyframes border-gradient { + + 0%, + 100% { + background-position: 0% 50%; + } + + 50% { + background-position: 100% 50%; + } + } + + .card-gradient-border { + position: relative; + } + + .card-gradient-border::before { + content: ''; + position: absolute; + inset: 0; + padding: 1px; + border-radius: calc(var(--radius) + 4px); + background: linear-gradient(135deg, + transparent 0%, + hsl(var(--primary) / 0.3) 25%, + hsl(var(--primary) / 0.5) 50%, + hsl(var(--primary) / 0.3) 75%, + transparent 100%); + background-size: 400% 400%; + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + opacity: 0; + transition: opacity 0.3s ease; + pointer-events: none; + } + + .card-gradient-border:hover::before { + opacity: 1; + animation: border-gradient 3s ease infinite; + } + + /* Glass morphism effect */ + .glass-card { + background: hsl(var(--card) / 0.6); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid hsl(var(--border) / 0.5); + } + + .dark .glass-card { + background: hsl(var(--card) / 0.4); + border: 1px solid hsl(var(--border) / 0.3); + } + + /* Subtle grid background for dashboard */ + .dashboard-grid-bg { + background-image: + linear-gradient(hsl(var(--border) / 0.3) 1px, transparent 1px), + linear-gradient(90deg, hsl(var(--border) / 0.3) 1px, transparent 1px); + background-size: 60px 60px; + } + + .dark .dashboard-grid-bg { + background-image: + linear-gradient(hsl(var(--border) / 0.15) 1px, transparent 1px), + linear-gradient(90deg, hsl(var(--border) / 0.15) 1px, transparent 1px); + } + + /* Icon pulse animation */ + @keyframes icon-pulse { + + 0%, + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 hsl(var(--primary) / 0.4); + } + + 50% { + transform: scale(1.05); + box-shadow: 0 0 0 8px hsl(var(--primary) / 0); + } + } + + .icon-container-pulse:hover { + animation: icon-pulse 1.5s ease infinite; + } + + /* Section header gradient line */ + .section-header-line { + position: relative; + } + + .section-header-line::after { + content: ''; + position: absolute; + bottom: -8px; + left: 0; + width: 60px; + height: 3px; + border-radius: 2px; + background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--primary) / 0.3)); + } + + /* Stats card glow */ + .stats-glow { + box-shadow: 0 0 20px hsl(var(--primary) / 0.1); + } + + /* Dashboard greeting — brand gradient both themes (deep, legible stops in light) */ + .dash-greeting { + background: linear-gradient(100deg, #4338ca 0%, #7c3aed 52%, #0e7490 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + color: transparent; + } + .dark .dash-greeting { + background: var(--mdt-grad); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + color: transparent; + } + + /* Ambient top glow for the dashboard surface (theme-aware, decorative) */ + .dash-ambient { + position: absolute; + inset: 0 0 auto 0; + height: 26rem; + pointer-events: none; + background: + radial-gradient(58% 100% at 12% 0%, rgba(99, 102, 241, 0.16), transparent 68%), + radial-gradient(50% 100% at 50% 0%, rgba(124, 58, 237, 0.10), transparent 70%), + radial-gradient(58% 100% at 90% 0%, rgba(34, 211, 238, 0.12), transparent 70%); + } + .dark .dash-ambient { + background: + radial-gradient(60% 100% at 18% 0%, hsl(var(--primary) / 0.16), transparent 70%), + radial-gradient(55% 100% at 88% 0%, rgba(79, 208, 230, 0.12), transparent 70%); + } + + /* Tool card gradient sheen revealed on hover (transform/opacity only) */ + .dash-card-sheen::before { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + padding: 1px; + background: linear-gradient(130deg, hsl(var(--primary) / 0.55), transparent 40%, transparent 60%, rgba(79, 208, 230, 0.45)); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + opacity: 0; + transition: opacity var(--dur-2, 0.2s) var(--ease-out-quart, ease); + } + .group:hover .dash-card-sheen::before { + opacity: 1; + } + /* Cursor-anchored glow — illuminates the card under the pointer (set via --mx/--my). */ + .dash-card-sheen::after { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + background: radial-gradient( + 240px circle at var(--mx, 50%) var(--my, 50%), + hsl(var(--primary) / 0.13), + rgba(79, 208, 230, 0.06) 35%, + transparent 60% + ); + opacity: 0; + transition: opacity var(--dur-2, 0.2s) var(--ease-out-quart, ease); + } + .group:hover .dash-card-sheen::after { + opacity: 1; + } + + .dark .stats-glow { + box-shadow: 0 0 30px hsl(var(--primary) / 0.15); + } + + /* ============================================ + HYBRID GLASSMORPHISM 2.0 / MODERN SAAS + ============================================ */ + + /* Navigation chrome — header, top bars */ + .glass-nav { + background: hsl(var(--background) / 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid hsl(var(--border) / 0.5); + } + + .dark .glass-nav { + background: hsl(var(--background) / 0.72); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + + /* Overlay cards — landing page cards, feature cards */ + .glass-overlay { + background: hsl(var(--card) / 0.65); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid hsl(var(--border) / 0.5); + } + + .dark .glass-overlay { + background: linear-gradient(180deg, rgba(122, 107, 245, 0.05), transparent 42%), var(--mdt-surface); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--mdt-border); + border-radius: 18px; + box-shadow: 0 18px 50px -24px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.05); + } + + /* Modals, command palette, dropdowns */ + .glass-modal { + background: hsl(var(--popover) / 0.92); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border: 1px solid hsl(var(--border) / 0.4); + box-shadow: 0 24px 64px rgba(0, 0, 0, 0.12); + } + + .dark .glass-modal { + background: linear-gradient(180deg, rgba(122, 107, 245, 0.06), transparent 38%), var(--mdt-surface-hi); + border: 1px solid var(--mdt-border); + box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06); + } + + /* Core workspace — NO glass, solid crisp background */ + .surface-workspace { + background: hsl(var(--background)); + backdrop-filter: none; + -webkit-backdrop-filter: none; + } + + .dark .surface-workspace { + background: hsl(var(--background)); + } + + /* Floating sidebar glass — dark mode only */ + .dark [data-variant="floating"] [data-sidebar="sidebar"] { + background: hsl(var(--sidebar-background) / 0.82); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + border-color: rgba(255, 255, 255, 0.06); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.04); + } + + /* ============================================ + MOBILE-FIRST UTILITIES & TOUCH OPTIMIZATION + ============================================ */ + + /* Prevent iOS Safari auto-zoom on inputs with font-size < 16px */ + @media (max-width: 768px) { + input, textarea, select { + font-size: 16px; + } + } + + /* Touch Target Sizing - Minimum 44px for accessibility */ + .touch-target { + min-height: 44px; + min-width: 44px; + } + + .touch-target-sm { + min-height: 36px; + min-width: 36px; + } + + .touch-target-lg { + min-height: 48px; + min-width: 48px; + } + + /* Safe Area Padding for Mobile Devices */ + .safe-area-bottom { + padding-bottom: env(safe-area-inset-bottom); + } + + .safe-area-padding { + padding-bottom: calc(env(safe-area-inset-bottom) + 4rem); + } + + .safe-area-padding-sm { + padding-bottom: calc(env(safe-area-inset-bottom) + 2rem); + } + + /* Mobile Navigation Offset */ + .mobile-nav-offset { + padding-bottom: calc(env(safe-area-inset-bottom) + 5rem); + } + + @media (min-width: 768px) { + .mobile-nav-offset { + padding-bottom: 0; + } + } + + /* ============================================ + SWIPE ACTIONS & GESTURE SUPPORT + ============================================ */ + + /* Swipe Action Indicators */ + .swipe-action-left { + background: linear-gradient(90deg, hsl(var(--destructive) / 0.15), transparent); + } + + .swipe-action-right { + background: linear-gradient(-90deg, hsl(var(--primary) / 0.15), transparent); + } + + /* Swipeable Item Base */ + .swipeable-item { + touch-action: pan-y; + user-select: none; + -webkit-user-select: none; + } + + /* Horizontal Scroll Container */ + .scroll-snap-x { + display: flex; + overflow-x: auto; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + -ms-overflow-style: none; + } + + .scroll-snap-x::-webkit-scrollbar { + display: none; + } + + .scroll-snap-item { + scroll-snap-align: start; + flex-shrink: 0; + } + + .scroll-snap-center { + scroll-snap-align: center; + } + + /* Pull to Refresh Indicator */ + .pull-indicator { + position: absolute; + top: -40px; + left: 50%; + transform: translateX(-50%); + opacity: 0; + transition: opacity 0.2s ease, transform 0.2s ease; + } + + .pull-indicator.visible { + opacity: 1; + transform: translateX(-50%) translateY(40px); + } + + /* ============================================ + MOBILE CARD & SHADOW OPTIMIZATIONS + ============================================ */ + + .card-mobile { + border-radius: calc(var(--radius) + 2px); + } + + @media (max-width: 768px) { + .card-mobile-shadow { + box-shadow: 0 2px 8px -2px rgb(0 0 0 / 0.1); + } + + .dark .card-mobile-shadow { + box-shadow: 0 2px 12px -2px rgb(0 0 0 / 0.3); + } + + .card-mobile { + border-radius: var(--radius); + } + } + + /* Compact Card for Mobile Lists */ + .card-compact { + padding: 0.75rem 1rem; + } + + @media (min-width: 768px) { + .card-compact { + padding: 1rem 1.25rem; + } + } + + /* ============================================ + TOUCH-OPTIMIZED FOCUS STATES + ============================================ */ + + @media (pointer: coarse) { + + /* Larger focus rings for touch devices */ + *:focus-visible { + outline-offset: 4px; + outline-width: 3px; + } + + /* Disable tap highlight */ + button, + a, + [role="button"], + [tabindex]:not([tabindex="-1"]) { + -webkit-tap-highlight-color: transparent; + } + + /* Better touch feedback */ + .touch-ripple { + position: relative; + overflow: hidden; + } + + .touch-ripple::after { + content: ''; + position: absolute; + inset: 0; + background: hsl(var(--foreground) / 0.1); + opacity: 0; + transition: opacity 0.15s ease; + } + + .touch-ripple:active::after { + opacity: 1; + } + } + + /* ============================================ + MOBILE-OPTIMIZED ANIMATIONS + ============================================ */ + + /* Slide animations with better performance */ + @keyframes slide-in-from-bottom { + from { + opacity: 0; + transform: translateY(16px); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes slide-in-from-right { + from { + opacity: 0; + transform: translateX(16px); + } + + to { + opacity: 1; + transform: translateX(0); + } + } + + @keyframes slide-out-to-right { + from { + opacity: 1; + transform: translateX(0); + } + + to { + opacity: 0; + transform: translateX(100%); + } + } + + @keyframes slide-out-to-left { + from { + opacity: 1; + transform: translateX(0); + } + + to { + opacity: 0; + transform: translateX(-100%); + } + } + + .animate-slide-in-bottom { + animation: slide-in-from-bottom 0.3s ease-out; + } + + .animate-slide-in-right { + animation: slide-in-from-right 0.3s ease-out; + } + + .animate-slide-out-right { + animation: slide-out-to-right 0.25s ease-in forwards; + } + + .animate-slide-out-left { + animation: slide-out-to-left 0.25s ease-in forwards; + } + + /* Skeleton loading animation */ + @keyframes skeleton-pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + } + + .skeleton { + background: hsl(var(--muted)); + animation: skeleton-pulse 1.5s ease-in-out infinite; + border-radius: calc(var(--radius) - 2px); + } + + /* Public profile page (/[username]) — hero fade-up, avatar float, ring pulse */ + @keyframes profileFadeUp { + from { + opacity: 0; + transform: translateY(16px); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes subtleFloat { + + 0%, + 100% { + transform: translateY(0px); + } + + 50% { + transform: translateY(-6px); + } + } + + @keyframes ringPulse { + + 0%, + 100% { + box-shadow: 0 0 0 0 hsl(var(--primary) / 0.15); + } + + 50% { + box-shadow: 0 0 0 8px hsl(var(--primary) / 0); + } + } + + /* Fab pulse animation */ + @keyframes fab-pulse { + + 0%, + 100% { + box-shadow: 0 4px 12px hsl(var(--primary) / 0.3); + } + + 50% { + box-shadow: 0 4px 20px hsl(var(--primary) / 0.5); + } + } + + .fab-pulse { + animation: fab-pulse 2s ease-in-out infinite; + } + + /* ============================================ + FLOATING ACTION BUTTON + ============================================ */ + + .fab { + position: fixed; + bottom: calc(env(safe-area-inset-bottom) + 5.5rem); + right: 1rem; + z-index: 40; + border-radius: 9999px; + box-shadow: 0 4px 12px hsl(var(--primary) / 0.25); + transition: transform 0.2s ease, box-shadow 0.2s ease; + } + + .fab:hover { + transform: scale(1.05); + box-shadow: 0 6px 16px hsl(var(--primary) / 0.35); + } + + .fab:active { + transform: scale(0.95); + } + + @media (min-width: 768px) { + .fab { + bottom: 2rem; + right: 2rem; + } + } + + /* ============================================ + MOBILE SPACING UTILITIES + ============================================ */ + + .gap-mobile-sm { + gap: 0.5rem; + } + + .gap-mobile-md { + gap: 0.75rem; + } + + @media (min-width: 768px) { + .gap-mobile-sm { + gap: 0.75rem; + } + + .gap-mobile-md { + gap: 1rem; + } + } + + /* Compact text for mobile */ + .text-mobile-sm { + font-size: 0.8125rem; + line-height: 1.4; + } + + @media (min-width: 768px) { + .text-mobile-sm { + font-size: 0.875rem; + line-height: 1.5; + } + } + + /* ============================================ + STICKY HEADER/TOOLBAR STYLES + ============================================ */ + + .sticky-toolbar { + position: sticky; + top: 0; + z-index: 30; + background: hsl(var(--background) / 0.85); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid hsl(var(--border) / 0.5); + } + + .dark .sticky-toolbar { + background: hsl(var(--background) / 0.8); + } + + /* ============================================ + BOTTOM SHEET STYLES + ============================================ */ + + .bottom-sheet { + border-top-left-radius: 1rem; + border-top-right-radius: 1rem; + box-shadow: 0 -4px 24px rgb(0 0 0 / 0.15); + } + + .dark .bottom-sheet { + box-shadow: 0 -4px 32px rgb(0 0 0 / 0.4); + } + + .bottom-sheet-handle { + width: 36px; + height: 4px; + border-radius: 2px; + background: hsl(var(--muted-foreground) / 0.3); + margin: 0.75rem auto; + } + + /* ============================================ + ENHANCED MOBILE SCROLLBARS + ============================================ */ + + @media (max-width: 768px) { + .mobile-scrollbar-hide { + scrollbar-width: none; + -ms-overflow-style: none; + } + + .mobile-scrollbar-hide::-webkit-scrollbar { + display: none; + } + } + + /* ============================================ + ACTIVE STATE INDICATORS + ============================================ */ + + .active-indicator { + position: relative; + } + + .active-indicator::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: hsl(var(--primary)); + border-radius: 1px; + transition: width 0.2s ease; + } + + .active-indicator.active::after, + .active-indicator[data-state="active"]::after { + width: 60%; + } + + /* Pill indicator for navigation */ + .pill-indicator { + position: absolute; + inset: 2px; + background: hsl(var(--primary) / 0.1); + border-radius: 9999px; + z-index: -1; + } + + /* ============================================ + CONTAINER QUERIES (Modern Browsers) + ============================================ */ + + @supports (container-type: inline-size) { + .container-query { + container-type: inline-size; + } + + @container (max-width: 400px) { + .container-compact .card-title { + font-size: 0.9375rem; + } + + .container-compact .card-description { + font-size: 0.75rem; + } + } + } + + /* ============================================ + PERFORMANCE OPTIMIZATIONS + ============================================ */ + + /* GPU acceleration for animations */ + .gpu-accelerated { + will-change: transform; + transform: translateZ(0); + } + + /* Disable animations on low-end devices */ + @media (prefers-reduced-motion: reduce) { + + /* Universal reset — honor the OS-level "reduce motion" preference everywhere */ + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + .fab-pulse, + .animate-float, + .animate-float-delayed, + .animate-pulse-glow, + .animate-shimmer, + .gradient-text-animated, + .icon-container-pulse:hover { + animation: none !important; + } + + .swipeable-item, + .scroll-snap-x { + scroll-behavior: auto; + } + } + + /* Disable heavy animations on mobile for battery savings */ + @media (max-width: 768px) { + + .animate-float, + .animate-float-delayed { + animation-play-state: paused; + } + + /* Simpler hover effects on mobile */ + .card-gradient-border:hover::before { + animation: none; + opacity: 0.5; + } + } +} + +/* =========================================================================== + Todo app — editorial productivity theme + Scoped under .todo-app so it never leaks into other tools. + =========================================================================== */ +.todo-app { + --todo-accent: 220 14% 16%; + --todo-accent-soft: 220 14% 16% / 0.08; + --todo-status-not-started: 210 70% 50%; + --todo-status-ongoing: 28 95% 55%; + --todo-status-completed: 145 60% 38%; +} + +.dark .todo-app { + --todo-accent: 40 18% 88%; + --todo-accent-soft: 40 18% 88% / 0.1; +} + +.todo-app .font-display { + font-family: var(--font-display), "Iowan Old Style", Georgia, serif; + letter-spacing: -0.02em; + font-feature-settings: "ss01", "ss02"; +} + +.todo-app .font-meta { + font-family: var(--font-geist-mono), ui-monospace, "SF Mono", monospace; + font-feature-settings: "tnum", "zero"; + letter-spacing: 0; +} + +/* Status dot indicator — replaces pastel column/card backgrounds */ +.todo-app .status-dot { + width: 0.5rem; + height: 0.5rem; + border-radius: 9999px; + display: inline-block; + flex-shrink: 0; +} +.todo-app .status-dot[data-status="not-started"] { background: hsl(var(--todo-status-not-started)); } +.todo-app .status-dot[data-status="ongoing"] { background: hsl(var(--todo-status-ongoing)); } +.todo-app .status-dot[data-status="completed"] { background: hsl(var(--todo-status-completed)); } + +/* Subtle paper grain — atmosphere without noise */ +.todo-app .paper-grain { + position: relative; +} +.todo-app .paper-grain::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background-image: url("data:image/svg+xml;utf8,"); + opacity: 0.025; + mix-blend-mode: multiply; +} +.dark .todo-app .paper-grain::before { + mix-blend-mode: screen; + opacity: 0.04; +} +/* ───────────────────────────────────────────────────────────────────────── + MYDEVTOOLS · DECK DESIGN LAYER (matches the investor-deck aesthetic) + Theme-independent — marketing surfaces always render the dark "deck" look. + ───────────────────────────────────────────────────────────────────────── */ +:root { + --mdt-bg: #08090f; + --mdt-bg2: #0d0f18; + --mdt-surface: #14161f; + --mdt-surface-hi: #1c1f2b; + --mdt-border: #2a2e3d; + --mdt-text: #f4f5fa; + --mdt-muted: #9aa0b4; + --mdt-faint: #5b6076; + --mdt-indigo: #5b63f0; + --mdt-violet: #9a5cf2; + --mdt-cyan: #4fd0e6; + --mdt-green: #3fd79a; + --mdt-amber: #f2a93b; + --mdt-grad: linear-gradient(100deg, #5b63f0 0%, #9a5cf2 52%, #4fd0e6 100%); + --mdt-grad-soft: linear-gradient(100deg, rgba(91,99,240,.16), rgba(154,92,242,.16) 52%, rgba(79,208,230,.16)); +} + +/* dark base retuned to the deck palette */ +.dark { + --background: 232 30% 5%; + --foreground: 228 50% 97%; + --card: 230 24% 9%; + --card-foreground: 228 50% 97%; + --popover: 230 24% 9%; + --popover-foreground: 228 50% 97%; + --muted: 232 26% 10%; + --muted-foreground: 228 16% 70%; + --border: 232 22% 15%; + --input: 232 22% 15%; + --surface-1: 232 30% 6%; + --surface-2: 232 28% 8%; + --surface-3: 232 26% 11%; +} + +/* deck canvas wrapper — forces the dark premium look on marketing pages */ +html { overflow-x: hidden; } +.mdt-deck { + position: relative; + background: var(--mdt-bg); + color: var(--mdt-text); + isolation: isolate; +} +/* ambient color depth fixed behind all content (kills flat grey sections) */ +.mdt-deck::before { + content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none; + background: + radial-gradient(52% 42% at 88% 6%, rgba(122,107,245,.15), transparent 60%), + radial-gradient(48% 40% at 4% 70%, rgba(79,140,235,.12), transparent 60%), + radial-gradient(85% 55% at 52% 118%, rgba(154,92,242,.11), transparent 60%); +} +.mdt-deck ::selection { background: rgba(122,107,245,.35); } + +/* atmospheric backdrop: orbs + grid + grain, behind all content */ +.mdt-backdrop { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; } +.mdt-orb { position: absolute; border-radius: 9999px; filter: blur(40px); opacity: .9; } +.mdt-orb--brand { background: radial-gradient(circle at 35% 35%, rgba(154,92,242,.55), rgba(91,99,240,.28) 45%, transparent 70%); } +.mdt-orb--cyan { background: radial-gradient(circle, rgba(79,208,230,.32), transparent 70%); } +.mdt-orb--blue { background: radial-gradient(circle, rgba(79,140,235,.30), transparent 70%); } +.mdt-grid { + position: absolute; inset: 0; pointer-events: none; + background-image: + linear-gradient(to right, rgba(160,172,255,.045) 1px, transparent 1px), + linear-gradient(to bottom, rgba(160,172,255,.045) 1px, transparent 1px); + background-size: 64px 64px; + -webkit-mask-image: radial-gradient(130% 110% at 0% 0%, #000 0%, transparent 62%); + mask-image: radial-gradient(130% 110% at 0% 0%, #000 0%, transparent 62%); +} +.mdt-noise { + position: absolute; inset: 0; pointer-events: none; opacity: .28; mix-blend-mode: soft-light; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); +} +/* CRT scanlines — status pages (subtle, matches the boot loader) */ +.mdt-status-scanlines { + pointer-events: none; opacity: .5; + background-image: repeating-linear-gradient( + to bottom, + rgba(255, 255, 255, 0.03) 0px, + rgba(255, 255, 255, 0.03) 1px, + transparent 1px, + transparent 3px + ); + mask-image: radial-gradient(ellipse 80% 60% at 50% 45%, #000 30%, transparent 100%); + -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 45%, #000 30%, transparent 100%); +} +@media (prefers-reduced-motion: no-preference) { + .mdt-status-scanlines { animation: mdt-scanline-roll 8s linear infinite; } +} +@keyframes mdt-scanline-roll { + from { background-position: 0 0; } + to { background-position: 0 6px; } +} + +/* content stacks above the backdrop naturally; backdrop sits at z-index:-1. + (No blanket position rule — it would override fixed/sticky children.) */ + +/* typography helpers */ +.mdt-grad-text { + background: var(--mdt-grad); + -webkit-background-clip: text; background-clip: text; + -webkit-text-fill-color: transparent; color: transparent; +} +.mdt-kicker { + font-family: var(--font-geist-mono), ui-monospace, monospace; + text-transform: uppercase; letter-spacing: .22em; + font-size: .78rem; color: var(--mdt-muted); +} +.mdt-mono { font-family: var(--font-geist-mono), ui-monospace, monospace; } +.mdt-text-muted { color: var(--mdt-muted); } +.mdt-text-faint { color: var(--mdt-faint); } + +/* surfaces, rails, pills */ +.mdt-surface { + background: linear-gradient(180deg, rgba(255,255,255,.02), transparent), var(--mdt-surface); + border: 1px solid var(--mdt-border); + border-radius: 18px; +} +.mdt-surface-hi { background: var(--mdt-surface-hi); border: 1px solid var(--mdt-border); border-radius: 18px; } +.mdt-rail { background: var(--mdt-grad); } +.mdt-pill { + display: inline-flex; align-items: center; gap: .5rem; + border: 1px solid var(--mdt-border); border-radius: 9999px; + background: var(--mdt-surface); padding: .35rem .85rem; + font-family: var(--font-geist-mono), monospace; font-size: .8rem; color: var(--mdt-muted); +} +.mdt-card-hover { transition: transform .3s cubic-bezier(.16,1,.3,1), border-color .3s, box-shadow .3s; } +.mdt-card-hover:hover { transform: translateY(-4px); border-color: rgba(122,107,245,.5); box-shadow: 0 20px 60px -20px rgba(91,99,240,.35); } + +/* gradient-stroke icons (uses the hidden ) */ +.mdt-icon-grad svg, svg.mdt-icon-grad { stroke: url(#mdtGrad); } + +/* buttons */ +.mdt-btn-grad { + background: var(--mdt-grad); color: #0a0b12; border: none; + font-weight: 600; transition: filter .2s, transform .2s, box-shadow .2s; + box-shadow: 0 10px 30px -10px rgba(122,107,245,.6); +} +.mdt-btn-grad:hover { filter: brightness(1.08); transform: translateY(-2px); box-shadow: 0 16px 40px -10px rgba(122,107,245,.7); } +.mdt-btn-ghost { + background: rgba(255,255,255,.03); color: var(--mdt-text); + border: 1px solid var(--mdt-border); transition: background .2s, border-color .2s, transform .2s; +} +.mdt-btn-ghost:hover { background: rgba(255,255,255,.06); border-color: rgba(122,107,245,.5); transform: translateY(-2px); } + +/* gradient hairline divider */ +.mdt-hr { height: 1px; border: 0; background: linear-gradient(90deg, transparent, var(--mdt-border) 20%, var(--mdt-border) 80%, transparent); } + +/* ─── DECK FX: futuristic motion + interaction (scoped to .mdt-deck) ───────── */ +@property --mdt-angle { syntax: ""; initial-value: 0deg; inherits: false; } + +/* cursor-following gradient border on every surface card */ +.mdt-deck .glass-overlay { position: relative; } +.mdt-deck .glass-overlay::before { + content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px; + pointer-events: none; z-index: 3; + background: radial-gradient(180px circle at var(--mx, 50%) var(--my, 50%), rgba(150,160,255,.9), rgba(122,107,245,.18) 45%, transparent 66%); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + -webkit-mask-composite: xor; mask-composite: exclude; + opacity: 0; transition: opacity .4s ease; +} +.mdt-deck .glass-overlay:hover::before { opacity: 1; } + +/* animated gradient border-beam for a featured card */ +.mdt-deck .mdt-beam { position: relative; } +.mdt-deck .mdt-beam::after { + content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1.5px; + pointer-events: none; z-index: 3; + background: conic-gradient(from var(--mdt-angle), transparent 0 62%, #5b63f0 78%, #9a5cf2 88%, #4fd0e6 96%, transparent 100%); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + -webkit-mask-composite: xor; mask-composite: exclude; + animation: mdt-spin 6s linear infinite; +} +@keyframes mdt-spin { to { --mdt-angle: 360deg; } } + +/* ── Branded app loading screen (theme-aware) ───────────── */ +.mdt-loading__aura { + position: absolute; inset: 0; pointer-events: none; + background: + radial-gradient(40rem 28rem at 50% 42%, hsl(var(--primary) / 0.10), transparent 70%), + radial-gradient(26rem 26rem at 66% 66%, rgba(154, 92, 242, 0.10), transparent 70%); +} +.mdt-loading__ring { + position: absolute; width: 8.5rem; height: 8.5rem; border-radius: 50%; + background: conic-gradient(from var(--mdt-angle), transparent 0 58%, #5b63f0 74%, #9a5cf2 86%, #4fd0e6 95%, transparent 100%); + -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 3px)); + mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 3px)); + animation: mdt-spin 1.4s linear infinite; + filter: drop-shadow(0 0 10px rgba(122, 107, 245, 0.35)); +} +.mdt-loading__bar { + position: relative; display: block; width: 11rem; height: 3px; border-radius: 999px; + overflow: hidden; background: hsl(var(--foreground) / 0.08); +} +.mdt-loading__bar > span { + position: absolute; inset: 0; width: 40%; border-radius: inherit; + background: var(--mdt-grad); + animation: mdt-loading-slide 1.25s cubic-bezier(0.65, 0, 0.35, 1) infinite; +} +@keyframes mdt-loading-slide { + 0% { transform: translateX(-110%); } + 100% { transform: translateX(360%); } +} +.mdt-loading__dots::after { + content: "…"; display: inline-block; width: 1.2ch; text-align: left; + animation: mdt-loading-dots 1.4s steps(1, end) infinite; +} +@keyframes mdt-loading-dots { + 0% { content: ""; } 33% { content: "·"; } 66% { content: "··"; } 100% { content: "···"; } +} +@media (prefers-reduced-motion: reduce) { + .mdt-loading__ring { animation-duration: 3s; } + .mdt-loading__bar > span { animation: mdt-loading-pulse 1.6s ease-in-out infinite; width: 100%; } + .mdt-loading__dots::after { animation: none; content: "…"; } +} +@keyframes mdt-loading-pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } } + +/* Compact branded spinner for per-tool lazy loading */ +.mdt-tool-spinner { + width: 2.25rem; height: 2.25rem; border-radius: 50%; + background: conic-gradient(from var(--mdt-angle), transparent 0 55%, #5b63f0 74%, #9a5cf2 88%, #4fd0e6 96%, transparent 100%); + -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2px)); + mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2px)); + animation: mdt-spin 1.1s linear infinite; +} +@media (prefers-reduced-motion: reduce) { .mdt-tool-spinner { animation-duration: 2.4s; } } + +/* scroll-progress beam */ +.mdt-progress { position: fixed; top: 0; left: 0; right: 0; height: 2px; transform-origin: 0 50%; background: var(--mdt-grad); z-index: 60; } + +/* slow sheen across gradient headline text */ +.mdt-grad-anim { background-size: 220% 100%; animation: mdt-sheen 9s ease-in-out infinite; } +@keyframes mdt-sheen { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } } + +/* aurora canvas in hero */ +.mdt-aurora { position: absolute; inset: 0; width: 100%; height: 100%; filter: blur(72px) saturate(1.25); opacity: .5; mix-blend-mode: screen; pointer-events: none; } + +/* infinite tool-name ticker */ +.mdt-marquee { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent); mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent); } +.mdt-marquee__track { display: inline-flex; gap: 2.5rem; padding-right: 2.5rem; white-space: nowrap; animation: mdt-scroll 45s linear infinite; will-change: transform; } +.mdt-marquee:hover .mdt-marquee__track { animation-play-state: paused; } +@keyframes mdt-scroll { to { transform: translateX(-50%); } } + +@media (prefers-reduced-motion: reduce) { + .mdt-marquee__track, .mdt-grad-anim, .mdt-deck .mdt-beam::after { animation: none !important; } +} + +/* ─── DECK MOTION TOKENS (from motion-design skill) ───────────────────────── */ +:root { + --dur-1: 120ms; --dur-2: 180ms; --dur-3: 240ms; --dur-4: 300ms; --dur-5: 500ms; + --ease-out-quart: cubic-bezier(.165, .84, .44, 1); + --ease-out-quint: cubic-bezier(.23, 1, .32, 1); + --ease-out-expo: cubic-bezier(.19, 1, .22, 1); + --ease-in-out-cubic: cubic-bezier(.645, .045, .355, 1); +} + +/* living background: slow grid drift + slightly richer aurora */ +.mdt-grid { animation: mdt-grid-drift 64s linear infinite; } +@keyframes mdt-grid-drift { to { background-position: 64px 64px, 64px 64px; } } +.mdt-aurora { opacity: .6; } + +/* card hover easing aligned to tokens (transform/shadow only) */ +.mdt-card-hover { transition: transform var(--dur-3) var(--ease-out-quart), border-color var(--dur-3) ease, box-shadow var(--dur-3) ease; } + +/* header scroll-translucent transition (subtle state change) */ +.mdt-nav { transition: background-color var(--dur-3) ease, backdrop-filter var(--dur-3) ease, border-color var(--dur-3) ease, box-shadow var(--dur-3) ease; } + +/* accessibility: honor reduced-motion globally */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: .01ms !important; + animation-iteration-count: 1 !important; + transition-duration: .01ms !important; + scroll-behavior: auto !important; + } +} + +/* ─── CRT BOOT LOADER (monitor illustration) ──────────────────────────────── */ +.crt-boot { position: fixed; inset: 0; z-index: 200; display: grid; place-items: center; overflow: hidden; + background: radial-gradient(120% 120% at 50% 28%, #0c0e16 0%, #050608 58%, #030305 100%); } +.crt-boot--out { animation: crt-out 600ms cubic-bezier(.19,1,.22,1) forwards; } +@keyframes crt-out { to { opacity: 0; transform: scale(1.06); filter: blur(8px); visibility: hidden; } } + +.crt { width: min(540px, 84vw); transform: perspective(1500px) rotateX(2deg); } +.crt__monitor { position: relative; border-radius: 26px; padding: 24px 24px 28px; + background: linear-gradient(180deg, #262932, #16181f 58%, #0f1116); + box-shadow: 0 44px 100px -34px rgba(0,0,0,.92), inset 0 1px 0 rgba(255,255,255,.07), inset 0 -3px 10px rgba(0,0,0,.6); } +.crt__screen { position: relative; aspect-ratio: 4 / 3; border-radius: 14px; overflow: hidden; padding: 18px 18px 16px; + background: radial-gradient(120% 120% at 50% 42%, #0a1510 0%, #060d0a 68%, #040706 100%); + box-shadow: inset 0 0 70px rgba(0,0,0,.9), inset 0 0 0 2px rgba(0,0,0,.55), 0 0 34px rgba(94,242,160,.1); } +.crt__screen::before { content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none; + background: repeating-linear-gradient(0deg, transparent 0 2px, rgba(0,0,0,.3) 2px 3px); } +.crt__screen::after { content: ""; position: absolute; inset: 0; z-index: 6; pointer-events: none; + background: radial-gradient(130% 80% at 50% -10%, rgba(160,210,255,.14), transparent 55%); } +.crt__flicker { animation: crt-flicker 3.6s infinite steps(60); } +@keyframes crt-flicker { 0%,100%{opacity:1} 8%{opacity:.92} 10%{opacity:1} 50%{opacity:.96} 52%{opacity:1} } +.crt__code { position: relative; z-index: 2; height: 100%; font-family: var(--font-geist-mono), monospace; + font-size: clamp(10px, 1.5vw, 13px); line-height: 1.72; color: #5ef2a0; text-shadow: 0 0 6px rgba(94,242,160,.5); } +.crt__line { white-space: pre-wrap; word-break: break-word; } +.crt__accent { color: #7fd7ff; text-shadow: 0 0 6px rgba(127,215,255,.6); } +.crt__cursor { display: inline-block; width: 7px; height: 1em; transform: translateY(2px); + background: #5ef2a0; box-shadow: 0 0 8px #5ef2a0; animation: crt-blink 1s steps(2) infinite; } +@keyframes crt-blink { 50% { opacity: 0; } } +.crt__brand { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; padding: 0 6px; } +.crt__brand span { font-family: var(--font-geist-mono), monospace; font-size: 10px; letter-spacing: .34em; color: #5b6076; } +.crt__led { width: 8px; height: 8px; border-radius: 50%; background: #5ef2a0; box-shadow: 0 0 10px #5ef2a0; animation: crt-blink 1.4s steps(2) infinite; } +.crt__neck { width: 78px; height: 24px; margin: 0 auto; background: linear-gradient(180deg, #1a1c22, #101116); } +.crt__base { width: 196px; height: 14px; margin: 0 auto; border-radius: 0 0 14px 14px; + background: linear-gradient(180deg, #1d1f26, #0e0f14); box-shadow: 0 20px 44px -18px rgba(0,0,0,.9); } +@media (prefers-reduced-motion: reduce) { + .crt__flicker, .crt__cursor, .crt__led { animation: none !important; } + .crt { transform: none; } +} + + +/* dashboard editor caret */ +.mdt-dash-caret { background: var(--mdt-cyan); box-shadow: 0 0 8px var(--mdt-cyan); border-radius: 1px; animation: mdt-caret-blink 1.1s steps(2) infinite; } +@keyframes mdt-caret-blink { 50% { opacity: 0; } } + +/* ============================================ + iOS INPUT AUTO-ZOOM PREVENTION + ============================================ */ +@media (max-width: 640px) { + input, + textarea, + select { + font-size: 16px !important; + } +} diff --git a/apps/desktop-ui/src/app/layout.tsx b/apps/desktop-ui/src/app/layout.tsx new file mode 100644 index 00000000..390f2741 --- /dev/null +++ b/apps/desktop-ui/src/app/layout.tsx @@ -0,0 +1,135 @@ +import type { Metadata } from "next"; +import { ThemeProvider } from "@/components/theme-provider" +import { cn } from "@/lib/utils" +import { GeistSans } from 'geist/font/sans' +import { GeistMono } from 'geist/font/mono' + +import { NextIntlClientProvider } from 'next-intl'; +import { getLocale, getMessages } from 'next-intl/server'; +import { ClientShell } from "@/components/client-shell"; +import { DesktopInit } from "@/components/desktop/desktop-init"; + +import "./globals.css"; + + +import { siteMetadata } from "@/lib/metadata" +import { SiteWideJsonLd } from "@/components/seo/site-wide-json-ld" + +export const metadata: Metadata = { + metadataBase: new URL(siteMetadata.url), + title: { + default: siteMetadata.title, + template: '%s | MyDevTools' + }, + description: siteMetadata.description, + authors: [{ name: 'MyDevTools' }], + creator: 'MyDevTools', + publisher: 'MyDevTools', + formatDetection: { + email: false, + address: false, + telephone: false, + }, + openGraph: { + type: 'website', + locale: 'en_US', + url: siteMetadata.url, + title: siteMetadata.title, + description: siteMetadata.description, + siteName: siteMetadata.name, + images: [ + { + url: siteMetadata.ogImage, + width: 1200, + height: 630, + alt: siteMetadata.title, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: siteMetadata.title, + description: siteMetadata.description, + images: [siteMetadata.ogImage], + creator: '@mydevtools', + }, + alternates: { + canonical: siteMetadata.url, + }, + manifest: '/manifest.json', + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, + }, + icons: { + icon: '/favicon.ico', + shortcut: '/favicon.ico', + apple: '/icon-192x192.png', + other: { + rel: 'apple-touch-icon-precomposed', + url: '/icon-192x192.png', + }, + }, +}; + +export const viewport = { + themeColor: [ + { media: '(prefers-color-scheme: light)', color: 'white' }, + { media: '(prefers-color-scheme: dark)', color: 'black' }, + ], + width: 'device-width', + initialScale: 1, + viewportFit: 'cover', +} + +export default async function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const locale = await getLocale(); + const messages = await getMessages(); + const dir = locale === 'ar' || locale === 'fa' ? 'rtl' : 'ltr'; + const htmlLang = locale === 'zh' ? 'zh-Hans' : locale; + + return ( + + + {/* Firebase Auth & token refresh */} + + + {/* Firebase Storage */} + + {/* Vercel Speed Insights */} + + + + + + + + + + {children} + + + + + + ); +} diff --git a/apps/desktop-ui/src/app/login/page.tsx b/apps/desktop-ui/src/app/login/page.tsx new file mode 100644 index 00000000..b8dd304f --- /dev/null +++ b/apps/desktop-ui/src/app/login/page.tsx @@ -0,0 +1,148 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { ArrowLeft, Cloud, ShieldCheck, Zap } from "lucide-react"; +import { LegalAgreementFooter } from "@/components/legal-agreement-footer"; +import { LoginForm } from "@/components/login-form"; +import { LoginRedirectIfAuthed } from "@/components/login-redirect-if-authed"; +import { HideOnDesktop } from "@/components/desktop/hide-on-desktop"; +import { Logo } from "@/components/logo"; +import { Magnetic } from "@/components/mdt-magnetic"; +import MdtAurora from "@/components/mdt-aurora"; + +const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://mydevtools.tech"; + +export const metadata: Metadata = { + title: "Login - MyDevTools", + description: "Login to access your developer tools and workspace", + alternates: { canonical: `${baseUrl}/login` }, +}; + +const FEATURES = [ + { + icon: Zap, + title: "80+ tools, one app", + body: "Formatters, generators, API client, SQL, crypto — all in one place.", + }, + { + icon: Cloud, + title: "Completely offline", + body: "Runs fully on your device. No network needed once you're in.", + }, + { + icon: ShieldCheck, + title: "Client-side AES encryption", + body: "Data is AES-256 encrypted and never leaves your device — unless you back it up.", + }, +]; + +const TOOL_PILLS = [ + "JSON", + "JWT", + "Regex", + "Base64", + "UUID", + "API Client", + "SQL", + "GraphQL", +]; + +export default function LoginPage() { + return ( +
+ + + {/* deck backdrop */} +
+ +
+
+
+
+ + + + + + + + + +
+ +
+ + + + + Back to home + + + +
+ +
+ {/* ── Showcase (desktop only) ── */} +
+

Welcome back

+

+ Your entire dev toolkit,{" "} + one app away. +

+

+ Sign in to sync your work across devices and pick up exactly where you left off. +

+ +
    + {FEATURES.map(({ icon: Icon, title, body }) => ( +
  • + + + +
    +

    {title}

    +

    {body}

    +
    +
  • + ))} +
+ +
+ {TOOL_PILLS.map((pill) => ( + + {pill} + + ))} +
+
+ + {/* ── Login card ── */} +
+
+
+ +
+ +

+ Sign in to MyDevTools +

+

+ Continue with your preferred provider to open your workspace. +

+
+ + + +

+ No passwords. One-click OAuth — we never see your credentials. +

+
+ + +
+
+
+ ); +} diff --git a/apps/desktop-ui/src/app/not-found.tsx b/apps/desktop-ui/src/app/not-found.tsx new file mode 100644 index 00000000..4b5ef6b5 --- /dev/null +++ b/apps/desktop-ui/src/app/not-found.tsx @@ -0,0 +1,34 @@ +import Link from 'next/link' +import { ArrowRight, Home } from 'lucide-react' +import { MdtStatusPage } from '@/components/mdt-status-page' + +export default function NotFound() { + return ( + + + + Back home + + + Browse all tools + + + + ) +} diff --git a/apps/desktop-ui/src/app/page.tsx b/apps/desktop-ui/src/app/page.tsx new file mode 100644 index 00000000..4c811955 --- /dev/null +++ b/apps/desktop-ui/src/app/page.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; + +/** + * Desktop launch route: no marketing surface in the app bundle. Route to the + * one-time activation screen until the app is activated, then straight to the + * tools. Offline-first — no network in this decision. + */ +export default function Page() { + const router = useRouter(); + useEffect(() => { + void (async () => { + const { localApi } = await import("@/lib/desktop/bridge"); + const activated = (await localApi("GET", "/desktop/activation")).status === 200; + router.replace(activated ? "/dashboard" : "/activate"); + })(); + }, [router]); + return
; +} diff --git a/apps/web/src/app/settings/layout.tsx b/apps/desktop-ui/src/app/settings/layout.tsx similarity index 100% rename from apps/web/src/app/settings/layout.tsx rename to apps/desktop-ui/src/app/settings/layout.tsx diff --git a/apps/web/src/app/settings/page.tsx b/apps/desktop-ui/src/app/settings/page.tsx similarity index 96% rename from apps/web/src/app/settings/page.tsx rename to apps/desktop-ui/src/app/settings/page.tsx index 8275ed84..cf24cccc 100644 --- a/apps/web/src/app/settings/page.tsx +++ b/apps/desktop-ui/src/app/settings/page.tsx @@ -10,11 +10,9 @@ import { cn } from '@/lib/utils' import { useLocale, useTranslations } from 'next-intl' import { useRouter } from 'next/navigation' import { COLOR_THEME_OPTIONS, type ColorTheme, useColorTheme } from '@/hooks/use-color-theme' -import { PasskeySection } from '@/components/settings/passkey-section' -import { DesktopSyncSettings } from '@/components/desktop/desktop-sync-settings' -import { DesktopSyncConflicts } from '@/components/desktop/desktop-sync-conflicts' import { DesktopBackupSettings } from '@/components/desktop/desktop-backup-settings' import { DesktopUpdateSettings } from '@/components/desktop/desktop-update-settings' +import { DesktopPlanSettings } from '@/components/desktop/desktop-plan-settings' import { useActiveWorkspace } from '@/store/workspace-store' import { Briefcase } from 'lucide-react' const colorDisplay: Record = { @@ -70,16 +68,12 @@ export default function SettingsPage() {
- + - - - - diff --git a/apps/web/src/app/settings/settings-client-layout.tsx b/apps/desktop-ui/src/app/settings/settings-client-layout.tsx similarity index 100% rename from apps/web/src/app/settings/settings-client-layout.tsx rename to apps/desktop-ui/src/app/settings/settings-client-layout.tsx diff --git a/apps/web/src/app/settings/workspaces/page.tsx b/apps/desktop-ui/src/app/settings/workspaces/page.tsx similarity index 100% rename from apps/web/src/app/settings/workspaces/page.tsx rename to apps/desktop-ui/src/app/settings/workspaces/page.tsx diff --git a/apps/web/src/components/__tests__/migration-banner.test.ts b/apps/desktop-ui/src/components/__tests__/migration-banner.test.ts similarity index 100% rename from apps/web/src/components/__tests__/migration-banner.test.ts rename to apps/desktop-ui/src/components/__tests__/migration-banner.test.ts diff --git a/apps/web/src/components/__tests__/pinned-tools-preferences-sync.test.tsx b/apps/desktop-ui/src/components/__tests__/pinned-tools-preferences-sync.test.tsx similarity index 100% rename from apps/web/src/components/__tests__/pinned-tools-preferences-sync.test.tsx rename to apps/desktop-ui/src/components/__tests__/pinned-tools-preferences-sync.test.tsx diff --git a/apps/desktop-ui/src/components/announcement-banner.tsx b/apps/desktop-ui/src/components/announcement-banner.tsx new file mode 100644 index 00000000..9ad515c2 --- /dev/null +++ b/apps/desktop-ui/src/components/announcement-banner.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Link from "next/link"; +import { ArrowRight, X } from "lucide-react"; + +const DISMISS_KEY = "mdt-launch-offer-dismissed"; + +/** + * Slim launch-offer strip above the marketing header. Dismissible per browser + * (localStorage). Rendered on marketing pages only via
. + */ +export function AnnouncementBanner() { + // Start hidden to avoid a flash for users who already dismissed it. + const [visible, setVisible] = useState(false); + + useEffect(() => { + try { + if (!localStorage.getItem(DISMISS_KEY)) setVisible(true); + } catch { + setVisible(true); + } + }, []); + + const dismiss = () => { + setVisible(false); + try { + localStorage.setItem(DISMISS_KEY, "1"); + } catch { + /* ignore */ + } + }; + + if (!visible) return null; + + return ( +
+
+ + Launch offer + +

+ First 250 accounts get every Pro & Team feature, + free. No card needed. +

+ + Claim yours + + +
+
+ ); +} diff --git a/apps/web/src/components/api-client/__tests__/collection-item.memo.test.tsx b/apps/desktop-ui/src/components/api-client/__tests__/collection-item.memo.test.tsx similarity index 100% rename from apps/web/src/components/api-client/__tests__/collection-item.memo.test.tsx rename to apps/desktop-ui/src/components/api-client/__tests__/collection-item.memo.test.tsx diff --git a/apps/web/src/components/api-client/__tests__/collections-context.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/collections-context.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/collections-context.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/collections-context.test.ts diff --git a/apps/web/src/components/api-client/__tests__/environments-context.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/environments-context.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/environments-context.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/environments-context.test.ts diff --git a/apps/web/src/components/api-client/__tests__/generate-code.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/generate-code.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/generate-code.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/generate-code.test.ts diff --git a/apps/web/src/components/api-client/__tests__/history-context.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/history-context.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/history-context.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/history-context.test.ts diff --git a/apps/web/src/components/api-client/__tests__/response-panel.large-body.test.tsx b/apps/desktop-ui/src/components/api-client/__tests__/response-panel.large-body.test.tsx similarity index 100% rename from apps/web/src/components/api-client/__tests__/response-panel.large-body.test.tsx rename to apps/desktop-ui/src/components/api-client/__tests__/response-panel.large-body.test.tsx diff --git a/apps/web/src/components/api-client/__tests__/scripts-runner.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/scripts-runner.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/scripts-runner.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/scripts-runner.test.ts diff --git a/apps/web/src/components/api-client/__tests__/security-batch1.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/security-batch1.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/security-batch1.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/security-batch1.test.ts diff --git a/apps/web/src/components/api-client/__tests__/security-batch3.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/security-batch3.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/security-batch3.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/security-batch3.test.ts diff --git a/apps/web/src/components/api-client/__tests__/security-batch4.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/security-batch4.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/security-batch4.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/security-batch4.test.ts diff --git a/apps/web/src/components/api-client/__tests__/tabs-context.test.tsx b/apps/desktop-ui/src/components/api-client/__tests__/tabs-context.test.tsx similarity index 100% rename from apps/web/src/components/api-client/__tests__/tabs-context.test.tsx rename to apps/desktop-ui/src/components/api-client/__tests__/tabs-context.test.tsx diff --git a/apps/web/src/components/api-client/__tests__/use-json-formatter.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/use-json-formatter.test.ts similarity index 100% rename from apps/web/src/components/api-client/__tests__/use-json-formatter.test.ts rename to apps/desktop-ui/src/components/api-client/__tests__/use-json-formatter.test.ts diff --git a/apps/web/src/components/api-client/__tests__/virtual-history-list.test.tsx b/apps/desktop-ui/src/components/api-client/__tests__/virtual-history-list.test.tsx similarity index 100% rename from apps/web/src/components/api-client/__tests__/virtual-history-list.test.tsx rename to apps/desktop-ui/src/components/api-client/__tests__/virtual-history-list.test.tsx diff --git a/apps/web/src/components/api-client/api-client.tsx b/apps/desktop-ui/src/components/api-client/api-client.tsx similarity index 100% rename from apps/web/src/components/api-client/api-client.tsx rename to apps/desktop-ui/src/components/api-client/api-client.tsx diff --git a/apps/web/src/components/api-client/code-generator.tsx b/apps/desktop-ui/src/components/api-client/code-generator.tsx similarity index 100% rename from apps/web/src/components/api-client/code-generator.tsx rename to apps/desktop-ui/src/components/api-client/code-generator.tsx diff --git a/apps/web/src/components/api-client/collection-runner-dialog.tsx b/apps/desktop-ui/src/components/api-client/collection-runner-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/collection-runner-dialog.tsx rename to apps/desktop-ui/src/components/api-client/collection-runner-dialog.tsx diff --git a/apps/web/src/components/api-client/collections/collection-item.tsx b/apps/desktop-ui/src/components/api-client/collections/collection-item.tsx similarity index 100% rename from apps/web/src/components/api-client/collections/collection-item.tsx rename to apps/desktop-ui/src/components/api-client/collections/collection-item.tsx diff --git a/apps/web/src/components/api-client/collections/collections-sidebar.tsx b/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx similarity index 100% rename from apps/web/src/components/api-client/collections/collections-sidebar.tsx rename to apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx diff --git a/apps/web/src/components/api-client/collections/save-request-dialog.tsx b/apps/desktop-ui/src/components/api-client/collections/save-request-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/collections/save-request-dialog.tsx rename to apps/desktop-ui/src/components/api-client/collections/save-request-dialog.tsx diff --git a/apps/web/src/components/api-client/collections/use-collections.ts b/apps/desktop-ui/src/components/api-client/collections/use-collections.ts similarity index 100% rename from apps/web/src/components/api-client/collections/use-collections.ts rename to apps/desktop-ui/src/components/api-client/collections/use-collections.ts diff --git a/apps/web/src/components/api-client/collections/virtual-history-list.tsx b/apps/desktop-ui/src/components/api-client/collections/virtual-history-list.tsx similarity index 100% rename from apps/web/src/components/api-client/collections/virtual-history-list.tsx rename to apps/desktop-ui/src/components/api-client/collections/virtual-history-list.tsx diff --git a/apps/web/src/components/api-client/comments-panel.tsx b/apps/desktop-ui/src/components/api-client/comments-panel.tsx similarity index 100% rename from apps/web/src/components/api-client/comments-panel.tsx rename to apps/desktop-ui/src/components/api-client/comments-panel.tsx diff --git a/apps/web/src/components/api-client/context/api-client-context.tsx b/apps/desktop-ui/src/components/api-client/context/api-client-context.tsx similarity index 100% rename from apps/web/src/components/api-client/context/api-client-context.tsx rename to apps/desktop-ui/src/components/api-client/context/api-client-context.tsx diff --git a/apps/web/src/components/api-client/context/collections-context.tsx b/apps/desktop-ui/src/components/api-client/context/collections-context.tsx similarity index 100% rename from apps/web/src/components/api-client/context/collections-context.tsx rename to apps/desktop-ui/src/components/api-client/context/collections-context.tsx diff --git a/apps/web/src/components/api-client/context/environments-context.tsx b/apps/desktop-ui/src/components/api-client/context/environments-context.tsx similarity index 100% rename from apps/web/src/components/api-client/context/environments-context.tsx rename to apps/desktop-ui/src/components/api-client/context/environments-context.tsx diff --git a/apps/web/src/components/api-client/context/history-context.tsx b/apps/desktop-ui/src/components/api-client/context/history-context.tsx similarity index 100% rename from apps/web/src/components/api-client/context/history-context.tsx rename to apps/desktop-ui/src/components/api-client/context/history-context.tsx diff --git a/apps/web/src/components/api-client/context/tabs-context.tsx b/apps/desktop-ui/src/components/api-client/context/tabs-context.tsx similarity index 100% rename from apps/web/src/components/api-client/context/tabs-context.tsx rename to apps/desktop-ui/src/components/api-client/context/tabs-context.tsx diff --git a/apps/web/src/components/api-client/context/workspaces-context.tsx b/apps/desktop-ui/src/components/api-client/context/workspaces-context.tsx similarity index 100% rename from apps/web/src/components/api-client/context/workspaces-context.tsx rename to apps/desktop-ui/src/components/api-client/context/workspaces-context.tsx diff --git a/apps/web/src/components/api-client/cookie-jar-dialog.tsx b/apps/desktop-ui/src/components/api-client/cookie-jar-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/cookie-jar-dialog.tsx rename to apps/desktop-ui/src/components/api-client/cookie-jar-dialog.tsx diff --git a/apps/web/src/components/api-client/display-name.ts b/apps/desktop-ui/src/components/api-client/display-name.ts similarity index 100% rename from apps/web/src/components/api-client/display-name.ts rename to apps/desktop-ui/src/components/api-client/display-name.ts diff --git a/apps/web/src/components/api-client/environment-manager.tsx b/apps/desktop-ui/src/components/api-client/environment-manager.tsx similarity index 100% rename from apps/web/src/components/api-client/environment-manager.tsx rename to apps/desktop-ui/src/components/api-client/environment-manager.tsx diff --git a/apps/web/src/components/api-client/folder-defaults-dialog.tsx b/apps/desktop-ui/src/components/api-client/folder-defaults-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/folder-defaults-dialog.tsx rename to apps/desktop-ui/src/components/api-client/folder-defaults-dialog.tsx diff --git a/apps/web/src/components/api-client/fuzz-run-dialog.tsx b/apps/desktop-ui/src/components/api-client/fuzz-run-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/fuzz-run-dialog.tsx rename to apps/desktop-ui/src/components/api-client/fuzz-run-dialog.tsx diff --git a/apps/web/src/components/api-client/generate-code.ts b/apps/desktop-ui/src/components/api-client/generate-code.ts similarity index 100% rename from apps/web/src/components/api-client/generate-code.ts rename to apps/desktop-ui/src/components/api-client/generate-code.ts diff --git a/apps/web/src/components/api-client/grpc-panel.tsx b/apps/desktop-ui/src/components/api-client/grpc-panel.tsx similarity index 100% rename from apps/web/src/components/api-client/grpc-panel.tsx rename to apps/desktop-ui/src/components/api-client/grpc-panel.tsx diff --git a/apps/web/src/components/api-client/help-shortcuts-dialog.tsx b/apps/desktop-ui/src/components/api-client/help-shortcuts-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/help-shortcuts-dialog.tsx rename to apps/desktop-ui/src/components/api-client/help-shortcuts-dialog.tsx diff --git a/apps/web/src/components/api-client/import-curl-dialog.tsx b/apps/desktop-ui/src/components/api-client/import-curl-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/import-curl-dialog.tsx rename to apps/desktop-ui/src/components/api-client/import-curl-dialog.tsx diff --git a/apps/web/src/components/api-client/import-dialog.tsx b/apps/desktop-ui/src/components/api-client/import-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/import-dialog.tsx rename to apps/desktop-ui/src/components/api-client/import-dialog.tsx diff --git a/apps/web/src/components/api-client/json-visualisers.tsx b/apps/desktop-ui/src/components/api-client/json-visualisers.tsx similarity index 100% rename from apps/web/src/components/api-client/json-visualisers.tsx rename to apps/desktop-ui/src/components/api-client/json-visualisers.tsx diff --git a/apps/web/src/components/api-client/key-value-editor.tsx b/apps/desktop-ui/src/components/api-client/key-value-editor.tsx similarity index 100% rename from apps/web/src/components/api-client/key-value-editor.tsx rename to apps/desktop-ui/src/components/api-client/key-value-editor.tsx diff --git a/apps/web/src/components/api-client/metrics-dialog.tsx b/apps/desktop-ui/src/components/api-client/metrics-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/metrics-dialog.tsx rename to apps/desktop-ui/src/components/api-client/metrics-dialog.tsx diff --git a/apps/web/src/components/api-client/offline-indicator.tsx b/apps/desktop-ui/src/components/api-client/offline-indicator.tsx similarity index 100% rename from apps/web/src/components/api-client/offline-indicator.tsx rename to apps/desktop-ui/src/components/api-client/offline-indicator.tsx diff --git a/apps/web/src/components/api-client/perf-run-dialog.tsx b/apps/desktop-ui/src/components/api-client/perf-run-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/perf-run-dialog.tsx rename to apps/desktop-ui/src/components/api-client/perf-run-dialog.tsx diff --git a/apps/web/src/components/api-client/plugins-dialog.tsx b/apps/desktop-ui/src/components/api-client/plugins-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/plugins-dialog.tsx rename to apps/desktop-ui/src/components/api-client/plugins-dialog.tsx diff --git a/apps/web/src/components/api-client/public-mocks-dialog.tsx b/apps/desktop-ui/src/components/api-client/public-mocks-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/public-mocks-dialog.tsx rename to apps/desktop-ui/src/components/api-client/public-mocks-dialog.tsx diff --git a/apps/web/src/components/api-client/recorder-dialog.tsx b/apps/desktop-ui/src/components/api-client/recorder-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/recorder-dialog.tsx rename to apps/desktop-ui/src/components/api-client/recorder-dialog.tsx diff --git a/apps/web/src/components/api-client/request-panel.tsx b/apps/desktop-ui/src/components/api-client/request-panel.tsx similarity index 100% rename from apps/web/src/components/api-client/request-panel.tsx rename to apps/desktop-ui/src/components/api-client/request-panel.tsx diff --git a/apps/web/src/components/api-client/request-tabs.tsx b/apps/desktop-ui/src/components/api-client/request-tabs.tsx similarity index 100% rename from apps/web/src/components/api-client/request-tabs.tsx rename to apps/desktop-ui/src/components/api-client/request-tabs.tsx diff --git a/apps/web/src/components/api-client/response-panel.tsx b/apps/desktop-ui/src/components/api-client/response-panel.tsx similarity index 100% rename from apps/web/src/components/api-client/response-panel.tsx rename to apps/desktop-ui/src/components/api-client/response-panel.tsx diff --git a/apps/web/src/components/api-client/save-example-dialog.tsx b/apps/desktop-ui/src/components/api-client/save-example-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/save-example-dialog.tsx rename to apps/desktop-ui/src/components/api-client/save-example-dialog.tsx diff --git a/apps/web/src/components/api-client/skeletons.tsx b/apps/desktop-ui/src/components/api-client/skeletons.tsx similarity index 100% rename from apps/web/src/components/api-client/skeletons.tsx rename to apps/desktop-ui/src/components/api-client/skeletons.tsx diff --git a/apps/web/src/components/api-client/tab-bar.tsx b/apps/desktop-ui/src/components/api-client/tab-bar.tsx similarity index 100% rename from apps/web/src/components/api-client/tab-bar.tsx rename to apps/desktop-ui/src/components/api-client/tab-bar.tsx diff --git a/apps/web/src/components/api-client/truncate-body.ts b/apps/desktop-ui/src/components/api-client/truncate-body.ts similarity index 100% rename from apps/web/src/components/api-client/truncate-body.ts rename to apps/desktop-ui/src/components/api-client/truncate-body.ts diff --git a/apps/web/src/components/api-client/types.ts b/apps/desktop-ui/src/components/api-client/types.ts similarity index 100% rename from apps/web/src/components/api-client/types.ts rename to apps/desktop-ui/src/components/api-client/types.ts diff --git a/apps/web/src/components/api-client/use-environments.ts b/apps/desktop-ui/src/components/api-client/use-environments.ts similarity index 100% rename from apps/web/src/components/api-client/use-environments.ts rename to apps/desktop-ui/src/components/api-client/use-environments.ts diff --git a/apps/web/src/components/api-client/use-history.ts b/apps/desktop-ui/src/components/api-client/use-history.ts similarity index 100% rename from apps/web/src/components/api-client/use-history.ts rename to apps/desktop-ui/src/components/api-client/use-history.ts diff --git a/apps/web/src/components/api-client/use-json-validation.ts b/apps/desktop-ui/src/components/api-client/use-json-validation.ts similarity index 100% rename from apps/web/src/components/api-client/use-json-validation.ts rename to apps/desktop-ui/src/components/api-client/use-json-validation.ts diff --git a/apps/web/src/components/api-client/use-workspaces.ts b/apps/desktop-ui/src/components/api-client/use-workspaces.ts similarity index 100% rename from apps/web/src/components/api-client/use-workspaces.ts rename to apps/desktop-ui/src/components/api-client/use-workspaces.ts diff --git a/apps/web/src/components/api-client/websocket-panel.tsx b/apps/desktop-ui/src/components/api-client/websocket-panel.tsx similarity index 100% rename from apps/web/src/components/api-client/websocket-panel.tsx rename to apps/desktop-ui/src/components/api-client/websocket-panel.tsx diff --git a/apps/web/src/components/api-client/workers/json-formatter.worker.ts b/apps/desktop-ui/src/components/api-client/workers/json-formatter.worker.ts similarity index 100% rename from apps/web/src/components/api-client/workers/json-formatter.worker.ts rename to apps/desktop-ui/src/components/api-client/workers/json-formatter.worker.ts diff --git a/apps/web/src/components/api-client/workers/scripts-runner.worker.ts b/apps/desktop-ui/src/components/api-client/workers/scripts-runner.worker.ts similarity index 100% rename from apps/web/src/components/api-client/workers/scripts-runner.worker.ts rename to apps/desktop-ui/src/components/api-client/workers/scripts-runner.worker.ts diff --git a/apps/web/src/components/api-client/workers/use-json-formatter.ts b/apps/desktop-ui/src/components/api-client/workers/use-json-formatter.ts similarity index 100% rename from apps/web/src/components/api-client/workers/use-json-formatter.ts rename to apps/desktop-ui/src/components/api-client/workers/use-json-formatter.ts diff --git a/apps/web/src/components/api-client/workers/use-scripts-runner.ts b/apps/desktop-ui/src/components/api-client/workers/use-scripts-runner.ts similarity index 100% rename from apps/web/src/components/api-client/workers/use-scripts-runner.ts rename to apps/desktop-ui/src/components/api-client/workers/use-scripts-runner.ts diff --git a/apps/web/src/components/api-client/workspaces-dialog.tsx b/apps/desktop-ui/src/components/api-client/workspaces-dialog.tsx similarity index 100% rename from apps/web/src/components/api-client/workspaces-dialog.tsx rename to apps/desktop-ui/src/components/api-client/workspaces-dialog.tsx diff --git a/apps/web/src/components/api-key-vault/add-api-key-dialog.tsx b/apps/desktop-ui/src/components/api-key-vault/add-api-key-dialog.tsx similarity index 100% rename from apps/web/src/components/api-key-vault/add-api-key-dialog.tsx rename to apps/desktop-ui/src/components/api-key-vault/add-api-key-dialog.tsx diff --git a/apps/web/src/components/api-key-vault/api-key-list.tsx b/apps/desktop-ui/src/components/api-key-vault/api-key-list.tsx similarity index 100% rename from apps/web/src/components/api-key-vault/api-key-list.tsx rename to apps/desktop-ui/src/components/api-key-vault/api-key-list.tsx diff --git a/apps/web/src/components/app-loading-screen.tsx b/apps/desktop-ui/src/components/app-loading-screen.tsx similarity index 100% rename from apps/web/src/components/app-loading-screen.tsx rename to apps/desktop-ui/src/components/app-loading-screen.tsx diff --git a/apps/web/src/components/app-tools/client-only-tool-loaders.tsx b/apps/desktop-ui/src/components/app-tools/client-only-tool-loaders.tsx similarity index 100% rename from apps/web/src/components/app-tools/client-only-tool-loaders.tsx rename to apps/desktop-ui/src/components/app-tools/client-only-tool-loaders.tsx diff --git a/apps/desktop-ui/src/components/app-update-notifier.tsx b/apps/desktop-ui/src/components/app-update-notifier.tsx new file mode 100644 index 00000000..f4f05bf2 --- /dev/null +++ b/apps/desktop-ui/src/components/app-update-notifier.tsx @@ -0,0 +1,140 @@ +'use client' + +import { useCallback, useEffect, useRef, useState } from 'react' +import { X, RefreshCw } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' + +const DISMISS_KEY = 'app-update-dismissed-build' +const POLL_MS = 5 * 60 * 1000 + +export function AppUpdateNotifier() { + const [visible, setVisible] = useState(false) + const clientBuildIdRef = useRef(null) + const remoteBuildIdRef = useRef(null) + + const checkVersion = useCallback(async () => { + const clientId = clientBuildIdRef.current + if (!clientId || clientId === 'development' || clientId === 'local') { + return + } + + try { + const res = await fetch('/api/app-version', { + cache: 'no-store', + credentials: 'same-origin', + }) + if (!res.ok) return + const data = (await res.json()) as { buildId?: string } + const remoteId = typeof data.buildId === 'string' ? data.buildId : null + if (!remoteId || remoteId === 'unknown') return + + remoteBuildIdRef.current = remoteId + if (remoteId === clientId) { + setVisible(false) + return + } + + try { + const dismissed = sessionStorage.getItem(DISMISS_KEY) + if (dismissed === remoteId) return + } catch { + /* private mode */ + } + + setVisible(true) + } catch { + /* offline or blocked */ + } + }, []) + + useEffect(() => { + const clientId = process.env.NEXT_PUBLIC_APP_BUILD_ID ?? 'unknown' + if (clientId === 'development' || clientId === 'local') { + return + } + + clientBuildIdRef.current = clientId + void checkVersion() + + const interval = window.setInterval(() => void checkVersion(), POLL_MS) + + const onVisible = () => { + if (document.visibilityState === 'visible') void checkVersion() + } + document.addEventListener('visibilitychange', onVisible) + window.addEventListener('focus', onVisible) + + return () => { + window.clearInterval(interval) + document.removeEventListener('visibilitychange', onVisible) + window.removeEventListener('focus', onVisible) + } + }, [checkVersion]) + + const handleDismiss = () => { + const remote = remoteBuildIdRef.current + if (remote) { + try { + sessionStorage.setItem(DISMISS_KEY, remote) + } catch { + /* ignore */ + } + } + setVisible(false) + } + + const handleReload = () => { + window.location.reload() + } + + if (!visible) return null + + return ( +
+
+
+
+

New version available

+

+ Reload the page to get the latest fixes and features. You can dismiss this if you want to + finish what you are doing first. +

+
+ +
+
+ + +
+
+
+ ) +} diff --git a/apps/desktop-ui/src/components/auth-logout-listener.tsx b/apps/desktop-ui/src/components/auth-logout-listener.tsx new file mode 100644 index 00000000..dd4343e0 --- /dev/null +++ b/apps/desktop-ui/src/components/auth-logout-listener.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import { useRouter } from "next/navigation" +import { FORCE_LOGOUT_EVENT, logoutUser, type LogoutReason } from "@/lib/logout-user" + +export function AuthLogoutListener() { + const router = useRouter() + + React.useEffect(() => { + const handler = (evt: Event) => { + const detail = (evt as CustomEvent<{ reason?: LogoutReason }>).detail + const reason = detail?.reason ?? "session-expired" + ;(async () => { + await logoutUser(reason) + router.replace("/login") + })() + } + + window.addEventListener(FORCE_LOGOUT_EVENT, handler) + return () => window.removeEventListener(FORCE_LOGOUT_EVENT, handler) + }, [router]) + + return null +} + diff --git a/apps/web/src/components/base64/base64-layout.tsx b/apps/desktop-ui/src/components/base64/base64-layout.tsx similarity index 100% rename from apps/web/src/components/base64/base64-layout.tsx rename to apps/desktop-ui/src/components/base64/base64-layout.tsx diff --git a/apps/web/src/components/bcrypt-generator/bcrypt-generator-layout.tsx b/apps/desktop-ui/src/components/bcrypt-generator/bcrypt-generator-layout.tsx similarity index 100% rename from apps/web/src/components/bcrypt-generator/bcrypt-generator-layout.tsx rename to apps/desktop-ui/src/components/bcrypt-generator/bcrypt-generator-layout.tsx diff --git a/apps/web/src/components/beautify-minify/beautify-minify-layout.tsx b/apps/desktop-ui/src/components/beautify-minify/beautify-minify-layout.tsx similarity index 100% rename from apps/web/src/components/beautify-minify/beautify-minify-layout.tsx rename to apps/desktop-ui/src/components/beautify-minify/beautify-minify-layout.tsx diff --git a/apps/web/src/components/bookmarks/add-bookmark-dialog.tsx b/apps/desktop-ui/src/components/bookmarks/add-bookmark-dialog.tsx similarity index 100% rename from apps/web/src/components/bookmarks/add-bookmark-dialog.tsx rename to apps/desktop-ui/src/components/bookmarks/add-bookmark-dialog.tsx diff --git a/apps/web/src/components/bookmarks/add-folder-dialog.tsx b/apps/desktop-ui/src/components/bookmarks/add-folder-dialog.tsx similarity index 100% rename from apps/web/src/components/bookmarks/add-folder-dialog.tsx rename to apps/desktop-ui/src/components/bookmarks/add-folder-dialog.tsx diff --git a/apps/web/src/components/bookmarks/bookmark-card.tsx b/apps/desktop-ui/src/components/bookmarks/bookmark-card.tsx similarity index 100% rename from apps/web/src/components/bookmarks/bookmark-card.tsx rename to apps/desktop-ui/src/components/bookmarks/bookmark-card.tsx diff --git a/apps/web/src/components/bookmarks/bookmark-grid.tsx b/apps/desktop-ui/src/components/bookmarks/bookmark-grid.tsx similarity index 100% rename from apps/web/src/components/bookmarks/bookmark-grid.tsx rename to apps/desktop-ui/src/components/bookmarks/bookmark-grid.tsx diff --git a/apps/web/src/components/bookmarks/bookmarks-manager.tsx b/apps/desktop-ui/src/components/bookmarks/bookmarks-manager.tsx similarity index 100% rename from apps/web/src/components/bookmarks/bookmarks-manager.tsx rename to apps/desktop-ui/src/components/bookmarks/bookmarks-manager.tsx diff --git a/apps/web/src/components/bookmarks/edit-folder-dialog.tsx b/apps/desktop-ui/src/components/bookmarks/edit-folder-dialog.tsx similarity index 100% rename from apps/web/src/components/bookmarks/edit-folder-dialog.tsx rename to apps/desktop-ui/src/components/bookmarks/edit-folder-dialog.tsx diff --git a/apps/web/src/components/bookmarks/folder-tree.tsx b/apps/desktop-ui/src/components/bookmarks/folder-tree.tsx similarity index 100% rename from apps/web/src/components/bookmarks/folder-tree.tsx rename to apps/desktop-ui/src/components/bookmarks/folder-tree.tsx diff --git a/apps/web/src/components/bookmarks/import-dialog.tsx b/apps/desktop-ui/src/components/bookmarks/import-dialog.tsx similarity index 100% rename from apps/web/src/components/bookmarks/import-dialog.tsx rename to apps/desktop-ui/src/components/bookmarks/import-dialog.tsx diff --git a/apps/desktop-ui/src/components/branded-toaster.tsx b/apps/desktop-ui/src/components/branded-toaster.tsx new file mode 100644 index 00000000..529abc8f --- /dev/null +++ b/apps/desktop-ui/src/components/branded-toaster.tsx @@ -0,0 +1,32 @@ +"use client" + +import { useTheme } from "next-themes" +import { Toaster as SonnerToaster } from "sonner" + +/** + * App-wide toast styling. Theme-aware (syncs with next-themes) and matched to + * the overlay design language — rounded-xl, soft border, blur, layered shadow. + */ +export function BrandedToaster() { + const { resolvedTheme } = useTheme() + + return ( + + ) +} diff --git a/apps/web/src/components/break-room/2048/engine.ts b/apps/desktop-ui/src/components/break-room/2048/engine.ts similarity index 100% rename from apps/web/src/components/break-room/2048/engine.ts rename to apps/desktop-ui/src/components/break-room/2048/engine.ts diff --git a/apps/web/src/components/break-room/2048/game-2048-layout.tsx b/apps/desktop-ui/src/components/break-room/2048/game-2048-layout.tsx similarity index 100% rename from apps/web/src/components/break-room/2048/game-2048-layout.tsx rename to apps/desktop-ui/src/components/break-room/2048/game-2048-layout.tsx diff --git a/apps/web/src/components/break-room/minesweeper/engine.ts b/apps/desktop-ui/src/components/break-room/minesweeper/engine.ts similarity index 100% rename from apps/web/src/components/break-room/minesweeper/engine.ts rename to apps/desktop-ui/src/components/break-room/minesweeper/engine.ts diff --git a/apps/web/src/components/break-room/minesweeper/minesweeper-layout.tsx b/apps/desktop-ui/src/components/break-room/minesweeper/minesweeper-layout.tsx similarity index 100% rename from apps/web/src/components/break-room/minesweeper/minesweeper-layout.tsx rename to apps/desktop-ui/src/components/break-room/minesweeper/minesweeper-layout.tsx diff --git a/apps/web/src/components/break-room/snake/engine.ts b/apps/desktop-ui/src/components/break-room/snake/engine.ts similarity index 100% rename from apps/web/src/components/break-room/snake/engine.ts rename to apps/desktop-ui/src/components/break-room/snake/engine.ts diff --git a/apps/web/src/components/break-room/snake/snake-layout.tsx b/apps/desktop-ui/src/components/break-room/snake/snake-layout.tsx similarity index 100% rename from apps/web/src/components/break-room/snake/snake-layout.tsx rename to apps/desktop-ui/src/components/break-room/snake/snake-layout.tsx diff --git a/apps/web/src/components/break-room/sudoku/engine.ts b/apps/desktop-ui/src/components/break-room/sudoku/engine.ts similarity index 100% rename from apps/web/src/components/break-room/sudoku/engine.ts rename to apps/desktop-ui/src/components/break-room/sudoku/engine.ts diff --git a/apps/web/src/components/break-room/sudoku/sudoku-layout.tsx b/apps/desktop-ui/src/components/break-room/sudoku/sudoku-layout.tsx similarity index 94% rename from apps/web/src/components/break-room/sudoku/sudoku-layout.tsx rename to apps/desktop-ui/src/components/break-room/sudoku/sudoku-layout.tsx index 6e5373a5..a8e5fc38 100644 --- a/apps/web/src/components/break-room/sudoku/sudoku-layout.tsx +++ b/apps/desktop-ui/src/components/break-room/sudoku/sudoku-layout.tsx @@ -4,8 +4,6 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslations } from 'next-intl' import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' -import { backendFetch } from '@/lib/backend-auth' -import useAuth from '@/utils/useAuth' import { buildConflicts, calcScore, DIFFICULTIES, emptyGrid, emptyNotes, generatePuzzle, isSolved, removeNoteFromPeers, @@ -14,7 +12,6 @@ import { export function SudokuLayout() { const t = useTranslations('Sudoku') - const { user } = useAuth() const [level, setLevel] = useState('easy') const [stage, setStage] = useState(1) @@ -33,7 +30,6 @@ export function SudokuLayout() { const [timerActive, setTimerActive] = useState(false) const [completed, setCompleted] = useState(false) const [scores, setScores] = useState([]) - const [saving, setSaving] = useState(false) const timerRef = useRef | null>(null) const wasTimerActiveRef = useRef(false) @@ -84,42 +80,29 @@ export function SudokuLayout() { return () => document.removeEventListener('visibilitychange', handler) }, [timerActive, completed]) - // Load scores from backend + // Load scores from local storage useEffect(() => { - if (!user) return - backendFetch('/api/backend/game-scores?game=sudoku', { method: 'GET' }) - .then(r => r.ok ? r.json() : []) - .then((data: GameScore[]) => setScores(data)) - .catch(() => {}) - }, [user]) + try { + const raw = window.localStorage.getItem('sudoku-scores') + if (raw) setScores(JSON.parse(raw) as GameScore[]) + } catch { /* corrupt/blocked storage — start fresh */ } + }, []) const getBestScore = useCallback((l: Difficulty, s: number) => scores.find(sc => sc.level === l && sc.stage === s) ?? null , [scores]) - const saveScore = useCallback(async (timeSecs: number, hints: number) => { - if (!user || saving) return + const saveScore = useCallback((timeSecs: number, hints: number) => { const score = calcScore(timeSecs, level, hints) const existing = getBestScore(level, stage) if (existing && existing.score >= score) return - setSaving(true) - try { - const res = await backendFetch('/api/backend/game-scores', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ game: 'sudoku', level, stage, score, time_seconds: timeSecs }), - }) - if (res.ok) { - const saved: GameScore = await res.json() - setScores(prev => { - const filtered = prev.filter(sc => !(sc.level === level && sc.stage === stage)) - return [...filtered, saved] - }) - } - } finally { - setSaving(false) - } - }, [user, saving, level, stage, getBestScore]) + const saved: GameScore = { level, stage, score, time_seconds: timeSecs } + setScores(prev => { + const next = [...prev.filter(sc => !(sc.level === level && sc.stage === stage)), saved] + try { window.localStorage.setItem('sudoku-scores', JSON.stringify(next)) } catch { /* storage blocked */ } + return next + }) + }, [level, stage, getBestScore]) const pushHistory = useCallback((g: Grid, n: Notes) => { setHistory(prev => [...prev.slice(-49), { diff --git a/apps/web/src/components/break-room/tetris/engine.ts b/apps/desktop-ui/src/components/break-room/tetris/engine.ts similarity index 100% rename from apps/web/src/components/break-room/tetris/engine.ts rename to apps/desktop-ui/src/components/break-room/tetris/engine.ts diff --git a/apps/web/src/components/break-room/tetris/tetris-layout.tsx b/apps/desktop-ui/src/components/break-room/tetris/tetris-layout.tsx similarity index 100% rename from apps/web/src/components/break-room/tetris/tetris-layout.tsx rename to apps/desktop-ui/src/components/break-room/tetris/tetris-layout.tsx diff --git a/apps/web/src/components/chmod-calculator/chmod-calculator-layout.tsx b/apps/desktop-ui/src/components/chmod-calculator/chmod-calculator-layout.tsx similarity index 100% rename from apps/web/src/components/chmod-calculator/chmod-calculator-layout.tsx rename to apps/desktop-ui/src/components/chmod-calculator/chmod-calculator-layout.tsx diff --git a/apps/desktop-ui/src/components/clarity-analytics.tsx b/apps/desktop-ui/src/components/clarity-analytics.tsx new file mode 100644 index 00000000..f223230e --- /dev/null +++ b/apps/desktop-ui/src/components/clarity-analytics.tsx @@ -0,0 +1,20 @@ +"use client" + +import { useEffect } from "react" + +// Microsoft Clarity heatmaps + session replays. No-op unless +// NEXT_PUBLIC_CLARITY_PROJECT_ID is set (so it stays off in local dev), +// matching how Vercel Analytics only reports in production. +// Data lives in clarity.microsoft.com — nothing touches our backend. +let initialized = false // ponytail: module guard so StrictMode/remounts can't double-inject the script + +export function ClarityAnalytics() { + useEffect(() => { + const projectId = process.env.NEXT_PUBLIC_CLARITY_PROJECT_ID + if (!projectId || initialized) return + initialized = true + import("@microsoft/clarity").then((m) => m.default.init(projectId)) + }, []) + + return null +} diff --git a/apps/desktop-ui/src/components/client-shell.tsx b/apps/desktop-ui/src/components/client-shell.tsx new file mode 100644 index 00000000..849df1bf --- /dev/null +++ b/apps/desktop-ui/src/components/client-shell.tsx @@ -0,0 +1,60 @@ +"use client" + +import dynamic from "next/dynamic" +import { Suspense, type ReactNode } from "react" +import { UserPreferencesSync } from "@/components/user-preferences-sync" +import { PinnedToolsPreferencesSync } from "@/components/pinned-tools-preferences-sync" +import { AppUpdateNotifier } from "@/components/app-update-notifier" +import { AuthLogoutListener } from "@/components/auth-logout-listener" + +const GlobalCommandPalette = dynamic( + () => import('@/components/global-command-palette').then((m) => m.GlobalCommandPalette), + { ssr: false } +) +const ClientAnalytics = dynamic( + () => import('@vercel/analytics/next').then((m) => m.Analytics), + { ssr: false } +) +const ClientSpeedInsights = dynamic( + () => import('@vercel/speed-insights/next').then((m) => m.SpeedInsights), + { ssr: false } +) +const ClientClarity = dynamic( + () => import('@/components/clarity-analytics').then((m) => m.ClarityAnalytics), + { ssr: false } +) +const ClientToaster = dynamic( + () => import('@/components/branded-toaster').then((m) => m.BrandedToaster), + { ssr: false } +) + +type Props = { + children: ReactNode +} + +export function ClientShell({ children }: Props) { + return ( + <> + + + + + + + + {children} + + + + + + + + + + + + + + ) +} diff --git a/apps/web/src/components/code-screenshot/code-screenshot-layout.tsx b/apps/desktop-ui/src/components/code-screenshot/code-screenshot-layout.tsx similarity index 100% rename from apps/web/src/components/code-screenshot/code-screenshot-layout.tsx rename to apps/desktop-ui/src/components/code-screenshot/code-screenshot-layout.tsx diff --git a/apps/web/src/components/color-picker-tool/color-helpers.ts b/apps/desktop-ui/src/components/color-picker-tool/color-helpers.ts similarity index 100% rename from apps/web/src/components/color-picker-tool/color-helpers.ts rename to apps/desktop-ui/src/components/color-picker-tool/color-helpers.ts diff --git a/apps/web/src/components/color-picker-tool/color-picker-tool-layout.tsx b/apps/desktop-ui/src/components/color-picker-tool/color-picker-tool-layout.tsx similarity index 100% rename from apps/web/src/components/color-picker-tool/color-picker-tool-layout.tsx rename to apps/desktop-ui/src/components/color-picker-tool/color-picker-tool-layout.tsx diff --git a/apps/web/src/components/confirm-dialog.tsx b/apps/desktop-ui/src/components/confirm-dialog.tsx similarity index 100% rename from apps/web/src/components/confirm-dialog.tsx rename to apps/desktop-ui/src/components/confirm-dialog.tsx diff --git a/apps/web/src/components/contrast-checker-tool/contrast-checker-tool-layout.tsx b/apps/desktop-ui/src/components/contrast-checker-tool/contrast-checker-tool-layout.tsx similarity index 100% rename from apps/web/src/components/contrast-checker-tool/contrast-checker-tool-layout.tsx rename to apps/desktop-ui/src/components/contrast-checker-tool/contrast-checker-tool-layout.tsx diff --git a/apps/web/src/components/cron-builder/cron-builder-layout.tsx b/apps/desktop-ui/src/components/cron-builder/cron-builder-layout.tsx similarity index 100% rename from apps/web/src/components/cron-builder/cron-builder-layout.tsx rename to apps/desktop-ui/src/components/cron-builder/cron-builder-layout.tsx diff --git a/apps/web/src/components/css-generators/border-radius-tab.tsx b/apps/desktop-ui/src/components/css-generators/border-radius-tab.tsx similarity index 100% rename from apps/web/src/components/css-generators/border-radius-tab.tsx rename to apps/desktop-ui/src/components/css-generators/border-radius-tab.tsx diff --git a/apps/web/src/components/css-generators/box-shadow-tab.tsx b/apps/desktop-ui/src/components/css-generators/box-shadow-tab.tsx similarity index 100% rename from apps/web/src/components/css-generators/box-shadow-tab.tsx rename to apps/desktop-ui/src/components/css-generators/box-shadow-tab.tsx diff --git a/apps/web/src/components/css-generators/clamp-tab.tsx b/apps/desktop-ui/src/components/css-generators/clamp-tab.tsx similarity index 100% rename from apps/web/src/components/css-generators/clamp-tab.tsx rename to apps/desktop-ui/src/components/css-generators/clamp-tab.tsx diff --git a/apps/web/src/components/css-generators/css-generators-layout.tsx b/apps/desktop-ui/src/components/css-generators/css-generators-layout.tsx similarity index 100% rename from apps/web/src/components/css-generators/css-generators-layout.tsx rename to apps/desktop-ui/src/components/css-generators/css-generators-layout.tsx diff --git a/apps/web/src/components/css-generators/css-output.tsx b/apps/desktop-ui/src/components/css-generators/css-output.tsx similarity index 100% rename from apps/web/src/components/css-generators/css-output.tsx rename to apps/desktop-ui/src/components/css-generators/css-output.tsx diff --git a/apps/web/src/components/css-gradient-builder/gradient-layout.tsx b/apps/desktop-ui/src/components/css-gradient-builder/gradient-layout.tsx similarity index 100% rename from apps/web/src/components/css-gradient-builder/gradient-layout.tsx rename to apps/desktop-ui/src/components/css-gradient-builder/gradient-layout.tsx diff --git a/apps/web/src/components/csv-excel-json/csv-excel-json-tool.tsx b/apps/desktop-ui/src/components/csv-excel-json/csv-excel-json-tool.tsx similarity index 100% rename from apps/web/src/components/csv-excel-json/csv-excel-json-tool.tsx rename to apps/desktop-ui/src/components/csv-excel-json/csv-excel-json-tool.tsx diff --git a/apps/web/src/components/curl-to-code/curl-to-code-layout.tsx b/apps/desktop-ui/src/components/curl-to-code/curl-to-code-layout.tsx similarity index 100% rename from apps/web/src/components/curl-to-code/curl-to-code-layout.tsx rename to apps/desktop-ui/src/components/curl-to-code/curl-to-code-layout.tsx diff --git a/apps/web/src/components/dashboard/activity/activity-log-drawer.tsx b/apps/desktop-ui/src/components/dashboard/activity/activity-log-drawer.tsx similarity index 100% rename from apps/web/src/components/dashboard/activity/activity-log-drawer.tsx rename to apps/desktop-ui/src/components/dashboard/activity/activity-log-drawer.tsx diff --git a/apps/web/src/components/dashboard/activity/activity-log-panel.tsx b/apps/desktop-ui/src/components/dashboard/activity/activity-log-panel.tsx similarity index 92% rename from apps/web/src/components/dashboard/activity/activity-log-panel.tsx rename to apps/desktop-ui/src/components/dashboard/activity/activity-log-panel.tsx index b609db35..8a9bc86f 100644 --- a/apps/web/src/components/dashboard/activity/activity-log-panel.tsx +++ b/apps/desktop-ui/src/components/dashboard/activity/activity-log-panel.tsx @@ -11,7 +11,7 @@ import { XCircle, } from 'lucide-react' import { cn } from '@/lib/utils' -import { fetchAuditLog, type AuditEvent } from '@/lib/audit-log-api' +import { auditModules, fetchAuditLog, type AuditEvent } from '@/lib/audit-log-api' import { Select, SelectContent, @@ -28,24 +28,8 @@ const DAY_MS = 86_400_000 type Outcome = '' | 'success' | 'failure' type Range = 'all' | 'today' | '7d' | '30d' -const MODULE_OPTIONS: { value: string; key: string }[] = [ - { value: 'auth', key: 'moduleAuth' }, - { value: 'bookmarks', key: 'moduleBookmarks' }, - { value: 'bookmark-folders', key: 'moduleBookmarkFolders' }, - { value: 'notes', key: 'moduleNotes' }, - { value: 'tasks', key: 'moduleTasks' }, - { value: 'passwords', key: 'modulePasswords' }, - { value: 'code-snippets', key: 'moduleSnippets' }, - { value: 'api-client', key: 'moduleApiClient' }, - { value: 'sql-client', key: 'moduleSqlClient' }, - { value: 'nosql', key: 'moduleNosql' }, - { value: 'environment-manager', key: 'moduleEnvManager' }, - { value: 'user-preferences', key: 'modulePrefs' }, - { value: 'projects', key: 'moduleProjects' }, - { value: 'url-shortener', key: 'moduleUrlShortener' }, - { value: 'game-scores', key: 'moduleGameScores' }, - { value: 'feedback', key: 'moduleFeedback' }, -] +// Local activity is grouped by tool category, not backend module. +const MODULE_OPTIONS: string[] = auditModules() function rangeBounds(range: Range): { from?: number; to?: number } { if (range === 'all') return {} @@ -282,8 +266,8 @@ export function ActivityLogPanel({ embedded = false }: { embedded?: boolean }) { {t('all')} {MODULE_OPTIONS.map((m) => ( - - {t(m.key)} + + {m} ))} diff --git a/apps/web/src/components/dashboard/activity/audit-event-row.tsx b/apps/desktop-ui/src/components/dashboard/activity/audit-event-row.tsx similarity index 97% rename from apps/web/src/components/dashboard/activity/audit-event-row.tsx rename to apps/desktop-ui/src/components/dashboard/activity/audit-event-row.tsx index 04745043..06b953f1 100644 --- a/apps/web/src/components/dashboard/activity/audit-event-row.tsx +++ b/apps/desktop-ui/src/components/dashboard/activity/audit-event-row.tsx @@ -12,7 +12,6 @@ import { Folder, KeyRound, Layers, - Link as LinkIcon, ListTodo, Lock, MessageSquare, @@ -21,7 +20,6 @@ import { Settings, Smartphone, Tablet, - Trophy, UserCircle2, } from 'lucide-react' import { cn } from '@/lib/utils' @@ -84,8 +82,6 @@ const MODULE_NOUNS: Record = { sql_client: 'SQL connection', 'environment-manager': 'environment', environment_manager: 'environment', - 'url-shortener': 'short link', - 'game-scores': 'game score', nosql: 'database connection', passwords: 'password', bookmarks: 'bookmark', @@ -117,8 +113,6 @@ const MODULE_ICON: Record = { 'user-preferences': { Icon: Settings, tone: 'bg-slate-500/10 text-slate-600 dark:text-slate-300' }, user_preferences: { Icon: Settings, tone: 'bg-slate-500/10 text-slate-600 dark:text-slate-300' }, projects: { Icon: Folder, tone: 'bg-orange-500/10 text-orange-600 dark:text-orange-400' }, - 'url-shortener': { Icon: LinkIcon, tone: 'bg-pink-500/10 text-pink-600 dark:text-pink-400' }, - 'game-scores': { Icon: Trophy, tone: 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400' }, feedback: { Icon: MessageSquare, tone: 'bg-green-500/10 text-green-600 dark:text-green-400' }, passwords_session: { Icon: KeyRound, tone: 'bg-rose-500/10 text-rose-600 dark:text-rose-400' }, } diff --git a/apps/web/src/components/dashboard/analytics-cells.tsx b/apps/desktop-ui/src/components/dashboard/analytics-cells.tsx similarity index 100% rename from apps/web/src/components/dashboard/analytics-cells.tsx rename to apps/desktop-ui/src/components/dashboard/analytics-cells.tsx diff --git a/apps/web/src/components/dashboard/analytics-helpers.ts b/apps/desktop-ui/src/components/dashboard/analytics-helpers.ts similarity index 100% rename from apps/web/src/components/dashboard/analytics-helpers.ts rename to apps/desktop-ui/src/components/dashboard/analytics-helpers.ts diff --git a/apps/web/src/components/dashboard/charts/__tests__/chart-utils.test.ts b/apps/desktop-ui/src/components/dashboard/charts/__tests__/chart-utils.test.ts similarity index 100% rename from apps/web/src/components/dashboard/charts/__tests__/chart-utils.test.ts rename to apps/desktop-ui/src/components/dashboard/charts/__tests__/chart-utils.test.ts diff --git a/apps/web/src/components/dashboard/charts/activity-bar-chart.tsx b/apps/desktop-ui/src/components/dashboard/charts/activity-bar-chart.tsx similarity index 100% rename from apps/web/src/components/dashboard/charts/activity-bar-chart.tsx rename to apps/desktop-ui/src/components/dashboard/charts/activity-bar-chart.tsx diff --git a/apps/web/src/components/dashboard/charts/chart-utils.ts b/apps/desktop-ui/src/components/dashboard/charts/chart-utils.ts similarity index 100% rename from apps/web/src/components/dashboard/charts/chart-utils.ts rename to apps/desktop-ui/src/components/dashboard/charts/chart-utils.ts diff --git a/apps/web/src/components/dashboard/charts/donut-chart.tsx b/apps/desktop-ui/src/components/dashboard/charts/donut-chart.tsx similarity index 100% rename from apps/web/src/components/dashboard/charts/donut-chart.tsx rename to apps/desktop-ui/src/components/dashboard/charts/donut-chart.tsx diff --git a/apps/web/src/components/dashboard/charts/top-tools-bars.tsx b/apps/desktop-ui/src/components/dashboard/charts/top-tools-bars.tsx similarity index 100% rename from apps/web/src/components/dashboard/charts/top-tools-bars.tsx rename to apps/desktop-ui/src/components/dashboard/charts/top-tools-bars.tsx diff --git a/apps/web/src/components/dashboard/dashboard-analytics-panel.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-analytics-panel.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-analytics-panel.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-analytics-panel.tsx diff --git a/apps/web/src/components/dashboard/dashboard-featured.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-featured.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-featured.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-featured.tsx diff --git a/apps/web/src/components/dashboard/dashboard-hero.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-hero.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-hero.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-hero.tsx diff --git a/apps/web/src/components/dashboard/dashboard-pinned-section.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-pinned-section.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-pinned-section.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-pinned-section.tsx diff --git a/apps/web/src/components/dashboard/dashboard-recent-tools.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-recent-tools.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-recent-tools.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-recent-tools.tsx diff --git a/apps/web/src/components/dashboard/dashboard-reveal.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-reveal.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-reveal.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-reveal.tsx diff --git a/apps/web/src/components/dashboard/dashboard-search-bar.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-search-bar.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-search-bar.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-search-bar.tsx diff --git a/apps/web/src/components/dashboard/dashboard-section-header.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-section-header.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-section-header.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-section-header.tsx diff --git a/apps/web/src/components/dashboard/dashboard-tool-card.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-tool-card.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-tool-card.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-tool-card.tsx diff --git a/apps/web/src/components/dashboard/dashboard-tool-grid.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-tool-grid.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-tool-grid.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-tool-grid.tsx diff --git a/apps/web/src/components/dashboard/dashboard-ui-panel.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-ui-panel.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-ui-panel.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-ui-panel.tsx diff --git a/apps/web/src/components/dashboard/dashboard-whats-new.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-whats-new.tsx similarity index 100% rename from apps/web/src/components/dashboard/dashboard-whats-new.tsx rename to apps/desktop-ui/src/components/dashboard/dashboard-whats-new.tsx diff --git a/apps/web/src/components/dashboard/task-card.tsx b/apps/desktop-ui/src/components/dashboard/task-card.tsx similarity index 100% rename from apps/web/src/components/dashboard/task-card.tsx rename to apps/desktop-ui/src/components/dashboard/task-card.tsx diff --git a/apps/web/src/components/dashboard/types.ts b/apps/desktop-ui/src/components/dashboard/types.ts similarity index 100% rename from apps/web/src/components/dashboard/types.ts rename to apps/desktop-ui/src/components/dashboard/types.ts diff --git a/apps/web/src/components/desktop/desktop-backup-settings.tsx b/apps/desktop-ui/src/components/desktop/desktop-backup-settings.tsx similarity index 100% rename from apps/web/src/components/desktop/desktop-backup-settings.tsx rename to apps/desktop-ui/src/components/desktop/desktop-backup-settings.tsx diff --git a/apps/web/src/components/desktop/desktop-init.tsx b/apps/desktop-ui/src/components/desktop/desktop-init.tsx similarity index 55% rename from apps/web/src/components/desktop/desktop-init.tsx rename to apps/desktop-ui/src/components/desktop/desktop-init.tsx index f901540c..ec867700 100644 --- a/apps/web/src/components/desktop/desktop-init.tsx +++ b/apps/desktop-ui/src/components/desktop/desktop-init.tsx @@ -1,32 +1,40 @@ "use client"; import { useEffect } from "react"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { toast } from "sonner"; import { isDesktop } from "@/lib/desktop/is-desktop"; /** - * Desktop-only bootstrap: deep-link sign-in listener + startup session probe. + * Desktop-only bootstrap: mandatory one-time activation gate, deep-link + * sign-in listener, startup session probe, and update check. * Renders nothing; a no-op on web (the isDesktop guard compiles to false). */ export function DesktopInit() { const router = useRouter(); + const pathname = usePathname(); + + // Activation gate: without a local activation record every route funnels to + // /activate. Local check only — never blocks on the network. + useEffect(() => { + if (!isDesktop() || pathname === "/activate") return; + void (async () => { + const { getActivation } = await import("@/lib/desktop/activation"); + const activated = await getActivation().catch(() => null); + if (!activated) router.replace("/activate"); + })(); + }, [router, pathname]); + useEffect(() => { if (!isDesktop()) return; - // After browser sign-in completes, enter the workspace via Next routing. - const onAuthed = () => router.replace("/dashboard"); - window.addEventListener("mydevtools:desktop-authed", onAuthed); void (async () => { - const [{ initDeepLinkListener }, { checkRemoteSession }, { startSyncEngine }] = - await Promise.all([ - import("@/lib/desktop/cloud-signin"), - import("@/lib/desktop/remote"), - import("@/lib/desktop/sync-engine"), - ]); + const [{ initDeepLinkListener }, { checkRemoteSession }] = await Promise.all([ + import("@/lib/desktop/cloud-signin"), + import("@/lib/desktop/remote"), + ]); await initDeepLinkListener().catch(() => {}); await checkRemoteSession().catch(() => {}); - startSyncEngine(); })(); // Fire-and-forget update check — nudge the user if a newer build exists. void import("@/lib/desktop/updater").then(async ({ checkForUpdate, installUpdate }) => { @@ -42,7 +50,6 @@ export function DesktopInit() { // offline or endpoint unreachable — silent } }); - return () => window.removeEventListener("mydevtools:desktop-authed", onAuthed); - }, [router]); + }, []); return null; } diff --git a/apps/web/src/components/desktop/desktop-login.tsx b/apps/desktop-ui/src/components/desktop/desktop-login.tsx similarity index 100% rename from apps/web/src/components/desktop/desktop-login.tsx rename to apps/desktop-ui/src/components/desktop/desktop-login.tsx diff --git a/apps/web/src/components/desktop/desktop-online-gate.tsx b/apps/desktop-ui/src/components/desktop/desktop-online-gate.tsx similarity index 100% rename from apps/web/src/components/desktop/desktop-online-gate.tsx rename to apps/desktop-ui/src/components/desktop/desktop-online-gate.tsx diff --git a/apps/desktop-ui/src/components/desktop/desktop-plan-settings.tsx b/apps/desktop-ui/src/components/desktop/desktop-plan-settings.tsx new file mode 100644 index 00000000..fc5b8714 --- /dev/null +++ b/apps/desktop-ui/src/components/desktop/desktop-plan-settings.tsx @@ -0,0 +1,69 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useTranslations } from 'next-intl' +import { Crown, ExternalLink } from 'lucide-react' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { isDesktop } from '@/lib/desktop/is-desktop' +import type { ActivationRecord } from '@/lib/desktop/activation' + +/** Settings card: locally-stored plan summary + link to the web plan page. */ +export function DesktopPlanSettings() { + const t = useTranslations('PlanCard') + const [record, setRecord] = useState(null) + + useEffect(() => { + if (!isDesktop()) return + void import('@/lib/desktop/activation').then(({ getActivation }) => + getActivation().then(setRecord).catch(() => {}) + ) + }, []) + + if (!isDesktop() || !record) return null + + const isPro = record.plan === 'pro' + const isEarlyAdopter = record.plan_source === 'early_adopter' + const grantedAt = record.plan_granted_at + ? new Date(record.plan_granted_at).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }) + : null + + const openPlanPage = async () => { + const [{ openUrl }, { desktopWebBase }] = await Promise.all([ + import('@tauri-apps/plugin-opener'), + import('@/lib/desktop/remote'), + ]) + await openUrl(`${desktopWebBase()}/account/plan`) + } + + return ( + + + + + + + {t('title')} + {isPro ? ( + + {isEarlyAdopter ? t('earlyAdopter') : 'Pro'} + + ) : ( + Free + )} + + + {isPro ? t('lifetimeDescription') : t('freeDescription')} + {grantedAt ? ` · ${t('grantedOn')} ${grantedAt}` : ''} + + + + + + + ) +} diff --git a/apps/web/src/components/desktop/desktop-update-settings.tsx b/apps/desktop-ui/src/components/desktop/desktop-update-settings.tsx similarity index 100% rename from apps/web/src/components/desktop/desktop-update-settings.tsx rename to apps/desktop-ui/src/components/desktop/desktop-update-settings.tsx diff --git a/apps/web/src/components/desktop/hide-on-desktop.tsx b/apps/desktop-ui/src/components/desktop/hide-on-desktop.tsx similarity index 100% rename from apps/web/src/components/desktop/hide-on-desktop.tsx rename to apps/desktop-ui/src/components/desktop/hide-on-desktop.tsx diff --git a/apps/web/src/components/diff-checker/diff-checker-layout.tsx b/apps/desktop-ui/src/components/diff-checker/diff-checker-layout.tsx similarity index 100% rename from apps/web/src/components/diff-checker/diff-checker-layout.tsx rename to apps/desktop-ui/src/components/diff-checker/diff-checker-layout.tsx diff --git a/apps/web/src/components/dns-lookup/dns-lookup-layout.tsx b/apps/desktop-ui/src/components/dns-lookup/dns-lookup-layout.tsx similarity index 94% rename from apps/web/src/components/dns-lookup/dns-lookup-layout.tsx rename to apps/desktop-ui/src/components/dns-lookup/dns-lookup-layout.tsx index 30f0c252..fdec2126 100644 --- a/apps/web/src/components/dns-lookup/dns-lookup-layout.tsx +++ b/apps/desktop-ui/src/components/dns-lookup/dns-lookup-layout.tsx @@ -21,21 +21,7 @@ import { IconRefresh, } from '@tabler/icons-react'; import { cn } from '@/lib/utils'; -import { backendFetch } from '@/lib/backend-auth'; - -interface DNSRecord { - type: string; - value: string; - ttl: number | null; - priority: number | null; - extra: Record | null; -} - -interface DNSLookupResult { - domain: string; - records: Record; - errors: Record; -} +import { dohLookup, type DNSRecord, type DNSLookupResult } from '@/lib/dns-doh'; const RECORD_TYPES = ['A', 'AAAA', 'MX', 'TXT', 'NS', 'CNAME', 'SOA', 'CAA', 'PTR'] as const; type RecordType = (typeof RECORD_TYPES)[number]; @@ -204,21 +190,8 @@ export function DnsLookupLayout() { if (overrideDomain) setDomain(overrideDomain); try { - const params = new URLSearchParams({ domain: target }); - selectedTypes.forEach(rt => params.append('record_types', rt)); - const res = await backendFetch(`/api/backend/dns-lookup/lookup?${params}`, { - headers: { accept: 'application/json' }, - }); - if (!res.ok) { - const body = await res.json().catch(() => ({})); - throw new Error(body?.detail || `Request failed (${res.status})`); - } - const data: DNSLookupResult = await res.json(); - if (data.errors?._ ) { - setGlobalError(data.errors._); - } else { - setResult(data); - } + const data = await dohLookup(target, selectedTypes); + setResult(data); } catch (e) { setGlobalError(e instanceof Error ? e.message : t('errors.lookupFailed')); } finally { diff --git a/apps/web/src/components/docker-compose-generator/docker-compose-generator-layout.tsx b/apps/desktop-ui/src/components/docker-compose-generator/docker-compose-generator-layout.tsx similarity index 100% rename from apps/web/src/components/docker-compose-generator/docker-compose-generator-layout.tsx rename to apps/desktop-ui/src/components/docker-compose-generator/docker-compose-generator-layout.tsx diff --git a/apps/web/src/components/email-validator/email-validator.tsx b/apps/desktop-ui/src/components/email-validator/email-validator.tsx similarity index 100% rename from apps/web/src/components/email-validator/email-validator.tsx rename to apps/desktop-ui/src/components/email-validator/email-validator.tsx diff --git a/apps/web/src/components/encrypted-tool-placeholder.tsx b/apps/desktop-ui/src/components/encrypted-tool-placeholder.tsx similarity index 100% rename from apps/web/src/components/encrypted-tool-placeholder.tsx rename to apps/desktop-ui/src/components/encrypted-tool-placeholder.tsx diff --git a/apps/web/src/components/encryption-playground/encryption-playground-layout.tsx b/apps/desktop-ui/src/components/encryption-playground/encryption-playground-layout.tsx similarity index 100% rename from apps/web/src/components/encryption-playground/encryption-playground-layout.tsx rename to apps/desktop-ui/src/components/encryption-playground/encryption-playground-layout.tsx diff --git a/apps/web/src/components/encryption-playground/error-mapping.ts b/apps/desktop-ui/src/components/encryption-playground/error-mapping.ts similarity index 100% rename from apps/web/src/components/encryption-playground/error-mapping.ts rename to apps/desktop-ui/src/components/encryption-playground/error-mapping.ts diff --git a/apps/web/src/components/ensure-backend-session.tsx b/apps/desktop-ui/src/components/ensure-backend-session.tsx similarity index 100% rename from apps/web/src/components/ensure-backend-session.tsx rename to apps/desktop-ui/src/components/ensure-backend-session.tsx diff --git a/apps/web/src/components/environment-manager/add-environment-set-dialog.tsx b/apps/desktop-ui/src/components/environment-manager/add-environment-set-dialog.tsx similarity index 100% rename from apps/web/src/components/environment-manager/add-environment-set-dialog.tsx rename to apps/desktop-ui/src/components/environment-manager/add-environment-set-dialog.tsx diff --git a/apps/web/src/components/environment-manager/dotenv-modal-highlighter.css b/apps/desktop-ui/src/components/environment-manager/dotenv-modal-highlighter.css similarity index 100% rename from apps/web/src/components/environment-manager/dotenv-modal-highlighter.css rename to apps/desktop-ui/src/components/environment-manager/dotenv-modal-highlighter.css diff --git a/apps/web/src/components/environment-manager/edit-environment-set-dialog.tsx b/apps/desktop-ui/src/components/environment-manager/edit-environment-set-dialog.tsx similarity index 100% rename from apps/web/src/components/environment-manager/edit-environment-set-dialog.tsx rename to apps/desktop-ui/src/components/environment-manager/edit-environment-set-dialog.tsx diff --git a/apps/web/src/components/environment-manager/env-paste-collapsible.tsx b/apps/desktop-ui/src/components/environment-manager/env-paste-collapsible.tsx similarity index 100% rename from apps/web/src/components/environment-manager/env-paste-collapsible.tsx rename to apps/desktop-ui/src/components/environment-manager/env-paste-collapsible.tsx diff --git a/apps/web/src/components/environment-manager/environment-set-list.tsx b/apps/desktop-ui/src/components/environment-manager/environment-set-list.tsx similarity index 100% rename from apps/web/src/components/environment-manager/environment-set-list.tsx rename to apps/desktop-ui/src/components/environment-manager/environment-set-list.tsx diff --git a/apps/web/src/components/escape-encode/escape-encode-layout.tsx b/apps/desktop-ui/src/components/escape-encode/escape-encode-layout.tsx similarity index 100% rename from apps/web/src/components/escape-encode/escape-encode-layout.tsx rename to apps/desktop-ui/src/components/escape-encode/escape-encode-layout.tsx diff --git a/apps/web/src/components/exif-viewer/exif-viewer-layout.tsx b/apps/desktop-ui/src/components/exif-viewer/exif-viewer-layout.tsx similarity index 100% rename from apps/web/src/components/exif-viewer/exif-viewer-layout.tsx rename to apps/desktop-ui/src/components/exif-viewer/exif-viewer-layout.tsx diff --git a/apps/web/src/components/favicon-generator/favicon-generator-layout.tsx b/apps/desktop-ui/src/components/favicon-generator/favicon-generator-layout.tsx similarity index 100% rename from apps/web/src/components/favicon-generator/favicon-generator-layout.tsx rename to apps/desktop-ui/src/components/favicon-generator/favicon-generator-layout.tsx diff --git a/apps/web/src/components/favicon-img.tsx b/apps/desktop-ui/src/components/favicon-img.tsx similarity index 100% rename from apps/web/src/components/favicon-img.tsx rename to apps/desktop-ui/src/components/favicon-img.tsx diff --git a/apps/web/src/components/feedback-dialog.tsx b/apps/desktop-ui/src/components/feedback-dialog.tsx similarity index 100% rename from apps/web/src/components/feedback-dialog.tsx rename to apps/desktop-ui/src/components/feedback-dialog.tsx diff --git a/apps/desktop-ui/src/components/footer.tsx b/apps/desktop-ui/src/components/footer.tsx new file mode 100644 index 00000000..d0a293db --- /dev/null +++ b/apps/desktop-ui/src/components/footer.tsx @@ -0,0 +1,104 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { motion, AnimatePresence, useReducedMotion } from "framer-motion"; +import { ArrowUp } from "lucide-react"; +import { Logo } from "./logo"; + +const footerLinks = [ + { href: "/dashboard", label: "Dashboard" }, + { href: "/blog", label: "Blog" }, + { href: "/developer-tools", label: "Platform" }, + { href: "/features", label: "Features" }, + { href: "/pricing", label: "Pricing" }, + { href: "/#tools", label: "Tools" }, + { href: "/security", label: "Security" }, + { href: "/open-source", label: "Open Source" }, + { href: "/self-host", label: "Self-host" }, + { href: "/help", label: "Help" }, + { + href: "https://github.com/itsmeakhil/mydevtools.tech", + label: "GitHub", + external: true, + }, +]; + +export function Footer() { + const year = new Date().getFullYear(); + + return ( + <> +
+
+
+ {/* Brand + copyright */} + + + + © {year} MyDevTools{" "} + · GPL-3.0 + + + + {/* Nav links */} + +
+
+
+ + + + ); +} + +function BackToTop() { + const [visible, setVisible] = useState(false); + const reduceMotion = useReducedMotion(); + + useEffect(() => { + const onScroll = () => setVisible(window.scrollY > 400); + onScroll(); + window.addEventListener("scroll", onScroll, { passive: true }); + return () => window.removeEventListener("scroll", onScroll); + }, []); + + const scrollTop = () => { + window.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" }); + }; + + return ( + + {visible && ( + + + + )} + + ); +} diff --git a/apps/web/src/components/format-converter/format-converter-layout.tsx b/apps/desktop-ui/src/components/format-converter/format-converter-layout.tsx similarity index 100% rename from apps/web/src/components/format-converter/format-converter-layout.tsx rename to apps/desktop-ui/src/components/format-converter/format-converter-layout.tsx diff --git a/apps/web/src/components/gitignore-generator/gitignore-layout.tsx b/apps/desktop-ui/src/components/gitignore-generator/gitignore-layout.tsx similarity index 100% rename from apps/web/src/components/gitignore-generator/gitignore-layout.tsx rename to apps/desktop-ui/src/components/gitignore-generator/gitignore-layout.tsx diff --git a/apps/web/src/components/global-command-palette.tsx b/apps/desktop-ui/src/components/global-command-palette.tsx similarity index 100% rename from apps/web/src/components/global-command-palette.tsx rename to apps/desktop-ui/src/components/global-command-palette.tsx diff --git a/apps/web/src/components/graphql-formatter/graphql-formatter-layout.tsx b/apps/desktop-ui/src/components/graphql-formatter/graphql-formatter-layout.tsx similarity index 100% rename from apps/web/src/components/graphql-formatter/graphql-formatter-layout.tsx rename to apps/desktop-ui/src/components/graphql-formatter/graphql-formatter-layout.tsx diff --git a/apps/web/src/components/graphql-formatter/register-graphql-monarch.ts b/apps/desktop-ui/src/components/graphql-formatter/register-graphql-monarch.ts similarity index 100% rename from apps/web/src/components/graphql-formatter/register-graphql-monarch.ts rename to apps/desktop-ui/src/components/graphql-formatter/register-graphql-monarch.ts diff --git a/apps/web/src/components/hash-generator/hash-generator-layout.tsx b/apps/desktop-ui/src/components/hash-generator/hash-generator-layout.tsx similarity index 100% rename from apps/web/src/components/hash-generator/hash-generator-layout.tsx rename to apps/desktop-ui/src/components/hash-generator/hash-generator-layout.tsx diff --git a/apps/desktop-ui/src/components/header.tsx b/apps/desktop-ui/src/components/header.tsx new file mode 100644 index 00000000..02e359aa --- /dev/null +++ b/apps/desktop-ui/src/components/header.tsx @@ -0,0 +1,243 @@ +"use client"; +import { useState, useEffect, type ElementType } from "react"; +import Link from "next/link"; +import { Menu, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { ModeToggle } from "./modeToggle"; +import { Logo } from "./logo"; +import { AnnouncementBanner } from "./announcement-banner"; +import { motion, AnimatePresence } from "framer-motion"; +import { cn } from "@/lib/utils"; + +// Mobile menu animation variants +const menuVariants = { + closed: { + opacity: 0, + height: 0, + transition: { + duration: 0.2, + ease: "easeInOut" as const, + }, + }, + open: { + opacity: 1, + height: "auto", + transition: { + duration: 0.3, + ease: "easeOut" as const, + }, + }, +}; + +const menuItemVariants = { + closed: { opacity: 0, x: -16 }, + open: (i: number) => ({ + opacity: 1, + x: 0, + transition: { + delay: i * 0.05, + duration: 0.25, + ease: "easeOut" as const, + }, + }), +}; + +type HeaderProps = { + /** When false, theme toggle is hidden (e.g. marketing page fixed to dark). */ + showThemeToggle?: boolean; +}; + +export function Header({ showThemeToggle = true }: HeaderProps) { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [scrolled, setScrolled] = useState(false); + + useEffect(() => { + const onScroll = () => setScrolled(window.scrollY > 8); + onScroll(); + window.addEventListener("scroll", onScroll, { passive: true }); + return () => window.removeEventListener("scroll", onScroll); + }, []); + + const mobileNavLinks: { + href: string; + label: string; + icon?: ElementType; + external?: boolean; + }[] = [ + { href: "/", label: "Home" }, + { href: "/features", label: "Features" }, + { href: "/pricing", label: "Pricing" }, + { href: "/developer-tools", label: "Platform" }, + { href: "/tools", label: "Tools" }, + ]; + + return ( +
+ +
+
+ {/* Logo */} + + + + + {/* Desktop Navigation */} +
+ +
+ + {/* Desktop Actions */} +
+ {/* Mobile Menu Toggle */} + + + {/* Desktop Actions */} +
+ {showThemeToggle ? : null} +
+
+
+
+ + {/* Mobile Menu */} + + {isMenuOpen && ( + <> + {/* Backdrop */} + setIsMenuOpen(false)} + /> + + {/* Menu Content */} + + + + + )} + +
+ ); +} \ No newline at end of file diff --git a/apps/web/src/components/hmac-generator/hmac-generator-layout.tsx b/apps/desktop-ui/src/components/hmac-generator/hmac-generator-layout.tsx similarity index 100% rename from apps/web/src/components/hmac-generator/hmac-generator-layout.tsx rename to apps/desktop-ui/src/components/hmac-generator/hmac-generator-layout.tsx diff --git a/apps/web/src/components/hooks/use-mobile.tsx b/apps/desktop-ui/src/components/hooks/use-mobile.tsx similarity index 100% rename from apps/web/src/components/hooks/use-mobile.tsx rename to apps/desktop-ui/src/components/hooks/use-mobile.tsx diff --git a/apps/web/src/components/http-status-codes/http-status-codes-layout.tsx b/apps/desktop-ui/src/components/http-status-codes/http-status-codes-layout.tsx similarity index 100% rename from apps/web/src/components/http-status-codes/http-status-codes-layout.tsx rename to apps/desktop-ui/src/components/http-status-codes/http-status-codes-layout.tsx diff --git a/apps/web/src/components/icons.tsx b/apps/desktop-ui/src/components/icons.tsx similarity index 100% rename from apps/web/src/components/icons.tsx rename to apps/desktop-ui/src/components/icons.tsx diff --git a/apps/web/src/components/image-compressor/image-compressor-layout.tsx b/apps/desktop-ui/src/components/image-compressor/image-compressor-layout.tsx similarity index 100% rename from apps/web/src/components/image-compressor/image-compressor-layout.tsx rename to apps/desktop-ui/src/components/image-compressor/image-compressor-layout.tsx diff --git a/apps/web/src/components/image-to-base64/image-to-base64-layout.tsx b/apps/desktop-ui/src/components/image-to-base64/image-to-base64-layout.tsx similarity index 100% rename from apps/web/src/components/image-to-base64/image-to-base64-layout.tsx rename to apps/desktop-ui/src/components/image-to-base64/image-to-base64-layout.tsx diff --git a/apps/web/src/components/ip-subnet-calculator/ip-subnet-calculator-layout.tsx b/apps/desktop-ui/src/components/ip-subnet-calculator/ip-subnet-calculator-layout.tsx similarity index 100% rename from apps/web/src/components/ip-subnet-calculator/ip-subnet-calculator-layout.tsx rename to apps/desktop-ui/src/components/ip-subnet-calculator/ip-subnet-calculator-layout.tsx diff --git a/apps/web/src/components/json-diff/json-diff-layout.tsx b/apps/desktop-ui/src/components/json-diff/json-diff-layout.tsx similarity index 100% rename from apps/web/src/components/json-diff/json-diff-layout.tsx rename to apps/desktop-ui/src/components/json-diff/json-diff-layout.tsx diff --git a/apps/web/src/components/json-formatter/json-formatter-layout.tsx b/apps/desktop-ui/src/components/json-formatter/json-formatter-layout.tsx similarity index 100% rename from apps/web/src/components/json-formatter/json-formatter-layout.tsx rename to apps/desktop-ui/src/components/json-formatter/json-formatter-layout.tsx diff --git a/apps/web/src/components/json-formatter/vanilla-editor.tsx b/apps/desktop-ui/src/components/json-formatter/vanilla-editor.tsx similarity index 100% rename from apps/web/src/components/json-formatter/vanilla-editor.tsx rename to apps/desktop-ui/src/components/json-formatter/vanilla-editor.tsx diff --git a/apps/web/src/components/json-schema-generator/json-schema-generator-layout.tsx b/apps/desktop-ui/src/components/json-schema-generator/json-schema-generator-layout.tsx similarity index 100% rename from apps/web/src/components/json-schema-generator/json-schema-generator-layout.tsx rename to apps/desktop-ui/src/components/json-schema-generator/json-schema-generator-layout.tsx diff --git a/apps/web/src/components/json-schema-generator/json-schema-highlighter.css b/apps/desktop-ui/src/components/json-schema-generator/json-schema-highlighter.css similarity index 100% rename from apps/web/src/components/json-schema-generator/json-schema-highlighter.css rename to apps/desktop-ui/src/components/json-schema-generator/json-schema-highlighter.css diff --git a/apps/web/src/components/json-schema-generator/json-schema-input-editor.tsx b/apps/desktop-ui/src/components/json-schema-generator/json-schema-input-editor.tsx similarity index 100% rename from apps/web/src/components/json-schema-generator/json-schema-input-editor.tsx rename to apps/desktop-ui/src/components/json-schema-generator/json-schema-input-editor.tsx diff --git a/apps/web/src/components/json-to-code/json-to-code-layout.tsx b/apps/desktop-ui/src/components/json-to-code/json-to-code-layout.tsx similarity index 100% rename from apps/web/src/components/json-to-code/json-to-code-layout.tsx rename to apps/desktop-ui/src/components/json-to-code/json-to-code-layout.tsx diff --git a/apps/web/src/components/json-visualizer/graph-canvas.tsx b/apps/desktop-ui/src/components/json-visualizer/graph-canvas.tsx similarity index 100% rename from apps/web/src/components/json-visualizer/graph-canvas.tsx rename to apps/desktop-ui/src/components/json-visualizer/graph-canvas.tsx diff --git a/apps/web/src/components/json-visualizer/json-visualizer-layout.tsx b/apps/desktop-ui/src/components/json-visualizer/json-visualizer-layout.tsx similarity index 100% rename from apps/web/src/components/json-visualizer/json-visualizer-layout.tsx rename to apps/desktop-ui/src/components/json-visualizer/json-visualizer-layout.tsx diff --git a/apps/web/src/components/json-visualizer/node-table-dialog.tsx b/apps/desktop-ui/src/components/json-visualizer/node-table-dialog.tsx similarity index 100% rename from apps/web/src/components/json-visualizer/node-table-dialog.tsx rename to apps/desktop-ui/src/components/json-visualizer/node-table-dialog.tsx diff --git a/apps/web/src/components/jsonpath-playground/jsonpath-playground-layout.tsx b/apps/desktop-ui/src/components/jsonpath-playground/jsonpath-playground-layout.tsx similarity index 100% rename from apps/web/src/components/jsonpath-playground/jsonpath-playground-layout.tsx rename to apps/desktop-ui/src/components/jsonpath-playground/jsonpath-playground-layout.tsx diff --git a/apps/web/src/components/jwt-decoder/jwt-decoder-layout.tsx b/apps/desktop-ui/src/components/jwt-decoder/jwt-decoder-layout.tsx similarity index 100% rename from apps/web/src/components/jwt-decoder/jwt-decoder-layout.tsx rename to apps/desktop-ui/src/components/jwt-decoder/jwt-decoder-layout.tsx diff --git a/apps/web/src/components/jwt-decoder/jwt-signer-layout.tsx b/apps/desktop-ui/src/components/jwt-decoder/jwt-signer-layout.tsx similarity index 100% rename from apps/web/src/components/jwt-decoder/jwt-signer-layout.tsx rename to apps/desktop-ui/src/components/jwt-decoder/jwt-signer-layout.tsx diff --git a/apps/web/src/components/keycode-inspector/keycode-inspector-layout.tsx b/apps/desktop-ui/src/components/keycode-inspector/keycode-inspector-layout.tsx similarity index 100% rename from apps/web/src/components/keycode-inspector/keycode-inspector-layout.tsx rename to apps/desktop-ui/src/components/keycode-inspector/keycode-inspector-layout.tsx diff --git a/apps/web/src/components/language-switcher.tsx b/apps/desktop-ui/src/components/language-switcher.tsx similarity index 100% rename from apps/web/src/components/language-switcher.tsx rename to apps/desktop-ui/src/components/language-switcher.tsx diff --git a/apps/web/src/components/lazy/LazyMonaco.tsx b/apps/desktop-ui/src/components/lazy/LazyMonaco.tsx similarity index 100% rename from apps/web/src/components/lazy/LazyMonaco.tsx rename to apps/desktop-ui/src/components/lazy/LazyMonaco.tsx diff --git a/apps/desktop-ui/src/components/legal-agreement-footer.tsx b/apps/desktop-ui/src/components/legal-agreement-footer.tsx new file mode 100644 index 00000000..0b87f562 --- /dev/null +++ b/apps/desktop-ui/src/components/legal-agreement-footer.tsx @@ -0,0 +1,244 @@ +"use client"; + +import * as React from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; + +const LAST_UPDATED = "July 14, 2026"; + +function TermsBody() { + return ( +
+

Last updated: {LAST_UPDATED}

+ +
+

1. Agreement

+

+ By accessing or using MyDevTools (the "Service"), you agree to these Terms of + Service and our Privacy Policy. If you do not agree, do not use the Service. +

+
+ +
+

2. Your account

+

+ You sign in with a supported provider, and we associate your account with your name and + email address. You are responsible for keeping your account and sign-in credentials + secure and for all activity under your account. +

+
+ +
+

3. Plans & payment

+

+ Paid plans are billed through a third-party payment processor. By purchasing a plan you + authorize us and our processor to charge the applicable fees. Except where required by + law or stated otherwise, fees are non-refundable. We may change pricing on a + going-forward basis with notice. +

+
+ +
+

4. Your data and encryption

+

+ The Service is local-first: the content you create with the tools is stored on your + device. If you enable cloud sync, your data is encrypted on your device with a vault + password that only you know, and only the encrypted result is sent to us. We never + receive your vault password and cannot decrypt or recover your synced data. +

+

+ If you lose your vault password, your encrypted data cannot be recovered by anyone, + including us. Keeping a backup of your vault password is your responsibility. +

+
+ +
+

5. Acceptable use

+

+ You agree to use the Service lawfully and not to infringe others' rights, disrupt the + Service, or attempt to access accounts or data that are not yours. We may suspend or + terminate access for conduct that we reasonably believe violates these terms. +

+
+ +
+

6. Availability & disclaimer

+

+ The Service is provided "as is" without warranties of any kind. We do not + guarantee uninterrupted or error-free operation. To the maximum extent permitted by law, + we are not liable for any indirect or consequential damages, or for loss of data resulting + from a lost vault password or from data stored only on your device. +

+
+ +
+

7. Changes

+

+ We may update these terms from time to time. Continued use after changes take effect + constitutes acceptance of the revised terms. +

+
+ +
+

8. Contact

+

For questions about these terms, contact us using the details provided on the website.

+
+
+ ); +} + +function PrivacyBody() { + return ( +
+

Last updated: {LAST_UPDATED}

+ +
+

1. Our approach

+

+ MyDevTools is built to be private by design. The only personal information we hold on our + servers is your account and billing details. Everything you create with the tools stays on + your device unless you choose to sync it — and anything you sync is end-to-end encrypted so + that we cannot read it. +

+
+ +
+

2. Information we store

+

We store only the minimum needed to run your account:

+
    +
  • Your name and email address, from the sign-in provider you use.
  • +
  • + Payment information required to process your subscription. Card details are handled by + our third-party payment processor; we do not store full card numbers. +
  • +
+
+ +
+

3. Your tool data stays local

+

+ The content you create with the tools (notes, snippets, requests, keys, and other tool + data) is saved locally on your device. It is not sent to us and is not part of your + account unless you enable cloud sync. +

+
+ +
+

4. Optional cloud sync (zero-knowledge)

+

+ If you turn on cloud sync in Settings, your data is encrypted on your device with a vault + password that only you know. Only the resulting encrypted blob is transmitted and stored. + We never receive your vault password and have no way to decrypt or view your raw data — + only you can, with your vault password. +

+
+ +
+

5. What we do not do

+

+ We do not read your tool data, we do not sell your personal information, and we do not use + your content for advertising. If you lose your vault password, we cannot recover your + encrypted data — that is the trade-off of true client-side encryption. +

+
+ +
+

6. How we use what we store

+

+ We use your account and billing information to provide the Service, process payments, + provide support, communicate service-related messages, and comply with legal obligations. +

+
+ +
+

7. Retention & deletion

+

+ We keep account and billing data for as long as your account is active or as required by + law. When you delete your account, we delete your account data and any encrypted synced + blobs; data stored only on your device is removed when you delete it locally. +

+
+ +
+

8. Your rights

+

+ Depending on your location, you may have rights to access, correct, export, or delete your + personal information. Contact us to exercise these rights. +

+
+ +
+

9. Updates

+

+ We may update this Privacy Policy from time to time. The "last updated" date above + reflects the latest version; continued use after changes constitutes acceptance. +

+
+
+ ); +} + +type LegalAgreementFooterProps = { + className?: string; + linkClassName?: string; +}; + +export function LegalAgreementFooter({ className, linkClassName }: LegalAgreementFooterProps) { + const [termsOpen, setTermsOpen] = React.useState(false); + const [privacyOpen, setPrivacyOpen] = React.useState(false); + + const linkStyles = cn( + "font-medium text-foreground/80 underline decoration-border underline-offset-4 transition-colors hover:text-foreground", + linkClassName + ); + + return ( + <> +

+ By continuing, you agree to our{" "} + {" "} + and{" "} + + . +

+ + + + + Terms of Service + + How you may use MyDevTools, and how your data and vault password are handled. + + +
+ +
+
+
+ + + + + Privacy Policy + + What we store, what stays on your device, and our zero-knowledge sync. + + +
+ +
+
+
+ + ); +} diff --git a/apps/web/src/components/line-sort-dedupe/line-sort-dedupe-layout.tsx b/apps/desktop-ui/src/components/line-sort-dedupe/line-sort-dedupe-layout.tsx similarity index 100% rename from apps/web/src/components/line-sort-dedupe/line-sort-dedupe-layout.tsx rename to apps/desktop-ui/src/components/line-sort-dedupe/line-sort-dedupe-layout.tsx diff --git a/apps/desktop-ui/src/components/login-form.tsx b/apps/desktop-ui/src/components/login-form.tsx new file mode 100644 index 00000000..f4be2ef6 --- /dev/null +++ b/apps/desktop-ui/src/components/login-form.tsx @@ -0,0 +1,327 @@ +"use client"; + +import * as React from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { + GoogleAuthProvider, + GithubAuthProvider, + signInWithPopup, + fetchSignInMethodsForEmail, + linkWithCredential, + OAuthProvider, +} from "firebase/auth"; +import { auth } from "../database/firebase"; +import { useEffect, useRef, useState } from "react"; +import { establishBackendSession } from "@/lib/backend-auth"; +import { handoffDesktopToken } from "@/lib/desktop-handoff"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Loader2, AlertCircle, Github, Fingerprint } from "lucide-react"; +import { signInWithPasskey, startConditionalPasskeyAuth } from "@/lib/passkey" +import { toast } from "sonner"; +import { isDesktop } from "@/lib/desktop/is-desktop"; +import { DesktopLogin } from "@/components/desktop/desktop-login"; + +export function LoginForm() { + // Gate on mount so the statically-exported HTML (window undefined → web form) + // doesn't mismatch the desktop client render. + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + if (!mounted) return null; + + // Desktop (Tauri) can't use OAuth popups in WKWebView — it signs in through + // the system browser instead. Web keeps the full provider/passkey flow. + if (isDesktop()) { + return ; + } + return ; +} + +function WebLoginForm() { + const router = useRouter(); + const [loadingProvider, setLoadingProvider] = useState<"google" | "github" | "passkey" | "">(""); + const [error, setError] = useState(""); + const conditionalStarted = useRef(false); + + // Conditional autofill: surfaces passkeys in the username field's autocomplete UI. + useEffect(() => { + if (conditionalStarted.current) return; + conditionalStarted.current = true; + let aborted = false; + (async () => { + try { + const result = await startConditionalPasskeyAuth(); + if (!aborted && result) router.replace("/dashboard"); + } catch { + // Conditional auth races with explicit button; ignore silently. + } + })(); + return () => { + aborted = true; + }; + }, [router]); + + // After a successful login, check for ?invite= in the URL and + // auto-accept the invitation, then redirect to the invited workspace or + // the dashboard. Always redirects — never throws or blocks navigation. + const handleInviteToken = async (): Promise => { + const params = new URLSearchParams( + typeof window !== "undefined" ? window.location.search : "" + ) + // Desktop-app sign-in handoff: mint a token and hand it back to the app + // (loopback callback when ?cb= present, else the mydevtools:// deep link). + if (params.get("desktop") === "1") { + const ok = await handoffDesktopToken(window.location.search) + toast[ok ? "success" : "error"]( + ok ? "Signed in — returning to the MyDevTools app…" : "Could not hand off sign-in to the desktop app" + ) + return "/dashboard" + } + + return "/dashboard" + } + + const handlePasskey = async () => { + setLoadingProvider("passkey"); + setError(""); + const NO_PASSKEY_MSG = + "No passkey found for this site. Sign in with Google or GitHub first, then add a passkey from Settings → Security."; + try { + await signInWithPasskey(); + router.push(await handleInviteToken()); + } catch (e: any) { + const msg = e instanceof Error ? e.message : "Passkey sign-in failed."; + // Browser-side: NotAllowedError fires for both "user cancelled" and "no + // credentials available". Treat both as the same guidance — harmless if + // the user actually cancelled. + const noCreds = + e?.name === "NotAllowedError" || + /no .*(credential|passkey)|not .*registered|unknown passkey/i.test(msg); + setError(noCreds ? NO_PASSKEY_MSG : msg); + } finally { + setLoadingProvider(""); + } + }; + + const handleLogin = async (provider: GoogleAuthProvider | GithubAuthProvider, providerName: "google" | "github") => { + setLoadingProvider(providerName); + setError(""); + try { + const result = await signInWithPopup(auth, provider); + const idToken = await result.user.getIdToken(); + try { + await establishBackendSession(idToken, { checkRevoked: true }); + } catch (sessionErr) { + console.error("Backend session failed:", sessionErr); + setError("Signed in, but could not start an API session. Please try again."); + return; + } + router.push(await handleInviteToken()); + } catch (error: any) { + console.error("Error during sign-in:", error); + + if (error.code === 'auth/account-exists-with-different-credential') { + try { + const email = error.customData?.email; + const pendingCredential = OAuthProvider.credentialFromError(error); + + console.log("Account linking error details:", { + email, + pendingCredential, + customData: error.customData + }); + + if (!email || !pendingCredential) { + throw new Error("Could not resolve account details for linking."); + } + + // Get sign-in methods for this email. + const methods = await fetchSignInMethodsForEmail(auth, email); + console.log("Available sign-in methods:", methods); + + if (methods.length > 0) { + const providerId = methods[0]; + let existingProvider: GoogleAuthProvider | GithubAuthProvider | null = null; + + if (providerId === GoogleAuthProvider.PROVIDER_ID) { + existingProvider = new GoogleAuthProvider(); + } else if (providerId === GithubAuthProvider.PROVIDER_ID) { + existingProvider = new GithubAuthProvider(); + } + + if (existingProvider) { + // Clear previous error + setError(""); + + // Inform user + const linkProviderName = providerId === GoogleAuthProvider.PROVIDER_ID ? "Google" : "GitHub"; + alert(`You already have an account with ${linkProviderName}. Please sign in with ${linkProviderName} to link your accounts.`); + + // Sign in with the existing provider + const result = await signInWithPopup(auth, existingProvider); + + // Link the pending credential + await linkWithCredential(result.user, pendingCredential); + const idToken = await result.user.getIdToken(); + try { + await establishBackendSession(idToken, { checkRevoked: true }); + } catch (sessionErr) { + console.error("Backend session failed:", sessionErr); + setError("Signed in, but could not start an API session. Please try again."); + return; + } + router.push(await handleInviteToken()); + return; + } else { + setError(`Account exists with provider: ${providerId}, but automatic linking is not supported.`); + } + } else { + setError("An account with this email already exists, but we couldn't determine the sign-in method. Please try signing in with the other provider."); + } + } catch (linkError: any) { + console.error("Error linking accounts:", linkError); + setError("Failed to link accounts. Please try signing in with the provider you originally used."); + } + } else { + setError( + error.code === "auth/popup-closed-by-user" + ? "Sign-in was cancelled. Please try again." + : "Failed to sign in. Please try again." + ); + } + } finally { + setLoadingProvider(""); + } + }; + + const oauthButtonClass = + "h-11 w-full justify-center border-border/70 bg-background/50 text-[15px] font-medium shadow-sm backdrop-blur-sm transition-all hover:border-border hover:bg-muted/60 hover:shadow-md focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background dark:bg-background/30"; + + return ( +
+ {error && ( + + + {error} + + )} + + {/* Hidden username field — required for conditional WebAuthn autofill UI. */} + + +
+ + +
+
+ +
+
+ + or continue with + +
+
+ + + +
+
+ +
+
+ + or continue with + +
+
+ + +
+
+ ); +} diff --git a/apps/desktop-ui/src/components/login-redirect-if-authed.tsx b/apps/desktop-ui/src/components/login-redirect-if-authed.tsx new file mode 100644 index 00000000..670758d5 --- /dev/null +++ b/apps/desktop-ui/src/components/login-redirect-if-authed.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import useAuth from "@/utils/useAuth"; +import { Loader2 } from "lucide-react"; +import { ensureBackendSession } from "@/lib/backend-auth"; +import { handoffDesktopToken, isDesktopHandoff } from "@/lib/desktop-handoff"; + +export function LoginRedirectIfAuthed() { + const { user, loading } = useAuth(false); + const router = useRouter(); + + // Only a signed-in (real) user redirects away from the sign-in screen. + const isRealUser = !!user; + + useEffect(() => { + if (loading || !isRealUser || !user) return; + // Desktop sign-in handoff (?desktop=1): an already-signed-in browser must + // mint the token and return to the app, NOT bounce to /dashboard. The + // backend session cookie may be stale (Firebase client outlives it), so + // refresh it before minting or the desktop-token endpoint 401s. + if (isDesktopHandoff(window.location.search)) { + void (async () => { + try { + await ensureBackendSession(user); + } catch { + /* fall through — handoff will surface failure to the app */ + } + await handoffDesktopToken(window.location.search); + })(); + return; + } + const next = new URLSearchParams(window.location.search).get("next"); + router.replace(next && next.startsWith("/") ? next : "/dashboard"); + }, [loading, isRealUser, user, router]); + + if (loading || isRealUser) { + return ( +
+ +
+ ); + } + + return null; +} diff --git a/apps/desktop-ui/src/components/logo.tsx b/apps/desktop-ui/src/components/logo.tsx new file mode 100644 index 00000000..f0018f86 --- /dev/null +++ b/apps/desktop-ui/src/components/logo.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import Image from 'next/image'; + +interface LogoProps { + size?: number; + className?: string; + showText?: boolean; +} + +export function Logo({ size = 32, className = '', showText = true }: LogoProps) { + return ( +
+
+ Logo + Logo +
+ {showText && ( +
+
+ MyDevTools + MyDevTools +
+ + Beta + +
+ )} +
+ ); +} diff --git a/apps/web/src/components/lorem-ipsum/lorem-ipsum-layout.tsx b/apps/desktop-ui/src/components/lorem-ipsum/lorem-ipsum-layout.tsx similarity index 100% rename from apps/web/src/components/lorem-ipsum/lorem-ipsum-layout.tsx rename to apps/desktop-ui/src/components/lorem-ipsum/lorem-ipsum-layout.tsx diff --git a/apps/web/src/components/markdown-preview-html/markdown-preview-html-layout.tsx b/apps/desktop-ui/src/components/markdown-preview-html/markdown-preview-html-layout.tsx similarity index 100% rename from apps/web/src/components/markdown-preview-html/markdown-preview-html-layout.tsx rename to apps/desktop-ui/src/components/markdown-preview-html/markdown-preview-html-layout.tsx diff --git a/apps/web/src/components/markdown-table-generator/markdown-table-generator-layout.tsx b/apps/desktop-ui/src/components/markdown-table-generator/markdown-table-generator-layout.tsx similarity index 100% rename from apps/web/src/components/markdown-table-generator/markdown-table-generator-layout.tsx rename to apps/desktop-ui/src/components/markdown-table-generator/markdown-table-generator-layout.tsx diff --git a/apps/desktop-ui/src/components/marketing-seo-page.tsx b/apps/desktop-ui/src/components/marketing-seo-page.tsx new file mode 100644 index 00000000..6c75b9cb --- /dev/null +++ b/apps/desktop-ui/src/components/marketing-seo-page.tsx @@ -0,0 +1,197 @@ +import type { ReactNode } from 'react' +import Link from 'next/link' +import { ArrowRight, CheckCircle2 } from 'lucide-react' +import { Header } from '@/components/header' +import { Footer } from '@/components/footer' +import { MdtFx } from '@/components/mdt-fx' +import MdtAurora from '@/components/mdt-aurora' +import { MdtBoot } from '@/components/mdt-boot' +import type { PlatformSeoPage } from '@/lib/seo/platform-pages' +import { publicToolSlugs } from '@/lib/tool-categories' +import { toolsMetadata } from '@/lib/metadata' +import { buildPlatformPageJsonLd } from '@/lib/seo/structured-data' + +const popularToolSlugs = [ + 'json-formatter', + 'jwt-decoder', + 'api-client', + 'regex-tester', + 'uuid-generator', + 'base64', +] + +function isExternalHref(href: string) { + return href.startsWith('http://') || href.startsWith('https://') +} + +function CtaLink({ + href, + label, + variant = 'primary', +}: { + href: string + label: string + variant?: 'primary' | 'secondary' +}) { + const external = isExternalHref(href) + return ( + + {label} + + + ) +} + +export function MarketingSeoPage({ + page, + children, +}: { + page: PlatformSeoPage + children?: ReactNode +}) { + const popularTools = popularToolSlugs + .filter((slug) => publicToolSlugs.includes(slug) && toolsMetadata[slug]) + .map((slug) => ({ slug, ...toolsMetadata[slug] })) + const jsonLd = buildPlatformPageJsonLd(page.slug) + + return ( +
+
+
+
+ + + + + + + + + +
+ + + {jsonLd ? ( +