Files
SnapOtter/tests/integration/platform/docs-i18n.test.ts
T
SnapOtterandGitHub 00b651c9f8 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.
2026-07-11 13:01:55 +08:00

81 lines
3.3 KiB
TypeScript

// tests/integration/platform/docs-i18n.test.ts
import { rmSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import yaml from "js-yaml";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { buildTestApp, type TestApp } from "../test-server";
// The docs route resolves locale specs relative to apps/api/src (next to
// openapi.yaml). Write a small German fixture spec there so serve-by-lang has
// a real file to pick up, then clean it up. This avoids committing a full
// generated locale spec into the repo just for the test.
const apiSrc = join(dirname(fileURLToPath(import.meta.url)), "../../../apps/api/src");
const deSpecPath = join(apiSrc, "openapi.de.yaml");
const DE_SPEC = {
openapi: "3.1.0",
info: { title: "SnapOtter API", version: "2.0.0", description: "Deutsche Beschreibung." },
tags: [{ name: "Tools", description: "Werkzeuge." }],
paths: {
"/api/v1/tools/image/resize": {
post: { tags: ["Tools"], summary: "Groesse aendern", description: "Bild skalieren." },
},
},
"x-i18n": { locale: "de", entries: {} },
};
describe("API docs i18n serving", () => {
let testApp: TestApp;
beforeAll(async () => {
writeFileSync(deSpecPath, yaml.dump(DE_SPEC, { lineWidth: -1 }), "utf8");
testApp = await buildTestApp();
});
afterAll(async () => {
rmSync(deSpecPath, { force: true });
await testApp.cleanup();
});
it("serves the German spec for ?lang=de", async () => {
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml?lang=de" });
expect(res.statusCode).toBe(200);
expect(res.headers["content-type"]).toContain("text/yaml");
expect(res.body).toContain("Groesse aendern");
expect(res.body).toContain("locale: de");
});
it("falls back to English for an unsupported lang", async () => {
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml?lang=zz" });
expect(res.statusCode).toBe(200);
// English spec has the English summary, not the German one.
expect(res.body).toContain("openapi: 3.1.0");
expect(res.body).not.toContain("Groesse aendern");
});
it("falls back to English for a supported locale with no spec file", async () => {
// fr is supported but no openapi.fr.yaml exists.
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml?lang=fr" });
expect(res.statusCode).toBe(200);
expect(res.body).not.toContain("Groesse aendern");
});
it("keeps the default (no lang) response ASCII-only", async () => {
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml" });
const offending = [...res.body].find((ch) => ch.charCodeAt(0) > 0x7f);
expect(offending).toBeUndefined();
});
it("serves a localized llms.de.txt using translated tool strings", async () => {
const res = await testApp.app.inject({ method: "GET", url: "/llms.de.txt" });
expect(res.statusCode).toBe(200);
expect(res.headers["content-type"]).toContain("text/plain");
expect(res.body).toContain("## Tools");
// Tag prose comes from the German fixture spec.
expect(res.body).toContain("Werkzeuge.");
// Tool lines come from shared i18n; the Resize tool id is present with a mode.
expect(res.body).toContain("(resize, sync)");
});
});