@smartsoft001-mobilems/claude-plugins 2.89.0 → 2.90.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartsoft001-mobilems/claude-plugins",
3
- "version": "2.89.0",
3
+ "version": "2.90.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "mobilems",
3
3
  "description": "MobilEMS-specific Claude Code skills (MuzaCMS / MobileMS indexer API)",
4
- "version": "2.89.0"
4
+ "version": "2.90.0"
5
5
  }
@@ -78,11 +78,16 @@ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on
78
78
  Also add any **extra third-party packages the copied pages import** that aren't already in the
79
79
  workspace — e.g. the reference list page uses `lodash-decorators` (`@debounce`); grep the pages you
80
80
  copy for non-`@angular`/`@smartsoft001` imports and add the missing ones.
81
- 3. **Library** — create `libs/museum-<name>/{domain,shell/angular}` per `references/lib-contents.md`:
82
- the thin domain entity (`Museum<Name> extends Article`), the shell (`Museum<Name>Module`, routes,
83
- `pages/{list,item}`, services, and — if the module keeps feature state — `+state/<name>.reducer.ts`),
84
- threading `<PREFIX>` and the chosen name/entity. Add the two `tsconfig.base.json` paths
85
- (`@<PREFIX>/museum-<name>/domain` + `/shell/angular`).
81
+ 3. **Library** — scaffold `libs/museum-news` with this package's Nx generator (deterministic — do
82
+ NOT copy files by hand when the generator is available):
83
+ ```bash
84
+ npx nx g @smartsoft001-mobilems/articles-shell-angular:museum-module \
85
+ --referenceRoot=<reference-repo> --prefix=<PREFIX>
86
+ ```
87
+ It copies the lib from the reference, threads `@<PREFIX>/`, adds both `tsconfig.base.json`
88
+ paths, normalizes `project.json` paths and copies the module's translation groups (pl+en).
89
+ Fallback (older framework line without the generator): do the same by hand per
90
+ `references/lib-contents.md`. For a NON-news article module (different `<name>`/entity), the generator's `news` output is the starting point — rename per `references/lib-contents.md` after generating, or fall back to the manual path entirely.
86
91
  4. **Module + route** — see `references/module-and-routes.md`: `Museum<Name>Module` wraps
87
92
  `ArticlesSharedModule.forFeature` with `type: Article`, the chosen `entity`, and
88
93
  `baseQuery: [{ key: 'template', value: '<template>', type: '=', hidden: true }]`, plus the `Masonry`
@@ -95,7 +100,11 @@ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on
95
100
  and list cards (`<name>-grid-item`, `<name>-list-item`) in `libs/shared/angular` and export them; if
96
101
  the module renders article-gallery content blocks, also build the article gallery widget and register
97
102
  it in the widget content-type map (build once, idempotent across article modules).
