@jskit-ai/resource-crud-core 0.1.196 → 0.1.197
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/CHANGELOG.md +21 -0
- package/locales/en.json +18 -0
- package/package.json +5 -4
- package/src/shared/crudListFilterValues.js +32 -14
- package/test/crudListFilters.test.js +37 -1
- package/test/i18nCatalog.test.js +16 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- New catalog `locales/en.json` (exported as `./locales/en.json`) with the list filter words under `jskit.crud.listFilter.*`.
|
|
6
|
+
- `CRUD_LIST_FILTER_PRESENCE_OPTIONS` labels are message keys (`jskit.crud.listFilter.present`, `jskit.crud.listFilter.missing`); render them with `text()`. `formatCrudListFilterDefaultChipLabel` takes a `t` option for the app's translator and keeps its English without one.
|
|
7
|
+
|
|
8
|
+
### New message keys (8)
|
|
9
|
+
|
|
10
|
+
<details><summary>Key list</summary>
|
|
11
|
+
|
|
12
|
+
- `jskit.crud.listFilter.present`
|
|
13
|
+
- `jskit.crud.listFilter.missing`
|
|
14
|
+
- `jskit.crud.listFilter.chip.value`
|
|
15
|
+
- `jskit.crud.listFilter.chip.range`
|
|
16
|
+
- `jskit.crud.listFilter.chip.from`
|
|
17
|
+
- `jskit.crud.listFilter.chip.to`
|
|
18
|
+
- `jskit.crud.listFilter.chip.min`
|
|
19
|
+
- `jskit.crud.listFilter.chip.max`
|
|
20
|
+
|
|
21
|
+
</details>
|
package/locales/en.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jskit": {
|
|
3
|
+
"crud": {
|
|
4
|
+
"listFilter": {
|
|
5
|
+
"present": "Present",
|
|
6
|
+
"missing": "Missing",
|
|
7
|
+
"chip": {
|
|
8
|
+
"value": "{label}: {value}",
|
|
9
|
+
"range": "{label}: {from} to {to}",
|
|
10
|
+
"from": "{label}: from {from}",
|
|
11
|
+
"to": "{label}: to {to}",
|
|
12
|
+
"min": "{label}: min {min}",
|
|
13
|
+
"max": "{label}: max {max}"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/resource-crud-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.197",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
"./shared/crudListFilters": "./src/shared/crudListFilters.js",
|
|
11
11
|
"./shared/crudLookup": "./src/shared/crudLookup.js",
|
|
12
12
|
"./shared/crudNamespaceSupport": "./src/shared/crudNamespaceSupport.js",
|
|
13
|
-
"./shared/crudResource": "./src/shared/crudResource.js"
|
|
13
|
+
"./shared/crudResource": "./src/shared/crudResource.js",
|
|
14
|
+
"./locales/en.json": "./locales/en.json"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"@jskit-ai/resource-core": "0.1.
|
|
17
|
+
"@jskit-ai/resource-core": "0.1.197"
|
|
17
18
|
},
|
|
18
19
|
"description": "Shared CRUD resource, field, lookup, filter, and namespace contracts.",
|
|
19
20
|
"jskit": {
|
|
@@ -34,6 +35,6 @@
|
|
|
34
35
|
}
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
|
-
"@jskit-ai/kernel": "0.2.
|
|
38
|
+
"@jskit-ai/kernel": "0.2.6"
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// The values a CRUD list filter holds: its types and their empty, UI, query and chip forms.
|
|
2
2
|
import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
3
|
+
import { catalogMessage, formatMessage } from "@jskit-ai/kernel/shared/i18n";
|
|
3
4
|
import {
|
|
4
5
|
normalizeBoolean,
|
|
5
6
|
normalizeCanonicalRecordIdText,
|
|
6
7
|
normalizeUniqueTextList,
|
|
7
8
|
normalizeText
|
|
8
9
|
} from "@jskit-ai/kernel/shared/support/normalize";
|
|
10
|
+
import en from "../../locales/en.json" with { type: "json" };
|
|
9
11
|
|
|
10
12
|
const CRUD_LIST_FILTER_TYPE_FLAG = "flag";
|
|
11
13
|
const CRUD_LIST_FILTER_TYPE_ENUM = "enum";
|
|
@@ -35,14 +37,16 @@ const CRUD_LIST_FILTER_PRESENCE_VALUES = new Set([
|
|
|
35
37
|
CRUD_LIST_FILTER_PRESENCE_PRESENT,
|
|
36
38
|
CRUD_LIST_FILTER_PRESENCE_MISSING
|
|
37
39
|
]);
|
|
40
|
+
// Option labels are message keys; JSKIT's filter controls and chips render them
|
|
41
|
+
// with text(), which shows any other label as written.
|
|
38
42
|
const CRUD_LIST_FILTER_PRESENCE_OPTIONS = Object.freeze([
|
|
39
43
|
Object.freeze({
|
|
40
44
|
value: CRUD_LIST_FILTER_PRESENCE_PRESENT,
|
|
41
|
-
label: "
|
|
45
|
+
label: "jskit.crud.listFilter.present"
|
|
42
46
|
}),
|
|
43
47
|
Object.freeze({
|
|
44
48
|
value: CRUD_LIST_FILTER_PRESENCE_MISSING,
|
|
45
|
-
label: "
|
|
49
|
+
label: "jskit.crud.listFilter.missing"
|
|
46
50
|
})
|
|
47
51
|
]);
|
|
48
52
|
const CRUD_LIST_FILTER_INVALID_VALUES_REJECT = "reject";
|
|
@@ -556,12 +560,26 @@ function atomicValueText(value) {
|
|
|
556
560
|
return String(value || "");
|
|
557
561
|
}
|
|
558
562
|
|
|
559
|
-
|
|
563
|
+
/**
|
|
564
|
+
* A chip message in English, from this package's catalog; a client passes its own
|
|
565
|
+
* translator instead.
|
|
566
|
+
* @param {string} key
|
|
567
|
+
* @param {Record<string, unknown>} params
|
|
568
|
+
*/
|
|
569
|
+
function englishChipText(key, params) {
|
|
570
|
+
return formatMessage(catalogMessage(en, key), params);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function formatCrudListFilterDefaultChipLabel(filter = {}, rawValue, {
|
|
574
|
+
resolveAtomicValue: resolveAtomicLabel = atomicValueText,
|
|
575
|
+
t = englishChipText
|
|
576
|
+
} = {}) {
|
|
560
577
|
const normalizedValue = normalizeCrudListFilterUiValue(filter, rawValue);
|
|
561
578
|
const type = normalizeCrudListFilterType(filter.type);
|
|
579
|
+
const label = filter.label;
|
|
562
580
|
|
|
563
581
|
if (type === CRUD_LIST_FILTER_TYPE_FLAG) {
|
|
564
|
-
return
|
|
582
|
+
return label;
|
|
565
583
|
}
|
|
566
584
|
|
|
567
585
|
if (
|
|
@@ -569,41 +587,41 @@ function formatCrudListFilterDefaultChipLabel(filter = {}, rawValue, { resolveAt
|
|
|
569
587
|
type === CRUD_LIST_FILTER_TYPE_PRESENCE ||
|
|
570
588
|
type === CRUD_LIST_FILTER_TYPE_RECORD_ID
|
|
571
589
|
) {
|
|
572
|
-
return
|
|
590
|
+
return t("jskit.crud.listFilter.chip.value", { label, value: resolveAtomicLabel(normalizedValue, filter) });
|
|
573
591
|
}
|
|
574
592
|
|
|
575
593
|
if (type === CRUD_LIST_FILTER_TYPE_ENUM_MANY || type === CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY) {
|
|
576
594
|
const atomicValue = Array.isArray(normalizedValue)
|
|
577
595
|
? normalizedValue[0] || ""
|
|
578
596
|
: normalizeText(normalizedValue);
|
|
579
|
-
return
|
|
597
|
+
return t("jskit.crud.listFilter.chip.value", { label, value: resolveAtomicLabel(atomicValue, filter) });
|
|
580
598
|
}
|
|
581
599
|
|
|
582
600
|
if (type === CRUD_LIST_FILTER_TYPE_DATE) {
|
|
583
|
-
return
|
|
601
|
+
return t("jskit.crud.listFilter.chip.value", { label, value: normalizedValue });
|
|
584
602
|
}
|
|
585
603
|
|
|
586
604
|
if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
|
|
587
605
|
if (normalizedValue?.from && normalizedValue?.to) {
|
|
588
|
-
return
|
|
606
|
+
return t("jskit.crud.listFilter.chip.range", { label, from: normalizedValue.from, to: normalizedValue.to });
|
|
589
607
|
}
|
|
590
608
|
if (normalizedValue?.from) {
|
|
591
|
-
return
|
|
609
|
+
return t("jskit.crud.listFilter.chip.from", { label, from: normalizedValue.from });
|
|
592
610
|
}
|
|
593
|
-
return
|
|
611
|
+
return t("jskit.crud.listFilter.chip.to", { label, to: normalizedValue?.to || "" });
|
|
594
612
|
}
|
|
595
613
|
|
|
596
614
|
if (type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
|
|
597
615
|
if (normalizedValue?.min && normalizedValue?.max) {
|
|
598
|
-
return
|
|
616
|
+
return t("jskit.crud.listFilter.chip.range", { label, from: normalizedValue.min, to: normalizedValue.max });
|
|
599
617
|
}
|
|
600
618
|
if (normalizedValue?.min) {
|
|
601
|
-
return
|
|
619
|
+
return t("jskit.crud.listFilter.chip.min", { label, min: normalizedValue.min });
|
|
602
620
|
}
|
|
603
|
-
return
|
|
621
|
+
return t("jskit.crud.listFilter.chip.max", { label, max: normalizedValue?.max || "" });
|
|
604
622
|
}
|
|
605
623
|
|
|
606
|
-
return
|
|
624
|
+
return label;
|
|
607
625
|
}
|
|
608
626
|
|
|
609
627
|
function normalizeCrudListFilterType(value = "") {
|
|
@@ -19,8 +19,11 @@ import {
|
|
|
19
19
|
formatCrudListFilterQueryValue,
|
|
20
20
|
parseCrudListFilterQueryValue,
|
|
21
21
|
resolveCrudListFilterQueryKeys,
|
|
22
|
-
resolveCrudListFilterOptionLabel
|
|
22
|
+
resolveCrudListFilterOptionLabel,
|
|
23
|
+
CRUD_LIST_FILTER_PRESENCE_OPTIONS
|
|
23
24
|
} from "../src/shared/crudListFilters.js";
|
|
25
|
+
import { catalogMessage, formatMessage } from "@jskit-ai/kernel/shared/i18n";
|
|
26
|
+
import en from "../locales/en.json" with { type: "json" };
|
|
24
27
|
|
|
25
28
|
test("defineCrudListFilters normalizes common filter shapes", () => {
|
|
26
29
|
const filters = defineCrudListFilters({
|
|
@@ -350,3 +353,36 @@ test("crud list filter helpers share canonical UI and query normalization", () =
|
|
|
350
353
|
"Status: Active"
|
|
351
354
|
);
|
|
352
355
|
});
|
|
356
|
+
|
|
357
|
+
test("default chip labels keep their English and render through a caller's translator", () => {
|
|
358
|
+
const filters = defineCrudListFilters({
|
|
359
|
+
price: { type: "numberRange", label: "Price" },
|
|
360
|
+
arrival: { type: "dateRange", label: "Arrival" },
|
|
361
|
+
storage: { type: "presence", label: "Storage" }
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
assert.equal(formatCrudListFilterDefaultChipLabel(filters.price, { min: "1", max: "5" }), "Price: 1 to 5");
|
|
365
|
+
assert.equal(formatCrudListFilterDefaultChipLabel(filters.price, { min: "1", max: "" }), "Price: min 1");
|
|
366
|
+
assert.equal(formatCrudListFilterDefaultChipLabel(filters.price, { min: "", max: "5" }), "Price: max 5");
|
|
367
|
+
assert.equal(
|
|
368
|
+
formatCrudListFilterDefaultChipLabel(filters.arrival, { from: "2026-04-01", to: "2026-04-30" }),
|
|
369
|
+
"Arrival: 2026-04-01 to 2026-04-30"
|
|
370
|
+
);
|
|
371
|
+
assert.equal(formatCrudListFilterDefaultChipLabel(filters.arrival, { from: "2026-04-01", to: "" }), "Arrival: from 2026-04-01");
|
|
372
|
+
assert.equal(formatCrudListFilterDefaultChipLabel(filters.arrival, { from: "", to: "2026-04-30" }), "Arrival: to 2026-04-30");
|
|
373
|
+
|
|
374
|
+
// The presence options' labels are message keys whose English is unchanged.
|
|
375
|
+
assert.deepEqual(
|
|
376
|
+
CRUD_LIST_FILTER_PRESENCE_OPTIONS.map((option) => formatMessage(catalogMessage(en, option.label))),
|
|
377
|
+
["Present", "Missing"]
|
|
378
|
+
);
|
|
379
|
+
assert.equal(
|
|
380
|
+
formatCrudListFilterDefaultChipLabel(filters.storage, "missing", {
|
|
381
|
+
resolveAtomicValue: (value) => formatMessage(catalogMessage(en, resolveCrudListFilterOptionLabel(filters.storage, value)))
|
|
382
|
+
}),
|
|
383
|
+
"Storage: Missing"
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const t = (key, params) => `${key.split(".").at(-1)}(${Object.values(params).join(",")})`;
|
|
387
|
+
assert.equal(formatCrudListFilterDefaultChipLabel(filters.price, { min: "1", max: "5" }, { t }), "range(Price,1,5)");
|
|
388
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { evaluateI18nCatalogContract } from "../../../tooling/test-support/i18nCatalogContract.mjs";
|
|
6
|
+
|
|
7
|
+
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const REPO_ROOT = path.resolve(TEST_DIRECTORY, "..", "..", "..");
|
|
9
|
+
|
|
10
|
+
test("resource-crud-core English catalog matches its source", () => {
|
|
11
|
+
const { problems } = evaluateI18nCatalogContract({
|
|
12
|
+
repoRoot: REPO_ROOT,
|
|
13
|
+
packageDir: path.join(REPO_ROOT, "packages", "resource-crud-core")
|
|
14
|
+
});
|
|
15
|
+
assert.deepEqual(problems, []);
|
|
16
|
+
});
|