@lingo.dev/spec 1.0.3 → 2.0.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 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
@@ -275,5 +275,3 @@ function readLocaleFile(content) {
275
275
  }
276
276
  //#endregion
277
277
  export { buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };
278
-
279
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@lingo.dev/spec",
3
- "version": "1.0.3",
3
+ "version": "2.0.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
- "typecheck": "tsc --noEmit",
37
- "test": "vitest run"
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].metadata).toEqual({
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].metadata.orphan).toBe(true);
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].metadata.context).toBe("Form button");
67
- expect(result.entries[1].metadata.src).toBe("hero.tsx:12");
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
  });
@@ -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"}
@@ -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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"}