98
- 6. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX> <name> [template]`: asserts the
103
+ 6. **Translations** — copy the module's translation groups (`NEWS` + `ARTICLES` for the aktualności instance (override with `TRANS_GROUPS` when the module uses different groups)) from the reference
104
+ repo's `apps/web/public/translations.json` into the app's file, for BOTH `pl` and `en`.
105
+ Complete group = every reference key present; every key referenced in the module's code
106
+ must exist in both languages — a raw `GROUP.key` rendered in the UI is a defect.
107
+ 7. **Verify** — run `REF_ROOT=<reference-repo> scripts/verify-module.sh <workspace-root> <PREFIX> <name> [template]`: asserts the
99
108
  library exists, the `forFeature` carries the chosen `entity` (and template, if given), the route is
100
109
  registered, and the article card shared components are built and exported. Fix and re-run.
101
110
 
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-translations.js "<GROUP GROUP …>" [reference-root]
4
+ *
5
+ * Run from the workspace root (verify-module.sh cd's there). Verifies the
6
+ * module's translation groups in apps/web/public/translations.json:
7
+ * 1. each group exists and is non-empty in BOTH pl and en,
8
+ * 2. every literal GROUP.* key referenced in libs/ or apps/web/src exists
9
+ * in both languages (dead references = defect),
10
+ * 3. with a reference root: every reference key of the group exists in the
11
+ * generated file — missing keys are listed (the module is incomplete).
12
+ * Prints PASS/FAIL lines in the verify-module.sh idiom; exit 1 on any FAIL.
13
+ */
14
+ const fs = require("fs");
15
+
16
+ const [groupsArg, refRoot] = process.argv.slice(2);
17
+ const groups = (groupsArg || "").split(/\s+/).filter(Boolean);
18
+ if (!groups.length) { console.log(" FAIL: no translation groups given"); process.exit(1); }
19
+
20
+ const TRANS = "apps/web/public/translations.json";
21
+ let fail = 0;
22
+ const pass = (m) => console.log(` PASS: ${m}`);
23
+ const bad = (m) => { console.log(` FAIL: ${m}`); fail = 1; };
24
+
25
+ const flat = (obj, prefix) => Object.entries(obj || {}).flatMap(([k, v]) =>
26
+ v && typeof v === "object" ? flat(v, `${prefix}.${k}`) : [`${prefix}.${k}`]);
27
+
28
+ const readTrans = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf8")).data.translations; } catch { return null; } };
29
+
30
+ const gen = readTrans(TRANS);
31
+ if (!gen) { bad(`cannot read ${TRANS}`); process.exit(1); }
32
+
33
+ // 1. groups present + non-empty in both languages
34
+ const genKeys = { pl: new Set(), en: new Set() };
35
+ for (const lang of ["pl", "en"]) {
36
+ for (const g of groups) {
37
+ const keys = flat(gen[lang]?.[g], g);
38
+ keys.forEach((k) => genKeys[lang].add(k));
39
+ if (keys.length) pass(`${g} present in ${lang} (${keys.length} keys)`);
40
+ else bad(`${g} missing or empty in ${lang}`);
41
+ }
42
+ }
43
+
44
+ // 2. dead references — literal GROUP.* keys used in sources must exist.
45
+ // Dynamic tails (`GROUP.${x}`) don't match the literal regex, and a matched
46
+ // PREFIX that is a parent of existing keys counts as present.
47
+ const walk = (dir) => {
48
+ let out = [];
49
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
50
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
51
+ const p = `${dir}/${e.name}`;
52
+ if (e.isDirectory()) out = out.concat(walk(p));
53
+ else if (/\.(ts|html)$/.test(e.name)) out.push(p);
54
+ }
55
+ return out;
56
+ };
57
+ let src = "";
58
+ for (const d of ["libs", "apps/web/src"]) {
59
+ if (fs.existsSync(d)) src += walk(d).map((f) => { try { return fs.readFileSync(f, "utf8"); } catch { return ""; } }).join("\n");
60
+ }
61
+ const re = new RegExp(`\\b(?:${groups.join("|")})(?:\\.[A-Za-z0-9_]+)+`, "g");
62
+ const used = new Set(src.match(re) || []);
63
+ const dead = { pl: [], en: [] };
64
+ for (const key of used) {
65
+ for (const lang of ["pl", "en"]) {
66
+ const ks = genKeys[lang];
67
+ if (!ks.has(key) && ![...ks].some((k) => k.startsWith(`${key}.`))) dead[lang].push(key);
68
+ }
69
+ }
70
+ for (const lang of ["pl", "en"]) {
71
+ if (dead[lang].length) bad(`${lang}: keys referenced in code but missing in ${TRANS}: ${dead[lang].slice(0, 8).join(", ")}${dead[lang].length > 8 ? ` (+${dead[lang].length - 8})` : ""}`);
72
+ else pass(`no dead ${lang} references (${used.size} literal keys checked)`);
73
+ }
74
+
75
+ // 3. completeness vs reference
76
+ if (refRoot) {
77
+ const ref = readTrans(`${refRoot}/apps/web/public/translations.json`);
78
+ if (!ref) bad(`cannot read reference translations under ${refRoot}`);
79
+ else {
80
+ for (const lang of ["pl", "en"]) {
81
+ const missing = groups.flatMap((g) => flat(ref[lang]?.[g], g)).filter((k) => !genKeys[lang].has(k));
82
+ if (missing.length) bad(`${lang}: ${missing.length} reference keys missing: ${missing.slice(0, 8).join(", ")}${missing.length > 8 ? " …" : ""}`);
83
+ else pass(`${lang}: all reference keys covered`);
84
+ }
85
+ }
86
+ } else {
87
+ console.log(" SKIP: no reference root given — completeness vs reference not checked");
88
+ }
89
+
90
+ process.exit(fail);
@@ -11,6 +11,7 @@ ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
11
11
  PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
12
12
  NAME="${3:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
13
13
  TEMPLATE="${4:-}"
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
14
15
  cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
15
16
 
16
17
  LIBF="museum-$NAME"
@@ -99,6 +100,15 @@ else
99
100
  pass "no filters component in this module"
100
101
  fi
101
102
 
103
+ echo "== Translations (REF_ROOT=${REF_ROOT:-unset}) =="
104
+ # Groups: present in pl+en, no dead references, complete vs the reference repo
105
+ # (export REF_ROOT=<reference-repo-root> to enable the completeness diff).
106
+ if node "$SCRIPT_DIR/check-translations.js" "${TRANS_GROUPS:-NEWS ARTICLES}" "${REF_ROOT:-}"; then
107
+ pass "translation groups complete"
108
+ else
109
+ bad "translation gaps — fix the FAIL lines above"
110
+ fi
111
+
102
112
  echo
103
113
  if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
104
114
  exit "$fail"
@@ -50,10 +50,16 @@ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on
50
50
  `@smartsoft001-mobilems/public-collections-shell-angular`,
51
51
  `@smartsoft001-mobilems/public-collections-shell-dtos` (match the installed mobilems line).
52
52
  Resolve versions at run time; do not `npm install` unless asked. No extra third-party viewer deps.
53
- 2. **Library** — create `libs/museum-collections/{domain,shell/angular}` per `references/lib-contents.md`:
54
- copy the domain entity (`MuseumCollection extends PublicCollection`) and the shell
55
- (`MuseumCollectionsModule`, routes, `components/filters`, `pages/{list,item}`, `services`), thread
56
- `<PREFIX>`. Add the two `tsconfig.base.json` paths and the `museum-collections` commit scope.
53
+ 2. **Library** — scaffold `libs/museum-collections` with this package's Nx generator (deterministic — do
54
+ NOT copy files by hand when the generator is available):
55
+ ```bash
56
+ npx nx g @smartsoft001-mobilems/public-collections-shell-angular:museum-module \
57
+ --referenceRoot=<reference-repo> --prefix=<PREFIX>
58
+ ```
59
+ It copies the lib from the reference, threads `@<PREFIX>/`, adds both `tsconfig.base.json`
60
+ paths, normalizes `project.json` paths and copies the module's translation groups (pl+en).
61
+ Fallback (older framework line without the generator): do the same by hand per
62
+ `references/lib-contents.md`. The `museum-collections` commit scope registers automatically from the `libs/` dir name.
57
63
  3. **Module + routes** — see `references/module-and-routes.md`: `MuseumCollectionsModule` wraps
58
64
  `PublicCollectionsSharedModule.forFeature({ routing: false, config: { type: MuseumCollection, apiUrl, entity: 'publicCollections' } })`
59
65
  with the `Masonry` SSR factory and the `CrudService → CollectionsService` provider. Add the lazy
@@ -63,7 +69,11 @@ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on
63
69
  `components/collections-grid-item` (it imports `public-collections-domain`) in the shared lib and
64
70
  export it from `components/index.ts`. Collections contributes no widget-map entry and no model type
65
71
  (those are objects-only).
66
- 5. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib +
72
+ 5. **Translations** — copy the module's translation groups (`COLLECTIONS`) from the reference
73
+ repo's `apps/web/public/translations.json` into the app's file, for BOTH `pl` and `en`.
74
+ Complete group = every reference key present; every key referenced in the module's code
75
+ must exist in both languages — a raw `GROUP.key` rendered in the UI is a defect.
76
+ 6. **Verify** — run `REF_ROOT=<reference-repo> scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib +
67
77
  `MuseumCollection` entity exist, the `forFeature` is wired, the `kolekcje` route is registered
