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.
153 lines (136 loc) • 5.53 kB
JavaScript
/*
Auto-translates EN wiki pages into IT, DE, FR, ES, 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: 'fr', prefix: 'fr-', lang: 'fr' },
{ code: 'es', prefix: 'es-', lang: 'es' },
{ code: 'zh-CN', prefix: 'zh-CN-', lang: 'zh-CN' }
]
const LANG_BAR_ENTRIES = [
{ label: 'EN', prefix: '' },
{ label: 'IT', prefix: 'it-' },
{ label: 'DE', prefix: 'de-' },
{ label: 'FR', prefix: 'fr-' },
{ label: 'ES', prefix: 'es-' },
{ label: '简体中文', prefix: '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('fr-') || base.startsWith('es-') || 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 parts = LANG_BAR_ENTRIES.map(({ label, prefix }) => {
const slug = prefix ? prefix + slugEN : slugEN
return `[${label}](${ABS}${slug})`
})
return `🌐 Language: ${parts.join(' | ')}`
}
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; let skipped = 0; let 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
const 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()