@linguanahq/catalog 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context.d.ts +73 -0
- package/dist/icu-validation.d.ts +2 -0
- package/dist/index.d.ts +209 -0
- package/dist/index.js +356 -0
- package/dist/index.test.d.ts +1 -0
- package/package.json +25 -23
- package/src/index.ts +0 -219
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const contextProfileSchema: z.ZodObject<{
|
|
3
|
+
product: z.ZodDefault<z.ZodString>;
|
|
4
|
+
audience: z.ZodDefault<z.ZodString>;
|
|
5
|
+
tone: z.ZodDefault<z.ZodString>;
|
|
6
|
+
formality: z.ZodDefault<z.ZodEnum<{
|
|
7
|
+
neutral: "neutral";
|
|
8
|
+
formal: "formal";
|
|
9
|
+
informal: "informal";
|
|
10
|
+
}>>;
|
|
11
|
+
preferred: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
12
|
+
discouraged: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
13
|
+
conventions: z.ZodDefault<z.ZodString>;
|
|
14
|
+
localeOverrides: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
15
|
+
product: z.ZodOptional<z.ZodString>;
|
|
16
|
+
audience: z.ZodOptional<z.ZodString>;
|
|
17
|
+
tone: z.ZodOptional<z.ZodString>;
|
|
18
|
+
formality: z.ZodOptional<z.ZodEnum<{
|
|
19
|
+
neutral: "neutral";
|
|
20
|
+
formal: "formal";
|
|
21
|
+
informal: "informal";
|
|
22
|
+
}>>;
|
|
23
|
+
preferred: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
24
|
+
discouraged: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
25
|
+
conventions: z.ZodOptional<z.ZodString>;
|
|
26
|
+
}, z.core.$strip>>>;
|
|
27
|
+
glossary: z.ZodDefault<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
28
|
+
mode: z.ZodLiteral<"active">;
|
|
29
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
30
|
+
mode: z.ZodLiteral<"pinned">;
|
|
31
|
+
versionId: z.ZodString;
|
|
32
|
+
}, z.core.$strip>], "mode">>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export type ContextProfileContent = z.infer<typeof contextProfileSchema>;
|
|
35
|
+
export declare function resolveContextProfile(content: ContextProfileContent, locale: string): {
|
|
36
|
+
product: string;
|
|
37
|
+
audience: string;
|
|
38
|
+
tone: string;
|
|
39
|
+
formality: "neutral" | "formal" | "informal";
|
|
40
|
+
preferred: string[];
|
|
41
|
+
discouraged: string[];
|
|
42
|
+
conventions: string;
|
|
43
|
+
};
|
|
44
|
+
export declare const meaningSchema: z.ZodString;
|
|
45
|
+
export declare const runtimeItemSchema: z.ZodObject<{
|
|
46
|
+
source: z.ZodString;
|
|
47
|
+
format: z.ZodDefault<z.ZodEnum<{
|
|
48
|
+
plain: "plain";
|
|
49
|
+
icu: "icu";
|
|
50
|
+
}>>;
|
|
51
|
+
meaning: z.ZodDefault<z.ZodString>;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export type RuntimeItem = z.infer<typeof runtimeItemSchema>;
|
|
54
|
+
export declare const runtimeBatchSchema: z.ZodObject<{
|
|
55
|
+
projectId: z.ZodString;
|
|
56
|
+
environment: z.ZodString;
|
|
57
|
+
surface: z.ZodString;
|
|
58
|
+
sourceLocale: z.ZodString;
|
|
59
|
+
locale: z.ZodString;
|
|
60
|
+
items: z.ZodArray<z.ZodObject<{
|
|
61
|
+
source: z.ZodString;
|
|
62
|
+
format: z.ZodDefault<z.ZodEnum<{
|
|
63
|
+
plain: "plain";
|
|
64
|
+
icu: "icu";
|
|
65
|
+
}>>;
|
|
66
|
+
meaning: z.ZodDefault<z.ZodString>;
|
|
67
|
+
}, z.core.$strip>>;
|
|
68
|
+
}, z.core.$strip>;
|
|
69
|
+
export type RuntimeBatchInput = z.infer<typeof runtimeBatchSchema>;
|
|
70
|
+
export declare function runtimeContentKey(input: {
|
|
71
|
+
sourceLocale: string;
|
|
72
|
+
surface: string;
|
|
73
|
+
} & RuntimeItem): string;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const SCHEMA_VERSION: "1.0";
|
|
3
|
+
export declare const MANIFEST_SCHEMA_VERSION: "2.0";
|
|
4
|
+
export declare const messageLocationSchema: z.ZodObject<{
|
|
5
|
+
file: z.ZodString;
|
|
6
|
+
line: z.ZodNumber;
|
|
7
|
+
column: z.ZodNumber;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export declare const placeholderSchema: z.ZodObject<{
|
|
10
|
+
name: z.ZodString;
|
|
11
|
+
kind: z.ZodEnum<{
|
|
12
|
+
number: "number";
|
|
13
|
+
value: "value";
|
|
14
|
+
component: "component";
|
|
15
|
+
plural: "plural";
|
|
16
|
+
select: "select";
|
|
17
|
+
date: "date";
|
|
18
|
+
time: "time";
|
|
19
|
+
}>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export declare const messageContextSchema: z.ZodObject<{
|
|
22
|
+
kind: z.ZodEnum<{
|
|
23
|
+
text: "text";
|
|
24
|
+
attribute: "attribute";
|
|
25
|
+
explicit: "explicit";
|
|
26
|
+
}>;
|
|
27
|
+
element: z.ZodOptional<z.ZodString>;
|
|
28
|
+
attribute: z.ZodOptional<z.ZodString>;
|
|
29
|
+
developer: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
export declare const manifestMessageSchema: z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
source: z.ZodString;
|
|
34
|
+
normalized: z.ZodString;
|
|
35
|
+
contentHash: z.ZodString;
|
|
36
|
+
context: z.ZodObject<{
|
|
37
|
+
kind: z.ZodEnum<{
|
|
38
|
+
text: "text";
|
|
39
|
+
attribute: "attribute";
|
|
40
|
+
explicit: "explicit";
|
|
41
|
+
}>;
|
|
42
|
+
element: z.ZodOptional<z.ZodString>;
|
|
43
|
+
attribute: z.ZodOptional<z.ZodString>;
|
|
44
|
+
developer: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
placeholders: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
47
|
+
name: z.ZodString;
|
|
48
|
+
kind: z.ZodEnum<{
|
|
49
|
+
number: "number";
|
|
50
|
+
value: "value";
|
|
51
|
+
component: "component";
|
|
52
|
+
plural: "plural";
|
|
53
|
+
select: "select";
|
|
54
|
+
date: "date";
|
|
55
|
+
time: "time";
|
|
56
|
+
}>;
|
|
57
|
+
}, z.core.$strip>>>;
|
|
58
|
+
confidence: z.ZodEnum<{
|
|
59
|
+
explicit: "explicit";
|
|
60
|
+
high: "high";
|
|
61
|
+
}>;
|
|
62
|
+
locations: z.ZodArray<z.ZodObject<{
|
|
63
|
+
file: z.ZodString;
|
|
64
|
+
line: z.ZodNumber;
|
|
65
|
+
column: z.ZodNumber;
|
|
66
|
+
}, z.core.$strip>>;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
export declare const diagnosticSchema: z.ZodObject<{
|
|
69
|
+
code: z.ZodString;
|
|
70
|
+
severity: z.ZodEnum<{
|
|
71
|
+
error: "error";
|
|
72
|
+
info: "info";
|
|
73
|
+
warning: "warning";
|
|
74
|
+
}>;
|
|
75
|
+
message: z.ZodString;
|
|
76
|
+
location: z.ZodOptional<z.ZodObject<{
|
|
77
|
+
file: z.ZodString;
|
|
78
|
+
line: z.ZodNumber;
|
|
79
|
+
column: z.ZodNumber;
|
|
80
|
+
}, z.core.$strip>>;
|
|
81
|
+
remediation: z.ZodOptional<z.ZodString>;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
export declare const manifestIntegrationSchema: z.ZodObject<{
|
|
84
|
+
tool: z.ZodEnum<{
|
|
85
|
+
vite: "vite";
|
|
86
|
+
next: "next";
|
|
87
|
+
}>;
|
|
88
|
+
toolVersion: z.ZodString;
|
|
89
|
+
framework: z.ZodEnum<{
|
|
90
|
+
react: "react";
|
|
91
|
+
vue: "vue";
|
|
92
|
+
svelte: "svelte";
|
|
93
|
+
solid: "solid";
|
|
94
|
+
}>;
|
|
95
|
+
adapterVersion: z.ZodString;
|
|
96
|
+
ssr: z.ZodBoolean;
|
|
97
|
+
}, z.core.$strip>;
|
|
98
|
+
export declare const manifestSchema: z.ZodObject<{
|
|
99
|
+
schemaVersion: z.ZodEnum<{
|
|
100
|
+
"1.0": "1.0";
|
|
101
|
+
"2.0": "2.0";
|
|
102
|
+
}>;
|
|
103
|
+
compilerVersion: z.ZodString;
|
|
104
|
+
projectId: z.ZodString;
|
|
105
|
+
environment: z.ZodString;
|
|
106
|
+
sourceLocale: z.ZodString;
|
|
107
|
+
sourceRevision: z.ZodOptional<z.ZodString>;
|
|
108
|
+
integration: z.ZodOptional<z.ZodObject<{
|
|
109
|
+
tool: z.ZodEnum<{
|
|
110
|
+
vite: "vite";
|
|
111
|
+
next: "next";
|
|
112
|
+
}>;
|
|
113
|
+
toolVersion: z.ZodString;
|
|
114
|
+
framework: z.ZodEnum<{
|
|
115
|
+
react: "react";
|
|
116
|
+
vue: "vue";
|
|
117
|
+
svelte: "svelte";
|
|
118
|
+
solid: "solid";
|
|
119
|
+
}>;
|
|
120
|
+
adapterVersion: z.ZodString;
|
|
121
|
+
ssr: z.ZodBoolean;
|
|
122
|
+
}, z.core.$strip>>;
|
|
123
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
124
|
+
id: z.ZodString;
|
|
125
|
+
source: z.ZodString;
|
|
126
|
+
normalized: z.ZodString;
|
|
127
|
+
contentHash: z.ZodString;
|
|
128
|
+
context: z.ZodObject<{
|
|
129
|
+
kind: z.ZodEnum<{
|
|
130
|
+
text: "text";
|
|
131
|
+
attribute: "attribute";
|
|
132
|
+
explicit: "explicit";
|
|
133
|
+
}>;
|
|
134
|
+
element: z.ZodOptional<z.ZodString>;
|
|
135
|
+
attribute: z.ZodOptional<z.ZodString>;
|
|
136
|
+
developer: z.ZodOptional<z.ZodString>;
|
|
137
|
+
}, z.core.$strip>;
|
|
138
|
+
placeholders: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
139
|
+
name: z.ZodString;
|
|
140
|
+
kind: z.ZodEnum<{
|
|
141
|
+
number: "number";
|
|
142
|
+
value: "value";
|
|
143
|
+
component: "component";
|
|
144
|
+
plural: "plural";
|
|
145
|
+
select: "select";
|
|
146
|
+
date: "date";
|
|
147
|
+
time: "time";
|
|
148
|
+
}>;
|
|
149
|
+
}, z.core.$strip>>>;
|
|
150
|
+
confidence: z.ZodEnum<{
|
|
151
|
+
explicit: "explicit";
|
|
152
|
+
high: "high";
|
|
153
|
+
}>;
|
|
154
|
+
locations: z.ZodArray<z.ZodObject<{
|
|
155
|
+
file: z.ZodString;
|
|
156
|
+
line: z.ZodNumber;
|
|
157
|
+
column: z.ZodNumber;
|
|
158
|
+
}, z.core.$strip>>;
|
|
159
|
+
}, z.core.$strip>>;
|
|
160
|
+
diagnostics: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
161
|
+
code: z.ZodString;
|
|
162
|
+
severity: z.ZodEnum<{
|
|
163
|
+
error: "error";
|
|
164
|
+
info: "info";
|
|
165
|
+
warning: "warning";
|
|
166
|
+
}>;
|
|
167
|
+
message: z.ZodString;
|
|
168
|
+
location: z.ZodOptional<z.ZodObject<{
|
|
169
|
+
file: z.ZodString;
|
|
170
|
+
line: z.ZodNumber;
|
|
171
|
+
column: z.ZodNumber;
|
|
172
|
+
}, z.core.$strip>>;
|
|
173
|
+
remediation: z.ZodOptional<z.ZodString>;
|
|
174
|
+
}, z.core.$strip>>>;
|
|
175
|
+
}, z.core.$strip>;
|
|
176
|
+
export declare const catalogSchema: z.ZodObject<{
|
|
177
|
+
schemaVersion: z.ZodLiteral<"1.0">;
|
|
178
|
+
projectId: z.ZodString;
|
|
179
|
+
environment: z.ZodString;
|
|
180
|
+
sourceLocale: z.ZodString;
|
|
181
|
+
locale: z.ZodString;
|
|
182
|
+
manifestHash: z.ZodString;
|
|
183
|
+
version: z.ZodNumber;
|
|
184
|
+
messages: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
185
|
+
integrity: z.ZodString;
|
|
186
|
+
}, z.core.$strip>;
|
|
187
|
+
export type MessageLocation = z.infer<typeof messageLocationSchema>;
|
|
188
|
+
export type Placeholder = z.infer<typeof placeholderSchema>;
|
|
189
|
+
export type MessageContext = z.infer<typeof messageContextSchema>;
|
|
190
|
+
export type ManifestMessage = z.infer<typeof manifestMessageSchema>;
|
|
191
|
+
export type Diagnostic = z.infer<typeof diagnosticSchema>;
|
|
192
|
+
export type Manifest = z.infer<typeof manifestSchema>;
|
|
193
|
+
export type ManifestIntegration = z.infer<typeof manifestIntegrationSchema>;
|
|
194
|
+
export type Catalog = z.infer<typeof catalogSchema>;
|
|
195
|
+
export declare function canonicalJson(value: unknown): string;
|
|
196
|
+
export declare function contentHash(value: unknown): string;
|
|
197
|
+
export declare function normalizeSource(source: string): string;
|
|
198
|
+
export declare function parseMessagePlaceholders(source: string): Placeholder[];
|
|
199
|
+
export declare function stableMessageId(input: {
|
|
200
|
+
sourceLocale: string;
|
|
201
|
+
source: string;
|
|
202
|
+
context: MessageContext;
|
|
203
|
+
explicitId?: string;
|
|
204
|
+
}): string;
|
|
205
|
+
export declare function manifestHash(manifest: Manifest): string;
|
|
206
|
+
export declare function createCatalog(input: Omit<Catalog, "integrity">): Catalog;
|
|
207
|
+
export declare function verifyCatalog(catalog: Catalog): boolean;
|
|
208
|
+
export * from "./context.ts";
|
|
209
|
+
export * from "./icu-validation.ts";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
parse as parse2,
|
|
4
|
+
TYPE as TYPE2
|
|
5
|
+
} from "@formatjs/icu-messageformat-parser";
|
|
6
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
7
|
+
import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
8
|
+
import { z as z2 } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/context.ts
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
var guidance = z.object({
|
|
13
|
+
product: z.string().max(2e3).default(""),
|
|
14
|
+
audience: z.string().max(1e3).default(""),
|
|
15
|
+
tone: z.string().max(1e3).default(""),
|
|
16
|
+
formality: z.enum(["neutral", "formal", "informal"]).default("neutral"),
|
|
17
|
+
preferred: z.array(z.string().max(512)).max(20).default([]),
|
|
18
|
+
discouraged: z.array(z.string().max(512)).max(20).default([]),
|
|
19
|
+
conventions: z.string().max(2e3).default("")
|
|
20
|
+
});
|
|
21
|
+
var contextProfileSchema = guidance.extend({
|
|
22
|
+
localeOverrides: z.record(
|
|
23
|
+
z.string(),
|
|
24
|
+
z.object({
|
|
25
|
+
product: guidance.shape.product.removeDefault().optional(),
|
|
26
|
+
audience: guidance.shape.audience.removeDefault().optional(),
|
|
27
|
+
tone: guidance.shape.tone.removeDefault().optional(),
|
|
28
|
+
formality: guidance.shape.formality.removeDefault().optional(),
|
|
29
|
+
preferred: guidance.shape.preferred.removeDefault().optional(),
|
|
30
|
+
discouraged: guidance.shape.discouraged.removeDefault().optional(),
|
|
31
|
+
conventions: guidance.shape.conventions.removeDefault().optional()
|
|
32
|
+
})
|
|
33
|
+
).default({}),
|
|
34
|
+
glossary: z.discriminatedUnion("mode", [
|
|
35
|
+
z.object({ mode: z.literal("active") }),
|
|
36
|
+
z.object({ mode: z.literal("pinned"), versionId: z.string().min(1) })
|
|
37
|
+
]).default({ mode: "active" })
|
|
38
|
+
}).superRefine((value, ctx) => {
|
|
39
|
+
if (new TextEncoder().encode(canonicalJson(value)).byteLength > 16 * 1024)
|
|
40
|
+
ctx.addIssue({
|
|
41
|
+
code: "custom",
|
|
42
|
+
message: "Context profile exceeds 16 KiB"
|
|
43
|
+
});
|
|
44
|
+
for (const locale of Object.keys(value.localeOverrides)) {
|
|
45
|
+
try {
|
|
46
|
+
if (Intl.getCanonicalLocales(locale)[0] !== locale) throw new Error();
|
|
47
|
+
} catch {
|
|
48
|
+
ctx.addIssue({
|
|
49
|
+
code: "custom",
|
|
50
|
+
message: `Use a canonical locale tag: ${locale}`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
function resolveContextProfile(content, locale) {
|
|
56
|
+
const { localeOverrides, glossary: _glossary, ...base } = content;
|
|
57
|
+
const canonical = Intl.getCanonicalLocales(locale)[0];
|
|
58
|
+
return {
|
|
59
|
+
...base,
|
|
60
|
+
...localeOverrides[canonical] ?? localeOverrides[canonical.split("-")[0]] ?? {}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
var meaningSchema = z.string().refine(
|
|
64
|
+
(value) => [...value].length <= 512,
|
|
65
|
+
"Meaning exceeds 512 code points"
|
|
66
|
+
);
|
|
67
|
+
var runtimeItemSchema = z.object({
|
|
68
|
+
source: z.string().min(1),
|
|
69
|
+
format: z.enum(["plain", "icu"]).default("plain"),
|
|
70
|
+
meaning: meaningSchema.default("")
|
|
71
|
+
});
|
|
72
|
+
var runtimeBatchSchema = z.object({
|
|
73
|
+
projectId: z.string().min(1),
|
|
74
|
+
environment: z.string().min(1),
|
|
75
|
+
surface: z.string().min(1),
|
|
76
|
+
sourceLocale: z.string().min(2),
|
|
77
|
+
locale: z.string().min(2),
|
|
78
|
+
items: z.array(runtimeItemSchema).min(1).max(50)
|
|
79
|
+
}).superRefine((value, ctx) => {
|
|
80
|
+
if (value.items.reduce((n, item) => n + [...item.source].length, 0) > 12e3)
|
|
81
|
+
ctx.addIssue({
|
|
82
|
+
code: "custom",
|
|
83
|
+
message: "Batch exceeds 12000 source code points"
|
|
84
|
+
});
|
|
85
|
+
if (new TextEncoder().encode(JSON.stringify(value)).byteLength > 256 * 1024)
|
|
86
|
+
ctx.addIssue({ code: "custom", message: "Batch exceeds 256 KiB" });
|
|
87
|
+
});
|
|
88
|
+
function runtimeContentKey(input) {
|
|
89
|
+
return contentHash({
|
|
90
|
+
normalizationVersion: 1,
|
|
91
|
+
sourceLocale: Intl.getCanonicalLocales(input.sourceLocale)[0],
|
|
92
|
+
surface: input.surface,
|
|
93
|
+
source: input.source.replace(/\r\n?/g, "\n").normalize("NFC"),
|
|
94
|
+
format: input.format,
|
|
95
|
+
meaning: input.meaning
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/icu-validation.ts
|
|
100
|
+
import {
|
|
101
|
+
parse,
|
|
102
|
+
TYPE
|
|
103
|
+
} from "@formatjs/icu-messageformat-parser";
|
|
104
|
+
function validateIcuSemantics(source, target, targetLocale) {
|
|
105
|
+
const issues = [];
|
|
106
|
+
function requirements(text, validateLocale) {
|
|
107
|
+
const entries = /* @__PURE__ */ new Set();
|
|
108
|
+
function visit(elements) {
|
|
109
|
+
for (const e of elements) {
|
|
110
|
+
if (e.type === TYPE.literal || e.type === TYPE.pound) continue;
|
|
111
|
+
const requirement = {
|
|
112
|
+
name: e.value,
|
|
113
|
+
type: e.type
|
|
114
|
+
};
|
|
115
|
+
if (e.type === TYPE.plural || e.type === TYPE.select) {
|
|
116
|
+
if (!("other" in e.options)) issues.push("ICU_OTHER_REQUIRED");
|
|
117
|
+
if (e.type === TYPE.plural) {
|
|
118
|
+
requirement.pluralType = e.pluralType;
|
|
119
|
+
requirement.offset = e.offset;
|
|
120
|
+
requirement.exact = Object.keys(e.options).filter((k) => k.startsWith("=")).sort();
|
|
121
|
+
if (validateLocale && targetLocale) {
|
|
122
|
+
const allowed = new Set(
|
|
123
|
+
new Intl.PluralRules(targetLocale, {
|
|
124
|
+
type: e.pluralType
|
|
125
|
+
}).resolvedOptions().pluralCategories
|
|
126
|
+
);
|
|
127
|
+
for (const key of Object.keys(e.options))
|
|
128
|
+
if (!key.startsWith("=") && !allowed.has(key))
|
|
129
|
+
issues.push("INVALID_PLURAL_CATEGORY");
|
|
130
|
+
}
|
|
131
|
+
} else requirement.selectors = Object.keys(e.options).sort();
|
|
132
|
+
for (const option of Object.values(e.options)) visit(option.value);
|
|
133
|
+
}
|
|
134
|
+
if (e.type === TYPE.number) {
|
|
135
|
+
requirement.style = typeof e.style === "object" && e.style ? e.style.tokens : e.style;
|
|
136
|
+
}
|
|
137
|
+
if (e.type === TYPE.tag) visit(e.children);
|
|
138
|
+
entries.add(canonicalJson(requirement));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
visit(
|
|
142
|
+
parse(text, {
|
|
143
|
+
captureLocation: false,
|
|
144
|
+
ignoreTag: false,
|
|
145
|
+
requiresOtherClause: true
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
return [...entries].sort();
|
|
149
|
+
}
|
|
150
|
+
const left = requirements(source, false);
|
|
151
|
+
const right = requirements(target, true);
|
|
152
|
+
if (canonicalJson(left) !== canonicalJson(right))
|
|
153
|
+
issues.push("ICU_SEMANTICS_CHANGED");
|
|
154
|
+
return [...new Set(issues)];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/index.ts
|
|
158
|
+
var SCHEMA_VERSION = "1.0";
|
|
159
|
+
var MANIFEST_SCHEMA_VERSION = "2.0";
|
|
160
|
+
var messageLocationSchema = z2.object({
|
|
161
|
+
file: z2.string().min(1),
|
|
162
|
+
line: z2.number().int().positive(),
|
|
163
|
+
column: z2.number().int().nonnegative()
|
|
164
|
+
});
|
|
165
|
+
var placeholderSchema = z2.object({
|
|
166
|
+
name: z2.string().min(1),
|
|
167
|
+
kind: z2.enum([
|
|
168
|
+
"value",
|
|
169
|
+
"component",
|
|
170
|
+
"plural",
|
|
171
|
+
"select",
|
|
172
|
+
"number",
|
|
173
|
+
"date",
|
|
174
|
+
"time"
|
|
175
|
+
])
|
|
176
|
+
});
|
|
177
|
+
var messageContextSchema = z2.object({
|
|
178
|
+
kind: z2.enum(["text", "attribute", "explicit"]),
|
|
179
|
+
element: z2.string().optional(),
|
|
180
|
+
attribute: z2.string().optional(),
|
|
181
|
+
developer: z2.string().optional()
|
|
182
|
+
});
|
|
183
|
+
var manifestMessageSchema = z2.object({
|
|
184
|
+
id: z2.string().min(1),
|
|
185
|
+
source: z2.string().min(1),
|
|
186
|
+
normalized: z2.string().min(1),
|
|
187
|
+
contentHash: z2.string().length(64),
|
|
188
|
+
context: messageContextSchema,
|
|
189
|
+
placeholders: z2.array(placeholderSchema).default([]),
|
|
190
|
+
confidence: z2.enum(["high", "explicit"]),
|
|
191
|
+
locations: z2.array(messageLocationSchema).min(1)
|
|
192
|
+
});
|
|
193
|
+
var diagnosticSchema = z2.object({
|
|
194
|
+
code: z2.string().min(1),
|
|
195
|
+
severity: z2.enum(["info", "warning", "error"]),
|
|
196
|
+
message: z2.string().min(1),
|
|
197
|
+
location: messageLocationSchema.optional(),
|
|
198
|
+
remediation: z2.string().optional()
|
|
199
|
+
});
|
|
200
|
+
var manifestIntegrationSchema = z2.object({
|
|
201
|
+
tool: z2.enum(["vite", "next"]),
|
|
202
|
+
toolVersion: z2.string().min(1),
|
|
203
|
+
framework: z2.enum(["react", "vue", "svelte", "solid"]),
|
|
204
|
+
adapterVersion: z2.string().min(1),
|
|
205
|
+
ssr: z2.boolean()
|
|
206
|
+
});
|
|
207
|
+
var manifestSchema = z2.object({
|
|
208
|
+
schemaVersion: z2.enum([SCHEMA_VERSION, MANIFEST_SCHEMA_VERSION]),
|
|
209
|
+
compilerVersion: z2.string().min(1),
|
|
210
|
+
projectId: z2.string().min(1),
|
|
211
|
+
environment: z2.string().min(1),
|
|
212
|
+
sourceLocale: z2.string().min(2),
|
|
213
|
+
sourceRevision: z2.string().optional(),
|
|
214
|
+
integration: manifestIntegrationSchema.optional(),
|
|
215
|
+
messages: z2.array(manifestMessageSchema),
|
|
216
|
+
diagnostics: z2.array(diagnosticSchema).default([])
|
|
217
|
+
}).superRefine((manifest, context) => {
|
|
218
|
+
if (manifest.schemaVersion === MANIFEST_SCHEMA_VERSION && !manifest.integration) {
|
|
219
|
+
context.addIssue({
|
|
220
|
+
code: "custom",
|
|
221
|
+
path: ["integration"],
|
|
222
|
+
message: "Manifest schema 2.0 requires integration metadata."
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
var catalogSchema = z2.object({
|
|
227
|
+
schemaVersion: z2.literal(SCHEMA_VERSION),
|
|
228
|
+
projectId: z2.string().min(1),
|
|
229
|
+
environment: z2.string().min(1),
|
|
230
|
+
sourceLocale: z2.string().min(2),
|
|
231
|
+
locale: z2.string().min(2),
|
|
232
|
+
manifestHash: z2.string().length(64),
|
|
233
|
+
version: z2.number().int().positive(),
|
|
234
|
+
messages: z2.record(z2.string(), z2.string()),
|
|
235
|
+
integrity: z2.string().length(64)
|
|
236
|
+
});
|
|
237
|
+
function sortValue(value) {
|
|
238
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
239
|
+
if (value && typeof value === "object") {
|
|
240
|
+
return Object.fromEntries(
|
|
241
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, sortValue(item)])
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
function canonicalJson(value) {
|
|
247
|
+
return JSON.stringify(sortValue(value));
|
|
248
|
+
}
|
|
249
|
+
function contentHash(value) {
|
|
250
|
+
const input = typeof value === "string" ? value : canonicalJson(value);
|
|
251
|
+
return bytesToHex(sha256(utf8ToBytes(input)));
|
|
252
|
+
}
|
|
253
|
+
function normalizeSource(source) {
|
|
254
|
+
return source.replace(/\s+/g, " ").trim();
|
|
255
|
+
}
|
|
256
|
+
function parseMessagePlaceholders(source) {
|
|
257
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
258
|
+
const priority = {
|
|
259
|
+
value: 0,
|
|
260
|
+
number: 1,
|
|
261
|
+
date: 1,
|
|
262
|
+
time: 1,
|
|
263
|
+
select: 2,
|
|
264
|
+
plural: 3,
|
|
265
|
+
component: 4
|
|
266
|
+
};
|
|
267
|
+
const add = (name, kind) => {
|
|
268
|
+
const existing = descriptors.get(name);
|
|
269
|
+
if (!existing || priority[kind] > priority[existing.kind])
|
|
270
|
+
descriptors.set(name, { name, kind });
|
|
271
|
+
};
|
|
272
|
+
const visit = (elements) => {
|
|
273
|
+
for (const element of elements) {
|
|
274
|
+
switch (element.type) {
|
|
275
|
+
case TYPE2.argument:
|
|
276
|
+
add(element.value, "value");
|
|
277
|
+
break;
|
|
278
|
+
case TYPE2.number:
|
|
279
|
+
add(element.value, "number");
|
|
280
|
+
break;
|
|
281
|
+
case TYPE2.date:
|
|
282
|
+
add(element.value, "date");
|
|
283
|
+
break;
|
|
284
|
+
case TYPE2.time:
|
|
285
|
+
add(element.value, "time");
|
|
286
|
+
break;
|
|
287
|
+
case TYPE2.select:
|
|
288
|
+
add(element.value, "select");
|
|
289
|
+
for (const option of Object.values(element.options))
|
|
290
|
+
visit(option.value);
|
|
291
|
+
break;
|
|
292
|
+
case TYPE2.plural:
|
|
293
|
+
add(element.value, "plural");
|
|
294
|
+
for (const option of Object.values(element.options))
|
|
295
|
+
visit(option.value);
|
|
296
|
+
break;
|
|
297
|
+
case TYPE2.tag:
|
|
298
|
+
add(element.value, "component");
|
|
299
|
+
visit(element.children);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
visit(parse2(source, { captureLocation: false, ignoreTag: false }));
|
|
305
|
+
return [...descriptors.values()].sort(
|
|
306
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
function stableMessageId(input) {
|
|
310
|
+
if (input.explicitId) return input.explicitId;
|
|
311
|
+
const descriptor = {
|
|
312
|
+
version: 1,
|
|
313
|
+
sourceLocale: input.sourceLocale,
|
|
314
|
+
source: normalizeSource(input.source),
|
|
315
|
+
context: input.context
|
|
316
|
+
};
|
|
317
|
+
return `lna_${contentHash(descriptor).slice(0, 24)}`;
|
|
318
|
+
}
|
|
319
|
+
function manifestHash(manifest) {
|
|
320
|
+
return contentHash(manifestSchema.parse(manifest));
|
|
321
|
+
}
|
|
322
|
+
function createCatalog(input) {
|
|
323
|
+
const integrity = contentHash(input);
|
|
324
|
+
return catalogSchema.parse({ ...input, integrity });
|
|
325
|
+
}
|
|
326
|
+
function verifyCatalog(catalog) {
|
|
327
|
+
const { integrity, ...payload } = catalogSchema.parse(catalog);
|
|
328
|
+
return contentHash(payload) === integrity;
|
|
329
|
+
}
|
|
330
|
+
export {
|
|
331
|
+
MANIFEST_SCHEMA_VERSION,
|
|
332
|
+
SCHEMA_VERSION,
|
|
333
|
+
canonicalJson,
|
|
334
|
+
catalogSchema,
|
|
335
|
+
contentHash,
|
|
336
|
+
contextProfileSchema,
|
|
337
|
+
createCatalog,
|
|
338
|
+
diagnosticSchema,
|
|
339
|
+
manifestHash,
|
|
340
|
+
manifestIntegrationSchema,
|
|
341
|
+
manifestMessageSchema,
|
|
342
|
+
manifestSchema,
|
|
343
|
+
meaningSchema,
|
|
344
|
+
messageContextSchema,
|
|
345
|
+
messageLocationSchema,
|
|
346
|
+
normalizeSource,
|
|
347
|
+
parseMessagePlaceholders,
|
|
348
|
+
placeholderSchema,
|
|
349
|
+
resolveContextProfile,
|
|
350
|
+
runtimeBatchSchema,
|
|
351
|
+
runtimeContentKey,
|
|
352
|
+
runtimeItemSchema,
|
|
353
|
+
stableMessageId,
|
|
354
|
+
validateIcuSemantics,
|
|
355
|
+
verifyCatalog
|
|
356
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
"name": "@linguanahq/catalog",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Linguana catalog integration",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22.12.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@formatjs/icu-messageformat-parser": "^2.11.4",
|
|
21
|
+
"@noble/hashes": "^2.0.1",
|
|
22
|
+
"zod": "^4.4.3"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
25
27
|
}
|
package/src/index.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type MessageFormatElement,
|
|
3
|
-
parse,
|
|
4
|
-
TYPE,
|
|
5
|
-
} from "@formatjs/icu-messageformat-parser";
|
|
6
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
7
|
-
import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
|
|
10
|
-
export const SCHEMA_VERSION = "1.0" as const;
|
|
11
|
-
export const MANIFEST_SCHEMA_VERSION = "2.0" as const;
|
|
12
|
-
|
|
13
|
-
export const messageLocationSchema = z.object({
|
|
14
|
-
file: z.string().min(1),
|
|
15
|
-
line: z.number().int().positive(),
|
|
16
|
-
column: z.number().int().nonnegative(),
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export const placeholderSchema = z.object({
|
|
20
|
-
name: z.string().min(1),
|
|
21
|
-
kind: z.enum([
|
|
22
|
-
"value",
|
|
23
|
-
"component",
|
|
24
|
-
"plural",
|
|
25
|
-
"select",
|
|
26
|
-
"number",
|
|
27
|
-
"date",
|
|
28
|
-
"time",
|
|
29
|
-
]),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export const messageContextSchema = z.object({
|
|
33
|
-
kind: z.enum(["text", "attribute", "explicit"]),
|
|
34
|
-
element: z.string().optional(),
|
|
35
|
-
attribute: z.string().optional(),
|
|
36
|
-
developer: z.string().optional(),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const manifestMessageSchema = z.object({
|
|
40
|
-
id: z.string().min(1),
|
|
41
|
-
source: z.string().min(1),
|
|
42
|
-
normalized: z.string().min(1),
|
|
43
|
-
contentHash: z.string().length(64),
|
|
44
|
-
context: messageContextSchema,
|
|
45
|
-
placeholders: z.array(placeholderSchema).default([]),
|
|
46
|
-
confidence: z.enum(["high", "explicit"]),
|
|
47
|
-
locations: z.array(messageLocationSchema).min(1),
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
export const diagnosticSchema = z.object({
|
|
51
|
-
code: z.string().min(1),
|
|
52
|
-
severity: z.enum(["info", "warning", "error"]),
|
|
53
|
-
message: z.string().min(1),
|
|
54
|
-
location: messageLocationSchema.optional(),
|
|
55
|
-
remediation: z.string().optional(),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
export const manifestIntegrationSchema = z.object({
|
|
59
|
-
tool: z.literal("vite"),
|
|
60
|
-
toolVersion: z.string().min(1),
|
|
61
|
-
framework: z.enum(["react", "vue", "svelte", "solid"]),
|
|
62
|
-
adapterVersion: z.string().min(1),
|
|
63
|
-
ssr: z.boolean(),
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
export const manifestSchema = z
|
|
67
|
-
.object({
|
|
68
|
-
schemaVersion: z.enum([SCHEMA_VERSION, MANIFEST_SCHEMA_VERSION]),
|
|
69
|
-
compilerVersion: z.string().min(1),
|
|
70
|
-
projectId: z.string().min(1),
|
|
71
|
-
environment: z.string().min(1),
|
|
72
|
-
sourceLocale: z.string().min(2),
|
|
73
|
-
sourceRevision: z.string().optional(),
|
|
74
|
-
integration: manifestIntegrationSchema.optional(),
|
|
75
|
-
messages: z.array(manifestMessageSchema),
|
|
76
|
-
diagnostics: z.array(diagnosticSchema).default([]),
|
|
77
|
-
})
|
|
78
|
-
.superRefine((manifest, context) => {
|
|
79
|
-
if (
|
|
80
|
-
manifest.schemaVersion === MANIFEST_SCHEMA_VERSION &&
|
|
81
|
-
!manifest.integration
|
|
82
|
-
) {
|
|
83
|
-
context.addIssue({
|
|
84
|
-
code: "custom",
|
|
85
|
-
path: ["integration"],
|
|
86
|
-
message: "Manifest schema 2.0 requires integration metadata.",
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
export const catalogSchema = z.object({
|
|
92
|
-
schemaVersion: z.literal(SCHEMA_VERSION),
|
|
93
|
-
projectId: z.string().min(1),
|
|
94
|
-
environment: z.string().min(1),
|
|
95
|
-
sourceLocale: z.string().min(2),
|
|
96
|
-
locale: z.string().min(2),
|
|
97
|
-
manifestHash: z.string().length(64),
|
|
98
|
-
version: z.number().int().positive(),
|
|
99
|
-
messages: z.record(z.string(), z.string()),
|
|
100
|
-
integrity: z.string().length(64),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
export type MessageLocation = z.infer<typeof messageLocationSchema>;
|
|
104
|
-
export type Placeholder = z.infer<typeof placeholderSchema>;
|
|
105
|
-
export type MessageContext = z.infer<typeof messageContextSchema>;
|
|
106
|
-
export type ManifestMessage = z.infer<typeof manifestMessageSchema>;
|
|
107
|
-
export type Diagnostic = z.infer<typeof diagnosticSchema>;
|
|
108
|
-
export type Manifest = z.infer<typeof manifestSchema>;
|
|
109
|
-
export type ManifestIntegration = z.infer<typeof manifestIntegrationSchema>;
|
|
110
|
-
export type Catalog = z.infer<typeof catalogSchema>;
|
|
111
|
-
|
|
112
|
-
function sortValue(value: unknown): unknown {
|
|
113
|
-
if (Array.isArray(value)) return value.map(sortValue);
|
|
114
|
-
if (value && typeof value === "object") {
|
|
115
|
-
return Object.fromEntries(
|
|
116
|
-
Object.entries(value)
|
|
117
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
118
|
-
.map(([key, item]) => [key, sortValue(item)]),
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
return value;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function canonicalJson(value: unknown): string {
|
|
125
|
-
return JSON.stringify(sortValue(value));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function contentHash(value: unknown): string {
|
|
129
|
-
const input = typeof value === "string" ? value : canonicalJson(value);
|
|
130
|
-
return bytesToHex(sha256(utf8ToBytes(input)));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function normalizeSource(source: string): string {
|
|
134
|
-
return source.replace(/\s+/g, " ").trim();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function parseMessagePlaceholders(source: string): Placeholder[] {
|
|
138
|
-
const descriptors = new Map<string, Placeholder>();
|
|
139
|
-
const priority: Record<Placeholder["kind"], number> = {
|
|
140
|
-
value: 0,
|
|
141
|
-
number: 1,
|
|
142
|
-
date: 1,
|
|
143
|
-
time: 1,
|
|
144
|
-
select: 2,
|
|
145
|
-
plural: 3,
|
|
146
|
-
component: 4,
|
|
147
|
-
};
|
|
148
|
-
const add = (name: string, kind: Placeholder["kind"]) => {
|
|
149
|
-
const existing = descriptors.get(name);
|
|
150
|
-
if (!existing || priority[kind] > priority[existing.kind])
|
|
151
|
-
descriptors.set(name, { name, kind });
|
|
152
|
-
};
|
|
153
|
-
const visit = (elements: MessageFormatElement[]) => {
|
|
154
|
-
for (const element of elements) {
|
|
155
|
-
switch (element.type) {
|
|
156
|
-
case TYPE.argument:
|
|
157
|
-
add(element.value, "value");
|
|
158
|
-
break;
|
|
159
|
-
case TYPE.number:
|
|
160
|
-
add(element.value, "number");
|
|
161
|
-
break;
|
|
162
|
-
case TYPE.date:
|
|
163
|
-
add(element.value, "date");
|
|
164
|
-
break;
|
|
165
|
-
case TYPE.time:
|
|
166
|
-
add(element.value, "time");
|
|
167
|
-
break;
|
|
168
|
-
case TYPE.select:
|
|
169
|
-
add(element.value, "select");
|
|
170
|
-
for (const option of Object.values(element.options))
|
|
171
|
-
visit(option.value);
|
|
172
|
-
break;
|
|
173
|
-
case TYPE.plural:
|
|
174
|
-
add(element.value, "plural");
|
|
175
|
-
for (const option of Object.values(element.options))
|
|
176
|
-
visit(option.value);
|
|
177
|
-
break;
|
|
178
|
-
case TYPE.tag:
|
|
179
|
-
add(element.value, "component");
|
|
180
|
-
visit(element.children);
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
visit(parse(source, { captureLocation: false, ignoreTag: false }));
|
|
186
|
-
return [...descriptors.values()].sort((left, right) =>
|
|
187
|
-
left.name.localeCompare(right.name),
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function stableMessageId(input: {
|
|
192
|
-
sourceLocale: string;
|
|
193
|
-
source: string;
|
|
194
|
-
context: MessageContext;
|
|
195
|
-
explicitId?: string;
|
|
196
|
-
}): string {
|
|
197
|
-
if (input.explicitId) return input.explicitId;
|
|
198
|
-
const descriptor = {
|
|
199
|
-
version: 1,
|
|
200
|
-
sourceLocale: input.sourceLocale,
|
|
201
|
-
source: normalizeSource(input.source),
|
|
202
|
-
context: input.context,
|
|
203
|
-
};
|
|
204
|
-
return `lna_${contentHash(descriptor).slice(0, 24)}`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function manifestHash(manifest: Manifest): string {
|
|
208
|
-
return contentHash(manifestSchema.parse(manifest));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export function createCatalog(input: Omit<Catalog, "integrity">): Catalog {
|
|
212
|
-
const integrity = contentHash(input);
|
|
213
|
-
return catalogSchema.parse({ ...input, integrity });
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function verifyCatalog(catalog: Catalog): boolean {
|
|
217
|
-
const { integrity, ...payload } = catalogSchema.parse(catalog);
|
|
218
|
-
return contentHash(payload) === integrity;
|
|
219
|
-
}
|