68
78
  (top-level + search child), the `collections-grid-item` component is built and exported, and the
69
79
  three public-collections deps are present. Fix and re-run.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-translations.js "<GROUP GROUP …>" [reference-root]
4
+ *
5
+ * Run from the workspace root (verify-module.sh cd's there). Verifies the
6
+ * module's translation groups in apps/web/public/translations.json:
7
+ * 1. each group exists and is non-empty in BOTH pl and en,
8
+ * 2. every literal GROUP.* key referenced in libs/ or apps/web/src exists
9
+ * in both languages (dead references = defect),
10
+ * 3. with a reference root: every reference key of the group exists in the
11
+ * generated file — missing keys are listed (the module is incomplete).
12
+ * Prints PASS/FAIL lines in the verify-module.sh idiom; exit 1 on any FAIL.
13
+ */
14
+ const fs = require("fs");
15
+
16
+ const [groupsArg, refRoot] = process.argv.slice(2);
17
+ const groups = (groupsArg || "").split(/\s+/).filter(Boolean);
18
+ if (!groups.length) { console.log(" FAIL: no translation groups given"); process.exit(1); }
19
+
20
+ const TRANS = "apps/web/public/translations.json";
21
+ let fail = 0;
22
+ const pass = (m) => console.log(` PASS: ${m}`);
23
+ const bad = (m) => { console.log(` FAIL: ${m}`); fail = 1; };
24
+
25
+ const flat = (obj, prefix) => Object.entries(obj || {}).flatMap(([k, v]) =>
26
+ v && typeof v === "object" ? flat(v, `${prefix}.${k}`) : [`${prefix}.${k}`]);
27
+
28
+ const readTrans = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf8")).data.translations; } catch { return null; } };
29
+
30
+ const gen = readTrans(TRANS);
31
+ if (!gen) { bad(`cannot read ${TRANS}`); process.exit(1); }
32
+
33
+ // 1. groups present + non-empty in both languages
34
+ const genKeys = { pl: new Set(), en: new Set() };
35
+ for (const lang of ["pl", "en"]) {
36
+ for (const g of groups) {
37
+ const keys = flat(gen[lang]?.[g], g);
38
+ keys.forEach((k) => genKeys[lang].add(k));
39
+ if (keys.length) pass(`${g} present in ${lang} (${keys.length} keys)`);
40
+ else bad(`${g} missing or empty in ${lang}`);
41
+ }
42
+ }
43
+
44
+ // 2. dead references — literal GROUP.* keys used in sources must exist.
45
+ // Dynamic tails (`GROUP.${x}`) don't match the literal regex, and a matched
46
+ // PREFIX that is a parent of existing keys counts as present.
47
+ const walk = (dir) => {
48
+ let out = [];
49
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
50
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
51
+ const p = `${dir}/${e.name}`;
52
+ if (e.isDirectory()) out = out.concat(walk(p));
53
+ else if (/\.(ts|html)$/.test(e.name)) out.push(p);
54
+ }
55
+ return out;
56
+ };
57
+ let src = "";
58
+ for (const d of ["libs", "apps/web/src"]) {
59
+ if (fs.existsSync(d)) src += walk(d).map((f) => { try { return fs.readFileSync(f, "utf8"); } catch { return ""; } }).join("\n");
60
+ }
61
+ const re = new RegExp(`\\b(?:${groups.join("|")})(?:\\.[A-Za-z0-9_]+)+`, "g");
62
+ const used = new Set(src.match(re) || []);
63
+ const dead = { pl: [], en: [] };
64
+ for (const key of used) {
65
+ for (const lang of ["pl", "en"]) {
66
+ const ks = genKeys[lang];
67
+ if (!ks.has(key) && ![...ks].some((k) => k.startsWith(`${key}.`))) dead[lang].push(key);
68
+ }
69
+ }
70
+ for (const lang of ["pl", "en"]) {
71
+ if (dead[lang].length) bad(`${lang}: keys referenced in code but missing in ${TRANS}: ${dead[lang].slice(0, 8).join(", ")}${dead[lang].length > 8 ? ` (+${dead[lang].length - 8})` : ""}`);
72
+ else pass(`no dead ${lang} references (${used.size} literal keys checked)`);
73
+ }
74
+
75
+ // 3. completeness vs reference
76
+ if (refRoot) {
77
+ const ref = readTrans(`${refRoot}/apps/web/public/translations.json`);
78
+ if (!ref) bad(`cannot read reference translations under ${refRoot}`);
79
+ else {
80
+ for (const lang of ["pl", "en"]) {
81
+ const missing = groups.flatMap((g) => flat(ref[lang]?.[g], g)).filter((k) => !genKeys[lang].has(k));
82
+ if (missing.length) bad(`${lang}: ${missing.length} reference keys missing: ${missing.slice(0, 8).join(", ")}${missing.length > 8 ? " …" : ""}`);
83
+ else pass(`${lang}: all reference keys covered`);
84
+ }
85
+ }
86
+ } else {
87
+ console.log(" SKIP: no reference root given — completeness vs reference not checked");
88
+ }
89
+
90
+ process.exit(fail);
@@ -9,6 +9,7 @@ set -eu
9
9
 
