@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 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>
@@ -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.196",
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.196"
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.5"
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: "Present"
45
+ label: "jskit.crud.listFilter.present"
42
46
  }),
43
47
  Object.freeze({
44
48
  value: CRUD_LIST_FILTER_PRESENCE_MISSING,
45
- label: "Missing"
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
- function formatCrudListFilterDefaultChipLabel(filter = {}, rawValue, { resolveAtomicValue: resolveAtomicLabel = atomicValueText } = {}) {
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 filter.label;
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 `${filter.label}: ${resolveAtomicLabel(normalizedValue, filter)}`;
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 `${filter.label}: ${resolveAtomicLabel(atomicValue, filter)}`;
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 `${filter.label}: ${normalizedValue}`;
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 `${filter.label}: ${normalizedValue.from} to ${normalizedValue.to}`;
606
+ return t("jskit.crud.listFilter.chip.range", { label, from: normalizedValue.from, to: normalizedValue.to });
589
607
  }
590
608
  if (normalizedValue?.from) {
591
- return `${filter.label}: from ${normalizedValue.from}`;
609
+ return t("jskit.crud.listFilter.chip.from", { label, from: normalizedValue.from });
592
610
  }
593
- return `${filter.label}: to ${normalizedValue?.to || ""}`;
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 `${filter.label}: ${normalizedValue.min} to ${normalizedValue.max}`;
616
+ return t("jskit.crud.listFilter.chip.range", { label, from: normalizedValue.min, to: normalizedValue.max });
599
617
  }
600
618
  if (normalizedValue?.min) {
601
- return `${filter.label}: min ${normalizedValue.min}`;
619
+ return t("jskit.crud.listFilter.chip.min", { label, min: normalizedValue.min });
602
620
  }
603
- return `${filter.label}: max ${normalizedValue?.max || ""}`;
621
+ return t("jskit.crud.listFilter.chip.max", { label, max: normalizedValue?.max || "" });
604
622
  }
605
623
 
606
- return filter.label;
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
+ });