mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(i18n): 21-language pipeline, landing/docs/API wiring, landing+API translations
Shared Claude Code translation pipeline (scripts/i18n, no API key) plus Astro/VitePress/Scalar i18n wiring. Landing and API reference translated into all 20 languages; docs i18n wiring + English source anchors. The translated docs markdown (apps/docs/<locale>/**, 3,620 files) follows in a companion PR because it exceeds GitHub's per-PR CI file limit.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
// tests/unit/scripts/i18n/landing-seo.test.ts
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { makeLandingSeoAdapter } from "../../../../scripts/i18n/adapters/landing-seo.mjs";
|
||||
import { hash } from "../../../../scripts/i18n/lib/hash.mjs";
|
||||
|
||||
let dir: string;
|
||||
|
||||
const ALTERNATIVES = [
|
||||
{
|
||||
slug: "smallpdf",
|
||||
pageTitle: "Alternative to Smallpdf",
|
||||
h1: "The alternative to Smallpdf",
|
||||
metaDescription: "Self-hosted PDF tools.",
|
||||
intro: "Smallpdf is hosted. SnapOtter is self-hosted.",
|
||||
breadth: "One stack, five file types.",
|
||||
rows: [{ feature: "Deployment", snapotter: "Self-hosted", competitor: "Hosted" }],
|
||||
faqs: [{ q: "Is it free?", a: "Yes, AGPLv3." }],
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(async () => {
|
||||
dir = await mkdtemp(join(tmpdir(), "landing-seo-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function make() {
|
||||
return makeLandingSeoAdapter({
|
||||
dir,
|
||||
alternatives: ALTERNATIVES,
|
||||
});
|
||||
}
|
||||
|
||||
describe("landing-seo adapter", () => {
|
||||
it("extracts only alternatives prose units with stable indexed ids", async () => {
|
||||
const units = await make().extract();
|
||||
const ids = units.map((u) => u.id);
|
||||
expect(ids).toContain("alt:smallpdf:h1");
|
||||
expect(ids).toContain("alt:smallpdf:metaDescription");
|
||||
expect(ids).toContain("alt:smallpdf:rows.0.snapotter");
|
||||
expect(ids).toContain("alt:smallpdf:faqs.0.q");
|
||||
// Tool-detail pages are English-only, so no seo:* units are extracted.
|
||||
expect(ids.some((id) => id.startsWith("seo:"))).toBe(false);
|
||||
});
|
||||
|
||||
it("write then load round-trips StoredEntry with inline _sourceHash", async () => {
|
||||
const adapter = make();
|
||||
const entries = new Map([
|
||||
[
|
||||
"alt:smallpdf:h1",
|
||||
{
|
||||
text: "Die Alternative zu Smallpdf",
|
||||
sourceHash: hash("The alternative to Smallpdf"),
|
||||
provenance: "machine",
|
||||
outputHash: hash("Die Alternative zu Smallpdf"),
|
||||
stale: false,
|
||||
},
|
||||
],
|
||||
]);
|
||||
await adapter.write("de", entries);
|
||||
|
||||
const file = JSON.parse(await readFile(join(dir, "alternatives.de.json"), "utf8"));
|
||||
expect(file["alt:smallpdf:h1"].text).toBe("Die Alternative zu Smallpdf");
|
||||
expect(file["alt:smallpdf:h1"]._sourceHash).toBe(hash("The alternative to Smallpdf"));
|
||||
expect(file["alt:smallpdf:h1"].provenance).toBe("machine");
|
||||
|
||||
const loaded = await adapter.load("de");
|
||||
expect(loaded.get("alt:smallpdf:h1")).toEqual({
|
||||
text: "Die Alternative zu Smallpdf",
|
||||
sourceHash: hash("The alternative to Smallpdf"),
|
||||
provenance: "machine",
|
||||
outputHash: hash("Die Alternative zu Smallpdf"),
|
||||
stale: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes alt: ids to alternatives.<locale>.json and never a tool-seo file", async () => {
|
||||
const adapter = make();
|
||||
await adapter.write(
|
||||
"de",
|
||||
new Map([
|
||||
[
|
||||
"alt:smallpdf:intro",
|
||||
{
|
||||
text: "Smallpdf ist gehostet. SnapOtter ist selbst gehostet.",
|
||||
sourceHash: hash("Smallpdf is hosted. SnapOtter is self-hosted."),
|
||||
provenance: "machine",
|
||||
outputHash: hash("Smallpdf ist gehostet. SnapOtter ist selbst gehostet."),
|
||||
stale: false,
|
||||
},
|
||||
],
|
||||
]),
|
||||
);
|
||||
const alt = JSON.parse(await readFile(join(dir, "alternatives.de.json"), "utf8"));
|
||||
expect(alt["alt:smallpdf:intro"].text).toBe(
|
||||
"Smallpdf ist gehostet. SnapOtter ist selbst gehostet.",
|
||||
);
|
||||
// No tool-seo file is ever written now that tool-detail pages are English-only.
|
||||
await expect(readFile(join(dir, "tool-seo.de.json"), "utf8")).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user