10
10
  ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX>}"
11
11
  PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX>}"
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
13
  cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
13
14
 
14
15
  fail=0
@@ -59,13 +60,14 @@ if grep -rq "provide: Masonry" "$LIBDIR" 2>/dev/null; then
59
60
  bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
60
61
  fi
61
62
  fi
62
- # 3. a list page must render through the framework grid
63
- LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
64
- if [ -n "$LISTP" ]; then
65
- if grep -rq "smart-mobilems-masonry-grid" $LISTP 2>/dev/null; then
66
- pass "list page renders via <smart-mobilems-masonry-grid>"
63
+ # 3. the ITEM page renders its objects grid through the framework grid — in the
64
+ # reference the collections LIST is a plain grid; masonry lives on the item page
65
+ ITEMP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'item*' 2>/dev/null || true)"
66
+ if [ -n "$ITEMP" ]; then
67
+ if grep -rq "smart-mobilems-masonry-grid" $ITEMP 2>/dev/null; then
68
+ pass "item page renders via <smart-mobilems-masonry-grid>"
67
69
  else
68
- bad "list page does not render <smart-mobilems-masonry-grid> (README.md#masonrygridcomponent)"
70
+ bad "item page does not render <smart-mobilems-masonry-grid> (README.md#masonrygridcomponent)"
69
71
  fi
