@x18n/core 0.0.6 → 0.0.8
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.mts +155 -21
- package/dist/index.mjs +110 -90
- package/package.json +2 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,29 +1,163 @@
|
|
|
1
|
+
import { Diagnostic as Diagnostic$1, Diagnostics } from "nostics";
|
|
1
2
|
import * as v from "valibot";
|
|
2
3
|
//#region src/errors.d.ts
|
|
4
|
+
/** A place in a file; `line` is 1-based. */
|
|
5
|
+
interface At {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly line?: number | undefined;
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* Every coded error. Groups: 0xxx config, 1xxx plugin, 2xxx discovery and parse,
|
|
5
|
-
* 3xxx scanning, 4xxx edits, 5xxx reviews.
|
|
11
|
+
* 3xxx scanning, 4xxx edits, 5xxx reviews. `fix` stays static so the docs can show it.
|
|
6
12
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
declare const errorDefinitions: {
|
|
14
|
+
X18N0001: {
|
|
15
|
+
why: (p: {
|
|
16
|
+
issue: string;
|
|
17
|
+
}) => string;
|
|
18
|
+
fix: string;
|
|
19
|
+
};
|
|
20
|
+
X18N0002: {
|
|
21
|
+
why: (p: {
|
|
22
|
+
file: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}) => string;
|
|
25
|
+
fix: string;
|
|
26
|
+
};
|
|
27
|
+
X18N0003: {
|
|
28
|
+
why: (p: {
|
|
29
|
+
name: string | undefined;
|
|
30
|
+
available: readonly string[];
|
|
31
|
+
}) => string;
|
|
32
|
+
fix: string;
|
|
33
|
+
};
|
|
34
|
+
X18N1001: {
|
|
35
|
+
why: (p: {
|
|
36
|
+
specifier: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
}) => string;
|
|
39
|
+
fix: string;
|
|
40
|
+
};
|
|
41
|
+
X18N1002: {
|
|
42
|
+
why: (p: {
|
|
43
|
+
kind: string;
|
|
44
|
+
id: string;
|
|
45
|
+
}) => string;
|
|
46
|
+
fix: string;
|
|
47
|
+
};
|
|
48
|
+
X18N1003: {
|
|
49
|
+
why: (p: {
|
|
50
|
+
preset: string;
|
|
51
|
+
}) => string;
|
|
52
|
+
fix: string;
|
|
53
|
+
};
|
|
54
|
+
X18N1004: {
|
|
55
|
+
why: (p: {
|
|
56
|
+
file: string;
|
|
57
|
+
issues: string;
|
|
58
|
+
}) => string;
|
|
59
|
+
fix: string;
|
|
60
|
+
};
|
|
61
|
+
X18N2001: {
|
|
62
|
+
why: (p: {
|
|
63
|
+
path: string;
|
|
64
|
+
}) => string;
|
|
65
|
+
fix: string;
|
|
66
|
+
};
|
|
67
|
+
X18N2002: {
|
|
68
|
+
why: (p: At & {
|
|
69
|
+
reason: string;
|
|
70
|
+
}) => string;
|
|
71
|
+
fix: string;
|
|
72
|
+
};
|
|
73
|
+
X18N2003: {
|
|
74
|
+
why: (p: At & {
|
|
75
|
+
reason?: string | undefined;
|
|
76
|
+
}) => string;
|
|
77
|
+
fix: string;
|
|
78
|
+
};
|
|
79
|
+
X18N2004: {
|
|
80
|
+
why: (p: At & {
|
|
81
|
+
reason: string;
|
|
82
|
+
}) => string;
|
|
83
|
+
fix: string;
|
|
84
|
+
};
|
|
85
|
+
X18N3001: {
|
|
86
|
+
why: (p: {
|
|
87
|
+
path: string;
|
|
88
|
+
reason: string;
|
|
89
|
+
}) => string;
|
|
90
|
+
fix: string;
|
|
91
|
+
};
|
|
92
|
+
X18N4001: {
|
|
93
|
+
why: (p: {
|
|
94
|
+
path: string;
|
|
95
|
+
}) => string;
|
|
96
|
+
fix: string;
|
|
97
|
+
};
|
|
98
|
+
X18N4002: {
|
|
99
|
+
why: (p: {
|
|
100
|
+
key: string;
|
|
101
|
+
files: readonly string[];
|
|
102
|
+
}) => string;
|
|
103
|
+
fix: string;
|
|
104
|
+
};
|
|
105
|
+
X18N4003: {
|
|
106
|
+
why: (p: {
|
|
107
|
+
decisions: readonly string[];
|
|
108
|
+
}) => string;
|
|
109
|
+
fix: string;
|
|
110
|
+
};
|
|
111
|
+
X18N4004: {
|
|
112
|
+
why: (p: {
|
|
113
|
+
path?: string | undefined;
|
|
114
|
+
}) => string;
|
|
115
|
+
fix: string;
|
|
116
|
+
};
|
|
117
|
+
X18N4005: {
|
|
118
|
+
why: (p: {
|
|
119
|
+
key: string;
|
|
120
|
+
locale: string;
|
|
121
|
+
}) => string;
|
|
122
|
+
fix: string;
|
|
123
|
+
};
|
|
124
|
+
X18N4006: {
|
|
125
|
+
why: (p: {
|
|
126
|
+
key: string;
|
|
127
|
+
}) => string;
|
|
128
|
+
fix: string;
|
|
129
|
+
};
|
|
130
|
+
X18N4007: {
|
|
131
|
+
why: (p: {
|
|
132
|
+
count: number;
|
|
133
|
+
}) => string;
|
|
134
|
+
fix: string;
|
|
135
|
+
};
|
|
136
|
+
X18N4008: {
|
|
137
|
+
why: (p: {
|
|
138
|
+
reason: string;
|
|
139
|
+
}) => string;
|
|
140
|
+
fix: string;
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
type ErrorCode = keyof typeof errorDefinitions;
|
|
144
|
+
type X18nError = Diagnostic$1 & {
|
|
145
|
+
readonly name: ErrorCode;
|
|
19
146
|
readonly code: ErrorCode;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
147
|
+
};
|
|
148
|
+
type ErrorHandles = { readonly [C in ErrorCode]: (...args: Parameters<Diagnostics<typeof errorDefinitions, []>[C]>) => X18nError; };
|
|
149
|
+
/** Raise with `throw errors.X18N2002({ path, reason })`. Core has no reporters: surfaces print what they catch. */
|
|
150
|
+
export declare const errors: ErrorHandles;
|
|
151
|
+
/** JSON form of an error: the `toJSON()` of a nostics diagnostic, without `stack`, `cause` and `data`. */
|
|
152
|
+
interface ErrorInfo {
|
|
153
|
+
/** The code, such as `X18N2002`, or the JS error name of an error without a code. */
|
|
154
|
+
readonly name: string;
|
|
155
|
+
readonly why: string;
|
|
156
|
+
readonly fix?: string | undefined;
|
|
157
|
+
readonly docs?: string | undefined;
|
|
25
158
|
}
|
|
26
159
|
export declare function isX18nError(error: unknown): error is X18nError;
|
|
160
|
+
export declare function toErrorInfo(error: unknown): ErrorInfo;
|
|
27
161
|
//#endregion
|
|
28
162
|
//#region src/types/model.d.ts
|
|
29
163
|
/** A locale code exactly as written in paths and files: `en`, `zh_CN`, `pt-BR`. Never normalized. */
|
|
@@ -184,8 +318,8 @@ interface JsonObject {
|
|
|
184
318
|
}
|
|
185
319
|
//#endregion
|
|
186
320
|
//#region src/contracts.d.ts
|
|
187
|
-
/** Version of every JSON contract (CLI `--json`, LSP `x18n/*`, RPC, MCP). Bumped on breaking changes to any
|
|
188
|
-
export declare const contractVersion =
|
|
321
|
+
/** Version of every JSON contract (CLI `--json`, LSP `x18n/*`, RPC, MCP). Bumped on breaking changes to any shape. */
|
|
322
|
+
export declare const contractVersion = 2;
|
|
189
323
|
interface WireRange {
|
|
190
324
|
/** 1-based. */
|
|
191
325
|
readonly line: number;
|
|
@@ -1313,4 +1447,4 @@ export declare function rangeContains(range: TextRange, offset: number): boolean
|
|
|
1313
1447
|
/** Fast non-cryptographic string hash (cyrb53), as hex. */
|
|
1314
1448
|
export declare function hashText(text: string): string;
|
|
1315
1449
|
//#endregion
|
|
1316
|
-
export type { Analysis, Answers, ApplyResult, ArgSource, Builtins, CacheStore, CallSpec, CapabilityConflict, Check, CheckContext, CheckIds, CheckLevel, CheckSetting, ConfigContext, ConfigDraft, ConfigOrigin, CreateProjectOptions, Decision, DecisionOption, DetectContext, Diagnostic, DiagnosticInput, Disposable, EditOp, Envelope, ErrorCode, ErrorInfo, ExtractItem, ExtractOptions, ExtractTemplate, Extractor, FileChange, FileEvent, FileScan, FinderContext, Fix, Format, FormatContext, GlobOptions, GlossaryEntry, HardString, HistoryConfigs, HistoryContext, HistoryOptions, HistoryProvider, HistorySettings, Host, JsonObject, JsonPrimitive, JsonValue, KeyContext, KeyId, KeyInfo, KeyQuery, KeyString, KeyTarget, LineIndex, LinePosition, LocaleCode, LocaleCoverage, LocaleFinder, LocaleInfo, Location, Logger, ManifestReader, MemoryHost, Message, MessageChange, MessageHistory, MessageLeaf, MessageOp, MessageTree, NamespaceFromPath, NamespaceOptions, NodeRange, OverlayHost, ParseResult, Placeholder, PlaceholderComparer, PlaceholderProblem, PlaceholderSyntax, Plan, Plugin, PluginConfigs, PluginFile, PluginOption, PluginRegistry, PluginSpecifier, Preset, PresetExplanation, PresetFactory, PresetOption, Project, ProjectChange, ProjectExplanation, PromptContext, PromptData, PromptDraft, PromptItem, PromptOptions, RegexScannerSpec, ReportInput, ReportSummary, Reporter, ReporterContext, ResolveConfigOptions, ResolvedConfig, Resource, ResourceBlock, ResourceCandidate, ResourceId, ResourceNode, ResourceSpec, ResourceTransform, ResourceTransformContext, ScanContext, ScanResult, Scanner, SchemaIssue, Scope, SerializableConfig, Setup, Severity, SourceFile, TextEdit, TextRange, TreeLeaf, Usage, UsageCandidate, UsageContext, UsageKind, UsageMatch, UsageOptions, UsageQuery, UserConfig, WireDiagnostic, WireRange, WriteOptions };
|
|
1450
|
+
export type { Analysis, Answers, ApplyResult, ArgSource, Builtins, CacheStore, CallSpec, CapabilityConflict, Check, CheckContext, CheckIds, CheckLevel, CheckSetting, ConfigContext, ConfigDraft, ConfigOrigin, CreateProjectOptions, Decision, DecisionOption, DetectContext, Diagnostic, DiagnosticInput, Disposable, EditOp, Envelope, ErrorCode, ErrorInfo, ExtractItem, ExtractOptions, ExtractTemplate, Extractor, FileChange, FileEvent, FileScan, FinderContext, Fix, Format, FormatContext, GlobOptions, GlossaryEntry, HardString, HistoryConfigs, HistoryContext, HistoryOptions, HistoryProvider, HistorySettings, Host, JsonObject, JsonPrimitive, JsonValue, KeyContext, KeyId, KeyInfo, KeyQuery, KeyString, KeyTarget, LineIndex, LinePosition, LocaleCode, LocaleCoverage, LocaleFinder, LocaleInfo, Location, Logger, ManifestReader, MemoryHost, Message, MessageChange, MessageHistory, MessageLeaf, MessageOp, MessageTree, NamespaceFromPath, NamespaceOptions, NodeRange, OverlayHost, ParseResult, Placeholder, PlaceholderComparer, PlaceholderProblem, PlaceholderSyntax, Plan, Plugin, PluginConfigs, PluginFile, PluginOption, PluginRegistry, PluginSpecifier, Preset, PresetExplanation, PresetFactory, PresetOption, Project, ProjectChange, ProjectExplanation, PromptContext, PromptData, PromptDraft, PromptItem, PromptOptions, RegexScannerSpec, ReportInput, ReportSummary, Reporter, ReporterContext, ResolveConfigOptions, ResolvedConfig, Resource, ResourceBlock, ResourceCandidate, ResourceId, ResourceNode, ResourceSpec, ResourceTransform, ResourceTransformContext, ScanContext, ScanResult, Scanner, SchemaIssue, Scope, SerializableConfig, Setup, Severity, SourceFile, TextEdit, TextRange, TreeLeaf, Usage, UsageCandidate, UsageContext, UsageKind, UsageMatch, UsageOptions, UsageQuery, UserConfig, WireDiagnostic, WireRange, WriteOptions, X18nError };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Diagnostic, defineDiagnostics } from "nostics";
|
|
1
2
|
import * as v from "valibot";
|
|
2
3
|
//#region src/text.ts
|
|
3
4
|
/** One edit covering only the part that differs, so bytes outside it stay the same. */
|
|
@@ -85,8 +86,8 @@ function hashText(text) {
|
|
|
85
86
|
}
|
|
86
87
|
//#endregion
|
|
87
88
|
//#region src/contracts.ts
|
|
88
|
-
/** Version of every JSON contract (CLI `--json`, LSP `x18n/*`, RPC, MCP). Bumped on breaking changes to any
|
|
89
|
-
const contractVersion =
|
|
89
|
+
/** Version of every JSON contract (CLI `--json`, LSP `x18n/*`, RPC, MCP). Bumped on breaking changes to any shape. */
|
|
90
|
+
const contractVersion = 2;
|
|
90
91
|
/** Line and column form of an offset range in a text. */
|
|
91
92
|
function toWireRange(text, start, end) {
|
|
92
93
|
const index = createLineIndex(text ?? "");
|
|
@@ -117,7 +118,7 @@ function toWireDiagnostic(project, diagnostic, text) {
|
|
|
117
118
|
}
|
|
118
119
|
function envelope(command, data, diagnostics = [], errors = []) {
|
|
119
120
|
return {
|
|
120
|
-
x18n:
|
|
121
|
+
x18n: 2,
|
|
121
122
|
command,
|
|
122
123
|
data,
|
|
123
124
|
diagnostics,
|
|
@@ -137,113 +138,115 @@ function defineConfig(config) {
|
|
|
137
138
|
}
|
|
138
139
|
//#endregion
|
|
139
140
|
//#region src/errors.ts
|
|
140
|
-
const
|
|
141
|
+
const at = (p) => p.line === void 0 ? p.path : `${p.path}:${p.line}`;
|
|
142
|
+
/**
|
|
143
|
+
* Every coded error. Groups: 0xxx config, 1xxx plugin, 2xxx discovery and parse,
|
|
144
|
+
* 3xxx scanning, 4xxx edits, 5xxx reviews. `fix` stays static so the docs can show it.
|
|
145
|
+
*/
|
|
146
|
+
const errorDefinitions = {
|
|
141
147
|
X18N0001: {
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
why: (p) => `Invalid config: ${p.issue}`,
|
|
149
|
+
fix: "Check the reported field against the config schema (`x18n schema config`)."
|
|
144
150
|
},
|
|
145
151
|
X18N0002: {
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
why: (p) => `Config file ${p.file} could not be loaded: ${p.reason}`,
|
|
153
|
+
fix: "Fix the syntax error, or run `x18n doctor` for details."
|
|
148
154
|
},
|
|
149
155
|
X18N0003: {
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
why: (p) => `${p.name === void 0 ? "Pick a project" : `Unknown project "${p.name}"`} (${p.available.join(", ")})`,
|
|
157
|
+
fix: "Pick a project name listed by `x18n status`, for example with `--project`."
|
|
152
158
|
},
|
|
153
159
|
X18N1001: {
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
why: (p) => `Plugin ${p.specifier} could not be loaded: ${p.reason}`,
|
|
161
|
+
fix: "Install the plugin package, or check the path."
|
|
156
162
|
},
|
|
157
163
|
X18N1002: {
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
why: (p) => `Duplicate ${p.kind} id "${p.id}"`,
|
|
165
|
+
fix: "Rename the id, or set `override: true` on the later one."
|
|
160
166
|
},
|
|
161
167
|
X18N1003: {
|
|
162
|
-
|
|
163
|
-
|
|
168
|
+
why: (p) => `Unknown preset "${p.preset}"`,
|
|
169
|
+
fix: "Use a preset name listed in the docs, or a preset object."
|
|
164
170
|
},
|
|
165
171
|
X18N1004: {
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
why: (p) => `Invalid YAML plugin ${p.file}: ${p.issues}`,
|
|
173
|
+
fix: "Validate the file against `schema/plugin.json`."
|
|
168
174
|
},
|
|
169
175
|
X18N2001: {
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
why: (p) => `No format for resource ${p.path}`,
|
|
177
|
+
fix: "Map the extension in `formats`, or add a format plugin."
|
|
172
178
|
},
|
|
173
179
|
X18N2002: {
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
why: (p) => `Resource ${at(p)} could not be parsed: ${p.reason}`,
|
|
181
|
+
fix: "Fix the syntax error in the file."
|
|
176
182
|
},
|
|
177
183
|
X18N2003: {
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
why: (p) => `Resource ${at(p)} is not an object of messages${p.reason ? `: ${p.reason}` : ""}`,
|
|
185
|
+
fix: "Locale files hold nested objects of strings, numbers, booleans or null."
|
|
180
186
|
},
|
|
181
187
|
X18N2004: {
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
why: (p) => `Locale file ${at(p)} is not static: ${p.reason}`,
|
|
189
|
+
fix: "Use JSON or YAML, or opt in to execution with `execute: true` on the resource pattern."
|
|
184
190
|
},
|
|
185
191
|
X18N3001: {
|
|
186
|
-
|
|
187
|
-
|
|
192
|
+
why: (p) => `Source file ${p.path} could not be scanned: ${p.reason}`,
|
|
193
|
+
fix: "Fix the syntax error, or exclude the file with `usage.exclude`."
|
|
188
194
|
},
|
|
189
195
|
X18N4001: {
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
why: (p) => `Resource ${p.path} is read-only`,
|
|
197
|
+
fix: "Edit the source of computed values, or use a writable format."
|
|
192
198
|
},
|
|
193
199
|
X18N4002: {
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
why: (p) => `Rename target "${p.key}" already exists${p.files.length ? ` in ${p.files.join(", ")}` : ""}`,
|
|
201
|
+
fix: "Delete or rename the existing key first."
|
|
196
202
|
},
|
|
197
203
|
X18N4003: {
|
|
198
|
-
|
|
199
|
-
|
|
204
|
+
why: (p) => `A decision is needed: ${p.decisions.join(" ")}`,
|
|
205
|
+
fix: "Answer the listed decisions, or pass `--yes` to accept defaults."
|
|
200
206
|
},
|
|
201
207
|
X18N4004: {
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
why: (p) => p.path === void 0 ? "Plan is stale" : `Plan is stale: ${p.path} changed since the plan was made`,
|
|
209
|
+
fix: "Files changed since the plan was made. Plan again."
|
|
204
210
|
},
|
|
205
211
|
X18N4005: {
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
why: (p) => `No resource for the key ${p.key} in ${p.locale}`,
|
|
213
|
+
fix: "Add a resource pattern for the locale, or create the locale file."
|
|
208
214
|
},
|
|
209
215
|
X18N4006: {
|
|
210
|
-
|
|
211
|
-
|
|
216
|
+
why: (p) => `Key not found: "${p.key}"`,
|
|
217
|
+
fix: "List keys with `x18n keys` (agents: the `x18n_keys` tool)."
|
|
212
218
|
},
|
|
213
219
|
X18N4007: {
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
why: (p) => `Placeholder mismatch in ${p.count} values`,
|
|
221
|
+
fix: "Keep every placeholder of the source value, or pass `--force`."
|
|
216
222
|
},
|
|
217
223
|
X18N4008: {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
var X18nError = class extends Error {
|
|
223
|
-
code;
|
|
224
|
-
hint;
|
|
225
|
-
docs;
|
|
226
|
-
details;
|
|
227
|
-
constructor(code, detail, details = []) {
|
|
228
|
-
const base = errorCodes[code];
|
|
229
|
-
super(detail ? `${base.message}: ${detail}` : base.message);
|
|
230
|
-
this.name = "X18nError";
|
|
231
|
-
this.code = code;
|
|
232
|
-
this.hint = base.hint;
|
|
233
|
-
this.docs = `https://x18n.antfu.dev/errors/${code.toLowerCase()}`;
|
|
234
|
-
this.details = details;
|
|
235
|
-
}
|
|
236
|
-
toJSON() {
|
|
237
|
-
return {
|
|
238
|
-
code: this.code,
|
|
239
|
-
message: this.message,
|
|
240
|
-
hint: this.hint,
|
|
241
|
-
docs: this.docs
|
|
242
|
-
};
|
|
224
|
+
why: (p) => `Invalid edit input: ${p.reason}`,
|
|
225
|
+
fix: "Check the input shape against the docs."
|
|
243
226
|
}
|
|
244
227
|
};
|
|
228
|
+
/** Raise with `throw errors.X18N2002({ path, reason })`. Core has no reporters: surfaces print what they catch. */
|
|
229
|
+
const errors = defineDiagnostics({
|
|
230
|
+
docsBase: "https://x18n.antfu.dev/errors",
|
|
231
|
+
codes: errorDefinitions
|
|
232
|
+
});
|
|
245
233
|
function isX18nError(error) {
|
|
246
|
-
return error instanceof
|
|
234
|
+
return error instanceof Diagnostic && Object.hasOwn(errorDefinitions, error.code);
|
|
235
|
+
}
|
|
236
|
+
function toErrorInfo(error) {
|
|
237
|
+
if (isX18nError(error)) return {
|
|
238
|
+
name: error.name,
|
|
239
|
+
why: error.why,
|
|
240
|
+
fix: error.fix,
|
|
241
|
+
docs: error.docs
|
|
242
|
+
};
|
|
243
|
+
return error instanceof Error ? {
|
|
244
|
+
name: error.name,
|
|
245
|
+
why: error.message
|
|
246
|
+
} : {
|
|
247
|
+
name: "Error",
|
|
248
|
+
why: String(error)
|
|
249
|
+
};
|
|
247
250
|
}
|
|
248
251
|
//#endregion
|
|
249
252
|
//#region src/keys.ts
|
|
@@ -547,7 +550,10 @@ function sortByEnforce(plugins) {
|
|
|
547
550
|
function uniqueById(items, kind) {
|
|
548
551
|
const out = /* @__PURE__ */ new Map();
|
|
549
552
|
for (const item of items) {
|
|
550
|
-
if (out.has(item.id) && !item.override) throw
|
|
553
|
+
if (out.has(item.id) && !item.override) throw errors.X18N1002({
|
|
554
|
+
kind,
|
|
555
|
+
id: item.id
|
|
556
|
+
});
|
|
551
557
|
out.set(item.id, item);
|
|
552
558
|
}
|
|
553
559
|
return out;
|
|
@@ -799,12 +805,12 @@ function isLeaf(value) {
|
|
|
799
805
|
}
|
|
800
806
|
/** Parses the batch shape `{ "<locale>": { "<key>": "<value>" } }` (as in `x18n apply`) into set ops. */
|
|
801
807
|
function parseMessageBatch(project, data) {
|
|
802
|
-
if (!isJsonObject(data)) throw
|
|
808
|
+
if (!isJsonObject(data)) throw errors.X18N4008({ reason: "expected { \"<locale>\": { \"<key>\": \"<value>\" } }" });
|
|
803
809
|
const ops = [];
|
|
804
810
|
for (const [locale, entries] of Object.entries(data)) {
|
|
805
|
-
if (!isJsonObject(entries)) throw
|
|
811
|
+
if (!isJsonObject(entries)) throw errors.X18N4008({ reason: `"${locale}" must map keys to values` });
|
|
806
812
|
for (const [raw, value] of Object.entries(entries)) {
|
|
807
|
-
if (!isLeaf(value)) throw
|
|
813
|
+
if (!isLeaf(value)) throw errors.X18N4008({ reason: `"${locale}.${raw}" must be a string, number, boolean or null` });
|
|
808
814
|
ops.push({
|
|
809
815
|
type: "set",
|
|
810
816
|
key: project.parseKey(raw),
|
|
@@ -1186,7 +1192,7 @@ var PlanBuilder = class {
|
|
|
1186
1192
|
/** ---- Targets ---- */
|
|
1187
1193
|
targetOf(s) {
|
|
1188
1194
|
const format = this.options.registry.formatById(s.resource.format);
|
|
1189
|
-
if (!format) throw
|
|
1195
|
+
if (!format) throw errors.X18N2001({ path: s.resource.path });
|
|
1190
1196
|
return {
|
|
1191
1197
|
id: s.resource.id,
|
|
1192
1198
|
path: s.resource.path,
|
|
@@ -1212,7 +1218,7 @@ var PlanBuilder = class {
|
|
|
1212
1218
|
}
|
|
1213
1219
|
writable(id) {
|
|
1214
1220
|
const s = this.stateById.get(id);
|
|
1215
|
-
if (s.resource.readonly) throw
|
|
1221
|
+
if (s.resource.readonly) throw errors.X18N4001({ path: s.resource.path });
|
|
1216
1222
|
return s;
|
|
1217
1223
|
}
|
|
1218
1224
|
keyId(key) {
|
|
@@ -1263,7 +1269,10 @@ var PlanBuilder = class {
|
|
|
1263
1269
|
const format = model && this.options.registry.formatById(model.resource.format);
|
|
1264
1270
|
let path = model && mirrorPath(model.resource.path, model.resource.locale, locale);
|
|
1265
1271
|
if (model && path && namespace !== void 0 && model.resource.namespace !== void 0 && model.resource.namespace !== namespace) path = mirrorPath(path, model.resource.namespace, namespace);
|
|
1266
|
-
if (!model || !format?.serialize || model.resource.block || !path || exists(path)) throw
|
|
1272
|
+
if (!model || !format?.serialize || model.resource.block || !path || exists(path)) throw errors.X18N4005({
|
|
1273
|
+
key,
|
|
1274
|
+
locale
|
|
1275
|
+
});
|
|
1267
1276
|
const target = {
|
|
1268
1277
|
id: path,
|
|
1269
1278
|
path,
|
|
@@ -1478,7 +1487,10 @@ var PlanBuilder = class {
|
|
|
1478
1487
|
const existing = this.options.messages(to);
|
|
1479
1488
|
if (!existing && !this.options.parents().has(to)) return;
|
|
1480
1489
|
const files = [...new Set([...existing?.values() ?? []].flatMap((list) => list.map((m) => this.stateById.get(m.resource)?.resource.path ?? m.resource)))];
|
|
1481
|
-
throw
|
|
1490
|
+
throw errors.X18N4002({
|
|
1491
|
+
key: to,
|
|
1492
|
+
files
|
|
1493
|
+
});
|
|
1482
1494
|
}
|
|
1483
1495
|
rename(op) {
|
|
1484
1496
|
if (op.from === op.to) return;
|
|
@@ -1490,7 +1502,7 @@ var PlanBuilder = class {
|
|
|
1490
1502
|
if (op.type === "rename-prefix") keys = under;
|
|
1491
1503
|
else if (isLeaf) keys = [op.from, ...derived];
|
|
1492
1504
|
else keys = under.length > 0 ? under : derived;
|
|
1493
|
-
if (keys.length === 0) throw
|
|
1505
|
+
if (keys.length === 0) throw errors.X18N4006({ key: op.from });
|
|
1494
1506
|
const pairs = keys.map((key) => [key, replaceKeyPrefix(key, op.from, op.to)]);
|
|
1495
1507
|
for (const [, to] of pairs) this.conflictCheck(to);
|
|
1496
1508
|
const renamed = keys.includes(op.from) ? /* @__PURE__ */ new Set() : this.renameNode(op.from, op.to);
|
|
@@ -1504,7 +1516,7 @@ var PlanBuilder = class {
|
|
|
1504
1516
|
}
|
|
1505
1517
|
delete(op) {
|
|
1506
1518
|
const keys = this.options.messages(op.key) ? [op.key] : this.options.allKeys().filter((key) => isKeyUnder(key, op.key));
|
|
1507
|
-
if (keys.length === 0) throw
|
|
1519
|
+
if (keys.length === 0) throw errors.X18N4006({ key: op.key });
|
|
1508
1520
|
for (const key of keys) {
|
|
1509
1521
|
this.touched.add(key);
|
|
1510
1522
|
for (const [locale, list] of this.options.messages(key) ?? []) if (op.locales === void 0 || op.locales === "all" || op.locales.includes(locale)) list.forEach((m) => this.deleteMessage(m));
|
|
@@ -1563,7 +1575,7 @@ var PlanBuilder = class {
|
|
|
1563
1575
|
const sort = this.config.write.sort !== false;
|
|
1564
1576
|
const edit = target.format.edit;
|
|
1565
1577
|
if (entry.formatOnly || sort || !edit) {
|
|
1566
|
-
if (!target.format.serialize) throw
|
|
1578
|
+
if (!target.format.serialize) throw errors.X18N4001({ path: target.path });
|
|
1567
1579
|
if (!entry.formatOnly && !edit) this.warnings.push(`${target.path} was re-written in full (the ${target.format.id} format cannot edit in place); formatting was reset.`);
|
|
1568
1580
|
return target.format.serialize(sort || entry.formatOnly ? sortTree(entry.tree, sort ? this.compare() : () => 0) : entry.tree, ctx);
|
|
1569
1581
|
}
|
|
@@ -1713,8 +1725,8 @@ function createPlanner(options) {
|
|
|
1713
1725
|
},
|
|
1714
1726
|
async apply(plan, answers) {
|
|
1715
1727
|
const unanswered = plan.decisions.filter((d) => answers[d.id] === void 0);
|
|
1716
|
-
if (unanswered.length > 0) throw
|
|
1717
|
-
if (plan.revision !== options.state().revision) throw
|
|
1728
|
+
if (unanswered.length > 0) throw errors.X18N4003({ decisions: unanswered.map((d) => d.message) });
|
|
1729
|
+
if (plan.revision !== options.state().revision) throw errors.X18N4004();
|
|
1718
1730
|
const fresh = plan.decisions.length > 0 ? planWith(plan.ops, answers) : {
|
|
1719
1731
|
plan,
|
|
1720
1732
|
texts: texts.get(plan.id) ?? /* @__PURE__ */ new Map()
|
|
@@ -1722,7 +1734,7 @@ function createPlanner(options) {
|
|
|
1722
1734
|
for (const change of fresh.plan.changes) {
|
|
1723
1735
|
const current = await options.host.readFile(change.path);
|
|
1724
1736
|
const expected = fresh.texts.get(change.path);
|
|
1725
|
-
if (change.type === "edit" && expected !== void 0 && current !== expected || change.type === "create" && current !== void 0) throw
|
|
1737
|
+
if (change.type === "edit" && expected !== void 0 && current !== expected || change.type === "create" && current !== void 0) throw errors.X18N4004({ path: change.path });
|
|
1726
1738
|
}
|
|
1727
1739
|
const events = [];
|
|
1728
1740
|
for (const change of fresh.plan.changes) events.push(await write(change));
|
|
@@ -2105,13 +2117,13 @@ function createProject(options) {
|
|
|
2105
2117
|
tree: {},
|
|
2106
2118
|
rawTree: {},
|
|
2107
2119
|
ranges: /* @__PURE__ */ new Map(),
|
|
2108
|
-
error:
|
|
2120
|
+
error: errors.X18N2001({ path: candidate.path })
|
|
2109
2121
|
};
|
|
2110
2122
|
try {
|
|
2111
2123
|
const parsed = format.parse(content, ctx);
|
|
2112
2124
|
let tree = parsed.tree;
|
|
2113
2125
|
for (const transform of transforms) if (transform.applies?.(ctx) !== false) tree = transform.read(tree, ctx);
|
|
2114
|
-
if (!isMessageTree(tree)) throw
|
|
2126
|
+
if (!isMessageTree(tree)) throw errors.X18N2003({ path: candidate.path });
|
|
2115
2127
|
return {
|
|
2116
2128
|
...base,
|
|
2117
2129
|
tree,
|
|
@@ -2120,7 +2132,11 @@ function createProject(options) {
|
|
|
2120
2132
|
error: void 0
|
|
2121
2133
|
};
|
|
2122
2134
|
} catch (error) {
|
|
2123
|
-
const coded = isX18nError(error) ? error :
|
|
2135
|
+
const coded = isX18nError(error) ? error : errors.X18N2002({
|
|
2136
|
+
path: candidate.path,
|
|
2137
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
2138
|
+
cause: error
|
|
2139
|
+
});
|
|
2124
2140
|
return {
|
|
2125
2141
|
...base,
|
|
2126
2142
|
tree: {},
|
|
@@ -2249,7 +2265,11 @@ function createProject(options) {
|
|
|
2249
2265
|
try {
|
|
2250
2266
|
store(file, await scanOne(file));
|
|
2251
2267
|
} catch (error) {
|
|
2252
|
-
store(file, void 0,
|
|
2268
|
+
store(file, void 0, errors.X18N3001({
|
|
2269
|
+
path: file.path,
|
|
2270
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
2271
|
+
cause: error
|
|
2272
|
+
}));
|
|
2253
2273
|
}
|
|
2254
2274
|
}));
|
|
2255
2275
|
}
|
|
@@ -2554,7 +2574,7 @@ function createProject(options) {
|
|
|
2554
2574
|
path
|
|
2555
2575
|
}] : [])
|
|
2556
2576
|
].map(({ error, path }) => ({
|
|
2557
|
-
...error
|
|
2577
|
+
...toErrorInfo(error),
|
|
2558
2578
|
path
|
|
2559
2579
|
})),
|
|
2560
2580
|
config
|
|
@@ -2934,7 +2954,7 @@ function validateLayers(layers) {
|
|
|
2934
2954
|
for (const layer of layers) {
|
|
2935
2955
|
if (!layer) continue;
|
|
2936
2956
|
const result = validate(ConfigSchema, serializablePart(layer));
|
|
2937
|
-
if (!result.success) throw
|
|
2957
|
+
if (!result.success) throw errors.X18N0001({ issue: result.issues.map((issue) => `${issue.path || "(root)"}: ${issue.message}`).join("; ") });
|
|
2938
2958
|
}
|
|
2939
2959
|
}
|
|
2940
2960
|
function createLoader(options) {
|
|
@@ -2944,7 +2964,7 @@ function createLoader(options) {
|
|
|
2944
2964
|
const [specifier, pluginOptions] = typeof option === "string" ? [option, void 0] : [option[0], option[1]];
|
|
2945
2965
|
const factory = options.builtins.presets[specifier];
|
|
2946
2966
|
if (factory) return factory(pluginOptions);
|
|
2947
|
-
if (!options.loadPlugin) throw
|
|
2967
|
+
if (!options.loadPlugin) throw errors.X18N1003({ preset: specifier });
|
|
2948
2968
|
return options.loadPlugin(specifier, pluginOptions);
|
|
2949
2969
|
};
|
|
2950
2970
|
}
|
|
@@ -3043,4 +3063,4 @@ async function resolveConfig(options) {
|
|
|
3043
3063
|
};
|
|
3044
3064
|
}
|
|
3045
3065
|
//#endregion
|
|
3046
|
-
export { ConfigSchema, PluginFileSchema,
|
|
3066
|
+
export { ConfigSchema, PluginFileSchema, applyMessageOps, applyTextEdits, buildTree, contractVersion, createLineIndex, createMemoryHost, createOverlayHost, createPlaceholderComparer, createProject, createRegistry, defineConfig, definePlugin, definePreset, diffText, envelope, errors, extensionOf, globToRegExp, hashText, isJsonObject, isKeyUnder, isMessageLeaf, isMessageTree, isPreset, isX18nError, joinKey, keyMatcher, localeSpellingKey, parseLocaleInfo, parseMessageBatch, pathId, pathMatcher, placeholderProblems, rangeContains, renderPrompt, replaceKeyPrefix, resolveConfig, sortTree, splitKey, toErrorInfo, toWireDiagnostic, toWireRange, validate, walkLeaves };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x18n/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.8",
|
|
5
5
|
"description": "Runtime-neutral engine of x18n: domain model, plugin API and project analysis",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"schema"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"nostics": "^1.3.0",
|
|
28
29
|
"valibot": "^1.5.0"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|