@the-i18n-kit/cli 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1 -1
- package/dist/{cli-waEjbUM1.js → cli-BTMcWXGs.js} +3 -3
- package/dist/{cli-waEjbUM1.js.map → cli-BTMcWXGs.js.map} +1 -1
- package/dist/{define-config-e0B0VHq7.d.ts → define-config-BpdVaEVR.d.ts} +1 -1
- package/dist/{define-config-ChY3jk6K.d.ts → define-config-ZRZw5PWy.d.ts} +15 -3
- package/dist/define-config-ZRZw5PWy.d.ts.map +1 -0
- package/dist/define-config.d.ts +1 -1
- package/dist/{descriptors-10sgyqEs.js → descriptors-Bo5UM031.js} +23 -6
- package/dist/{descriptors-10sgyqEs.js.map → descriptors-Bo5UM031.js.map} +1 -1
- package/dist/{detector-DtFY4qm3.js → detector-B4z_RZde.js} +2 -2
- package/dist/{detector-DtFY4qm3.js.map → detector-B4z_RZde.js.map} +1 -1
- package/dist/{detector-BJTkyhQ0.js → detector-DTfFbwSU.js} +2 -2
- package/dist/{index-CdjJ71Ag.d.ts → index-CWFWYHf5.d.ts} +76 -50
- package/dist/index-CWFWYHf5.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/operations-CKHEmLNJ.js +8 -0
- package/dist/{operations-CNqEBD__.js → operations-Daf2Ommm.js} +183 -46
- package/dist/operations-Daf2Ommm.js.map +1 -0
- package/dist/{project-config-C3ao4Uii.js → project-config-eLR0J377.js} +6 -2
- package/dist/project-config-eLR0J377.js.map +1 -0
- package/package.json +1 -1
- package/dist/define-config-ChY3jk6K.d.ts.map +0 -1
- package/dist/index-CdjJ71Ag.d.ts.map +0 -1
- package/dist/operations-CNqEBD__.js.map +0 -1
- package/dist/operations-D7IMu_xY.js +0 -8
- package/dist/project-config-C3ao4Uii.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { i as toErrorMessage, n as FileIOError, r as ToolError } from "./errors-coI1dhw1.js";
|
|
2
2
|
import { c as findLocaleOrThrow, d as findWritableLayerOrThrow, f as localeRefInfo, h as resolveReferenceLocale, l as findLocaleSuggestion, m as resolveLocaleRef, n as TranslateProviderError, o as findLayerOrThrow, p as resolveLayersToScan, s as findLocaleImpl, u as findReferenceLocaleOrThrow } from "./providers-BbVPelvp.js";
|
|
3
|
-
import { i as log, r as validateProjectConfig, t as CONFIG_FILENAME } from "./project-config-
|
|
3
|
+
import { i as log, r as validateProjectConfig, t as CONFIG_FILENAME } from "./project-config-eLR0J377.js";
|
|
4
4
|
import { a as atomicWrite, c as hasNestedKey, d as renameNestedKey, f as setNestedValue, m as validateTranslationValue, o as getLeafKeys, r as writeLocaleFile, s as getNestedValue, u as removeNestedValue } from "./json-writer-ek8yEI5R.js";
|
|
5
|
-
import { a as detectFrameworkMatch, n as clearConfigCache, o as formatForFile, s as getFormat, t as detectI18nConfig } from "./detector-
|
|
5
|
+
import { a as detectFrameworkMatch, n as clearConfigCache, o as formatForFile, s as getFormat, t as detectI18nConfig } from "./detector-B4z_RZde.js";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
8
8
|
import { mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
@@ -2169,13 +2169,86 @@ async function collectEmptyTranslations(config, opts) {
|
|
|
2169
2169
|
};
|
|
2170
2170
|
}
|
|
2171
2171
|
/**
|
|
2172
|
-
*
|
|
2172
|
+
* Everything a comparison should ignore when the question is whether a
|
|
2173
|
+
* translation for some text already exists: case, accents, punctuation and how
|
|
2174
|
+
* much whitespace sits between the words. "Save changes!" and "save changes"
|
|
2175
|
+
* are the same phrase to whoever is deciding whether to reuse the key.
|
|
2176
|
+
*/
|
|
2177
|
+
function normalizeForMatch(text) {
|
|
2178
|
+
return text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
2179
|
+
}
|
|
2180
|
+
/**
|
|
2181
|
+
* How much of two normalized strings is the same words (Sørensen–Dice over
|
|
2182
|
+
* tokens). Word order and the words neither side shares are what it ignores,
|
|
2183
|
+
* which is what "Save your changes" and "Changes saved" need it to ignore —
|
|
2184
|
+
* and what keeps "Save" away from "Delete".
|
|
2185
|
+
*/
|
|
2186
|
+
function tokenSimilarity(a, b) {
|
|
2187
|
+
const left = new Set(a.split(" ").filter(Boolean));
|
|
2188
|
+
const right = new Set(b.split(" ").filter(Boolean));
|
|
2189
|
+
if (left.size === 0 || right.size === 0) return 0;
|
|
2190
|
+
let shared = 0;
|
|
2191
|
+
for (const token of left) if (right.has(token)) shared++;
|
|
2192
|
+
return 2 * shared / (left.size + right.size);
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Where a fuzzy match stops being one. Fixed rather than a parameter: a caller
|
|
2196
|
+
* cannot calibrate a number it never sees the scores behind, and a threshold
|
|
2197
|
+
* that moves would make the same query answer differently between runs.
|
|
2198
|
+
*/
|
|
2199
|
+
const FUZZY_THRESHOLD = .6;
|
|
2200
|
+
/**
|
|
2201
|
+
* The comparison one search runs against every key path and value, built once
|
|
2202
|
+
* so the query is normalized once instead of per candidate.
|
|
2203
|
+
*/
|
|
2204
|
+
function buildMatcher(matchMode, query) {
|
|
2205
|
+
if (matchMode === "contains") {
|
|
2206
|
+
const needle = query.toLowerCase();
|
|
2207
|
+
return (candidate) => candidate.toLowerCase().includes(needle);
|
|
2208
|
+
}
|
|
2209
|
+
const normalizedQuery = normalizeForMatch(query);
|
|
2210
|
+
if (matchMode === "exact") return (candidate) => normalizeForMatch(candidate) === normalizedQuery;
|
|
2211
|
+
return (candidate) => {
|
|
2212
|
+
if (normalizedQuery === "") return false;
|
|
2213
|
+
const normalized = normalizeForMatch(candidate);
|
|
2214
|
+
return normalized.includes(normalizedQuery) || tokenSimilarity(normalizedQuery, normalized) >= FUZZY_THRESHOLD;
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Collapse the detail rows to one row per key.
|
|
2219
|
+
*
|
|
2220
|
+
* `layers` and `localeCount` are counted over every sheet in scope rather than
|
|
2221
|
+
* over the rows that matched: a key whose German value matched is still defined
|
|
2222
|
+
* in the other six layers, and that is the fact the caller is asking for.
|
|
2223
|
+
*/
|
|
2224
|
+
function groupMatchesByKey(matches, sheets, referenceCode) {
|
|
2225
|
+
const grouped = [];
|
|
2226
|
+
for (const key of new Set(matches.map((match) => match.key))) {
|
|
2227
|
+
const defining = sheets.filter((sheet) => getNestedValue(sheet.data, key) !== void 0);
|
|
2228
|
+
const source = defining.find((sheet) => sheet.locale === referenceCode) ?? defining[0];
|
|
2229
|
+
if (!source) continue;
|
|
2230
|
+
grouped.push({
|
|
2231
|
+
key,
|
|
2232
|
+
layers: [...new Set(defining.map((sheet) => sheet.layer))],
|
|
2233
|
+
value: getNestedValue(source.data, key),
|
|
2234
|
+
locale: source.locale,
|
|
2235
|
+
localeCount: new Set(defining.map((sheet) => sheet.locale)).size
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
return grouped;
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Search translation files by key path or value.
|
|
2242
|
+
*
|
|
2243
|
+
* Returns one row per key. The detail rows — one per key and locale — are what
|
|
2244
|
+
* `includeLocales` asks for.
|
|
2173
2245
|
*/
|
|
2174
2246
|
async function searchTranslations(opts) {
|
|
2175
2247
|
const { query, layer, locale } = opts;
|
|
2176
2248
|
const config = await detectI18nConfig(opts.projectDir ?? process.cwd());
|
|
2177
2249
|
const mode = opts.searchIn ?? "both";
|
|
2178
|
-
const
|
|
2250
|
+
const matchMode = opts.matchMode ?? "contains";
|
|
2251
|
+
const isMatch = buildMatcher(matchMode, query);
|
|
2179
2252
|
const layersToSearch = layer && layer !== "*" ? config.localeDirs.filter((d) => d.layer === layer) : config.localeDirs.filter((d) => !d.aliasOf);
|
|
2180
2253
|
if (layersToSearch.length === 0) {
|
|
2181
2254
|
if (layer && layer !== "*") findLayerOrThrow(config, layer);
|
|
@@ -2186,28 +2259,43 @@ async function searchTranslations(opts) {
|
|
|
2186
2259
|
if (!found) throw new ToolError(`Locale not found: "${locale}". Available: ${config.locales.map((l) => l.code).join(", ")}. Use one of the available locale codes or file names.`, "LOCALE_NOT_FOUND");
|
|
2187
2260
|
return [found];
|
|
2188
2261
|
})() : config.locales;
|
|
2189
|
-
const
|
|
2262
|
+
const referenceLocale = findLocaleImpl(config, locale ?? config.defaultLocale) ?? localesToSearch[0];
|
|
2263
|
+
const matchedCodes = new Set((matchMode === "contains" || locale || !referenceLocale ? localesToSearch : [referenceLocale]).map((l) => l.code));
|
|
2264
|
+
const sheets = [];
|
|
2190
2265
|
for (const localeDir of layersToSearch) for (const loc of localesToSearch) {
|
|
2191
2266
|
const data = await readLocaleDataIfPresent(config, localeDir.layer, loc);
|
|
2192
2267
|
if (!data) continue;
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2268
|
+
sheets.push({
|
|
2269
|
+
layer: localeDir.layer,
|
|
2270
|
+
locale: loc.code,
|
|
2271
|
+
data
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
const matches = [];
|
|
2275
|
+
for (const sheet of sheets) {
|
|
2276
|
+
if (!matchedCodes.has(sheet.locale)) continue;
|
|
2277
|
+
for (const key of getLeafKeys(sheet.data)) {
|
|
2278
|
+
const value = getNestedValue(sheet.data, key);
|
|
2279
|
+
const valueStr = typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
2280
|
+
const keyMatch = (mode === "keys" || mode === "both") && isMatch(key);
|
|
2281
|
+
const valueMatch = (mode === "values" || mode === "both") && isMatch(valueStr);
|
|
2199
2282
|
if (keyMatch || valueMatch) matches.push({
|
|
2200
|
-
layer:
|
|
2201
|
-
locale:
|
|
2283
|
+
layer: sheet.layer,
|
|
2284
|
+
locale: sheet.locale,
|
|
2202
2285
|
key,
|
|
2203
2286
|
value
|
|
2204
2287
|
});
|
|
2205
2288
|
}
|
|
2206
2289
|
}
|
|
2207
|
-
return {
|
|
2290
|
+
if (opts.includeLocales) return {
|
|
2208
2291
|
matches,
|
|
2209
2292
|
totalMatches: matches.length
|
|
2210
2293
|
};
|
|
2294
|
+
const grouped = groupMatchesByKey(matches, sheets, referenceLocale?.code ?? config.defaultLocale);
|
|
2295
|
+
return {
|
|
2296
|
+
matches: grouped,
|
|
2297
|
+
totalMatches: grouped.length
|
|
2298
|
+
};
|
|
2211
2299
|
}
|
|
2212
2300
|
/**
|
|
2213
2301
|
* Build a prefix tree of all translation keys grouped by layer and namespace.
|
|
@@ -3064,7 +3152,8 @@ function interpret(sites, ctx) {
|
|
|
3064
3152
|
//#endregion
|
|
3065
3153
|
//#region src/scanner/frontends/patterns.ts
|
|
3066
3154
|
/**
|
|
3067
|
-
*
|
|
3155
|
+
* Pattern matching as a language frontend, reached only for a file a syntax
|
|
3156
|
+
* frontend declined.
|
|
3068
3157
|
*
|
|
3069
3158
|
* Regexes frame text and report call sites; what a site means is decided once,
|
|
3070
3159
|
* in the rules, the same as for every other frontend. Binding is always
|
|
@@ -3484,32 +3573,11 @@ async function extractFileEvidence(content, filePath, frontends, patterns) {
|
|
|
3484
3573
|
};
|
|
3485
3574
|
}
|
|
3486
3575
|
/**
|
|
3487
|
-
*
|
|
3488
|
-
*
|
|
3489
|
-
*
|
|
3490
|
-
* on a differential run showing it is at least as conservative as what it
|
|
3491
|
-
* replaces, and on anny-ui the AST frontend still misses 13 keys the patterns
|
|
3492
|
-
* find — most of them regex artifacts, a handful genuine. A key the outgoing
|
|
3493
|
-
* frontend saw and the incoming one does not becomes an orphan, and orphans
|
|
3494
|
-
* get deleted, so the default stays where the evidence is.
|
|
3495
|
-
*
|
|
3496
|
-
* Flip it once `packages/cli/scripts/scanner-diff.mjs` reports nothing in that direction.
|
|
3497
|
-
*/
|
|
3498
|
-
let warnedRegexHatch = false;
|
|
3499
|
-
/**
|
|
3500
|
-
* The syntax frontends are the default (#402 for JS/TS/Vue, #405 for
|
|
3501
|
-
* PHP/Blade); patterns read only what they decline. `I18N_SCANNER=regex`
|
|
3502
|
-
* restores the old scanner for exactly one release — an escape hatch for
|
|
3503
|
-
* reporting a regression, not a mode.
|
|
3576
|
+
* Syntax decides what a translation usage is. The pattern frontend sits last
|
|
3577
|
+
* and never declines, so a file no parser can read still contributes evidence
|
|
3578
|
+
* instead of dropping out of the scan.
|
|
3504
3579
|
*/
|
|
3505
3580
|
function defaultFrontends(pat) {
|
|
3506
|
-
if (process.env.I18N_SCANNER === "regex") {
|
|
3507
|
-
if (!warnedRegexHatch) {
|
|
3508
|
-
warnedRegexHatch = true;
|
|
3509
|
-
log.warn("I18N_SCANNER=regex is deprecated and will be removed in the next major. If the default scanner misses something the regex found, please file it: https://github.com/fabkho/the-i18n-kit/issues");
|
|
3510
|
-
}
|
|
3511
|
-
return [createPatternsFrontend(pat)];
|
|
3512
|
-
}
|
|
3513
3581
|
return [...pat.bareShapes === "php" ? [phpFrontend, bladeFrontend] : [oxcFrontend], createPatternsFrontend(pat)];
|
|
3514
3582
|
}
|
|
3515
3583
|
const oxcFrontend = createOxcFrontend();
|
|
@@ -3667,6 +3735,7 @@ function nestedUnitIgnores(unit, units) {
|
|
|
3667
3735
|
}
|
|
3668
3736
|
async function findOrphanKeysForConfig(options) {
|
|
3669
3737
|
const { keysByLayer, excludeDirs, resolveIgnorePatterns, patterns } = options;
|
|
3738
|
+
const declaredRegexes = buildIgnorePatternRegexes(options.declaredNamespaces ?? []);
|
|
3670
3739
|
const globalScope = options.scanDirs !== void 0;
|
|
3671
3740
|
const units = options.scanDirs !== void 0 ? options.scanDirs.map((d) => ({
|
|
3672
3741
|
name: d,
|
|
@@ -3739,6 +3808,7 @@ async function findOrphanKeysForConfig(options) {
|
|
|
3739
3808
|
let uncertainCount = 0;
|
|
3740
3809
|
let dynamicMatchedCount = 0;
|
|
3741
3810
|
let ignoredCount = 0;
|
|
3811
|
+
let declaredCount = 0;
|
|
3742
3812
|
const misplacedUsages = [];
|
|
3743
3813
|
const scanScopeByLayer = {};
|
|
3744
3814
|
for (const [layerName, { keys }] of keysByLayer) {
|
|
@@ -3760,6 +3830,10 @@ async function findOrphanKeysForConfig(options) {
|
|
|
3760
3830
|
dynamicMatchedCount++;
|
|
3761
3831
|
return false;
|
|
3762
3832
|
}
|
|
3833
|
+
if (declaredRegexes.some((re) => re.test(k))) {
|
|
3834
|
+
declaredCount++;
|
|
3835
|
+
return false;
|
|
3836
|
+
}
|
|
3763
3837
|
if (ignoreRegexes.length > 0 && ignoreRegexes.some((re) => re.test(k))) {
|
|
3764
3838
|
ignoredCount++;
|
|
3765
3839
|
return false;
|
|
@@ -3809,6 +3883,7 @@ async function findOrphanKeysForConfig(options) {
|
|
|
3809
3883
|
totalFilesDeclined,
|
|
3810
3884
|
dynamicMatchedCount,
|
|
3811
3885
|
ignoredCount,
|
|
3886
|
+
declaredCount,
|
|
3812
3887
|
allDynamicKeys: allDynamicKeysRaw,
|
|
3813
3888
|
dirsScanned: units.map((u) => u.dir),
|
|
3814
3889
|
unresolvedKeyWarnings: unresolvedWarnings,
|
|
@@ -4785,6 +4860,7 @@ async function tally(ctx) {
|
|
|
4785
4860
|
* referenced in source code, plus code-usage scanning.
|
|
4786
4861
|
*/
|
|
4787
4862
|
const MISPLACED_USAGE_NOTE = "Keys referenced only from apps that do not consume their layer. Either the key belongs in a broader (shared) layer, or the usage is a bug. These keys are not counted as orphans and are never removed.";
|
|
4863
|
+
const DECLARED_NAMESPACE_NOTE = "Keys covered by a declaredNamespaces entry. They exist by contract rather than by a call site, so they are never reported as orphans and never removed. A declaration with no matchedKeys covers nothing in this catalog — either the namespace is gone or the pattern is wrong.";
|
|
4788
4864
|
const CANDIDATE_ONLY_NOTE = "These keys are protected only by the bare-candidate net: either a dotted string somewhere merely shares their name (often a comment or a data structure), or a call too ambiguous to commit to references them (a bare t(...) that could be anything). They are not offered for removal, but dead references hide here - verify before pruning.";
|
|
4789
4865
|
/** True when `child` equals `parent` or lies inside it. */
|
|
4790
4866
|
function isWithin(child, parent) {
|
|
@@ -4924,6 +5000,7 @@ async function runOrphanScan(config, keysByLayer, opts) {
|
|
|
4924
5000
|
...opts.scanDirs?.length ? { scanDirs: opts.scanDirs } : { scanPlan: buildOrphanScanPlan(config, opts.dir) },
|
|
4925
5001
|
excludeDirs: opts.excludeDirs || void 0,
|
|
4926
5002
|
resolveIgnorePatterns: (layerName) => resolveOrphanIgnorePatterns(config, layerName),
|
|
5003
|
+
declaredNamespaces: resolveDeclaredNamespaces(config).map((d) => d.pattern),
|
|
4927
5004
|
patterns: getPatternSet(config.localeFileFormat),
|
|
4928
5005
|
progress
|
|
4929
5006
|
});
|
|
@@ -4952,6 +5029,38 @@ function resolveOrphanIgnorePatterns(config, layer) {
|
|
|
4952
5029
|
return layerConfig.ignorePatterns;
|
|
4953
5030
|
}
|
|
4954
5031
|
/**
|
|
5032
|
+
* The declared namespaces of a project: key patterns that exist by contract
|
|
5033
|
+
* rather than by a call site. They are not keyed by layer — the contract
|
|
5034
|
+
* defines the key set, whichever layer happens to hold it.
|
|
5035
|
+
*/
|
|
5036
|
+
function resolveDeclaredNamespaces(config) {
|
|
5037
|
+
return config.projectConfig?.declaredNamespaces ?? [];
|
|
5038
|
+
}
|
|
5039
|
+
/**
|
|
5040
|
+
* Each declaration with the catalog keys it covers, so the report says which
|
|
5041
|
+
* keys a declaration protects and why — and, with an empty `matchedKeys`,
|
|
5042
|
+
* which declaration protects nothing at all.
|
|
5043
|
+
*
|
|
5044
|
+
* Coverage is read off the catalog rather than off the orphan list: a
|
|
5045
|
+
* namespace whose keys are also referenced in code still exists, and only a
|
|
5046
|
+
* declaration that matches no key anywhere is stale.
|
|
5047
|
+
*/
|
|
5048
|
+
function buildDeclaredNamespaceRefs(config, keysByLayer) {
|
|
5049
|
+
const declarations = resolveDeclaredNamespaces(config);
|
|
5050
|
+
if (declarations.length === 0) return void 0;
|
|
5051
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
5052
|
+
for (const { keys } of keysByLayer.values()) for (const key of keys) allKeys.add(key);
|
|
5053
|
+
const sortedKeys = [...allKeys].sort(byCodePoint);
|
|
5054
|
+
return declarations.map(({ pattern, reason }) => {
|
|
5055
|
+
const [regex] = buildIgnorePatternRegexes([pattern]);
|
|
5056
|
+
return {
|
|
5057
|
+
pattern,
|
|
5058
|
+
reason,
|
|
5059
|
+
matchedKeys: regex ? sortedKeys.filter((key) => regex.test(key)) : []
|
|
5060
|
+
};
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
/**
|
|
4955
5064
|
* Shared helper for findOrphanKeys and removeOrphanKeys.
|
|
4956
5065
|
* Resolves the locale, filters layers, validates aliases, and builds the
|
|
4957
5066
|
* keysByLayer Map. Returns the resolved context — or throws on invalid input.
|
|
@@ -5029,6 +5138,7 @@ async function findOrphanKeys(opts) {
|
|
|
5029
5138
|
sortedByLayer[keyLayer].push(key);
|
|
5030
5139
|
}
|
|
5031
5140
|
const misplacedCount = orphanResult.misplacedUsages.length;
|
|
5141
|
+
const declaredNamespaces = buildDeclaredNamespaceRefs(config, keysByLayer);
|
|
5032
5142
|
return {
|
|
5033
5143
|
orphanKeys: sortedByLayer,
|
|
5034
5144
|
uncertainKeys: orphanResult.uncertainCount > 0 ? orphanResult.uncertainByLayer : void 0,
|
|
@@ -5036,6 +5146,8 @@ async function findOrphanKeys(opts) {
|
|
|
5036
5146
|
candidateOnlyNote: orphanResult.candidateOnlyCount > 0 ? CANDIDATE_ONLY_NOTE : void 0,
|
|
5037
5147
|
misplacedUsages: misplacedCount > 0 ? orphanResult.misplacedUsages : void 0,
|
|
5038
5148
|
misplacedUsageNote: misplacedCount > 0 ? MISPLACED_USAGE_NOTE : void 0,
|
|
5149
|
+
declaredNamespaces,
|
|
5150
|
+
declaredNamespaceNote: declaredNamespaces ? DECLARED_NAMESPACE_NOTE : void 0,
|
|
5039
5151
|
summary: {
|
|
5040
5152
|
totalKeys,
|
|
5041
5153
|
orphanCount: orphanResult.orphanCount,
|
|
@@ -5044,6 +5156,7 @@ async function findOrphanKeys(opts) {
|
|
|
5044
5156
|
misplacedCount,
|
|
5045
5157
|
dynamicMatchedCount: orphanResult.dynamicMatchedCount,
|
|
5046
5158
|
ignoredCount: orphanResult.ignoredCount,
|
|
5159
|
+
declaredCount: orphanResult.declaredCount,
|
|
5047
5160
|
usedCount: totalKeys - orphanResult.orphanCount - orphanResult.uncertainCount - misplacedCount,
|
|
5048
5161
|
filesScanned: orphanResult.totalFilesScanned,
|
|
5049
5162
|
filesDeclined: orphanResult.totalFilesDeclined,
|
|
@@ -5145,23 +5258,29 @@ async function removeOrphanKeys(opts) {
|
|
|
5145
5258
|
const totalFilesScanned = orphanResult.totalFilesScanned;
|
|
5146
5259
|
const dynamicMatchedCount = orphanResult.dynamicMatchedCount;
|
|
5147
5260
|
const ignoredCount = orphanResult.ignoredCount;
|
|
5261
|
+
const declaredCount = orphanResult.declaredCount;
|
|
5148
5262
|
const misplacedCount = orphanResult.misplacedUsages.length;
|
|
5149
5263
|
const misplacedUsages = misplacedCount > 0 ? orphanResult.misplacedUsages : void 0;
|
|
5150
5264
|
const misplacedUsageNote = misplacedCount > 0 ? MISPLACED_USAGE_NOTE : void 0;
|
|
5265
|
+
const declaredNamespaces = buildDeclaredNamespaceRefs(config, keysByLayer);
|
|
5266
|
+
const declaredNamespaceNote = declaredNamespaces ? DECLARED_NAMESPACE_NOTE : void 0;
|
|
5151
5267
|
const scanScope = relativeScanScope(orphanResult, dir);
|
|
5152
5268
|
const allDynamicKeys = toDynamicKeyEntries(orphanResult.allDynamicKeys, dir);
|
|
5153
5269
|
if (orphanCount === 0) {
|
|
5154
5270
|
const messageParts = ["No orphan keys found."];
|
|
5155
5271
|
if (dynamicMatchedCount > 0) messageParts.push(`${dynamicMatchedCount} key(s) were excluded by dynamic pattern matching.`);
|
|
5156
5272
|
if (ignoredCount > 0) messageParts.push(`${ignoredCount} key(s) were excluded by ignore patterns.`);
|
|
5273
|
+
if (declaredCount > 0) messageParts.push(`${declaredCount} key(s) were excluded by declared namespaces (see declaredNamespaces).`);
|
|
5157
5274
|
if (orphanResult.uncertainCount > 0) messageParts.push(`${orphanResult.uncertainCount} uncertain key(s) were excluded because they overlap with dynamic translation patterns.`);
|
|
5158
5275
|
if (misplacedCount > 0) messageParts.push(`${misplacedCount} key(s) are referenced only outside their layer's scope (see misplacedUsages).`);
|
|
5159
|
-
if (dynamicMatchedCount === 0 && ignoredCount === 0 && orphanResult.uncertainCount === 0 && misplacedCount === 0) messageParts.push("All translation keys are referenced in code.");
|
|
5276
|
+
if (dynamicMatchedCount === 0 && ignoredCount === 0 && declaredCount === 0 && orphanResult.uncertainCount === 0 && misplacedCount === 0) messageParts.push("All translation keys are referenced in code.");
|
|
5160
5277
|
return {
|
|
5161
5278
|
orphanKeys: {},
|
|
5162
5279
|
uncertainKeys: orphanResult.uncertainCount > 0 ? orphanResult.uncertainByLayer : void 0,
|
|
5163
5280
|
misplacedUsages,
|
|
5164
5281
|
misplacedUsageNote,
|
|
5282
|
+
declaredNamespaces,
|
|
5283
|
+
declaredNamespaceNote,
|
|
5165
5284
|
summary: {
|
|
5166
5285
|
totalKeys,
|
|
5167
5286
|
orphanCount: 0,
|
|
@@ -5169,6 +5288,7 @@ async function removeOrphanKeys(opts) {
|
|
|
5169
5288
|
misplacedCount,
|
|
5170
5289
|
dynamicMatchedCount,
|
|
5171
5290
|
ignoredCount,
|
|
5291
|
+
declaredCount,
|
|
5172
5292
|
filesScanned: totalFilesScanned,
|
|
5173
5293
|
scanScope,
|
|
5174
5294
|
message: messageParts.join(" ")
|
|
@@ -5181,6 +5301,8 @@ async function removeOrphanKeys(opts) {
|
|
|
5181
5301
|
uncertainKeys: orphanResult.uncertainCount > 0 ? orphanResult.uncertainByLayer : void 0,
|
|
5182
5302
|
misplacedUsages,
|
|
5183
5303
|
misplacedUsageNote,
|
|
5304
|
+
declaredNamespaces,
|
|
5305
|
+
declaredNamespaceNote,
|
|
5184
5306
|
summary: {
|
|
5185
5307
|
dryRun: true,
|
|
5186
5308
|
totalKeys,
|
|
@@ -5189,10 +5311,11 @@ async function removeOrphanKeys(opts) {
|
|
|
5189
5311
|
misplacedCount,
|
|
5190
5312
|
dynamicMatchedCount,
|
|
5191
5313
|
ignoredCount,
|
|
5314
|
+
declaredCount,
|
|
5192
5315
|
usedCount: totalKeys - orphanCount - orphanResult.uncertainCount - misplacedCount,
|
|
5193
5316
|
filesScanned: totalFilesScanned,
|
|
5194
5317
|
scanScope,
|
|
5195
|
-
message: `Found ${orphanCount} orphan key(s) safe to remove.${orphanResult.uncertainCount > 0 ? ` ${orphanResult.uncertainCount} uncertain key(s) excluded (overlap with dynamic translation patterns).` : ""}${misplacedCount > 0 ? ` ${misplacedCount} key(s) referenced only outside their layer's scope were excluded (see misplacedUsages).` : ""} ${dynamicMatchedCount > 0 ? `${dynamicMatchedCount} key(s) matched dynamic patterns and were excluded. ` : ""}${ignoredCount > 0 ? `${ignoredCount} key(s) matched ignore patterns and were excluded. ` : ""}Call again with dryRun: false to remove them.`
|
|
5318
|
+
message: `Found ${orphanCount} orphan key(s) safe to remove.${orphanResult.uncertainCount > 0 ? ` ${orphanResult.uncertainCount} uncertain key(s) excluded (overlap with dynamic translation patterns).` : ""}${misplacedCount > 0 ? ` ${misplacedCount} key(s) referenced only outside their layer's scope were excluded (see misplacedUsages).` : ""} ${dynamicMatchedCount > 0 ? `${dynamicMatchedCount} key(s) matched dynamic patterns and were excluded. ` : ""}${ignoredCount > 0 ? `${ignoredCount} key(s) matched ignore patterns and were excluded. ` : ""}${declaredCount > 0 ? `${declaredCount} key(s) are covered by declared namespaces and were excluded. ` : ""}Call again with dryRun: false to remove them.`
|
|
5196
5319
|
}
|
|
5197
5320
|
};
|
|
5198
5321
|
if (allDynamicKeys.length > 0) {
|
|
@@ -5227,6 +5350,8 @@ async function removeOrphanKeys(opts) {
|
|
|
5227
5350
|
uncertainKeys: orphanResult.uncertainCount > 0 ? orphanResult.uncertainByLayer : void 0,
|
|
5228
5351
|
misplacedUsages,
|
|
5229
5352
|
misplacedUsageNote,
|
|
5353
|
+
declaredNamespaces,
|
|
5354
|
+
declaredNamespaceNote,
|
|
5230
5355
|
summary: {
|
|
5231
5356
|
dryRun: false,
|
|
5232
5357
|
totalKeys,
|
|
@@ -5235,6 +5360,7 @@ async function removeOrphanKeys(opts) {
|
|
|
5235
5360
|
misplacedCount,
|
|
5236
5361
|
dynamicMatchedCount,
|
|
5237
5362
|
ignoredCount,
|
|
5363
|
+
declaredCount,
|
|
5238
5364
|
remainingCount: totalKeys - orphanCount,
|
|
5239
5365
|
filesWritten: totalFilesWritten,
|
|
5240
5366
|
filesScanned: totalFilesScanned,
|
|
@@ -5545,14 +5671,19 @@ function groupBy(items, keyOf) {
|
|
|
5545
5671
|
}
|
|
5546
5672
|
/**
|
|
5547
5673
|
* Classify one unit's static keys: keys not resolvable in the searched
|
|
5548
|
-
* layers become undefined findings — unless
|
|
5549
|
-
* them,
|
|
5550
|
-
* dynamic expression), or they are only
|
|
5674
|
+
* layers become undefined findings — unless a declared namespace covers
|
|
5675
|
+
* them, an ignore pattern excludes them, a dynamic pattern overlaps them
|
|
5676
|
+
* (possible partial extraction of a dynamic expression), or they are only
|
|
5677
|
+
* probed via $te.
|
|
5551
5678
|
*/
|
|
5552
5679
|
function classifyStaticKeys(usages, dynRegexes, ctx, outcome) {
|
|
5553
5680
|
for (const [key, keyUsages] of groupBy(usages, (u) => u.key)) {
|
|
5554
5681
|
if (key.endsWith(".")) continue;
|
|
5555
5682
|
if (ctx.resolvable.has(key)) continue;
|
|
5683
|
+
if (ctx.declaredRegexes.some((re) => re.test(key))) {
|
|
5684
|
+
outcome.declaredCount++;
|
|
5685
|
+
continue;
|
|
5686
|
+
}
|
|
5556
5687
|
if (ctx.ignoreRegexes.some((re) => re.test(key))) {
|
|
5557
5688
|
outcome.ignoredCount++;
|
|
5558
5689
|
continue;
|
|
@@ -5608,6 +5739,7 @@ function classifyUnitUsages(scan, ctx) {
|
|
|
5608
5739
|
undefinedKeys: [],
|
|
5609
5740
|
uncertainKeys: [],
|
|
5610
5741
|
ignoredCount: 0,
|
|
5742
|
+
declaredCount: 0,
|
|
5611
5743
|
checkedKeys: new Set(scan.uniqueKeys)
|
|
5612
5744
|
};
|
|
5613
5745
|
const dynRegexes = buildDynamicKeyRegexes([...scan.dynamicKeys, ...[...scan.bareDynamicCandidates].map((expression) => ({ expression }))]);
|
|
@@ -5725,6 +5857,8 @@ async function checkUndefinedKeys(opts = {}) {
|
|
|
5725
5857
|
let filesScanned = 0;
|
|
5726
5858
|
let filesDeclined = 0;
|
|
5727
5859
|
let ignoredCount = 0;
|
|
5860
|
+
let declaredCount = 0;
|
|
5861
|
+
const declaredRegexes = buildIgnorePatternRegexes(resolveDeclaredNamespaces(config).map((declaration) => declaration.pattern));
|
|
5728
5862
|
for (const unit of units) {
|
|
5729
5863
|
const ignores = globalScope ? [] : nestedUnitIgnores(unit, units);
|
|
5730
5864
|
const scan = await scanSourceFiles(unit.dir, [...opts.excludeDirs ?? [], ...ignores], patterns);
|
|
@@ -5737,11 +5871,13 @@ async function checkUndefinedKeys(opts = {}) {
|
|
|
5737
5871
|
searchedLayers,
|
|
5738
5872
|
resolvable: resolvablePaths(searchedLayers),
|
|
5739
5873
|
ignoreRegexes: buildIgnorePatternRegexes(searchedLayers.flatMap((layer) => resolveOrphanIgnorePatterns(config, layer) ?? [])),
|
|
5874
|
+
declaredRegexes,
|
|
5740
5875
|
projectDir: dir
|
|
5741
5876
|
});
|
|
5742
5877
|
undefinedKeys.push(...outcome.undefinedKeys);
|
|
5743
5878
|
uncertainKeys.push(...outcome.uncertainKeys);
|
|
5744
5879
|
ignoredCount += outcome.ignoredCount;
|
|
5880
|
+
declaredCount += outcome.declaredCount;
|
|
5745
5881
|
for (const key of outcome.checkedKeys) checkedKeys.add(key);
|
|
5746
5882
|
}
|
|
5747
5883
|
undefinedKeys.sort(byAppThenKey);
|
|
@@ -5755,6 +5891,7 @@ async function checkUndefinedKeys(opts = {}) {
|
|
|
5755
5891
|
undefinedCount: undefinedKeys.length,
|
|
5756
5892
|
uncertainCount: uncertainKeys.length,
|
|
5757
5893
|
ignoredCount,
|
|
5894
|
+
declaredCount,
|
|
5758
5895
|
filesScanned,
|
|
5759
5896
|
filesDeclined,
|
|
5760
5897
|
locale: localeCode,
|
|
@@ -5770,6 +5907,6 @@ async function checkUndefinedKeys(opts = {}) {
|
|
|
5770
5907
|
});
|
|
5771
5908
|
}
|
|
5772
5909
|
//#endregion
|
|
5773
|
-
export {
|
|
5910
|
+
export { extractJsonFromResponse as A, listNamespaces as C, translateMissing as D, translateKey as E, readLocaleData as F, buildTranslationUserMessage as M, validatePlaceholders as N, buildLayerGraph as O, resolveProtectedLocales as P, listLocaleDirs as S, computeProgressTotal as T, describeProject as _, scanCodeUsage as a, getMissingTranslations as b, moveTranslationKey as c, scaffoldLocaleFiles as d, writeTranslations as f, createPhpFrontend as g, LARAVEL_PATTERNS as h, removeOrphanKeys as i, buildTranslationSystemPrompt as j, serializeLayerGraph as k, removeTranslations as l, createPatternsFrontend as m, findDuplicateKeys as n, getTranslationStatus as o, scanSourceFiles as p, findOrphanKeys as r, initProjectConfig as s, checkUndefinedKeys as t, renameTranslationKey as u, detectConfig as v, searchTranslations as w, getTranslations as x, findEmptyTranslations as y };
|
|
5774
5911
|
|
|
5775
|
-
//# sourceMappingURL=operations-
|
|
5912
|
+
//# sourceMappingURL=operations-Daf2Ommm.js.map
|