70
72
  fi
71
73
  # 4. filters must EXTEND the framework base class — `context.` alone proves nothing, and a
@@ -82,6 +84,15 @@ else
82
84
  pass "no filters component in this module"
83
85
  fi
84
86
 
87
+ echo "== Translations (REF_ROOT=${REF_ROOT:-unset}) =="
88
+ # Groups: present in pl+en, no dead references, complete vs the reference repo
89
+ # (export REF_ROOT=<reference-repo-root> to enable the completeness diff).
90
+ if node "$SCRIPT_DIR/check-translations.js" "COLLECTIONS" "${REF_ROOT:-}"; then
91
+ pass "translation groups complete"
92
+ else
93
+ bad "translation gaps — fix the FAIL lines above"
94
+ fi
95
+
85
96
  echo
86
97
  if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
87
98
  exit "$fail"
@@ -51,10 +51,16 @@ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on
51
51
  `@smartsoft001-mobilems/objects-shell-dtos` (match the installed mobilems line) and
52
52
  `online-3d-viewer` (the 3D tab; `leaflet`/`openseadragon`/`swiper` are already foundation deps).
53
53
  Resolve versions at run time; do not `npm install` unless asked.
54
- 2. **Library** — create `libs/museum-objects/{domain,shell/angular}` per `references/lib-contents.md`:
55
- copy the domain entity/enums and the shell (`MuseumObjectsModule`, routes, `components/*`,
56
- `pages/{list,item}`, `services`), thread `<PREFIX>`. Add the two `tsconfig.base.json` paths and the
57
- `museum-objects` commit scope.
54
+ 2. **Library** — scaffold `libs/museum-objects` with this package's Nx generator (deterministic — do
55
+ NOT copy files by hand when the generator is available):
56
+ ```bash
57
+ npx nx g @smartsoft001-mobilems/objects-shell-angular:museum-module \
58
+ --referenceRoot=<reference-repo> --prefix=<PREFIX>
59
+ ```
60
+ It copies the lib from the reference, threads `@<PREFIX>/`, adds both `tsconfig.base.json`
61
+ paths, normalizes `project.json` paths and copies the module's translation groups (pl+en).
62
+ Fallback (older framework line without the generator): do the same by hand per
63
+ `references/lib-contents.md`. The `museum-objects` commit scope registers automatically from the `libs/` dir name.
58
64
  3. **Module + routes** — see `references/module-and-routes.md`: `MuseumObjectsModule` wraps
59
65
  `ObjectsSharedModule.forFeature({ routing: false, config: { type: MsObject, apiUrl, entity: 'objects' } })`
60
66
  with the `Masonry` SSR factory. Add the lazy `obiekty` route to `appRoutes` in
@@ -63,7 +69,11 @@ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on
63
69
  `objects-list-item`, and the object gallery widget (`widget/gallery-object`) in the shared lib +
64
70
  export them; register the object gallery widget in the widget content-type map (`GALLERY_OBJECT`);
65
71
  and add `ISpecificObject` (extends `MsObject`) to `models/objects.model.ts`.
