@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 +1 -1
- package/plugins/mobilems/.claude-plugin/plugin.json +1 -1
- package/plugins/mobilems/skills/mobilems-articles/SKILL.md +15 -6
- package/plugins/mobilems/skills/mobilems-articles/scripts/check-translations.js +90 -0
- package/plugins/mobilems/skills/mobilems-articles/scripts/verify-module.sh +10 -0
- package/plugins/mobilems/skills/mobilems-object-collections/SKILL.md +15 -5
- package/plugins/mobilems/skills/mobilems-object-collections/scripts/check-translations.js +90 -0
- package/plugins/mobilems/skills/mobilems-object-collections/scripts/verify-module.sh +17 -6
- package/plugins/mobilems/skills/mobilems-objects/SKILL.md +15 -5
- package/plugins/mobilems/skills/mobilems-objects/scripts/check-translations.js +90 -0
- package/plugins/mobilems/skills/mobilems-objects/scripts/verify-module.sh +10 -0
- package/plugins/mobilems/skills/mobilems-partners/SKILL.md +5 -1
- package/plugins/mobilems/skills/mobilems-partners/scripts/check-translations.js +90 -0
- package/plugins/mobilems/skills/mobilems-partners/scripts/verify-module.sh +10 -0
package/package.json
CHANGED
|
@@ -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** —
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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. **
|
|
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** —
|
|
54
|
-
copy
|
|
55
|
-
|
|
56
|
-
|
|
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. **
|
|
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.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 "
|
|
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** —
|
|
55
|
-
copy
|
|
56
|
-
|
|
57
|
-
|
|
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. **
|
|
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. **
|
|
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"
|