node-red-contrib-knx-ultimate
Version:
Control your KNX and KNX Secure intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.
188 lines (166 loc) • 7.22 kB
JavaScript
/*
Interactive helper to add a page link to the header menu config (wiki-menu.json).
- Prompts for base English page title (exact wiki page title, e.g., "Quick-Start")
- Prompts for target section (existing or new)
- Optionally creates a new section with labels per language
- Optionally customizes item labels per language (otherwise uses EN for all)
- Writes scripts/wiki-menu.json; then run: npm run wiki:inject-header
*/
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const translate = require('translate-google');
// Paths
const REPO = process.cwd();
const WIKI_DIR = path.resolve(REPO, '..', 'node-red-contrib-knx-ultimate.wiki');
const NODES_DIR = path.join(REPO, 'nodes');
const CFG_PATH = path.join(__dirname, 'wiki-menu.json');
function loadCfg() {
return JSON.parse(fs.readFileSync(CFG_PATH, 'utf8'));
}
function saveCfg(cfg) {
fs.writeFileSync(CFG_PATH, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
}
function ask(rl, q) {
return new Promise(res => rl.question(q, a => res(a.trim())));
}
async function run() {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const cfg = loadCfg();
console.log('Add page to wiki menu');
const page = await ask(rl, 'Page title (EN, exact wiki title, e.g., "Quick-Start"): ');
if (!page) { rl.close(); return; }
console.log('\nChoose section:');
cfg.sections.forEach((s, i) => console.log(` ${i+1}) ${s.labels.en} [key=${s.key}]`));
console.log(` n) New section`);
const sel = await ask(rl, 'Your choice: ');
let section;
if (sel.toLowerCase() === 'n') {
const key = await ask(rl, 'New section key (e.g., gettingStarted): ');
const en = await ask(rl, 'New section label EN: ');
// If other labels are left empty, auto-translate from EN
let it = await ask(rl, 'New section label IT (leave empty to auto-translate): ');
let de = await ask(rl, 'New section label DE (leave empty to auto-translate): ');
let zh = await ask(rl, 'New section label ZH (leave empty to auto-translate): ');
const auto = async (val, lang) => val || await translate(en, { to: lang }).catch(() => en);
it = await auto(it, 'it');
de = await auto(de, 'de');
zh = await auto(zh, 'zh-CN');
section = { key, labels: { en, it, de, zh }, items: [] };
cfg.sections.push(section);
} else {
const idx = parseInt(sel, 10) - 1;
if (isNaN(idx) || idx < 0 || idx >= cfg.sections.length) {
console.error('Invalid selection.');
rl.close();
return;
}
section = cfg.sections[idx];
}
const custom = (await ask(rl, 'Custom labels per language? (y/N): ')).toLowerCase() === 'y';
let labels = { en: page, it: page, de: page, zh: page };
if (custom) {
const enLabel = await ask(rl, 'Label EN: ');
let itLabel = await ask(rl, 'Label IT (leave empty to auto-translate): ');
let deLabel = await ask(rl, 'Label DE (leave empty to auto-translate): ');
let zhLabel = await ask(rl, 'Label ZH (leave empty to auto-translate): ');
const auto = async (val, lang) => val || await translate(enLabel || page, { to: lang }).catch(() => enLabel || page);
itLabel = await auto(itLabel, 'it');
deLabel = await auto(deLabel, 'de');
zhLabel = await auto(zhLabel, 'zh-CN');
labels = { en: enLabel || page, it: itLabel, de: deLabel, zh: zhLabel };
}
// Avoid duplicates
if (section.items.some(i => i.type === 'page' && i.page === page)) {
console.log('Item already exists in that section.');
rl.close();
return;
}
section.items.push({ type: 'page', labels, page });
saveCfg(cfg);
rl.close();
console.log('Saved. Now run: npm run wiki:inject-header');
try {
// Also generate localized node help files, if this wiki page maps to a node help
generateLocalizedHelpForPage(page);
} catch (e) {
console.warn('Help generation skipped:', e.message);
}
}
run();
// ---- Helpers to generate localized help files from wiki pages ----
// Map node help-name -> wiki page title
const HELP_TO_WIKI = new Map([
['hue-config', 'HUE Bridge configuration'],
['knxUltimate-config', 'Gateway-configuration'],
['knxUltimate', 'KNX Node Configuration'],
['knxUltimateLogger', 'Logger-Configuration'],
['knxUltimateGlobalContext', 'GlobalVariable'],
['knxUltimateWatchDog', 'WatchDog-Configuration'],
['knxUltimateAlerter', 'Alerter-Configuration'],
['knxUltimateLoadControl', 'LoadControl-Configuration'],
['knxUltimateSceneController', 'SceneController-Configuration'],
['knxUltimateViewer', 'knxUltimateViewer'],
['knxUltimateAutoResponder', 'KNXAutoResponder'],
['knxUltimateHATranslator', 'HATranslator'],
['knxUltimateHueLight', 'HUE Light'],
['knxUltimateHueBattery', 'HUE Battery'],
['knxUltimateHueButton', 'HUE Button'],
['knxUltimateHueContactSensor', 'HUE Contact sensor'],
['knxUltimateHuedevice_software_update', 'HUE Device software update'],
['knxUltimateHueLightSensor', 'HUE Light sensor'],
['knxUltimateHueMotion', 'HUE Motion'],
['knxUltimateHueScene', 'HUE Scene'],
['knxUltimateHueTapDial', 'HUE Tapdial'],
['knxUltimateHueTemperatureSensor', 'HUE Temperature sensor'],
['knxUltimateHueZigbeeConnectivity', 'HUE Zigbee connectivity'],
['knxUltimateHueContactSensor', 'HUE Contact sensor'],
['knxUltimateGarageDoorBarrierOpener', null], // no wiki page
]);
// Build inverse map: wiki title -> help-name
const WIKI_TO_HELP = (() => {
const m = new Map();
for (const [help, wiki] of HELP_TO_WIKI.entries()) {
if (wiki) m.set(wiki, help);
}
return m;
})();
const LANGS = [
{ key: 'en', dir: 'en-US', prefix: '' },
{ key: 'it', dir: 'it-IT', prefix: 'it-' },
{ key: 'de', dir: 'de-DE', prefix: 'de-' },
{ key: 'zh', dir: 'zh-CN', prefix: 'zh-CN-' },
];
function extractWikiContent(md) {
if (!md) return null;
const lines = md.split(/\r?\n/);
let i = 0;
if (lines[i] && lines[i].startsWith('🌐 Language:')) i++;
while (i < lines.length && lines[i].trim() !== '---') i++;
if (i < lines.length && lines[i].trim() === '---') i++;
return lines.slice(i).join('\n').trim();
}
function readFileSafe(p) { try { return fs.readFileSync(p, 'utf8'); } catch { return null; } }
function writeLocaleHelp(destDir, helpName, content) {
if (!content) return;
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
const out = `<script type="text/markdown" data-help-name="${helpName}">\n${content}\n</script>\n`;
fs.writeFileSync(path.join(destDir, `${helpName}.html`), out, 'utf8');
}
function generateLocalizedHelpForPage(wikiTitle) {
const helpName = WIKI_TO_HELP.get(wikiTitle);
if (!helpName) {
console.log(`No node help mapping for wiki page: ${wikiTitle}`);
return;
}
for (const lang of LANGS) {
const wikiFile = path.join(WIKI_DIR, `${lang.prefix}${wikiTitle}.md`);
const md = readFileSafe(wikiFile);
const content = extractWikiContent(md || '');
if (!content) continue;
const destDir = path.join(NODES_DIR, 'locales', lang.dir);
writeLocaleHelp(destDir, helpName, content);
}
console.log(`Localized help updated for node '${helpName}' from wiki page '${wikiTitle}'.`);
}