refactor(stylesheet): move Semantic CSS to the browser (#3329)

This commit is contained in:
Amruth Pillai
2026-08-16 16:50:27 +02:00
committed by GitHub
parent f848e57436
commit 9509b5bc2e
203 changed files with 11838 additions and 14842 deletions
-3
View File
@@ -52,7 +52,6 @@
"@reactive-resume/db": "workspace:*",
"@reactive-resume/env": "workspace:*",
"@reactive-resume/mcp": "workspace:*",
"@reactive-resume/pdf": "workspace:*",
"@reactive-resume/schema": "workspace:*",
"@reactive-resume/utils": "workspace:*",
"@sindresorhus/slugify": "^3.0.0",
@@ -61,7 +60,6 @@
"ai": "^7.0.58",
"bcrypt": "^6.0.0",
"better-auth": "1.6.26",
"canonicalize": "^3.0.0",
"cjk-regex": "^3.4.0",
"css-tree": "^3.2.1",
"deepmerge-ts": "^7.1.5",
@@ -74,7 +72,6 @@
"node-html-parser": "^9.0.1",
"nodemailer": "^9.0.5",
"ollama-ai-provider-v2": "^4.0.1",
"pdfjs-dist": "^6.2.108",
"pg": "^8.23.0",
"phosphor-icons-react-pdf": "^0.1.3",
"react": "^19.2.8",
+4 -4
View File
@@ -131,10 +131,10 @@ describe("createApp", () => {
it("uses the transport address for public PDF fallback despite rotated forwarding headers", async () => {
const { createApp } = await import("./app");
const app = createApp();
const first = new Request("http://localhost:3001/api/resumes/jane/resume/pdf?reason=render-data-hash", {
const first = new Request("http://localhost:3001/api/resumes/jane/resume/pdf", {
headers: { "x-forwarded-for": "198.51.100.1" },
});
const rotated = new Request("http://localhost:3001/api/resumes/jane/resume/pdf?reason=render-data-hash", {
const rotated = new Request("http://localhost:3001/api/resumes/jane/resume/pdf", {
headers: { "x-forwarded-for": "198.51.100.2" },
});
const env = transportEnv("203.0.113.9");
@@ -159,8 +159,8 @@ describe("createApp", () => {
const unknownRpcRequest = new Request("http://localhost:3001/api/rpc", {
headers: { "cf-connecting-ip": "198.51.100.2" },
});
const trustedOpenApiRequest = new Request("http://localhost:3001/api/openapi/resumes/jane/resume/style-projection");
const unknownOpenApiRequest = new Request("http://localhost:3001/api/openapi/resumes/jane/resume/style-projection");
const trustedOpenApiRequest = new Request("http://localhost:3001/api/openapi/resumes/jane/resume");
const unknownOpenApiRequest = new Request("http://localhost:3001/api/openapi/resumes/jane/resume");
await app.fetch(trustedRpcRequest, transportEnv("203.0.113.9"));
await app.fetch(unknownRpcRequest);
+11 -41
View File
@@ -6,16 +6,6 @@ const mocks = vi.hoisted(() => ({
vi.mock("@reactive-resume/api/features/resume/public-pdf", () => ({
createPublicResumePdf: mocks.createPublicResumePdf,
PUBLIC_RESUME_PDF_MISMATCH_REASONS: [
"missing-projection",
"format-version",
"language-version",
"semantic-tree-version",
"registry-fingerprint",
"adapter-fingerprint",
"render-data-hash",
"invalid-projection",
],
}));
const { handlePublicResumePdf } = await import("./public-resume-pdf");
@@ -24,18 +14,15 @@ const trustedClient = "203.0.113.9";
describe("handlePublicResumePdf", () => {
beforeEach(() => vi.clearAllMocks());
it("returns the authorized fallback PDF with strict mismatch metadata and cache policy", async () => {
it("returns the authorized on-demand PDF without forwarding compatibility metadata", async () => {
const body = new File(["%PDF"], "Ada_Lovelace.pdf", { type: "text/plain" });
mocks.createPublicResumePdf.mockResolvedValueOnce({
body,
filename: "Ada_Lovelace.pdf",
});
const registry = "0".repeat(64);
const adapter = "1".repeat(64);
const request = new Request(
`https://example.com/api/resumes/jane/resume/pdf?reason=render-data-hash&registryFingerprint=${registry}&adapterFingerprint=${adapter}`,
{ headers: { "x-forwarded-for": "203.0.113.7" } },
);
const request = new Request("https://example.com/api/resumes/jane/resume/pdf?ignored=true", {
headers: { "x-forwarded-for": "203.0.113.7" },
});
const response = await handlePublicResumePdf(request, "jane", "resume", trustedClient);
@@ -50,13 +37,10 @@ describe("handlePublicResumePdf", () => {
slug: "resume",
requestHeaders: request.headers,
trustedClient,
mismatchReason: "render-data-hash",
clientRegistryFingerprint: registry,
clientAdapterFingerprint: adapter,
});
});
it("defaults a missing mismatch reason and keeps password/private responses uncacheable", async () => {
it("keeps password and private responses uncacheable", async () => {
mocks.createPublicResumePdf.mockResolvedValueOnce({
body: new File(["%PDF"], "resume.pdf", { type: "application/pdf" }),
filename: "resume.pdf",
@@ -66,13 +50,15 @@ describe("handlePublicResumePdf", () => {
const response = await handlePublicResumePdf(request, "jane", "resume", trustedClient);
expect(response.headers.get("Cache-Control")).toBe("private, no-store");
expect(mocks.createPublicResumePdf).toHaveBeenCalledWith(
expect.objectContaining({ mismatchReason: "missing-projection" }),
);
expect(mocks.createPublicResumePdf).toHaveBeenCalledWith({
username: "jane",
slug: "resume",
requestHeaders: request.headers,
trustedClient,
});
});
it.each([
[{ code: "BAD_REQUEST" }, 400],
[{ code: "NEED_PASSWORD" }, 401],
[{ code: "NOT_FOUND" }, 404],
[{ code: "RATE_LIMIT_EXCEEDED" }, 429],
@@ -90,20 +76,4 @@ describe("handlePublicResumePdf", () => {
expect(response.status).toBe(status);
expect(response.headers.get("Cache-Control")).toBe("private, no-store");
});
it.each(["?reason=private-source", "?registryFingerprint=unsafe", "?adapterFingerprint=unsafe"])(
"rejects invalid fallback metadata before the API service",
async (search) => {
const response = await handlePublicResumePdf(
new Request(`https://example.com/api/resumes/jane/resume/pdf${search}`),
"jane",
"resume",
trustedClient,
);
expect(response.status).toBe(400);
expect(response.headers.get("Cache-Control")).toBe("private, no-store");
expect(mocks.createPublicResumePdf).not.toHaveBeenCalled();
},
);
});
+1 -30
View File
@@ -1,57 +1,28 @@
import type { PublicResumePdfMismatchReason } from "@reactive-resume/api/features/resume/public-pdf";
import {
createPublicResumePdf,
PUBLIC_RESUME_PDF_MISMATCH_REASONS,
} from "@reactive-resume/api/features/resume/public-pdf";
import { createPublicResumePdf } from "@reactive-resume/api/features/resume/public-pdf";
const noStoreResponse = (body: string, status: number) =>
new Response(body, { status, headers: { "Cache-Control": "private, no-store" } });
const errorStatus = (error: unknown): number => {
const code = typeof error === "object" && error && "code" in error ? (error as { code?: unknown }).code : undefined;
if (code === "BAD_REQUEST") return 400;
if (code === "NEED_PASSWORD") return 401;
if (code === "NOT_FOUND") return 404;
if (code === "RATE_LIMIT_EXCEEDED") return 429;
return 500;
};
const fingerprint = (value: string | null): string | undefined => {
if (value === null) return;
return /^[a-f0-9]{64}$/.test(value) ? value : undefined;
};
export async function handlePublicResumePdf(
request: Request,
username: string,
slug: string,
trustedClient = "unknown",
): Promise<Response> {
const searchParams = new URL(request.url).searchParams;
const rawReason = searchParams.get("reason") ?? "missing-projection";
if (!PUBLIC_RESUME_PDF_MISMATCH_REASONS.includes(rawReason as PublicResumePdfMismatchReason)) {
return noStoreResponse("Invalid fallback metadata", 400);
}
const rawRegistryFingerprint = searchParams.get("registryFingerprint");
const rawAdapterFingerprint = searchParams.get("adapterFingerprint");
const clientRegistryFingerprint = fingerprint(rawRegistryFingerprint);
const clientAdapterFingerprint = fingerprint(rawAdapterFingerprint);
if (
(rawRegistryFingerprint !== null && clientRegistryFingerprint === undefined) ||
(rawAdapterFingerprint !== null && clientAdapterFingerprint === undefined)
) {
return noStoreResponse("Invalid fallback metadata", 400);
}
try {
const result = await createPublicResumePdf({
username,
slug,
requestHeaders: request.headers,
trustedClient,
mismatchReason: rawReason as PublicResumePdfMismatchReason,
...(clientRegistryFingerprint ? { clientRegistryFingerprint } : {}),
...(clientAdapterFingerprint ? { clientAdapterFingerprint } : {}),
});
return new Response(result.body, {
-5
View File
@@ -2,7 +2,6 @@ import { pathToFileURL } from "node:url";
import { serve } from "@hono/node-server";
import { env } from "@reactive-resume/env/server";
import { createApp } from "./http/app";
import { stylesheetPreflightRunner } from "./services/stylesheet-preflight";
import { runStartupChecks } from "./startup/checks";
export { createApp } from "./http/app";
@@ -32,10 +31,6 @@ async function main() {
console.info(`🚀 Up and running on http://localhost:${info.port}`);
},
);
// Load the heavy PDF preflight runtime once, now, so the first semantic-CSS edit
// does not pay (and time out on) the cold worker start.
stylesheetPreflightRunner.warmup();
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
-2
View File
@@ -3,7 +3,6 @@ import { RPCHandler } from "@orpc/server/fetch";
import { BatchHandlerPlugin, RequestHeadersPlugin, StrictGetMethodPlugin } from "@orpc/server/plugins";
import router from "@reactive-resume/api/routers";
import { mergeResponseHeaders } from "../http/headers";
import { stylesheetPreflightRunner } from "../services/stylesheet-preflight";
import { getRequestLocale } from "./locale";
const rpcHandler = new RPCHandler(router, {
@@ -24,7 +23,6 @@ export async function handleRpc(request: Request, trustedClient = "unknown") {
reqHeaders: request.headers,
resHeaders,
trustedClient,
stylesheetPreflightRunner,
},
});
@@ -1,332 +0,0 @@
import { afterEach, describe, expect, it } from "vitest";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { createStylesheetPreflightRunner, STYLESHEET_PREFLIGHT_LIMITS } from "./stylesheet-preflight";
const validStylesheet = {
languageVersion: 1,
text: "@version 1;",
} as const;
const input = {
data: defaultResumeData,
template: defaultResumeData.metadata.template,
stylesheet: validStylesheet,
} as const;
// Runners now own a long-lived, reused worker; destroy them so no worker thread
// outlives its test.
const runners: Array<{ destroy(): Promise<void> }> = [];
const track = <T extends { destroy(): Promise<void> }>(runner: T): T => {
runners.push(runner);
return runner;
};
afterEach(async () => {
await Promise.all(runners.splice(0).map((runner) => runner.destroy()));
});
const memoryExhaustionWorker = new URL(
`data:text/javascript,${encodeURIComponent(`
const retained = [];
while (true) {
const batch = Array.from({ length: 100_000 }, (_, index) => ({ batch: retained.length, index }));
retained.push(batch);
}
`)}`,
);
const failedWorker = new URL(
`data:text/javascript,${encodeURIComponent('throw new Error("sensitive worker details");')}`,
);
// Counts how many preflights this single worker instance served so a reuse test
// can prove the worker is not respawned per request.
const reuseCountingWorker = new URL(
`data:text/javascript,${encodeURIComponent(`
import { parentPort } from "node:worker_threads";
let served = 0;
parentPort.postMessage({ type: "ready" });
parentPort.on("message", (message) => {
if (message?.type !== "preflight") return;
served += 1;
parentPort.postMessage({
type: "result",
requestId: message.requestId,
result: { ok: true, pageCount: 1, byteCount: served, diagnostics: [] },
});
});
`)}`,
);
const delayedSuccessfulWorker = new URL(
`data:text/javascript,${encodeURIComponent(`
import { parentPort } from "node:worker_threads";
parentPort.postMessage({ type: "ready" });
parentPort.on("message", (message) => {
if (message?.type !== "preflight") return;
setTimeout(() => {
parentPort.postMessage({
type: "result",
requestId: message.requestId,
result: {
ok: true,
pageCount: 1,
byteCount: Number(message.input.data.basics.name),
diagnostics: [],
},
});
}, 300);
});
`)}`,
);
const delayedReadyWorker = new URL(
`data:text/javascript,${encodeURIComponent(`
import { parentPort } from "node:worker_threads";
parentPort.on("message", (message) => {
if (message?.type !== "preflight") return;
setTimeout(() => {
parentPort.postMessage({
type: "result",
requestId: message.requestId,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [] },
});
}, 10);
});
setTimeout(() => parentPort.postMessage({ type: "ready" }), 50);
`)}`,
);
const neverCompletesWorker = new URL(
`data:text/javascript,${encodeURIComponent(`
import { parentPort } from "node:worker_threads";
parentPort.postMessage({ type: "ready" });
setInterval(() => {}, 1_000);
`)}`,
);
const synchronousFailureWorker = new URL("https://example.com/stylesheet-preflight.mjs");
const numberedInput = (number: number) => ({
...input,
data: {
...input.data,
basics: {
...input.data.basics,
name: String(number),
},
},
});
const invalidInput = () => {
const data = structuredClone(defaultResumeData);
data.customSections = [
{
id: "custom-experience",
type: "experience",
title: "Experience",
icon: "",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [{ id: "summary-shaped-item", hidden: false, content: "<p>Missing company</p>" }],
} as never,
];
return { ...input, data };
};
describe("stylesheet PDF preflight worker", () => {
it("keeps the production resource policy fixed and immutable", () => {
expect(STYLESHEET_PREFLIGHT_LIMITS).toEqual({
timeoutMs: 30_000,
maxPages: 20,
maxBytes: 10_000_000,
maxPageWidthPt: 2_000,
maxPageHeightPt: 20_000,
maxPageAreaPt2: 20_000_000,
maxOldGenerationMb: 256,
maxConcurrentWorkers: 1,
maxQueuedRequests: 32,
});
expect(Object.isFrozen(STYLESHEET_PREFLIGHT_LIMITS)).toBe(true);
});
it("accepts a bounded candidate render in an isolated worker", async () => {
const runner = track(createStylesheetPreflightRunner({ timeoutMs: 15_000 }));
const result = await runner.run(input);
expect(result).toEqual(
expect.objectContaining({
ok: true,
pageCount: 1,
byteCount: expect.any(Number),
}),
);
if (!result.ok) throw new Error(`Expected successful preflight, received ${result.code}.`);
expect(result.byteCount).toBeGreaterThan(0);
expect(runner.activeWorkerCount).toBe(0);
}, 45_000);
it("reuses one warm worker across sequential requests instead of cold-starting each one", async () => {
const runner = track(createStylesheetPreflightRunner({}, reuseCountingWorker));
const first = await runner.run(input);
const second = await runner.run(input);
const third = await runner.run(input);
// A single reused worker increments its per-instance counter; a per-request
// worker would report byteCount 1 every time.
expect([first, second, third].map((result) => (result.ok ? result.byteCount : -1))).toEqual([1, 2, 3]);
expect(runner.activeWorkerCount).toBe(0);
expect(runner.queuedPreflightCount).toBe(0);
});
it("preserves structured resume-data failures across the worker boundary", async () => {
const runner = track(createStylesheetPreflightRunner({ timeoutMs: 15_000 }));
const result = runner.run(invalidInput());
await expect(result).rejects.toMatchObject({
name: "ZodError",
issues: expect.arrayContaining([expect.objectContaining({ path: ["customSections", 0, "items", 0, "company"] })]),
});
expect(runner.activeWorkerCount).toBe(0);
expect(runner.queuedPreflightCount).toBe(0);
}, 20_000);
it("terminates a worker when the render exceeds its deadline", async () => {
const runner = track(createStylesheetPreflightRunner({ timeoutMs: 1 }, neverCompletesWorker));
const result = await runner.run(input);
expect(result).toEqual(expect.objectContaining({ ok: false, code: "STYLESHEET_PREFLIGHT_TIMEOUT" }));
expect(runner.activeWorkerCount).toBe(0);
expect(runner.queuedPreflightCount).toBe(0);
});
it("starts the authored render deadline after the worker runtime is ready", async () => {
const runner = track(createStylesheetPreflightRunner({ timeoutMs: 20 }, delayedReadyWorker));
await expect(runner.run(input)).resolves.toEqual(expect.objectContaining({ ok: true, pageCount: 1 }));
expect(runner.activeWorkerCount).toBe(0);
});
it("returns deterministic output byte and page limit codes", async () => {
const byteRunner = track(createStylesheetPreflightRunner({ maxBytes: 16 }));
const pageRunner = track(createStylesheetPreflightRunner({ maxPages: 0 }));
await expect(byteRunner.run(input)).resolves.toEqual(
expect.objectContaining({ ok: false, code: "STYLESHEET_PREFLIGHT_BYTE_LIMIT" }),
);
await expect(pageRunner.run(input)).resolves.toEqual(
expect.objectContaining({ ok: false, code: "STYLESHEET_PREFLIGHT_PAGE_LIMIT" }),
);
expect(byteRunner.activeWorkerCount).toBe(0);
expect(pageRunner.activeWorkerCount).toBe(0);
}, 30_000);
it("maps worker heap exhaustion to a controlled memory-limit result", async () => {
const runner = track(
createStylesheetPreflightRunner({ maxOldGenerationMb: 8, timeoutMs: 10_000 }, memoryExhaustionWorker),
);
const result = await runner.run(input);
expect(result).toEqual(expect.objectContaining({ ok: false, code: "STYLESHEET_PREFLIGHT_MEMORY_LIMIT" }));
expect(runner.activeWorkerCount).toBe(0);
}, 15_000);
it("does not expose internal errors from a failed worker bootstrap", async () => {
const runner = track(createStylesheetPreflightRunner({}, failedWorker));
const result = await runner.run(input);
expect(result).toEqual({
ok: false,
code: "STYLESHEET_PREFLIGHT_WORKER_FAILED",
message: "The PDF preflight worker failed.",
diagnostics: [],
});
expect(runner.activeWorkerCount).toBe(0);
expect(runner.queuedPreflightCount).toBe(0);
});
it("surfaces sanitized render failures from inside the worker catch path", async () => {
const throwingWorker = new URL(
`data:text/javascript,${encodeURIComponent(`
import { parentPort } from "node:worker_threads";
parentPort.postMessage({ type: "ready" });
parentPort.on("message", (message) => {
if (message?.type !== "preflight") return;
parentPort.postMessage({
type: "result",
requestId: message.requestId,
result: {
ok: false,
code: "STYLESHEET_PREFLIGHT_WORKER_FAILED",
message: "The PDF preflight worker failed. (Error: Canvas is already closed)",
diagnostics: [],
},
});
});
`)}`,
);
const runner = track(createStylesheetPreflightRunner({ timeoutMs: 5_000 }, throwingWorker));
const result = await runner.run(input);
expect(result).toEqual({
ok: false,
code: "STYLESHEET_PREFLIGHT_WORKER_FAILED",
message: "The PDF preflight worker failed. (Error: Canvas is already closed)",
diagnostics: [],
});
});
it("bounds concurrent workers and queued requests without charging queue time to the worker deadline", async () => {
const runner = track(
createStylesheetPreflightRunner(
{
timeoutMs: 500,
maxConcurrentWorkers: 1,
maxQueuedRequests: 2,
},
delayedSuccessfulWorker,
),
);
const completionOrder: number[] = [];
const accepted = [1, 2, 3].map((number) =>
runner.run(numberedInput(number)).then((result) => {
if (result.ok) completionOrder.push(result.byteCount);
return result;
}),
);
const rejected = runner.run(numberedInput(4));
expect(runner.activeWorkerCount).toBe(1);
expect(runner.queuedPreflightCount).toBe(2);
await expect(rejected).resolves.toEqual(
expect.objectContaining({
code: "STYLESHEET_PREFLIGHT_WORKER_FAILED",
message: "The PDF preflight queue is full.",
}),
);
const results = await Promise.all(accepted);
expect(results.every((result) => result.ok)).toBe(true);
expect(completionOrder).toEqual([1, 2, 3]);
expect(runner.activeWorkerCount).toBe(0);
expect(runner.queuedPreflightCount).toBe(0);
}, 5_000);
it("does not leak a slot when the worker constructor throws synchronously", async () => {
const runner = track(createStylesheetPreflightRunner({}, synchronousFailureWorker));
await expect(runner.run(input)).resolves.toEqual(
expect.objectContaining({ ok: false, code: "STYLESHEET_PREFLIGHT_WORKER_FAILED" }),
);
expect(runner.activeWorkerCount).toBe(0);
expect(runner.queuedPreflightCount).toBe(0);
});
});
@@ -1,362 +0,0 @@
import type {
PdfPreflightFailure,
PdfPreflightResult,
StylesheetPreflightInput,
StylesheetPreflightRunner,
} from "@reactive-resume/pdf/server";
import { fileURLToPath } from "node:url";
import { Worker } from "node:worker_threads";
import { STYLESHEET_PREFLIGHT_LIMITS } from "@reactive-resume/pdf/preflight-reference";
export { STYLESHEET_PREFLIGHT_LIMITS } from "@reactive-resume/pdf/preflight-reference";
type StylesheetPreflightLimits = {
timeoutMs: number;
maxPages: number;
maxBytes: number;
maxPageWidthPt: number;
maxPageHeightPt: number;
maxPageAreaPt2: number;
maxOldGenerationMb: number;
maxConcurrentWorkers: number;
maxQueuedRequests: number;
};
const SOURCE_WORKER_LOADER_HEAP_MB = 256;
// The worker is warmed once and reused, so the one-time cold load (which is
// super-linear in available CPU — measured ~9s at 0.5 vCPU, ~53s at 0.35, ~93s at
// 0.25) is paid at startup, not per request. This ceiling must exceed that cold
// load or warmup is killed mid-bootstrap and never completes on a throttled box;
// it only bounds a genuinely stuck bootstrap and is off the per-edit path.
const WORKER_READINESS_TIMEOUT_MS = 120_000;
type SerializedPreflightCause = {
name: string;
message: string;
issues: readonly unknown[];
};
type StylesheetPreflightWorkerMessage =
| { type: "ready" }
| { type: "load_error"; message: string }
| { type: "result"; requestId: number; result: PdfPreflightResult }
| { type: "preflight_error"; requestId: number; cause: SerializedPreflightCause };
export type NodeStylesheetPreflightRunner = StylesheetPreflightRunner & {
readonly activeWorkerCount: number;
readonly queuedPreflightCount: number;
warmup(): void;
destroy(): Promise<void>;
};
const failure = (code: PdfPreflightFailure["code"], message: string): PdfPreflightFailure => ({
ok: false,
code,
message,
diagnostics: [],
});
const workerFailure = (error: Error): PdfPreflightFailure => {
const code = (error as NodeJS.ErrnoException).code;
return code === "ERR_WORKER_OUT_OF_MEMORY" || /heap out of memory/i.test(error.message)
? failure("STYLESHEET_PREFLIGHT_MEMORY_LIMIT", "The PDF preflight worker exceeded its memory limit.")
: failure("STYLESHEET_PREFLIGHT_WORKER_FAILED", "The PDF preflight worker failed.");
};
const sourceWorkerExecArgv = () => {
const importIndex = process.execArgv.findIndex(
(argument, index, arguments_) =>
(argument === "--import" && arguments_[index + 1]?.includes("tsx")) ||
(argument.startsWith("--import=") && argument.includes("tsx")),
);
const inherited = process.execArgv[importIndex];
if (inherited?.startsWith("--import=")) return [inherited];
if (inherited === "--import") return [inherited, process.execArgv[importIndex + 1] as string];
return ["--import", import.meta.resolve("tsx")];
};
const workerLocation = () => {
const source = import.meta.url.endsWith(".ts");
return {
source,
url: source
? new URL("../workers/stylesheet-preflight.ts", import.meta.url)
: new URL("./stylesheet-preflight-worker.mjs", import.meta.url),
// Production must not inherit parent execArgv (e.g. --import/--input-type); source workers need tsx.
execArgv: source ? sourceWorkerExecArgv() : [],
...(source
? {
env: {
...process.env,
TSX_TSCONFIG_PATH: fileURLToPath(new URL("../../tsconfig.json", import.meta.url)),
},
}
: {}),
};
};
type PendingRequest = {
resolve: (result: PdfPreflightResult) => void;
reject: (cause: unknown) => void;
renderTimer?: ReturnType<typeof setTimeout>;
};
type Readiness = {
promise: Promise<Worker>;
resolve: (worker: Worker) => void;
reject: (error: Error) => void;
timer: ReturnType<typeof setTimeout>;
};
export function createStylesheetPreflightRunner(
overrides: Partial<StylesheetPreflightLimits> = {},
testWorkerUrl?: URL,
): NodeStylesheetPreflightRunner {
const limits = Object.freeze({ ...STYLESHEET_PREFLIGHT_LIMITS, ...overrides });
let worker: Worker | undefined;
let ready = false;
let readiness: Readiness | undefined;
let destroyed = false;
let requestSeq = 0;
const pending = new Map<number, PendingRequest>();
// ponytail: process-local, bounded admission; upgrade to a pooled/distributed queue only for multi-process coordination.
const queue: Array<{
input: StylesheetPreflightInput;
resolve: PendingRequest["resolve"];
reject: PendingRequest["reject"];
}> = [];
const teardownWorker = () => {
const dead = worker;
worker = undefined;
ready = false;
if (readiness) {
clearTimeout(readiness.timer);
readiness.reject(new Error("Stylesheet preflight worker did not become ready."));
readiness = undefined;
}
if (!dead) return;
dead.off("message", onMessage);
dead.off("error", onError);
dead.off("exit", onExit);
void dead.terminate().catch(() => undefined);
};
const clearPending = (requestId: number): PendingRequest | undefined => {
const entry = pending.get(requestId);
if (!entry) return undefined;
if (entry.renderTimer) clearTimeout(entry.renderTimer);
pending.delete(requestId);
return entry;
};
const drainQueue = () => {
while (!destroyed && pending.size < limits.maxConcurrentWorkers && queue.length > 0) {
const next = queue.shift();
if (!next) return;
dispatch(next.input, next.resolve, next.reject);
}
};
const resolveRequest = (requestId: number, result: PdfPreflightResult) => {
const entry = clearPending(requestId);
if (!entry) return;
entry.resolve(result);
drainQueue();
};
const rejectRequest = (requestId: number, cause: unknown) => {
const entry = clearPending(requestId);
if (!entry) return;
entry.reject(cause);
drainQueue();
};
// A crashed/exited/timed-out worker is torn down and its in-flight requests are
// failed; the next dispatch (including any queued requests) spawns a fresh one,
// so a poisoned render never lingers across requests.
const failWorker = (result: PdfPreflightFailure) => {
teardownWorker();
const stale = [...pending.keys()];
for (const requestId of stale) resolveRequest(requestId, result);
drainQueue();
};
function onMessage(message: StylesheetPreflightWorkerMessage) {
if (message.type === "ready") {
if (readiness) {
clearTimeout(readiness.timer);
ready = true;
const resolveReady = readiness.resolve;
const readyWorker = worker;
readiness = undefined;
if (readyWorker) resolveReady(readyWorker);
}
return;
}
if (message.type === "load_error") {
failWorker(failure("STYLESHEET_PREFLIGHT_WORKER_FAILED", message.message));
return;
}
if (message.type === "result") {
resolveRequest(message.requestId, message.result);
return;
}
if (message.type === "preflight_error") {
rejectRequest(
message.requestId,
Object.assign(new Error(message.cause.message), { name: message.cause.name, issues: message.cause.issues }),
);
}
}
function onError(error: Error) {
console.warn("[stylesheet-preflight] worker error:", error.message);
failWorker(workerFailure(error));
}
function onExit(code: number) {
if (destroyed || (!worker && pending.size === 0)) return;
console.warn(`[stylesheet-preflight] worker exited unexpectedly (code ${code})`);
failWorker(failure("STYLESHEET_PREFLIGHT_WORKER_FAILED", "The PDF preflight worker failed."));
}
const startWorker = (): Promise<Worker> => {
const location = testWorkerUrl ? { source: false, url: testWorkerUrl, execArgv: [] as string[] } : workerLocation();
let spawned: Worker;
try {
spawned = new Worker(location.url, {
name: "stylesheet-preflight",
resourceLimits: {
// The source-only tsx compiler heap is outside the production render budget.
maxOldGenerationSizeMb: limits.maxOldGenerationMb + (location.source ? SOURCE_WORKER_LOADER_HEAP_MB : 0),
},
execArgv: location.execArgv,
...("env" in location ? { env: location.env } : {}),
});
} catch (error) {
return Promise.reject(error instanceof Error ? error : new Error("Failed to start PDF preflight worker."));
}
worker = spawned;
ready = false;
// The idle reused worker must not keep the process (or a test run) alive; active
// requests stay alive through their ref'd readiness/render timers.
spawned.unref();
spawned.on("message", onMessage);
spawned.once("error", onError);
spawned.once("exit", onExit);
let resolve!: (value: Worker) => void;
let reject!: (error: Error) => void;
const promise = new Promise<Worker>((resolvePromise, rejectPromise) => {
resolve = resolvePromise;
reject = rejectPromise;
});
const timer = setTimeout(() => {
console.warn(`[stylesheet-preflight] worker did not become ready within ${WORKER_READINESS_TIMEOUT_MS}ms`);
teardownWorker();
}, WORKER_READINESS_TIMEOUT_MS);
readiness = { promise, resolve, reject, timer };
return promise;
};
const getReadyWorker = (): Promise<Worker> => {
if (destroyed) return Promise.reject(new Error("Stylesheet preflight worker was terminated."));
if (worker && ready) return Promise.resolve(worker);
if (readiness) return readiness.promise;
return startWorker();
};
// One retry: a worker that failed to become ready is torn down; a second
// attempt spawns a fresh worker before the request gives up.
const waitUntilReady = async (): Promise<Worker> => {
try {
return await getReadyWorker();
} catch {
return await getReadyWorker();
}
};
function dispatch(
input: StylesheetPreflightInput,
resolve: PendingRequest["resolve"],
reject: PendingRequest["reject"],
) {
const requestId = ++requestSeq;
pending.set(requestId, { resolve, reject });
void waitUntilReady()
.then((readyWorker) => {
const entry = pending.get(requestId);
if (!entry) return;
entry.renderTimer = setTimeout(() => {
// A stuck render poisons the reused worker: tear it down and respawn — but
// only the worker this request actually ran on, so a stale timer can never
// kill a newer worker already serving other requests.
clearPending(requestId);
if (worker === readyWorker) teardownWorker();
resolve(failure("STYLESHEET_PREFLIGHT_TIMEOUT", "The PDF preflight exceeded its deadline."));
drainQueue();
}, limits.timeoutMs);
readyWorker.postMessage({ type: "preflight", requestId, input, limits });
})
.catch((error: unknown) => {
resolveRequest(
requestId,
workerFailure(error instanceof Error ? error : new Error("Failed to start PDF preflight worker.")),
);
});
}
return {
get activeWorkerCount() {
return pending.size;
},
get queuedPreflightCount() {
return queue.length;
},
warmup() {
void waitUntilReady().catch(() => undefined);
},
run(input: StylesheetPreflightInput): Promise<PdfPreflightResult> {
return new Promise<PdfPreflightResult>((resolve, reject) => {
if (destroyed) {
resolve(failure("STYLESHEET_PREFLIGHT_WORKER_FAILED", "The PDF preflight worker was terminated."));
return;
}
if (pending.size < limits.maxConcurrentWorkers) {
dispatch(input, resolve, reject);
return;
}
if (queue.length >= limits.maxQueuedRequests) {
resolve(failure("STYLESHEET_PREFLIGHT_WORKER_FAILED", "The PDF preflight queue is full."));
return;
}
queue.push({ input, resolve, reject });
});
},
async destroy() {
destroyed = true;
const dead = worker;
teardownWorker();
await dead?.terminate().catch(() => undefined);
for (const requestId of [...pending.keys()]) {
resolveRequest(
requestId,
failure("STYLESHEET_PREFLIGHT_WORKER_FAILED", "The PDF preflight worker was terminated."),
);
}
while (queue.length > 0) {
const next = queue.shift();
next?.resolve(failure("STYLESHEET_PREFLIGHT_WORKER_FAILED", "The PDF preflight worker was terminated."));
}
},
};
}
export const stylesheetPreflightRunner = createStylesheetPreflightRunner();
@@ -1,78 +0,0 @@
import type {
PdfPreflightPageLimits,
PdfPreflightResult,
RenderPreflightPdfResult,
} from "@reactive-resume/pdf/preflight";
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
type StylesheetPreflightInspectionLimits = PdfPreflightPageLimits & {
maxPages: number;
maxBytes: number;
};
type PdfLoadingTask = {
promise: PromiseLike<{ numPages: number }>;
destroy(): Promise<void>;
};
type LoadPdf = (options: { data: Uint8Array }) => PdfLoadingTask;
export async function inspectPreflightPdf(
rendered: Extract<RenderPreflightPdfResult, { ok: true }>,
limits: StylesheetPreflightInspectionLimits,
loadPdf: LoadPdf = getDocument,
): Promise<PdfPreflightResult> {
if (rendered.bytes.byteLength > limits.maxBytes) {
return {
ok: false,
code: "STYLESHEET_PREFLIGHT_BYTE_LIMIT",
message: "The rendered PDF exceeds the preflight byte limit.",
diagnostics: rendered.diagnostics,
};
}
const byteCount = rendered.bytes.byteLength;
let loadingTask: PdfLoadingTask;
try {
loadingTask = loadPdf({ data: rendered.bytes });
} catch {
return {
ok: false,
code: "STYLESHEET_PREFLIGHT_PARSE_FAILED",
message: "PDF inspection failed.",
diagnostics: rendered.diagnostics,
};
}
try {
let document: { numPages: number };
try {
document = await loadingTask.promise;
} catch {
return {
ok: false,
code: "STYLESHEET_PREFLIGHT_PARSE_FAILED",
message: "PDF inspection failed.",
diagnostics: rendered.diagnostics,
};
}
if (document.numPages > limits.maxPages) {
return {
ok: false,
code: "STYLESHEET_PREFLIGHT_PAGE_LIMIT",
message: "The rendered PDF exceeds the preflight page limit.",
diagnostics: rendered.diagnostics,
};
}
return {
ok: true,
pageCount: document.numPages,
byteCount,
diagnostics: rendered.diagnostics,
};
} finally {
await loadingTask.destroy().catch(() => undefined);
}
}
@@ -1,58 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { inspectPreflightPdf } from "./stylesheet-preflight-inspection";
const warning = {
code: "EXTREME_VALUE",
severity: "warning",
message: "The authored value is unusually large.",
range: {
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 10, offset: 9 },
},
} as const;
const rendered = {
ok: true as const,
bytes: new TextEncoder().encode("%PDF-1.7"),
diagnostics: [warning],
};
const limits = {
maxPages: 20,
maxBytes: 10_000_000,
maxPageWidthPt: 2_000,
maxPageHeightPt: 20_000,
maxPageAreaPt2: 20_000_000,
};
describe("stylesheet preflight PDF inspection", () => {
it("preserves compiler warnings and hides parser exceptions", async () => {
const destroy = vi.fn(async () => undefined);
const result = await inspectPreflightPdf(rendered, limits, () => ({
promise: Promise.reject(new Error("sensitive parser details")),
destroy,
}));
expect(result).toEqual({
ok: false,
code: "STYLESHEET_PREFLIGHT_PARSE_FAILED",
message: "PDF inspection failed.",
diagnostics: [warning],
});
expect(destroy).toHaveBeenCalledOnce();
});
it("preserves compiler warnings when PDF.js fails before returning a loading task", async () => {
const result = await inspectPreflightPdf(rendered, limits, () => {
throw new Error("sensitive parser setup details");
});
expect(result).toEqual({
ok: false,
code: "STYLESHEET_PREFLIGHT_PARSE_FAILED",
message: "PDF inspection failed.",
diagnostics: [warning],
});
});
});
@@ -1,90 +0,0 @@
import type {
PdfPreflightPageLimits,
PdfPreflightResult,
StylesheetPreflightInput,
} from "@reactive-resume/pdf/preflight";
import { parentPort } from "node:worker_threads";
import * as React from "react";
import { inspectPreflightPdf } from "./stylesheet-preflight-inspection";
(globalThis as typeof globalThis & { React: typeof React }).React = React;
type StylesheetPreflightWorkerLimits = PdfPreflightPageLimits & {
maxPages: number;
maxBytes: number;
};
// The worker is long-lived and reused across requests: the heavy PDF runtime is
// loaded once at startup and each preflight arrives as a message (input can no
// longer come from `workerData`, which is fixed at construction time).
type StylesheetPreflightWorkerRequest = {
type: "preflight";
requestId: number;
input: StylesheetPreflightInput;
limits: StylesheetPreflightWorkerLimits;
};
type SerializedPreflightCause = {
name: string;
message: string;
issues: readonly unknown[];
};
const sanitizeWorkerCause = (cause: unknown): string => {
if (!(cause instanceof Error)) return "The PDF preflight worker failed.";
const detail = cause.message.replace(/\s+/g, " ").trim().slice(0, 200);
if (!detail) return "The PDF preflight worker failed.";
return `The PDF preflight worker failed. (${cause.name}: ${detail})`;
};
const serializeZodCause = (cause: unknown): SerializedPreflightCause | undefined => {
if (!(cause instanceof Error) || cause.name !== "ZodError" || !("issues" in cause) || !Array.isArray(cause.issues)) {
return;
}
return { name: cause.name, message: cause.message, issues: cause.issues };
};
// Load the heavy PDF runtime once. A rejection here (missing/broken dependency in
// a pruned production install) is reported explicitly instead of becoming an
// unhandled rejection that silently kills the worker and surfaces as an opaque
// runner-side failure.
const initialization = import("@reactive-resume/pdf/preflight");
void initialization.then(
() => parentPort?.postMessage({ type: "ready" }),
(cause: unknown) => {
console.error("[stylesheet-preflight] worker runtime failed to load", cause);
parentPort?.postMessage({ type: "load_error", message: sanitizeWorkerCause(cause) });
},
);
const handle = async (request: StylesheetPreflightWorkerRequest): Promise<void> => {
try {
const { renderPreflightPdf } = await initialization;
const rendered = await renderPreflightPdf(request.input, request.limits);
const result: PdfPreflightResult = rendered.ok ? await inspectPreflightPdf(rendered, request.limits) : rendered;
parentPort?.postMessage({ type: "result", requestId: request.requestId, result });
} catch (cause) {
const serializedCause = serializeZodCause(cause);
if (serializedCause) {
parentPort?.postMessage({ type: "preflight_error", requestId: request.requestId, cause: serializedCause });
return;
}
console.error("[stylesheet-preflight]", cause);
parentPort?.postMessage({
type: "result",
requestId: request.requestId,
result: {
ok: false,
code: "STYLESHEET_PREFLIGHT_WORKER_FAILED",
message: sanitizeWorkerCause(cause),
diagnostics: [],
},
});
}
};
parentPort?.on("message", (message: StylesheetPreflightWorkerRequest) => {
if (message.type !== "preflight") return;
void handle(message);
});
+1 -4
View File
@@ -33,10 +33,7 @@ const promptAssetsPlugin: TsdownPlugin = {
};
export default defineConfig({
entry: {
index: "src/index.ts",
"stylesheet-preflight-worker": "src/workers/stylesheet-preflight.ts",
},
entry: { index: "src/index.ts" },
format: "esm",
platform: "node",
target: "node24",
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Aansoeke per week gestuur (laaste 8 weke)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Aansoek gedoen"
@@ -534,10 +533,6 @@ msgstr "Aansoek gedoen"
msgid "Applied on"
msgstr "Toegepas op"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Toegepas met waarskuwings"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabies"
@@ -1765,7 +1760,6 @@ msgstr "Voer jou wagwoord in om die opstel van twee-faktor-magtiging te bevestig
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Voer jou wagwoord in om twee-faktor-magtiging te deaktiveer. Jou rekening sal minder veilig wees sonder 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Fout"
@@ -2003,6 +1997,10 @@ msgstr "Misluk om jou rugsteunkodeverifikasie te voltooi. Probeer asseblief weer
msgid "Failed to verify your code. Please try again."
msgstr "Misluk om jou kode te verifieer. Probeer asseblief weer."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funksies"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Voorskou"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Voorskou en uitvoer gebruik die laaste geldige weergawe."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Lees…"
msgid "Ready to activate"
msgstr "Gereed om te aktiveer"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Gereed om te aktiveer met waarskuwings"
@@ -3663,10 +3669,6 @@ msgstr "Herstel"
msgid "Reset Password"
msgstr "Herstel wagwoord"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Stel terug na toegepaste stylblad"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Herstel jou wagwoord"
@@ -4195,6 +4197,10 @@ msgstr "Sommige aansoekstelsels vereis een voordat jy kan indien."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Sommige ontleders hanteer beelde verkeerd, en foto's word in sommige streke ontmoedig."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Iets het verkeerd geloop"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stylbladredigeerder"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stylblad bevat foute"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Gebruikers"
msgid "Uzbek"
msgstr "Oezbeeks"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Geldige URL's moet met http:// of https:// begin."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel KI-toegangspoort"
@@ -5411,4 +5429,3 @@ msgstr "Zoem uit"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zoeloe"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "በሳምንት የተላኩ ማመልከቻዎች (ያለፉት 8 ሳምንታት)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "ተመልክቷል"
@@ -534,10 +533,6 @@ msgstr "ተመልክቷል"
msgid "Applied on"
msgstr "የተተገበረው በ"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "ከማስጠንቀቂያዎች ጋር ተተግብሯል"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "ዓረብኛ"
@@ -1765,7 +1760,6 @@ msgstr "ሁለት-ደረጃ ማረጋገጫን ስትቆጣጠሩ የሚሆን
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "ሁለት-ደረጃ ማረጋገጫን ለማቦዘን የይለፍ ቃልዎን ያስገቡ። 2FA ባቦዘነ ጊዜ መለያዎ ያነሰ ደህንነት ይኖረዋል።"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "ስህተት"
@@ -2003,6 +1997,10 @@ msgstr "የመጠባበቂያ ኮድዎን ማረጋገጥ አልተሳካም።
msgid "Failed to verify your code. Please try again."
msgstr "ኮድዎን ማረጋገጥ አልተሳካም። እባክዎ እንደገና ይሞክሩ።"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "መለያዎች"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "ቅድመ እይታ"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "ቅድመ እይታ እና ወደ ውጭ መላክ የመጨረሻውን ትክክለኛ ስሪት ይጠቀሙ።"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "ንባብ…"
msgid "Ready to activate"
msgstr "ለማግበር ዝግጁ"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "ከማስጠንቀቂያዎች ጋር ለማግበር ዝግጁ"
@@ -3663,10 +3669,6 @@ msgstr "ዳግም ለመጀመር"
msgid "Reset Password"
msgstr "የይለፍ ቃል ዳግም አስጀምር"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "ወደ ተተገበረ የቅጥ ሉህ ዳግም አስጀምር"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "የይለፍ ቃልዎን ዳግም ያስጀምሩ"
@@ -4195,6 +4197,10 @@ msgstr "ከማቅረብዎ በፊት አንዳንድ የመተግበሪያ ስ
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "አንዳንድ ተንታኞች ምስሎችን ያታልላሉ፣ እና ፎቶዎች በአንዳንድ ክልሎች ተስፋ ይቆርጣሉ።"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "የሆነ ችግር ተፈጥሯል"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "የቅጥ ሉህ አርታኢ"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "የቅጥ ሉህ ስህተቶች አሉት"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "ተጠቃሚዎች"
msgid "Uzbek"
msgstr "ኡዝቤክኛ"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "ትክክለኛ URL በ http:// ወይም https:// መጀመር አለባቸው።"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "አጉር"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "ዙሉ"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "الطلبات المرسلة أسبوعيًا (آخر 8 أسابيع)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "تم التقديم"
@@ -534,10 +533,6 @@ msgstr "تم التقديم"
msgid "Applied on"
msgstr "تم التقديم في"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "تم استخدامه مع التحذيرات"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "العربية"
@@ -1765,7 +1760,6 @@ msgstr "أدخِل كلمة المرور الخاصة بك لتأكيد إعدا
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "أدخِل كلمة المرور الخاصة بك لتعطيل المصادقة الثنائية. سيكون حسابك أقل أمانًا بدون تمكين المصادقة الثنائية (2FA)."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "خطأ"
@@ -2003,6 +1997,10 @@ msgstr "فشل التحقق من رمز النسخ الاحتياطي الخاص
msgid "Failed to verify your code. Please try again."
msgstr "فشل التحقق من الرمز الخاص بك. يرجى المحاولة مرة أخرى."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "الميزات"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "معاينة"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "استخدم آخر نسخة صالحة للمعاينة والتصدير."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "قراءة…"
msgid "Ready to activate"
msgstr "جاهز للتفعيل"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "جاهز للتفعيل مع التحذيرات"
@@ -3663,10 +3669,6 @@ msgstr "إعادة تعيين"
msgid "Reset Password"
msgstr "إعادة تعيين كلمة المرور"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "إعادة ضبط نمط التنسيق المُطبق"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "إعادة تعيين كلمة المرور"
@@ -4195,6 +4197,10 @@ msgstr "تتطلب بعض أنظمة التقديم واحدًا قبل أن ت
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "يسيء بعض المحللين التعامل مع الصور، ولا يُنصح باستخدام الصور في بعض المناطق."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "حدث خطأ ما"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "محرر أنماط CSS"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "يحتوي ملف الأنماط على أخطاء"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "المستخدمون"
msgid "Uzbek"
msgstr "الأوزبكية"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "يجب أن تبدأ عناوين URL الصالحة بـ http:// أو https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "بوابة فيركل للذكاء الاصطناعي"
@@ -5411,4 +5429,3 @@ msgstr "تصغير"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "الزولو"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Həftəlik göndərilən müraciətlər (son 8 həftə)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Müraciət edildi"
@@ -534,10 +533,6 @@ msgstr "Müraciət edildi"
msgid "Applied on"
msgstr "Tətbiq edildiyi tarix"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Xəbərdarlıqlarla tətbiq olunur"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Ərəb"
@@ -1765,7 +1760,6 @@ msgstr "İki mərhələli autentifikasiyanı qurmağı təsdiqləmək üçün pa
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "İki mərhələli autentifikasiyanı deaktiv etmək üçün parolunuzu daxil edin. 2FA deaktiv edildikdə, hesabınız daha az təhlükəsiz olacaq."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Xəta"
@@ -2003,6 +1997,10 @@ msgstr "Ehtiyat kodunuzu təsdiqləmək mümkün olmadı. Zəhmət olmasa yenid
msgid "Failed to verify your code. Please try again."
msgstr "Kodunuzu təsdiqləmək mümkün olmadı. Zəhmət olmasa yenidən cəhd edin."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Xüsusiyyətlər"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Önizləmə"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Önizləmə və ixrac üçün son etibarlı versiyadan istifadə edin."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "… oxunur"
msgid "Ready to activate"
msgstr "Aktivləşdirməyə hazırdır"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Xəbərdarlıqlarla aktivləşdirməyə hazırdır"
@@ -3663,10 +3669,6 @@ msgstr "Sıfırla"
msgid "Reset Password"
msgstr "Parolu Sıfırla"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Tətbiq olunmuş stil cədvəlinə sıfırlayın"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Parolunuzu sıfırlayın"
@@ -4195,6 +4197,10 @@ msgstr "Bəzi proqram sistemləri təqdim etməzdən əvvəl birini tələb edir
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Bəzi təhlilçilər şəkilləri səhv idarə edir və bəzi bölgələrdə fotoşəkillər tövsiyə edilmir."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Nəsə səhv getdi"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stil cədvəli redaktoru"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stil cədvəlində səhvlər var"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "İstifadəçilər"
msgid "Uzbek"
msgstr "Özbək"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Etibarlı URLlər http:// və ya https:// ilə başlamalıdır."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Qapısı"
@@ -5411,4 +5429,3 @@ msgstr "Uzaqlaşdır"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Изпратени кандидатури на седмица (последните 8 седмици)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Кандидатствано"
@@ -534,10 +533,6 @@ msgstr "Кандидатствано"
msgid "Applied on"
msgstr "Приложено на"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Прилага се с предупреждения"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Арабски"
@@ -1765,7 +1760,6 @@ msgstr "Въведете паролата си, за да потвърдите
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Въведете паролата си, за да изключите двуфакторното удостоверяване. Вашият профил ще бъде по-малко защитен без активирано 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Грешка"
@@ -2003,6 +1997,10 @@ msgstr "Не успяхте да проверите кода си за архи
msgid "Failed to verify your code. Please try again."
msgstr "Не успяхме да проверим кода ви. Моля, опитайте отново."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Функции"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Преглед"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Прегледът и експортът използват последната валидна версия."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Четене…"
msgid "Ready to activate"
msgstr "Готов за активиране"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Готово за активиране с предупреждения"
@@ -3663,10 +3669,6 @@ msgstr "Нулиране"
msgid "Reset Password"
msgstr "Нулиране на паролата"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Възстановяване към приложения стилов лист"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Нулирайте паролата си"
@@ -4195,6 +4197,10 @@ msgstr "Някои системи за кандидатстване изискв
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Някои парсери обработват неправилно изображения и снимките не се препоръчват в някои региони."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Нещо се обърка"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Редактор на стилови листове"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Стилният лист съдържа грешки"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Потребители"
msgid "Uzbek"
msgstr "Узбекски"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Валидните URL адреси трябва да започват с http:// или https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Намаляване"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Зулуски"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "প্রতি সপ্তাহে পাঠানো আবেদন (শেষ 8 সপ্তাহ)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "আবেদন করা হয়েছে"
@@ -534,10 +533,6 @@ msgstr "আবেদন করা হয়েছে"
msgid "Applied on"
msgstr "প্রয়োগ করা হয়েছে"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "সতর্কতাসহ প্রয়োগ করা হয়েছে"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "আরবি"
@@ -1765,7 +1760,6 @@ msgstr "টু‑ফ্যাক্টর প্রমাণীকরণ সে
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "টু‑ফ্যাক্টর প্রমাণীকরণ বন্ধ করতে আপনার পাসওয়ার্ড লিখুন। 2FA বন্ধ করলে আপনার অ্যাকাউন্ট কম সুরক্ষিত থাকবে।"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "ত্রুটি"
@@ -2003,6 +1997,10 @@ msgstr "আপনার ব্যাকআপ কোড যাচাই কর
msgid "Failed to verify your code. Please try again."
msgstr "আপনার কোড যাচাই করতে ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "বৈশিষ্ট্যসমূহ"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "প্রিভিউ"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "প্রিভিউ এবং এক্সপোর্টের জন্য সর্বশেষ বৈধ সংস্করণটি ব্যবহার করা হয়।"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "পড়া…"
msgid "Ready to activate"
msgstr "সক্রিয় করার জন্য প্রস্তুত"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "সতর্কতা সহ সক্রিয় করার জন্য প্রস্তুত"
@@ -3663,10 +3669,6 @@ msgstr "রিসেট"
msgid "Reset Password"
msgstr "পাসওয়ার্ড রিসেট করুন"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "প্রয়োগকৃত স্টাইলশীটে রিসেট করুন"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "আপনার পাসওয়ার্ড রিসেট করুন"
@@ -4195,6 +4197,10 @@ msgstr "আপনি জমা দেওয়ার আগে কিছু অ
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "কিছু বিশ্লেষক চিত্রগুলিকে ভুলভাবে পরিচালনা করে এবং কিছু অঞ্চলে ফটোগুলিকে নিরুৎসাহিত করা হয়৷"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "কিছু একটা ভুল হয়েছে"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "স্টাইলশিট সম্পাদক"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "স্টাইলশিটে ত্রুটি আছে"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "ব্যবহারকারী"
msgid "Uzbek"
msgstr "উজবেক"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "বৈধ URL অবশ্যই http:// বা https:// দিয়ে শুরু হতে হবে।"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "ভার্সেল এআই গেটওয়ে"
@@ -5411,4 +5429,3 @@ msgstr "জুম আউট"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "জুলু"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Sol·licituds enviades per setmana (darreres 8 setmanes)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Sol·licitada"
@@ -534,10 +533,6 @@ msgstr "Sol·licitada"
msgid "Applied on"
msgstr "Aplicat a"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Aplicat amb avisos"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Àrab"
@@ -1765,7 +1760,6 @@ msgstr "Introdueix la contrasenya per confirmar la configuració de lautentic
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Introdueix la contrasenya per desactivar lautenticació en dos passos. El compte serà menys segur sense lA2F activada."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Error"
@@ -2003,6 +1997,10 @@ msgstr "No s'ha pogut verificar el vostre codi de recuperació. Torneu-ho a prov
msgid "Failed to verify your code. Please try again."
msgstr "S'ha fallat la verificació del vostre codi. Torneu-ho a provar."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funcions"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Vista prèvia"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "La vista prèvia i l'exportació utilitzen l'última versió vàlida."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Llegint…"
msgid "Ready to activate"
msgstr "Llest per activar"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Llest per activar amb avisos"
@@ -3663,10 +3669,6 @@ msgstr "Restableix"
msgid "Reset Password"
msgstr "Restableix la contrasenya"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Restableix al full d'estils aplicat"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Restableix la contrasenya"
@@ -4195,6 +4197,10 @@ msgstr "Alguns sistemes de sol·licitud en requereixen un abans de poder enviar-
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Alguns analitzadors analítics gestionen malament les imatges i, en algunes regions, no es recomanen les fotos."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Alguna cosa ha anat malament"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor de fulls d'estil"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "El full d'estil té errors"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Usuaris"
msgid "Uzbek"
msgstr "Uzbek"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Les URL vàlides han de començar per http:// o https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Allunya"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulú"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Žádosti odeslané týdně (posledních 8 týdnů)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Podáno"
@@ -534,10 +533,6 @@ msgstr "Podáno"
msgid "Applied on"
msgstr "Aplikováno na"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Použito s varováním"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabština"
@@ -1765,7 +1760,6 @@ msgstr "Zadejte své heslo pro potvrzení nastavení dvoufázového ověření.
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Zadejte své heslo pro deaktivaci dvoufázového ověření. Bez aktivního 2FA bude váš účet méně zabezpečený."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Chyba"
@@ -2003,6 +1997,10 @@ msgstr "Nepodařilo se ověřit váš záložní kód. Zkuste to prosím znovu."
msgid "Failed to verify your code. Please try again."
msgstr "Nepodařilo se ověřit váš kód. Zkuste to prosím znovu."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funkce"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Náhled"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Náhled a export použijí poslední platnou verzi."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Čtení…"
msgid "Ready to activate"
msgstr "Připraveno k aktivaci"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Připraveno k aktivaci s varováními"
@@ -3663,10 +3669,6 @@ msgstr "Resetovat"
msgid "Reset Password"
msgstr "Obnovit heslo"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Obnovit použitý stylový list"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Obnovte své heslo"
@@ -4195,6 +4197,10 @@ msgstr "Některé aplikační systémy vyžadují jeden před odesláním."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Některé analyzátory špatně zacházejí s obrázky a v některých oblastech se fotografie nedoporučuje."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Něco se pokazilo"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor stylů"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stylový list obsahuje chyby"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Uživatelé"
msgid "Uzbek"
msgstr "Uzbečtina"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Platné URL adresy musí začínat http:// nebo https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Brána Vercel AI"
@@ -5411,4 +5429,3 @@ msgstr "Oddálit"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Ansøgninger sendt pr. uge (sidste 8 uger)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Ansøgt"
@@ -534,10 +533,6 @@ msgstr "Ansøgt"
msgid "Applied on"
msgstr "Anvendt den"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Anvendt med advarsler"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabisk"
@@ -1765,7 +1760,6 @@ msgstr "Indtast din adgangskode for at bekræfte opsætning af to-faktor-godkend
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Indtast din adgangskode for at deaktivere to-faktor-godkendelse. Din konto vil være mindre sikker uden 2FA aktiveret."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Fejl"
@@ -2003,6 +1997,10 @@ msgstr "Det lykkedes ikke at bekræfte din backup-kode. Prøv venligst igen."
msgid "Failed to verify your code. Please try again."
msgstr "Det lykkedes ikke at bekræfte din kode. Prøv venligst igen."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funktioner"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Forhåndsvisning"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Forhåndsvisning og eksport med den seneste gyldige version."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Læsning…"
msgid "Ready to activate"
msgstr "Klar til aktivering"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Klar til aktivering med advarsler"
@@ -3663,10 +3669,6 @@ msgstr "Nulstil"
msgid "Reset Password"
msgstr "Nulstil adgangskode"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Nulstil til anvendt stylesheet"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Nulstil din adgangskode"
@@ -4195,6 +4197,10 @@ msgstr "Nogle ansøgningssystemer kræver et, før du kan indsende."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Nogle parsere mishandler billeder, og fotos frarådes i nogle regioner."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Noget gik galt"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stylesheet-editor"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stylesheet indeholder fejl"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Brugere"
msgid "Uzbek"
msgstr "Usbekisk"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Gyldige URL'er skal starte med http:// eller https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Zoom ud"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Pro Woche gesendete Bewerbungen (letzte 8 Wochen)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Beworben"
@@ -534,10 +533,6 @@ msgstr "Beworben"
msgid "Applied on"
msgstr "Beworben am"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Mit Warnungen angewendet"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabisch"
@@ -1765,7 +1760,6 @@ msgstr "Geben Sie Ihr Passwort ein, um die Einrichtung der Zwei-Faktor-Authentif
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Geben Sie Ihr Passwort ein, um die Zwei-Faktor-Authentifizierung zu deaktivieren. Ihr Konto ist ohne 2FA weniger sicher."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Fehler"
@@ -2003,6 +1997,10 @@ msgstr "Ihr Sicherungscode konnte nicht verifiziert werden. Bitte versuchen Sie
msgid "Failed to verify your code. Please try again."
msgstr "Ihr Code konnte nicht verifiziert werden. Bitte versuchen Sie es erneut."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funktionen"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Vorschau"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Vorschau und Export verwenden die letzte gültige Version."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Wird gelesen…"
msgid "Ready to activate"
msgstr "Bereit zur Aktivierung"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Bereit zur Aktivierung mit Warnungen"
@@ -3663,10 +3669,6 @@ msgstr "Zurücksetzen"
msgid "Reset Password"
msgstr "Passwort zurücksetzen"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Auf angewendetes Stylesheet zurücksetzen"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Setzen Sie Ihr Passwort zurück"
@@ -4195,6 +4197,10 @@ msgstr "Bei einigen Bewerbungssystemen ist ein Antrag erforderlich, bevor Sie ih
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Einige Parser verarbeiten Bilder falsch, und in einigen Regionen wird von Fotos abgeraten."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Etwas ist schiefgelaufen."
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stylesheet-Editor"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Das Stylesheet enthält Fehler."
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Nutzende"
msgid "Uzbek"
msgstr "Usbekisch"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Gültige URLs müssen mit http:// oder https:// beginnen."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Herauszoomen"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Αιτήσεις που στάλθηκαν ανά εβδομάδα (τελευταίες 8 εβδομάδες)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Υποβλήθηκε"
@@ -534,10 +533,6 @@ msgstr "Υποβλήθηκε"
msgid "Applied on"
msgstr "Ημερομηνία αίτησης"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Εφαρμόζεται με προειδοποιήσεις"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Αραβικά"
@@ -1765,7 +1760,6 @@ msgstr "Εισαγάγετε τον κωδικό πρόσβασής σας γι
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Εισαγάγετε τον κωδικό πρόσβασής σας για να απενεργοποιήσετε τον έλεγχο ταυτότητας δύο παραγόντων. Ο λογαριασμός σας θα είναι λιγότερο ασφαλής χωρίς ενεργοποιημένο 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Σφάλμα"
@@ -2003,6 +1997,10 @@ msgstr "Απέτυχε η επαλήθευση του κωδικού αντιγ
msgid "Failed to verify your code. Please try again."
msgstr "Απέτυχε η επαλήθευση του κωδικού σας. Προσπαθήστε ξανά."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Λειτουργίες"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Πρεμιέρα"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Η προεπισκόπηση και η εξαγωγή χρησιμοποιούν την τελευταία έγκυρη έκδοση."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Ανάγνωση…"
msgid "Ready to activate"
msgstr "Έτοιμο για ενεργοποίηση"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Έτοιμο για ενεργοποίηση με προειδοποιήσεις"
@@ -3663,10 +3669,6 @@ msgstr "Επαναφορά"
msgid "Reset Password"
msgstr "Επαναφορά κωδικού πρόσβασης"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Επαναφορά στο εφαρμοσμένο φύλλο στυλ"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Επαναφορά κωδικού πρόσβασης"
@@ -4195,6 +4197,10 @@ msgstr "Ορισμένα συστήματα αιτήσεων απαιτούν έ
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Ορισμένοι αναλυτές χειρίζονται εσφαλμένα τις εικόνες και οι φωτογραφίες αποθαρρύνονται σε ορισμένες περιοχές."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Κάτι πήγε στραβά"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Επεξεργαστής φύλλου στυλ"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Το φύλλο στυλ έχει σφάλματα"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Χρήστες"
msgid "Uzbek"
msgstr "Ουζμπεκικά"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Τα έγκυρα URLs πρέπει να ξεκινούν με http:// ή https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Πύλη Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Σμίκρυνση"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Ζουλού"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Applications sent per week (last 8 weeks)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Applied"
@@ -534,10 +533,6 @@ msgstr "Applied"
msgid "Applied on"
msgstr "Applied on"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Applied with warnings"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabic"
@@ -1765,7 +1760,6 @@ msgstr "Enter your password to confirm setting up two-factor authentication. Whe
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Error"
@@ -2003,6 +1997,10 @@ msgstr "Failed to verify your backup code. Please try again."
msgid "Failed to verify your code. Please try again."
msgstr "Failed to verify your code. Please try again."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Features"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Preview"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Preview and export use the last valid version."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Reading…"
msgid "Ready to activate"
msgstr "Ready to activate"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Ready to activate with warnings"
@@ -3663,10 +3669,6 @@ msgstr "Reset"
msgid "Reset Password"
msgstr "Reset Password"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Reset to applied stylesheet"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Reset your password"
@@ -4195,6 +4197,10 @@ msgstr "Some application systems require one before you can submit."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Some parsers mishandle images, and photos are discouraged in some regions."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Something went wrong"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stylesheet editor"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stylesheet has errors"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Users"
msgid "Uzbek"
msgstr "Uzbek"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Valid URLs must start with http:// or https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Zoom out"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -14
View File
@@ -521,7 +521,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Applications sent per week (last 8 weeks)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Applied"
@@ -529,10 +528,6 @@ msgstr "Applied"
msgid "Applied on"
msgstr "Applied on"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Applied with warnings"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabic"
@@ -1760,7 +1755,6 @@ msgstr "Enter your password to confirm setting up two-factor authentication. Whe
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Error"
@@ -1998,6 +1992,10 @@ msgstr "Failed to verify your backup code. Please try again."
msgid "Failed to verify your code. Please try again."
msgstr "Failed to verify your code. Please try again."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr "Fatal error"
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Features"
@@ -3415,8 +3413,12 @@ msgid "Preview"
msgstr "Preview"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Preview and export use the last valid version."
msgid "Preview and export fall back to base styles."
msgstr "Preview and export fall back to base styles."
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr "Preview and export keep valid styles and ignore invalid styles."
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3537,6 +3539,10 @@ msgstr "Reading…"
msgid "Ready to activate"
msgstr "Ready to activate"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr "Ready to activate with errors"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Ready to activate with warnings"
@@ -3658,10 +3664,6 @@ msgstr "Reset"
msgid "Reset Password"
msgstr "Reset Password"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Reset to applied stylesheet"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Reset your password"
@@ -4190,6 +4192,10 @@ msgstr "Some application systems require one before you can submit."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Some parsers mishandle images, and photos are discouraged in some regions."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr "Some styles were ignored"
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Something went wrong"
@@ -4333,8 +4339,8 @@ msgid "Stylesheet editor"
msgstr "Stylesheet editor"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stylesheet has errors"
msgid "Stylesheet has fatal errors"
msgstr "Stylesheet has fatal errors"
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5098,10 +5104,22 @@ msgstr "Users"
msgid "Uzbek"
msgstr "Uzbek"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr "Valid"
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Valid URLs must start with http:// or https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr "Valid with errors"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr "Valid with warnings"
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Candidaturas enviadas por semana (últimas 8 semanas)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Enviada"
@@ -534,10 +533,6 @@ msgstr "Enviada"
msgid "Applied on"
msgstr "Postulado el"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Aplicado con advertencias"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Árabe"
@@ -1765,7 +1760,6 @@ msgstr "Introduce tu contraseña para confirmar la configuración de la autentic
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Introduce tu contraseña para deshabilitar la autenticación de doble factor. Tu cuenta será menos segura sin 2FA habilitada."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Error"
@@ -2003,6 +1997,10 @@ msgstr "No se ha podido verificar su código de copia de seguridad. Por favor, i
msgid "Failed to verify your code. Please try again."
msgstr "No se ha podido verificar su código. Por favor, inténtelo de nuevo."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funciones"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Avance"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "La vista previa y la exportación utilizan la última versión válida."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Leyendo…"
msgid "Ready to activate"
msgstr "Listo para activar"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Listo para activarse con advertencias"
@@ -3663,10 +3669,6 @@ msgstr "Restablecer"
msgid "Reset Password"
msgstr "Restablecer contraseña"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Restablecer a la hoja de estilos aplicada"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Restablecer tu contraseña"
@@ -4195,6 +4197,10 @@ msgstr "Algunos sistemas de solicitud requieren uno antes de poder enviarlo."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Algunos analizadores manejan mal las imágenes y en algunas regiones no se recomiendan las fotografías."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Algo salió mal"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor de hojas de estilo"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "La hoja de estilos tiene errores."
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Usuarios"
msgid "Uzbek"
msgstr "Uzbeko"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Las URL válidas deben comenzar con http:// o https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Pasarela Vercel AI"
@@ -5411,4 +5429,3 @@ msgstr "Alejar"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulú"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "درخواست‌های ارسال‌شده در هفته (۸ هفته گذشته)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "ارسال‌شده"
@@ -534,10 +533,6 @@ msgstr "ارسال‌شده"
msgid "Applied on"
msgstr "تاریخ درخواست"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "با هشدارها اعمال می‌شود"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "عربی"
@@ -1765,7 +1760,6 @@ msgstr "برای تأیید راه‌اندازی احراز هویت دو مر
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "برای غیرفعال کردن احراز هویت دو مرحله‌ای گذرواژهٔ خود را وارد کنید. بدون فعال بودن 2FA حساب شما امنیت کمتری خواهد داشت."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "خطا"
@@ -2003,6 +1997,10 @@ msgstr "کد پشتیبان شما تأیید نشد. لطفاً دوباره ت
msgid "Failed to verify your code. Please try again."
msgstr "تأیید کد شما ناموفق بود. لطفاً دوباره تلاش کنید."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "امکانات"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "پیش‌نمایش"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "پیش‌نمایش و خروجی گرفتن از آخرین نسخه معتبر."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "در حال خواندن…"
msgid "Ready to activate"
msgstr "آماده فعال سازی"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "آماده فعال شدن با هشدارها"
@@ -3663,10 +3669,6 @@ msgstr "ریست"
msgid "Reset Password"
msgstr "تنظیم مجدد گذرواژه"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "تنظیم مجدد به شیوه‌نامه اعمال شده"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "تنظیم مجدد گذرواژهٔ شما"
@@ -4195,6 +4197,10 @@ msgstr "برخی سامانه‌های درخواست شغلی پیش از ار
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "برخی تجزیه‌گرها تصاویر را نادرست پردازش می‌کنند و در بعضی مناطق استفاده از عکس توصیه نمی‌شود."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "چیزی اشتباه پیش رفت"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "ویرایشگر استایل‌شیت"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "استایل‌شیت خطا دارد"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "کاربران"
msgid "Uzbek"
msgstr "ازبکی"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URLهای معتبر باید با http:// یا https:// شروع شوند."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "دروازه ورچل ای‌آی"
@@ -5411,4 +5429,3 @@ msgstr "کوچک‌نمایی"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "زولو"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Lähetetyt hakemukset viikoittain (viimeiset 8 viikkoa)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Haettu"
@@ -534,10 +533,6 @@ msgstr "Haettu"
msgid "Applied on"
msgstr "Haettu"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Käytetty varoituksilla"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "arabia"
@@ -1765,7 +1760,6 @@ msgstr "Syötä salasanasi vahvistaaksesi kaksivaiheisen todennuksen käyttöön
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Syötä salasanasi poistaaksesi kaksivaiheisen todennuksen käytöstä. Tilisi on vähemmän suojattu ilman 2FA:ta."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Virhe"
@@ -2003,6 +1997,10 @@ msgstr "Varmuuskopiointikoodin vahvistaminen epäonnistui. Yritä uudelleen."
msgid "Failed to verify your code. Please try again."
msgstr "Koodiasi ei onnistuttu tarkistamaan. Yritä uudelleen."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Ominaisuudet"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Esikatselu"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Esikatsele ja vie käyttämällä viimeisintä kelvollista versiota."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Luetaan…"
msgid "Ready to activate"
msgstr "Valmis aktivoitavaksi"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Valmis aktivoitavaksi varoituksilla"
@@ -3663,10 +3669,6 @@ msgstr "Palauta"
msgid "Reset Password"
msgstr "Palauta salasana"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Palauta käytettyyn tyylitiedostoon"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Palauta salasanasi"
@@ -4195,6 +4197,10 @@ msgstr "Jotkin hakujärjestelmät vaativat sellaisen, ennen kuin voit lähettä
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Jotkut jäsentimet käsittelevät kuvia väärin, eikä valokuvia suositella joillain alueilla."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Jokin meni pieleen"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Tyylitiedostoeditori"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Tyylitiedostossa on virheitä"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Käyttäjät"
msgid "Uzbek"
msgstr "uzbekki"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Kelvollisten URL-osoitteiden on alettava http:// tai https:// ."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Loitonna"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Candidatures envoyées par semaine (8 dernières semaines)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Candidature envoyée"
@@ -534,10 +533,6 @@ msgstr "Candidature envoyée"
msgid "Applied on"
msgstr "Postulé le"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Appliqué avec avertissements"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabe"
@@ -1765,7 +1760,6 @@ msgstr "Saisissez votre mot de passe pour confirmer la configuration de l'authen
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Saisissez votre mot de passe pour désactiver l'authentification à deux facteurs. Votre compte sera moins sécurisé si l'authentification à deux facteurs n'est pas activée."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Erreur"
@@ -2003,6 +1997,10 @@ msgstr "La vérification de votre code de sauvegarde a échoué. Veuillez réess
msgid "Failed to verify your code. Please try again."
msgstr "La vérification de votre code a échoué. Veuillez réessayer."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Fonctionnalités"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Aperçu"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "L'aperçu et l'exportation utilisent la dernière version valide."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Lecture…"
msgid "Ready to activate"
msgstr "Prêt à activer"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Prêt à être activé avec avertissements"
@@ -3663,10 +3669,6 @@ msgstr "Réinitialiser"
msgid "Reset Password"
msgstr "Réinitialiser le mot de passe"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Réinitialiser la feuille de style appliquée"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Réinitialisez votre mot de passe"
@@ -4195,6 +4197,10 @@ msgstr "Certains systèmes de candidature en exigent un avant que vous puissiez
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Certains analyseurs traitent mal les images et les photos sont déconseillées dans certaines régions."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Quelque chose s'est mal passé"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Éditeur de feuilles de style"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "La feuille de style contient des erreurs"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Utilisateurs"
msgid "Uzbek"
msgstr "Ouzbékistanais"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Les URL valides doivent commencer par http:// ou https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Passerelle Vercel AI"
@@ -5411,4 +5429,3 @@ msgstr "Zoom arrière"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zoulou"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "מועמדויות שנשלחו בשבוע (8 השבועות האחרונים)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "הוגש"
@@ -534,10 +533,6 @@ msgstr "הוגש"
msgid "Applied on"
msgstr "הוגש בתאריך"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "יושם עם אזהרות"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "ערבית"
@@ -1765,7 +1760,6 @@ msgstr "הזן את הסיסמה שלך כדי לאשר הגדרת אימות ד
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "הזן את הסיסמה שלך כדי להשבית אימות דו־שלבי. החשבון שלך יהיה פחות מאובטח ללא אימות דו־שלבי."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "שְׁגִיאָה"
@@ -2003,6 +1997,10 @@ msgstr "לא הצלחנו לאמת את קוד הגיבוי שלך. אנא נס
msgid "Failed to verify your code. Please try again."
msgstr "לא הצלחנו לאמת את הקוד שלך. אנא נסה שוב."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "יכולות"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "תצוגה מקדימה"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "תצוגה מקדימה וייצוא משתמשים בגרסה התקינה האחרונה."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "קורא…"
msgid "Ready to activate"
msgstr "מוכן להפעלה"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "מוכן להפעלה עם אזהרות"
@@ -3663,10 +3669,6 @@ msgstr "איפוס"
msgid "Reset Password"
msgstr "איפוס סיסמה"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "איפוס לגיליון הסגנונות שהוחל"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "איפוס הסיסמה שלך"
@@ -4195,6 +4197,10 @@ msgstr "מערכות מועמדות מסוימות דורשות זאת לפני
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "מנתחים מסוימים מטפלים בתמונות באופן שגוי, ובאזורים מסוימים לא מומלץ להשתמש בתמונות."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "משהו השתבש"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "עורך גיליון סגנונות"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "גיליון הסגנונות מכיל שגיאות"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "משתמשים"
msgid "Uzbek"
msgstr "אוזבקית"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "כתובות URL חוקיות חייבות להתחיל ב־http:// או https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "התרחקות"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "זולו"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "प्रति सप्ताह भेजे गए आवेदन (पिछले 8 सप्ताह)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "आवेदन किया"
@@ -534,10 +533,6 @@ msgstr "आवेदन किया"
msgid "Applied on"
msgstr "आवेदन की तारीख"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "चेतावनी सहित लागू"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "अरबी"
@@ -1765,7 +1760,6 @@ msgstr "दो‑कारक प्रमाणीकरण सेटअप क
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "दो‑कारक प्रमाणीकरण अक्षम करने के लिए अपना पासवर्ड दर्ज करें। 2FA सक्षम न होने पर आपका खाता कम सुरक्षित रहेगा।"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "गलती"
@@ -2003,6 +1997,10 @@ msgstr "आपके बैकअप कोड को सत्यापित
msgid "Failed to verify your code. Please try again."
msgstr "आपके कोड का सत्यापन विफल हो गया। कृपया फिर से प्रयास करें।"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "विशेषताएँ"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "पूर्व दर्शन"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "पूर्वावलोकन और निर्यात के लिए अंतिम मान्य संस्करण का उपयोग करें।"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "पढ़ा जा रहा है…"
msgid "Ready to activate"
msgstr "सक्रिय करने के लिए तैयार"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "चेतावनी सहित सक्रिय करने के लिए तैयार"
@@ -3663,10 +3669,6 @@ msgstr "रीसेट"
msgid "Reset Password"
msgstr "पासवर्ड रीसेट करें"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "लागू स्टाइलशीट पर रीसेट करें"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "अपना पासवर्ड रीसेट करें"
@@ -4195,6 +4197,10 @@ msgstr "आपके सबमिट करने से पहले कुछ
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "कुछ पार्सर्स छवियों को गलत तरीके से संभालते हैं, और कुछ क्षेत्रों में फ़ोटो को हतोत्साहित किया जाता है।"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "कुछ गलत हो गया"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "स्टाइलशीट संपादक"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "स्टाइलशीट में त्रुटियाँ हैं"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "उपयोगकर्ता"
msgid "Uzbek"
msgstr "उज़्बेक"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "मान्य URL http:// या https:// से शुरू होना चाहिए।"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "वर्सेल एआई गेटवे"
@@ -5411,4 +5429,3 @@ msgstr "ज़ूम आउट"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "ज़ुलु"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Hetente elküldött jelentkezések (az elmúlt 8 hét)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Jelentkezve"
@@ -534,10 +533,6 @@ msgstr "Jelentkezve"
msgid "Applied on"
msgstr "Jelentkezés dátuma"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Figyelmeztetésekkel alkalmazva"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "arab"
@@ -1765,7 +1760,6 @@ msgstr "Add meg a jelszavadat a kétlépcsős hitelesítés beállításának me
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Add meg a jelszavadat a kétlépcsős hitelesítés kikapcsolásához. A fiókod kevésbé lesz biztonságos 2FA nélkül."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Hiba"
@@ -2003,6 +1997,10 @@ msgstr "Nem sikerült ellenőrizni a biztonsági mentés kódját. Kérjük, pr
msgid "Failed to verify your code. Please try again."
msgstr "Nem sikerült ellenőrizni a kódot. Kérjük, próbálja meg újra."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funkciók"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Előnézet"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Az előnézet és az exportálás a legutóbbi érvényes verziót használja."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Olvasás…"
msgid "Ready to activate"
msgstr "Aktiválásra kész"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Aktiválásra kész, figyelmeztetésekkel"
@@ -3663,10 +3669,6 @@ msgstr "Visszaállítás"
msgid "Reset Password"
msgstr "Jelszó visszaállítása"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Visszaállítás az alkalmazott stíluslapra"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Jelszó visszaállítása"
@@ -4195,6 +4197,10 @@ msgstr "Egyes jelentkezési rendszereknek szüksége van egy ilyenre a benyújt
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Egyes elemzők rosszul kezelik a képeket, és egyes régiókban nem javasoljuk a fotók használatát."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Valami rosszul sült el"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stíluslap-szerkesztő"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "A stíluslap hibákat tartalmaz"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Felhasználók"
msgid "Uzbek"
msgstr "üzbég"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Az érvényes URL http:// vagy https:// előtaggal kezdődik."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Kicsinyítés"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Lamaran dikirim per minggu (8 minggu terakhir)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Dilamar"
@@ -534,10 +533,6 @@ msgstr "Dilamar"
msgid "Applied on"
msgstr "Dilamar pada"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Diterapkan dengan peringatan"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arab"
@@ -1765,7 +1760,6 @@ msgstr "Masukkan kata sandi Anda untuk mengonfirmasi penyiapan autentikasi dua f
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Masukkan kata sandi Anda untuk menonaktifkan autentikasi dua faktor. Akun Anda akan menjadi kurang aman tanpa 2FA diaktifkan."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Kesalahan"
@@ -2003,6 +1997,10 @@ msgstr "Gagal memverifikasi kode cadangan Anda. Silakan coba lagi."
msgid "Failed to verify your code. Please try again."
msgstr "Gagal memverifikasi kode Anda. Silakan coba lagi."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Fitur"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Pratinjau"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Pratinjau dan ekspor menggunakan versi valid terakhir."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Membaca…"
msgid "Ready to activate"
msgstr "Siap diaktifkan"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Siap diaktifkan dengan peringatan"
@@ -3663,10 +3669,6 @@ msgstr "Atur Ulang"
msgid "Reset Password"
msgstr "Atur Ulang Kata Sandi"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Atur ulang ke stylesheet yang diterapkan"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Atur ulang kata sandi Anda"
@@ -4195,6 +4197,10 @@ msgstr "Beberapa sistem lamaran memerlukannya sebelum Anda dapat mengirimkannya.
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Beberapa parser salah menangani gambar, dan foto tidak disarankan di beberapa wilayah."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Terjadi kesalahan."
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor lembar gaya"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stylesheet memiliki kesalahan"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Pengguna"
msgid "Uzbek"
msgstr "Uzbek"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URL yang valid harus dimulai dengan http:// atau https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Perkecil"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Candidature inviate a settimana (ultime 8 settimane)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Candidatura inviata"
@@ -534,10 +533,6 @@ msgstr "Candidatura inviata"
msgid "Applied on"
msgstr "Candidatura inviata il"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Applicato con avvertenze"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabo"
@@ -1765,7 +1760,6 @@ msgstr "Inserisci la tua password per confermare la configurazione dell'autentic
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Inserisci la tua password per disabilitare l'autenticazione a due fattori. Il tuo account sarà meno sicuro senza 2FA attiva."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Errore"
@@ -2003,6 +1997,10 @@ msgstr "Impossibile verificare il suo codice di backup. Provi di nuovo."
msgid "Failed to verify your code. Please try again."
msgstr "Non è stato possibile verificare il suo codice. La preghiamo di riprovare."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funzionalità"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Anteprima"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "L'anteprima e l'esportazione utilizzano l'ultima versione valida."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Lettura…"
msgid "Ready to activate"
msgstr "Pronto per l'attivazione"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Pronto per l'attivazione con avvisi"
@@ -3663,10 +3669,6 @@ msgstr "Ripristina"
msgid "Reset Password"
msgstr "Reimposta password"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Ripristina il foglio di stile applicato"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Reimposta la tua password"
@@ -4195,6 +4197,10 @@ msgstr "Alcuni sistemi di candidatura ne richiedono uno prima di poter inviare."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Alcuni parser gestiscono male le immagini e le foto sono sconsigliate in alcune regioni."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Qualcosa è andato storto"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor di fogli di stile"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Il foglio di stile presenta degli errori"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Utenti"
msgid "Uzbek"
msgstr "Uzbeco"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Gli URL validi devono iniziare con http:// o https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Gateway Vercel AI"
@@ -5411,4 +5429,3 @@ msgstr "Rimpicciolisci"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "週ごとの応募送信数(直近8週間)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "応募済み"
@@ -534,10 +533,6 @@ msgstr "応募済み"
msgid "Applied on"
msgstr "応募日"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "警告付きで適用"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "アラビア語"
@@ -1765,7 +1760,6 @@ msgstr "二要素認証を設定することを確定するために、パスワ
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "二要素認証を無効にするには、パスワードを入力してください。2FA を無効化すると、アカウントのセキュリティレベルは下がります。"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "エラー"
@@ -2003,6 +1997,10 @@ msgstr "バックアップコードの検証に失敗しました。もう一度
msgid "Failed to verify your code. Please try again."
msgstr "コードの検証に失敗しました。もう一度やり直してください。"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "機能"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "プレビュー"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "プレビューとエクスポートには、最新の有効なバージョンを使用してください。"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "読み込み中…"
msgid "Ready to activate"
msgstr "起動準備完了"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "警告付きで有効化準備完了"
@@ -3663,10 +3669,6 @@ msgstr "リセット"
msgid "Reset Password"
msgstr "パスワードをリセット"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "適用済みのスタイルシートにリセットします"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "パスワードをリセット"
@@ -4195,6 +4197,10 @@ msgstr "一部のアプリケーション システムでは、送信する前
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "一部のパーサーは画像を誤って処理するため、一部の地域では写真の使用が推奨されません。"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "何らかの問題が発生しました"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "スタイルシートエディタ"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "スタイルシートにエラーがあります"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "ユーザー数"
msgid "Uzbek"
msgstr "ウズベク語"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "有効な URL は http:// または https:// で始まる必要があります。"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "ズームアウト"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "ズールー語"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "ពាក្យសុំបានផ្ញើក្នុងមួយសប្តាហ៍ (៨ សប្តាហ៍ចុងក្រោយ)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "បានដាក់ពាក្យ"
@@ -534,10 +533,6 @@ msgstr "បានដាក់ពាក្យ"
msgid "Applied on"
msgstr "បានដាក់ពាក្យនៅ"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "អនុវត្តជាមួយការព្រមាន"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabic"
@@ -1765,7 +1760,6 @@ msgstr "បញ្ចូល​ពាក្យសម្ងាត់​របស់
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "បញ្ចូល​ពាក្យសម្ងាត់​របស់​អ្នក ដើម្បី​បិទ​ការ​ផ្ទៀងផ្ទាត់​ពីរ​ជាន់។ គណនី​របស់​អ្នក​នឹង​កាន់តែ​មាន​ហានិភ័យ​នៅពេល​មិន​បាន​បើក 2FA ទៀត។"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "កំហុស"
@@ -2003,6 +1997,10 @@ msgstr "បរាជ័យក្នុងការផ្ទៀងផ្ទាត
msgid "Failed to verify your code. Please try again."
msgstr "បរាជ័យក្នុងការផ្ទៀងផ្ទាត់កូដរបស់អ្នក។ សូមព្យាយាមម្ដងទៀត។"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "មុខងារ"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "មើលជាមុន"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "ការមើលជាមុន និងការនាំចេញប្រើប្រាស់កំណែដែលមានសុពលភាពចុងក្រោយ។"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "កំពុងអាន…"
msgid "Ready to activate"
msgstr "រួចរាល់ដើម្បីធ្វើឱ្យសកម្ម"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "រួចរាល់ដើម្បីធ្វើឲ្យសកម្មជាមួយនឹងការព្រមាន"
@@ -3663,10 +3669,6 @@ msgstr "កំណត់​ឡើងវិញ"
msgid "Reset Password"
msgstr "កំណត់​ពាក្យសម្ងាត់​ឡើងវិញ"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "កំណត់ឡើងវិញទៅសន្លឹករចនាប័ទ្មដែលបានអនុវត្ត"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "កំណត់​ពាក្យសម្ងាត់​របស់​អ្នក​ឡើងវិញ"
@@ -4195,6 +4197,10 @@ msgstr "ប្រព័ន្ធ​កម្មវិធី​មួយ​ចំ
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "ឧបករណ៍ញែកមួយចំនួនធ្វើឱ្យរូបភាពមិនត្រឹមត្រូវ ហើយរូបថតត្រូវបានលើកទឹកចិត្តនៅក្នុងតំបន់មួយចំនួន។"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "មានអ្វីមួយខុសប្រក្រតី"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "កម្មវិធីនិពន្ធសន្លឹករចនាប័ទ្ម"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "សន្លឹករចនាប័ទ្មមានកំហុស"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "អ្នកប្រើ"
msgid "Uzbek"
msgstr "Uzbek"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URL ត្រឹមត្រូវ ត្រូវតែ​ចាប់ផ្តើម​ដោយ http:// ឬ https://។"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "បង្រួម"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "ಪ್ರತಿ ವಾರ ಕಳುಹಿಸಿದ ಅರ್ಜಿಗಳು (ಕೊನೆಯ 8 ವಾರಗಳು)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "ಅರ್ಜಿಸಲಾಗಿದೆ"
@@ -534,10 +533,6 @@ msgstr "ಅರ್ಜಿಸಲಾಗಿದೆ"
msgid "Applied on"
msgstr "ಅರ್ಜಿ ಸಲ್ಲಿಸಿದ ದಿನಾಂಕ"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "ಎಚ್ಚರಿಕೆಗಳೊಂದಿಗೆ ಅನ್ವಯಿಸಲಾಗಿದೆ"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "ಅರೇಬಿಕ್"
@@ -1765,7 +1760,6 @@ msgstr "ಎರಡು ಅಂಶಗಳ ದೃಢೀಕರಣವನ್ನು ಹೊ
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "ಎರಡು ಅಂಶಗಳ ದೃಢೀಕರಣವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ನಮೂದಿಸಿ. 2FA ಸಕ್ರಿಯಗೊಳಿಸದಿದ್ದರೆ ನಿಮ್ಮ ಖಾತೆ ಕಡಿಮೆ ಸುರಕ್ಷಿತವಾಗಿರುತ್ತದೆ."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "ದೋಷ"
@@ -2003,6 +1997,10 @@ msgstr "ನಿಮ್ಮ ಬ್ಯಾಕಪ್ ಕೋಡ್ ಅನ್ನು ಪ
msgid "Failed to verify your code. Please try again."
msgstr "ನಿಮ್ಮ ಕೋಡ್ ಅನ್ನು ಪರಿಶೀಲಿಸಲು ವಿಫಲವಾಗಿದೆ. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "ವೈಶಿಷ್ಟ್ಯಗಳು"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "ಪೂರ್ವವೀಕ್ಷಣೆ"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "ಕೊನೆಯ ಮಾನ್ಯ ಆವೃತ್ತಿಯನ್ನು ಬಳಸಿಕೊಂಡು ಪೂರ್ವವೀಕ್ಷಣೆ ಮತ್ತು ರಫ್ತು ಮಾಡಿ."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "ಓದುತ್ತಿದೆ…"
msgid "Ready to activate"
msgstr "ಸಕ್ರಿಯಗೊಳಿಸಲು ಸಿದ್ಧವಾಗಿದೆ"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "ಎಚ್ಚರಿಕೆಗಳೊಂದಿಗೆ ಸಕ್ರಿಯಗೊಳಿಸಲು ಸಿದ್ಧವಾಗಿದೆ"
@@ -3663,10 +3669,6 @@ msgstr "ಮರುಹೊಂದಿಸಿ"
msgid "Reset Password"
msgstr "ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಮರುಹೊಂದಿಸಿ"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "ಅನ್ವಯಿಸಲಾದ ಸ್ಟೈಲ್‌ಶೀಟ್‌ಗೆ ಮರುಹೊಂದಿಸಿ"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಮರುಹೊಂದಿಸಿ"
@@ -4195,6 +4197,10 @@ msgstr "ನೀವು ಸಲ್ಲಿಸುವ ಮೊದಲು ಕೆಲವು
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "ಕೆಲವು ಪಾರ್ಸರ್‌ಗಳು ಚಿತ್ರಗಳನ್ನು ತಪ್ಪಾಗಿ ನಿರ್ವಹಿಸುತ್ತವೆ ಮತ್ತು ಕೆಲವು ಪ್ರದೇಶಗಳಲ್ಲಿ ಫೋಟೋಗಳನ್ನು ನಿರುತ್ಸಾಹಗೊಳಿಸಲಾಗುತ್ತದೆ."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "ಏನೋ ತಪ್ಪಾಗಿದೆ."
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "ಸ್ಟೈಲ್‌ಶೀಟ್ ಸಂಪಾದಕ"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "ಸ್ಟೈಲ್‌ಶೀಟ್ ದೋಷಗಳನ್ನು ಹೊಂದಿದೆ."
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "ಬಳಕೆದಾರರು"
msgid "Uzbek"
msgstr "ಉಜ್ಬೇಕ್"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "ಮಾನ್ಯ URL ಗಳು http:// ಅಥವಾ https:// ನಿಂದ ಪ್ರಾರಂಭವಾಗಿರಬೇಕು."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "ಗಾತ್ರ ಕುಗ್ಗಿಸಿ"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "ಜೂಲೂ"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "주당 보낸 지원서 (최근 8주)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "지원함"
@@ -534,10 +533,6 @@ msgstr "지원함"
msgid "Applied on"
msgstr "지원일"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "경고와 함께 적용됨"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "아랍어"
@@ -1765,7 +1760,6 @@ msgstr "2단계 인증을 설정하려면 비밀번호를 입력해 확인하세
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "2단계 인증을 비활성화하려면 비밀번호를 입력하세요. 2단계 인증을 사용하지 않으면 계정 보안이 낮아집니다."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "오류"
@@ -2003,6 +1997,10 @@ msgstr "백업 코드를 인증하지 못했습니다. 다시 시도해 주세
msgid "Failed to verify your code. Please try again."
msgstr "코드를 인증하지 못했습니다. 다시 시도해 주세요."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "기능"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "시사"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "미리보기 및 내보내기 시에는 마지막으로 유효한 버전을 사용합니다."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "읽는 중…"
msgid "Ready to activate"
msgstr "활성화 준비 완료"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "경고 메시지와 함께 활성화할 준비가 되었습니다."
@@ -3663,10 +3669,6 @@ msgstr "초기화"
msgid "Reset Password"
msgstr "비밀번호 재설정"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "적용된 스타일시트로 재설정"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "비밀번호 재설정"
@@ -4195,6 +4197,10 @@ msgstr "일부 지원 시스템에서는 제출하기 전에 하나의 정보가
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "일부 파서는 이미지를 잘못 처리하므로 일부 지역에서는 사진을 사용하지 않는 것이 좋습니다."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "뭔가 잘못됐어요"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "스타일시트 편집기"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "스타일시트에 오류가 있습니다"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "사용자 수"
msgid "Uzbek"
msgstr "우즈베크어"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "유효한 URL은 http:// 또는 https://로 시작해야 합니다."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "버셀 AI 게이트웨이"
@@ -5411,4 +5429,3 @@ msgstr "축소"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "줄루어"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Per savaitę išsiųstos paraiškos (paskutinės 8 savaitės)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Pateikta"
@@ -534,10 +533,6 @@ msgstr "Pateikta"
msgid "Applied on"
msgstr "Pateikta"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Taikoma su įspėjimais"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabų"
@@ -1765,7 +1760,6 @@ msgstr "Įveskite slaptažodį, kad patvirtintumėte dviejų veiksnių tapatumo
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Įveskite slaptažodį, kad išjungtumėte dviejų veiksnių tapatumo patvirtinimą. Be įjungto 2FA jūsų paskyra bus mažiau saugi."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Klaida"
@@ -2003,6 +1997,10 @@ msgstr "Nepavyko patikrinti atsarginės kopijos kodo. Bandykite dar kartą."
msgid "Failed to verify your code. Please try again."
msgstr "Nepavyko patikrinti jūsų kodo. Bandykite dar kartą."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funkcijos"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Peržiūra"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Peržiūrai ir eksportui naudokite paskutinę galiojančią versiją."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Skaitoma…"
msgid "Ready to activate"
msgstr "Paruošta aktyvuoti"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Paruošta aktyvuoti su įspėjimais"
@@ -3663,10 +3669,6 @@ msgstr "Nustatyti iš naujo"
msgid "Reset Password"
msgstr "Atkurti slaptažodį"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Atkurti pritaikytą stiliaus lapą"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Atkurkite slaptažodį"
@@ -4195,6 +4197,10 @@ msgstr "Kai kurioms paraiškų teikimo sistemoms reikia, kad galėtumėte pateik
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Kai kurie analizatoriai netinkamai tvarko vaizdus, o kai kuriuose regionuose nuotraukos nerekomenduojamos."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Kažkas nutiko ne taip"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stilių lapų redaktorius"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stiliaus lape yra klaidų"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Vartotojai"
msgid "Uzbek"
msgstr "Uzbekų"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Teisingi URL turi prasidėti http:// arba https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "\"Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Tolinti"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulų"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Nedēļā nosūtītie pieteikumi (pēdējās 8 nedēļas)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Pieteikts"
@@ -534,10 +533,6 @@ msgstr "Pieteikts"
msgid "Applied on"
msgstr "Pieteikts"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Pielietots ar brīdinājumiem"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arābu"
@@ -1765,7 +1760,6 @@ msgstr "Ievadiet savu paroli, lai apstiprinātu divu faktoru autentifikācijas i
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Ievadiet savu paroli, lai deaktivētu divu faktoru autentifikāciju. Bez ieslēgtas 2FA jūsu konts būs mazāk drošs."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Kļūda"
@@ -2003,6 +1997,10 @@ msgstr "Neizdevās pārbaudīt jūsu dublējuma kodu. Lūdzu, mēģiniet vēlrei
msgid "Failed to verify your code. Please try again."
msgstr "Jūsu kodu neizdevās pārbaudīt. Lūdzu, mēģiniet vēlreiz."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funkcijas"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Priekšskatījums"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Priekšskatījumā un eksportā tiek izmantota pēdējā derīgā versija."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Lasa…"
msgid "Ready to activate"
msgstr "Gatavs aktivizēšanai"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Gatavs aktivizēšanai ar brīdinājumiem"
@@ -3663,10 +3669,6 @@ msgstr "Atiestatīt"
msgid "Reset Password"
msgstr "Atiestatīt paroli"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Atiestatīt uz lietoto stila lapu"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Atiestatīt paroli"
@@ -4195,6 +4197,10 @@ msgstr "Dažām pieteikumu sistēmām tas ir nepieciešams, lai to varētu iesni
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Daži parsētāji nepareizi apstrādā attēlus, un dažos reģionos fotoattēli nav ieteicami."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Kaut kas nogāja greizi"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stilu lapu redaktors"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stilu lapā ir kļūdas"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Lietotāji"
msgid "Uzbek"
msgstr "Uzbeku"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Derīgiem URL jāsākas ar http:// vai https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI vārtejas"
@@ -5411,4 +5429,3 @@ msgstr "Tālināt"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "ആഴ്ചയിൽ അയച്ച അപേക്ഷകൾ (കഴിഞ്ഞ 8 ആഴ്ച)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "അപേക്ഷിച്ചു"
@@ -534,10 +533,6 @@ msgstr "അപേക്ഷിച്ചു"
msgid "Applied on"
msgstr "പ്രയോഗിച്ചു"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "മുന്നറിയിപ്പുകൾ ഉപയോഗിച്ച് പ്രയോഗിച്ചു"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "അറബിക്"
@@ -1765,7 +1760,6 @@ msgstr "ടു‑ഫാക്ടർ ഓത്‌ന്റിക്കേഷൻ
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "ടു‑ഫാക്ടർ ഓത്‌ന്റിക്കേഷൻ അപ്രാപ്തമാക്കാൻ നിങ്ങളുടെ പാസ്‌വേഡ് നൽകുക. 2FA പ്രാപ്തമാക്കിയിട്ടില്ലാത്തതിനാൽ നിങ്ങളുടെ അക്കൗണ്ട് കുറച്ച് കുറവായി സുരക്ഷിതമായിവരും."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "പിശക്"
@@ -2003,6 +1997,10 @@ msgstr "നിങ്ങളുടെ ബാക്കപ്പ് കോഡ് പ
msgid "Failed to verify your code. Please try again."
msgstr "നിങ്ങളുടെ കോഡ് പരിശോധിക്കുന്നതിൽ പരാജയപ്പെട്ടു. ദയവായി വീണ്ടും ശ്രമിക്കുക."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "സവിശേഷതകൾ"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "പ്രിവ്യൂ"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "അവസാനത്തെ സാധുവായ പതിപ്പ് ഉപയോഗിച്ച് പ്രിവ്യൂ ചെയ്ത് എക്സ്പോർട്ട് ചെയ്യുക."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "വായിക്കുന്നു..."
msgid "Ready to activate"
msgstr "സജീവമാക്കാൻ തയ്യാറാണ്"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "മുന്നറിയിപ്പുകൾ നൽകി സജീവമാക്കാൻ തയ്യാറാണ്"
@@ -3663,10 +3669,6 @@ msgstr "റീസെറ്റ് ചെയ്യുക"
msgid "Reset Password"
msgstr "പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യുക"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "പ്രയോഗിച്ച സ്റ്റൈൽഷീറ്റിലേക്ക് പുനഃസജ്ജമാക്കുക"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "നിങ്ങളുടെ പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യുക"
@@ -4195,6 +4197,10 @@ msgstr "നിങ്ങൾ സമർപ്പിക്കുന്നതിന
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "ചില പാഴ്‌സർമാർ ചിത്രങ്ങൾ തെറ്റായി കൈകാര്യം ചെയ്യുന്നു, ചില പ്രദേശങ്ങളിൽ ഫോട്ടോകൾ നിരുത്സാഹപ്പെടുത്തുന്നു."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "എന്തോ കുഴപ്പം സംഭവിച്ചു."
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "സ്റ്റൈൽഷീറ്റ് എഡിറ്റർ"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "സ്റ്റൈൽഷീറ്റിൽ പിശകുകൾ ഉണ്ട്"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "യൂസർമാർ"
msgid "Uzbek"
msgstr "ഉസ്‌ബെക്ക്"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "സാധുവായ URL‑കൾ http:// അല്ലെങ്കിൽ https:// കൊണ്ട് ആരംഭിക്കണം."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "വെർസെൽ എഐ ഗേറ്റ്‌വേ"
@@ -5411,4 +5429,3 @@ msgstr "സൂം ഔട്ട് ചെയ്യുക"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "സൂളു"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "दर आठवड्याला पाठवलेले अर्ज (मागील 8 आठवडे)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "अर्ज केला"
@@ -534,10 +533,6 @@ msgstr "अर्ज केला"
msgid "Applied on"
msgstr "वर अर्ज केला"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "इशाऱ्यांसह लागू केले"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "अरबी"
@@ -1765,7 +1760,6 @@ msgstr "दुहेरी घटक ओथेंटिकेशन सेटअ
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "दुहेरी घटक ओथेंटिकेशन अक्षम करण्यासाठी तुमचा पासवर्ड टाका. 2FA शिवाय तुमचे खाते कमी सुरक्षित राहील."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "त्रुटी"
@@ -2003,6 +1997,10 @@ msgstr "आपला बॅकअप कोड पडताळणी करण
msgid "Failed to verify your code. Please try again."
msgstr "तुमचा कोड पडताळणी करण्यात अयशस्वी झाला. कृपया पुन्हा प्रयत्न करा."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "वैशिष्ट्ये"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "पूर्वावलोकन"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "पूर्वावलोकन आणि निर्यातीसाठी शेवटची वैध आवृत्ती वापरली जाते."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "वाचत आहे…"
msgid "Ready to activate"
msgstr "सक्रिय करण्यासाठी तयार"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "इशाऱ्यांसह सक्रिय करण्यास तयार"
@@ -3663,10 +3669,6 @@ msgstr "रीसेट करा"
msgid "Reset Password"
msgstr "पासवर्ड रीसेट करा"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "लागू केलेल्या स्टाईलशीटवर रीसेट करा"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "तुमचा पासवर्ड रीसेट करा"
@@ -4195,6 +4197,10 @@ msgstr "तुम्ही सबमिट करण्यापूर्वी
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "काही विश्लेषक प्रतिमांची चुकीची हाताळणी करतात आणि काही प्रदेशांमध्ये फोटोंना परावृत्त केले जाते."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "काहीतरी चूक झाली."
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "स्टाईलशीट संपादक"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "स्टाईलशीटमध्ये त्रुटी आहेत"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "वापरकर्ते"
msgid "Uzbek"
msgstr "उझबेक"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "वैध URL ची सुरुवात http:// किंवा https:// ने होणे आवश्यक आहे."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "व्हर्सेल एआय गेटवे"
@@ -5411,4 +5429,3 @@ msgstr "बाहेर झूम करा"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "झुलू"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Permohonan dihantar setiap minggu (8 minggu lepas)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Dimohon"
@@ -534,10 +533,6 @@ msgstr "Dimohon"
msgid "Applied on"
msgstr "Digunakan pada"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Digunakan dengan amaran"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Bahasa Arab"
@@ -1765,7 +1760,6 @@ msgstr "Masukkan kata laluan anda untuk mengesahkan penyediaan pengesahan dua fa
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Masukkan kata laluan anda untuk melumpuhkan pengesahan dua faktor. Akaun anda akan menjadi kurang selamat tanpa 2FA diaktifkan."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Ralat"
@@ -2003,6 +1997,10 @@ msgstr "Gagal mengesahkan kod sandaran anda. Sila cuba lagi."
msgid "Failed to verify your code. Please try again."
msgstr "Gagal mengesahkan kod anda. Sila cuba lagi."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Ciri"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Pratonton"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Pratonton dan eksport gunakan versi terakhir yang sah."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Membaca…"
msgid "Ready to activate"
msgstr "Bersedia untuk diaktifkan"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Bersedia untuk diaktifkan dengan amaran"
@@ -3663,10 +3669,6 @@ msgstr "Tetapkan Semula"
msgid "Reset Password"
msgstr "Tetapkan Semula Kata Laluan"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Tetapkan semula kepada helaian gaya yang digunakan"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Tetapkan semula kata laluan anda"
@@ -4195,6 +4197,10 @@ msgstr "Sesetengah sistem aplikasi memerlukan satu sebelum anda boleh menyerahka
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Sesetengah penghurai salah mengendalikan imej, dan foto tidak digalakkan di sesetengah wilayah."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Ada yang tidak kena"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor helaian gaya"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Helaian gaya mempunyai ralat"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Pengguna"
msgid "Uzbek"
msgstr "Uzbek"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URL yang sah mesti bermula dengan http:// atau https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Pintu Gerbang AI Vercel"
@@ -5411,4 +5429,3 @@ msgstr "Zum keluar"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "प्रति हप्ता पठाइएका आवेदनहरू (पछिल्ला 8 हप्ता)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "आवेदन गरियो"
@@ -534,10 +533,6 @@ msgstr "आवेदन गरियो"
msgid "Applied on"
msgstr "मा लागू गरियो"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "चेतावनीहरू सहित लागू गरियो"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "अरबी"
@@ -1765,7 +1760,6 @@ msgstr "दुई-कारक प्रमाणीकरण सेटअप
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "दुई-कारक प्रमाणीकरण अक्षम गर्न आफ्नो पासवर्ड प्रविष्ट गर्नुहोस्। २FA सक्रिय नभएको अवस्थामा तपाईंको खाता कम सुरक्षित हुनेछ।"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "त्रुटि"
@@ -2003,6 +1997,10 @@ msgstr "तपाईंको ब्याकअप कोड प्रमाण
msgid "Failed to verify your code. Please try again."
msgstr "तपाईंको कोड प्रमाणित गर्न असफल भयो। कृपया फेरि प्रयास गर्नुहोस्।"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "विशेषताहरू"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "पूर्वावलोकन"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "पूर्वावलोकन र निर्यात अन्तिम मान्य संस्करण प्रयोग गर्नुहोस्।"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "पढ्दै…"
msgid "Ready to activate"
msgstr "सक्रिय गर्न तयार छ"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "चेतावनी सहित सक्रिय गर्न तयार"
@@ -3663,10 +3669,6 @@ msgstr "रिसेट गर्नुहोस्"
msgid "Reset Password"
msgstr "पासवर्ड रिसेट गर्नुहोस्"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "लागू गरिएको शैली पानामा रिसेट गर्नुहोस्"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "तपाईंको पासवर्ड रिसेट गर्नुहोस्"
@@ -4195,6 +4197,10 @@ msgstr "तपाईंले पेश गर्न सक्नु अघि
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "केही पार्सरहरूले तस्बिरहरूलाई गलत प्रयोग गर्छन्, र फोटोहरू केही क्षेत्रहरूमा निरुत्साहित हुन्छन्।"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "केही गडबड भयो।"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "स्टाइलसिट सम्पादक"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "स्टाइलसिटमा त्रुटिहरू छन्"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "प्रयोगकर्ताहरू"
msgid "Uzbek"
msgstr "उज्बेक"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "मान्य URL http:// वा https:// बाट सुरु हुनुुपर्छ।"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "वर्सेल एआई गेटवे"
@@ -5411,4 +5429,3 @@ msgstr "जूम आउट"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "जुलु"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Sollicitaties per week verstuurd (laatste 8 weken)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Gesolliciteerd"
@@ -534,10 +533,6 @@ msgstr "Gesolliciteerd"
msgid "Applied on"
msgstr "Toegepast op"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Toegepast met waarschuwingen"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabisch"
@@ -1765,7 +1760,6 @@ msgstr "Voer uw wachtwoord in om het instellen van tweestapsverificatie te beves
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Voer uw wachtwoord in om tweestapsverificatie uit te schakelen. Uw account is minder goed beveiligd zonder 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Fout"
@@ -2003,6 +1997,10 @@ msgstr "Het is niet gelukt om uw back-upcode te verifiëren. Probeer het opnieuw
msgid "Failed to verify your code. Please try again."
msgstr "Het is niet gelukt om uw code te verifiëren. Probeer het opnieuw."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Functies"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Voorbeeld"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Voor het bekijken en exporteren wordt de laatst geldige versie gebruikt."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Lezen…"
msgid "Ready to activate"
msgstr "Klaar om te activeren"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Klaar voor activering met waarschuwingen"
@@ -3663,10 +3669,6 @@ msgstr "Resetten"
msgid "Reset Password"
msgstr "Wachtwoord resetten"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Terugzetten naar toegepaste stijlblad"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Reset uw wachtwoord"
@@ -4195,6 +4197,10 @@ msgstr "Sommige parsers verwerken afbeeldingen verkeerd en fotos worden in so
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Wissel de begin- en einddatum om."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Er is iets misgegaan."
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stijlpagina-editor"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Het stylesheet bevat fouten."
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Gebruikers"
msgid "Uzbek"
msgstr "Oezbeeks"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Geldige URL's moeten beginnen met http:// of https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Uitzoomen"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Søknader sendt per uke (siste 8 uker)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Søkt"
@@ -534,10 +533,6 @@ msgstr "Søkt"
msgid "Applied on"
msgstr "Påført på"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Brukes med advarsler"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabisk"
@@ -1765,7 +1760,6 @@ msgstr "Skriv inn passordet ditt for å bekrefte at du vil sette opp tofaktoraut
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Skriv inn passordet ditt for å deaktivere tofaktorautentisering. Kontoen din vil være mindre sikker uten aktivert 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Feil"
@@ -2003,6 +1997,10 @@ msgstr "Kunne ikke bekrefte reservekoden din. Prøv igjen."
msgid "Failed to verify your code. Please try again."
msgstr "Kunne ikke bekrefte koden din. Prøv igjen."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funksjoner"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Forhåndsvisning"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Forhåndsvisning og eksport med den siste gyldige versjonen."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Leser …"
msgid "Ready to activate"
msgstr "Klar til å aktiveres"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Klar til aktivering med advarsler"
@@ -3663,10 +3669,6 @@ msgstr "Tilbakestill"
msgid "Reset Password"
msgstr "Tilbakestill passord"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Tilbakestill til brukt stilark"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Tilbakestill passordet ditt"
@@ -4195,6 +4197,10 @@ msgstr "Noen søknadssystemer krever dette før du kan sende inn."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Noen parsere håndterer bilder dårlig, og bilder frarådes i enkelte regioner."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Noe gikk galt"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stilarkredigeringsprogram"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stilarket inneholder feil"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Brukere"
msgid "Uzbek"
msgstr "Usbekisk"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Gyldige URL-er må starte med http:// eller https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Zoom ut"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "ପ୍ରତି ସପ୍ତାହରେ ପଠାଯାଇଥିବା ଆବେଦନ (ଗତ 8 ସପ୍ତାହ)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "ଆବେଦନ କରାଯାଇଛି"
@@ -534,10 +533,6 @@ msgstr "ଆବେଦନ କରାଯାଇଛି"
msgid "Applied on"
msgstr "ପ୍ରୟୋଗ ହୋଇଛି |"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "ଚେତାବନୀ ସହିତ ଲାଗୁ କରାଯାଇଛି"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "ଆରବୀ"
@@ -1765,7 +1760,6 @@ msgstr "ଦ୍ୱି-ଘଟକ ପରିଚୟ ପ୍ରମାଣିକରଣ
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "ଦ୍ୱି-ଘଟକ ପରିଚୟ ପ୍ରମାଣିକରଣ ଅସକ୍ରିୟ କରିବା ପାଇଁ ଆପଣଙ୍କ ପାସୱାର୍ଡ ପ୍ରବେଶ କରନ୍ତୁ। 2FA ସକ୍ରିୟ ନ ଥିଲେ ଆପଣଙ୍କ ଆକାଉଣ୍ଟ କମ୍ ସୁରକ୍ଷିତ ହେବ।"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "ତ୍ରୁଟି"
@@ -2003,6 +1997,10 @@ msgstr "ଆପଣଙ୍କ ବ୍ୟାକଅପ୍ କୋଡ୍ ସତ୍ୟା
msgid "Failed to verify your code. Please try again."
msgstr "ଆପଣଙ୍କ କୋଡ୍ ସତ୍ୟାପନ କରିବାରେ ବିଫଳ ହେଲା। ଦୟାକରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "ବିଶେଷତାଗୁଡିକ"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "ପୂର୍ବାବଲୋକନ"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "ପୂର୍ବାବଲୋକନ ଏବଂ ରପ୍ତାନି ଶେଷ ବୈଧ ସଂସ୍କରଣ ବ୍ୟବହାର କରନ୍ତୁ।"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "ପ Reading ିବା…"
msgid "Ready to activate"
msgstr "ସକ୍ରିୟ କରିବାକୁ ପ୍ରସ୍ତୁତ"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "ଚେତାବନୀ ସହିତ ସକ୍ରିୟ କରିବାକୁ ପ୍ରସ୍ତୁତ"
@@ -3663,10 +3669,6 @@ msgstr "ରିସେଟ୍ କରନ୍ତୁ"
msgid "Reset Password"
msgstr "ପାସୱାର୍ଡ ରିସେଟ୍ କରନ୍ତୁ"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "ଲାଗୁ ହୋଇଥିବା ଷ୍ଟାଇଲସିଟକୁ ରିସେଟ୍ କରନ୍ତୁ"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "ଆପଣଙ୍କ ପାସୱାର୍ଡ ରିସେଟ୍ କରନ୍ତୁ"
@@ -4195,6 +4197,10 @@ msgstr "କିଛି ଆବେଦନ ସିଷ୍ଟମ୍‌ରେ ଦାଖଲ
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "କିଛି ପାର୍ସର୍ ଚିତ୍ରକୁ ଠିକ୍‌ଭାବେ ପ୍ରକ୍ରିୟା କରିପାରେ ନାହିଁ, ଏବଂ କିଛି ଅଞ୍ଚଳରେ ଫଟୋ ନିରୁତ୍ସାହିତ କରାଯାଏ।"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "କିଛି ତ୍ରୁଟି ହୋଇଗଲା"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "ଷ୍ଟାଇଲସିଟ୍ ଏଡିଟର୍"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "ଷ୍ଟାଇଲସିଟରେ ତ୍ରୁଟି ଅଛି"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "ଉପଯୋଗକର୍ତ୍ତାମାନେ"
msgid "Uzbek"
msgstr "ଉଜବେକ୍"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "ବୈଧ URL http:// କିମ୍ବା https:// ସହ ଆରମ୍ଭ ହେବା ଉଚିତ।"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "ଜୁମ୍ ଆଉଟ୍ କରନ୍ତୁ"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "ଜୁଲୁ"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Aplikacje wysłane tygodniowo (ostatnie 8 tygodni)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Złożono aplikację"
@@ -534,10 +533,6 @@ msgstr "Złożono aplikację"
msgid "Applied on"
msgstr "Zastosowano"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Zastosowano z ostrzeżeniami"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabski"
@@ -1765,7 +1760,6 @@ msgstr "Wprowadź hasło, aby potwierdzić konfigurację uwierzytelniania dwusk
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Wprowadź hasło, aby wyłączyć uwierzytelnianie dwuskładnikowe. Twoje konto będzie mniej bezpieczne bez włączonego 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Błąd"
@@ -2003,6 +1997,10 @@ msgstr "Nie udało się zweryfikować kodu kopii zapasowej. Proszę spróbować
msgid "Failed to verify your code. Please try again."
msgstr "Nie udało się zweryfikować kodu. Proszę spróbować ponownie."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funkcje"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Zapowiedź"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Podczas podglądu i eksportu użyj ostatniej ważnej wersji."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Czytanie…"
msgid "Ready to activate"
msgstr "Gotowy do aktywacji"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Gotowy do aktywacji z ostrzeżeniami"
@@ -3663,10 +3669,6 @@ msgstr "Zresetuj"
msgid "Reset Password"
msgstr "Zresetuj hasło"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Resetuj do zastosowanego arkusza stylów"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Zresetuj swoje hasło"
@@ -4195,6 +4197,10 @@ msgstr "Niektóre systemy rekrutacyjne wymagają go przed wysłaniem zgłoszenia
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Niektóre parsery nieprawidłowo obsługują obrazy, a w niektórych regionach odradza się dodawanie zdjęć."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Coś poszło nie tak"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Edytor arkuszy stylów"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Arkusz stylów zawiera błędy"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Użytkownicy"
msgid "Uzbek"
msgstr "Uzbecki"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Prawidłowe adresy URL muszą zaczynać się od http:// lub https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Pomniejsz"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Candidaturas enviadas por semana (últimas 8 semanas)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Aplicado"
@@ -534,10 +533,6 @@ msgstr "Aplicado"
msgid "Applied on"
msgstr "Aplicado em"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Aplicado com ressalvas."
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Árabe"
@@ -1765,7 +1760,6 @@ msgstr "Digite sua senha para confirmar a configuração da autenticação de do
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Digite sua senha para desativar a autenticação de dois fatores. Sua conta ficará menos segura sem o 2FA ativado."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Erro"
@@ -2003,6 +1997,10 @@ msgstr "Falha ao verificar seu código de backup. Tente novamente."
msgid "Failed to verify your code. Please try again."
msgstr "Falha ao verificar seu código. Por favor, tente novamente."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Recursos"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Pré-visualização"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "A pré-visualização e a exportação utilizam a última versão válida."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Lendo…"
msgid "Ready to activate"
msgstr "Pronto para ativar"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Pronto para ativar com avisos"
@@ -3663,10 +3669,6 @@ msgstr "Redefinir"
msgid "Reset Password"
msgstr "Redefinir senha"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Redefinir para a folha de estilo aplicada"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Redefinir a sua senha"
@@ -4195,6 +4197,10 @@ msgstr "Alguns sistemas de candidatura exigem um número de telefone antes que v
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Alguns analisadores lidam mal com imagens, e fotos são desencorajadas em algumas regiões."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Algo deu errado"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor de folhas de estilo"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "A folha de estilo contém erros."
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Usuários"
msgid "Uzbek"
msgstr "Uzbeque"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URLs válidos devem começar com http:// ou https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Gateway de IA da Vercel"
@@ -5411,4 +5429,3 @@ msgstr "Diminuir zoom"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Candidaturas enviadas por semana (últimas 8 semanas)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Candidatura enviada"
@@ -534,10 +533,6 @@ msgstr "Candidatura enviada"
msgid "Applied on"
msgstr "Aplicado em"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Aplicado com reservas."
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Árabe"
@@ -1765,7 +1760,6 @@ msgstr "Introduza a sua senha para confirmar a configuração da autenticação
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Introduza a sua senha para desativar a autenticação de dois fatores. A sua conta ficará menos segura sem a 2FA ativada."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Erro"
@@ -2003,6 +1997,10 @@ msgstr "Não foi possível verificar o seu código de cópia de segurança. Tent
msgid "Failed to verify your code. Please try again."
msgstr "Não foi possível verificar o seu código. Por favor, tente novamente."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funcionalidades"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Pré-visualização"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "A pré-visualização e a exportação utilizam a última versão válida."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Leitura…"
msgid "Ready to activate"
msgstr "Pronto para ativar"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Pronto para ativar com avisos"
@@ -3663,10 +3669,6 @@ msgstr "Repor"
msgid "Reset Password"
msgstr "Repor senha"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Repor para a folha de estilo aplicada"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Repor a sua senha"
@@ -4195,6 +4197,10 @@ msgstr "Alguns sistemas de inscrição exigem um antes que você possa enviar."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Alguns analisadores manipulam imagens incorretamente e as fotos são desencorajadas em algumas regiões."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Algo correu mal"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor de folhas de estilo"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "A folha de estilos contém erros."
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Utilizadores"
msgid "Uzbek"
msgstr "Uzbeque"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URLs válidos devem começar por http:// ou https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Afastar"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Candidaturi trimise pe săptămână (ultimele 8 săptămâni)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Candidatură trimisă"
@@ -534,10 +533,6 @@ msgstr "Candidatură trimisă"
msgid "Applied on"
msgstr "Aplicat pe"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Aplicat cu avertismente"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabă"
@@ -1765,7 +1760,6 @@ msgstr "Introduceți parola pentru a confirma configurarea autentificării cu do
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Introduceți parola pentru a dezactiva autentificarea cu doi factori. Contul va fi mai puțin sigur fără 2FA activat."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Eroare"
@@ -2003,6 +1997,10 @@ msgstr "Nu s-a reușit verificarea codului dvs. de rezervă. Vă rugăm să înc
msgid "Failed to verify your code. Please try again."
msgstr "Nu s-a reușit verificarea codului dvs. Vă rugăm să încercați din nou."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funcționalități"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Previzualizare"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Previzualizarea și exportarea utilizează ultima versiune validă."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Lectură…"
msgid "Ready to activate"
msgstr "Gata de activare"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Gata de activare cu avertismente"
@@ -3663,10 +3669,6 @@ msgstr "Resetează"
msgid "Reset Password"
msgstr "Resetați parola"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Resetare la foaia de stil aplicată"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Resetați-vă parola"
@@ -4195,6 +4197,10 @@ msgstr "Unele sisteme de aplicații necesită unul înainte de a putea trimite."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Unii analizatori gestionează greșit imaginile, iar fotografiile sunt descurajate în unele regiuni."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Ceva nu a mers bine"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor de foi de stil"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Foaia de stil conține erori"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Utilizatori"
msgid "Uzbek"
msgstr "Uzbecă"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URL-urile valide trebuie să înceapă cu http:// sau https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Micșorează"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Заявки, отправленные за неделю (последние 8 недель)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Отклик отправлен"
@@ -534,10 +533,6 @@ msgstr "Отклик отправлен"
msgid "Applied on"
msgstr "Применяется на"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Применяется с предупреждениями."
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Арабский"
@@ -1765,7 +1760,6 @@ msgstr "Введите пароль, чтобы подтвердить наст
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Введите пароль, чтобы отключить двухфакторную аутентификацию. Без 2FA ваша учетная запись будет менее защищена."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Ошибка"
@@ -2003,6 +1997,10 @@ msgstr "Не удалось проверить Ваш код резервног
msgid "Failed to verify your code. Please try again."
msgstr "Не удалось проверить Ваш код. Пожалуйста, попробуйте еще раз."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Возможности"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Предварительный просмотр"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Для предварительного просмотра и экспорта используется последняя действующая версия."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Чтение…"
msgid "Ready to activate"
msgstr "Готово к активации"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Готов к активации с предупреждениями."
@@ -3663,10 +3669,6 @@ msgstr "Сброс"
msgid "Reset Password"
msgstr "Сбросить пароль"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Сбросить до примененной таблицы стилей"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Сбросить пароль"
@@ -4195,6 +4197,10 @@ msgstr "Некоторые системы подачи заявок требую
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Некоторые парсеры неправильно обрабатывают изображения, а в некоторых регионах фотографии не рекомендуются."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Что-то пошло не так."
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Редактор таблиц стилей"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "В таблице стилей обнаружены ошибки."
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Пользователи"
msgid "Uzbek"
msgstr "Узбекский"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Корректные URL должны начинаться с http:// или https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Уменьшить"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Зулу"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Žiadosti odoslané za týždeň (posledných 8 týždňov)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Odoslané"
@@ -534,10 +533,6 @@ msgstr "Odoslané"
msgid "Applied on"
msgstr "Aplikované dňa"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Použité s upozorneniami"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabčina"
@@ -1765,7 +1760,6 @@ msgstr "Zadaj svoje heslo na potvrdenie nastavenia dvojfaktorového overenia. Po
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Zadaj svoje heslo na vypnutie dvojfaktorového overenia. Bez zapnutého 2FA bude tvoj účet menej zabezpečený."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Chyba"
@@ -2003,6 +1997,10 @@ msgstr "Nepodarilo sa overiť váš záložný kód. Skúste to prosím znova."
msgid "Failed to verify your code. Please try again."
msgstr "Nepodarilo sa overiť váš kód. Skúste to prosím znova."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funkcie"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Náhľad"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Pri ukážke a exporte sa použije posledná platná verzia."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Číta sa…"
msgid "Ready to activate"
msgstr "Pripravené na aktiváciu"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Pripravené na aktiváciu s upozorneniami"
@@ -3663,10 +3669,6 @@ msgstr "Resetovať"
msgid "Reset Password"
msgstr "Obnoviť heslo"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Obnoviť na použitý štýlový hárok"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Obnov svoje heslo"
@@ -4195,6 +4197,10 @@ msgstr "Niektoré aplikačné systémy vyžadujú pred odoslaním žiadosť."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Niektoré analyzátory nesprávne narábajú s obrázkami a v niektorých regiónoch sa fotografie neodporúčajú."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Niečo sa pokazilo"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Editor štýlov"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Štýlový hárok obsahuje chyby"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Používatelia"
msgid "Uzbek"
msgstr "Uzbečtina"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Platné URL adresy musia začínať http:// alebo https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Brána Vercel AI"
@@ -5411,4 +5429,3 @@ msgstr "Oddialiť"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Prijave, poslane na teden (zadnjih 8 tednov)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Prijavljeno"
@@ -534,10 +533,6 @@ msgstr "Prijavljeno"
msgid "Applied on"
msgstr "Uporabljeno na"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Uporabljeno z opozorili"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabščina"
@@ -1765,7 +1760,6 @@ msgstr "Vnesite svoje geslo, da potrdite nastavitev dvostopenjskega preverjanja
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Vnesite svoje geslo za onemogočanje dvostopenjskega preverjanja pristnosti. Vaš račun bo brez omogočenega 2FA manj varen."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Napaka"
@@ -2003,6 +1997,10 @@ msgstr "Varnostne kode ni uspelo preveriti. Poskusite znova."
msgid "Failed to verify your code. Please try again."
msgstr "Ni uspelo preveriti vaše kode. Poskusite znova."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funkcionalnosti"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Predogled"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Predogled in izvoz uporabljata zadnjo veljavno različico."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Branje ..."
msgid "Ready to activate"
msgstr "Pripravljeno za aktivacijo"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Pripravljeno za aktivacijo z opozorili"
@@ -3663,10 +3669,6 @@ msgstr "Ponastavi"
msgid "Reset Password"
msgstr "Ponastavi geslo"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Ponastavi na uporabljeno slogovno predlogo"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Ponastavite svoje geslo"
@@ -4195,6 +4197,10 @@ msgstr "Nekateri sistemi prijav zahtevajo, da jo lahko oddate."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Nekateri razčlenjevalniki napačno obravnavajo slike, zato fotografije v nekaterih regijah odsvetujejo."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Nekaj je šlo narobe"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Urejevalnik slogovnih predlog"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Slogovna predloga vsebuje napake"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Uporabniki"
msgid "Uzbek"
msgstr "uzbeščina"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Veljavni URL-ji se morajo začeti z http:// ali https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Oddalji"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zuluščina"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Aplikimet e dërguara për javë (8 javët e fundit)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Aplikuar"
@@ -534,10 +533,6 @@ msgstr "Aplikuar"
msgid "Applied on"
msgstr "Aplikuar në"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Zbatuar me paralajmërime"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabisht"
@@ -1765,7 +1760,6 @@ msgstr "Shkruani fjalëkalimin tuaj për të konfirmuar konfigurimin e vërtetim
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Shkruani fjalëkalimin tuaj për të çaktivizuar vërtetimin me dy faktorë. Llogaria juaj do të jetë më pak e sigurt pa 2FA të aktivizuar."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Gabim"
@@ -2003,6 +1997,10 @@ msgstr "Dështoi verifikimi i kodit tuaj të rezervës. Ju lutemi provoni përs
msgid "Failed to verify your code. Please try again."
msgstr "Dështoi verifikimi i kodit tuaj. Ju lutemi provoni përsëri."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Veçori"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Pamje paraprake"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Parapamja dhe eksportimi përdorin versionin e fundit të vlefshëm."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Duke lexuar…"
msgid "Ready to activate"
msgstr "Gati për t'u aktivizuar"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Gati për t'u aktivizuar me paralajmërime"
@@ -3663,10 +3669,6 @@ msgstr "Rivendos"
msgid "Reset Password"
msgstr "Rivendos fjalëkalimin"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Rivendos në fletën e stilit të aplikuar"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Rivendosni fjalëkalimin tuaj"
@@ -4195,6 +4197,10 @@ msgstr "Disa sisteme aplikimi kërkojnë një para se të mund të dorëzoni."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Disa analizues i keqpërdorin imazhet dhe fotot dekurajohen në disa rajone."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Diçka shkoi keq"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Redaktuesi i fletës së stilit"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Fleta e stilit ka gabime"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Përdorues"
msgid "Uzbek"
msgstr "Uzbeke"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URL-të e vlefshme duhet të fillojnë me http:// ose https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Porta Vercel AI"
@@ -5411,4 +5429,3 @@ msgstr "Zvogëlo"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Пријаве послате недељно (последњих 8 недеља)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Пријављено"
@@ -534,10 +533,6 @@ msgstr "Пријављено"
msgid "Applied on"
msgstr "Примењено на"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Примењено са упозорењима"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Арапски"
@@ -1765,7 +1760,6 @@ msgstr "Унесите своју лозинку да потврдите под
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Унесите своју лозинку да бисте онемогућили двофакторску аутентификацију. Ваш налог ће бити мање безбедан без омогућеног 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Грешка"
@@ -2003,6 +1997,10 @@ msgstr "Није успело да се потврди ваш резервни
msgid "Failed to verify your code. Please try again."
msgstr "Није успело да се потврди ваш код. Молимо покушајте поново."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Функције"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Преглед"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Преглед и извоз користе последњу важећу верзију."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Читање…"
msgid "Ready to activate"
msgstr "Спремно за активирање"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Спремно за активирање са упозорењима"
@@ -3663,10 +3669,6 @@ msgstr "Ресетуј"
msgid "Reset Password"
msgstr "Ресетуј лозинку"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Врати на примењени стилски лист"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Ресетујте своју лозинку"
@@ -4195,6 +4197,10 @@ msgstr "Неки системи апликација захтевају једа
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Неки парсери погрешно рукују сликама, а фотографије су обесхрабрене у неким регионима."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Нешто је пошло наопако"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Уређивач стилских листова"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Стилски лист садржи грешке"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Корисници"
msgid "Uzbek"
msgstr "Узбечки"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Важећи URL-ови морају да почињу са http:// или https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Версел АИ Гејтвеј"
@@ -5411,4 +5429,3 @@ msgstr "Умањи"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Зулу"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Ansökningar skickade per vecka (senaste 8 veckorna)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Ansökt"
@@ -534,10 +533,6 @@ msgstr "Ansökt"
msgid "Applied on"
msgstr "Tillämpas på"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Tillämpas med varningar"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arabiska"
@@ -1765,7 +1760,6 @@ msgstr "Ange ditt lösenord för att bekräfta att du vill ställa in tvåfaktor
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Ange ditt lösenord för att inaktivera tvåfaktorsautentisering. Ditt konto blir mindre säkert utan aktiverad 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Fel"
@@ -2003,6 +1997,10 @@ msgstr "Det gick inte att verifiera din säkerhetskopieringskod. Vänligen förs
msgid "Failed to verify your code. Please try again."
msgstr "Det gick inte att verifiera din kod. Vänligen försök igen."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Funktioner"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Förhandsvisning"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Förhandsgranska och exportera med den senast giltiga versionen."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Läsning…"
msgid "Ready to activate"
msgstr "Klar att aktiveras"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Klar att aktiveras med varningar"
@@ -3663,10 +3669,6 @@ msgstr "Återställ"
msgid "Reset Password"
msgstr "Återställ lösenord"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Återställ till tillämpat formatmall"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Återställ ditt lösenord"
@@ -4195,6 +4197,10 @@ msgstr "Vissa ansökningssystem kräver ett innan du kan skicka in."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Vissa tolkar hanterar bilder felaktigt och foton avråds från vissa regioner."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Något gick fel"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stilarksredigerare"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stilarket innehåller fel"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Användare"
msgid "Uzbek"
msgstr "Uzbekiska"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Giltiga URL:er måste börja med http:// eller https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Zooma ut"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "வாரத்திற்கு அனுப்பப்பட்ட விண்ணப்பங்கள் (கடைசி 8 வாரங்கள்)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "விண்ணப்பிக்கப்பட்டது"
@@ -534,10 +533,6 @@ msgstr "விண்ணப்பிக்கப்பட்டது"
msgid "Applied on"
msgstr "அன்று விண்ணப்பித்தது"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "எச்சரிக்கைகளுடன் கூடிய அறிவு"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "அரபிக்"
@@ -1765,7 +1760,6 @@ msgstr "இரண்டு நிலை அங்கீகாரத்தை அ
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "இரண்டு நிலை அங்கீகாரத்தை முடக்க உங்கள் கடவுச்சொல்லை உள்ளிடவும். 2FA இயங்காவிட்டால் உங்கள் கணக்கு குறைந்த பாதுகாப்புடன் இருக்கும்."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "பிழை"
@@ -2003,6 +1997,10 @@ msgstr "உங்கள் காப்புக் குறியீட்ட
msgid "Failed to verify your code. Please try again."
msgstr "உங்கள் குறியீட்டைச் சரிபார்க்கத் தவறியது. மீண்டும் முயற்சிக்கவும்."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "அம்சங்கள்"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "முன்னோட்டம்"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "முன்னோட்டம் மற்றும் ஏற்றுமதிக்கு, கடைசியாகச் செல்லுபடியாகும் பதிப்பைப் பயன்படுத்தவும்."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "படிக்கிறது…"
msgid "Ready to activate"
msgstr "செயல்படுத்தத் தயார்"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "எச்சரிக்கைகளுடன் செயல்படுத்தத் தயார்"
@@ -3663,10 +3669,6 @@ msgstr "மீட்டமை"
msgid "Reset Password"
msgstr "கடவுச்சொல்லை மீட்டமைக்கவும்"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "பயன்படுத்தப்பட்ட பாணித் தாளுக்கு மீட்டமைக்கவும்"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "உங்கள் கடவுச்சொல்லை மீட்டமைக்கவும்"
@@ -4195,6 +4197,10 @@ msgstr "நீங்கள் சமர்ப்பிக்கும் மு
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "சில பாகுபடுத்துபவர்கள் படங்களை தவறாகக் கையாளுகின்றனர், மேலும் சில பகுதிகளில் புகைப்படங்கள் ஊக்கமளிக்கவில்லை."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "ஏதோ தவறு நடந்துவிட்டது"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "ஸ்டைல்ஷீட் எடிட்டர்"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "ஸ்டைல்ஷீட்டில் பிழைகள் உள்ளன"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "பயனர்கள்"
msgid "Uzbek"
msgstr "உஸ்பெக்"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "சரியான URL-கள் http:// அல்லது https:// கொண்டு தொடங்க வேண்டும்."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "வெர்சல் ஏஐ கேட்வே"
@@ -5411,4 +5429,3 @@ msgstr "சிறிதாக்கு"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "ஜூலு"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "వారానికి పంపిన దరఖాస్తులు (చివరి 8 వారాలు)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "దరఖాస్తు చేశారు"
@@ -534,10 +533,6 @@ msgstr "దరఖాస్తు చేశారు"
msgid "Applied on"
msgstr "దరఖాస్తు చేసిన తేదీ"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "హెచ్చరికలతో వర్తింపజేయబడింది"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "అరబిక్"
@@ -1765,7 +1760,6 @@ msgstr "రెండు దశల ధృవీకరణ ఏర్పాటున
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "రెండు దశల ధృవీకరణను నిలిపివేయడానికి మీ పాస్‌వర్డ్‌ని నమోదు చేయండి. 2FA ప్రారంభించబడకుండా ఉంటే మీ ఖాతా తక్కువ భద్రతగా ఉంటుంది."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "లోపం"
@@ -2003,6 +1997,10 @@ msgstr "మీ బ్యాకప్ కోడ్‌ను ధృవీకరి
msgid "Failed to verify your code. Please try again."
msgstr "మీ కోడ్‌ను ధృవీకరించడంలో విఫలమయ్యాము. దయచేసి మళ్ళీ ప్రయత్నించండి."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "ఫీచర్లు"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "ప్రివ్యూ"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "ప్రివ్యూ మరియు ఎక్స్‌పోర్ట్ చివరి చెల్లుబాటు అయ్యే వెర్షన్‌ను ఉపయోగిస్తాయి."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "చదువుతోంది…"
msgid "Ready to activate"
msgstr "యాక్టివేట్ చేయడానికి సిద్ధంగా ఉంది"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "హెచ్చరికలతో యాక్టివేట్ చేయడానికి సిద్ధంగా ఉంది"
@@ -3663,10 +3669,6 @@ msgstr "రీసెట్ చేయండి"
msgid "Reset Password"
msgstr "పాస్‌వర్డ్ రీసెట్ చేయండి"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "వర్తింపజేసిన స్టైల్‌షీట్‌కు రీసెట్ చేయండి"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "మీ పాస్‌వర్డ్‌ను రీసెట్ చేయండి"
@@ -4195,6 +4197,10 @@ msgstr "మీరు సమర్పించే ముందు కొన్న
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "కొంతమంది పార్సర్‌లు చిత్రాలను తప్పుగా నిర్వహిస్తారు మరియు కొన్ని ప్రాంతాలలో ఫోటోలు నిరుత్సాహపరుస్తాయి."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "ఏదో తప్పు జరిగింది"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "స్టైల్‌షీట్ ఎడిటర్"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "స్టైల్‌షీట్‌లో లోపాలు ఉన్నాయి"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "వినియోగదారులు"
msgid "Uzbek"
msgstr "ఉజ్‌బెక్"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "చెల్లే URLలు తప్పనిసరిగా http:// లేదా https://తో ప్రారంభించాలి."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "వర్సెల్ AI గేట్‌వే"
@@ -5411,4 +5429,3 @@ msgstr "జూమ్ అవుట్ చేయండి"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "జులు"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "ใบสมัครที่ส่งต่อสัปดาห์ (8 สัปดาห์ล่าสุด)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "สมัครแล้ว"
@@ -534,10 +533,6 @@ msgstr "สมัครแล้ว"
msgid "Applied on"
msgstr "สมัครเมื่อ"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "ใช้งานโดยมีคำเตือน"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "อารบิก"
@@ -1765,7 +1760,6 @@ msgstr "กรอกรหัสผ่านของคุณเพื่อย
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "กรอกรหัสผ่านของคุณเพื่อปิดการใช้งานการยืนยันตัวตนสองขั้นตอน บัญชีของคุณจะไม่ปลอดภัยเท่าเดิมหากไม่ได้เปิดใช้งาน 2FA"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "ข้อผิดพลาด"
@@ -2003,6 +1997,10 @@ msgstr "ไม่สามารถยืนยันรหัสสำรอง
msgid "Failed to verify your code. Please try again."
msgstr "ไม่สามารถยืนยันรหัสของคุณได้ กรุณาลองอีกครั้ง"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "คุณสมบัติ"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "ตัวอย่าง"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "ให้ใช้เวอร์ชันล่าสุดที่ถูกต้องในการแสดงตัวอย่างและการส่งออก"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "กำลังอ่าน…"
msgid "Ready to activate"
msgstr "พร้อมใช้งานแล้ว"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "พร้อมใช้งานพร้อมคำเตือน"
@@ -3663,10 +3669,6 @@ msgstr "รีเซ็ต"
msgid "Reset Password"
msgstr "รีเซ็ตรหัสผ่าน"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "รีเซ็ตเป็นสไตล์ชีตที่ใช้งานอยู่"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "รีเซ็ตรหัสผ่านของคุณ"
@@ -4195,6 +4197,10 @@ msgstr "ระบบการสมัครบางระบบจำเป็
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "โปรแกรมแยกวิเคราะห์บางคนจัดการรูปภาพในทางที่ผิด และรูปภาพไม่ได้รับการสนับสนุนในบางภูมิภาค"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "เกิดข้อผิดพลาดบางอย่าง"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "ตัวแก้ไขสไตล์ชีต"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "ไฟล์สไตล์ชีตมีข้อผิดพลาด"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "ผู้ใช้"
msgid "Uzbek"
msgstr "อุซเบก"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URL ที่ถูกต้องต้องขึ้นต้นด้วย http:// หรือ https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "เกตเวย์ AI ของ Vercel"
@@ -5411,4 +5429,3 @@ msgstr "ซูมออก"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "ซูลู"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Haftalık gönderilen başvurular (son 8 hafta)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Başvuruldu"
@@ -534,10 +533,6 @@ msgstr "Başvuruldu"
msgid "Applied on"
msgstr "Başvuru tarihi"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Uyarılarla birlikte uygulandı."
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arapça"
@@ -1765,7 +1760,6 @@ msgstr "İki faktörlü kimlik doğrulamayı kurmayı onaylamak için şifrenizi
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "İki faktörlü kimlik doğrulamayı devre dışı bırakmak için şifrenizi girin. 2FA etkin olmadığında hesabınız daha az güvenli olur."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Hata"
@@ -2003,6 +1997,10 @@ msgstr "Yedekleme kodunuz doğrulanamadı. Lütfen tekrar deneyin."
msgid "Failed to verify your code. Please try again."
msgstr "Kodunuz doğrulanamadı. Lütfen tekrar deneyin."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Özellikler"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Önizleme"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Önizleme ve dışa aktarma işlemleri için en son geçerli sürüm kullanılır."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Okunuyor…"
msgid "Ready to activate"
msgstr "Etkinleştirmeye hazır"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Uyarılarla birlikte etkinleştirmeye hazır."
@@ -3663,10 +3669,6 @@ msgstr "Sıfırla"
msgid "Reset Password"
msgstr "Şifreyi Sıfırla"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Uygulanan stil sayfasına sıfırla"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Şifrenizi sıfırlayın"
@@ -4195,6 +4197,10 @@ msgstr "Bazı başvuru sistemleri, gönderebilmeniz için bir başvuru gerektiri
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Bazı ayrıştırıcılar görüntüleri yanlış kullanıyor ve bazı bölgelerde fotoğraf kullanılması önerilmez."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Bir şeyler ters gitti."
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Stil sayfası düzenleyici"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Stil dosyasında hatalar var."
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Kullanıcılar"
msgid "Uzbek"
msgstr "Özbekçe"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Geçerli URL'ler http:// veya https:// ile başlamalıdır."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Ağ Geçidi"
@@ -5411,4 +5429,3 @@ msgstr "Uzaklaştır"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Заявки, надіслані за тиждень (останні 8 тижнів)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Подано"
@@ -534,10 +533,6 @@ msgstr "Подано"
msgid "Applied on"
msgstr "Подано"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Застосовується з попередженнями"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Арабська"
@@ -1765,7 +1760,6 @@ msgstr "Введіть пароль для підтвердження налаш
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Введіть свій пароль для вимикання двофакторної автентифікації. Ваш профіль буде менш захищений без увімкненого 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Помилка"
@@ -2003,6 +1997,10 @@ msgstr "Не вдалося підтвердити код резервної к
msgid "Failed to verify your code. Please try again."
msgstr "Не вдалося підтвердити ваш код. Спробуйте ще раз."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Функції"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Попередній перегляд"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Для попереднього перегляду та експорту використовується остання дійсна версія."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Читання…"
msgid "Ready to activate"
msgstr "Готовий до активації"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Готовий до активації з попередженнями"
@@ -3663,10 +3669,6 @@ msgstr "Скинути"
msgid "Reset Password"
msgstr "Скинути пароль"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Скинути до застосованого таблиці стилів"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Скинути пароль"
@@ -4195,6 +4197,10 @@ msgstr "Деякі системи заявок вимагають її, перш
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Деякі аналізатори неправильно обробляють зображення, тому в деяких регіонах фотографії не рекомендуються."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Щось пішло не так"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Редактор таблиць стилів"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Таблиця стилів містить помилки"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Користувачі"
msgid "Uzbek"
msgstr "Узбецька"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Дійсні URL-адреси мають починатися з http:// або https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Шлюз штучного інтелекту Vercel"
@@ -5411,4 +5429,3 @@ msgstr "Зменшити"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Зулу"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Haftasiga yuborilgan arizalar (songgi 8 hafta)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Topshirildi"
@@ -534,10 +533,6 @@ msgstr "Topshirildi"
msgid "Applied on"
msgstr "Ariza sanasi"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Ogohlantirishlar bilan qo'llanildi"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Arab tili"
@@ -1765,7 +1760,6 @@ msgstr "Ikki bosqichli autentifikatsiyani yoqishni tasdiqlash uchun parolingizni
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Ikki bosqichli autentifikatsiyani ochirishni tasdiqlash uchun parolingizni kiriting. 2BShT yoqilmagan holda hisobingiz kamroq xavfsiz boʻladi."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Xato"
@@ -2003,6 +1997,10 @@ msgstr "Zaxira kodingizni tasdiqlash muvaffaqiyatsiz bo'ldi. Iltimos, yana urini
msgid "Failed to verify your code. Please try again."
msgstr "Kodingizni tekshirish muvaffaqiyatsiz bo'ldi. Iltimos, yana urinib ko'ring."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Xususiyatlar"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Oldindan ko'rish"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Oldindan ko'rish va eksport qilish uchun oxirgi amaldagi versiyadan foydalaning."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "O'qilmoqda…"
msgid "Ready to activate"
msgstr "Faollashtirishga tayyor"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Ogohlantirishlar bilan faollashtirishga tayyor"
@@ -3663,10 +3669,6 @@ msgstr "Tiklash"
msgid "Reset Password"
msgstr "Parolni tiklash"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Qo'llanilgan uslublar jadvaliga qayta o'rnatish"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Parolingizni tiklang"
@@ -4195,6 +4197,10 @@ msgstr "Ba'zi dastur tizimlari siz yuborishingizdan oldin uni talab qiladi."
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Ba'zi tahlilchilar tasvirlarni noto'g'ri ishlatishadi va ba'zi hududlarda fotosuratlar tavsiya etilmaydi."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Nimadir noto'g'ri ketdi"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Uslublar jadvali muharriri"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Uslublar jadvalida xatolar mavjud"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Foydalanuvchilar"
msgid "Uzbek"
msgstr "O'zbek tili"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "Toʻgʻri URL http:// yoki https:// bilan boshlanishi kerak."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "Kichraytirish"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "Đơn ứng tuyển đã gửi mỗi tuần (8 tuần gần nhất)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "Đã ứng tuyển"
@@ -534,10 +533,6 @@ msgstr "Đã ứng tuyển"
msgid "Applied on"
msgstr "Đã ứng tuyển vào"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "Được áp dụng kèm theo cảnh báo"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "Tiếng Ả Rập"
@@ -1765,7 +1760,6 @@ msgstr "Nhập mật khẩu của bạn để xác nhận thiết lập xác th
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "Nhập mật khẩu của bạn để tắt xác thực hai yếu tố. Tài khoản của bạn sẽ kém an toàn hơn nếu không bật 2FA."
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "Lỗi"
@@ -2003,6 +1997,10 @@ msgstr "Không thể xác minh mã dự phòng của bạn. Vui lòng thử lạ
msgid "Failed to verify your code. Please try again."
msgstr "Không thể xác minh mã của bạn. Vui lòng thử lại."
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "Tính năng"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "Xem trước"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "Chức năng xem trước và xuất khẩu sử dụng phiên bản hợp lệ mới nhất."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "Đang đọc…"
msgid "Ready to activate"
msgstr "Sẵn sàng kích hoạt"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "Sẵn sàng kích hoạt kèm cảnh báo"
@@ -3663,10 +3669,6 @@ msgstr "Thiết lập lại"
msgid "Reset Password"
msgstr "Đặt mật khẩu mới"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "Đặt lại về kiểu định dạng đã áp dụng"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "Đặt lại mật khẩu"
@@ -4195,6 +4197,10 @@ msgstr "Một số hệ thống ứng dụng yêu cầu phải có trước khi
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "Một số trình phân tích cú pháp xử lý sai hình ảnh và ảnh không được khuyến khích ở một số vùng."
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "Đã xảy ra lỗi"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "Trình chỉnh sửa bảng định kiểu"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "Tệp định kiểu có lỗi"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "Người dùng"
msgid "Uzbek"
msgstr "Uzbek"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "URL hợp lệ phải bắt đầu với http:// hoặc https://."
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Cổng AI Vercel"
@@ -5411,4 +5429,3 @@ msgstr "Thu nhỏ"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "Zulu"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "每周发送的申请(最近 8 周)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "已申请"
@@ -534,10 +533,6 @@ msgstr "已申请"
msgid "Applied on"
msgstr "申请日期"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "附带警告"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "阿拉伯语"
@@ -1765,7 +1760,6 @@ msgstr "请输入你的密码以确认设置双重身份验证。启用后,你
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "请输入你的密码以禁用双重身份验证。关闭 2FA 后,你的账号将不再那么安全。"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "错误"
@@ -2003,6 +1997,10 @@ msgstr "验证备份代码失败。请重试。"
msgid "Failed to verify your code. Please try again."
msgstr "验证您的代码失败。请重试。"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "功能"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "预览"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "预览和导出时请使用最新有效版本。"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "正在读取…"
msgid "Ready to activate"
msgstr "准备激活"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "准备激活并发出警告"
@@ -3663,10 +3669,6 @@ msgstr "重置"
msgid "Reset Password"
msgstr "重置密码"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "重置为已应用的样式表"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "重置密码"
@@ -4195,6 +4197,10 @@ msgstr "有些申请系统需要先有一个,然后才能提交。”"
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "一些解析器错误地处理图像,并且在某些地区不鼓励使用照片。”"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "出问题了"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "样式表编辑器"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "样式表存在错误"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "用户数"
msgid "Uzbek"
msgstr "乌兹别克语"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "有效的 URL 必须以 http:// 或 https:// 开头。"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel 人工智能网关"
@@ -5411,4 +5429,3 @@ msgstr "缩小"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "祖鲁语"
+32 -15
View File
@@ -526,7 +526,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr "每週送出的申請(最近 8 週)"
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr "已申請"
@@ -534,10 +533,6 @@ msgstr "已申請"
msgid "Applied on"
msgstr "應徵日期"
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr "附帶警告"
#: src/libs/locale.ts
msgid "Arabic"
msgstr "阿拉伯文"
@@ -1765,7 +1760,6 @@ msgstr "請輸入您的密碼以確認設定雙因子驗證。啟用後,每次
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr "請輸入您的密碼以停用雙因子驗證。停用後,您的帳戶將會比較不安全。"
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr "錯誤"
@@ -2003,6 +1997,10 @@ msgstr "驗證您的備份碼失敗。請再試一次。"
msgid "Failed to verify your code. Please try again."
msgstr "驗證您的驗證碼失敗。請再試一次。"
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr "功能"
@@ -3420,8 +3418,12 @@ msgid "Preview"
msgstr "預覽"
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgstr "預覽和匯出時請使用最新有效版本。"
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
msgid "Primary Color"
@@ -3542,6 +3544,10 @@ msgstr "正在讀取…"
msgid "Ready to activate"
msgstr "準備啟用"
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr "準備啟動並發出警告"
@@ -3663,10 +3669,6 @@ msgstr "重置"
msgid "Reset Password"
msgstr "重設密碼"
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr "重設為已套用的樣式表"
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr "重設您的密碼"
@@ -4195,6 +4197,10 @@ msgstr "有些申請系統需要先有一個,然後再提交。 」"
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr "有些解析器錯誤地處理圖像,並且在某些地區不鼓勵使用照片。 」"
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr "出問題了"
@@ -4338,8 +4344,8 @@ msgid "Stylesheet editor"
msgstr "樣式表編輯器"
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgstr "樣式表存在錯誤"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
msgid "Subreddit"
@@ -5103,10 +5109,22 @@ msgstr "使用者"
msgid "Uzbek"
msgstr "烏茲別克語"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr "有效的 URL 必須以 http:// 或 https:// 開頭。"
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr "Vercel AI Gateway"
@@ -5411,4 +5429,3 @@ msgstr "縮小"
#: src/libs/locale.ts
msgid "Zulu"
msgstr "祖魯語"
+30 -12
View File
@@ -521,7 +521,6 @@ msgid "Applications sent per week (last 8 weeks)"
msgstr ""
#: src/features/applications/components/table-view.tsx
#: src/features/resume/stylesheet/status.tsx
msgid "Applied"
msgstr ""
@@ -529,10 +528,6 @@ msgstr ""
msgid "Applied on"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Applied with warnings"
msgstr ""
#: src/libs/locale.ts
msgid "Arabic"
msgstr ""
@@ -1760,7 +1755,6 @@ msgstr ""
msgid "Enter your password to disable two-factor authentication. Your account will be less secure without 2FA enabled."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
#: src/routes/builder/$resumeId/-sidebar/right/sections/ats-check.tsx
msgid "Error"
msgstr ""
@@ -1998,6 +1992,10 @@ msgstr ""
msgid "Failed to verify your code. Please try again."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Fatal error"
msgstr ""
#: src/routes/_home/-sections/features.tsx
msgid "Features"
msgstr ""
@@ -3415,7 +3413,11 @@ msgid "Preview"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export use the last valid version."
msgid "Preview and export fall back to base styles."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Preview and export keep valid styles and ignore invalid styles."
msgstr ""
#: src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx
@@ -3537,6 +3539,10 @@ msgstr ""
msgid "Ready to activate"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Ready to activate with warnings"
msgstr ""
@@ -3658,10 +3664,6 @@ msgstr ""
msgid "Reset Password"
msgstr ""
#: src/features/resume/stylesheet/toolbar.tsx
msgid "Reset to applied stylesheet"
msgstr ""
#: src/features/auth/pages/reset-password.tsx
msgid "Reset your password"
msgstr ""
@@ -4190,6 +4192,10 @@ msgstr ""
msgid "Some parsers mishandle images, and photos are discouraged in some regions."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Some styles were ignored"
msgstr ""
#: src/components/layout/error-screen.tsx
msgid "Something went wrong"
msgstr ""
@@ -4333,7 +4339,7 @@ msgid "Stylesheet editor"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Stylesheet has errors"
msgid "Stylesheet has fatal errors"
msgstr ""
#: src/routes/_home/-sections/footer.tsx
@@ -5098,10 +5104,22 @@ msgstr ""
msgid "Uzbek"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid"
msgstr ""
#: src/components/input/rich-input.tsx
msgid "Valid URLs must start with http:// or https://."
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with errors"
msgstr ""
#: src/features/resume/stylesheet/status.tsx
msgid "Valid with warnings"
msgstr ""
#: src/features/settings/integrations/components/ai-section.tsx
msgid "Vercel AI Gateway"
msgstr ""
@@ -35,10 +35,6 @@ const toastMocks = vi.hoisted(() => ({
error: vi.fn(() => "sync-error-toast"),
}));
const stylesheetMocks = vi.hoisted(() => ({
refresh: vi.fn(),
}));
vi.mock("@orpc/client", () => ({
consumeEventIterator: consumeEventIteratorMock,
}));
@@ -81,10 +77,6 @@ vi.mock("sonner", () => ({
toast: toastMocks,
}));
vi.mock("@/features/resume/stylesheet/store", () => ({
refreshStylesheetStore: stylesheetMocks.refresh,
}));
function cloneResumeData(data: ResumeData): ResumeData {
return structuredClone(data);
}
@@ -135,7 +127,6 @@ describe("builder resume autosave", () => {
i18n.loadAndActivate({ locale: "en-US", messages: {} });
toastMocks.dismiss.mockClear();
toastMocks.error.mockClear();
stylesheetMocks.refresh.mockReset();
useResumeStore.getState().reset();
});
@@ -169,6 +160,26 @@ describe("builder resume autosave", () => {
expect(orpcMocks.patchResume).not.toHaveBeenCalled();
});
it("autosaves stylesheet source through the ordinary full-data update", async () => {
const initial = makeResume("resume-stylesheet-autosave");
const source = { languageVersion: 1, text: "@version 1;\nname { color: blue; }\n" };
const updated = makeResume(initial.id);
updated.data.metadata.stylesheet = { mode: "semantic", source };
orpcMocks.updateResume.mockResolvedValue(updated);
useResumeStore.getState().initialize(initial);
useResumeStore.getState().updateResumeData((draft) => {
draft.metadata.stylesheet = { mode: "semantic", source };
});
vi.advanceTimersByTime(500);
await flushMicrotasks();
expect(orpcMocks.updateResume).toHaveBeenCalledWith(
{ id: initial.id, data: updated.data },
expect.objectContaining({ signal: expect.any(AbortSignal) }),
);
});
it("saves the latest pending snapshot after an in-flight save resolves", async () => {
const initial = makeResume("resume-in-flight");
const first = withBasicsName(initial, "First Name");
@@ -507,25 +518,13 @@ describe("resume update stream subscription", () => {
expect(useResumeStore.getState().resume?.data.basics.name).toBe("Local Name");
});
it("refetches canonical stylesheet state for stylesheet SSE events", async () => {
it("applies stylesheet source from the ordinary resume SSE flow", async () => {
const initial = makeResume("resume-stylesheet");
consumeEventIteratorMock.mockReturnValue(vi.fn().mockResolvedValue(undefined));
routerParamsMock.value = { resumeId: initial.id };
useResumeStore.getState().initialize(initial);
renderHook(() => useBuilderResumeUpdateSubscription());
const handlers = consumeEventIteratorMock.mock.calls[0]?.[1] as {
onEvent: (event: { mutation: string }) => Promise<void>;
const remote = makeResume("resume-stylesheet");
remote.data.metadata.stylesheet = {
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\nname { color: blue; }\n" },
};
await act(async () => handlers.onEvent({ mutation: "stylesheet" }));
expect(stylesheetMocks.refresh).toHaveBeenCalledWith(initial.id);
expect(orpcMocks.getResumeById).not.toHaveBeenCalled();
});
it("refreshes the render-data version after content SSE events", async () => {
const initial = makeResume("resume-content");
const remote = withBasicsName(initial, "Remote");
consumeEventIteratorMock.mockReturnValue(vi.fn().mockResolvedValue(undefined));
orpcMocks.getResumeById.mockResolvedValue(remote);
routerParamsMock.value = { resumeId: initial.id };
@@ -537,6 +536,7 @@ describe("resume update stream subscription", () => {
};
await act(async () => handlers.onEvent({ mutation: "update" }));
expect(stylesheetMocks.refresh).toHaveBeenCalledWith(initial.id, remote.data);
expect(orpcMocks.getResumeById).toHaveBeenCalledWith({ id: initial.id });
expect(useResumeStore.getState().resume?.data.metadata.stylesheet).toEqual(remote.data.metadata.stylesheet);
});
});
@@ -10,7 +10,6 @@ import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { immer } from "zustand/middleware/immer";
import { create } from "zustand/react";
import { refreshStylesheetStore } from "@/features/resume/stylesheet/store";
import { orpc, streamClient } from "@/libs/orpc/client";
export type Resume = {
@@ -26,7 +25,7 @@ export type Resume = {
};
// Mirrors the server-side ResumeUpdatedEvent discriminator (packages/api resume/events.ts).
type ResumeUpdateMutation = "sync" | "create" | "update" | "patch" | "lock" | "password" | "delete" | "stylesheet";
type ResumeUpdateMutation = "sync" | "create" | "update" | "patch" | "lock" | "password" | "delete";
type ResumeUpdateEvent = { mutation: ResumeUpdateMutation };
type SaveStatus = "idle" | "saving" | "saved" | "error";
@@ -616,14 +615,9 @@ export function useBuilderResumeUpdateSubscription() {
if (!resumeId) return;
bindRuntimeQueryClient(resumeId, queryClient);
if (event.mutation === "stylesheet") {
await refreshStylesheetStore(resumeId);
return;
}
const resume = (await orpc.resume.getById.call({ id: resumeId })) as Resume;
queryClient.setQueryData(getResumeQueryKey(resumeId), resume);
await refreshStylesheetStore(resumeId, resume.data);
if (hasPendingLocalChanges(resumeId)) {
useResumeStore.getState().mergeResumeMetadata(resume);
@@ -1,6 +1,4 @@
import type { PublicStyleProjection } from "@reactive-resume/pdf/public-projection";
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { SemanticStylesheet, StylesheetSource } from "@reactive-resume/schema/resume/stylesheet";
import type { Template } from "@reactive-resume/schema/templates";
import { useMemo } from "react";
import { createResumePdfBlob as createPdfBlob } from "@reactive-resume/pdf/browser";
@@ -11,22 +9,6 @@ type ResumePdfRenderOptions = {
includeCoverLetterHeader?: boolean;
};
export type ResumePdfPresentation =
| { stylesheet: Pick<SemanticStylesheet, "mode"> & { applied: StylesheetSource } }
| { publicStyleProjection: PublicStyleProjection };
const withAppliedStylesheet = (data: ResumeData, presentation?: ResumePdfPresentation): ResumeData => {
if (!presentation || !("stylesheet" in presentation)) return data;
const { mode, applied } = presentation.stylesheet;
return {
...data,
metadata: {
...data.metadata,
stylesheet: { mode, source: applied, applied },
},
};
};
export const useLocalizedResumeDocument = (data?: ResumeData, template?: Template) => {
const sectionTitleResolver = useSectionTitleResolver(data?.metadata.page.locale);
@@ -47,17 +29,13 @@ export const createResumePdfBlob = async (
data: ResumeData,
template?: Template,
renderOptions?: ResumePdfRenderOptions,
presentation?: ResumePdfPresentation,
) => {
const sectionTitleResolver = await createSectionTitleResolverForLocale(data.metadata.page.locale);
return createPdfBlob({
data: withAppliedStylesheet(data, presentation),
data,
template,
...(renderOptions ? { renderOptions } : {}),
...(presentation && "publicStyleProjection" in presentation
? { publicStyleProjection: presentation.publicStyleProjection }
: {}),
resolveSectionTitle: sectionTitleResolver,
});
};
@@ -3,7 +3,6 @@
import { act, renderHook } from "@testing-library/react";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { i18n } from "@lingui/core";
import { createPublicStyleProjection } from "@reactive-resume/pdf/public-projection";
import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
import { useResumeExport } from "./use-resume-export";
@@ -14,22 +13,13 @@ const mocks = vi.hoisted(() => ({
toastError: vi.fn(),
}));
vi.mock("./pdf-document", () => ({
vi.mock("@/features/resume/export/pdf-document", () => ({
createResumePdfBlob: mocks.createResumePdfBlob,
}));
vi.mock("@reactive-resume/utils/file", () => ({
downloadWithAnchor: mocks.downloadWithAnchor,
generateFilename: (name: string, extension: string) => `${name}.${extension}`,
}));
vi.mock("@/features/resume/stylesheet/store", () => ({
useStylesheetStore: (selector: (state: object) => unknown) =>
selector({
resumeId: undefined,
mode: "legacy",
source: { languageVersion: 1, text: "@version 1;\n" },
applied: { languageVersion: 1, text: "@version 1;\n" },
}),
}));
vi.mock("sonner", () => ({
toast: {
loading: vi.fn(() => "toast"),
@@ -49,21 +39,16 @@ beforeEach(() => {
});
describe("useResumeExport public PDF", () => {
it("downloads the authorized server blob after one mismatched-projection refetch", async () => {
it("downloads the authorized server blob after public browser rendering rejects", async () => {
const semanticData = structuredClone(sampleResumeData);
const source = { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" };
semanticData.metadata.stylesheet = { mode: "semantic", source, applied: source };
const projection = await createPublicStyleProjection({ data: semanticData });
const mismatchedProjection = { ...projection, renderDataHash: "0".repeat(64) };
const refetchStyleProjection = vi.fn(async () => mismatchedProjection);
semanticData.metadata.stylesheet = { mode: "semantic", source };
mocks.createResumePdfBlob.mockRejectedValueOnce(new Error("browser renderer failed"));
const { result } = renderHook(() =>
useResumeExport(
{ name: "Sample", slug: "sample", data: sampleResumeData },
{ name: "Sample", slug: "sample", data: semanticData },
{
publicResumePdf: {
stylesheetMode: "semantic",
styleProjection: mismatchedProjection,
refetchStyleProjection,
publicResume: { username: "amruth", slug: "sample" },
},
},
@@ -72,19 +57,14 @@ describe("useResumeExport public PDF", () => {
await act(() => result.current.onDownloadPDF());
expect(refetchStyleProjection).toHaveBeenCalledTimes(1);
expect(mocks.createResumePdfBlob).toHaveBeenCalledWith(semanticData);
expect(mocks.fetch).toHaveBeenCalledTimes(1);
expect(mocks.createResumePdfBlob).not.toHaveBeenCalled();
const blob = mocks.downloadWithAnchor.mock.calls[0]?.[0] as Blob;
expect(await blob.text()).toBe("server");
});
it("does not download an unstyled PDF when semantic rendering rejects", async () => {
mocks.createResumePdfBlob.mockRejectedValueOnce(
new Error("The semantic stylesheet could not be rendered.", {
cause: [{ code: "RESOURCE_LIMIT", severity: "error" }],
}),
);
it("does not download a PDF when the renderer rejects", async () => {
mocks.createResumePdfBlob.mockRejectedValueOnce(new Error("PDF renderer failed"));
const { result } = renderHook(() => useResumeExport({ name: "Sample", slug: "sample", data: sampleResumeData }));
await act(() => result.current.onDownloadPDF());
@@ -1,10 +1,8 @@
import type { ResumeExportTarget } from "@reactive-resume/resume/export-sections";
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { SemanticStylesheet } from "@reactive-resume/schema/resume/stylesheet";
import type { PublicResumePdfOptions } from "@/features/resume/public/public-pdf";
import type { ResumePdfPresentation } from "./pdf-document";
import { t } from "@lingui/core/macro";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useState } from "react";
import { toast } from "sonner";
import { buildDocx } from "@reactive-resume/docx";
import { getResumeSectionTitle } from "@reactive-resume/pdf/section-title";
@@ -12,7 +10,6 @@ import { getResumeExportData, resumeHasCoverLetter } from "@reactive-resume/resu
import { buildMarkdown } from "@reactive-resume/resume/markdown";
import { downloadWithAnchor, generateFilename } from "@reactive-resume/utils/file";
import { resolvePublicResumePdfBlob } from "@/features/resume/public/public-pdf";
import { useStylesheetStore } from "@/features/resume/stylesheet/store";
import { createSectionTitleResolverForLocale } from "@/libs/resume/section-title-locale";
import { createResumePdfBlob } from "./pdf-document";
@@ -54,43 +51,12 @@ type DownloadPdfOptions = {
export function useResumeExport(resume: ExportableResume | undefined, exportOptions: UseResumeExportOptions = {}) {
const [isExporting, setIsExporting] = useState(false);
const hasCoverLetter = resume ? resumeHasCoverLetter(resume.data) : false;
const stylesheetResumeId = useStylesheetStore((state) => state.resumeId);
const stylesheetMode = useStylesheetStore((state) => state.mode);
const stylesheetSource = useStylesheetStore((state) => state.source);
const stylesheetApplied = useStylesheetStore((state) => state.applied);
const canonicalStylesheet = useMemo<SemanticStylesheet | undefined>(
() =>
resume?.id && resume.id === stylesheetResumeId
? {
mode: stylesheetMode,
source: stylesheetSource,
applied: stylesheetApplied,
}
: undefined,
[resume?.id, stylesheetApplied, stylesheetMode, stylesheetResumeId, stylesheetSource],
);
const pdfPresentation = useMemo<ResumePdfPresentation | undefined>(
() =>
canonicalStylesheet
? { stylesheet: { mode: canonicalStylesheet.mode, applied: canonicalStylesheet.applied } }
: undefined,
[canonicalStylesheet],
);
const onDownloadJSON = useCallback(() => {
if (!resume) return;
const data = canonicalStylesheet
? {
...resume.data,
metadata: {
...resume.data.metadata,
stylesheet: canonicalStylesheet,
},
}
: resume.data;
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const blob = new Blob([JSON.stringify(resume.data, null, 2)], { type: "application/json" });
downloadWithAnchor(blob, generateFilename(getExportName(resume), "json"));
}, [canonicalStylesheet, resume]);
}, [resume]);
const onDownloadMarkdown = useCallback(
async (target: ResumeExportTarget = "resume") => {
@@ -136,7 +102,6 @@ export function useResumeExport(resume: ExportableResume | undefined, exportOpti
target === "cover-letter"
? { includeCoverLetterHeader: downloadOptions?.includeCoverLetterHeader }
: undefined,
pdfPresentation,
);
downloadWithAnchor(blob, generateFilename(getTargetExportName(resume, target), "pdf"));
} catch {
@@ -146,7 +111,7 @@ export function useResumeExport(resume: ExportableResume | undefined, exportOpti
toast.dismiss(toastId);
}
},
[exportOptions.publicResumePdf, pdfPresentation, resume],
[exportOptions.publicResumePdf, resume],
);
const onPrint = useCallback(async () => {
@@ -156,7 +121,7 @@ export function useResumeExport(resume: ExportableResume | undefined, exportOpti
try {
const blob = exportOptions.publicResumePdf
? await resolvePublicResumePdfBlob({ data: resume.data, ...exportOptions.publicResumePdf })
: await createResumePdfBlob(resume.data, undefined, undefined, pdfPresentation);
: await createResumePdfBlob(resume.data);
const url = URL.createObjectURL(blob);
// ponytail: print the generated PDF via a hidden iframe (reliable in Chromium). If the browser
// blocks iframe printing, fall back to opening the PDF in a new tab so the user can print manually.
@@ -182,7 +147,7 @@ export function useResumeExport(resume: ExportableResume | undefined, exportOpti
setIsExporting(false);
toast.dismiss(toastId);
}
}, [exportOptions.publicResumePdf, pdfPresentation, resume]);
}, [exportOptions.publicResumePdf, resume]);
return { onDownloadJSON, onDownloadMarkdown, onDownloadDOCX, onDownloadPDF, onPrint, isExporting, hasCoverLetter };
}
@@ -8,14 +8,7 @@ import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
import { ResumePreviewClient } from "./preview.browser";
const previewMock = vi.hoisted(() => ({
builderResumeId: undefined as string | undefined,
builderResumeData: undefined as ResumeData | undefined,
stylesheet: {
resumeId: undefined as string | undefined,
mode: "legacy" as "legacy" | "semantic",
source: { languageVersion: 1, text: "@version 1;\n" },
applied: { languageVersion: 1, text: "@version 1;\n" },
},
toastError: vi.fn(),
toBlob: vi.fn(async () => new Blob(["%PDF"], { type: "application/pdf" })),
}));
@@ -53,22 +46,9 @@ vi.mock("sonner", () => ({
vi.mock("../builder/draft", () => ({
useResumeData: () => previewMock.builderResumeData,
useResumeStore: (selector: (state: { resumeId?: string }) => unknown) =>
selector({ resumeId: previewMock.builderResumeId }),
usePreviewPausedStore: (selector: (state: { paused: boolean }) => unknown) => selector({ paused: false }),
}));
vi.mock("@/features/resume/stylesheet/store", () => ({
useStylesheetStore: (
selector: (state: {
resumeId?: string;
mode: "legacy" | "semantic";
source: { languageVersion: number; text: string };
applied: { languageVersion: number; text: string };
}) => unknown,
) => selector(previewMock.stylesheet),
}));
vi.mock("./pdf-canvas", async () => {
const React = await import("react");
const pdfDocument = { numPages: 1 };
@@ -102,14 +82,7 @@ describe("ResumePreviewClient", () => {
});
beforeEach(() => {
previewMock.builderResumeId = undefined;
previewMock.builderResumeData = undefined;
previewMock.stylesheet = {
resumeId: undefined,
mode: "legacy",
source: { languageVersion: 1, text: "@version 1;\n" },
applied: { languageVersion: 1, text: "@version 1;\n" },
};
previewMock.toBlob.mockReset();
previewMock.toBlob.mockImplementation(async () => new Blob(["%PDF"], { type: "application/pdf" }));
previewMock.toastError.mockReset();
@@ -135,7 +108,7 @@ describe("ResumePreviewClient", () => {
expect(previewMock.toBlob).toHaveBeenCalledTimes(1);
});
expect(previewMock.toBlob).toHaveBeenCalledWith(sampleResumeData, undefined, undefined, undefined);
expect(previewMock.toBlob).toHaveBeenCalledWith(sampleResumeData);
});
it("keeps the rendered template identity on the active layer while its replacement renders", async () => {
@@ -159,47 +132,39 @@ describe("ResumePreviewClient", () => {
expect(activeLayer?.getAttribute("data-resume-preview-template")).toBe("azurill");
});
it("renders the canonical applied stylesheet and ignores invalid editable source", async () => {
const validApplied = { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" };
previewMock.builderResumeId = "resume-1";
previewMock.builderResumeData = sampleResumeData;
previewMock.stylesheet = {
resumeId: "resume-1",
mode: "semantic",
source: { languageVersion: 1, text: "section {" },
applied: validApplied,
it("renders the current stylesheet source from the ordinary builder draft", async () => {
const source = { languageVersion: 1, text: "section {" };
previewMock.builderResumeData = {
...sampleResumeData,
metadata: {
...sampleResumeData.metadata,
stylesheet: { mode: "semantic", source },
},
};
render(<ResumePreviewClient pageLayout="vertical" pageScale={1.25} showPageNumbers={false} />);
await waitFor(() => expect(previewMock.toBlob).toHaveBeenCalledTimes(1));
expect(previewMock.toBlob).toHaveBeenCalledWith(sampleResumeData, undefined, undefined, {
stylesheet: { mode: "semantic", applied: validApplied },
});
expect(previewMock.toBlob).toHaveBeenCalledWith(previewMock.builderResumeData);
expect(previewMock.toBlob).toHaveBeenCalledTimes(1);
});
it("keeps the active PDF visible and reports later semantic render diagnostics", async () => {
previewMock.builderResumeId = "resume-1";
it("keeps the active PDF visible and reports a later renderer failure", async () => {
previewMock.builderResumeData = sampleResumeData;
previewMock.stylesheet = {
resumeId: "resume-1",
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\n" },
applied: { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" },
};
const view = render(<ResumePreviewClient pageLayout="vertical" pageScale={1.25} showPageNumbers={false} />);
expect(await screen.findByRole("img", { name: "Resume page 1 of 1" })).toBeTruthy();
previewMock.toBlob.mockRejectedValueOnce(
new Error("The semantic stylesheet could not be rendered.", {
cause: [{ code: "RESOURCE_LIMIT", severity: "error" }],
}),
);
previewMock.stylesheet.applied = {
languageVersion: 1,
text: "@version 1;\nname { color: #654321; }\n",
previewMock.toBlob.mockRejectedValueOnce(new Error("PDF renderer failed"));
previewMock.builderResumeData = {
...sampleResumeData,
metadata: {
...sampleResumeData.metadata,
stylesheet: {
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\nname { color: #654321; }\n" },
},
},
};
view.rerender(<ResumePreviewClient pageLayout="vertical" pageScale={1.25} showPageNumbers={false} />);
@@ -4,13 +4,12 @@ import type { ResolvedResumePreviewProps } from "./preview.shared";
import type { PreviewPageSize } from "./preview.shared.utils";
import { t } from "@lingui/core/macro";
import { AnimatePresence, m } from "motion/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { isRTL } from "@reactive-resume/utils/locale";
import { cn } from "@reactive-resume/utils/style";
import { createResumePdfBlob } from "@/features/resume/export/pdf-document";
import { useStylesheetStore } from "@/features/resume/stylesheet/store";
import { usePreviewPausedStore, useResumeData, useResumeStore } from "../builder/draft";
import { usePreviewPausedStore, useResumeData } from "../builder/draft";
import { PdfCanvasDocument, PdfCanvasPage } from "./pdf-canvas";
import { ResumePreviewLoader } from "./preview.shared";
import { getResumePreviewGapValue, getResumePreviewPageCount } from "./preview.shared.utils";
@@ -102,17 +101,6 @@ export function ResumePreviewClient({
}: ResolvedResumePreviewProps) {
const builderResumeData = useResumeData();
const resumeData = data ?? builderResumeData;
const builderResumeId = useResumeStore((state) => state.resumeId);
const stylesheetResumeId = useStylesheetStore((state) => state.resumeId);
const mode = useStylesheetStore((state) => state.mode);
const applied = useStylesheetStore((state) => state.applied);
const presentation = useMemo(
() =>
data === undefined && builderResumeId !== undefined && stylesheetResumeId === builderResumeId
? { stylesheet: { mode, applied } }
: undefined,
[applied, builderResumeId, data, mode, stylesheetResumeId],
);
const paused = usePreviewPausedStore((state) => state.paused);
const [previewLayers, setPreviewLayers] = useState<PreviewPdf[]>([]);
@@ -133,7 +121,7 @@ export function ResumePreviewClient({
const generatePdfPreview = async () => {
try {
if (cancelled || requestId !== requestIdRef.current) return;
const blob = await createResumePdfBlob(resumeData, undefined, undefined, presentation);
const blob = await createResumePdfBlob(resumeData);
if (!cancelled && requestId === requestIdRef.current) {
const nextPdf = createPreviewPdf(
@@ -162,7 +150,7 @@ export function ResumePreviewClient({
cancelled = true;
window.clearTimeout(timeoutId);
};
}, [paused, presentation, resumeData]);
}, [paused, resumeData]);
if (!resumeData) return null;
@@ -2,7 +2,6 @@
import { render, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createPublicStyleProjection } from "@reactive-resume/pdf/public-projection";
import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
const pdfViewerMock = vi.hoisted(() => {
@@ -120,67 +119,25 @@ describe("PdfViewer", () => {
expect(pdfViewerMock.loadingTask.destroy).toHaveBeenCalledTimes(1);
});
it("renders a valid public projection through the shared PDF entrypoint", async () => {
it("renders exposed semantic source through the shared PDF entrypoint", async () => {
const semanticData = structuredClone(sampleResumeData);
const applied = { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" };
semanticData.metadata.stylesheet = { mode: "semantic", source: applied, applied };
const projection = await createPublicStyleProjection({ data: semanticData });
const refetchStyleProjection = vi.fn();
semanticData.metadata.stylesheet = {
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" },
};
render(
<PdfViewer
data={sampleResumeData}
stylesheetMode="semantic"
styleProjection={projection}
refetchStyleProjection={refetchStyleProjection}
publicResume={{ username: "amruth", slug: "sample" }}
/>,
);
render(<PdfViewer data={semanticData} publicResume={{ username: "amruth", slug: "sample" }} />);
await waitFor(() =>
expect(pdfViewerMock.createResumePdfBlob).toHaveBeenCalledWith(sampleResumeData, undefined, undefined, {
publicStyleProjection: projection,
}),
);
expect(refetchStyleProjection).not.toHaveBeenCalled();
await waitFor(() => expect(pdfViewerMock.createResumePdfBlob).toHaveBeenCalledWith(semanticData));
expect(pdfViewerMock.fetch).not.toHaveBeenCalled();
});
it("refetches a mismatched projection once before using the authorized PDF fallback", async () => {
const semanticData = structuredClone(sampleResumeData);
const applied = { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" };
semanticData.metadata.stylesheet = { mode: "semantic", source: applied, applied };
const projection = await createPublicStyleProjection({ data: semanticData });
const mismatchedProjection = { ...projection, renderDataHash: "0".repeat(64) };
const refetchStyleProjection = vi.fn(async () => mismatchedProjection);
it("uses the authorized PDF fallback after browser generation rejects", async () => {
pdfViewerMock.createResumePdfBlob.mockRejectedValueOnce(new Error("browser renderer failed"));
const view = render(
<PdfViewer
data={sampleResumeData}
stylesheetMode="semantic"
styleProjection={mismatchedProjection}
refetchStyleProjection={refetchStyleProjection}
publicResume={{ username: "amruth", slug: "sample" }}
/>,
);
render(<PdfViewer data={sampleResumeData} publicResume={{ username: "amruth", slug: "sample" }} />);
await waitFor(() => expect(refetchStyleProjection).toHaveBeenCalledTimes(1));
await waitFor(() => expect(pdfViewerMock.fetch).toHaveBeenCalledTimes(1));
expect(String(pdfViewerMock.fetch.mock.calls[0]?.[0])).toContain("/api/resumes/amruth/sample/pdf");
expect(String(pdfViewerMock.fetch.mock.calls[0]?.[0])).toContain("reason=render-data-hash");
expect(pdfViewerMock.createResumePdfBlob).not.toHaveBeenCalled();
view.rerender(
<PdfViewer
data={sampleResumeData}
stylesheetMode="semantic"
styleProjection={{ ...mismatchedProjection, renderDataHash: "1".repeat(64) }}
refetchStyleProjection={refetchStyleProjection}
publicResume={{ username: "amruth", slug: "sample" }}
/>,
);
await waitFor(() => expect(pdfViewerMock.fetch).toHaveBeenCalledTimes(2));
expect(refetchStyleProjection).toHaveBeenCalledTimes(1);
expect(pdfViewerMock.fetch).toHaveBeenCalledWith("/api/resumes/amruth/sample/pdf", { credentials: "include" });
});
});
@@ -1,6 +1,4 @@
import type { PublicStyleProjection } from "@reactive-resume/pdf/public-projection";
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { SemanticStylesheet } from "@reactive-resume/schema/resume/stylesheet";
import type { PDFDocumentLoadingTask, PDFDocumentProxy } from "pdfjs-dist/legacy/build/pdf.mjs";
import { AnnotationMode, GlobalWorkerOptions, getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
import { EventBus, LinkTarget, PDFLinkService, PDFViewer } from "pdfjs-dist/legacy/web/pdf_viewer.mjs";
@@ -17,9 +15,6 @@ GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/legacy/build/pdf.worker.min.
type PdfViewerProps = {
className?: string;
data: ResumeData;
stylesheetMode?: SemanticStylesheet["mode"];
styleProjection?: PublicStyleProjection;
refetchStyleProjection?: () => Promise<PublicStyleProjection | undefined>;
publicResume?: {
username: string;
slug: string;
@@ -77,21 +72,11 @@ function pdfViewerReducer(state: PdfViewerState, action: PdfViewerAction): PdfVi
}
}
export function PdfViewer({
className,
data,
stylesheetMode,
styleProjection,
refetchStyleProjection,
publicResume,
}: PdfViewerProps) {
export function PdfViewer({ className, data, publicResume }: PdfViewerProps) {
const rootRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const viewerRef = useRef<HTMLDivElement>(null);
const fileRef = useRef<Blob | null>(null);
const projectionRetryRef = useRef<{ data?: ResumeData; publicKey?: string; retried: boolean }>({
retried: false,
});
const [{ error, fileVersion, isReady, viewerHeight }, dispatch] = useReducer(
pdfViewerReducer,
INITIAL_PDF_VIEWER_STATE,
@@ -103,27 +88,8 @@ export function PdfViewer({
fileRef.current = null;
dispatch({ type: "resetForData" });
const createPdf = () => {
if (!stylesheetMode || !publicResume) return createResumePdfBlob(data);
const publicKey = `${publicResume.username}/${publicResume.slug}`;
if (projectionRetryRef.current.data !== data || projectionRetryRef.current.publicKey !== publicKey) {
projectionRetryRef.current = { data, publicKey, retried: false };
}
const retryProjection =
refetchStyleProjection && !projectionRetryRef.current.retried
? () => {
projectionRetryRef.current.retried = true;
return refetchStyleProjection();
}
: undefined;
return resolvePublicResumePdfBlob({
data,
stylesheetMode,
publicResume,
...(styleProjection ? { styleProjection } : {}),
...(retryProjection ? { refetchStyleProjection: retryProjection } : {}),
});
};
const createPdf = () =>
publicResume ? resolvePublicResumePdfBlob({ data, publicResume }) : createResumePdfBlob(data);
void createPdf()
.then((blob) => {
@@ -142,7 +108,7 @@ export function PdfViewer({
return () => {
isCancelled = true;
};
}, [data, publicResume, refetchStyleProjection, styleProjection, stylesheetMode]);
}, [data, publicResume]);
useEffect(() => {
void fileVersion;
@@ -1,5 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createPublicStyleProjection } from "@reactive-resume/pdf/public-projection";
import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
import { resolvePublicResumePdfBlob } from "./public-pdf";
@@ -15,58 +14,35 @@ vi.mock("@/features/resume/export/pdf-document", () => ({
const publicResume = { username: "amruth", slug: "sample" };
beforeEach(() => {
mocks.createResumePdfBlob.mockClear();
mocks.fetch.mockClear();
mocks.createResumePdfBlob.mockReset();
mocks.createResumePdfBlob.mockResolvedValue(new Blob(["local"], { type: "application/pdf" }));
mocks.fetch.mockReset();
mocks.fetch.mockResolvedValue(new Response(new Blob(["server"], { type: "application/pdf" })));
vi.stubGlobal("fetch", mocks.fetch);
});
describe("resolvePublicResumePdfBlob", () => {
it("keeps legitimate legacy resumes on the local PDF path", async () => {
await resolvePublicResumePdfBlob({
data: sampleResumeData,
stylesheetMode: "legacy",
publicResume,
});
it("renders the exposed stylesheet source directly in the browser", async () => {
const data = structuredClone(sampleResumeData);
data.metadata.stylesheet = {
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" },
};
const blob = await resolvePublicResumePdfBlob({ data, publicResume });
expect(mocks.createResumePdfBlob).toHaveBeenCalledWith(data);
expect(mocks.fetch).not.toHaveBeenCalled();
expect(await blob.text()).toBe("local");
});
it("fetches the server PDF only after browser rendering rejects", async () => {
mocks.createResumePdfBlob.mockRejectedValue(new Error("browser renderer failed"));
const blob = await resolvePublicResumePdfBlob({ data: sampleResumeData, publicResume });
expect(mocks.createResumePdfBlob).toHaveBeenCalledWith(sampleResumeData);
expect(mocks.fetch).not.toHaveBeenCalled();
});
it("uses the authorized server fallback when a semantic projection is unavailable", async () => {
const refetchStyleProjection = vi.fn().mockRejectedValue(new Error("projection unavailable"));
await resolvePublicResumePdfBlob({
data: sampleResumeData,
stylesheetMode: "semantic",
publicResume,
refetchStyleProjection,
});
expect(refetchStyleProjection).toHaveBeenCalledTimes(1);
expect(String(mocks.fetch.mock.calls[0]?.[0])).toContain("/api/resumes/amruth/sample/pdf");
expect(String(mocks.fetch.mock.calls[0]?.[0])).toContain("reason=missing-projection");
expect(mocks.createResumePdfBlob).not.toHaveBeenCalled();
});
it("refetches a mismatched projection once before returning the authorized server blob", async () => {
const semanticData = structuredClone(sampleResumeData);
const source = { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" };
semanticData.metadata.stylesheet = { mode: "semantic", source, applied: source };
const projection = await createPublicStyleProjection({ data: semanticData });
const mismatchedProjection = { ...projection, renderDataHash: "0".repeat(64) };
const refetchStyleProjection = vi.fn(async () => mismatchedProjection);
const blob = await resolvePublicResumePdfBlob({
data: sampleResumeData,
stylesheetMode: "semantic",
styleProjection: mismatchedProjection,
publicResume,
refetchStyleProjection,
});
expect(refetchStyleProjection).toHaveBeenCalledTimes(1);
expect(mocks.fetch).toHaveBeenCalledTimes(1);
expect(mocks.fetch).toHaveBeenCalledWith("/api/resumes/amruth/sample/pdf", { credentials: "include" });
expect(await blob.text()).toBe("server");
expect(mocks.createResumePdfBlob).not.toHaveBeenCalled();
});
});
@@ -1,97 +1,28 @@
import type { PublicStyleProjection } from "@reactive-resume/pdf/public-projection";
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { SemanticStylesheet } from "@reactive-resume/schema/resume/stylesheet";
import {
getPublicStyleProjectionFingerprints,
PUBLIC_STYLE_PROJECTION_FORMAT_VERSION,
SEMANTIC_TREE_VERSION,
validatePublicStyleProjection,
} from "@reactive-resume/pdf/public-projection";
import { createResumePdfBlob } from "@/features/resume/export/pdf-document";
export type PublicResumePdfOptions = {
stylesheetMode: SemanticStylesheet["mode"];
styleProjection?: PublicStyleProjection;
refetchStyleProjection?: () => Promise<PublicStyleProjection | undefined>;
publicResume: {
username: string;
slug: string;
};
};
type ProjectionMismatchReason =
| "format-version"
| "language-version"
| "semantic-tree-version"
| "registry-fingerprint"
| "adapter-fingerprint"
| "render-data-hash"
| "invalid-projection";
const projectionMismatchReason = async (
data: ResumeData,
projection: PublicStyleProjection,
): Promise<ProjectionMismatchReason | null> => {
if (projection.formatVersion !== PUBLIC_STYLE_PROJECTION_FORMAT_VERSION) return "format-version";
if (projection.languageVersion !== 1) return "language-version";
if (projection.semanticTreeVersion !== SEMANTIC_TREE_VERSION) return "semantic-tree-version";
const fingerprints = await getPublicStyleProjectionFingerprints();
if (projection.registryFingerprint !== fingerprints.registryFingerprint) return "registry-fingerprint";
if (projection.adapterFingerprint !== fingerprints.adapterFingerprint) return "adapter-fingerprint";
return (await validatePublicStyleProjection(data, projection)) ? null : "render-data-hash";
};
const fetchPublicResumePdf = async (
publicResume: PublicResumePdfOptions["publicResume"],
reason: ProjectionMismatchReason | "missing-projection",
projection?: PublicStyleProjection,
) => {
const search = new URLSearchParams({
reason,
...(projection
? {
registryFingerprint: projection.registryFingerprint,
adapterFingerprint: projection.adapterFingerprint,
}
: {}),
const fetchPublicResumePdf = async ({ username, slug }: PublicResumePdfOptions["publicResume"]) => {
const response = await fetch(`/api/resumes/${encodeURIComponent(username)}/${encodeURIComponent(slug)}/pdf`, {
credentials: "include",
});
const response = await fetch(
`/api/resumes/${encodeURIComponent(publicResume.username)}/${encodeURIComponent(publicResume.slug)}/pdf?${search}`,
{ credentials: "include" },
);
if (!response.ok) throw new Error(`Public PDF fallback failed with ${response.status}`);
return response.blob();
};
export async function resolvePublicResumePdfBlob({
data,
...options
publicResume,
}: PublicResumePdfOptions & { data: ResumeData }): Promise<Blob> {
if (options.stylesheetMode === "legacy") return createResumePdfBlob(data);
let projection = options.styleProjection;
let refetched = false;
const refetch = async () => {
if (!options.refetchStyleProjection || refetched) return;
refetched = true;
try {
projection = await options.refetchStyleProjection();
} catch {
projection = undefined;
}
};
if (!projection) await refetch();
if (!projection) return fetchPublicResumePdf(options.publicResume, "missing-projection");
let reason = await projectionMismatchReason(data, projection).catch(() => "invalid-projection" as const);
if (reason) {
await refetch();
if (!projection) return fetchPublicResumePdf(options.publicResume, "missing-projection");
reason = await projectionMismatchReason(data, projection).catch(() => "invalid-projection" as const);
try {
return await createResumePdfBlob(data);
} catch {
return fetchPublicResumePdf(publicResume);
}
return reason
? fetchPublicResumePdf(options.publicResume, reason, projection)
: createResumePdfBlob(data, undefined, undefined, { publicStyleProjection: projection });
}
@@ -1,6 +1,5 @@
// @vitest-environment happy-dom
import type { PublicStyleProjection } from "@reactive-resume/pdf/public-projection";
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { ReactNode } from "react";
import { render, screen } from "@testing-library/react";
@@ -12,30 +11,12 @@ import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
type PdfViewerProps = {
className?: string;
data: ResumeData;
stylesheetMode?: "legacy" | "semantic";
styleProjection?: PublicStyleProjection;
refetchStyleProjection?: () => Promise<PublicStyleProjection | undefined>;
publicResume?: { username: string; slug: string };
};
const publicResumeMock = vi.hoisted(() => ({
onDownloadPDF: vi.fn(),
PdfViewer: vi.fn<(_props: PdfViewerProps) => ReactNode>(() => null),
projection: {
formatVersion: 1,
languageVersion: 1,
semanticTreeVersion: 1,
registryFingerprint: "1".repeat(64),
adapterFingerprint: "2".repeat(64),
renderDataHash: "3".repeat(64),
nodes: { resume: {} },
} as PublicStyleProjection,
refetchProjection: vi.fn(),
projectionResult: {
data: undefined as PublicStyleProjection | undefined,
isError: false,
isPending: false,
},
useResumeExport: vi.fn(),
resume: undefined as
| undefined
@@ -43,61 +24,28 @@ const publicResumeMock = vi.hoisted(() => ({
data: ResumeData;
name: string;
slug: string;
stylesheetMode: "legacy" | "semantic";
},
}));
vi.mock("@tanstack/react-query", () => ({
useQuery: (options: { query: "resume" | "projection" }) =>
options.query === "resume"
? { data: publicResumeMock.resume }
: { ...publicResumeMock.projectionResult, refetch: publicResumeMock.refetchProjection },
}));
vi.mock("@tanstack/react-query", () => ({ useQuery: () => ({ data: publicResumeMock.resume }) }));
vi.mock("@tanstack/react-router", () => ({
getRouteApi: () => ({
useParams: () => ({ username: "amruth", slug: "sample" }),
}),
getRouteApi: () => ({ useParams: () => ({ username: "amruth", slug: "sample" }) }),
}));
vi.mock("./pdf-viewer", () => ({
PdfViewer: publicResumeMock.PdfViewer,
}));
vi.mock("./pdf-viewer", () => ({ PdfViewer: publicResumeMock.PdfViewer }));
vi.mock("@/libs/orpc/client", () => ({
orpc: {
resume: {
getBySlug: { queryOptions: () => ({ query: "resume" }) },
getStyleProjection: { queryOptions: () => ({ query: "projection" }) },
},
},
orpc: { resume: { getBySlug: { queryOptions: () => ({ query: "resume" }) } } },
}));
vi.mock("@/features/resume/export/use-resume-export", () => ({
useResumeExport: publicResumeMock.useResumeExport,
}));
const { PublicResumeRoute } = await import("./public-resume");
beforeAll(() => {
i18n.loadAndActivate({ locale: "en", messages: {} });
});
beforeAll(() => i18n.loadAndActivate({ locale: "en", messages: {} }));
beforeEach(() => {
publicResumeMock.resume = {
data: sampleResumeData,
name: "Sample Resume",
slug: "sample",
stylesheetMode: "semantic",
};
publicResumeMock.projectionResult = {
data: publicResumeMock.projection,
isError: false,
isPending: false,
};
publicResumeMock.resume = { data: sampleResumeData, name: "Sample Resume", slug: "sample" };
publicResumeMock.PdfViewer.mockClear();
publicResumeMock.refetchProjection.mockReset();
publicResumeMock.refetchProjection.mockResolvedValue({ data: publicResumeMock.projection });
publicResumeMock.useResumeExport.mockReset();
publicResumeMock.useResumeExport.mockReturnValue({
onDownloadPDF: publicResumeMock.onDownloadPDF,
@@ -116,78 +64,26 @@ const renderPublicResumeRoute = () =>
);
describe("PublicResumeRoute", () => {
it("renders the public resume through the route-local PDF.js viewer", () => {
it("passes exposed source data directly to the browser viewer and export fallback", () => {
renderPublicResumeRoute();
expect(screen.getByTestId("pdf-viewer")).toHaveClass("block", "w-full");
expect(publicResumeMock.PdfViewer).toHaveBeenCalledWith(
expect.objectContaining({ data: sampleResumeData }),
expect.objectContaining({
data: sampleResumeData,
publicResume: { username: "amruth", slug: "sample" },
}),
undefined,
);
expect(publicResumeMock.useResumeExport).toHaveBeenCalledWith(publicResumeMock.resume, {
publicResumePdf: expect.objectContaining({
stylesheetMode: "semantic",
styleProjection: publicResumeMock.projection,
publicResume: { username: "amruth", slug: "sample" },
}),
publicResumePdf: { publicResume: { username: "amruth", slug: "sample" } },
});
});
it("routes a missing semantic projection to the shared fallback seam", () => {
publicResumeMock.projectionResult = { data: undefined, isError: true, isPending: false };
renderPublicResumeRoute();
expect(publicResumeMock.PdfViewer).toHaveBeenCalledWith(
expect.objectContaining({
stylesheetMode: "semantic",
styleProjection: undefined,
refetchStyleProjection: expect.any(Function),
publicResume: { username: "amruth", slug: "sample" },
}),
undefined,
);
expect(publicResumeMock.useResumeExport).toHaveBeenCalledWith(
publicResumeMock.resume,
expect.objectContaining({
publicResumePdf: expect.objectContaining({
stylesheetMode: "semantic",
}),
}),
);
});
it("keeps missing legacy projections on the local rendering path", () => {
if (publicResumeMock.resume) publicResumeMock.resume.stylesheetMode = "legacy";
publicResumeMock.projectionResult = { data: undefined, isError: false, isPending: false };
renderPublicResumeRoute();
expect(publicResumeMock.PdfViewer).toHaveBeenCalledWith(
expect.objectContaining({ stylesheetMode: "legacy", styleProjection: undefined }),
undefined,
);
});
it("loads the public projection and passes it to the shared viewer", () => {
renderPublicResumeRoute();
expect(publicResumeMock.PdfViewer).toHaveBeenCalledWith(
expect.objectContaining({
styleProjection: publicResumeMock.projection,
refetchStyleProjection: expect.any(Function),
publicResume: { username: "amruth", slug: "sample" },
}),
undefined,
);
});
it("lets the public resume page grow to the full PDF length", () => {
renderPublicResumeRoute();
const viewerFrame = screen.getByTestId("pdf-viewer").parentElement;
const page = viewerFrame?.parentElement;
expect(page).not.toHaveClass("min-h-svh", "h-svh", "max-h-svh", "overflow-hidden");
expect(viewerFrame).not.toHaveClass("min-h-0", "flex-1", "overflow-hidden");
});
@@ -3,7 +3,7 @@ import { Trans } from "@lingui/react/macro";
import { CircleNotchIcon, DownloadSimpleIcon } from "@phosphor-icons/react";
import { useQuery } from "@tanstack/react-query";
import { getRouteApi } from "@tanstack/react-router";
import { useCallback, useMemo } from "react";
import { useMemo } from "react";
import { BrandIcon } from "@reactive-resume/ui/components/brand-icon";
import { Button } from "@reactive-resume/ui/components/button";
import { LoadingScreen } from "@/components/layout/loading-screen";
@@ -17,30 +17,12 @@ export function PublicResumeRoute() {
const { username, slug } = publicResumeRoute.useParams();
const { data: resume } = useQuery(orpc.resume.getBySlug.queryOptions({ input: { username, slug } }));
const projectionQuery = useQuery(
orpc.resume.getStyleProjection.queryOptions({ input: { username, slug }, enabled: resume !== undefined }),
);
const styleProjection =
projectionQuery.data && Object.keys(projectionQuery.data.nodes).length > 0 ? projectionQuery.data : undefined;
const publicResume = useMemo(() => ({ username, slug }), [slug, username]);
const refetchStyleProjection = useCallback(async () => {
const result = await projectionQuery.refetch();
return result.data;
}, [projectionQuery.refetch]);
const { onDownloadPDF, isExporting } = useResumeExport(resume, {
...(resume
? {
publicResumePdf: {
stylesheetMode: resume.stylesheetMode,
publicResume,
refetchStyleProjection,
...(styleProjection ? { styleProjection } : {}),
},
}
: {}),
...(resume ? { publicResumePdf: { publicResume } } : {}),
});
if (!resume || projectionQuery.isPending) return <LoadingScreen />;
if (!resume) return <LoadingScreen />;
const { basics, picture } = resume.data;
@@ -66,14 +48,7 @@ export function PublicResumeRoute() {
</header>
<main className="w-full max-w-5xl bg-white print:max-w-full">
<PdfViewer
data={resume.data}
className="block w-full"
stylesheetMode={resume.stylesheetMode}
styleProjection={styleProjection}
publicResume={publicResume}
refetchStyleProjection={refetchStyleProjection}
/>
<PdfViewer data={resume.data} className="block w-full" publicResume={publicResume} />
</main>
<footer className="flex justify-center print:hidden">
@@ -1,18 +1,30 @@
// @vitest-environment happy-dom
import type { SemanticCssDiagnostic } from "@reactive-resume/resume/stylesheet";
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { SemanticCssDiagnostic, StyleProgram } from "@reactive-resume/resume/stylesheet";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { EditorView } from "@codemirror/view";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { TooltipProvider } from "@reactive-resume/ui/components/tooltip";
import StylesheetEditorShell, { StylesheetCodeEditor } from "./editor";
import { LegacyStylesheetBanner } from "./legacy-banner";
import { StylesheetStatus } from "./status";
import { useStylesheetStore } from "./store";
const media = vi.hoisted(() => ({ mobile: false }));
const compileWorker = vi.hoisted(() => ({
program: { languageVersion: 1, rules: [] } as StyleProgram | null,
diagnostics: [] as SemanticCssDiagnostic[],
}));
const builder = vi.hoisted(() => ({
canUndo: false,
canRedo: false,
data: undefined as typeof defaultResumeData | undefined,
isLocked: false,
undo: vi.fn(),
redo: vi.fn(),
}));
vi.mock("usehooks-ts", async (importOriginal) => ({
...(await importOriginal<typeof import("usehooks-ts")>()),
@@ -23,16 +35,52 @@ vi.mock("@/features/theme/provider", () => ({
useTheme: () => ({ theme: "light" }),
}));
const error: SemanticCssDiagnostic = {
code: "SEMANTIC_CSS_UNKNOWN_PROPERTY",
vi.mock("@/features/resume/builder/draft", () => ({
useResumeData: () => builder.data,
useIsResumeLocked: () => builder.isLocked,
useUpdateResumeData: () => (update: (draft: typeof defaultResumeData) => void) => {
if (builder.data) update(builder.data);
},
useResumeStore: (selector: (state: object) => unknown) =>
selector({ canUndo: builder.canUndo, canRedo: builder.canRedo, undo: builder.undo, redo: builder.redo }),
}));
vi.mock("./worker-client", () => ({
createCompileWorkerClient: () => ({
compile: vi.fn(async ({ editGeneration }: { editGeneration: number }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: compileWorker.program,
diagnostics: compileWorker.diagnostics,
colorTokens: [],
})),
destroy: vi.fn(),
}),
}));
const recoverableError: SemanticCssDiagnostic = {
code: "INVALID_VALUE",
severity: "error",
message: "Unknown property",
message: "Invalid value",
range: {
start: { line: 2, column: 3, offset: 17 },
end: { line: 2, column: 9, offset: 23 },
},
};
const fatalError: SemanticCssDiagnostic = {
...recoverableError,
code: "VERSION_MISMATCH",
message: "Version mismatch",
};
const resolutionError: SemanticCssDiagnostic = {
...recoverableError,
code: "UNRESOLVED_VARIABLE",
message: "Undefined variable --missing",
};
const guideName = /read the applying custom styles guide.*opens in new tab/i;
const expectGuideLink = (root: HTMLElement) => {
@@ -47,28 +95,48 @@ beforeAll(() => {
Object.defineProperty(Element.prototype, "getAnimations", { configurable: true, value: () => [] });
});
beforeEach(() => {
media.mobile = false;
builder.data = structuredClone(defaultResumeData);
builder.isLocked = false;
builder.canUndo = false;
builder.canRedo = false;
builder.undo.mockReset();
builder.redo.mockReset();
compileWorker.program = { languageVersion: 1, rules: [] };
compileWorker.diagnostics = [];
});
const renderWithI18n = (element: React.ReactNode) => render(<I18nProvider i18n={i18n}>{element}</I18nProvider>);
describe("stylesheet editor status", () => {
it("shows that invalid source keeps the last valid preview", () => {
renderWithI18n(<StylesheetStatus mode="semantic" status="error" diagnostics={[error]} />);
it("explains that fatal source falls back to base styles", () => {
renderWithI18n(<StylesheetStatus mode="semantic" status="idle" diagnostics={[fatalError]} />);
expect(screen.getByText(/preview and export use the last valid version/i)).toBeInTheDocument();
expect(screen.getByText("Unknown property")).toBeInTheDocument();
expect(screen.getByText(/preview and export fall back to base styles/i)).toBeInTheDocument();
expect(screen.getByText("Version mismatch")).toBeInTheDocument();
});
it("explains that recoverable errors preserve valid styles", () => {
renderWithI18n(<StylesheetStatus mode="semantic" status="idle" diagnostics={[recoverableError]} />);
expect(screen.getByText("Valid with errors")).toBeInTheDocument();
expect(screen.getByText(/preview and export keep valid styles/i)).toBeInTheDocument();
expect(screen.queryByText(/fall back to base styles/i)).not.toBeInTheDocument();
});
it("labels a valid legacy draft as ready to activate", () => {
renderWithI18n(<StylesheetStatus mode="legacy" status="idle" diagnostics={[]} />);
expect(screen.getByText("Ready to activate")).toBeInTheDocument();
expect(screen.queryByText("Applied")).not.toBeInTheDocument();
});
it("labels legacy warnings without claiming they are applied", () => {
renderWithI18n(<StylesheetStatus mode="legacy" status="idle" diagnostics={[{ ...error, severity: "warning" }]} />);
it("labels a legacy draft with warnings as ready to activate", () => {
renderWithI18n(
<StylesheetStatus mode="legacy" status="idle" diagnostics={[{ ...recoverableError, severity: "warning" }]} />,
);
expect(screen.getByText("Ready to activate with warnings")).toBeInTheDocument();
expect(screen.queryByText("Applied with warnings")).not.toBeInTheDocument();
});
it("disables activation while the converted draft has errors", () => {
@@ -113,7 +181,7 @@ describe("StylesheetCodeEditor", () => {
<StylesheetCodeEditor
value={"@version 1;\nsection { color: red; }\n"}
{...props}
diagnostics={[error]}
diagnostics={[recoverableError]}
theme="dark"
readOnly
/>
@@ -173,18 +241,105 @@ describe("StylesheetEditorShell", () => {
expectGuideLink(container);
});
it("makes the editor and mutation controls read-only while a restore is pending", () => {
media.mobile = false;
useStylesheetStore.setState({
mode: "legacy",
it("has no apply or save action for an already-semantic stylesheet", async () => {
if (!builder.data) throw new Error("Missing resume fixture");
builder.data.metadata.stylesheet = {
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\n" },
};
render(
<I18nProvider i18n={i18n}>
<TooltipProvider>
<StylesheetEditorShell />
</TooltipProvider>
</I18nProvider>,
);
await screen.findByText("Valid");
expect(screen.queryByRole("button", { name: /activate semantic css/i })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /save|apply/i })).not.toBeInTheDocument();
});
it("writes semantic edits into the ordinary resume draft immediately", () => {
if (!builder.data) throw new Error("Missing resume fixture");
builder.data.metadata.stylesheet = {
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\n" },
};
render(
<I18nProvider i18n={i18n}>
<TooltipProvider>
<StylesheetEditorShell />
</TooltipProvider>
</I18nProvider>,
);
const textbox = screen.getByRole("textbox", { name: "Semantic CSS stylesheet" });
const view = EditorView.findFromDOM(textbox);
if (!view) throw new Error("Missing editor view");
act(() => view.dispatch({ changes: { from: view.state.doc.length, insert: "name { color: blue; }\n" } }));
expect(builder.data.metadata.stylesheet.source.text).toBe("@version 1;\nname { color: blue; }\n");
});
it("switches a converted legacy draft through the ordinary resume update", async () => {
render(
<I18nProvider i18n={i18n}>
<TooltipProvider>
<StylesheetEditorShell />
</TooltipProvider>
</I18nProvider>,
);
const activate = await screen.findByRole("button", { name: "Activate Semantic CSS" });
await waitFor(() => expect(activate).toBeEnabled());
fireEvent.click(activate);
expect(builder.data?.metadata.stylesheet).toEqual({
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\n" },
applied: { languageVersion: 1, text: "@version 1;\n" },
diagnostics: [],
status: "idle",
canUndo: true,
canRedo: true,
restoreLocked: true,
});
});
it("allows a converted legacy draft with recoverable errors to activate", async () => {
compileWorker.diagnostics = [resolutionError];
render(
<I18nProvider i18n={i18n}>
<TooltipProvider>
<StylesheetEditorShell />
</TooltipProvider>
</I18nProvider>,
);
const activate = await screen.findByRole("button", { name: "Activate Semantic CSS" });
await waitFor(() => expect(activate).toBeEnabled());
fireEvent.click(activate);
expect(builder.data?.metadata.stylesheet?.mode).toBe("semantic");
});
it("keeps legacy activation disabled for fatal diagnostics", async () => {
compileWorker.diagnostics = [fatalError];
render(
<I18nProvider i18n={i18n}>
<TooltipProvider>
<StylesheetEditorShell />
</TooltipProvider>
</I18nProvider>,
);
await screen.findByText("Fatal error");
expect(screen.getByRole("button", { name: "Activate Semantic CSS" })).toBeDisabled();
});
it("makes editor mutation controls read-only while the resume is locked", () => {
builder.isLocked = true;
builder.canUndo = true;
builder.canRedo = true;
render(
<I18nProvider i18n={i18n}>
@@ -202,19 +357,10 @@ describe("StylesheetEditorShell", () => {
expect(screen.getByRole("button", { name: "Undo stylesheet edit" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Redo stylesheet edit" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Format stylesheet" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Reset to applied stylesheet" })).toBeDisabled();
});
it("moves the only visible editor into a titled mobile sheet", async () => {
media.mobile = true;
useStylesheetStore.setState({
mode: "legacy",
source: { languageVersion: 1, text: "@version 1;\n" },
applied: { languageVersion: 1, text: "@version 1;\n" },
diagnostics: [{ ...error, severity: "warning" }],
status: "idle",
restoreLocked: false,
});
const { container } = render(
<I18nProvider i18n={i18n}>
@@ -231,8 +377,7 @@ describe("StylesheetEditorShell", () => {
expect(within(sheet).getByRole("heading", { name: "Semantic CSS stylesheet" })).toBeInTheDocument();
expect(within(sheet).getByRole("button", { name: "Activate Semantic CSS" })).toBeInTheDocument();
expect(within(sheet).getByRole("toolbar", { name: "Stylesheet editor" })).toBeInTheDocument();
expect(within(sheet).getByText("Ready to activate with warnings")).toBeInTheDocument();
expect(within(sheet).getByText("Unknown property")).toBeInTheDocument();
await within(sheet).findByText("Ready to activate");
expectGuideLink(sheet);
expect(document.querySelectorAll(".cm-editor")).toHaveLength(1);
media.mobile = false;
@@ -1,5 +1,7 @@
import type { Extension } from "@codemirror/state";
import type { SemanticCssDiagnostic } from "@reactive-resume/resume/stylesheet";
import type { SemanticCssDiagnostic, SemanticNode } from "@reactive-resume/resume/stylesheet";
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { StylesheetSource } from "@reactive-resume/schema/resume/stylesheet";
import type { SemanticCssColorToken } from "./color-tokens";
import type { SemanticCssEditorMetadata } from "./protocol";
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
@@ -17,11 +19,20 @@ import {
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { BookOpenIcon } from "@phosphor-icons/react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useMediaQuery } from "usehooks-ts";
import { convertLegacyStyleRules } from "@reactive-resume/pdf/semantic-legacy";
import {
buildSemanticTree,
getTemplateSemanticManifest,
semanticNodeKeys,
shouldShowResumeHeader,
} from "@reactive-resume/pdf/semantic-tree";
import { isFatalStylesheetDiagnostic } from "@reactive-resume/resume/stylesheet";
import { PopoverTrigger } from "@reactive-resume/ui/components/popover";
import { Sheet, SheetContent, SheetTitle } from "@reactive-resume/ui/components/sheet";
import { ColorPicker } from "@/components/input/color-picker";
import { useIsResumeLocked, useResumeData, useResumeStore, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useTheme } from "@/features/theme/provider";
import { useBuilderSidebarStore } from "@/routes/builder/$resumeId/-store/sidebar";
import { compositionAwareDocumentListener, createSemanticCssEditorExtensions } from "./editor-extensions";
@@ -29,8 +40,8 @@ import { enterStylesheetFocusMode } from "./focus-mode";
import { formatEditorDocument } from "./formatter";
import { LegacyStylesheetBanner } from "./legacy-banner";
import { StylesheetStatus } from "./status";
import { useStylesheetStore } from "./store";
import { StylesheetToolbar } from "./toolbar";
import { createCompileWorkerClient } from "./worker-client";
const externalReplacement = Annotation.define<boolean>();
const emptyMetadata: SemanticCssEditorMetadata = {
@@ -314,30 +325,78 @@ type StylesheetEditorShellProps = {
readOnly?: boolean;
};
const pageDimensions = (data: ResumeData) => {
const size = data.metadata.page.format === "letter" ? { width: 612, height: 792 } : { width: 595.28, height: 841.89 };
return data.metadata.layout.pages.map((_page, index) => ({
pageKey: semanticNodeKeys.page(index + 1),
...size,
}));
};
const createEditorMetadata = (data: ResumeData): SemanticCssEditorMetadata => {
const pages = data.metadata.layout.pages.map((page, index) =>
buildSemanticTree({
data,
template: data.metadata.template,
page,
pageNumber: index + 1,
showHeader: shouldShowResumeHeader(data, index),
}),
);
const semanticTree: SemanticNode = {
key: semanticNodeKeys.resume(),
kind: "resume",
attributes: { template: data.metadata.template },
roles: [],
children: pages.flatMap(({ children }) => children),
};
return {
semanticTree,
templateParts: getTemplateSemanticManifest(data.metadata.template).parts.map(({ name }) => name),
};
};
function StylesheetEditorShell({ readOnly = false }: StylesheetEditorShellProps) {
const { theme } = useTheme();
const isMobile = useMediaQuery("(max-width: 767px)", { initializeWithValue: false });
const [focusOpen, setFocusOpen] = useState(false);
const [diagnostics, setDiagnostics] = useState<readonly SemanticCssDiagnostic[]>([]);
const [colorTokens, setColorTokens] = useState<readonly SemanticCssColorToken[]>([]);
const [status, setStatus] = useState<"idle" | "compiling" | "error">("compiling");
const [compiler, setCompiler] = useState<ReturnType<typeof createCompileWorkerClient>>();
const restoreDesktopRef = useRef<(() => void) | null>(null);
const mode = useStylesheetStore((state) => state.mode);
const source = useStylesheetStore((state) => state.source.text);
const applied = useStylesheetStore((state) => state.applied.text);
const diagnostics = useStylesheetStore((state) => state.diagnostics);
const colorTokens = useStylesheetStore((state) => state.colorTokens);
const metadata = useStylesheetStore((state) => state.editorMetadata);
const status = useStylesheetStore((state) => state.status);
const restoreLocked = useStylesheetStore((state) => state.restoreLocked);
const canUndo = useStylesheetStore((state) => state.canUndo);
const canRedo = useStylesheetStore((state) => state.canRedo);
const setSourceText = useStylesheetStore((state) => state.setSourceText);
const setFocused = useStylesheetStore((state) => state.setFocused);
const activate = useStylesheetStore((state) => state.activate);
const undo = useStylesheetStore((state) => state.undo);
const redo = useStylesheetStore((state) => state.redo);
const refreshIntelligence = useStylesheetStore((state) => state.refreshIntelligence);
const data = useResumeData();
const updateResumeData = useUpdateResumeData();
const isLocked = useIsResumeLocked();
const canUndo = useResumeStore((state) => state.canUndo);
const canRedo = useResumeStore((state) => state.canRedo);
const undo = useResumeStore((state) => state.undo);
const redo = useResumeStore((state) => state.redo);
const editorViewRef = useRef<EditorView | null>(null);
const hasErrors = status === "error" || diagnostics.some(({ severity }) => severity === "error");
const isChecking = status === "compiling" || status === "preflighting" || status === "saving";
const compileGenerationRef = useRef(0);
useEffect(() => {
const client = createCompileWorkerClient(
() =>
new Worker(new URL("./stylesheet.worker.ts", import.meta.url), {
type: "module",
name: "semantic-css-compiler",
}),
);
setCompiler(client);
return () => client.destroy();
}, []);
const stylesheet = data?.metadata.stylesheet;
const mode = stylesheet?.mode ?? "legacy";
const source = useMemo<StylesheetSource>(
() =>
stylesheet?.source ??
(data ? convertLegacyStyleRules(data).source : { languageVersion: 1, text: "@version 1;\n" }),
[data, stylesheet],
);
const metadata = useMemo(() => (data ? createEditorMetadata(data) : emptyMetadata), [data]);
const hasFatalErrors = status === "error" || diagnostics.some(isFatalStylesheetDiagnostic);
const isChecking = status === "compiling";
const disabled = readOnly || isLocked;
useEffect(
() => () => {
@@ -347,8 +406,59 @@ function StylesheetEditorShell({ readOnly = false }: StylesheetEditorShellProps)
);
useEffect(() => {
refreshIntelligence();
}, [refreshIntelligence]);
if (!compiler || !data) return;
let cancelled = false;
const editGeneration = ++compileGenerationRef.current;
setStatus("compiling");
setColorTokens([]);
const timer = window.setTimeout(() => {
void compiler
.compile({
editGeneration,
source,
semanticTree: metadata.semanticTree,
baseSettings: {
picture: data.picture,
template: data.metadata.template,
design: data.metadata.design,
typography: data.metadata.typography,
page: data.metadata.page,
layout: { sidebarWidth: data.metadata.layout.sidebarWidth },
},
pages: pageDimensions(data),
})
.then((result) => {
if (cancelled || result.editGeneration !== compileGenerationRef.current) return;
setDiagnostics(result.diagnostics);
setColorTokens(result.colorTokens ?? []);
setStatus(result.program && !result.diagnostics.some(isFatalStylesheetDiagnostic) ? "idle" : "error");
})
.catch(() => {
if (!cancelled && editGeneration === compileGenerationRef.current) setStatus("error");
});
}, 180);
return () => {
cancelled = true;
window.clearTimeout(timer);
};
}, [compiler, data, metadata, source]);
if (!data) return null;
const setSourceText = (text: string) => {
if (disabled || text === source.text) return;
updateResumeData((draft) => {
draft.metadata.stylesheet = { mode, source: { ...source, text } };
});
};
const activate = () => {
if (disabled || mode === "semantic" || hasFatalErrors || isChecking) return;
updateResumeData((draft) => {
draft.metadata.stylesheet = { mode: "semantic", source };
});
};
const toggleFocus = () => {
if (isMobile) {
@@ -374,15 +484,14 @@ function StylesheetEditorShell({ readOnly = false }: StylesheetEditorShellProps)
const editor = (
<StylesheetCodeEditor
value={source}
value={source.text}
diagnostics={diagnostics}
colorTokens={colorTokens}
metadata={metadata}
theme={theme}
readOnly={readOnly || restoreLocked}
readOnly={disabled}
label={t`Semantic CSS stylesheet`}
onChange={setSourceText}
onFocusChange={setFocused}
onReady={(view) => {
editorViewRef.current = view;
}}
@@ -393,22 +502,21 @@ function StylesheetEditorShell({ readOnly = false }: StylesheetEditorShellProps)
const editorChrome = (
<div className="space-y-3">
{mode === "legacy" && (
<LegacyStylesheetBanner disabled={restoreLocked || hasErrors || isChecking} onActivate={activate} />
<LegacyStylesheetBanner disabled={disabled || hasFatalErrors || isChecking} onActivate={activate} />
)}
<StylesheetToolbar
source={source}
source={source.text}
canUndo={canUndo}
canRedo={canRedo}
focused={focusOpen}
disabled={restoreLocked}
disabled={disabled}
onUndo={undo}
onRedo={redo}
onFormat={() => {
const view = editorViewRef.current;
if (view) void formatEditorDocument(view).catch(() => undefined);
}}
onReset={() => setSourceText(applied)}
onFocusToggle={toggleFocus}
/>
@@ -1,62 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { inspectPdfPageCount } from "./pdf-inspection";
const mocks = vi.hoisted(() => ({
workerDestroy: vi.fn(),
getDocument: vi.fn(),
}));
vi.mock("pdfjs-dist/legacy/build/pdf.mjs", () => ({
PDFWorker: class {
destroy = mocks.workerDestroy;
},
getDocument: mocks.getDocument,
}));
describe("inspectPdfPageCount", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("inspects a copy through a nested worker without detaching the result buffer", async () => {
const destroy = vi.fn();
const nestedWorker = { terminate: vi.fn() } as unknown as Worker;
mocks.getDocument.mockReturnValue({ promise: Promise.resolve({ numPages: 3 }), destroy });
const pdf = Uint8Array.of(1, 2, 3, 4).buffer;
await expect(inspectPdfPageCount(pdf, () => nestedWorker)).resolves.toBe(3);
expect(mocks.getDocument).toHaveBeenCalledWith(
expect.objectContaining({ data: expect.any(ArrayBuffer), worker: expect.any(Object) }),
);
const inspectedPdf = mocks.getDocument.mock.calls[0]?.[0].data as ArrayBuffer;
expect(inspectedPdf).not.toBe(pdf);
expect(Array.from(new Uint8Array(inspectedPdf))).toEqual([1, 2, 3, 4]);
expect(Array.from(new Uint8Array(pdf))).toEqual([1, 2, 3, 4]);
expect(destroy).toHaveBeenCalledOnce();
expect(nestedWorker.terminate).toHaveBeenCalledOnce();
});
it("destroys the loading task and nested worker when parsing fails", async () => {
const destroy = vi.fn();
const nestedWorker = { terminate: vi.fn() } as unknown as Worker;
mocks.getDocument.mockReturnValue({ promise: Promise.reject(new Error("invalid PDF")), destroy });
await expect(inspectPdfPageCount(new ArrayBuffer(4), () => nestedWorker)).rejects.toThrow("invalid PDF");
expect(destroy).toHaveBeenCalledOnce();
expect(nestedWorker.terminate).toHaveBeenCalledOnce();
});
it("terminates the nested worker even when loading-task cleanup fails", async () => {
const nestedWorker = { terminate: vi.fn() } as unknown as Worker;
mocks.getDocument.mockReturnValue({
promise: Promise.resolve({ numPages: 1 }),
destroy: vi.fn().mockRejectedValue(new Error("cleanup failed")),
});
await expect(inspectPdfPageCount(new ArrayBuffer(4), () => nestedWorker)).rejects.toThrow("cleanup failed");
expect(nestedWorker.terminate).toHaveBeenCalledOnce();
});
});
@@ -1,39 +0,0 @@
let pdfModule: Promise<typeof import("pdfjs-dist/legacy/build/pdf.mjs")>;
const loadPdfModule = () => (pdfModule ??= import("pdfjs-dist/legacy/build/pdf.mjs"));
const createNestedWorker = () =>
new Worker(new URL("pdfjs-dist/legacy/build/pdf.worker.min.mjs", import.meta.url), {
type: "module",
name: "semantic-css-pdfjs",
});
export async function initializePdfInspection(): Promise<void> {
await loadPdfModule();
}
export async function inspectPdfPageCount(
pdf: ArrayBuffer,
createWorker: () => Worker = createNestedWorker,
): Promise<number> {
const { PDFWorker, getDocument } = await loadPdfModule();
const nestedWorker = createWorker();
const WorkerWithPort = PDFWorker as unknown as new (options: { port: Worker }) => InstanceType<typeof PDFWorker>;
const worker = new WorkerWithPort({ port: nestedWorker });
let loadingTask: ReturnType<typeof getDocument> | undefined;
try {
// PDF.js transfers its input to the nested worker and detaches the buffer.
// Keep the caller's buffer intact so preflight can return those same bytes.
loadingTask = getDocument({ data: pdf.slice(0), worker });
const document = await loadingTask.promise;
return document.numPages;
} finally {
try {
if (loadingTask) await loadingTask.destroy();
else worker.destroy();
} finally {
nestedWorker.terminate();
}
}
}
@@ -1,96 +0,0 @@
import type { PreflightWorkerRequest } from "./protocol";
import { afterEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
renderPreflightPdf: vi.fn(),
initializePdfInspection: vi.fn(async () => undefined),
inspectPdfPageCount: vi.fn(),
}));
vi.mock("@reactive-resume/pdf/preflight", () => ({
renderPreflightPdf: mocks.renderPreflightPdf,
}));
vi.mock("./pdf-inspection", () => ({
initializePdfInspection: mocks.initializePdfInspection,
inspectPdfPageCount: mocks.inspectPdfPageCount,
}));
describe("stylesheet preflight worker", () => {
afterEach(() => {
vi.unstubAllGlobals();
vi.clearAllMocks();
});
it("serializes schema errors into a correlated preflight error packet", async () => {
let handler: ((event: MessageEvent<PreflightWorkerRequest>) => Promise<void>) | undefined;
const postMessage = vi.fn();
vi.stubGlobal("self", {
postMessage,
addEventListener: vi.fn((_type, listener) => {
handler = listener as typeof handler;
}),
});
const issues = [{ path: ["customSections", 0, "items", 0, "company"] }];
mocks.renderPreflightPdf.mockRejectedValueOnce(
Object.assign(new Error("Invalid resume data"), { name: "ZodError", issues }),
);
vi.resetModules();
await import("./preflight.worker");
await handler?.({
data: {
type: "preflight",
requestId: 7,
editGeneration: 3,
input: {} as never,
limits: {} as never,
},
} as unknown as MessageEvent<PreflightWorkerRequest>);
expect(postMessage).toHaveBeenCalledWith({
type: "preflight_error",
requestId: 7,
editGeneration: 3,
cause: { name: "ZodError", message: "Invalid resume data", issues },
});
});
it("includes a sanitized cause when PDF preflight throws an unexpected error", async () => {
let handler: ((event: MessageEvent<PreflightWorkerRequest>) => Promise<void>) | undefined;
const postMessage = vi.fn();
vi.stubGlobal("self", {
postMessage,
addEventListener: vi.fn((_type, listener) => {
handler = listener as typeof handler;
}),
});
mocks.renderPreflightPdf.mockRejectedValueOnce(new Error("Canvas is already closed"));
vi.resetModules();
await import("./preflight.worker");
await handler?.({
data: {
type: "preflight",
requestId: 8,
editGeneration: 4,
input: {} as never,
limits: {} as never,
},
} as unknown as MessageEvent<PreflightWorkerRequest>);
expect(postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "preflight_result",
requestId: 8,
editGeneration: 4,
result: expect.objectContaining({
ok: false,
code: "STYLESHEET_PREFLIGHT_WORKER_FAILED",
message: expect.stringContaining("Canvas is already closed"),
diagnostics: [],
}),
}),
);
});
});
@@ -1,101 +0,0 @@
/// <reference lib="webworker" />
import type { PdfPreflightFailure } from "@reactive-resume/pdf/preflight";
import type {
PreflightWorkerError,
PreflightWorkerRequest,
PreflightWorkerResponse,
SerializedPreflightCause,
} from "./protocol";
import { Buffer } from "buffer";
import { initializePdfInspection, inspectPdfPageCount } from "./pdf-inspection";
import { getPreflightTransferables } from "./protocol";
Object.assign(globalThis, { Buffer });
const failure = (code: PdfPreflightFailure["code"], message: string): PdfPreflightFailure => ({
ok: false,
code,
message,
diagnostics: [],
});
const sanitizeWorkerCause = (cause: unknown): string => {
if (!(cause instanceof Error)) return "The PDF preflight worker failed.";
const detail = cause.message.replace(/\s+/g, " ").trim().slice(0, 200);
if (!detail) return "The PDF preflight worker failed.";
return `The PDF preflight worker failed. (${cause.name}: ${detail})`;
};
const serializeZodCause = (cause: unknown): SerializedPreflightCause | undefined => {
if (!(cause instanceof Error) || cause.name !== "ZodError" || !("issues" in cause) || !Array.isArray(cause.issues)) {
return;
}
return { name: cause.name, message: cause.message, issues: cause.issues };
};
const initialization = Promise.all([import("@reactive-resume/pdf/preflight"), initializePdfInspection()] as const);
void initialization.then(() => self.postMessage({ type: "preflight_ready" }));
self.addEventListener("message", async ({ data }: MessageEvent<PreflightWorkerRequest>) => {
if (data.type !== "preflight") return;
const [{ renderPreflightPdf }] = await initialization;
let rendered: Awaited<ReturnType<typeof renderPreflightPdf>>;
try {
rendered = await renderPreflightPdf(data.input, data.limits);
} catch (cause) {
const serializedCause = serializeZodCause(cause);
if (serializedCause) {
const response: PreflightWorkerError = {
type: "preflight_error",
requestId: data.requestId,
editGeneration: data.editGeneration,
cause: serializedCause,
};
self.postMessage(response);
return;
}
const result = failure("STYLESHEET_PREFLIGHT_WORKER_FAILED", sanitizeWorkerCause(cause));
const response: PreflightWorkerResponse = {
type: "preflight_result",
requestId: data.requestId,
editGeneration: data.editGeneration,
result,
};
self.postMessage(response);
return;
}
let result: PreflightWorkerResponse["result"];
if (!rendered.ok) {
result = rendered;
} else if (rendered.bytes.byteLength > data.limits.maxBytes) {
result = failure("STYLESHEET_PREFLIGHT_BYTE_LIMIT", "The PDF exceeds the preflight byte limit.");
} else {
try {
const pdf = Uint8Array.from(rendered.bytes).buffer;
const pageCount = await inspectPdfPageCount(pdf);
result =
pageCount > data.limits.maxPages
? failure("STYLESHEET_PREFLIGHT_PAGE_LIMIT", "The PDF exceeds the preflight page limit.")
: {
ok: true,
pageCount,
byteCount: pdf.byteLength,
diagnostics: rendered.diagnostics,
pdf,
};
} catch {
result = failure("STYLESHEET_PREFLIGHT_PARSE_FAILED", "The generated PDF could not be inspected.");
}
}
const response: PreflightWorkerResponse = {
type: "preflight_result",
requestId: data.requestId,
editGeneration: data.editGeneration,
result,
};
self.postMessage(response, { transfer: getPreflightTransferables(response) });
});
@@ -1,8 +1,3 @@
import type {
BrowserPdfPreflightResult,
PdfPreflightPageLimits,
StylesheetPreflightInput,
} from "@reactive-resume/pdf/preflight";
import type {
AuthoredPageContext,
BaseSettingsSnapshot,
@@ -39,47 +34,3 @@ export type CompileWorkerResponse = {
diagnostics: readonly SemanticCssDiagnostic[];
colorTokens?: readonly SemanticCssColorToken[];
};
type PreflightLimits = PdfPreflightPageLimits & {
maxPages: number;
maxBytes: number;
};
export type PreflightWorkerInput = {
editGeneration: number;
input: StylesheetPreflightInput;
limits: PreflightLimits;
};
export type PreflightWorkerRequest = PreflightWorkerInput & {
type: "preflight";
requestId: number;
};
export type PreflightWorkerResponse = {
type: "preflight_result";
requestId: number;
editGeneration: number;
result: BrowserPdfPreflightResult;
};
export type SerializedPreflightCause = {
name: string;
message: string;
issues: readonly unknown[];
};
export type PreflightWorkerError = {
type: "preflight_error";
requestId: number;
editGeneration: number;
cause: SerializedPreflightCause;
};
export type PreflightWorkerReady = {
type: "preflight_ready";
};
export function getPreflightTransferables(response: PreflightWorkerResponse): Transferable[] {
return response.result.ok ? [response.result.pdf] : [];
}
@@ -1,155 +0,0 @@
// @vitest-environment happy-dom
import type { SemanticStylesheet } from "@reactive-resume/schema/resume/stylesheet";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
const mocks = vi.hoisted(() => ({
getState: vi.fn(),
workers: [] as FakeWorker[],
}));
class FakeWorker {
terminated = false;
constructor() {
mocks.workers.push(this);
}
postMessage() {}
addEventListener() {}
removeEventListener() {}
terminate() {
this.terminated = true;
}
}
vi.mock("@/libs/orpc/client", () => ({
orpc: {
resume: {
stylesheet: {
getState: { call: mocks.getState },
mutate: { call: vi.fn() },
},
},
},
}));
const stylesheet = (text: string): SemanticStylesheet => {
const source = { languageVersion: 1, text };
return { mode: "semantic", source, applied: source };
};
describe("stylesheet store reinitialization", () => {
beforeEach(() => {
mocks.workers.length = 0;
vi.stubGlobal("Worker", FakeWorker);
});
afterEach(() => vi.unstubAllGlobals());
it("suspends edits while a delayed restore is pending, then atomically installs the restored state", async () => {
const storeModule = await import("./store");
const cleanup = storeModule.initializeStylesheetStore({
resumeId: "resume-1",
initial: { stylesheet: stylesheet("old"), revision: 3, renderDataVersion: 7 },
resumeData: defaultResumeData,
});
let finishRestore: (() => void) | undefined;
const delayedRestore = new Promise<void>((resolve) => {
finishRestore = resolve;
});
const restore = async () => {
const token = storeModule.lockStylesheetStoreForRestore("resume-1");
await delayedRestore;
return storeModule.replaceStylesheetStoreAfterRestore({
resumeId: "resume-1",
resumeData: defaultResumeData,
initial: { stylesheet: stylesheet("restored"), revision: 9, renderDataVersion: 12 },
token,
});
};
const pendingRestore = restore();
expect(storeModule.useStylesheetStore.getState().restoreLocked).toBe(true);
const editGeneration = storeModule.useStylesheetStore.getState().editGeneration;
storeModule.useStylesheetStore.getState().setSourceText("edit while restoring");
storeModule.useStylesheetStore.getState().deactivate();
storeModule.useStylesheetStore.getState().undo();
expect(storeModule.useStylesheetStore.getState().source.text).toBe("old");
expect(storeModule.useStylesheetStore.getState().editGeneration).toBe(editGeneration);
finishRestore?.();
const replaced = await pendingRestore;
expect(replaced).toBe(true);
expect(mocks.getState).not.toHaveBeenCalled();
expect(storeModule.useStylesheetStore.getState()).toMatchObject({
resumeId: "resume-1",
source: { text: "restored" },
applied: { text: "restored" },
revision: 9,
renderDataVersion: 12,
restoreLocked: false,
});
storeModule.useStylesheetStore.getState().setSourceText("later edit");
expect(storeModule.useStylesheetStore.getState().source.text).toBe("later edit");
expect(mocks.workers).toHaveLength(4);
expect(mocks.workers.slice(0, 2).every((worker) => worker.terminated)).toBe(true);
cleanup();
expect(mocks.workers.slice(2).every((worker) => worker.terminated)).toBe(true);
expect(storeModule.useStylesheetStore.getState().resumeId).toBeUndefined();
});
it("unlocks interaction after a restore request fails", async () => {
const storeModule = await import("./store");
const cleanup = storeModule.initializeStylesheetStore({
resumeId: "resume-1",
initial: { stylesheet: stylesheet("old"), revision: 3, renderDataVersion: 7 },
resumeData: defaultResumeData,
});
const token = storeModule.lockStylesheetStoreForRestore("resume-1");
expect(storeModule.useStylesheetStore.getState().restoreLocked).toBe(true);
expect(storeModule.unlockStylesheetStoreAfterRestore(token)).toBe(true);
expect(storeModule.useStylesheetStore.getState().restoreLocked).toBe(false);
storeModule.useStylesheetStore.getState().setSourceText("edit after failure");
expect(storeModule.useStylesheetStore.getState().source.text).toBe("edit after failure");
cleanup();
});
it("ignores a stale same-resume restore completion after away-and-back runtime replacement", async () => {
const storeModule = await import("./store");
const cleanupFirst = storeModule.initializeStylesheetStore({
resumeId: "resume-1",
initial: { stylesheet: stylesheet("first"), revision: 1, renderDataVersion: 1 },
resumeData: defaultResumeData,
});
const staleToken = storeModule.lockStylesheetStoreForRestore("resume-1");
cleanupFirst();
const cleanupSecond = storeModule.initializeStylesheetStore({
resumeId: "resume-1",
initial: { stylesheet: stylesheet("second"), revision: 2, renderDataVersion: 2 },
resumeData: defaultResumeData,
});
const replaced = storeModule.replaceStylesheetStoreAfterRestore({
resumeId: "resume-1",
resumeData: defaultResumeData,
initial: { stylesheet: stylesheet("stale restore"), revision: 3, renderDataVersion: 3 },
token: staleToken,
});
expect(replaced).toBe(false);
expect(storeModule.useStylesheetStore.getState()).toMatchObject({
source: { text: "second" },
revision: 2,
renderDataVersion: 2,
});
cleanupSecond();
});
});
@@ -1,50 +1,67 @@
import type { SemanticCssDiagnostic } from "@reactive-resume/resume/stylesheet";
import { Trans } from "@lingui/react/macro";
import { WarningCircleIcon, WarningIcon } from "@phosphor-icons/react";
import { isFatalStylesheetDiagnostic } from "@reactive-resume/resume/stylesheet";
import { Alert, AlertDescription, AlertTitle } from "@reactive-resume/ui/components/alert";
import { Badge } from "@reactive-resume/ui/components/badge";
import { ScrollArea } from "@reactive-resume/ui/components/scroll-area";
export type StylesheetStatusProps = {
mode: "legacy" | "semantic";
status: "idle" | "compiling" | "preflighting" | "saving" | "applied" | "error";
status: "idle" | "compiling" | "error";
diagnostics: readonly SemanticCssDiagnostic[];
};
export function StylesheetStatus({ mode, status, diagnostics }: StylesheetStatusProps) {
const errors = diagnostics.filter(({ severity }) => severity === "error");
const warnings = diagnostics.filter(({ severity }) => severity === "warning");
const hasErrors = status === "error" || errors.length > 0;
const isPending = status === "compiling" || status === "preflighting" || status === "saving";
const hasFatalErrors = status === "error" || diagnostics.some(isFatalStylesheetDiagnostic);
const hasRecoverableErrors = !hasFatalErrors && errors.length > 0;
const isPending = status === "compiling";
return (
<div className="space-y-2" aria-live="polite">
{hasErrors ? (
{hasFatalErrors ? (
<Badge variant="destructive">
<WarningCircleIcon data-icon="inline-start" />
<Trans>Error</Trans>
<Trans>Fatal error</Trans>
</Badge>
) : isPending ? (
<Badge variant="outline">{mode === "legacy" ? <Trans>Checking draft</Trans> : <Trans>Checking</Trans>}</Badge>
) : hasRecoverableErrors ? (
<Badge variant="secondary">
<WarningCircleIcon data-icon="inline-start" />
{mode === "legacy" ? <Trans>Ready to activate with errors</Trans> : <Trans>Valid with errors</Trans>}
</Badge>
) : warnings.length > 0 ? (
<Badge variant="secondary">
<WarningIcon data-icon="inline-start" />
{mode === "legacy" ? <Trans>Ready to activate with warnings</Trans> : <Trans>Applied with warnings</Trans>}
{mode === "legacy" ? <Trans>Ready to activate with warnings</Trans> : <Trans>Valid with warnings</Trans>}
</Badge>
) : (
<Badge variant="secondary">
{mode === "legacy" ? <Trans>Ready to activate</Trans> : <Trans>Applied</Trans>}
</Badge>
<Badge variant="secondary">{mode === "legacy" ? <Trans>Ready to activate</Trans> : <Trans>Valid</Trans>}</Badge>
)}
{hasErrors && (
{hasFatalErrors && (
<Alert variant="destructive">
<WarningCircleIcon />
<AlertTitle>
<Trans>Stylesheet has errors</Trans>
<Trans>Stylesheet has fatal errors</Trans>
</AlertTitle>
<AlertDescription>
<Trans>Preview and export use the last valid version.</Trans>
<Trans>Preview and export fall back to base styles.</Trans>
</AlertDescription>
</Alert>
)}
{hasRecoverableErrors && (
<Alert>
<WarningCircleIcon />
<AlertTitle>
<Trans>Some styles were ignored</Trans>
</AlertTitle>
<AlertDescription>
<Trans>Preview and export keep valid styles and ignore invalid styles.</Trans>
</AlertDescription>
</Alert>
)}
@@ -1,940 +0,0 @@
import type { SemanticStylesheet, StylesheetSource } from "@reactive-resume/schema/resume/stylesheet";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { createStylesheetStoreRuntime } from "./store";
const source = (text: string): StylesheetSource => ({ languageVersion: 1, text });
const stylesheet = (text: string): SemanticStylesheet => ({
mode: "semantic",
source: source(text),
applied: source(text),
});
const initial = {
stylesheet: stylesheet("generation zero"),
revision: 3,
renderDataVersion: 7,
};
type MutationResult = {
stylesheet: SemanticStylesheet;
revision: number;
renderDataVersion: number;
editGeneration: number;
diagnostics: [];
};
describe("stylesheet store runtime", () => {
beforeEach(() => vi.useFakeTimers());
it("clears compiler-confirmed color tokens synchronously when same-length source text changes", () => {
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial: { ...initial, stylesheet: stylesheet("section { color: red; }") },
resumeData: defaultResumeData,
debounceMs: 1_000_000,
compile: vi.fn(),
preflight: vi.fn(),
mutate: vi.fn(),
});
runtime.store.setState({ colorTokens: [{ from: 17, to: 20, value: "red" }] });
runtime.store.getState().setSourceText("section { color: var; }");
expect(runtime.store.getState().source.text).toBe("section { color: var; }");
expect(runtime.store.getState().colorTokens).toEqual([]);
});
it("rejects delayed editor intelligence for a canonically replaced source", async () => {
let resolveCompile!: (value: {
type: "compile_result";
requestId: number;
editGeneration: number;
program: { languageVersion: number; rules: [] };
diagnostics: [
{
code: string;
severity: "error";
message: string;
range: {
start: { line: number; column: number; offset: number };
end: { line: number; column: number; offset: number };
};
},
];
colorTokens: [{ from: number; to: number; value: string }];
}) => void;
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial: { ...initial, stylesheet: stylesheet("section { color: red; }") },
resumeData: defaultResumeData,
compile: () => new Promise((resolve) => (resolveCompile = resolve)),
preflight: vi.fn(),
mutate: vi.fn(),
});
runtime.store.getState().refreshIntelligence();
runtime.rebaseCanonical({
stylesheet: stylesheet("section { color: blue; }"),
revision: 4,
renderDataVersion: 7,
});
resolveCompile({
type: "compile_result",
requestId: 1,
editGeneration: 0,
program: { languageVersion: 1, rules: [] },
diagnostics: [
{
code: "OLD_SOURCE",
severity: "error",
message: "Old source diagnostic",
range: {
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 2, offset: 1 },
},
},
],
colorTokens: [{ from: 17, to: 20, value: "red" }],
});
await Promise.resolve();
await Promise.resolve();
expect(runtime.store.getState()).toMatchObject({
source: source("section { color: blue; }"),
diagnostics: [],
colorTokens: [],
});
});
it("consumes stale acknowledgements before saving the replaceable pending edit", async () => {
const resolvers: Array<(value: MutationResult) => void> = [];
const mutate = vi.fn(
(_input: unknown) =>
new Promise<MutationResult>((resolve) => {
resolvers.push((value) => resolve(value));
}),
);
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 4, diagnostics: [], pdf: new ArrayBuffer(4) },
}),
mutate,
});
runtime.store.getState().setSourceText("generation one");
await vi.runAllTimersAsync();
runtime.store.getState().setSourceText("generation two");
await vi.runAllTimersAsync();
resolvers[0]?.({
stylesheet: stylesheet("generation one"),
revision: 4,
renderDataVersion: 8,
editGeneration: 1,
diagnostics: [],
});
await vi.runAllTimersAsync();
expect(runtime.store.getState()).toMatchObject({
revision: 4,
renderDataVersion: 8,
source: source("generation two"),
applied: source("generation zero"),
});
expect(mutate).toHaveBeenCalledTimes(2);
expect(mutate.mock.calls[1]?.[0]).toMatchObject({
expectedRevision: 4,
expectedRenderDataVersion: 8,
editGeneration: 2,
});
});
it("rebases conflicts without dropping the focused local draft", async () => {
const mutate = vi
.fn()
.mockRejectedValueOnce({
code: "STYLESHEET_REVISION_CONFLICT",
data: {
state: { stylesheet: stylesheet("remote"), revision: 8, renderDataVersion: 11 },
},
})
.mockResolvedValueOnce({
stylesheet: stylesheet("local unsaved source"),
revision: 9,
renderDataVersion: 11,
editGeneration: 1,
diagnostics: [],
});
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}),
mutate,
});
runtime.store.getState().setFocused(true);
runtime.store.getState().setSourceText("local unsaved source");
await vi.runAllTimersAsync();
expect(runtime.store.getState()).toMatchObject({
revision: 9,
renderDataVersion: 11,
source: source("local unsaved source"),
});
expect(mutate.mock.calls[1]?.[0]).toMatchObject({ expectedRevision: 8, expectedRenderDataVersion: 11 });
});
it("keeps the newer pending edit when an older request conflicts", async () => {
let rejectFirst!: (error: unknown) => void;
const mutate = vi
.fn()
.mockReturnValueOnce(
new Promise((_resolve, reject) => {
rejectFirst = reject;
}),
)
.mockResolvedValueOnce({
stylesheet: stylesheet("newer"),
revision: 9,
renderDataVersion: 11,
editGeneration: 2,
diagnostics: [],
});
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}),
mutate,
});
runtime.store.getState().setSourceText("older");
await vi.runAllTimersAsync();
runtime.store.getState().setSourceText("newer");
await vi.runAllTimersAsync();
rejectFirst({
code: "STYLESHEET_REVISION_CONFLICT",
data: { state: { stylesheet: stylesheet("remote"), revision: 8, renderDataVersion: 11 } },
});
await vi.runAllTimersAsync();
expect(mutate.mock.calls[1]?.[0]).toMatchObject({
transition: "edit_source",
editGeneration: 2,
source: source("newer"),
});
});
it("invalidates pending preflight eligibility on content changes and keeps versions monotonic", async () => {
let resolveMutation!: (result: MutationResult) => void;
let resolveRepreflight!: (result: {
type: "preflight_result";
requestId: number;
editGeneration: number;
result: {
ok: true;
pageCount: number;
byteCount: number;
diagnostics: [];
pdf: ArrayBuffer;
};
}) => void;
const mutate = vi
.fn()
.mockReturnValueOnce(new Promise<MutationResult>((resolve) => (resolveMutation = resolve)))
.mockResolvedValueOnce({
stylesheet: stylesheet("newer"),
revision: 11,
renderDataVersion: 20,
editGeneration: 2,
diagnostics: [],
});
const preflight = vi
.fn()
.mockImplementationOnce(async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}))
.mockImplementationOnce(async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}))
.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveRepreflight = resolve;
}),
);
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight,
mutate,
});
runtime.store.getState().setSourceText("older");
await vi.runAllTimersAsync();
runtime.store.getState().setSourceText("newer");
await vi.runAllTimersAsync();
expect(mutate).toHaveBeenCalledTimes(1);
runtime.replaceResumeSnapshot(defaultResumeData, {
stylesheet: stylesheet("remote"),
revision: 10,
renderDataVersion: 20,
});
await vi.runAllTimersAsync();
expect(preflight).toHaveBeenCalledTimes(3);
resolveMutation({
stylesheet: stylesheet("older"),
revision: 4,
renderDataVersion: 7,
editGeneration: 1,
diagnostics: [],
});
await Promise.resolve();
await Promise.resolve();
expect(runtime.store.getState()).toMatchObject({ revision: 10, renderDataVersion: 20 });
expect(mutate).toHaveBeenCalledTimes(1);
resolveRepreflight({
type: "preflight_result",
requestId: 3,
editGeneration: 2,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
});
await vi.runAllTimersAsync();
expect(mutate.mock.calls[1]?.[0]).toMatchObject({
source: source("newer"),
expectedRevision: 10,
expectedRenderDataVersion: 20,
});
});
it("does not requeue an invalidated in-flight candidate after conflict", async () => {
let rejectMutation!: (error: unknown) => void;
let resolveRepreflight!: (result: {
type: "preflight_result";
requestId: number;
editGeneration: number;
result: {
ok: true;
pageCount: number;
byteCount: number;
diagnostics: [];
pdf: ArrayBuffer;
};
}) => void;
const mutate = vi
.fn()
.mockReturnValueOnce(new Promise<MutationResult>((_resolve, reject) => (rejectMutation = reject)))
.mockResolvedValueOnce({
stylesheet: stylesheet("newer"),
revision: 11,
renderDataVersion: 20,
editGeneration: 2,
diagnostics: [],
});
const preflight = vi
.fn()
.mockImplementationOnce(async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}))
.mockImplementationOnce(async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}))
.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveRepreflight = resolve;
}),
);
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight,
mutate,
});
runtime.store.getState().setSourceText("older");
await vi.runAllTimersAsync();
runtime.store.getState().setSourceText("newer");
await vi.runAllTimersAsync();
runtime.replaceResumeSnapshot(defaultResumeData, {
stylesheet: stylesheet("remote"),
revision: 10,
renderDataVersion: 20,
});
await vi.runAllTimersAsync();
rejectMutation({
code: "STYLESHEET_REVISION_CONFLICT",
data: { state: { stylesheet: stylesheet("remote"), revision: 10, renderDataVersion: 20 } },
});
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
expect(mutate).toHaveBeenCalledTimes(1);
resolveRepreflight({
type: "preflight_result",
requestId: 3,
editGeneration: 2,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
});
await vi.runAllTimersAsync();
expect(mutate.mock.calls[1]?.[0]).toMatchObject({
source: source("newer"),
expectedRevision: 10,
expectedRenderDataVersion: 20,
});
});
it("reconciles a deferred focused canonical source on blur", () => {
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
compile: vi.fn(),
preflight: vi.fn(),
mutate: vi.fn(),
});
runtime.store.getState().setFocused(true);
runtime.rebaseCanonical({
stylesheet: stylesheet("remote"),
revision: 8,
renderDataVersion: 11,
});
expect(runtime.store.getState()).toMatchObject({
source: source("generation zero"),
applied: source("remote"),
revision: 8,
renderDataVersion: 11,
});
runtime.store.getState().setFocused(false);
expect(runtime.store.getState()).toMatchObject({
source: source("remote"),
applied: source("remote"),
});
});
it("persists invalid source while preserving applied and restores stylesheet history separately", async () => {
const mutate = vi
.fn()
.mockResolvedValueOnce({
stylesheet: {
mode: "semantic",
source: source("invalid {"),
applied: source("generation zero"),
},
revision: 4,
renderDataVersion: 7,
editGeneration: 1,
diagnostics: [
{
code: "PARSE_ERROR",
severity: "error",
message: "Invalid",
range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
},
],
})
.mockResolvedValueOnce({
stylesheet: stylesheet("generation zero"),
revision: 5,
renderDataVersion: 7,
editGeneration: 2,
diagnostics: [],
});
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration, source: candidate }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: candidate.text === "invalid {" ? null : { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}),
mutate,
});
runtime.store.getState().setSourceText("invalid {");
await vi.runAllTimersAsync();
expect(runtime.store.getState()).toMatchObject({
source: source("invalid {"),
applied: source("generation zero"),
});
expect(mutate.mock.calls[0]?.[0]).toMatchObject({ transition: "edit_source", source: source("invalid {") });
runtime.store.getState().undo();
await vi.runAllTimersAsync();
expect(mutate.mock.calls[1]?.[0]).toMatchObject({
transition: "restore_history",
restore: stylesheet("generation zero"),
});
});
it("does not publish historical applied state before restore acknowledgement", async () => {
const mutate = vi.fn(() => new Promise<MutationResult>(() => {}));
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial: { ...initial, stylesheet: stylesheet("current applied") },
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}),
mutate,
});
runtime.store.setState({
source: source("local invalid"),
applied: source("current applied"),
undoStack: [stylesheet("historical")],
canUndo: true,
});
runtime.store.getState().undo();
await vi.runAllTimersAsync();
expect(runtime.store.getState().source).toEqual(source("historical"));
expect(runtime.store.getState().applied).toEqual(source("current applied"));
expect(mutate).toHaveBeenCalledWith(
expect.objectContaining({ transition: "restore_history", restore: stylesheet("historical") }),
expect.any(AbortSignal),
);
});
it("retries the focused draft against a newer content render-data version", async () => {
const mutate = vi.fn().mockResolvedValue({
stylesheet: stylesheet("local"),
revision: 4,
renderDataVersion: 12,
editGeneration: 1,
diagnostics: [],
});
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}),
mutate,
});
runtime.store.getState().setFocused(true);
runtime.store.getState().setSourceText("local");
runtime.replaceResumeSnapshot(defaultResumeData, {
stylesheet: stylesheet("remote"),
revision: 9,
renderDataVersion: 12,
});
await vi.runAllTimersAsync();
expect(runtime.store.getState().source).toEqual(source("local"));
expect(mutate).toHaveBeenLastCalledWith(
expect.objectContaining({ expectedRevision: 9, expectedRenderDataVersion: 12, source: source("local") }),
expect.any(AbortSignal),
);
});
it("keeps a queued draft when content changes after editor blur", async () => {
const mutate = vi.fn().mockResolvedValue({
stylesheet: stylesheet("local"),
revision: 10,
renderDataVersion: 12,
editGeneration: 1,
diagnostics: [],
});
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}),
mutate,
});
runtime.store.getState().setSourceText("local");
runtime.replaceResumeSnapshot(defaultResumeData, {
stylesheet: stylesheet("remote"),
revision: 9,
renderDataVersion: 12,
});
await vi.runAllTimersAsync();
expect(runtime.store.getState().source).toEqual(source("local"));
expect(mutate).toHaveBeenLastCalledWith(
expect.objectContaining({ expectedRevision: 9, expectedRenderDataVersion: 12, source: source("local") }),
expect.any(AbortSignal),
);
});
it("queues activation only after browser preflight succeeds", async () => {
const mutate = vi.fn().mockResolvedValue({
stylesheet: stylesheet("generation zero"),
revision: 4,
renderDataVersion: 7,
editGeneration: 2,
diagnostics: [],
});
const preflight = vi
.fn()
.mockResolvedValueOnce({
type: "preflight_result",
requestId: 1,
editGeneration: 1,
result: {
ok: false,
code: "STYLESHEET_PREFLIGHT_RENDER_FAILED",
message: "failed",
diagnostics: [],
},
})
.mockResolvedValueOnce({
type: "preflight_result",
requestId: 2,
editGeneration: 2,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
});
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial: { ...initial, stylesheet: { ...initial.stylesheet, mode: "legacy" } },
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight,
mutate,
});
runtime.store.getState().activate();
await vi.runAllTimersAsync();
expect(mutate).not.toHaveBeenCalled();
runtime.store.getState().activate();
await vi.runAllTimersAsync();
expect(mutate).toHaveBeenCalledWith(
expect.objectContaining({ transition: "activate", source: source("generation zero") }),
expect.any(AbortSignal),
);
});
it("does not leave Checking stuck when compile rejects for the current edit", async () => {
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: () => Promise.reject(new Error("Discarded stale stylesheet compiler result.")),
preflight: vi.fn(),
mutate: vi.fn(),
});
runtime.store.getState().setSourceText("edited source");
await vi.runAllTimersAsync();
expect(runtime.store.getState().status).not.toBe("compiling");
expect(runtime.store.getState().status).toBe("error");
});
it("surfaces browser preflight failures as diagnostics when the worker returns an empty list", async () => {
let resolveMutate!: (value: MutationResult & { diagnostics: never[] }) => void;
const mutate = vi.fn(
() =>
new Promise<MutationResult & { diagnostics: never[] }>((resolve) => {
resolveMutate = resolve;
}),
);
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: async ({ editGeneration }) => ({
type: "compile_result",
requestId: editGeneration,
editGeneration,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
}),
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: {
ok: false,
code: "STYLESHEET_PREFLIGHT_WORKER_FAILED",
message: "The PDF preflight worker failed.",
diagnostics: [],
},
}),
mutate,
});
runtime.store.getState().setSourceText("@version 1;\nsection { color: teal; }");
await vi.runAllTimersAsync();
expect(runtime.store.getState().diagnostics).toEqual([
expect.objectContaining({
code: "STYLESHEET_PREFLIGHT_WORKER_FAILED",
severity: "error",
message: "The PDF preflight worker failed.",
range: {
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 },
},
}),
]);
expect(runtime.store.getState().diagnostics.some(({ severity }) => severity === "error")).toBe(true);
expect(mutate).toHaveBeenCalled();
resolveMutate({
stylesheet: stylesheet("@version 1;\n"),
revision: 4,
renderDataVersion: 7,
editGeneration: 1,
diagnostics: [],
});
await Promise.resolve();
});
it("finishes an edit when refreshIntelligence interleaves through the shared compile client", async () => {
const { createCompileWorkerClient } = await import("./worker-client");
const listeners = new Map<string, Set<EventListener>>();
const fake = {
postMessage: vi.fn(),
terminate: vi.fn(),
addEventListener: vi.fn((type: string, listener: EventListener) => {
const bucket = listeners.get(type) ?? new Set();
bucket.add(listener);
listeners.set(type, bucket);
}),
removeEventListener: vi.fn((type: string, listener: EventListener) => {
listeners.get(type)?.delete(listener);
}),
emit(data: unknown) {
for (const listener of listeners.get("message") ?? []) {
(listener as (event: MessageEvent) => void)(new MessageEvent("message", { data }));
}
},
};
const client = createCompileWorkerClient(() => fake);
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 0,
compile: client.compile,
preflight: async ({ editGeneration }) => ({
type: "preflight_result",
requestId: editGeneration,
editGeneration,
result: { ok: true, pageCount: 1, byteCount: 1, diagnostics: [], pdf: new ArrayBuffer(1) },
}),
mutate: async ({ editGeneration }) => ({
stylesheet: stylesheet("edited source"),
revision: 4,
renderDataVersion: 7,
editGeneration,
diagnostics: [],
}),
});
runtime.store.getState().setSourceText("edited source");
await vi.runAllTimersAsync();
expect(runtime.store.getState().status).toBe("compiling");
expect(fake.postMessage).toHaveBeenCalledTimes(1);
// Mobile Design remount / accordion reopen refreshes intelligence while the edit is compiling.
runtime.store.getState().refreshIntelligence();
expect(fake.postMessage).toHaveBeenCalledTimes(2);
fake.emit({
type: "compile_result",
requestId: 1,
editGeneration: 1,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
colorTokens: [],
});
fake.emit({
type: "compile_result",
requestId: 2,
editGeneration: 1,
program: { languageVersion: 1, rules: [] },
diagnostics: [],
colorTokens: [],
});
await vi.runAllTimersAsync();
expect(runtime.store.getState().status).toBe("applied");
expect(runtime.store.getState().applied.text).toBe("edited source");
});
it("terminates both worker clients and clears the store on cleanup", () => {
const destroy = vi.fn();
let mutationSignal: AbortSignal | undefined;
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
compile: vi.fn(),
preflight: vi.fn(),
mutate: vi.fn((_input: unknown, signal: AbortSignal) => {
mutationSignal = signal;
return new Promise<MutationResult>(() => {});
}),
destroy,
});
runtime.store.getState().deactivate();
runtime.destroy();
expect(destroy).toHaveBeenCalledOnce();
expect(mutationSignal?.aborted).toBe(true);
expect(runtime.store.getState().resumeId).toBeUndefined();
});
it("coalesces rapid source edits and bounds stylesheet history", () => {
const runtime = createStylesheetStoreRuntime({
resumeId: "resume-1",
initial,
resumeData: defaultResumeData,
debounceMs: 1_000_000,
compile: vi.fn(),
preflight: vi.fn(),
mutate: vi.fn(),
});
for (let index = 0; index < 10; index++) runtime.store.getState().setSourceText(`rapid ${index}`);
expect(runtime.store.getState().undoStack).toHaveLength(1);
expect(runtime.store.getState().undoStack[0]).toEqual(stylesheet("generation zero"));
for (let index = 0; index < 60; index++) {
vi.advanceTimersByTime(501);
runtime.store.getState().setSourceText(`separate ${index}`);
}
expect(runtime.store.getState().undoStack).toHaveLength(50);
});
});
@@ -1,699 +0,0 @@
import type { SemanticCssDiagnostic, SemanticNode } from "@reactive-resume/resume/stylesheet";
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { SemanticStylesheet, StylesheetSource } from "@reactive-resume/schema/resume/stylesheet";
import type { StoreApi } from "zustand/vanilla";
import type { SemanticCssColorToken } from "./color-tokens";
import type {
CompileWorkerInput,
CompileWorkerResponse,
PreflightWorkerInput,
PreflightWorkerResponse,
SemanticCssEditorMetadata,
} from "./protocol";
import { create } from "zustand/react";
import { createStore } from "zustand/vanilla";
import {
buildSemanticTree,
getTemplateSemanticManifest,
semanticNodeKeys,
shouldShowResumeHeader,
} from "@reactive-resume/pdf/semantic-tree";
import { orpc } from "@/libs/orpc/client";
import { createCompileWorkerClient, createPreflightWorkerClient } from "./worker-client";
export type StylesheetCanonicalState = {
stylesheet: SemanticStylesheet;
revision: number;
renderDataVersion: number;
};
type StylesheetMutationResult = StylesheetCanonicalState & {
editGeneration: number;
diagnostics: readonly SemanticCssDiagnostic[];
};
type EditMutation = {
id: string;
expectedRevision: number;
expectedRenderDataVersion: number;
editGeneration: number;
transition: "edit_source";
source: StylesheetSource;
};
type RestoreMutation = {
id: string;
expectedRevision: number;
expectedRenderDataVersion: number;
editGeneration: number;
transition: "restore_history";
restore: SemanticStylesheet;
};
type ActivateMutation = Omit<EditMutation, "transition"> & { transition: "activate" };
type DeactivateMutation = Omit<EditMutation, "transition" | "source"> & { transition: "deactivate" };
type StylesheetMutation = EditMutation | RestoreMutation | ActivateMutation | DeactivateMutation;
type Candidate =
| { generation: number; transition: "edit_source"; source: StylesheetSource }
| { generation: number; transition: "restore_history"; restore: SemanticStylesheet }
| { generation: number; transition: "activate"; source: StylesheetSource }
| { generation: number; transition: "deactivate" };
export type StylesheetStoreState = {
resumeId?: string;
mode: SemanticStylesheet["mode"];
source: StylesheetSource;
applied: StylesheetSource;
revision: number;
renderDataVersion: number;
editGeneration: number;
diagnostics: readonly SemanticCssDiagnostic[];
colorTokens: readonly SemanticCssColorToken[];
editorMetadata: SemanticCssEditorMetadata;
status: "idle" | "compiling" | "preflighting" | "saving" | "applied" | "error";
restoreLocked: boolean;
focused: boolean;
canUndo: boolean;
canRedo: boolean;
undoStack: SemanticStylesheet[];
redoStack: SemanticStylesheet[];
setSourceText(text: string): void;
setFocused(focused: boolean): void;
activate(): void;
deactivate(): void;
undo(): void;
redo(): void;
refreshIntelligence(): void;
};
type RuntimeDependencies = {
compile(input: CompileWorkerInput): Promise<CompileWorkerResponse>;
preflight(input: PreflightWorkerInput): Promise<PreflightWorkerResponse>;
mutate(input: StylesheetMutation, signal: AbortSignal): Promise<StylesheetMutationResult>;
destroy?(): void;
};
type CreateStylesheetStoreRuntimeOptions = RuntimeDependencies & {
resumeId: string;
initial: StylesheetCanonicalState;
resumeData: ResumeData;
debounceMs?: number;
store?: StoreApi<StylesheetStoreState>;
};
const emptySource = (): StylesheetSource => ({ languageVersion: 1, text: "@version 1;\n" });
const emptySemanticTree = (): SemanticNode => ({
key: "resume",
kind: "resume",
attributes: {},
roles: [],
children: [],
});
const HISTORY_COALESCE_MS = 500;
const MAX_HISTORY_ENTRIES = 50;
const preflightFailureDiagnostic = (
result: Extract<PreflightWorkerResponse["result"], { ok: false }>,
): SemanticCssDiagnostic => ({
code: result.code,
severity: "error",
message: result.message,
range: {
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 },
},
});
const inactiveState = (): Omit<
StylesheetStoreState,
"setSourceText" | "setFocused" | "activate" | "deactivate" | "undo" | "redo" | "refreshIntelligence"
> => ({
resumeId: undefined,
mode: "legacy",
source: emptySource(),
applied: emptySource(),
revision: 0,
renderDataVersion: 0,
editGeneration: 0,
diagnostics: [],
colorTokens: [],
editorMetadata: { semanticTree: emptySemanticTree(), templateParts: [] },
status: "idle",
restoreLocked: false,
focused: false,
canUndo: false,
canRedo: false,
undoStack: [],
redoStack: [],
});
const sourceFromText = (source: StylesheetSource, text: string): StylesheetSource => ({ ...source, text });
const sourcesEqual = (left: StylesheetSource, right: StylesheetSource) =>
left.languageVersion === right.languageVersion && left.text === right.text;
const isEditorFocused = () =>
typeof document !== "undefined" && document.activeElement instanceof HTMLElement
? document.activeElement.closest(".cm-editor") !== null
: false;
const currentStylesheet = (state: StylesheetStoreState): SemanticStylesheet => ({
mode: state.mode,
source: structuredClone(state.source),
applied: structuredClone(state.applied),
});
const appendHistory = (stack: SemanticStylesheet[], value: SemanticStylesheet) =>
[...stack, value].slice(-MAX_HISTORY_ENTRIES);
const pageDimensions = (data: ResumeData) => {
const format = data.metadata.page.format;
const size = format === "letter" ? { width: 612, height: 792 } : { width: 595.28, height: 841.89 };
return data.metadata.layout.pages.map((_page, index) => ({
pageKey: semanticNodeKeys.page(index + 1),
...size,
}));
};
const createEditorMetadata = (data: ResumeData): SemanticCssEditorMetadata => {
const pages = data.metadata.layout.pages.map((page, index) =>
buildSemanticTree({
data,
template: data.metadata.template,
page,
pageNumber: index + 1,
showHeader: shouldShowResumeHeader(data, index),
}),
);
const semanticTree: SemanticNode = {
key: semanticNodeKeys.resume(),
kind: "resume",
attributes: { template: data.metadata.template },
roles: [],
children: pages.flatMap(({ children }) => children),
};
return {
semanticTree,
templateParts: getTemplateSemanticManifest(data.metadata.template).parts.map(({ name }) => name),
};
};
const compileInput = (
data: ResumeData,
source: StylesheetSource,
editGeneration: number,
semanticTree: SemanticNode,
): CompileWorkerInput => {
return {
editGeneration,
source,
semanticTree,
baseSettings: {
picture: data.picture,
template: data.metadata.template,
design: data.metadata.design,
typography: data.metadata.typography,
page: data.metadata.page,
layout: { sidebarWidth: data.metadata.layout.sidebarWidth },
},
pages: pageDimensions(data),
};
};
const conflictState = (error: unknown): StylesheetCanonicalState | undefined => {
if (!error || typeof error !== "object") return;
const value = error as { code?: string; data?: { state?: StylesheetCanonicalState } };
return value.code === "STYLESHEET_REVISION_CONFLICT" ? value.data?.state : undefined;
};
export function createStylesheetStoreRuntime(options: CreateStylesheetStoreRuntimeOptions) {
let resumeData = structuredClone(options.resumeData);
let timer: ReturnType<typeof setTimeout> | undefined;
let inFlight: Candidate | undefined;
let pending: Candidate | undefined;
let latestCandidate: Candidate | undefined;
let deferredCanonical: StylesheetCanonicalState | undefined;
let validationEpoch = 0;
let intelligenceEpoch = 0;
let historyLastEditAt = 0;
let historyCanCoalesce = false;
let destroyed = false;
const abortController = new AbortController();
const debounceMs = options.debounceMs ?? 180;
const initial = options.initial.stylesheet;
let editorMetadata = createEditorMetadata(resumeData);
const store =
options.store ??
createStore<StylesheetStoreState>(() => ({
...inactiveState(),
setSourceText: () => {},
setFocused: () => {},
activate: () => {},
deactivate: () => {},
undo: () => {},
redo: () => {},
refreshIntelligence: () => {},
}));
const patch = (next: Partial<StylesheetStoreState>) => store.setState(next);
const replaceCanonical = (canonical: StylesheetCanonicalState, preserveSource: boolean) => {
const state = store.getState();
const next: Partial<StylesheetStoreState> = {
revision: Math.max(state.revision, canonical.revision),
renderDataVersion: Math.max(state.renderDataVersion, canonical.renderDataVersion),
};
if (canonical.revision >= state.revision) {
next.mode = canonical.stylesheet.mode;
const nextSource = preserveSource ? state.source : canonical.stylesheet.source;
next.source = nextSource;
next.applied = canonical.stylesheet.applied;
if (!sourcesEqual(nextSource, state.source)) {
intelligenceEpoch += 1;
next.colorTokens = [];
}
}
patch(next);
};
const resetHistoryCoalescing = () => {
historyLastEditAt = 0;
historyCanCoalesce = false;
};
const startNext = () => {
if (destroyed || inFlight || !pending) return;
const candidate = pending;
pending = undefined;
inFlight = candidate;
const requestValidationEpoch = validationEpoch;
const state = store.getState();
const common = {
id: options.resumeId,
expectedRevision: state.revision,
expectedRenderDataVersion: state.renderDataVersion,
editGeneration: candidate.generation,
};
let input: StylesheetMutation;
if (candidate.transition === "edit_source" || candidate.transition === "activate") {
input = { ...common, transition: candidate.transition, source: candidate.source };
} else if (candidate.transition === "restore_history") {
input = { ...common, transition: "restore_history", restore: candidate.restore };
} else {
input = { ...common, transition: "deactivate" };
}
patch({ status: "saving" });
void options
.mutate(input, abortController.signal)
.then((result) => {
if (destroyed) return;
const state = store.getState();
const staleStylesheet = result.revision < state.revision;
patch({
revision: Math.max(state.revision, result.revision),
renderDataVersion: Math.max(state.renderDataVersion, result.renderDataVersion),
});
if (result.editGeneration !== store.getState().editGeneration) return;
if (staleStylesheet) return;
const sourceChanged = !sourcesEqual(result.stylesheet.source, state.source);
if (sourceChanged) intelligenceEpoch += 1;
patch({
mode: result.stylesheet.mode,
source: result.stylesheet.source,
applied: result.stylesheet.applied,
diagnostics: result.diagnostics,
colorTokens: sourceChanged ? [] : state.colorTokens,
status: result.diagnostics.some(({ severity }) => severity === "error") ? "error" : "applied",
});
if (latestCandidate?.generation === result.editGeneration) latestCandidate = undefined;
if (deferredCanonical && result.revision >= deferredCanonical.revision) deferredCanonical = undefined;
})
.catch((error: unknown) => {
if (destroyed) return;
const canonical = conflictState(error);
if (!canonical) {
patch({ status: "error" });
return;
}
replaceCanonical(canonical, true);
if (requestValidationEpoch === validationEpoch) pending ??= candidate;
})
.finally(() => {
inFlight = undefined;
startNext();
});
};
const queue = (candidate: Candidate) => {
latestCandidate = candidate;
pending = candidate;
startNext();
};
const processCandidate = async (candidate: Candidate) => {
if (destroyed || candidate.generation !== store.getState().editGeneration) return;
const candidateValidationEpoch = validationEpoch;
if (candidate.transition === "deactivate") {
queue(candidate);
return;
}
const source = candidate.transition === "restore_history" ? candidate.restore.applied : candidate.source;
patch({ status: "compiling" });
let compiled: CompileWorkerResponse;
try {
compiled = await options.compile(
compileInput(resumeData, source, candidate.generation, editorMetadata.semanticTree),
);
} catch {
if (candidateValidationEpoch !== validationEpoch) return;
if (destroyed || candidate.generation !== store.getState().editGeneration) return;
patch({ status: "error" });
return;
}
if (candidateValidationEpoch !== validationEpoch) return;
if (destroyed || compiled.editGeneration !== store.getState().editGeneration) return;
patch({ diagnostics: compiled.diagnostics, colorTokens: compiled.colorTokens ?? [] });
if (!compiled.program) {
if (candidate.transition === "edit_source") queue(candidate);
else patch({ status: "error" });
return;
}
if (compiled.program) {
patch({ status: "preflighting" });
let preflight: PreflightWorkerResponse;
try {
preflight = await options.preflight({
editGeneration: candidate.generation,
input: { data: resumeData, template: resumeData.metadata.template, stylesheet: source },
limits: {
maxPages: 20,
maxBytes: 10_000_000,
maxPageWidthPt: 2_000,
maxPageHeightPt: 20_000,
maxPageAreaPt2: 20_000_000,
},
});
} catch {
if (candidateValidationEpoch !== validationEpoch) return;
if (candidate.generation !== store.getState().editGeneration) return;
patch({ status: "error" });
if (candidate.transition === "edit_source") queue(candidate);
return;
}
if (candidateValidationEpoch !== validationEpoch) return;
if (destroyed || preflight.editGeneration !== store.getState().editGeneration) return;
if (!preflight.result.ok) {
patch({
diagnostics: [
...compiled.diagnostics,
...preflight.result.diagnostics,
preflightFailureDiagnostic(preflight.result),
],
status: "error",
});
if (candidate.transition !== "edit_source") return;
}
}
queue(candidate);
};
const schedule = (candidate: Candidate) => {
if (timer) clearTimeout(timer);
latestCandidate = candidate;
timer = setTimeout(() => {
timer = undefined;
void processCandidate(candidate);
}, debounceMs);
};
const restore = (target: SemanticStylesheet, opposite: "undoStack" | "redoStack") => {
const state = store.getState();
const stack = opposite === "undoStack" ? state.undoStack : state.redoStack;
const previous = stack.at(-1);
if (!previous) return;
const generation = state.editGeneration + 1;
const other = opposite === "undoStack" ? "redoStack" : "undoStack";
resetHistoryCoalescing();
patch({
source: previous.source,
editGeneration: generation,
colorTokens: [],
[opposite]: stack.slice(0, -1),
[other]: appendHistory(state[other], target),
canUndo: opposite === "redoStack" || stack.length > 1,
canRedo: opposite === "undoStack" || stack.length > 1,
});
schedule({ generation, transition: "restore_history", restore: previous });
};
store.setState({
resumeId: options.resumeId,
mode: initial.mode,
source: structuredClone(initial.source),
applied: structuredClone(initial.applied),
revision: options.initial.revision,
renderDataVersion: options.initial.renderDataVersion,
editGeneration: 0,
diagnostics: [],
colorTokens: [],
editorMetadata,
status: "idle",
restoreLocked: false,
focused: false,
undoStack: [],
redoStack: [],
canUndo: false,
canRedo: false,
setSourceText(text) {
const state = store.getState();
if (state.restoreLocked || text === state.source.text) return;
const generation = state.editGeneration + 1;
const nextSource = sourceFromText(state.source, text);
const now = Date.now();
const undoStack =
historyCanCoalesce && now - historyLastEditAt <= HISTORY_COALESCE_MS
? state.undoStack
: appendHistory(state.undoStack, currentStylesheet(state));
historyLastEditAt = now;
historyCanCoalesce = true;
patch({
source: nextSource,
editGeneration: generation,
colorTokens: [],
undoStack,
redoStack: [],
canUndo: true,
canRedo: false,
});
schedule({ generation, transition: "edit_source", source: nextSource });
},
setFocused(focused) {
patch({ focused });
if (focused || !deferredCanonical) return;
const canonical = deferredCanonical;
deferredCanonical = undefined;
const candidate = latestCandidate;
const hasLocalDraft =
candidate !== undefined && store.getState().source.text !== canonical.stylesheet.source.text;
replaceCanonical(canonical, hasLocalDraft);
if (hasLocalDraft && candidate) schedule(candidate);
else resetHistoryCoalescing();
},
activate() {
const state = store.getState();
if (state.restoreLocked || state.mode === "semantic") return;
const generation = state.editGeneration + 1;
resetHistoryCoalescing();
patch({
editGeneration: generation,
colorTokens: [],
undoStack: appendHistory(state.undoStack, currentStylesheet(state)),
redoStack: [],
canUndo: true,
canRedo: false,
});
schedule({ generation, transition: "activate", source: state.source });
},
deactivate() {
const state = store.getState();
if (state.restoreLocked || state.mode === "legacy") return;
const generation = state.editGeneration + 1;
resetHistoryCoalescing();
patch({
editGeneration: generation,
colorTokens: [],
undoStack: appendHistory(state.undoStack, currentStylesheet(state)),
redoStack: [],
canUndo: true,
canRedo: false,
});
queue({ generation, transition: "deactivate" });
},
undo() {
if (store.getState().restoreLocked) return;
restore(currentStylesheet(store.getState()), "undoStack");
},
redo() {
if (store.getState().restoreLocked) return;
restore(currentStylesheet(store.getState()), "redoStack");
},
refreshIntelligence() {
const state = store.getState();
const generation = state.editGeneration;
const source = structuredClone(state.source);
const requestEpoch = ++intelligenceEpoch;
void options
.compile(compileInput(resumeData, source, generation, editorMetadata.semanticTree))
.then((compiled) => {
const current = store.getState();
if (
destroyed ||
requestEpoch !== intelligenceEpoch ||
current.editGeneration !== generation ||
!sourcesEqual(current.source, source)
) {
return;
}
patch({ diagnostics: compiled.diagnostics, colorTokens: compiled.colorTokens ?? [] });
});
},
});
return {
store,
replaceResumeSnapshot(data: ResumeData, canonical: StylesheetCanonicalState) {
const candidate = latestCandidate;
resumeData = structuredClone(data);
editorMetadata = createEditorMetadata(resumeData);
patch({ editorMetadata });
const renderDataChanged = canonical.renderDataVersion > store.getState().renderDataVersion;
const preserveSource = store.getState().focused || isEditorFocused() || candidate !== undefined;
replaceCanonical(canonical, preserveSource);
if (renderDataChanged) {
validationEpoch += 1;
pending = undefined;
if (candidate) schedule(candidate);
}
},
rebaseCanonical(canonical: StylesheetCanonicalState) {
const candidate = latestCandidate;
const hasLocalDraft =
candidate !== undefined && store.getState().source.text !== canonical.stylesheet.source.text;
const focused = store.getState().focused || isEditorFocused();
const sourceChanged = store.getState().source.text !== canonical.stylesheet.source.text;
if (focused && sourceChanged && canonical.revision >= store.getState().revision) deferredCanonical = canonical;
const preserveSource = (focused && sourceChanged) || hasLocalDraft;
replaceCanonical(canonical, preserveSource);
if (hasLocalDraft && candidate) schedule(candidate);
else if (!preserveSource) resetHistoryCoalescing();
},
destroy() {
destroyed = true;
abortController.abort();
if (timer) clearTimeout(timer);
timer = undefined;
pending = undefined;
latestCandidate = undefined;
deferredCanonical = undefined;
options.destroy?.();
store.setState(inactiveState());
},
};
}
export const useStylesheetStore = create<StylesheetStoreState>(() => ({
...inactiveState(),
setSourceText: () => {},
setFocused: () => {},
activate: () => {},
deactivate: () => {},
undo: () => {},
redo: () => {},
refreshIntelligence: () => {},
}));
let activeRuntime: ReturnType<typeof createStylesheetStoreRuntime> | undefined;
declare const stylesheetRuntimeTokenBrand: unique symbol;
export type StylesheetRuntimeToken = Readonly<{ [stylesheetRuntimeTokenBrand]: true }>;
let activeRuntimeToken: StylesheetRuntimeToken | undefined;
const compilerClient = () =>
createCompileWorkerClient(
() =>
new Worker(new URL("./stylesheet.worker.ts", import.meta.url), { type: "module", name: "semantic-css-compiler" }),
);
const preflightClient = () =>
createPreflightWorkerClient(
() =>
new Worker(new URL("./preflight.worker.ts", import.meta.url), { type: "module", name: "semantic-css-preflight" }),
5_000,
);
export function initializeStylesheetStore(input: {
resumeId: string;
initial: StylesheetCanonicalState;
resumeData: ResumeData;
}) {
activeRuntime?.destroy();
const compiler = compilerClient();
const preflight = preflightClient();
preflight.warmup();
const runtime = createStylesheetStoreRuntime({
...input,
store: useStylesheetStore,
compile: compiler.compile,
preflight: preflight.preflight,
mutate: (mutation, signal) => orpc.resume.stylesheet.mutate.call(mutation, { signal }),
destroy: () => {
compiler.destroy();
preflight.destroy();
},
});
activeRuntime = runtime;
activeRuntimeToken = {} as StylesheetRuntimeToken;
return () => {
if (activeRuntime?.store.getState().resumeId !== input.resumeId) return;
activeRuntime.destroy();
activeRuntime = undefined;
activeRuntimeToken = undefined;
};
}
export function lockStylesheetStoreForRestore(resumeId: string): StylesheetRuntimeToken | undefined {
if (!activeRuntime || !activeRuntimeToken) return;
const state = activeRuntime.store.getState();
if (state.resumeId !== resumeId || state.restoreLocked) return;
activeRuntime.store.setState({ restoreLocked: true });
return activeRuntimeToken;
}
export function unlockStylesheetStoreAfterRestore(token: StylesheetRuntimeToken | undefined): boolean {
if (!activeRuntime || !token || activeRuntimeToken !== token) return false;
activeRuntime.store.setState({ restoreLocked: false });
return true;
}
export function replaceStylesheetStoreAfterRestore(input: {
resumeId: string;
initial: StylesheetCanonicalState;
resumeData: ResumeData;
token: StylesheetRuntimeToken | undefined;
}): boolean {
if (
!activeRuntime ||
!input.token ||
activeRuntimeToken !== input.token ||
activeRuntime.store.getState().resumeId !== input.resumeId ||
!activeRuntime.store.getState().restoreLocked
) {
return false;
}
initializeStylesheetStore(input);
return true;
}
export async function refreshStylesheetStore(resumeId: string, resumeData?: ResumeData) {
if (!activeRuntime || activeRuntime.store.getState().resumeId !== resumeId) return;
const canonical = await orpc.resume.stylesheet.getState.call({ id: resumeId });
if (resumeData) activeRuntime.replaceResumeSnapshot(resumeData, canonical);
else activeRuntime.rebaseCanonical(canonical);
}
@@ -0,0 +1,72 @@
// @vitest-environment happy-dom
import type { CompileWorkerRequest, CompileWorkerResponse } from "./protocol";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
function request(source: string): CompileWorkerRequest {
return {
type: "compile",
requestId: 1,
editGeneration: 1,
source: { languageVersion: 1, text: source },
semanticTree: {
key: "resume",
kind: "resume",
attributes: {},
roles: [],
children: [{ key: "name", kind: "name", attributes: {}, roles: [], children: [] }],
},
baseSettings: {
picture: defaultResumeData.picture,
template: defaultResumeData.metadata.template,
design: defaultResumeData.metadata.design,
typography: defaultResumeData.metadata.typography,
page: defaultResumeData.metadata.page,
layout: { sidebarWidth: defaultResumeData.metadata.layout.sidebarWidth },
},
pages: [{ pageKey: "page-1", width: 595.28, height: 841.89 }],
};
}
describe("stylesheet worker", () => {
let handleMessage: ((event: MessageEvent<CompileWorkerRequest>) => void) | undefined;
const postMessage = vi.fn();
beforeEach(async () => {
handleMessage = undefined;
postMessage.mockReset();
vi.resetModules();
vi.stubGlobal("self", {
addEventListener: vi.fn((type: string, listener: (event: MessageEvent<CompileWorkerRequest>) => void) => {
if (type === "message") handleMessage = listener;
}),
postMessage,
});
await import("./stylesheet.worker");
});
it("returns diagnostics from variable resolution", () => {
handleMessage?.(
new MessageEvent("message", {
data: request("@version 1;\nname { color: var(--missing); }\n"),
}),
);
const response = postMessage.mock.calls[0]?.[0] as CompileWorkerResponse | undefined;
expect(response?.diagnostics).toEqual(
expect.arrayContaining([expect.objectContaining({ code: "UNRESOLVED_VARIABLE", severity: "error" })]),
);
});
it("returns overlapping semantic diagnostics once", () => {
handleMessage?.(
new MessageEvent("message", {
data: request('@version 1;\nsection[type="education"] { color: red; }\n'),
}),
);
const response = postMessage.mock.calls[0]?.[0] as CompileWorkerResponse | undefined;
expect(response?.diagnostics.filter(({ code }) => code === "SELECTOR_NO_MATCH")).toHaveLength(1);
});
});
@@ -1,14 +1,21 @@
/// <reference lib="webworker" />
import type { CompileWorkerRequest, CompileWorkerResponse } from "./protocol";
import { analyzeStylesheet, compileStylesheet } from "@reactive-resume/resume/stylesheet";
import { compileStylesheet, resolveStylesheet } from "@reactive-resume/resume/stylesheet";
import { collectCompiledColorTokens } from "./color-tokens";
self.addEventListener("message", ({ data }: MessageEvent<CompileWorkerRequest>) => {
if (data.type !== "compile") return;
const compiled = compileStylesheet(data.source);
const diagnostics = compiled.program
? [...compiled.diagnostics, ...analyzeStylesheet(compiled.program, data.semanticTree)]
? [
...compiled.diagnostics,
...resolveStylesheet(compiled.program, data.semanticTree, {
baseStyles: {},
baseSettings: data.baseSettings,
pages: data.pages,
}).diagnostics,
]
: compiled.diagnostics;
const response: CompileWorkerResponse = {
type: "compile_result",
@@ -1,6 +1,5 @@
import { t } from "@lingui/core/macro";
import {
ArrowCounterClockwiseIcon,
ArrowsInIcon,
ArrowsOutIcon,
ArrowUUpLeftIcon,
@@ -43,7 +42,6 @@ export type StylesheetToolbarProps = {
onUndo(): void;
onRedo(): void;
onFormat(): void;
onReset(): void;
onFocusToggle(): void;
};
@@ -56,7 +54,6 @@ export function StylesheetToolbar({
onUndo,
onRedo,
onFormat,
onReset,
onFocusToggle,
}: StylesheetToolbarProps) {
return (
@@ -73,9 +70,6 @@ export function StylesheetToolbar({
<ToolbarButton label={t`Format stylesheet`} disabled={disabled} onClick={onFormat}>
<MagicWandIcon data-icon="inline-start" />
</ToolbarButton>
<ToolbarButton label={t`Reset to applied stylesheet`} disabled={disabled} onClick={onReset}>
<ArrowCounterClockwiseIcon data-icon="inline-start" />
</ToolbarButton>
<ToolbarButton label={focused ? t`Exit focus mode` : t`Open focus mode`} onClick={onFocusToggle}>
{focused ? <ArrowsInIcon data-icon="inline-start" /> : <ArrowsOutIcon data-icon="inline-start" />}
</ToolbarButton>
@@ -1,6 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { getPreflightTransferables } from "./protocol";
import { createCompileWorkerClient, createPreflightWorkerClient } from "./worker-client";
import { createCompileWorkerClient } from "./worker-client";
type Listener = (event: MessageEvent) => void;
@@ -23,15 +22,13 @@ function worker() {
}
},
emitError(event: ErrorEvent) {
for (const listener of listeners.get("error") ?? []) {
listener(event);
}
for (const listener of listeners.get("error") ?? []) listener(event);
},
};
}
describe("stylesheet worker clients", () => {
it("resolves older compiler results so callers can generation-check without aborting", async () => {
describe("stylesheet compiler worker client", () => {
it("resolves every compiler result so the editor can generation-check", async () => {
const fake = worker();
const client = createCompileWorkerClient(() => fake);
const first = client.compile({ editGeneration: 1 } as never);
@@ -54,181 +51,14 @@ describe("stylesheet worker clients", () => {
await expect(pending).rejects.toThrow("Failed to load compiler worker");
});
it("terminates and recreates a timed-out preflight worker", async () => {
vi.useFakeTimers();
const first = worker();
const replacement = worker();
const createWorker = vi.fn().mockReturnValueOnce(first).mockReturnValueOnce(replacement);
const client = createPreflightWorkerClient(createWorker, 10);
const timedOut = client.preflight({ editGeneration: 1 } as never);
first.emit({ type: "preflight_ready" });
await vi.advanceTimersByTimeAsync(10);
await expect(timedOut).resolves.toMatchObject({
result: { ok: false, code: "STYLESHEET_PREFLIGHT_TIMEOUT" },
});
expect(first.terminate).toHaveBeenCalledOnce();
const next = client.preflight({ editGeneration: 2 } as never);
replacement.emit({ type: "preflight_ready" });
await vi.advanceTimersByTimeAsync(0);
replacement.emit({
type: "preflight_result",
requestId: 2,
editGeneration: 2,
result: { ok: true, pageCount: 1, byteCount: 4, diagnostics: [], pdf: new ArrayBuffer(4) },
});
await expect(next).resolves.toMatchObject({ requestId: 2 });
vi.useRealTimers();
});
it("warms the preflight worker before a request starts its deadline", () => {
it("terminates the compiler worker and rejects pending work on destroy", async () => {
const fake = worker();
const createWorker = vi.fn(() => fake);
const client = createPreflightWorkerClient(createWorker, 5_000);
client.warmup();
expect(createWorker).toHaveBeenCalledOnce();
expect(fake.addEventListener).toHaveBeenCalledTimes(2);
expect(fake.addEventListener).toHaveBeenCalledWith("message", expect.any(Function));
expect(fake.addEventListener).toHaveBeenCalledWith("error", expect.any(Function));
expect(fake.postMessage).not.toHaveBeenCalled();
});
it("waits for readiness without consuming the request deadline", async () => {
vi.useFakeTimers();
const fake = worker();
const client = createPreflightWorkerClient(() => fake, 5, 20);
const result = client.preflight({ editGeneration: 1 } as never);
await vi.advanceTimersByTimeAsync(5);
expect(fake.terminate).not.toHaveBeenCalled();
expect(fake.postMessage).not.toHaveBeenCalled();
fake.emit({ type: "preflight_ready" });
await vi.advanceTimersByTimeAsync(0);
expect(fake.postMessage).toHaveBeenCalledOnce();
await vi.advanceTimersByTimeAsync(5);
await expect(result).resolves.toMatchObject({ result: { code: "STYLESHEET_PREFLIGHT_TIMEOUT" } });
vi.useRealTimers();
});
it("rejects pending preflight work when the worker emits an error event", async () => {
const fake = worker();
const client = createPreflightWorkerClient(() => fake, 5_000);
const pending = client.preflight({ editGeneration: 1 } as never);
const outcome = pending.catch((error: unknown) => error);
fake.emit({ type: "preflight_ready" });
await Promise.resolve();
fake.emitError({ message: "Worker crashed" } as ErrorEvent);
expect(await outcome).toMatchObject({ message: "Worker crashed" });
expect(fake.terminate).toHaveBeenCalled();
});
it("rejects structured resume-data failures without waiting for the timeout", async () => {
vi.useFakeTimers();
const fake = worker();
const client = createPreflightWorkerClient(() => fake, 5_000);
const pending = client.preflight({ editGeneration: 1 } as never);
const outcome = pending.catch((error: unknown) => error);
fake.emit({ type: "preflight_ready" });
await vi.advanceTimersByTimeAsync(0);
fake.emit({
type: "preflight_error",
requestId: 1,
editGeneration: 1,
cause: {
name: "ZodError",
message: "Invalid resume data",
issues: [{ path: ["customSections", 0, "items", 0, "company"] }],
},
});
expect(await outcome).toMatchObject({
name: "ZodError",
issues: [{ path: ["customSections", 0, "items", 0, "company"] }],
});
await vi.advanceTimersByTimeAsync(5_000);
expect(fake.terminate).not.toHaveBeenCalled();
vi.useRealTimers();
});
it("bounds readiness, recreates once, and rejects after the retry also times out", async () => {
vi.useFakeTimers();
const first = worker();
const replacement = worker();
const client = createPreflightWorkerClient(
vi.fn().mockReturnValueOnce(first).mockReturnValueOnce(replacement),
5,
10,
);
const result = client.preflight({ editGeneration: 1 } as never);
const rejection = expect(result).rejects.toThrow("did not become ready");
await vi.advanceTimersByTimeAsync(10);
expect(first.terminate).toHaveBeenCalledOnce();
await vi.advanceTimersByTimeAsync(10);
await rejection;
expect(replacement.terminate).toHaveBeenCalledOnce();
vi.useRealTimers();
});
it("does not recreate a warming worker after destroy", async () => {
vi.useFakeTimers();
const fake = worker();
const createWorker = vi.fn(() => fake);
const client = createPreflightWorkerClient(createWorker, 5, 10);
client.warmup();
const client = createCompileWorkerClient(() => fake);
const pending = client.compile({ editGeneration: 1 } as never);
client.destroy();
await vi.runAllTimersAsync();
expect(createWorker).toHaveBeenCalledOnce();
expect(fake.terminate).toHaveBeenCalledOnce();
vi.useRealTimers();
});
it("terminates stale preflight work when a newer request starts", async () => {
const first = worker();
const replacement = worker();
const client = createPreflightWorkerClient(
vi.fn().mockReturnValueOnce(first).mockReturnValueOnce(replacement),
1_000,
);
const stale = client.preflight({ editGeneration: 1 } as never);
first.emit({ type: "preflight_ready" });
await Promise.resolve();
const staleOutcome = stale.catch((error: unknown) => error);
const current = client.preflight({ editGeneration: 2 } as never);
expect(first.terminate).toHaveBeenCalledOnce();
replacement.emit({ type: "preflight_ready" });
await Promise.resolve();
replacement.emit({
type: "preflight_result",
requestId: 2,
editGeneration: 2,
result: { ok: true, pageCount: 1, byteCount: 4, diagnostics: [], pdf: new ArrayBuffer(4) },
});
expect(await staleOutcome).toEqual(expect.objectContaining({ message: expect.stringContaining("stale") }));
await expect(current).resolves.toMatchObject({ requestId: 2 });
});
it("transfers the generated PDF buffer", () => {
const pdf = new ArrayBuffer(4);
expect(
getPreflightTransferables({
type: "preflight_result",
requestId: 1,
editGeneration: 1,
result: { ok: true, pageCount: 1, byteCount: 4, diagnostics: [], pdf },
}),
).toEqual([pdf]);
await expect(pending).rejects.toThrow("terminated");
});
});
@@ -1,13 +1,4 @@
import type {
CompileWorkerInput,
CompileWorkerRequest,
CompileWorkerResponse,
PreflightWorkerError,
PreflightWorkerInput,
PreflightWorkerReady,
PreflightWorkerRequest,
PreflightWorkerResponse,
} from "./protocol";
import type { CompileWorkerInput, CompileWorkerRequest, CompileWorkerResponse } from "./protocol";
type WorkerListener = (event: MessageEvent<unknown>) => void;
type WorkerErrorListener = (event: ErrorEvent) => void;
@@ -67,164 +58,3 @@ export function createCompileWorkerClient(createWorker: () => StylesheetWorker)
},
};
}
const timeoutResult = (request: PreflightWorkerRequest): PreflightWorkerResponse => ({
type: "preflight_result",
requestId: request.requestId,
editGeneration: request.editGeneration,
result: {
ok: false,
code: "STYLESHEET_PREFLIGHT_TIMEOUT",
message: "The PDF preflight exceeded its deadline.",
diagnostics: [],
},
});
export function createPreflightWorkerClient(
createWorker: () => StylesheetWorker,
timeoutMs: number,
readinessTimeoutMs = 10_000,
) {
let worker: StylesheetWorker | undefined;
let requestId = 0;
let ready = false;
let destroyed = false;
let readiness:
| (Pending<StylesheetWorker> & {
promise: Promise<StylesheetWorker>;
timer: ReturnType<typeof setTimeout>;
})
| undefined;
const pending = new Map<number, Pending<PreflightWorkerResponse> & { timer?: ReturnType<typeof setTimeout> }>();
const onMessage: WorkerListener = ({ data }) => {
if ((data as PreflightWorkerReady)?.type === "preflight_ready") {
if (!worker || !readiness) return;
clearTimeout(readiness.timer);
ready = true;
readiness.resolve(worker);
readiness = undefined;
return;
}
const workerError = data as PreflightWorkerError;
if (workerError?.type === "preflight_error") {
const request = pending.get(workerError.requestId);
if (!request) return;
if (request.timer) clearTimeout(request.timer);
pending.delete(workerError.requestId);
request.reject(
Object.assign(new Error(workerError.cause.message), {
name: workerError.cause.name,
issues: workerError.cause.issues,
}),
);
return;
}
const response = data as PreflightWorkerResponse;
if (response?.type !== "preflight_result") return;
const request = pending.get(response.requestId);
if (!request) return;
if (request.timer) clearTimeout(request.timer);
pending.delete(response.requestId);
request.resolve(response);
};
const failPending = (error: Error) => {
for (const request of pending.values()) {
if (request.timer) clearTimeout(request.timer);
request.reject(error);
}
pending.clear();
};
const onError: WorkerErrorListener = (event) => {
const error = new Error(event.message || "Stylesheet preflight worker failed.");
terminate();
failPending(error);
};
const terminate = () => {
if (!worker) return;
worker.removeEventListener("message", onMessage);
worker.removeEventListener("error", onError);
worker.terminate();
worker = undefined;
ready = false;
if (readiness) {
clearTimeout(readiness.timer);
readiness.reject(new Error("Stylesheet preflight worker did not become ready."));
readiness = undefined;
}
};
const getReadyWorker = () => {
if (destroyed) return Promise.reject(new Error("Stylesheet preflight worker was terminated."));
if (worker && ready) return Promise.resolve(worker);
if (readiness) return readiness.promise;
worker = createWorker();
worker.addEventListener("message", onMessage);
worker.addEventListener("error", onError);
let resolve!: (value: StylesheetWorker) => void;
let reject!: (error: Error) => void;
const promise = new Promise<StylesheetWorker>((resolvePromise, rejectPromise) => {
resolve = resolvePromise;
reject = rejectPromise;
});
const timer = setTimeout(() => terminate(), readinessTimeoutMs);
readiness = { promise, resolve, reject, timer };
return promise;
};
const waitUntilReady = async () => {
try {
return await getReadyWorker();
} catch {
return await getReadyWorker();
}
};
return {
warmup() {
void waitUntilReady().catch(() => {});
},
preflight(input: PreflightWorkerInput): Promise<PreflightWorkerResponse> {
if (pending.size > 0) {
terminate();
for (const stale of pending.values()) {
if (stale.timer) clearTimeout(stale.timer);
stale.reject(new Error("Discarded stale stylesheet preflight result."));
}
pending.clear();
}
const request: PreflightWorkerRequest = { ...input, type: "preflight", requestId: ++requestId };
return new Promise((resolve, reject) => {
pending.set(request.requestId, { resolve, reject });
void waitUntilReady()
.then((readyWorker) => {
const current = pending.get(request.requestId);
if (!current) return;
current.timer = setTimeout(() => {
pending.delete(request.requestId);
terminate();
resolve(timeoutResult(request));
}, timeoutMs);
readyWorker.postMessage(request);
})
.catch((error: unknown) => {
const current = pending.get(request.requestId);
if (!current) return;
pending.delete(request.requestId);
reject(error instanceof Error ? error : new Error("Stylesheet preflight worker failed to start."));
});
});
},
destroy() {
destroyed = true;
terminate();
for (const request of pending.values()) {
if (request.timer) clearTimeout(request.timer);
request.reject(new Error("Stylesheet preflight worker was terminated."));
}
pending.clear();
},
};
}
@@ -17,11 +17,6 @@ import {
DropdownMenuTrigger,
} from "@reactive-resume/ui/components/dropdown-menu";
import { useResumeStore } from "@/features/resume/builder/draft";
import {
lockStylesheetStoreForRestore,
replaceStylesheetStoreAfterRestore,
unlockStylesheetStoreAfterRestore,
} from "@/features/resume/stylesheet/store";
import { useConfirm } from "@/hooks/use-confirm";
import { getResumeErrorMessage } from "@/libs/error-message";
import { formatRelativeTime } from "@/libs/locale";
@@ -53,26 +48,13 @@ export function BuilderVersionHistory({ resumeId }: BuilderVersionHistoryProps)
if (!confirmed) return;
const token = lockStylesheetStoreForRestore(resumeId);
if (!token) return;
try {
const restored = await restoreVersion({ resumeId, versionId });
const applied = replaceStylesheetStoreAfterRestore({
resumeId,
resumeData: restored.resume.data,
initial: restored.stylesheetState,
token,
});
if (!applied) {
unlockStylesheetStoreAfterRestore(token);
return;
}
replaceResumeFromServer(restored.resume as Resume);
queryClient.setQueryData(orpc.resume.getById.queryOptions({ input: { id: resumeId } }).queryKey, restored.resume);
replaceResumeFromServer(restored as Resume);
queryClient.setQueryData(orpc.resume.getById.queryOptions({ input: { id: resumeId } }).queryKey, restored);
void queryClient.invalidateQueries({ queryKey: orpc.resume.listVersions.queryKey({ input: { resumeId } }) });
toast.success(t`Your resume has been restored to the selected version.`);
} catch (error) {
unlockStylesheetStoreAfterRestore(token);
toast.error(getResumeErrorMessage(error));
}
};
@@ -1,5 +1,6 @@
// @vitest-environment happy-dom
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { i18n } from "@lingui/core";
@@ -17,16 +18,8 @@ const resumeMock = vi.hoisted(() => ({
id: string;
name: string;
slug: string;
data: typeof defaultResumeData;
data: ResumeData;
},
stylesheet: {
resumeId: "r1" as string | undefined,
mode: "semantic" as "legacy" | "semantic",
source: { languageVersion: 1, text: "@version 1;\nname {" },
applied: { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" },
revision: 42,
renderDataVersion: 7,
},
}));
type SectionBaseProps = {
@@ -50,9 +43,6 @@ vi.mock("@/libs/resume/section-title-locale", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useResume: () => resumeMock.resume,
}));
vi.mock("@/features/resume/stylesheet/store", () => ({
useStylesheetStore: (selector: (state: typeof resumeMock.stylesheet) => unknown) => selector(resumeMock.stylesheet),
}));
const { ExportSectionBuilder } = await import("./export");
@@ -61,15 +51,12 @@ beforeAll(() => {
});
beforeEach(() => {
resumeMock.resume = { id: "r1", name: "My Resume", slug: "my-resume", data: defaultResumeData };
resumeMock.stylesheet = {
resumeId: "r1",
const data = structuredClone(defaultResumeData);
data.metadata.stylesheet = {
mode: "semantic",
source: { languageVersion: 1, text: "@version 1;\nname {" },
applied: { languageVersion: 1, text: "@version 1;\nname { color: #123456; }\n" },
revision: 42,
renderDataVersion: 7,
};
resumeMock.resume = { id: "r1", name: "My Resume", slug: "my-resume", data };
});
afterEach(() => {
@@ -115,7 +102,7 @@ describe("ExportSectionBuilder", () => {
expect(filename).toBe("My Resume.md");
});
it("downloads canonical stylesheet content in JSON without concurrency metadata", async () => {
it("downloads the current stylesheet source in JSON", async () => {
renderExport();
openDialog();
fireEvent.click(screen.getByRole("button", { name: "Download JSON" }));
@@ -127,13 +114,7 @@ describe("ExportSectionBuilder", () => {
expect((blob as Blob).type).toBe("application/json");
expect(filename).toBe("My Resume.json");
const exported = JSON.parse(await (blob as Blob).text());
expect(exported.metadata.stylesheet).toEqual({
mode: "semantic",
source: resumeMock.stylesheet.source,
applied: resumeMock.stylesheet.applied,
});
expect(JSON.stringify(exported)).not.toContain("revision");
expect(JSON.stringify(exported)).not.toContain("renderDataVersion");
expect(exported.metadata.stylesheet).toEqual(resumeMock.resume?.data.metadata.stylesheet);
});
it("calls buildDocx and downloads the resulting blob when DOCX is clicked", async () => {
@@ -155,12 +136,7 @@ describe("ExportSectionBuilder", () => {
await Promise.resolve();
expect(createResumePdfBlob).toHaveBeenCalledTimes(1);
expect(createResumePdfBlob).toHaveBeenCalledWith(defaultResumeData, undefined, undefined, {
stylesheet: {
mode: "semantic",
applied: resumeMock.stylesheet.applied,
},
});
expect(createResumePdfBlob).toHaveBeenCalledWith(resumeMock.resume?.data, undefined, undefined);
expect(downloadWithAnchor).toHaveBeenCalledTimes(1);
expect(downloadWithAnchor.mock.calls[0]?.[1]).toBe("My Resume.pdf");
});
@@ -1,10 +1,9 @@
import type { BuilderLayout } from "./-store/sidebar";
import { useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { useEffect, useRef } from "react";
import { useEffect } from "react";
import { useMediaQuery } from "usehooks-ts";
import { useBuilderResumeUpdateSubscription, useResumeCleanup, useResumeStore } from "@/features/resume/builder/draft";
import { initializeStylesheetStore, useStylesheetStore } from "@/features/resume/stylesheet/store";
import { orpc } from "@/libs/orpc/client";
import { createNoindexFollowMeta } from "@/libs/seo";
import { DesktopBuilderShell } from "./-components/desktop-builder-shell";
@@ -21,9 +20,6 @@ export const Route = createFileRoute("/builder/$resumeId")({
const [layout, resume] = await Promise.all([
getBuilderLayout(),
context.queryClient.ensureQueryData(orpc.resume.getById.queryOptions({ input: { id: params.resumeId } })),
context.queryClient.ensureQueryData(
orpc.resume.stylesheet.getState.queryOptions({ input: { id: params.resumeId } }),
),
]);
return { layout, name: resume.name };
@@ -40,17 +36,11 @@ function RouteComponent() {
const { resumeId } = Route.useParams();
const { data: resume } = useSuspenseQuery(orpc.resume.getById.queryOptions({ input: { id: resumeId } }));
const { data: stylesheet } = useSuspenseQuery(
orpc.resume.stylesheet.getState.queryOptions({ input: { id: resumeId } }),
);
const initializeResumeStore = useResumeStore((state) => state.initialize);
const mergeResumeMetadata = useResumeStore((state) => state.mergeResumeMetadata);
const isReady = useResumeStore((state) => state.isReady);
const initializedResumeId = useResumeStore((state) => state.resumeId);
const isInitialized = isReady && initializedResumeId === resumeId;
const isStylesheetInitialized = useStylesheetStore((state) => state.resumeId === resumeId);
const stylesheetInitialization = useRef({ resume, stylesheet });
stylesheetInitialization.current = { resume, stylesheet };
useResumeCleanup();
useBuilderResumeUpdateSubscription();
@@ -60,16 +50,6 @@ function RouteComponent() {
initializeResumeStore(resume);
}, [initializeResumeStore, isInitialized, resume]);
useEffect(() => {
if (!isInitialized) return;
const initial = stylesheetInitialization.current;
return initializeStylesheetStore({
resumeId,
initial: initial.stylesheet,
resumeData: initial.resume.data,
});
}, [isInitialized, resumeId]);
useEffect(() => {
mergeResumeMetadata(resume);
}, [
@@ -85,7 +65,7 @@ function RouteComponent() {
resume,
]);
if (!isInitialized || !isStylesheetInitialized) return null;
if (!isInitialized) return null;
return <BuilderLayoutShell initialLayout={initialLayout} />;
}
+11 -11
View File
@@ -50,12 +50,12 @@ every stylesheet.
<Step title="Paste one focused rule">
Add the stylesheet to the editor. Start with one visual change so it is easy to review in the preview.
</Step>
<Step title="Wait for Applied">
Reactive Resume checks the source and the PDF result. When the status changes to **Applied**, compare the preview
and export if you are ready to share the resume.
<Step title="Wait for Valid">
Reactive Resume checks the source in the browser. When the status changes to **Valid**, compare the preview and
export if you are ready to share the resume.
</Step>
<Step title="Build on the working rule">
Add one related change at a time. The editor keeps your draft, undo history, and last valid stylesheet separately.
Add one related change at a time. Your changes use the normal resume autosave and undo history.
</Step>
</Steps>
@@ -300,19 +300,19 @@ browser viewport.
Supported media features are `width`, `min-width`, `max-width`, `height`, `min-height`, `max-height`, and
`orientation: portrait` or `orientation: landscape`.
## Apply, diagnose, and recover safely
## Diagnose and recover safely
The editor saves your draft even when it has an error. The preview and PDF export continue using the last stylesheet
that compiled and passed PDF checks, so a mistake does not replace a working result.
The editor saves the current source even when it has an error. A recoverable error ignores only the invalid declaration,
value, selector, or rule; valid parts still appear in preview and PDF export. A fatal version or resource-limit error
ignores the whole stylesheet and renders the resume with its base styles until you fix the source.
If a rule does not work:
1. Read the status below the editor. Errors include a line and column number when available.
2. Check the selector's spelling, attribute value, placement, and template guard. A **selector matches nothing**
warning usually means the resume does not contain that semantic node.
3. Simplify the rule to one selector and one declaration, then wait for **Applied** before adding more.
4. Use **Reset to applied stylesheet** to discard the current draft, or use the stylesheet undo and redo controls to
restore an earlier source and applied pair.
3. Simplify the rule to one selector and one declaration, then wait for **Valid** before adding more.
4. Use the stylesheet undo and redo controls to restore an earlier source.
Select **Open focus mode** when you need a taller editor. On mobile, it opens a full-width sheet; switch to
**Preview** to inspect the result.
@@ -331,7 +331,7 @@ starting point. Exact IDs and template parts are intentionally specific to a res
2. Open **Design -> Custom Styles** in the destination resume.
3. Paste the stylesheet and review any warnings.
4. Replace or remove exact IDs and template-part rules that do not apply.
5. Wait for **Applied**, then compare the preview and exported PDF.
5. Wait for **Valid**, then compare the preview and exported PDF.
Semantic CSS does not support classes, pseudo-elements, CSS Grid, arbitrary at-rules, `@import`, `@font-face`, `url()`,
browser APIs, animations, filters, gradients, general box shadows, or external assets. Use the normal builder settings

Some files were not shown because too many files have changed in this diff Show More