@x18n/core 0.0.0 → 0.0.3
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 +20 -5
- package/dist/index.mjs +121 -22
- package/package.json +1 -1
- package/schema/config.json +8 -0
- package/schema/plugin.json +8 -0
package/dist/index.d.mts
CHANGED
|
@@ -426,6 +426,7 @@ interface CheckContext {
|
|
|
426
426
|
/** Usages whose key is not defined in the source locale. */
|
|
427
427
|
readonly undefinedUsages: () => readonly Usage[];
|
|
428
428
|
readonly placeholders: (value: string) => readonly Placeholder[];
|
|
429
|
+
readonly placeholderSyntaxes: readonly PlaceholderSyntax[];
|
|
429
430
|
/** Errors reported by the `validate` of every placeholder syntax. */
|
|
430
431
|
readonly syntaxErrors: (value: string) => readonly string[];
|
|
431
432
|
readonly localeInfo: (code: LocaleCode) => LocaleInfo;
|
|
@@ -475,6 +476,8 @@ interface PlaceholderSyntax {
|
|
|
475
476
|
readonly example?: string | undefined;
|
|
476
477
|
/** The placeholder name is another key (vue-i18n `@:key`, i18next `$t(key)`), which then counts as used. */
|
|
477
478
|
readonly reference?: boolean | undefined;
|
|
479
|
+
/** Names the library always passes, which a translation may leave out or add (vue-i18n `n` and `count`). */
|
|
480
|
+
readonly implicit?: readonly string[] | undefined;
|
|
478
481
|
}
|
|
479
482
|
interface KeyContext {
|
|
480
483
|
readonly path: string;
|
|
@@ -645,7 +648,10 @@ interface NamespaceOptions {
|
|
|
645
648
|
readonly separator?: string | undefined;
|
|
646
649
|
/** Namespace of keys written without one. */
|
|
647
650
|
readonly default?: string | undefined;
|
|
651
|
+
/** How `{namespaces}` folders name a namespace: `dot` makes `pages/home` into `pages.home`, `slash` keeps `pages/home`, `camel` makes `todos-page` into `todosPage`. Default `dot`. */
|
|
652
|
+
readonly fromPath?: "dot" | "slash" | "camel" | undefined;
|
|
648
653
|
}
|
|
654
|
+
type NamespaceFromPath = NonNullable<NamespaceOptions["fromPath"]>;
|
|
649
655
|
type PluginSpecifier = string | readonly [string, JsonObject];
|
|
650
656
|
/** The part of the config that YAML and JSON files can hold. Validated by `ConfigSchema`. */
|
|
651
657
|
interface SerializableConfig {
|
|
@@ -713,6 +719,7 @@ interface ResolvedConfig {
|
|
|
713
719
|
readonly namespace: {
|
|
714
720
|
readonly separator: string;
|
|
715
721
|
readonly default: string | undefined;
|
|
722
|
+
readonly fromPath: NamespaceFromPath;
|
|
716
723
|
};
|
|
717
724
|
readonly usage: {
|
|
718
725
|
readonly include: readonly string[];
|
|
@@ -1093,6 +1100,8 @@ interface Project {
|
|
|
1093
1100
|
readonly keys: (query?: KeyQuery) => KeyString[];
|
|
1094
1101
|
readonly get: (key: KeyString) => KeyInfo | undefined;
|
|
1095
1102
|
readonly message: (key: KeyString, locale: LocaleCode) => Message | undefined;
|
|
1103
|
+
/** The message a usage of `key` shows: the key's own, or one of its derived keys (`item` → `item_other`). */
|
|
1104
|
+
readonly lookup: (key: KeyString, locale: LocaleCode) => Message | undefined;
|
|
1096
1105
|
/** Parses user input such as `common:save` or `save` into a key string. */
|
|
1097
1106
|
readonly parseKey: (input: string) => KeyString;
|
|
1098
1107
|
readonly usages: (query?: UsageQuery) => UsageMatch[];
|
|
@@ -1137,6 +1146,15 @@ interface PlaceholderProblem {
|
|
|
1137
1146
|
/** Parses the batch shape `{ "<locale>": { "<key>": "<value>" } }` (as in `x18n apply`) into set ops. */
|
|
1138
1147
|
export declare function parseMessageBatch(project: Project, data: JsonValue): EditOp[];
|
|
1139
1148
|
/** Set ops whose value drops or adds placeholders compared to the source value. */
|
|
1149
|
+
type PlaceholderComparer = (source: string, target: string) => {
|
|
1150
|
+
readonly missing: string[];
|
|
1151
|
+
readonly extra: string[];
|
|
1152
|
+
};
|
|
1153
|
+
/**
|
|
1154
|
+
* Compares the placeholders of a target value with its source. Key references (`@:key`) are not
|
|
1155
|
+
* parameters, and implicit names (vue-i18n `{n}` in plural messages) may be left out. Build it once per run.
|
|
1156
|
+
*/
|
|
1157
|
+
export declare function createPlaceholderComparer(syntaxes: readonly PlaceholderSyntax[], find?: (value: string) => readonly Placeholder[]): PlaceholderComparer;
|
|
1140
1158
|
export declare function placeholderProblems(project: Project, ops: readonly EditOp[]): PlaceholderProblem[];
|
|
1141
1159
|
//#endregion
|
|
1142
1160
|
//#region src/project/project.d.ts
|
|
@@ -1166,10 +1184,7 @@ interface PluginFile {
|
|
|
1166
1184
|
readonly sourceLocale?: string | undefined;
|
|
1167
1185
|
readonly locales?: readonly string[] | undefined;
|
|
1168
1186
|
readonly resources?: readonly ResourceSpec[] | undefined;
|
|
1169
|
-
readonly namespace?:
|
|
1170
|
-
readonly separator?: string | undefined;
|
|
1171
|
-
readonly default?: string | undefined;
|
|
1172
|
-
} | undefined;
|
|
1187
|
+
readonly namespace?: NamespaceOptions | undefined;
|
|
1173
1188
|
readonly keyStyle?: "auto" | "nested" | "flat" | undefined;
|
|
1174
1189
|
} | undefined;
|
|
1175
1190
|
readonly scanners?: {
|
|
@@ -1237,4 +1252,4 @@ export declare function rangeContains(range: TextRange, offset: number): boolean
|
|
|
1237
1252
|
/** Fast non-cryptographic string hash (cyrb53), as hex. */
|
|
1238
1253
|
export declare function hashText(text: string): string;
|
|
1239
1254
|
//#endregion
|
|
1240
|
-
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, Host, JsonObject, JsonPrimitive, JsonValue, KeyContext, KeyId, KeyInfo, KeyQuery, KeyString, KeyTarget, LineIndex, LinePosition, LocaleCode, LocaleCoverage, LocaleFinder, LocaleInfo, Location, Logger, ManifestReader, MemoryHost, Message, MessageLeaf, MessageOp, MessageTree, NamespaceOptions, NodeRange, OverlayHost, ParseResult, Placeholder, 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 };
|
|
1255
|
+
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, Host, JsonObject, JsonPrimitive, JsonValue, KeyContext, KeyId, KeyInfo, KeyQuery, KeyString, KeyTarget, LineIndex, LinePosition, LocaleCode, LocaleCoverage, LocaleFinder, LocaleInfo, Location, Logger, ManifestReader, MemoryHost, Message, 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 };
|
package/dist/index.mjs
CHANGED
|
@@ -690,7 +690,12 @@ const glossaryEntry = v.strictObject({
|
|
|
690
690
|
const checkSetting = v.union([level, v.tuple([level, jsonObject])]);
|
|
691
691
|
const namespaceOptions = v.strictObject({
|
|
692
692
|
separator: v.optional(v.string()),
|
|
693
|
-
default: v.optional(v.string())
|
|
693
|
+
default: v.optional(v.string()),
|
|
694
|
+
fromPath: v.optional(v.picklist([
|
|
695
|
+
"dot",
|
|
696
|
+
"slash",
|
|
697
|
+
"camel"
|
|
698
|
+
]))
|
|
694
699
|
});
|
|
695
700
|
/** Validates YAML and JSON configs, and the serializable part of TS configs. */
|
|
696
701
|
const ConfigSchema = v.strictObject({
|
|
@@ -808,17 +813,38 @@ function parseMessageBatch(project, data) {
|
|
|
808
813
|
}
|
|
809
814
|
return ops;
|
|
810
815
|
}
|
|
811
|
-
/**
|
|
816
|
+
/**
|
|
817
|
+
* Compares the placeholders of a target value with its source. Key references (`@:key`) are not
|
|
818
|
+
* parameters, and implicit names (vue-i18n `{n}` in plural messages) may be left out. Build it once per run.
|
|
819
|
+
*/
|
|
820
|
+
function createPlaceholderComparer(syntaxes, find = (value) => syntaxes.flatMap((s) => s.find(value))) {
|
|
821
|
+
const references = new Set(syntaxes.filter((s) => s.reference).map((s) => s.id));
|
|
822
|
+
const implicit = new Set(syntaxes.flatMap((s) => s.implicit ?? []));
|
|
823
|
+
const seen = /* @__PURE__ */ new Map();
|
|
824
|
+
const names = (text) => {
|
|
825
|
+
let found = seen.get(text);
|
|
826
|
+
if (!found) {
|
|
827
|
+
found = new Set(find(text).filter((p) => !references.has(p.syntax) && !implicit.has(p.name)).map((p) => p.name));
|
|
828
|
+
seen.set(text, found);
|
|
829
|
+
}
|
|
830
|
+
return found;
|
|
831
|
+
};
|
|
832
|
+
return (source, target) => {
|
|
833
|
+
const expected = names(source);
|
|
834
|
+
const actual = names(target);
|
|
835
|
+
return {
|
|
836
|
+
missing: [...expected].filter((name) => !actual.has(name)).sort(),
|
|
837
|
+
extra: [...actual].filter((name) => !expected.has(name)).sort()
|
|
838
|
+
};
|
|
839
|
+
};
|
|
840
|
+
}
|
|
812
841
|
function placeholderProblems(project, ops) {
|
|
813
|
-
const
|
|
842
|
+
const compare = createPlaceholderComparer(project.placeholderSyntaxes);
|
|
814
843
|
return ops.flatMap((op) => {
|
|
815
844
|
if (op.type !== "set" || op.locale === project.sourceLocale || typeof op.value !== "string") return [];
|
|
816
845
|
const source = project.message(op.key, project.sourceLocale)?.value;
|
|
817
846
|
if (typeof source !== "string") return [];
|
|
818
|
-
const
|
|
819
|
-
const actual = names(op.value);
|
|
820
|
-
const missing = expected.filter((n) => !actual.includes(n));
|
|
821
|
-
const extra = actual.filter((n) => !expected.includes(n));
|
|
847
|
+
const { missing, extra } = compare(source, op.value);
|
|
822
848
|
return missing.length || extra.length ? [{
|
|
823
849
|
key: op.key,
|
|
824
850
|
locale: op.locale,
|
|
@@ -991,6 +1017,7 @@ function createAnalyzer(options) {
|
|
|
991
1017
|
unused: () => unused,
|
|
992
1018
|
undefinedUsages: () => undefinedUsages,
|
|
993
1019
|
placeholders,
|
|
1020
|
+
placeholderSyntaxes: registry.placeholders,
|
|
994
1021
|
syntaxErrors: (value) => registry.placeholders.flatMap((syntax) => syntax.validate?.(value) ?? []),
|
|
995
1022
|
localeInfo: options.localeInfo,
|
|
996
1023
|
text: options.text,
|
|
@@ -1151,9 +1178,12 @@ var PlanBuilder = class {
|
|
|
1151
1178
|
createTarget(key, locale) {
|
|
1152
1179
|
const { resources, sourceLocale, namespaced } = this.state;
|
|
1153
1180
|
const fits = (s) => this.relativePath(key, s.resource) !== void 0;
|
|
1154
|
-
const
|
|
1181
|
+
const source = this.options.message(key, sourceLocale);
|
|
1182
|
+
const sourceState = source && this.stateById.get(source.resource);
|
|
1183
|
+
const namespace = namespaced ? this.keyId(key).namespace ?? this.config.namespace.default : sourceState?.resource.namespace;
|
|
1184
|
+
const exists = (path) => resources.some((r) => r.resource.path === path);
|
|
1155
1185
|
const resolved = this.options.resolveNew(locale, namespace);
|
|
1156
|
-
if (resolved) {
|
|
1186
|
+
if (resolved && !exists(resolved)) {
|
|
1157
1187
|
const format = this.options.registry.formatFor(resolved);
|
|
1158
1188
|
if (format?.serialize) {
|
|
1159
1189
|
const target = {
|
|
@@ -1168,15 +1198,17 @@ var PlanBuilder = class {
|
|
|
1168
1198
|
return target;
|
|
1169
1199
|
}
|
|
1170
1200
|
}
|
|
1171
|
-
const
|
|
1201
|
+
const sibling = (r) => r.resource.locale === locale && r.resource.namespace !== void 0 && namespace !== void 0;
|
|
1202
|
+
const model = (sourceState && fits(sourceState) ? sourceState : void 0) ?? resources.find((s) => s.resource.locale === sourceLocale && fits(s)) ?? resources.find(fits) ?? resources.find(sibling);
|
|
1172
1203
|
const format = model && this.options.registry.formatById(model.resource.format);
|
|
1173
|
-
|
|
1174
|
-
if (
|
|
1204
|
+
let path = model && mirrorPath(model.resource.path, model.resource.locale, locale);
|
|
1205
|
+
if (model && path && namespace !== void 0 && model.resource.namespace !== void 0 && model.resource.namespace !== namespace) path = mirrorPath(path, model.resource.namespace, namespace);
|
|
1206
|
+
if (!model || !format?.serialize || model.resource.block || !path || exists(path)) throw new X18nError("X18N4005", `${key} in ${locale}`);
|
|
1175
1207
|
const target = {
|
|
1176
1208
|
id: path,
|
|
1177
1209
|
path,
|
|
1178
1210
|
locale,
|
|
1179
|
-
namespace: model.resource.namespace
|
|
1211
|
+
namespace: namespace ?? model.resource.namespace,
|
|
1180
1212
|
format,
|
|
1181
1213
|
state: void 0
|
|
1182
1214
|
};
|
|
@@ -1185,7 +1217,10 @@ var PlanBuilder = class {
|
|
|
1185
1217
|
}
|
|
1186
1218
|
/** Placement: the namespace's resource, then the longest shared prefix, then the first; a tie is a decision. */
|
|
1187
1219
|
pickTarget(key, locale) {
|
|
1188
|
-
|
|
1220
|
+
let inLocale = this.state.resources.filter((s) => s.resource.locale === locale && this.relativePath(key, s.resource) !== void 0);
|
|
1221
|
+
const source = this.options.message(key, this.state.sourceLocale);
|
|
1222
|
+
const sourceNamespace = source && this.stateById.get(source.resource)?.resource.namespace;
|
|
1223
|
+
if (sourceNamespace !== void 0 && !this.state.namespaced) inLocale = inLocale.filter((s) => s.resource.namespace === sourceNamespace);
|
|
1189
1224
|
if (inLocale.length === 0) return [...this.created.values()].find((t) => t.locale === locale && this.relativePath(key, t) !== void 0) ?? this.createTarget(key, locale);
|
|
1190
1225
|
if (inLocale.length === 1) return this.targetOf(inLocale[0]);
|
|
1191
1226
|
const path = this.keyId(key).path;
|
|
@@ -1253,11 +1288,50 @@ var PlanBuilder = class {
|
|
|
1253
1288
|
});
|
|
1254
1289
|
}
|
|
1255
1290
|
/** Renames a key in every locale, in place when only the last segment changes. */
|
|
1256
|
-
|
|
1291
|
+
/**
|
|
1292
|
+
* Renames a parent object in place where only its last segment changes, so the subtree keeps its
|
|
1293
|
+
* position and layout. Returns the resources it handled.
|
|
1294
|
+
*/
|
|
1295
|
+
renameNode(from, to) {
|
|
1296
|
+
const handled = /* @__PURE__ */ new Set();
|
|
1297
|
+
for (const s of this.state.resources) {
|
|
1298
|
+
const relativeFrom = this.relativePath(from, s.resource);
|
|
1299
|
+
const relativeTo = this.relativePath(to, s.resource);
|
|
1300
|
+
if (!relativeFrom || !relativeTo || s.resource.readonly || !this.options.registry.formatById(s.resource.format)?.edit) continue;
|
|
1301
|
+
const stored = s.toStored(relativeFrom);
|
|
1302
|
+
const next = s.toStored(relativeTo);
|
|
1303
|
+
const node = nodeAt(s.rawTree, stored);
|
|
1304
|
+
const range = s.ranges.get(pathId(stored));
|
|
1305
|
+
const last = next.at(-1);
|
|
1306
|
+
const sameParent = next.length === stored.length && next.slice(0, -1).every((segment, i) => segment === stored[i]);
|
|
1307
|
+
if (node === void 0 || isMessageLeaf(node) || !range || !sameParent || !safeSegment.test(last)) continue;
|
|
1308
|
+
const entry = this.workFor(this.targetOf(s));
|
|
1309
|
+
entry.keyRenames.push({
|
|
1310
|
+
range: {
|
|
1311
|
+
start: range.key.start + s.offset,
|
|
1312
|
+
end: range.key.end + s.offset
|
|
1313
|
+
},
|
|
1314
|
+
text: last
|
|
1315
|
+
});
|
|
1316
|
+
const leaves = [...walkLeaves(node, stored)];
|
|
1317
|
+
entry.tree = applyMessageOps(entry.tree, [...leaves.map((leaf) => ({
|
|
1318
|
+
type: "delete",
|
|
1319
|
+
path: leaf.path
|
|
1320
|
+
})), ...leaves.map((leaf) => ({
|
|
1321
|
+
type: "set",
|
|
1322
|
+
path: [...next, ...leaf.path.slice(stored.length)],
|
|
1323
|
+
value: leaf.value
|
|
1324
|
+
}))]);
|
|
1325
|
+
handled.add(s.resource.id);
|
|
1326
|
+
}
|
|
1327
|
+
return handled;
|
|
1328
|
+
}
|
|
1329
|
+
moveKey(from, to, renamed = /* @__PURE__ */ new Set()) {
|
|
1257
1330
|
this.touched.add(from);
|
|
1258
1331
|
this.touched.add(to);
|
|
1259
1332
|
for (const [locale, list] of this.options.messages(from) ?? []) {
|
|
1260
1333
|
const m = list[0];
|
|
1334
|
+
if (renamed.has(m.resource)) continue;
|
|
1261
1335
|
const s = this.writable(m.resource);
|
|
1262
1336
|
const relative = this.relativePath(to, s.resource);
|
|
1263
1337
|
if (!relative) {
|
|
@@ -1295,6 +1369,10 @@ var PlanBuilder = class {
|
|
|
1295
1369
|
}
|
|
1296
1370
|
/** The raw text a usage needs to refer to `next`, given its scope. */
|
|
1297
1371
|
rawFor(usage, next) {
|
|
1372
|
+
if (usage.raw.startsWith(".") && usage.key?.endsWith(usage.raw)) {
|
|
1373
|
+
const scope = usage.key.slice(0, usage.key.length - usage.raw.length);
|
|
1374
|
+
if (next.startsWith(`${scope}.`)) return next.slice(scope.length);
|
|
1375
|
+
}
|
|
1298
1376
|
const { namespace, path } = this.keyId(next);
|
|
1299
1377
|
let rest = path.join(".");
|
|
1300
1378
|
const separator = this.config.namespace.separator;
|
|
@@ -1355,8 +1433,9 @@ var PlanBuilder = class {
|
|
|
1355
1433
|
if (keys.length === 0) throw new X18nError("X18N4006", op.from);
|
|
1356
1434
|
const pairs = keys.map((key) => [key, replaceKeyPrefix(key, op.from, op.to)]);
|
|
1357
1435
|
for (const [, to] of pairs) this.conflictCheck(to);
|
|
1436
|
+
const renamed = keys.includes(op.from) ? /* @__PURE__ */ new Set() : this.renameNode(op.from, op.to);
|
|
1358
1437
|
for (const [from, to] of pairs) {
|
|
1359
|
-
this.moveKey(from, to);
|
|
1438
|
+
this.moveKey(from, to, renamed);
|
|
1360
1439
|
if (op.usages !== false) this.rewriteUsages(from, to);
|
|
1361
1440
|
}
|
|
1362
1441
|
if (op.usages === false) return;
|
|
@@ -1452,6 +1531,11 @@ var PlanBuilder = class {
|
|
|
1452
1531
|
path: newPath,
|
|
1453
1532
|
value
|
|
1454
1533
|
});
|
|
1534
|
+
else if (value !== void 0) renameOps.push(...[...walkLeaves(value, newPath)].map((leaf) => ({
|
|
1535
|
+
type: "set",
|
|
1536
|
+
path: leaf.path,
|
|
1537
|
+
value: leaf.value
|
|
1538
|
+
})));
|
|
1455
1539
|
}
|
|
1456
1540
|
return applyTextEdits(s.content, edit(s.content, [...renameOps, ...entry.ops], ctx));
|
|
1457
1541
|
}
|
|
@@ -2185,7 +2269,7 @@ function createProject(options) {
|
|
|
2185
2269
|
function buildIndex() {
|
|
2186
2270
|
resourceIndex ??= buildResourceIndex();
|
|
2187
2271
|
const usages = [];
|
|
2188
|
-
for (const [path, state] of files) {
|
|
2272
|
+
for (const [path, state] of [...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
2189
2273
|
let list = resolved.get(state);
|
|
2190
2274
|
if (!list) {
|
|
2191
2275
|
list = state.result.usages.map((candidate) => resolver.resolve(candidate, state.result.scopes ?? [], path, "scanner"));
|
|
@@ -2501,10 +2585,23 @@ function createProject(options) {
|
|
|
2501
2585
|
nodes: (path) => idx().nodes.get(path) ?? [],
|
|
2502
2586
|
locations(key) {
|
|
2503
2587
|
const order = new Map(locales.map((locale, i) => [locale, i]));
|
|
2504
|
-
|
|
2588
|
+
const all = [...idx().nodes.values()].flat();
|
|
2589
|
+
let found = all.filter((node) => node.key === key);
|
|
2590
|
+
if (found.length === 0) {
|
|
2591
|
+
const derived = new Set(idx().derived.get(key) ?? []);
|
|
2592
|
+
found = all.filter((node) => derived.has(node.key));
|
|
2593
|
+
}
|
|
2594
|
+
return found.sort((a, b) => (order.get(a.locale) ?? 0) - (order.get(b.locale) ?? 0));
|
|
2595
|
+
},
|
|
2596
|
+
lookup(key, locale) {
|
|
2597
|
+
const exact = message(key, locale);
|
|
2598
|
+
if (exact) return exact;
|
|
2599
|
+
const derived = [...idx().derived.get(key) ?? []].sort();
|
|
2600
|
+
const other = derived.find((k) => /[._-]other$/.test(k)) ?? derived[0];
|
|
2601
|
+
return other === void 0 ? void 0 : message(other, locale);
|
|
2505
2602
|
},
|
|
2506
2603
|
keySlots: (path) => files.get(path)?.result.keySlots ?? [],
|
|
2507
|
-
hardStrings: (path) => [...files].filter(([p]) => path === void 0 || p === path).flatMap(([p, state]) => (state.result.hardStrings ?? []).map((h) => ({
|
|
2604
|
+
hardStrings: (path) => [...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).filter(([p]) => path === void 0 || p === path).flatMap(([p, state]) => (state.result.hardStrings ?? []).map((h) => ({
|
|
2508
2605
|
...h,
|
|
2509
2606
|
path: p
|
|
2510
2607
|
}))),
|
|
@@ -2562,7 +2659,8 @@ function defaultDraft() {
|
|
|
2562
2659
|
keyStyle: "auto",
|
|
2563
2660
|
namespace: {
|
|
2564
2661
|
separator: ".",
|
|
2565
|
-
default: void 0
|
|
2662
|
+
default: void 0,
|
|
2663
|
+
fromPath: "dot"
|
|
2566
2664
|
},
|
|
2567
2665
|
usage: {
|
|
2568
2666
|
include: ["**/*"],
|
|
@@ -2625,7 +2723,8 @@ function applyLayer(draft, layer, origin, origins) {
|
|
|
2625
2723
|
if (layer.namespace !== void 0) {
|
|
2626
2724
|
draft.namespace = {
|
|
2627
2725
|
separator: layer.namespace.separator ?? draft.namespace.separator,
|
|
2628
|
-
default: layer.namespace.default ?? draft.namespace.default
|
|
2726
|
+
default: layer.namespace.default ?? draft.namespace.default,
|
|
2727
|
+
fromPath: layer.namespace.fromPath ?? draft.namespace.fromPath
|
|
2629
2728
|
};
|
|
2630
2729
|
mark("namespace");
|
|
2631
2730
|
}
|
|
@@ -2821,4 +2920,4 @@ async function resolveConfig(options) {
|
|
|
2821
2920
|
};
|
|
2822
2921
|
}
|
|
2823
2922
|
//#endregion
|
|
2824
|
-
export { ConfigSchema, PluginFileSchema, X18nError, applyMessageOps, applyTextEdits, buildTree, contractVersion, createLineIndex, createMemoryHost, createOverlayHost, createProject, createRegistry, defineConfig, definePlugin, definePreset, diffText, envelope, errorCodes, extensionOf, globToRegExp, hashText, isJsonObject, isKeyUnder, isMessageLeaf, isMessageTree, isPreset, isX18nError, joinKey, keyMatcher, localeSpellingKey, parseLocaleInfo, parseMessageBatch, pathId, pathMatcher, placeholderProblems, rangeContains, renderPrompt, replaceKeyPrefix, resolveConfig, sortTree, splitKey, toWireDiagnostic, toWireRange, validate, walkLeaves };
|
|
2923
|
+
export { ConfigSchema, PluginFileSchema, X18nError, applyMessageOps, applyTextEdits, buildTree, contractVersion, createLineIndex, createMemoryHost, createOverlayHost, createPlaceholderComparer, createProject, createRegistry, defineConfig, definePlugin, definePreset, diffText, envelope, errorCodes, extensionOf, globToRegExp, hashText, isJsonObject, isKeyUnder, isMessageLeaf, isMessageTree, isPreset, isX18nError, joinKey, keyMatcher, localeSpellingKey, parseLocaleInfo, parseMessageBatch, pathId, pathMatcher, placeholderProblems, rangeContains, renderPrompt, replaceKeyPrefix, resolveConfig, sortTree, splitKey, toWireDiagnostic, toWireRange, validate, walkLeaves };
|
package/package.json
CHANGED
package/schema/config.json
CHANGED