66
- 5. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib exists, the
72
+ 5. **Translations** — copy the module's translation groups (`OBJECTS`, `DETAILS_PAGE`, `GALLERY`) from the reference
73
+ repo's `apps/web/public/translations.json` into the app's file, for BOTH `pl` and `en`.
74
+ Complete group = every reference key present; every key referenced in the module's code
75
+ must exist in both languages — a raw `GROUP.key` rendered in the UI is a defect.
76
+ 6. **Verify** — run `REF_ROOT=<reference-repo> scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib exists, the
67
77
  `obiekty` route is registered (top-level + search child), the objects shared components are built
68
78
  and exported, the widget map registers the object gallery widget, and `ISpecificObject` is present.
69
79
  Fix and re-run.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-translations.js "<GROUP GROUP …>" [reference-root]
4
+ *
5
+ * Run from the workspace root (verify-module.sh cd's there). Verifies the
6
+ * module's translation groups in apps/web/public/translations.json:
7
+ * 1. each group exists and is non-empty in BOTH pl and en,
8
+ * 2. every literal GROUP.* key referenced in libs/ or apps/web/src exists
9
+ * in both languages (dead references = defect),
10
+ * 3. with a reference root: every reference key of the group exists in the
11
+ * generated file — missing keys are listed (the module is incomplete).
12
+ * Prints PASS/FAIL lines in the verify-module.sh idiom; exit 1 on any FAIL.
13
+ */
14
+ const fs = require("fs");
15
+
16
+ const [groupsArg, refRoot] = process.argv.slice(2);
17
+ const groups = (groupsArg || "").split(/\s+/).filter(Boolean);
18
+ if (!groups.length) { console.log(" FAIL: no translation groups given"); process.exit(1); }
19
+
20
+ const TRANS = "apps/web/public/translations.json";
21
+ let fail = 0;
22
+ const pass = (m) => console.log(` PASS: ${m}`);
23
+ const bad = (m) => { console.log(` FAIL: ${m}`); fail = 1; };
24
+
25
+ const flat = (obj, prefix) => Object.entries(obj || {}).flatMap(([k, v]) =>
26
+ v && typeof v === "object" ? flat(v, `${prefix}.${k}`) : [`${prefix}.${k}`]);
27
+
28
+ const readTrans = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf8")).data.translations; } catch { return null; } };
29
+
30
+ const gen = readTrans(TRANS);
31
+ if (!gen) { bad(`cannot read ${TRANS}`); process.exit(1); }
32
+
33
+ // 1. groups present + non-empty in both languages
34
+ const genKeys = { pl: new Set(), en: new Set() };
35
+ for (const lang of ["pl", "en"]) {
36
+ for (const g of groups) {
37
+ const keys = flat(gen[lang]?.[g], g);
38
+ keys.forEach((k) => genKeys[lang].add(k));
39
+ if (keys.length) pass(`${g} present in ${lang} (${keys.length} keys)`);
40
+ else bad(`${g} missing or empty in ${lang}`);
41
+ }
42
+ }
43
+
44
+ // 2. dead references — literal GROUP.* keys used in sources must exist.
45
+ // Dynamic tails (`GROUP.${x}`) don't match the literal regex, and a matched
46
+ // PREFIX that is a parent of existing keys counts as present.
47
+ const walk = (dir) => {
48
+ let out = [];
49
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
50
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
51
+ const p = `${dir}/${e.name}`;
52
+ if (e.isDirectory()) out = out.concat(walk(p));
53
+ else if (/\.(ts|html)$/.test(e.name)) out.push(p);
54
+ }
55
+ return out;
56
+ };
57
+ let src = "";
58
+ for (const d of ["libs", "apps/web/src"]) {
59
+ if (fs.existsSync(d)) src += walk(d).map((f) => { try { return fs.readFileSync(f, "utf8"); } catch { return ""; } }).join("\n");
60
+ }
61
+ const re = new RegExp(`\\b(?:${groups.join("|")})(?:\\.[A-Za-z0-9_]+)+`, "g");
62
+ const used = new Set(src.match(re) || []);
63
+ const dead = { pl: [], en: [] };
64
+ for (const key of used) {
65
+ for (const lang of ["pl", "en"]) {
66
+ const ks = genKeys[lang];
67
+ if (!ks.has(key) && ![...ks].some((k) => k.startsWith(`${key}.`))) dead[lang].push(key);
68
+ }
69
+ }
70
+ for (const lang of ["pl", "en"]) {
71
+ if (dead[lang].length) bad(`${lang}: keys referenced in code but missing in ${TRANS}: ${dead[lang].slice(0, 8).join(", ")}${dead[lang].length > 8 ? ` (+${dead[lang].length - 8})` : ""}`);
72
+ else pass(`no dead ${lang} references (${used.size} literal keys checked)`);
73
+ }
74
+
75
+ // 3. completeness vs reference
76
+ if (refRoot) {
77
+ const ref = readTrans(`${refRoot}/apps/web/public/translations.json`);
78
+ if (!ref) bad(`cannot read reference translations under ${refRoot}`);
79
+ else {
80
+ for (const lang of ["pl", "en"]) {
81
+ const missing = groups.flatMap((g) => flat(ref[lang]?.[g], g)).filter((k) => !genKeys[lang].has(k));
82
+ if (missing.length) bad(`${lang}: ${missing.length} reference keys missing: ${missing.slice(0, 8).join(", ")}${missing.length > 8 ? " …" : ""}`);
83
+ else pass(`${lang}: all reference keys covered`);
84
+ }
85
+ }
86
+ } else {
87
+ console.log(" SKIP: no reference root given — completeness vs reference not checked");
88
+ }
89
+
90
+ process.exit(fail);
@@ -8,6 +8,7 @@ set -eu
8
8
 
9
9
  ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX>}"
10
10
  PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX>}"
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11
12
  cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
12
13
 
13
14
  fail=0
@@ -89,6 +90,15 @@ else
89
90
  pass "no filters component in this module"
90
91
  fi
91
92
 
93
+ echo "== Translations (REF_ROOT=${REF_ROOT:-unset}) =="
94
+ # Groups: present in pl+en, no dead references, complete vs the reference repo
95
+ # (export REF_ROOT=<reference-repo-root> to enable the completeness diff).
96
+ if node "$SCRIPT_DIR/check-translations.js" "OBJECTS DETAILS_PAGE GALLERY" "${REF_ROOT:-}"; then
97
+ pass "translation groups complete"
98
+ else
99
+ bad "translation gaps — fix the FAIL lines above"
100
+ fi
101
+
92
102
  echo
93
103
  if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
94
104
  exit "$fail"
@@ -72,7 +72,11 @@ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on
72
72
  `partnerzy` route to the `appRoutes` array in `apps/web/src/app/app.routes.ts` as a top-level
73
73
  route, and a matching child under the `wyszukaj/:text` search route (partners appears in site
74
74
  search).
75
- 4. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib + entity
75
+ 4. **Translations** — copy the module's translation groups (`PARTNERS`) from the reference
76
+ repo's `apps/web/public/translations.json` into the app's file, for BOTH `pl` and `en`.
77
+ Complete group = every reference key present; every key referenced in the module's code
78
+ must exist in both languages — a raw `GROUP.key` rendered in the UI is a defect.
79
+ 5. **Verify** — run `REF_ROOT=<reference-repo> scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib + entity
76
80
  exist, both tsconfig paths are added, `ArticlesSharedModule.forFeature` is wired with
