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
JavaScript
/*
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();