@lingo.dev/spec 1.0.2 → 1.1.0
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/index.d.cts +1 -6
- package/dist/index.d.ts +1 -6
- package/dist/index.js +0 -2
- package/package.json +9 -8
- package/src/hash.ts +16 -16
- package/src/jsonc.test.ts +4 -4
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
* Same source + different context = different key (disambiguation).
|
|
19
19
|
*/
|
|
20
20
|
declare function computeKey(source: string, context?: string): string;
|
|
21
|
-
//# sourceMappingURL=hash.d.ts.map
|
|
22
21
|
//#endregion
|
|
23
22
|
//#region src/icu.d.ts
|
|
24
23
|
/**
|
|
@@ -30,8 +29,6 @@ declare function computeKey(source: string, context?: string): string;
|
|
|
30
29
|
*/
|
|
31
30
|
declare function buildIcuPlural(forms: Record<string, string>): string;
|
|
32
31
|
declare function buildIcuSelect(forms: Record<string, string>): string;
|
|
33
|
-
//# sourceMappingURL=icu.d.ts.map
|
|
34
|
-
|
|
35
32
|
//#endregion
|
|
36
33
|
//#region src/jsonc.d.ts
|
|
37
34
|
/**
|
|
@@ -71,7 +68,5 @@ declare function getActiveEntries(entries: LocaleEntry[]): LocaleEntry[];
|
|
|
71
68
|
* Pure function - no filesystem access. Works in Node.js and edge runtimes.
|
|
72
69
|
*/
|
|
73
70
|
declare function readLocaleFile(content: string): LocaleFile;
|
|
74
|
-
//# sourceMappingURL=jsonc.d.ts.map
|
|
75
71
|
//#endregion
|
|
76
|
-
export { type EntryMetadata, type LocaleEntry, type LocaleFile, buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };
|
|
77
|
-
//# sourceMappingURL=index.d.cts.map
|
|
72
|
+
export { type EntryMetadata, type LocaleEntry, type LocaleFile, buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
* Same source + different context = different key (disambiguation).
|
|
19
19
|
*/
|
|
20
20
|
declare function computeKey(source: string, context?: string): string;
|
|
21
|
-
//# sourceMappingURL=hash.d.ts.map
|
|
22
21
|
//#endregion
|
|
23
22
|
//#region src/icu.d.ts
|
|
24
23
|
/**
|
|
@@ -30,8 +29,6 @@ declare function computeKey(source: string, context?: string): string;
|
|
|
30
29
|
*/
|
|
31
30
|
declare function buildIcuPlural(forms: Record<string, string>): string;
|
|
32
31
|
declare function buildIcuSelect(forms: Record<string, string>): string;
|
|
33
|
-
//# sourceMappingURL=icu.d.ts.map
|
|
34
|
-
|
|
35
32
|
//#endregion
|
|
36
33
|
//#region src/jsonc.d.ts
|
|
37
34
|
/**
|
|
@@ -71,7 +68,5 @@ declare function getActiveEntries(entries: LocaleEntry[]): LocaleEntry[];
|
|
|
71
68
|
* Pure function - no filesystem access. Works in Node.js and edge runtimes.
|
|
72
69
|
*/
|
|
73
70
|
declare function readLocaleFile(content: string): LocaleFile;
|
|
74
|
-
//# sourceMappingURL=jsonc.d.ts.map
|
|
75
71
|
//#endregion
|
|
76
|
-
export { type EntryMetadata, type LocaleEntry, type LocaleFile, buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };
|
|
77
|
-
//# sourceMappingURL=index.d.ts.map
|
|
72
|
+
export { type EntryMetadata, type LocaleEntry, type LocaleFile, buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingo.dev/spec",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist",
|
|
6
|
+
"src"
|
|
7
|
+
],
|
|
4
8
|
"type": "module",
|
|
9
|
+
"sideEffects": false,
|
|
5
10
|
"exports": {
|
|
6
11
|
".": {
|
|
7
12
|
"typescript": {
|
|
@@ -18,22 +23,18 @@
|
|
|
18
23
|
}
|
|
19
24
|
}
|
|
20
25
|
},
|
|
21
|
-
"files": [
|
|
22
|
-
"dist",
|
|
23
|
-
"src"
|
|
24
|
-
],
|
|
25
|
-
"sideEffects": false,
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"jsonc-parser": "^3.3.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@typescript/native-preview": "7.0.0-dev.20260330.1",
|
|
30
31
|
"tsdown": "^0.12.5",
|
|
31
32
|
"typescript": "^5.8.2",
|
|
32
33
|
"vitest": "^4.0.18"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"build": "tsdown",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"typecheck": "tsgo"
|
|
38
39
|
}
|
|
39
40
|
}
|
package/src/hash.ts
CHANGED
|
@@ -38,7 +38,7 @@ function fmix32(h: number): number {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function readU32LE(bytes: Uint8Array, i: number): number {
|
|
41
|
-
return (bytes[i] | (bytes[i + 1] << 8) | (bytes[i + 2] << 16) | (bytes[i + 3] << 24)) >>> 0;
|
|
41
|
+
return (bytes[i]! | (bytes[i + 1]! << 8) | (bytes[i + 2]! << 16) | (bytes[i + 3]! << 24)) >>> 0;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function murmurhash3_x86_128(bytes: Uint8Array): [number, number, number, number] {
|
|
@@ -97,40 +97,40 @@ function murmurhash3_x86_128(bytes: Uint8Array): [number, number, number, number
|
|
|
97
97
|
let k4 = 0;
|
|
98
98
|
const rem = len & 15;
|
|
99
99
|
|
|
100
|
-
if (rem >= 15) k4 ^= bytes[tail + 14] << 16;
|
|
101
|
-
if (rem >= 14) k4 ^= bytes[tail + 13] << 8;
|
|
100
|
+
if (rem >= 15) k4 ^= bytes[tail + 14]! << 16;
|
|
101
|
+
if (rem >= 14) k4 ^= bytes[tail + 13]! << 8;
|
|
102
102
|
if (rem >= 13) {
|
|
103
|
-
k4 ^= bytes[tail + 12]
|
|
103
|
+
k4 ^= bytes[tail + 12]!;
|
|
104
104
|
k4 = Math.imul(k4, C4) >>> 0;
|
|
105
105
|
k4 = rotl32(k4, 18);
|
|
106
106
|
k4 = Math.imul(k4, C1) >>> 0;
|
|
107
107
|
h4 ^= k4;
|
|
108
108
|
}
|
|
109
|
-
if (rem >= 12) k3 ^= bytes[tail + 11] << 24;
|
|
110
|
-
if (rem >= 11) k3 ^= bytes[tail + 10] << 16;
|
|
111
|
-
if (rem >= 10) k3 ^= bytes[tail + 9] << 8;
|
|
109
|
+
if (rem >= 12) k3 ^= bytes[tail + 11]! << 24;
|
|
110
|
+
if (rem >= 11) k3 ^= bytes[tail + 10]! << 16;
|
|
111
|
+
if (rem >= 10) k3 ^= bytes[tail + 9]! << 8;
|
|
112
112
|
if (rem >= 9) {
|
|
113
|
-
k3 ^= bytes[tail + 8]
|
|
113
|
+
k3 ^= bytes[tail + 8]!;
|
|
114
114
|
k3 = Math.imul(k3, C3) >>> 0;
|
|
115
115
|
k3 = rotl32(k3, 17);
|
|
116
116
|
k3 = Math.imul(k3, C4) >>> 0;
|
|
117
117
|
h3 ^= k3;
|
|
118
118
|
}
|
|
119
|
-
if (rem >= 8) k2 ^= bytes[tail + 7] << 24;
|
|
120
|
-
if (rem >= 7) k2 ^= bytes[tail + 6] << 16;
|
|
121
|
-
if (rem >= 6) k2 ^= bytes[tail + 5] << 8;
|
|
119
|
+
if (rem >= 8) k2 ^= bytes[tail + 7]! << 24;
|
|
120
|
+
if (rem >= 7) k2 ^= bytes[tail + 6]! << 16;
|
|
121
|
+
if (rem >= 6) k2 ^= bytes[tail + 5]! << 8;
|
|
122
122
|
if (rem >= 5) {
|
|
123
|
-
k2 ^= bytes[tail + 4]
|
|
123
|
+
k2 ^= bytes[tail + 4]!;
|
|
124
124
|
k2 = Math.imul(k2, C2) >>> 0;
|
|
125
125
|
k2 = rotl32(k2, 16);
|
|
126
126
|
k2 = Math.imul(k2, C3) >>> 0;
|
|
127
127
|
h2 ^= k2;
|
|
128
128
|
}
|
|
129
|
-
if (rem >= 4) k1 ^= bytes[tail + 3] << 24;
|
|
130
|
-
if (rem >= 3) k1 ^= bytes[tail + 2] << 16;
|
|
131
|
-
if (rem >= 2) k1 ^= bytes[tail + 1] << 8;
|
|
129
|
+
if (rem >= 4) k1 ^= bytes[tail + 3]! << 24;
|
|
130
|
+
if (rem >= 3) k1 ^= bytes[tail + 2]! << 16;
|
|
131
|
+
if (rem >= 2) k1 ^= bytes[tail + 1]! << 8;
|
|
132
132
|
if (rem >= 1) {
|
|
133
|
-
k1 ^= bytes[tail]
|
|
133
|
+
k1 ^= bytes[tail]!;
|
|
134
134
|
k1 = Math.imul(k1, C1) >>> 0;
|
|
135
135
|
k1 = rotl32(k1, 15);
|
|
136
136
|
k1 = Math.imul(k1, C2) >>> 0;
|
package/src/jsonc.test.ts
CHANGED
|
@@ -21,7 +21,7 @@ describe("readLocaleFile", () => {
|
|
|
21
21
|
"mK9xqZ": "Welcome"
|
|
22
22
|
}`;
|
|
23
23
|
const result = readLocaleFile(jsonc);
|
|
24
|
-
expect(result.entries[0]
|
|
24
|
+
expect(result.entries[0]?.metadata).toEqual({
|
|
25
25
|
context: "Hero heading",
|
|
26
26
|
src: "app/hero.tsx:12",
|
|
27
27
|
});
|
|
@@ -35,7 +35,7 @@ describe("readLocaleFile", () => {
|
|
|
35
35
|
"old": "Removed"
|
|
36
36
|
}`;
|
|
37
37
|
const result = readLocaleFile(jsonc);
|
|
38
|
-
expect(result.entries[0]
|
|
38
|
+
expect(result.entries[0]?.metadata.orphan).toBe(true);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it("handles trailing commas", () => {
|
|
@@ -63,7 +63,7 @@ describe("readLocaleFile", () => {
|
|
|
63
63
|
}`;
|
|
64
64
|
const result = readLocaleFile(jsonc);
|
|
65
65
|
expect(result.entries).toHaveLength(2);
|
|
66
|
-
expect(result.entries[0]
|
|
67
|
-
expect(result.entries[1]
|
|
66
|
+
expect(result.entries[0]?.metadata.context).toBe("Form button");
|
|
67
|
+
expect(result.entries[1]?.metadata.src).toBe("hero.tsx:12");
|
|
68
68
|
});
|
|
69
69
|
});
|
package/dist/index.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/hash.ts","../src/icu.ts","../src/jsonc.ts"],"sourcesContent":[],"mappings":";;AAwLA;;;;AC9KA;AAOA;;;;ACIA;AAMA;AAMA;AAOA;;;;;AA8DgB,iBFkFA,UAAA,CElFiC,MAAA,EAAU,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;AFkF3D;;;;AC9KA;AAOA;iBAPgB,cAAA,QAAsB;iBAOtB,cAAA,QAAsB;;;;;;ADuKtC;;;;AC9KA;AAOA;;;;ACIA;AAMA;AAMA;AAOA;;;AAA0D,KAnB9C,aAAA,GAmB8C;EAAW,OAAA,CAAA,EAAA,MAAA;EA8DrD,GAAA,CAAA,EAAA,MAAA;;;KA3EJ,WAAA;;;YAGA;;KAGA,UAAA;WACD;;;iBAMK,gBAAA,UAA0B,gBAAgB;;;;;;iBA8D1C,cAAA,mBAAiC"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/hash.ts","../src/icu.ts","../src/jsonc.ts"],"sourcesContent":[],"mappings":";;AAwLA;;;;AC9KA;AAOA;;;;ACIA;AAMA;AAMA;AAOA;;;;;AA8DgB,iBFkFA,UAAA,CElFiC,MAAA,EAAU,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;AFkF3D;;;;AC9KA;AAOA;iBAPgB,cAAA,QAAsB;iBAOtB,cAAA,QAAsB;;;;;;ADuKtC;;;;AC9KA;AAOA;;;;ACIA;AAMA;AAMA;AAOA;;;AAA0D,KAnB9C,aAAA,GAmB8C;EAAW,OAAA,CAAA,EAAA,MAAA;EA8DrD,GAAA,CAAA,EAAA,MAAA;;;KA3EJ,WAAA;;;YAGA;;KAGA,UAAA;WACD;;;iBAMK,gBAAA,UAA0B,gBAAgB;;;;;;iBA8D1C,cAAA,mBAAiC"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["parseJsonc"],"sources":["../src/hash.ts","../src/icu.ts","../src/jsonc.ts"],"sourcesContent":["/**\n * Deterministic key generation from (source text, context) pairs.\n * Shared between runtime (message lookup) and build tools (extraction, JSONC generation).\n *\n * Uses MurmurHash3 x86_128 on NFC-normalized UTF-8 bytes.\n * Outputs an 8-character base62 string (e.g., \"aB3dEf9x\").\n *\n * Cross-platform deterministic: JS, Rust, Go, Python produce identical keys\n * when given the same (source, context) pair.\n *\n * 1% collision probability at ~2.4 million messages (48-bit effective entropy).\n */\n\nconst BASE62_CHARS = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\nconst KEY_LENGTH = 8;\nconst BASE62_SPACE = 62 ** KEY_LENGTH;\n\nconst encoder = new TextEncoder();\n\n// --- MurmurHash3 x86_128 (reference: github.com/aappleby/smhasher) ---\n\nconst C1 = 0x239b961b;\nconst C2 = 0xab0e9789;\nconst C3 = 0x38b34ae5;\nconst C4 = 0xa1e38b93;\n\nfunction rotl32(x: number, r: number): number {\n return ((x << r) | (x >>> (32 - r))) >>> 0;\n}\n\nfunction fmix32(h: number): number {\n h ^= h >>> 16;\n h = Math.imul(h, 0x85ebca6b) >>> 0;\n h ^= h >>> 13;\n h = Math.imul(h, 0xc2b2ae35) >>> 0;\n h ^= h >>> 16;\n return h >>> 0;\n}\n\nfunction readU32LE(bytes: Uint8Array, i: number): number {\n return (bytes[i] | (bytes[i + 1] << 8) | (bytes[i + 2] << 16) | (bytes[i + 3] << 24)) >>> 0;\n}\n\nfunction murmurhash3_x86_128(bytes: Uint8Array): [number, number, number, number] {\n const len = bytes.length;\n const nblocks = len >>> 4;\n\n let h1 = 0;\n let h2 = 0;\n let h3 = 0;\n let h4 = 0;\n\n for (let i = 0; i < nblocks; i++) {\n const off = i << 4;\n let k1 = readU32LE(bytes, off);\n let k2 = readU32LE(bytes, off + 4);\n let k3 = readU32LE(bytes, off + 8);\n let k4 = readU32LE(bytes, off + 12);\n\n k1 = Math.imul(k1, C1) >>> 0;\n k1 = rotl32(k1, 15);\n k1 = Math.imul(k1, C2) >>> 0;\n h1 ^= k1;\n h1 = rotl32(h1, 19);\n h1 = (h1 + h2) >>> 0;\n h1 = (Math.imul(h1, 5) + 0x561ccd1b) >>> 0;\n\n k2 = Math.imul(k2, C2) >>> 0;\n k2 = rotl32(k2, 16);\n k2 = Math.imul(k2, C3) >>> 0;\n h2 ^= k2;\n h2 = rotl32(h2, 17);\n h2 = (h2 + h3) >>> 0;\n h2 = (Math.imul(h2, 5) + 0x0bcaa747) >>> 0;\n\n k3 = Math.imul(k3, C3) >>> 0;\n k3 = rotl32(k3, 17);\n k3 = Math.imul(k3, C4) >>> 0;\n h3 ^= k3;\n h3 = rotl32(h3, 15);\n h3 = (h3 + h4) >>> 0;\n h3 = (Math.imul(h3, 5) + 0x96cd1c35) >>> 0;\n\n k4 = Math.imul(k4, C4) >>> 0;\n k4 = rotl32(k4, 18);\n k4 = Math.imul(k4, C1) >>> 0;\n h4 ^= k4;\n h4 = rotl32(h4, 13);\n h4 = (h4 + h1) >>> 0;\n h4 = (Math.imul(h4, 5) + 0x32ac3b17) >>> 0;\n }\n\n const tail = nblocks << 4;\n let k1 = 0;\n let k2 = 0;\n let k3 = 0;\n let k4 = 0;\n const rem = len & 15;\n\n if (rem >= 15) k4 ^= bytes[tail + 14] << 16;\n if (rem >= 14) k4 ^= bytes[tail + 13] << 8;\n if (rem >= 13) {\n k4 ^= bytes[tail + 12];\n k4 = Math.imul(k4, C4) >>> 0;\n k4 = rotl32(k4, 18);\n k4 = Math.imul(k4, C1) >>> 0;\n h4 ^= k4;\n }\n if (rem >= 12) k3 ^= bytes[tail + 11] << 24;\n if (rem >= 11) k3 ^= bytes[tail + 10] << 16;\n if (rem >= 10) k3 ^= bytes[tail + 9] << 8;\n if (rem >= 9) {\n k3 ^= bytes[tail + 8];\n k3 = Math.imul(k3, C3) >>> 0;\n k3 = rotl32(k3, 17);\n k3 = Math.imul(k3, C4) >>> 0;\n h3 ^= k3;\n }\n if (rem >= 8) k2 ^= bytes[tail + 7] << 24;\n if (rem >= 7) k2 ^= bytes[tail + 6] << 16;\n if (rem >= 6) k2 ^= bytes[tail + 5] << 8;\n if (rem >= 5) {\n k2 ^= bytes[tail + 4];\n k2 = Math.imul(k2, C2) >>> 0;\n k2 = rotl32(k2, 16);\n k2 = Math.imul(k2, C3) >>> 0;\n h2 ^= k2;\n }\n if (rem >= 4) k1 ^= bytes[tail + 3] << 24;\n if (rem >= 3) k1 ^= bytes[tail + 2] << 16;\n if (rem >= 2) k1 ^= bytes[tail + 1] << 8;\n if (rem >= 1) {\n k1 ^= bytes[tail];\n k1 = Math.imul(k1, C1) >>> 0;\n k1 = rotl32(k1, 15);\n k1 = Math.imul(k1, C2) >>> 0;\n h1 ^= k1;\n }\n\n h1 ^= len;\n h2 ^= len;\n h3 ^= len;\n h4 ^= len;\n\n h1 = (h1 + h2) >>> 0;\n h1 = (h1 + h3) >>> 0;\n h1 = (h1 + h4) >>> 0;\n h2 = (h2 + h1) >>> 0;\n h3 = (h3 + h1) >>> 0;\n h4 = (h4 + h1) >>> 0;\n\n h1 = fmix32(h1);\n h2 = fmix32(h2);\n h3 = fmix32(h3);\n h4 = fmix32(h4);\n\n h1 = (h1 + h2) >>> 0;\n h1 = (h1 + h3) >>> 0;\n h1 = (h1 + h4) >>> 0;\n h2 = (h2 + h1) >>> 0;\n h3 = (h3 + h1) >>> 0;\n h4 = (h4 + h1) >>> 0;\n\n return [h1, h2, h3, h4];\n}\n\n// --- Key generation ---\n\nfunction toBase62(num: number, length: number): string {\n let result = \"\";\n let remaining = num;\n for (let i = 0; i < length; i++) {\n result = BASE62_CHARS[remaining % 62] + result;\n remaining = Math.floor(remaining / 62);\n }\n return result;\n}\n\n/**\n * Computes a deterministic short key from source text and optional context.\n *\n * Same source + same context = same key (deterministic).\n * Same source + different context = different key (disambiguation).\n */\nexport function computeKey(source: string, context?: string): string {\n const input = context != null && context !== \"\" ? `${source}\\0${context}` : source;\n const bytes = encoder.encode(input.normalize(\"NFC\"));\n const [h1, h2] = murmurhash3_x86_128(bytes);\n\n // 48-bit value: h1 (32 bits) + upper 16 bits of h2\n // Safe for JS Number arithmetic (well within 2^53)\n const value = (h1 >>> 0) + (h2 >>> 16) * 0x100000000;\n\n return toBase62(value % BASE62_SPACE, KEY_LENGTH);\n}\n","/**\n * Canonical ICU MessageFormat expression builders.\n * Shared between runtime (l.plural/l.select) and build tools (extraction).\n *\n * These must produce identical output everywhere so that computeKey()\n * generates matching hash keys at build time and runtime.\n */\n\nconst CLDR_PLURAL_ORDER = [\"zero\", \"one\", \"two\", \"few\", \"many\", \"other\"];\n\nexport function buildIcuPlural(forms: Record<string, string>): string {\n const parts = CLDR_PLURAL_ORDER.filter((cat) => forms[cat] !== undefined)\n .map((cat) => `${cat} {${forms[cat]!}}`)\n .join(\" \");\n return `{count, plural, ${parts}}`;\n}\n\nexport function buildIcuSelect(forms: Record<string, string>): string {\n const parts = Object.keys(forms)\n .sort()\n .map((key) => `${key} {${forms[key]}}`)\n .join(\" \");\n return `{value, select, ${parts}}`;\n}\n","/**\n * JSONC locale file reader with structured metadata comments.\n * Lives in @lingo.dev/spec so framework adapters can parse locale files\n * without depending on @lingo.dev/cli.\n *\n * File format:\n * ```jsonc\n * {\n * /*\n * * @context Hero heading\n * * @src app/hero.tsx:12\n * */\n * \"mK9xqZ\": \"Welcome to Acme\"\n * }\n * ```\n */\n\nimport { parse as parseJsonc, type ParseError, printParseErrorCode } from \"jsonc-parser\";\n\n// --- Types (shared between reader in spec and writer in cli) ---\n\nexport type EntryMetadata = {\n context?: string;\n src?: string;\n orphan?: boolean;\n};\n\nexport type LocaleEntry = {\n key: string;\n value: string;\n metadata: EntryMetadata;\n};\n\nexport type LocaleFile = {\n entries: LocaleEntry[];\n};\n\n// --- Helpers ---\n\n/** Filters out orphaned entries, returning only active (non-orphaned) ones. */\nexport function getActiveEntries(entries: LocaleEntry[]): LocaleEntry[] {\n return entries.filter((e) => !e.metadata.orphan);\n}\n\n// --- Reader ---\n\nconst METADATA_PATTERN = /@(\\w+)(?:\\s+(.*))?/;\n\nfunction parseMetadataBlock(comment: string): EntryMetadata {\n const metadata: EntryMetadata = {};\n for (const line of comment.split(\"\\n\")) {\n const match = line.match(METADATA_PATTERN);\n if (!match) continue;\n const [, tag, value] = match;\n if (tag === \"context\" && value) metadata.context = value.trim();\n else if (tag === \"src\" && value) metadata.src = value.trim();\n else if (tag === \"orphan\") metadata.orphan = true;\n }\n return metadata;\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Scans JSONC source to associate block comments with the key that follows them.\n */\nfunction extractCommentsForKeys(content: string, keys: string[]): Map<string, EntryMetadata> {\n const result = new Map<string, EntryMetadata>();\n const blockCommentPattern = /\\/\\*[\\s\\S]*?\\*\\//g;\n\n const comments: { end: number; text: string }[] = [];\n let match: RegExpExecArray | null;\n while ((match = blockCommentPattern.exec(content)) !== null) {\n comments.push({ end: match.index + match[0].length, text: match[0] });\n }\n\n for (const key of keys) {\n const keyPattern = new RegExp(`\"${escapeRegex(key)}\"\\\\s*:`);\n const keyMatch = keyPattern.exec(content);\n if (!keyMatch) continue;\n\n const keyPos = keyMatch.index;\n const precedingComment = comments.filter((c) => c.end <= keyPos).sort((a, b) => b.end - a.end)[0];\n\n if (precedingComment) {\n const between = content.slice(precedingComment.end, keyPos);\n if (/^\\s*$/.test(between)) {\n result.set(key, parseMetadataBlock(precedingComment.text));\n }\n }\n }\n\n return result;\n}\n\n/**\n * Parses a JSONC string into structured locale entries with metadata.\n * Extracts @context, @src, and @orphan from block comments preceding each key.\n * Pure function - no filesystem access. Works in Node.js and edge runtimes.\n */\nexport function readLocaleFile(content: string): LocaleFile {\n const errors: ParseError[] = [];\n const data = parseJsonc(content, errors, { allowTrailingComma: true }) as Record<string, string> | undefined;\n\n if (errors.length > 0) {\n const msg = errors.map((e) => printParseErrorCode(e.error)).join(\", \");\n throw new Error(`Failed to parse JSONC: ${msg}`);\n }\n\n if (!data || typeof data !== \"object\") {\n return { entries: [] };\n }\n\n const commentsByKey = extractCommentsForKeys(content, Object.keys(data));\n\n const entries: LocaleEntry[] = Object.entries(data).map(([key, value]) => ({\n key,\n value,\n metadata: commentsByKey.get(key) ?? {},\n }));\n\n return { entries };\n}\n"],"mappings":"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"}
|