@x18n/core 0.1.8 → 0.1.9
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 +38 -6
- package/dist/index.mjs +140 -46
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -650,6 +650,29 @@ interface ResourceTransform {
|
|
|
650
650
|
/** Descriptions that the stored tree holds beside the messages, such as `{ "name": { "message": "...", "description": "..." } }`, keyed by `pathId(storedPath)`. */
|
|
651
651
|
readonly describe?: ((tree: MessageTree, ctx: ResourceTransformContext) => ReadonlyMap<string, string>) | undefined;
|
|
652
652
|
}
|
|
653
|
+
/** A review edit of a plan, after it is applied to the review data. */
|
|
654
|
+
interface ReviewEdit {
|
|
655
|
+
/** The review type of a new comment, or the op that resolved or applied one. */
|
|
656
|
+
readonly type: ReviewType | "resolve-review" | "apply-suggestion";
|
|
657
|
+
readonly key: KeyString;
|
|
658
|
+
readonly locale: LocaleCode;
|
|
659
|
+
/** The new comment, or the comment that was resolved or applied. */
|
|
660
|
+
readonly comment: ReviewComment;
|
|
661
|
+
/** The thread of the message after the edit, oldest first. */
|
|
662
|
+
readonly comments: readonly ReviewComment[];
|
|
663
|
+
}
|
|
664
|
+
interface ReviewEditContext {
|
|
665
|
+
/** A message before the plan. */
|
|
666
|
+
readonly message: (key: KeyString, locale: LocaleCode) => Message | undefined;
|
|
667
|
+
readonly resource: (id: ResourceId) => Resource | undefined;
|
|
668
|
+
/** The text of a project file as the plan leaves it so far, including earlier writes of `reviewEdit` hooks. */
|
|
669
|
+
readonly text: (path: string) => string | undefined;
|
|
670
|
+
}
|
|
671
|
+
/** The full new content of a file, which core puts into the plan. */
|
|
672
|
+
interface FileWrite {
|
|
673
|
+
readonly path: string;
|
|
674
|
+
readonly content: string;
|
|
675
|
+
}
|
|
653
676
|
interface PromptDraft {
|
|
654
677
|
readonly rules: string[];
|
|
655
678
|
readonly notes: string[];
|
|
@@ -688,6 +711,8 @@ interface Plugin {
|
|
|
688
711
|
readonly derivedKeys?: readonly string[] | undefined;
|
|
689
712
|
readonly transformResource?: ResourceTransform | undefined;
|
|
690
713
|
readonly translatePrompt?: ((draft: PromptDraft, ctx: PromptContext) => void) | undefined;
|
|
714
|
+
/** Called for each review edit of a plan, in op order. The writes it returns go into the same plan. */
|
|
715
|
+
readonly reviewEdit?: ((edit: ReviewEdit, ctx: ReviewEditContext) => readonly FileWrite[] | undefined) | undefined;
|
|
691
716
|
/** Display data for a locale code. The first plugin that returns a value wins. */
|
|
692
717
|
readonly localeInfo?: ((code: LocaleCode) => Partial<LocaleInfo> | undefined) | undefined;
|
|
693
718
|
}
|
|
@@ -917,7 +942,7 @@ interface SerializableConfig {
|
|
|
917
942
|
} | undefined;
|
|
918
943
|
/** Turn every warning into an error. */
|
|
919
944
|
readonly strict?: boolean | undefined;
|
|
920
|
-
/** Keys (globs) excluded from all checks. */
|
|
945
|
+
/** Keys (globs) excluded from all checks. `x18n.config.ts` also takes regular expressions. */
|
|
921
946
|
readonly ignoreKeys?: readonly string[] | undefined;
|
|
922
947
|
readonly glossary?: readonly GlossaryEntry[] | undefined;
|
|
923
948
|
/** History providers for stale translations. `false` turns every provider off. Options of a provider sit under its key, such as `git: { maxDepth, since }` (or `git: false`). */
|
|
@@ -940,9 +965,13 @@ interface HistorySettings extends HistoryOptions {
|
|
|
940
965
|
}
|
|
941
966
|
type PresetOption = Preset | PluginSpecifier | false | undefined;
|
|
942
967
|
type PluginOption = Plugin | Preset | PluginSpecifier | false | undefined;
|
|
968
|
+
/** A key glob (`*` inside a segment, `**` across segments), or a regular expression tested against the whole key. */
|
|
969
|
+
type KeyPattern = string | RegExp;
|
|
943
970
|
/** The config accepted by `defineConfig`: the serializable part plus plugin objects. */
|
|
944
|
-
interface UserConfig extends Omit<SerializableConfig, "presets" | "plugins" | "checks" | "history"> {
|
|
971
|
+
interface UserConfig extends Omit<SerializableConfig, "presets" | "plugins" | "checks" | "history" | "ignoreKeys"> {
|
|
945
972
|
readonly presets?: "auto" | readonly PresetOption[] | undefined;
|
|
973
|
+
/** Keys excluded from all checks: globs, or regular expressions tested against the whole key (`ns:a.b` when namespaced). */
|
|
974
|
+
readonly ignoreKeys?: readonly KeyPattern[] | undefined;
|
|
946
975
|
/** History providers for stale translations: `false` turns them off; provider options such as `git` sit under their key; `providers` adds providers asked first. */
|
|
947
976
|
readonly history?: boolean | (HistoryOptions & {
|
|
948
977
|
readonly providers?: readonly HistoryProvider[] | undefined;
|
|
@@ -998,7 +1027,7 @@ interface ResolvedConfig {
|
|
|
998
1027
|
readonly [checkId: string]: CheckSetting;
|
|
999
1028
|
};
|
|
1000
1029
|
readonly strict: boolean;
|
|
1001
|
-
readonly ignoreKeys: readonly
|
|
1030
|
+
readonly ignoreKeys: readonly KeyPattern[];
|
|
1002
1031
|
readonly glossary: readonly GlossaryEntry[];
|
|
1003
1032
|
readonly history: false | HistorySettings;
|
|
1004
1033
|
readonly review: {
|
|
@@ -1283,8 +1312,11 @@ export declare function replaceKeyPrefix(key: KeyString, from: KeyString, to: Ke
|
|
|
1283
1312
|
* `{a,b}` alternates, `[...]` is a character class.
|
|
1284
1313
|
*/
|
|
1285
1314
|
export declare function globToRegExp(glob: string, separator?: string): RegExp;
|
|
1286
|
-
/**
|
|
1287
|
-
|
|
1315
|
+
/**
|
|
1316
|
+
* Matcher over key patterns. In a glob `*` stays inside a segment and `**` spans segments; with literal keys
|
|
1317
|
+
* (`separator: false`) `*` spans the whole key. A regular expression is tested against the whole key as is.
|
|
1318
|
+
*/
|
|
1319
|
+
export declare function keyMatcher(patterns: readonly KeyPattern[], separator?: KeySeparator): (key: KeyString) => boolean;
|
|
1288
1320
|
/** Matcher over posix file paths. */
|
|
1289
1321
|
export declare function pathMatcher(globs: readonly string[]): (path: string) => boolean;
|
|
1290
1322
|
//#endregion
|
|
@@ -1914,4 +1946,4 @@ export declare function rangeContains(range: TextRange, offset: number): boolean
|
|
|
1914
1946
|
/** Fast non-cryptographic string hash (cyrb53), as hex. */
|
|
1915
1947
|
export declare function hashText(text: string): string;
|
|
1916
1948
|
//#endregion
|
|
1917
|
-
export type { Analysis, Answers, ApplyResult, ArgSource, BaselineSummary, 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, FormatEntry, GlobOptions, GlossaryEntry, HardString, HistoryConfigs, HistoryContext, HistoryOptions, HistoryProvider, HistorySettings, Host, JsonObject, JsonPrimitive, JsonValue, KeptUnusedKey, KeyContext, KeyFile, KeyId, KeyInfo, KeyQuery, KeySeparator, KeyString, KeyTarget, LazyFormat, LazyScanner, LibraryOptions, LineIndex, LinePosition, LocaleCode, LocaleCoverage, LocaleFinder, LocaleInfo, Location, Logger, ManifestReader, MemoryHost, Message, MessageChange, MessageHistory, MessageLeaf, MessageOp, MessageState, MessageTree, NamespaceFromPath, NamespaceOptions, NodeRange, OverlayHost, ParamsSource, ParseResult, Placeholder, PlaceholderComparer, PlaceholderProblem, PlaceholderSyntax, Plan, Plugin, PluginConfigs, PluginFile, PluginLoader, PluginOption, PluginRegistry, PluginSpecifier, PluralForm, Preset, PresetDetection, PresetEntry, PresetExplanation, PresetFactory, PresetLoader, PresetOption, Project, ProjectChange, ProjectExplanation, PromptContext, PromptData, PromptDraft, PromptIssue, PromptItem, PromptOptions, RegexScannerSpec, RelatedValue, ReportInput, ReportSummary, Reporter, ReporterContext, ReporterLoader, ResolveConfigOptions, ResolvedConfig, Resource, ResourceBlock, ResourceCandidate, ResourceId, ResourceNode, ResourceSpec, ResourceTransform, ResourceTransformContext, ReviewAuthor, ReviewComment, ReviewData, ReviewFile, ReviewFilter, ReviewOptions, ReviewState, ReviewStore, ReviewThread, ReviewType, ScanContext, ScanResult, Scanner, ScannerEntry, SchemaIssue, Scope, SearchMatch, SearchOptions, SerializableConfig, Setup, Severity, SourceFile, TextEdit, TextRange, ThreadComment, TreeLeaf, UnusedDeletion, UnusedKeyDeletion, UnusedQuery, Usage, UsageCandidate, UsageContext, UsageKind, UsageMatch, UsageOptions, UsageQuery, UserConfig, WireDiagnostic, WireRange, WriteOptions, X18nError };
|
|
1949
|
+
export type { Analysis, Answers, ApplyResult, ArgSource, BaselineSummary, 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, FileWrite, FinderContext, Fix, Format, FormatContext, FormatEntry, GlobOptions, GlossaryEntry, HardString, HistoryConfigs, HistoryContext, HistoryOptions, HistoryProvider, HistorySettings, Host, JsonObject, JsonPrimitive, JsonValue, KeptUnusedKey, KeyContext, KeyFile, KeyId, KeyInfo, KeyPattern, KeyQuery, KeySeparator, KeyString, KeyTarget, LazyFormat, LazyScanner, LibraryOptions, LineIndex, LinePosition, LocaleCode, LocaleCoverage, LocaleFinder, LocaleInfo, Location, Logger, ManifestReader, MemoryHost, Message, MessageChange, MessageHistory, MessageLeaf, MessageOp, MessageState, MessageTree, NamespaceFromPath, NamespaceOptions, NodeRange, OverlayHost, ParamsSource, ParseResult, Placeholder, PlaceholderComparer, PlaceholderProblem, PlaceholderSyntax, Plan, Plugin, PluginConfigs, PluginFile, PluginLoader, PluginOption, PluginRegistry, PluginSpecifier, PluralForm, Preset, PresetDetection, PresetEntry, PresetExplanation, PresetFactory, PresetLoader, PresetOption, Project, ProjectChange, ProjectExplanation, PromptContext, PromptData, PromptDraft, PromptIssue, PromptItem, PromptOptions, RegexScannerSpec, RelatedValue, ReportInput, ReportSummary, Reporter, ReporterContext, ReporterLoader, ResolveConfigOptions, ResolvedConfig, Resource, ResourceBlock, ResourceCandidate, ResourceId, ResourceNode, ResourceSpec, ResourceTransform, ResourceTransformContext, ReviewAuthor, ReviewComment, ReviewData, ReviewEdit, ReviewEditContext, ReviewFile, ReviewFilter, ReviewOptions, ReviewState, ReviewStore, ReviewThread, ReviewType, ScanContext, ScanResult, Scanner, ScannerEntry, SchemaIssue, Scope, SearchMatch, SearchOptions, SerializableConfig, Setup, Severity, SourceFile, TextEdit, TextRange, ThreadComment, TreeLeaf, UnusedDeletion, UnusedKeyDeletion, UnusedQuery, Usage, UsageCandidate, UsageContext, UsageKind, UsageMatch, UsageOptions, UsageQuery, UserConfig, WireDiagnostic, WireRange, WriteOptions, X18nError };
|
package/dist/index.mjs
CHANGED
|
@@ -379,10 +379,13 @@ function globToRegExp(glob, separator = "/") {
|
|
|
379
379
|
}
|
|
380
380
|
return new RegExp(`^${out}$`);
|
|
381
381
|
}
|
|
382
|
-
/**
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Matcher over key patterns. In a glob `*` stays inside a segment and `**` spans segments; with literal keys
|
|
384
|
+
* (`separator: false`) `*` spans the whole key. A regular expression is tested against the whole key as is.
|
|
385
|
+
*/
|
|
386
|
+
function keyMatcher(patterns, separator = ".") {
|
|
387
|
+
if (patterns.length === 0) return () => false;
|
|
388
|
+
const regexes = patterns.map((pattern) => pattern instanceof RegExp ? new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")) : globToRegExp(pattern, separator === false ? "\0" : separator));
|
|
386
389
|
return (key) => regexes.some((re) => re.test(key));
|
|
387
390
|
}
|
|
388
391
|
/** Matcher over posix file paths. */
|
|
@@ -621,11 +624,15 @@ function deleteIn(tree, path) {
|
|
|
621
624
|
delete parents[i - 1][path[i - 1]];
|
|
622
625
|
}
|
|
623
626
|
}
|
|
627
|
+
/** Applies ops to the tree itself. Parent objects left empty are removed. */
|
|
628
|
+
function applyMessageOpsInPlace(tree, ops) {
|
|
629
|
+
for (const op of ops) if (op.type === "set") setIn(tree, op.path, op.value);
|
|
630
|
+
else deleteIn(tree, op.path);
|
|
631
|
+
}
|
|
624
632
|
/** Applies ops to a copy of the tree. Parent objects left empty are removed. */
|
|
625
633
|
function applyMessageOps(tree, ops) {
|
|
626
634
|
const out = cloneTree(tree);
|
|
627
|
-
|
|
628
|
-
else deleteIn(out, op.path);
|
|
635
|
+
applyMessageOpsInPlace(out, ops);
|
|
629
636
|
return out;
|
|
630
637
|
}
|
|
631
638
|
/** Sorts keys at every level. */
|
|
@@ -1736,6 +1743,10 @@ function nodeAt(tree, path) {
|
|
|
1736
1743
|
}
|
|
1737
1744
|
return node;
|
|
1738
1745
|
}
|
|
1746
|
+
function leafAt(tree, path) {
|
|
1747
|
+
const node = nodeAt(tree, path);
|
|
1748
|
+
return isMessageLeaf(node) ? node : void 0;
|
|
1749
|
+
}
|
|
1739
1750
|
/** True for flat files (`{ "a.b": "x" }`), false for nested ones, undefined when it cannot tell. */
|
|
1740
1751
|
function isFlatTree(tree, separator) {
|
|
1741
1752
|
const entries = Object.entries(tree);
|
|
@@ -1795,6 +1806,7 @@ var PlanBuilder = class {
|
|
|
1795
1806
|
config;
|
|
1796
1807
|
/** Source values set by this plan, for the source records of targets set after them. */
|
|
1797
1808
|
plannedSource = /* @__PURE__ */ new Map();
|
|
1809
|
+
reviewEdits = [];
|
|
1798
1810
|
draft;
|
|
1799
1811
|
constructor(options, state, answers) {
|
|
1800
1812
|
this.options = options;
|
|
@@ -1822,7 +1834,7 @@ var PlanBuilder = class {
|
|
|
1822
1834
|
if (!entry) {
|
|
1823
1835
|
entry = {
|
|
1824
1836
|
target,
|
|
1825
|
-
tree: target.state?.rawTree ?? {},
|
|
1837
|
+
tree: cloneTree(target.state?.rawTree ?? {}),
|
|
1826
1838
|
ops: [],
|
|
1827
1839
|
keyRenames: [],
|
|
1828
1840
|
formatOnly: false
|
|
@@ -1866,7 +1878,8 @@ var PlanBuilder = class {
|
|
|
1866
1878
|
const stored = this.options.toStored(entry.target, relative);
|
|
1867
1879
|
const separator = this.separatorOf(entry.target);
|
|
1868
1880
|
if (separator === false) return stored;
|
|
1869
|
-
|
|
1881
|
+
if (stored.length === relative.length) entry.flat ??= isFlatTree(entry.tree, separator);
|
|
1882
|
+
return stored.length === relative.length && (entry.flat ?? this.config.keyStyle === "flat") ? [stored.join(separator)] : stored;
|
|
1870
1883
|
}
|
|
1871
1884
|
createTarget(key, locale) {
|
|
1872
1885
|
const { resources, sourceLocale, namespaced } = this.state;
|
|
@@ -1979,18 +1992,30 @@ var PlanBuilder = class {
|
|
|
1979
1992
|
/** ---- Message ops ---- */
|
|
1980
1993
|
pushOp(entry, op) {
|
|
1981
1994
|
entry.ops.push(op);
|
|
1982
|
-
|
|
1995
|
+
this.applyOps(entry, [op]);
|
|
1996
|
+
}
|
|
1997
|
+
/** Applies ops to the entry's tree in place; drops `flat` when a top-level object appears or goes, or a top-level key goes. */
|
|
1998
|
+
applyOps(entry, ops) {
|
|
1999
|
+
for (const op of ops) {
|
|
2000
|
+
const top = op.path[0];
|
|
2001
|
+
const before = entry.tree[top];
|
|
2002
|
+
applyMessageOpsInPlace(entry.tree, [op]);
|
|
2003
|
+
const after = entry.tree[top];
|
|
2004
|
+
if ((before !== void 0 && !isMessageLeaf(before)) !== (after !== void 0 && !isMessageLeaf(after)) || before !== void 0 && after === void 0) entry.flat = void 0;
|
|
2005
|
+
}
|
|
1983
2006
|
}
|
|
1984
2007
|
setValue(key, locale, value) {
|
|
1985
2008
|
this.touched.add(key);
|
|
1986
2009
|
const existing = this.options.message(key, locale);
|
|
1987
2010
|
if (existing) {
|
|
1988
|
-
this.
|
|
2011
|
+
const entry = this.workFor(this.targetOf(this.writable(existing.resource)));
|
|
2012
|
+
const before = leafAt(entry.tree, existing.storedPath);
|
|
2013
|
+
this.pushOp(entry, {
|
|
1989
2014
|
type: "set",
|
|
1990
2015
|
path: existing.storedPath,
|
|
1991
2016
|
value
|
|
1992
2017
|
});
|
|
1993
|
-
return;
|
|
2018
|
+
return before;
|
|
1994
2019
|
}
|
|
1995
2020
|
const target = this.pickTarget(key, locale);
|
|
1996
2021
|
if (target.state) this.writable(target.state.resource.id);
|
|
@@ -2003,6 +2028,7 @@ var PlanBuilder = class {
|
|
|
2003
2028
|
path: stored,
|
|
2004
2029
|
value
|
|
2005
2030
|
});
|
|
2031
|
+
return isMessageLeaf(occupied) ? occupied : void 0;
|
|
2006
2032
|
}
|
|
2007
2033
|
deleteMessage(m) {
|
|
2008
2034
|
const s = this.writable(m.resource);
|
|
@@ -2046,7 +2072,7 @@ var PlanBuilder = class {
|
|
|
2046
2072
|
text: last
|
|
2047
2073
|
});
|
|
2048
2074
|
const leaves = [...walkLeaves(node, stored)];
|
|
2049
|
-
|
|
2075
|
+
this.applyOps(entry, [...leaves.map((leaf) => ({
|
|
2050
2076
|
type: "delete",
|
|
2051
2077
|
path: leaf.path
|
|
2052
2078
|
})), ...leaves.map((leaf) => ({
|
|
@@ -2061,45 +2087,54 @@ var PlanBuilder = class {
|
|
|
2061
2087
|
moveKey(from, to, renamed = /* @__PURE__ */ new Set()) {
|
|
2062
2088
|
this.touched.add(from);
|
|
2063
2089
|
this.touched.add(to);
|
|
2064
|
-
for (const
|
|
2065
|
-
const m = list[0];
|
|
2090
|
+
for (const list of this.options.messages(from)?.values() ?? []) for (const m of [...list].reverse()) {
|
|
2066
2091
|
if (renamed.has(m.resource)) continue;
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
this.deleteMessage(m);
|
|
2072
|
-
this.setValue(to, locale, m.value);
|
|
2073
|
-
continue;
|
|
2074
|
-
}
|
|
2075
|
-
const entry = this.workFor(this.targetOf(s));
|
|
2076
|
-
const separator = this.separatorOf(s.resource);
|
|
2077
|
-
const flat = separator !== false && m.storedPath.length === 1 && m.storedPath[0].includes(separator);
|
|
2078
|
-
const next = flat ? [relative.join(separator)] : this.options.toStored(s.resource, relative);
|
|
2079
|
-
const sameParent = next.length === m.storedPath.length && next.slice(0, -1).every((segment, i) => segment === m.storedPath[i]);
|
|
2080
|
-
const last = next.at(-1);
|
|
2081
|
-
if (sameParent && m.range && (flat ? safeFlat : safeSegment).test(last)) {
|
|
2082
|
-
entry.keyRenames.push({
|
|
2083
|
-
range: m.range.key,
|
|
2084
|
-
text: last
|
|
2085
|
-
});
|
|
2086
|
-
entry.tree = applyMessageOps(entry.tree, [{
|
|
2087
|
-
type: "delete",
|
|
2088
|
-
path: m.storedPath
|
|
2089
|
-
}, {
|
|
2090
|
-
type: "set",
|
|
2091
|
-
path: next,
|
|
2092
|
-
value: m.value
|
|
2093
|
-
}]);
|
|
2094
|
-
continue;
|
|
2092
|
+
const replaced = this.moveMessage(m, to);
|
|
2093
|
+
if (replaced !== void 0 && replaced !== m.value) {
|
|
2094
|
+
const path = this.stateById.get(m.resource)?.resource.path ?? m.resource;
|
|
2095
|
+
this.warnings.push(`Copies of "${from}" in ${m.locale} have different values; "${to}" keeps the one from ${path}.`);
|
|
2095
2096
|
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
moveMessage(m, to) {
|
|
2100
|
+
const s = this.writable(m.resource);
|
|
2101
|
+
const relative = this.relativePath(to, s.resource);
|
|
2102
|
+
const own = this.options.resolveKey(to, m.locale);
|
|
2103
|
+
if (!relative || own && own.path !== s.resource.path) {
|
|
2096
2104
|
this.deleteMessage(m);
|
|
2097
|
-
this.
|
|
2105
|
+
return this.setValue(to, m.locale, m.value);
|
|
2106
|
+
}
|
|
2107
|
+
const entry = this.workFor(this.targetOf(s));
|
|
2108
|
+
const separator = this.separatorOf(s.resource);
|
|
2109
|
+
const flat = separator !== false && m.storedPath.length === 1 && m.storedPath[0].includes(separator);
|
|
2110
|
+
const next = flat ? [relative.join(separator)] : this.options.toStored(s.resource, relative);
|
|
2111
|
+
const sameParent = next.length === m.storedPath.length && next.slice(0, -1).every((segment, i) => segment === m.storedPath[i]);
|
|
2112
|
+
const last = next.at(-1);
|
|
2113
|
+
if (sameParent && m.range && (flat ? safeFlat : safeSegment).test(last)) {
|
|
2114
|
+
const replaced = leafAt(entry.tree, next);
|
|
2115
|
+
entry.keyRenames.push({
|
|
2116
|
+
range: m.range.key,
|
|
2117
|
+
text: last
|
|
2118
|
+
});
|
|
2119
|
+
this.applyOps(entry, [{
|
|
2120
|
+
type: "delete",
|
|
2121
|
+
path: m.storedPath
|
|
2122
|
+
}, {
|
|
2098
2123
|
type: "set",
|
|
2099
|
-
path:
|
|
2124
|
+
path: next,
|
|
2100
2125
|
value: m.value
|
|
2101
|
-
});
|
|
2126
|
+
}]);
|
|
2127
|
+
return replaced;
|
|
2102
2128
|
}
|
|
2129
|
+
this.deleteMessage(m);
|
|
2130
|
+
const path = this.storedPathFor(entry, relative);
|
|
2131
|
+
const replaced = leafAt(entry.tree, path);
|
|
2132
|
+
this.pushOp(entry, {
|
|
2133
|
+
type: "set",
|
|
2134
|
+
path,
|
|
2135
|
+
value: m.value
|
|
2136
|
+
});
|
|
2137
|
+
return replaced;
|
|
2103
2138
|
}
|
|
2104
2139
|
/** The raw text a usage needs to refer to `next`, given its scope. */
|
|
2105
2140
|
rawFor(usage, next) {
|
|
@@ -2239,6 +2274,17 @@ var PlanBuilder = class {
|
|
|
2239
2274
|
...op.review === "suggestion" && { suggestion: op.suggestion },
|
|
2240
2275
|
...op.review === "approve" && current !== void 0 && { value: valueText(current) }
|
|
2241
2276
|
});
|
|
2277
|
+
this.reviewEdited(draft, op.review, op.id);
|
|
2278
|
+
}
|
|
2279
|
+
reviewEdited(draft, type, id) {
|
|
2280
|
+
const { key, locale, comment } = findReview(draft.data, id);
|
|
2281
|
+
this.reviewEdits.push({
|
|
2282
|
+
type,
|
|
2283
|
+
key,
|
|
2284
|
+
locale,
|
|
2285
|
+
comment,
|
|
2286
|
+
comments: draft.data.threads[key][locale]
|
|
2287
|
+
});
|
|
2242
2288
|
}
|
|
2243
2289
|
applySuggestion(id) {
|
|
2244
2290
|
const draft = this.requireReviews();
|
|
@@ -2249,6 +2295,7 @@ var PlanBuilder = class {
|
|
|
2249
2295
|
this.setValue(key, locale, comment.suggestion);
|
|
2250
2296
|
this.recordSource(key, locale, true);
|
|
2251
2297
|
draft.resolve(id);
|
|
2298
|
+
this.reviewEdited(draft, "apply-suggestion", id);
|
|
2252
2299
|
}
|
|
2253
2300
|
resolveReview(id) {
|
|
2254
2301
|
const draft = this.requireReviews();
|
|
@@ -2256,6 +2303,7 @@ var PlanBuilder = class {
|
|
|
2256
2303
|
if (!found) throw errors.X18N5004({ id });
|
|
2257
2304
|
this.touched.add(found.key);
|
|
2258
2305
|
draft.resolve(id);
|
|
2306
|
+
this.reviewEdited(draft, "resolve-review", id);
|
|
2259
2307
|
}
|
|
2260
2308
|
markFresh(op) {
|
|
2261
2309
|
this.requireReviews();
|
|
@@ -2415,6 +2463,7 @@ var PlanBuilder = class {
|
|
|
2415
2463
|
});
|
|
2416
2464
|
texts.set(path, text);
|
|
2417
2465
|
}
|
|
2466
|
+
this.reviewWrites(changes, texts);
|
|
2418
2467
|
const review = this.reviewChange();
|
|
2419
2468
|
if (review) {
|
|
2420
2469
|
changes.push(review);
|
|
@@ -2425,6 +2474,41 @@ var PlanBuilder = class {
|
|
|
2425
2474
|
texts
|
|
2426
2475
|
};
|
|
2427
2476
|
}
|
|
2477
|
+
/** Runs the `reviewEdit` hooks and puts their writes into the changes. */
|
|
2478
|
+
reviewWrites(changes, texts) {
|
|
2479
|
+
const hooks = this.options.registry.plugins.flatMap((plugin) => plugin.reviewEdit ? [plugin.reviewEdit] : []);
|
|
2480
|
+
if (hooks.length === 0 || this.reviewEdits.length === 0) return;
|
|
2481
|
+
const written = /* @__PURE__ */ new Map();
|
|
2482
|
+
const text = (path) => {
|
|
2483
|
+
if (written.has(path)) return written.get(path);
|
|
2484
|
+
const change = changes.find((c) => c.path === path);
|
|
2485
|
+
if (!change) return this.options.text(path);
|
|
2486
|
+
return change.type === "create" ? change.content : change.type === "edit" ? applyTextEdits(texts.get(path) ?? "", change.edits) : void 0;
|
|
2487
|
+
};
|
|
2488
|
+
const ctx = {
|
|
2489
|
+
message: this.options.message,
|
|
2490
|
+
resource: (id) => this.stateById.get(id)?.resource,
|
|
2491
|
+
text
|
|
2492
|
+
};
|
|
2493
|
+
for (const edit of this.reviewEdits) for (const hook of hooks) for (const write of hook(edit, ctx) ?? []) written.set(write.path, write.content);
|
|
2494
|
+
for (const [path, content] of written) {
|
|
2495
|
+
const index = changes.findIndex((c) => c.path === path);
|
|
2496
|
+
const before = index === -1 ? this.options.text(path) : texts.get(path);
|
|
2497
|
+
if (index === -1 && before === content) continue;
|
|
2498
|
+
const change = before === void 0 ? {
|
|
2499
|
+
type: "create",
|
|
2500
|
+
path,
|
|
2501
|
+
content
|
|
2502
|
+
} : {
|
|
2503
|
+
type: "edit",
|
|
2504
|
+
path,
|
|
2505
|
+
edits: diffText(before, content)
|
|
2506
|
+
};
|
|
2507
|
+
if (index === -1) changes.push(change);
|
|
2508
|
+
else changes[index] = change;
|
|
2509
|
+
texts.set(path, before);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2428
2512
|
reviewChange() {
|
|
2429
2513
|
const reviews = this.state.reviews;
|
|
2430
2514
|
if (!reviews || !this.draft?.changed) return void 0;
|
|
@@ -2443,6 +2527,7 @@ var PlanBuilder = class {
|
|
|
2443
2527
|
};
|
|
2444
2528
|
function createPlanner(options) {
|
|
2445
2529
|
const texts = /* @__PURE__ */ new Map();
|
|
2530
|
+
let textsRevision;
|
|
2446
2531
|
/** Gives new reviews an id and a time, so planning again (after decisions) writes the same comments. */
|
|
2447
2532
|
function withReviewIds(ops, data) {
|
|
2448
2533
|
const used = /* @__PURE__ */ new Set();
|
|
@@ -2506,6 +2591,10 @@ function createPlanner(options) {
|
|
|
2506
2591
|
return {
|
|
2507
2592
|
plan(ops, answers = {}) {
|
|
2508
2593
|
const result = planWith(ops, answers);
|
|
2594
|
+
if (result.plan.revision !== textsRevision) {
|
|
2595
|
+
texts.clear();
|
|
2596
|
+
textsRevision = result.plan.revision;
|
|
2597
|
+
}
|
|
2509
2598
|
texts.set(result.plan.id, result.texts);
|
|
2510
2599
|
return result.plan;
|
|
2511
2600
|
},
|
|
@@ -3832,7 +3921,12 @@ function defaultDraft() {
|
|
|
3832
3921
|
};
|
|
3833
3922
|
}
|
|
3834
3923
|
function serializablePart(config) {
|
|
3835
|
-
const { presets, plugins, pluginConfigs, history, ...
|
|
3924
|
+
const { presets, plugins, pluginConfigs, history, ignoreKeys: patterns, ...fields } = config;
|
|
3925
|
+
const ignoreKeys = patterns?.filter((pattern) => !(pattern instanceof RegExp));
|
|
3926
|
+
const rest = ignoreKeys === void 0 ? fields : {
|
|
3927
|
+
...fields,
|
|
3928
|
+
ignoreKeys
|
|
3929
|
+
};
|
|
3836
3930
|
if (typeof history !== "object") return {
|
|
3837
3931
|
...rest,
|
|
3838
3932
|
history
|
package/package.json
CHANGED