UNPKG

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.

142 lines (125 loc) â€ĸ 5.21 kB
#!/usr/bin/env node /* Auto-translates EN wiki pages into IT, DE, zh-CN variants and creates missing files. - Detects base English pages (no it-/de-/zh-CN- prefix) - Skips special files (_Sidebar.md, _Footer.md) and samples/ - Preserves language bar (writes absolute links for all languages) - Skips translation inside code blocks and preserves URLs inside parentheses - After generation, run: npm run wiki:inject-header (to add the localized header) */ const fs = require('fs'); const path = require('path'); const translate = require('translate-google'); const ROOT = process.cwd(); const WIKI_DIR = path.resolve(ROOT, '..', 'node-red-contrib-knx-ultimate.wiki'); const ABS = 'https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/'; const TARGETS = [ { code: 'it', prefix: 'it-', lang: 'it' }, { code: 'de', prefix: 'de-', lang: 'de' }, { code: 'zh-CN', prefix: 'zh-CN-', lang: 'zh-CN' }, ]; function listMarkdown(dir) { const out = []; for (const e of fs.readdirSync(dir, { withFileTypes: true })) { const p = path.join(dir, e.name); if (e.isDirectory()) out.push(...listMarkdown(p)); else if (e.isFile() && e.name.endsWith('.md')) out.push(p); } return out; } function shouldSkipBase(file) { const rel = path.relative(WIKI_DIR, file); if (rel.startsWith('samples/')) return true; const base = path.basename(file); if (base.startsWith('it-') || base.startsWith('de-') || base.startsWith('zh-CN-')) return true; if (base === '_Sidebar.md' || base === '_Footer.md') return true; return false; } function slugify(title) { return title.replace(/ /g, '+'); } function langBarLine(baseTitle) { const slugEN = slugify(baseTitle); const slugIT = 'it-' + slugEN; const slugDE = 'de-' + slugEN; const slugZH = 'zh-CN-' + slugEN; return `🌐 Language: [EN](${ABS}${slugEN}) | [IT](${ABS}${slugIT}) | [DE](${ABS}${slugDE}) | [įŽ€äŊ“中文](${ABS}${slugZH})`; } function deriveBaseTitle(filepath) { return path.basename(filepath, '.md'); } function splitByCodeBlocks(text) { // Split by triple backticks, keep the delimiters const parts = text.split(/(```[\s\S]*?```)/g); return parts.map(part => ({ text: part, code: part.startsWith('```') })); } function protectUrls(s) { const urls = []; const replaced = s.replace(/\((https?:[^)]+)\)/g, (m, url) => { const token = `__URL_${urls.length}__`; urls.push(url); return `(${token})`; }); return { replaced, urls }; } function restoreUrls(s, urls) { return s.replace(/__URL_(\d+)__/g, (_, i) => urls[Number(i)]); } async function translateBody(src, toLang) { // Translate non-code parts, preserve URLs const blocks = splitByCodeBlocks(src); const out = []; for (const b of blocks) { if (b.code) { out.push(b.text); continue; } const { replaced, urls } = protectUrls(b.text); if (!replaced.trim()) { out.push(b.text); continue; } const t = await translate(replaced, { to: toLang }); out.push(restoreUrls(t, urls)); } return out.join(''); } async function run() { const files = listMarkdown(WIKI_DIR).filter(f => !shouldSkipBase(f)); let created = 0, skipped = 0, errors = 0; for (const f of files) { try { const baseTitle = deriveBaseTitle(f); // Compute target filenames const targets = TARGETS.map(t => ({ ...t, file: path.join(path.dirname(f), `${t.prefix}${baseTitle}.md`) })); const missing = targets.filter(t => !fs.existsSync(t.file)); if (missing.length === 0) { skipped++; continue; } const raw = fs.readFileSync(f, 'utf8'); const lines = raw.split(/\r?\n/); // remove existing header/nav from source if present let start = 0; // Ensure we skip the first line (we regenerate language bar per target) if (lines[0] && lines[0].startsWith('🌐 Language:')) start = 1; // Remove any NAV block at the top const navStart = lines.findIndex(l => l.trim() === '<!-- NAV START -->'); const navEnd = lines.findIndex(l => l.trim() === '<!-- NAV END -->'); let bodyLines = lines.slice(start); if (navStart !== -1 && navEnd !== -1 && navEnd > navStart) { // remove the block and any immediate '---' below if duplicate present later bodyLines = lines.slice(navEnd + 1); } // Ensure we keep single separator '---' at the top of body let body = bodyLines.join('\n'); // If body starts with --- keep it; otherwise add after translation phase const startsWithSep = body.trimStart().startsWith('---'); for (const t of missing) { const toLang = t.lang; const translated = await translateBody(body, toLang); const outLines = []; outLines.push(langBarLine(baseTitle)); if (!startsWithSep) outLines.push('---'); outLines.push(translated.replace(/^\n+/, '')); fs.writeFileSync(t.file, outLines.join('\n'), 'utf8'); created++; } } catch (e) { console.error('Error translating', f, e.message); errors++; } } console.log(`Created ${created} pages. Skipped (already present): ${skipped}. Errors: ${errors}.`); console.log('Next: npm run wiki:inject-header to add localized headers.'); } run();