77
81
  `entity: 'partners'` + `reducerFactory: getReducer`, the `+state/partners.reducer.ts` exports
78
82
  `getReducer`, the `components/{grid-item,list-item}` cards are present in the partners lib, the
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-translations.js "<GROUP GROUP …>" [reference-root]
4
+ *
5
+ * Run from the workspace root (verify-module.sh cd's there). Verifies the
6
+ * module's translation groups in apps/web/public/translations.json:
7
+ * 1. each group exists and is non-empty in BOTH pl and en,
8
+ * 2. every literal GROUP.* key referenced in libs/ or apps/web/src exists
9
+ * in both languages (dead references = defect),
10
+ * 3. with a reference root: every reference key of the group exists in the
11
+ * generated file — missing keys are listed (the module is incomplete).
12
+ * Prints PASS/FAIL lines in the verify-module.sh idiom; exit 1 on any FAIL.
13
+ */
14
+ const fs = require("fs");
15
+
16
+ const [groupsArg, refRoot] = process.argv.slice(2);
17
+ const groups = (groupsArg || "").split(/\s+/).filter(Boolean);
18
+ if (!groups.length) { console.log(" FAIL: no translation groups given"); process.exit(1); }
19
+
20
+ const TRANS = "apps/web/public/translations.json";
21
+ let fail = 0;
22
+ const pass = (m) => console.log(` PASS: ${m}`);
23
+ const bad = (m) => { console.log(` FAIL: ${m}`); fail = 1; };
24
+
25
+ const flat = (obj, prefix) => Object.entries(obj || {}).flatMap(([k, v]) =>
26
+ v && typeof v === "object" ? flat(v, `${prefix}.${k}`) : [`${prefix}.${k}`]);
27
+
28
+ const readTrans = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf8")).data.translations; } catch { return null; } };
29
+
30
+ const gen = readTrans(TRANS);
31
+ if (!gen) { bad(`cannot read ${TRANS}`); process.exit(1); }
32
+
33
+ // 1. groups present + non-empty in both languages
34
+ const genKeys = { pl: new Set(), en: new Set() };
35
+ for (const lang of ["pl", "en"]) {
36
+ for (const g of groups) {
37
+ const keys = flat(gen[lang]?.[g], g);
38
+ keys.forEach((k) => genKeys[lang].add(k));
39
+ if (keys.length) pass(`${g} present in ${lang} (${keys.length} keys)`);
40
+ else bad(`${g} missing or empty in ${lang}`);
41
+ }
42
+ }
43
+
44
+ // 2. dead references — literal GROUP.* keys used in sources must exist.
45
+ // Dynamic tails (`GROUP.${x}`) don't match the literal regex, and a matched
46
+ // PREFIX that is a parent of existing keys counts as present.
47
+ const walk = (dir) => {
48
+ let out = [];
49
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
50
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
51
+ const p = `${dir}/${e.name}`;
52
+ if (e.isDirectory()) out = out.concat(walk(p));
53
+ else if (/\.(ts|html)$/.test(e.name)) out.push(p);
54
+ }
55
+ return out;
56
+ };
57
+ let src = "";
58
+ for (const d of ["libs", "apps/web/src"]) {
59
+ if (fs.existsSync(d)) src += walk(d).map((f) => { try { return fs.readFileSync(f, "utf8"); } catch { return ""; } }).join("\n");
60
+ }
61
+ const re = new RegExp(`\\b(?:${groups.join("|")})(?:\\.[A-Za-z0-9_]+)+`, "g");
62
+ const used = new Set(src.match(re) || []);
63
+ const dead = { pl: [], en: [] };
64
+ for (const key of used) {
65
+ for (const lang of ["pl", "en"]) {
66
+ const ks = genKeys[lang];
67
+ if (!ks.has(key) && ![...ks].some((k) => k.startsWith(`${key}.`))) dead[lang].push(key);
68
+ }
69
+ }
70
+ for (const lang of ["pl", "en"]) {
71
+ if (dead[lang].length) bad(`${lang}: keys referenced in code but missing in ${TRANS}: ${dead[lang].slice(0, 8).join(", ")}${dead[lang].length > 8 ? ` (+${dead[lang].length - 8})` : ""}`);
72
+ else pass(`no dead ${lang} references (${used.size} literal keys checked)`);
73
+ }
74
+
75
+ // 3. completeness vs reference
76
+ if (refRoot) {
77
+ const ref = readTrans(`${refRoot}/apps/web/public/translations.json`);
78
+ if (!ref) bad(`cannot read reference translations under ${refRoot}`);
79
+ else {
80
+ for (const lang of ["pl", "en"]) {
81
+ const missing = groups.flatMap((g) => flat(ref[lang]?.[g], g)).filter((k) => !genKeys[lang].has(k));
82
+ if (missing.length) bad(`${lang}: ${missing.length} reference keys missing: ${missing.slice(0, 8).join(", ")}${missing.length > 8 ? " …" : ""}`);
83
+ else pass(`${lang}: all reference keys covered`);
84
+ }
85
+ }
86
+ } else {
87
+ console.log(" SKIP: no reference root given — completeness vs reference not checked");
88
+ }
89
+
90
+ process.exit(fail);
@@ -10,6 +10,7 @@ set -eu
10
10
 
11
11
  ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX>}"
12
12
  PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX>}"
13
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
14
  cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
14
15
 
15
16
  fail=0
@@ -97,6 +98,15 @@ else
97
98
  pass "no filters component in this module"
98
99
  fi
99
100
 
101
+ echo "== Translations (REF_ROOT=${REF_ROOT:-unset}) =="
102
+ # Groups: present in pl+en, no dead references, complete vs the reference repo
103
+ # (export REF_ROOT=<reference-repo-root> to enable the completeness diff).
104
+ if node "$SCRIPT_DIR/check-translations.js" "PARTNERS" "${REF_ROOT:-}"; then
105
+ pass "translation groups complete"
106
+ else
107
+ bad "translation gaps — fix the FAIL lines above"
108
+ fi
109
+
100
110
  echo
101
111
  if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
102
112
  exit "$fail"