@localeguard/core 0.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.
Files changed (43) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +32 -0
  3. package/dist/src/check.d.ts +29 -0
  4. package/dist/src/check.d.ts.map +1 -0
  5. package/dist/src/check.js +175 -0
  6. package/dist/src/check.js.map +1 -0
  7. package/dist/src/flatten.d.ts +10 -0
  8. package/dist/src/flatten.d.ts.map +1 -0
  9. package/dist/src/flatten.js +32 -0
  10. package/dist/src/flatten.js.map +1 -0
  11. package/dist/src/index.d.ts +19 -0
  12. package/dist/src/index.d.ts.map +1 -0
  13. package/dist/src/index.js +43 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/src/json/parse.d.ts +28 -0
  16. package/dist/src/json/parse.d.ts.map +1 -0
  17. package/dist/src/json/parse.js +269 -0
  18. package/dist/src/json/parse.js.map +1 -0
  19. package/dist/src/key-comparator/compare.d.ts +13 -0
  20. package/dist/src/key-comparator/compare.d.ts.map +1 -0
  21. package/dist/src/key-comparator/compare.js +31 -0
  22. package/dist/src/key-comparator/compare.js.map +1 -0
  23. package/dist/src/locale-parser/load.d.ts +19 -0
  24. package/dist/src/locale-parser/load.d.ts.map +1 -0
  25. package/dist/src/locale-parser/load.js +154 -0
  26. package/dist/src/locale-parser/load.js.map +1 -0
  27. package/dist/src/placeholder-validator/placeholder.d.ts +25 -0
  28. package/dist/src/placeholder-validator/placeholder.d.ts.map +1 -0
  29. package/dist/src/placeholder-validator/placeholder.js +99 -0
  30. package/dist/src/placeholder-validator/placeholder.js.map +1 -0
  31. package/dist/src/reporters/json.d.ts +6 -0
  32. package/dist/src/reporters/json.d.ts.map +1 -0
  33. package/dist/src/reporters/json.js +16 -0
  34. package/dist/src/reporters/json.js.map +1 -0
  35. package/dist/src/reporters/text.d.ts +10 -0
  36. package/dist/src/reporters/text.d.ts.map +1 -0
  37. package/dist/src/reporters/text.js +78 -0
  38. package/dist/src/reporters/text.js.map +1 -0
  39. package/dist/src/types.d.ts +92 -0
  40. package/dist/src/types.d.ts.map +1 -0
  41. package/dist/src/types.js +35 -0
  42. package/dist/src/types.js.map +1 -0
  43. package/package.json +37 -0
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ /**
3
+ * A small recursive-descent JSON parser.
4
+ *
5
+ * `JSON.parse` silently keeps the last value when a key is duplicated and
6
+ * gives no location information. LocaleGuard needs both, so this parser:
7
+ * - reports the 1-based line/column of syntax errors, and
8
+ * - records duplicate keys (per object) with their location.
9
+ *
10
+ * It implements the JSON grammar from RFC 8259.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.JsonParseError = void 0;
14
+ exports.parseJson = parseJson;
15
+ class JsonParseError extends Error {
16
+ line;
17
+ column;
18
+ constructor(message, line, column) {
19
+ super(message);
20
+ this.line = line;
21
+ this.column = column;
22
+ this.name = "JsonParseError";
23
+ }
24
+ }
25
+ exports.JsonParseError = JsonParseError;
26
+ const ESCAPES = {
27
+ '"': '"',
28
+ "\\": "\\",
29
+ "/": "/",
30
+ b: "\b",
31
+ f: "\f",
32
+ n: "\n",
33
+ r: "\r",
34
+ t: "\t",
35
+ };
36
+ class Parser {
37
+ text;
38
+ pos = 0;
39
+ line = 1;
40
+ col = 1;
41
+ keyLines = new Map();
42
+ duplicates = [];
43
+ constructor(text) {
44
+ this.text = text;
45
+ }
46
+ parse() {
47
+ this.skipWhitespace();
48
+ const value = this.parseValue("");
49
+ this.skipWhitespace();
50
+ if (this.pos < this.text.length) {
51
+ throw this.error(`Unexpected token ${JSON.stringify(this.peek())}`);
52
+ }
53
+ return value;
54
+ }
55
+ error(message) {
56
+ return new JsonParseError(message, this.line, this.col);
57
+ }
58
+ peek() {
59
+ return this.text[this.pos] ?? "";
60
+ }
61
+ advance() {
62
+ const ch = this.text[this.pos] ?? "";
63
+ this.pos++;
64
+ if (ch === "\n") {
65
+ this.line++;
66
+ this.col = 1;
67
+ }
68
+ else {
69
+ this.col++;
70
+ }
71
+ return ch;
72
+ }
73
+ skipWhitespace() {
74
+ while (this.pos < this.text.length) {
75
+ const ch = this.peek();
76
+ if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
77
+ this.advance();
78
+ }
79
+ else {
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ expect(ch) {
85
+ if (this.peek() !== ch) {
86
+ throw this.error(`Expected ${JSON.stringify(ch)} but found ${JSON.stringify(this.peek() || "<eof>")}`);
87
+ }
88
+ this.advance();
89
+ }
90
+ parseValue(path) {
91
+ const ch = this.peek();
92
+ switch (ch) {
93
+ case "{":
94
+ return this.parseObject(path);
95
+ case "[":
96
+ return this.parseArray(path);
97
+ case '"':
98
+ return this.parseString();
99
+ case "t":
100
+ case "f":
101
+ return this.parseBoolean();
102
+ case "n":
103
+ return this.parseNull();
104
+ default:
105
+ if (ch === "-" || (ch >= "0" && ch <= "9")) {
106
+ return this.parseNumber();
107
+ }
108
+ throw this.error(`Unexpected token ${JSON.stringify(ch || "<eof>")}`);
109
+ }
110
+ }
111
+ parseObject(path) {
112
+ this.expect("{");
113
+ const obj = {};
114
+ const seen = new Set();
115
+ this.skipWhitespace();
116
+ if (this.peek() === "}") {
117
+ this.advance();
118
+ return obj;
119
+ }
120
+ for (;;) {
121
+ this.skipWhitespace();
122
+ if (this.peek() !== '"') {
123
+ throw this.error("Expected string key in object");
124
+ }
125
+ const keyLine = this.line;
126
+ const key = this.parseString();
127
+ const childPath = path ? `${path}.${key}` : key;
128
+ this.skipWhitespace();
129
+ this.expect(":");
130
+ this.skipWhitespace();
131
+ const value = this.parseValue(childPath);
132
+ if (seen.has(key)) {
133
+ this.duplicates.push({ path: childPath, line: keyLine });
134
+ }
135
+ else {
136
+ seen.add(key);
137
+ }
138
+ this.keyLines.set(childPath, keyLine);
139
+ obj[key] = value;
140
+ this.skipWhitespace();
141
+ const next = this.peek();
142
+ if (next === ",") {
143
+ this.advance();
144
+ continue;
145
+ }
146
+ if (next === "}") {
147
+ this.advance();
148
+ return obj;
149
+ }
150
+ throw this.error(`Expected ',' or '}' but found ${JSON.stringify(next || "<eof>")}`);
151
+ }
152
+ }
153
+ parseArray(path) {
154
+ this.expect("[");
155
+ const arr = [];
156
+ this.skipWhitespace();
157
+ if (this.peek() === "]") {
158
+ this.advance();
159
+ return arr;
160
+ }
161
+ for (let index = 0;; index++) {
162
+ this.skipWhitespace();
163
+ const childPath = path ? `${path}.${index}` : String(index);
164
+ const value = this.parseValue(childPath);
165
+ this.keyLines.set(childPath, this.line);
166
+ arr.push(value);
167
+ this.skipWhitespace();
168
+ const next = this.peek();
169
+ if (next === ",") {
170
+ this.advance();
171
+ continue;
172
+ }
173
+ if (next === "]") {
174
+ this.advance();
175
+ return arr;
176
+ }
177
+ throw this.error(`Expected ',' or ']' but found ${JSON.stringify(next || "<eof>")}`);
178
+ }
179
+ }
180
+ parseString() {
181
+ this.expect('"');
182
+ let out = "";
183
+ for (;;) {
184
+ const ch = this.advance();
185
+ if (ch === "") {
186
+ throw this.error("Unterminated string");
187
+ }
188
+ if (ch === '"') {
189
+ return out;
190
+ }
191
+ if (ch === "\\") {
192
+ const esc = this.advance();
193
+ if (esc === "u") {
194
+ let hex = "";
195
+ for (let i = 0; i < 4; i++) {
196
+ const h = this.advance();
197
+ if (!/[0-9a-fA-F]/.test(h)) {
198
+ throw this.error("Invalid unicode escape");
199
+ }
200
+ hex += h;
201
+ }
202
+ out += String.fromCharCode(parseInt(hex, 16));
203
+ }
204
+ else if (esc in ESCAPES) {
205
+ out += ESCAPES[esc];
206
+ }
207
+ else {
208
+ throw this.error(`Invalid escape \\${esc}`);
209
+ }
210
+ }
211
+ else if (ch.charCodeAt(0) < 0x20) {
212
+ throw this.error("Control character in string");
213
+ }
214
+ else {
215
+ out += ch;
216
+ }
217
+ }
218
+ }
219
+ parseNumber() {
220
+ const start = this.pos;
221
+ if (this.peek() === "-")
222
+ this.advance();
223
+ while (/[0-9]/.test(this.peek()))
224
+ this.advance();
225
+ if (this.peek() === ".") {
226
+ this.advance();
227
+ while (/[0-9]/.test(this.peek()))
228
+ this.advance();
229
+ }
230
+ if (this.peek() === "e" || this.peek() === "E") {
231
+ this.advance();
232
+ if (this.peek() === "+" || this.peek() === "-")
233
+ this.advance();
234
+ while (/[0-9]/.test(this.peek()))
235
+ this.advance();
236
+ }
237
+ const raw = this.text.slice(start, this.pos);
238
+ const num = Number(raw);
239
+ if (Number.isNaN(num)) {
240
+ throw this.error(`Invalid number ${JSON.stringify(raw)}`);
241
+ }
242
+ return num;
243
+ }
244
+ parseKeyword(word) {
245
+ for (const expected of word) {
246
+ if (this.advance() !== expected) {
247
+ throw this.error(`Invalid literal, expected ${JSON.stringify(word)}`);
248
+ }
249
+ }
250
+ }
251
+ parseBoolean() {
252
+ if (this.peek() === "t") {
253
+ this.parseKeyword("true");
254
+ return true;
255
+ }
256
+ this.parseKeyword("false");
257
+ return false;
258
+ }
259
+ parseNull() {
260
+ this.parseKeyword("null");
261
+ return null;
262
+ }
263
+ }
264
+ function parseJson(text) {
265
+ const parser = new Parser(text);
266
+ const value = parser.parse();
267
+ return { value, keyLines: parser.keyLines, duplicates: parser.duplicates };
268
+ }
269
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../../src/json/parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Compare the key sets of two locales.
3
+ */
4
+ export interface KeyDiff {
5
+ /** Keys present in the source but absent from the target. */
6
+ missing: string[];
7
+ /** Keys present in the target but absent from the source. */
8
+ extra: string[];
9
+ /** Keys present in both (candidates for placeholder validation). */
10
+ shared: string[];
11
+ }
12
+ export declare function compareKeys(sourceKeys: Iterable<string>, targetKeys: Iterable<string>): KeyDiff;
13
+ //# sourceMappingURL=compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../../src/key-comparator/compare.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,OAAO;IACtB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,oEAAoE;IACpE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAyB/F"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /**
3
+ * Compare the key sets of two locales.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.compareKeys = compareKeys;
7
+ function compareKeys(sourceKeys, targetKeys) {
8
+ const source = sourceKeys instanceof Set ? sourceKeys : new Set(sourceKeys);
9
+ const target = targetKeys instanceof Set ? targetKeys : new Set(targetKeys);
10
+ const missing = [];
11
+ const shared = [];
12
+ for (const key of source) {
13
+ if (target.has(key)) {
14
+ shared.push(key);
15
+ }
16
+ else {
17
+ missing.push(key);
18
+ }
19
+ }
20
+ const extra = [];
21
+ for (const key of target) {
22
+ if (!source.has(key)) {
23
+ extra.push(key);
24
+ }
25
+ }
26
+ missing.sort();
27
+ extra.sort();
28
+ shared.sort();
29
+ return { missing, extra, shared };
30
+ }
31
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/key-comparator/compare.ts"],"names":[],"mappings":";AAAA;;GAEG;;AAWH,kCAyBC;AAzBD,SAAgB,WAAW,CAAC,UAA4B,EAAE,UAA4B;IACpF,MAAM,MAAM,GAAG,UAAU,YAAY,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,UAAU,YAAY,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IAE5E,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,IAAI,EAAE,CAAC;IACf,KAAK,CAAC,IAAI,EAAE,CAAC;IACb,MAAM,CAAC,IAAI,EAAE,CAAC;IACd,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Load a locale from disk into a flat key map.
3
+ *
4
+ * Two on-disk layouts are supported:
5
+ * - single file: `<localesPath>/<locale>.json`
6
+ * - per-namespace: `<localesPath>/<locale>/<namespace>.json`
7
+ *
8
+ * When namespaces are used, keys are qualified as `namespace:key` so they stay
9
+ * distinct across files.
10
+ */
11
+ import type { LoadedLocale } from "../types";
12
+ export interface LoadOptions {
13
+ /** Absolute project root; reported file paths are relative to this. */
14
+ rootDir: string;
15
+ /** Locales directory, relative to `rootDir`. */
16
+ localesPath: string;
17
+ }
18
+ export declare function loadLocale(locale: string, opts: LoadOptions): LoadedLocale;
19
+ //# sourceMappingURL=load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../src/locale-parser/load.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,OAAO,KAAK,EAAS,YAAY,EAAe,MAAM,UAAU,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;CACrB;AA+BD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,YAAY,CAiE1E"}
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ /**
3
+ * Load a locale from disk into a flat key map.
4
+ *
5
+ * Two on-disk layouts are supported:
6
+ * - single file: `<localesPath>/<locale>.json`
7
+ * - per-namespace: `<localesPath>/<locale>/<namespace>.json`
8
+ *
9
+ * When namespaces are used, keys are qualified as `namespace:key` so they stay
10
+ * distinct across files.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.loadLocale = loadLocale;
47
+ const fs = __importStar(require("node:fs"));
48
+ const path = __importStar(require("node:path"));
49
+ const flatten_1 = require("../flatten");
50
+ const parse_1 = require("../json/parse");
51
+ const types_1 = require("../types");
52
+ function listLocaleFiles(locale, opts) {
53
+ const base = path.resolve(opts.rootDir, opts.localesPath);
54
+ const files = [];
55
+ const singleFile = path.join(base, `${locale}.json`);
56
+ if (isFile(singleFile)) {
57
+ files.push({ absPath: singleFile, namespace: null });
58
+ }
59
+ const dir = path.join(base, locale);
60
+ if (isDirectory(dir)) {
61
+ for (const entry of fs.readdirSync(dir).sort()) {
62
+ if (entry.endsWith(".json")) {
63
+ files.push({
64
+ absPath: path.join(dir, entry),
65
+ namespace: entry.slice(0, -".json".length),
66
+ });
67
+ }
68
+ }
69
+ }
70
+ return files;
71
+ }
72
+ function loadLocale(locale, opts) {
73
+ const files = listLocaleFiles(locale, opts);
74
+ const entries = new Map();
75
+ const issues = [];
76
+ for (const { absPath, namespace } of files) {
77
+ const relPath = path.relative(opts.rootDir, absPath) || absPath;
78
+ let text;
79
+ try {
80
+ text = fs.readFileSync(absPath, "utf8");
81
+ }
82
+ catch (err) {
83
+ issues.push({
84
+ type: "invalid-json",
85
+ severity: types_1.SEVERITY_BY_TYPE["invalid-json"],
86
+ locale,
87
+ namespace: namespace ?? undefined,
88
+ file: relPath,
89
+ message: `Could not read locale file: ${err.message}`,
90
+ });
91
+ continue;
92
+ }
93
+ let parsed;
94
+ try {
95
+ parsed = (0, parse_1.parseJson)(text);
96
+ }
97
+ catch (err) {
98
+ const pe = err;
99
+ issues.push({
100
+ type: "invalid-json",
101
+ severity: types_1.SEVERITY_BY_TYPE["invalid-json"],
102
+ locale,
103
+ namespace: namespace ?? undefined,
104
+ file: relPath,
105
+ line: pe.line,
106
+ message: `Invalid JSON: ${pe.message}`,
107
+ suggestion: "Fix the JSON syntax so the locale can be parsed.",
108
+ });
109
+ continue;
110
+ }
111
+ for (const dup of parsed.duplicates) {
112
+ issues.push({
113
+ type: "duplicate-key",
114
+ severity: types_1.SEVERITY_BY_TYPE["duplicate-key"],
115
+ locale,
116
+ namespace: namespace ?? undefined,
117
+ key: qualify(namespace, dup.path),
118
+ file: relPath,
119
+ line: dup.line,
120
+ message: `Duplicate key "${dup.path}"`,
121
+ suggestion: "Remove the duplicate; only the last value is kept.",
122
+ });
123
+ }
124
+ for (const [flatKey, value] of (0, flatten_1.flatten)(parsed.value)) {
125
+ const fqKey = qualify(namespace, flatKey);
126
+ entries.set(fqKey, {
127
+ value,
128
+ file: relPath,
129
+ line: parsed.keyLines.get(flatKey) ?? 1,
130
+ });
131
+ }
132
+ }
133
+ return { locale, entries, issues, found: files.length > 0 };
134
+ }
135
+ function qualify(namespace, key) {
136
+ return namespace ? `${namespace}:${key}` : key;
137
+ }
138
+ function isFile(p) {
139
+ try {
140
+ return fs.statSync(p).isFile();
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ function isDirectory(p) {
147
+ try {
148
+ return fs.statSync(p).isDirectory();
149
+ }
150
+ catch {
151
+ return false;
152
+ }
153
+ }
154
+ //# sourceMappingURL=load.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../../src/locale-parser/load.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Interpolation-variable extraction and comparison.
3
+ *
4
+ * Supports the two most common styles:
5
+ * - i18next double-brace: `{{userName}}`, `{{count, number}}`
6
+ * - ICU / react-intl single-brace: `{userName}`, `{count, number}`
7
+ *
8
+ * Note: extraction is intentionally a heuristic. For parity checks this is
9
+ * safe — a placeholder that is captured identically in both the source and
10
+ * target value cancels out, so only genuine divergences are reported.
11
+ */
12
+ import type { JsonPrimitive } from "../types";
13
+ export declare function extractPlaceholders(value: JsonPrimitive): Set<string>;
14
+ export interface PlaceholderDiff {
15
+ /** Variables in the source value that are absent from the target. */
16
+ missing: string[];
17
+ /** Variables in the target value that are absent from the source. */
18
+ extra: string[];
19
+ }
20
+ /**
21
+ * Compare interpolation variables between a source and target value.
22
+ * Returns `null` when the variable sets match.
23
+ */
24
+ export declare function comparePlaceholders(sourceValue: JsonPrimitive, targetValue: JsonPrimitive): PlaceholderDiff | null;
25
+ //# sourceMappingURL=placeholder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholder.d.ts","sourceRoot":"","sources":["../../../src/placeholder-validator/placeholder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAM9C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAuBrE;AA0CD,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,qEAAqE;IACrE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,aAAa,EAC1B,WAAW,EAAE,aAAa,GACzB,eAAe,GAAG,IAAI,CAaxB"}
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * Interpolation-variable extraction and comparison.
4
+ *
5
+ * Supports the two most common styles:
6
+ * - i18next double-brace: `{{userName}}`, `{{count, number}}`
7
+ * - ICU / react-intl single-brace: `{userName}`, `{count, number}`
8
+ *
9
+ * Note: extraction is intentionally a heuristic. For parity checks this is
10
+ * safe — a placeholder that is captured identically in both the source and
11
+ * target value cancels out, so only genuine divergences are reported.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.extractPlaceholders = extractPlaceholders;
15
+ exports.comparePlaceholders = comparePlaceholders;
16
+ const DOUBLE_BRACE = /\{\{\s*([\w$.\-]+)[\s\S]*?\}\}/g;
17
+ const SINGLE_BRACE = /\{\s*([\w$.\-]+)\s*(?:,[^{}]*)?\}/g;
18
+ const ICU_HEAD = /^\s*([A-Za-z_]\w*)\s*,\s*(?:plural|select|selectordinal)\b/;
19
+ function extractPlaceholders(value) {
20
+ const names = new Set();
21
+ if (typeof value !== "string") {
22
+ return names;
23
+ }
24
+ // Remove ICU plural/select/selectordinal blocks first (matching braces),
25
+ // recording only their argument name. This stops their sub-message bodies —
26
+ // e.g. `{He}` in `{gender, select, male {He} ...}` — from being misread as
27
+ // interpolation variables, which would otherwise be a false positive.
28
+ const withoutIcu = stripIcuBlocks(value, names);
29
+ // i18next double-brace: {{userName}}, {{date, datetime}}.
30
+ const cleaned = withoutIcu.replace(DOUBLE_BRACE, (_match, name) => {
31
+ names.add(name);
32
+ return " ";
33
+ });
34
+ // ICU / react-intl single-brace: {userName}, {count, number}, {0}.
35
+ for (const match of cleaned.matchAll(SINGLE_BRACE)) {
36
+ names.add(match[1]);
37
+ }
38
+ return names;
39
+ }
40
+ function stripIcuBlocks(input, names) {
41
+ let out = "";
42
+ let i = 0;
43
+ while (i < input.length) {
44
+ if (input[i] !== "{") {
45
+ out += input[i];
46
+ i += 1;
47
+ continue;
48
+ }
49
+ const end = matchingBrace(input, i);
50
+ if (end === -1) {
51
+ out += input[i];
52
+ i += 1;
53
+ continue;
54
+ }
55
+ const inner = input.slice(i + 1, end);
56
+ const head = ICU_HEAD.exec(inner);
57
+ if (head) {
58
+ names.add(head[1]); // record the ICU argument, drop sub-messages
59
+ }
60
+ else {
61
+ out += input.slice(i, end + 1); // not ICU — leave for brace-based scanning
62
+ }
63
+ i = end + 1;
64
+ }
65
+ return out;
66
+ }
67
+ /** Index of the `}` matching the `{` at `start`, or -1 if unbalanced. */
68
+ function matchingBrace(s, start) {
69
+ let depth = 0;
70
+ for (let i = start; i < s.length; i += 1) {
71
+ if (s[i] === "{")
72
+ depth += 1;
73
+ else if (s[i] === "}") {
74
+ depth -= 1;
75
+ if (depth === 0)
76
+ return i;
77
+ }
78
+ }
79
+ return -1;
80
+ }
81
+ /**
82
+ * Compare interpolation variables between a source and target value.
83
+ * Returns `null` when the variable sets match.
84
+ */
85
+ function comparePlaceholders(sourceValue, targetValue) {
86
+ // Only meaningful when both sides are strings.
87
+ if (typeof sourceValue !== "string" || typeof targetValue !== "string") {
88
+ return null;
89
+ }
90
+ const source = extractPlaceholders(sourceValue);
91
+ const target = extractPlaceholders(targetValue);
92
+ const missing = [...source].filter((name) => !target.has(name)).sort();
93
+ const extra = [...target].filter((name) => !source.has(name)).sort();
94
+ if (missing.length === 0 && extra.length === 0) {
95
+ return null;
96
+ }
97
+ return { missing, extra };
98
+ }
99
+ //# sourceMappingURL=placeholder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholder.js","sourceRoot":"","sources":["../../../src/placeholder-validator/placeholder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Machine-readable JSON reporter, suitable for piping into other tooling.
3
+ */
4
+ import type { CheckResult } from "../types";
5
+ export declare function formatJson(result: CheckResult): string;
6
+ //# sourceMappingURL=json.d.ts.map