Skip to content

Commit ec2902e

Browse files
authored
feat: generate json schema (#498)
1 parent 3f2b766 commit ec2902e

File tree

8 files changed

+451
-131
lines changed

8 files changed

+451
-131
lines changed

.changeset/silly-kangaroos-pay.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@lingo.dev/_spec": patch
3+
"lingo.dev": patch
4+
---
5+
6+
build json schema for config

packages/cli/i18n.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@
4848
"yaml-root-key": {
4949
"include": ["demo/yaml-root-key/[locale].yml"]
5050
}
51-
}
51+
},
52+
"$schema": "https://lingo.dev/schema/i18n.json"
5253
}

packages/spec/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@
1717
"scripts": {
1818
"dev": "tsup --watch",
1919
"build": "tsc --noEmit && tsup",
20-
"test": "vitest run"
20+
"test": "vitest run",
21+
"test:watch": "vitest"
2122
},
2223
"keywords": [],
2324
"author": "",
2425
"license": "Apache-2.0",
2526
"dependencies": {
2627
"typescript": "^5.7.2",
2728
"vitest": "^2.1.8",
28-
"zod": "^3.24.1"
29+
"zod": "^3.24.1",
30+
"zod-to-json-schema": "^3.24.3"
2931
},
3032
"devDependencies": {
33+
"@types/node": "^22.13.5",
3134
"tsup": "^8.3.5"
3235
}
3336
}

packages/spec/src/config.spec.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ const createV1_2Config = () => ({
4242
version: 1.2,
4343
});
4444

45+
const createV1_3Config = () => ({
46+
...createV1_2Config(),
47+
version: 1.3,
48+
});
49+
50+
const createV1_4Config = () => ({
51+
...createV1_3Config(),
52+
version: 1.4,
53+
$schema: "https://lingo.dev/schema/i18n.json",
54+
});
55+
4556
const createInvalidLocaleConfig = () => ({
4657
version: 1,
4758
locale: {
@@ -55,20 +66,22 @@ const createInvalidLocaleConfig = () => ({
5566
});
5667

5768
describe("I18n Config Parser", () => {
58-
it("should upgrade v0 config to v1.2", () => {
69+
it("should upgrade v0 config to latest version", () => {
5970
const v0Config = createV0Config();
6071
const result = parseI18nConfig(v0Config);
6172

62-
expect(result.version).toBe(1.3);
73+
expect(result["$schema"]).toBeDefined();
74+
expect(result.version).toBe(1.4);
6375
expect(result.locale).toEqual(defaultConfig.locale);
6476
expect(result.buckets).toEqual({});
6577
});
6678

67-
it("should upgrade v1 config to v1.2", () => {
79+
it("should upgrade v1 config to latest version", () => {
6880
const v1Config = createV1Config();
6981
const result = parseI18nConfig(v1Config);
7082

71-
expect(result.version).toBe(1.3);
83+
expect(result["$schema"]).toBeDefined();
84+
expect(result.version).toBe(1.4);
7285
expect(result.locale).toEqual(v1Config.locale);
7386
expect(result.buckets).toEqual({
7487
json: {
@@ -80,13 +93,6 @@ describe("I18n Config Parser", () => {
8093
});
8194
});
8295

83-
it("should not modify v1.1 config", () => {
84-
const v1_1Config = createV1_1Config();
85-
const result = parseI18nConfig(v1_1Config);
86-
87-
expect(result).toEqual(v1_1Config);
88-
});
89-
9096
it("should throw an error for invalid configurations", () => {
9197
const invalidConfig = { version: "invalid" };
9298
expect(() => parseI18nConfig(invalidConfig)).toThrow("Failed to parse config");
@@ -101,13 +107,13 @@ describe("I18n Config Parser", () => {
101107

102108
it("should ignore extra fields in the config", () => {
103109
const configWithExtra = {
104-
...createV1_1Config(),
110+
...createV1_4Config(),
105111
extraField: "should be ignored",
106112
};
107113
const result = parseI18nConfig(configWithExtra);
108114

109115
expect(result).not.toHaveProperty("extraField");
110-
expect(result).toEqual(createV1_1Config());
116+
expect(result).toEqual(createV1_4Config());
111117
});
112118

113119
it("should throw an error for unsupported locales", () => {

packages/spec/src/config.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,29 @@ export const configV1_3Definition = extendConfigDefinition(configV1_2Definition,
200200
}),
201201
});
202202

203+
const configSchema = "https://lingo.dev/schema/i18n.json";
204+
205+
// v1.3 -> v1.4
206+
// Changes: Add $schema to the config
207+
export const configV1_4Definition = extendConfigDefinition(configV1_3Definition, {
208+
createSchema: (baseSchema) =>
209+
baseSchema.extend({
210+
$schema: Z.string().default(configSchema),
211+
}),
212+
createDefaultValue: (baseDefaultValue) => ({
213+
...baseDefaultValue,
214+
version: 1.4,
215+
$schema: configSchema,
216+
}),
217+
createUpgrader: (oldConfig) => ({
218+
...oldConfig,
219+
version: 1.4,
220+
$schema: configSchema,
221+
}),
222+
});
223+
203224
// exports
204-
const LATEST_CONFIG_DEFINITION = configV1_3Definition;
225+
export const LATEST_CONFIG_DEFINITION = configV1_4Definition;
205226

206227
export type I18nConfig = Z.infer<(typeof LATEST_CONFIG_DEFINITION)["schema"]>;
207228

packages/spec/src/json-schema.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import fs from "fs";
2+
import path from "path";
3+
import { fileURLToPath } from "url";
4+
import { zodToJsonSchema } from "zod-to-json-schema";
5+
import { LATEST_CONFIG_DEFINITION } from "./config";
6+
7+
export default function buildJsonSchema() {
8+
const configSchema = zodToJsonSchema(LATEST_CONFIG_DEFINITION.schema);
9+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
10+
fs.writeFileSync(`${currentDir}/../build/i18n.schema.json`, JSON.stringify(configSchema, null, 2));
11+
}

packages/spec/tsup.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { defineConfig } from "tsup";
2+
import buildJsonSchema from "./src/json-schema";
23

34
export default defineConfig({
45
clean: true,
@@ -12,4 +13,7 @@ export default defineConfig({
1213
outExtension: (ctx) => ({
1314
js: ctx.format === "cjs" ? ".cjs" : ".mjs",
1415
}),
16+
onSuccess: async () => {
17+
buildJsonSchema();
18+
},
1519
});

0 commit comments

Comments
 (0)