diff --git a/docs/marketing/beta-flyer.da.html b/docs/marketing/beta-flyer.da.html index efee303..c67da4a 100644 --- a/docs/marketing/beta-flyer.da.html +++ b/docs/marketing/beta-flyer.da.html @@ -5,46 +5,46 @@ Kapteins Daagbok - Beta-flyer @@ -314,7 +314,7 @@ også offline kan bruges til søs.

-
+
Rejsedage i nautisk logbogsformat (havn, vejr, sejl, besætning, brændstofniveauer)
Offline-kompatibel PWA - kører på enhver smartphone og tablet
Simpelt login uden adgangskode Passkey.
@@ -328,22 +328,22 @@
Krypteret sikkerhedskopiering og gendannelse
Del logbog med venner
Et vilkårligt antal skibe og logbøger
-
Dansk&Deutsch&Engelsk
+
Deutsch·English·Dansk·Svenska·Norsk
3 temaer, hver med en lys og en mørk variant
Fremstillet i Kiel.Sailing.City..
-
+
- Anmeldung mit Passkey und Demo + Registrering med Passkey og demo
Registrering & Passkey
- Logbuch-Journal mit Reisetagen + Logbogsdagbog med rejsedage
Logbogsdagbog
- Schiffs-Stammdaten mit Yachtfoto + Skibsdata med foto af yacht
Skibsdata
@@ -359,7 +359,7 @@
- QR-Code: kapteins-daagbok.eu + QR-kode: kapteins-daagbok.eu

kapteins-daagbok.eu

@@ -374,7 +374,7 @@
Påtryk
- KnorrLabs - Markus F.J. Busche - Knorrstr. 16 · 24106 Kiel - elpatron+kd@mailbox.org. + KnorrLabs · Markus F.J. Busche · Knorrstr. 16 · 24106 Kiel · elpatron+kd@mailbox.org
diff --git a/docs/marketing/beta-flyer.nb.html b/docs/marketing/beta-flyer.nb.html index 1379934..43a9f07 100644 --- a/docs/marketing/beta-flyer.nb.html +++ b/docs/marketing/beta-flyer.nb.html @@ -5,45 +5,45 @@ Kapteins Daagbok - Beta-flygeblad @@ -309,12 +309,12 @@

- Før loggboken om bord digitalt: reisedager, GPS-spor, mannskaps- og skipsdata + Oppbevar loggboken om bord digitalt: reisedager, GPS-spor, mannskaps- og skipsdata Ende-til-ende-kryptertkan installeres som en app og også offline kan brukes til sjøs.

-
+
Reisedager i nautisk loggbokformat (havn, vær, seil, mannskap, drivstoffnivå)
Offline-kompatibel PWA - kjører på alle smarttelefoner og nettbrett
Enkel passordfri Passkey-pålogging
@@ -328,22 +328,22 @@
Kryptert sikkerhetskopiering og gjenoppretting
Del loggboken med venner
Et hvilket som helst antall skip og loggbøker
-
Norsk&Deutsch&Engelsk
+
Deutsch·English·Dansk·Svenska·Norsk
3 temaer, hvert med en lys og en mørk variant
Laget i Kiel.Sailing.City..
-
+
- Anmeldung mit Passkey und Demo + Registrering med Passkey og demo
Registrering & Passkey
- Logbuch-Journal mit Reisetagen + Loggbok med reisedager
Loggbokdagbok
- Schiffs-Stammdaten mit Yachtfoto + Skipsdata med bilde av båten
Skipsdata
@@ -359,14 +359,14 @@
- QR-Code: kapteins-daagbok.eu + QR-kode: kapteins-daagbok.eu

kapteins-daagbok.eu

Åpne i nettleseren eller legg til som en app på startskjermen. Registrer deg med Passkey - ingen appbutikk er nødvendig.

Kostnadsfritt - Reklame gratis + Gratis annonsering E2E-kryptert
@@ -374,7 +374,7 @@
Avtrykk
- KnorrLabs - Markus F.J. Busche - Knorrstr. 16 · 24106 Kiel - elpatron+kd@mailbox.org - Knorrstr. 16 · 24106 Kiel - elpatron+kd@mailbox.org + KnorrLabs · Markus F.J. Busche · Knorrstr. 16 · 24106 Kiel · elpatron+kd@mailbox.org
diff --git a/docs/marketing/beta-flyer.sv.html b/docs/marketing/beta-flyer.sv.html index ef13beb..710185f 100644 --- a/docs/marketing/beta-flyer.sv.html +++ b/docs/marketing/beta-flyer.sv.html @@ -5,295 +5,295 @@ Kapteins Daagbok - Beta-flygblad @@ -314,7 +314,7 @@ också offline användbar till sjöss.

-
+
Resdagar i nautiskt loggboksformat (hamn, väder, segel, besättning, bränslenivåer)
Offline-kompatibel PWA - körs på alla smartphones och surfplattor
Enkel lösenordsfri Passkey-inloggning
@@ -328,22 +328,22 @@
Krypterad säkerhetskopiering och återställning
Dela loggbok med vänner
Valfritt antal fartyg och loggböcker
-
Svenska&Deutsch&Engelsk
+
Deutsch·English·Dansk·Svenska·Norsk
3 teman, vart och ett med en ljus och en mörk variant
Tillverkad i Kiel.Sailing.City..
-
+
- Anmeldung mit Passkey und Demo + Registrering med Passkey och demo
Registrering & Passkey
- Logbuch-Journal mit Reisetagen + Loggboksdagbok med resedagar
Loggboksjournal
- Schiffs-Stammdaten mit Yachtfoto + Fartygsdata med foto av yacht
Fartygsdata
@@ -359,7 +359,7 @@
- QR-Code: kapteins-daagbok.eu + QR-kod: kapteins-daagbok.eu

kapteins-daagbok.eu

@@ -374,7 +374,7 @@
Avtryck
- KnorrLabs - Markus F.J. Busche - Knorrstr. 16 · 24106 Kiel - elpatron+kd@mailbox.org + KnorrLabs · Markus F.J. Busche · Knorrstr. 16 · 24106 Kiel · elpatron+kd@mailbox.org
diff --git a/docs/marketing/kapteins-daagbok-beta-flyer.da.pdf b/docs/marketing/kapteins-daagbok-beta-flyer.da.pdf index 8a249fb..2c334c4 100644 Binary files a/docs/marketing/kapteins-daagbok-beta-flyer.da.pdf and b/docs/marketing/kapteins-daagbok-beta-flyer.da.pdf differ diff --git a/docs/marketing/kapteins-daagbok-beta-flyer.da.png b/docs/marketing/kapteins-daagbok-beta-flyer.da.png index 31fbe54..f3be0ac 100644 Binary files a/docs/marketing/kapteins-daagbok-beta-flyer.da.png and b/docs/marketing/kapteins-daagbok-beta-flyer.da.png differ diff --git a/docs/marketing/kapteins-daagbok-beta-flyer.nb.pdf b/docs/marketing/kapteins-daagbok-beta-flyer.nb.pdf index feecb98..94f5e71 100644 Binary files a/docs/marketing/kapteins-daagbok-beta-flyer.nb.pdf and b/docs/marketing/kapteins-daagbok-beta-flyer.nb.pdf differ diff --git a/docs/marketing/kapteins-daagbok-beta-flyer.nb.png b/docs/marketing/kapteins-daagbok-beta-flyer.nb.png index b2b6188..8722886 100644 Binary files a/docs/marketing/kapteins-daagbok-beta-flyer.nb.png and b/docs/marketing/kapteins-daagbok-beta-flyer.nb.png differ diff --git a/docs/marketing/kapteins-daagbok-beta-flyer.pdf b/docs/marketing/kapteins-daagbok-beta-flyer.pdf index 9d6256a..d705281 100644 Binary files a/docs/marketing/kapteins-daagbok-beta-flyer.pdf and b/docs/marketing/kapteins-daagbok-beta-flyer.pdf differ diff --git a/docs/marketing/kapteins-daagbok-beta-flyer.sv.pdf b/docs/marketing/kapteins-daagbok-beta-flyer.sv.pdf index 2c1e66b..e7c9a3a 100644 Binary files a/docs/marketing/kapteins-daagbok-beta-flyer.sv.pdf and b/docs/marketing/kapteins-daagbok-beta-flyer.sv.pdf differ diff --git a/docs/marketing/kapteins-daagbok-beta-flyer.sv.png b/docs/marketing/kapteins-daagbok-beta-flyer.sv.png index 0ace2d1..a6a62d4 100644 Binary files a/docs/marketing/kapteins-daagbok-beta-flyer.sv.png and b/docs/marketing/kapteins-daagbok-beta-flyer.sv.png differ diff --git a/scripts/lib/deepl-translate.mjs b/scripts/lib/deepl-translate.mjs index a744030..5dd0955 100644 --- a/scripts/lib/deepl-translate.mjs +++ b/scripts/lib/deepl-translate.mjs @@ -31,7 +31,8 @@ export const NO_TRANSLATE_TERMS = [ 'iPad', 'iPhone', 'Android', - 'Knorrstr. 16 · 24106 Kiel' + 'Knorrstr. 16 · 24106 Kiel', + 'KnorrLabs · Markus F.J. Busche · Knorrstr. 16 · 24106 Kiel · elpatron+kd@mailbox.org' ] const PLACEHOLDER_RE = /\{\{[^}]+\}\}/g diff --git a/scripts/translate-flyer.mjs b/scripts/translate-flyer.mjs index 85b51d9..48faa83 100644 --- a/scripts/translate-flyer.mjs +++ b/scripts/translate-flyer.mjs @@ -1,6 +1,7 @@ #!/usr/bin/env node /** - * Generate localized beta flyer HTML files from the German master via DeepL. + * Generate localized beta flyer HTML from the German master via DeepL. + * Only visible body text and are translated — CSS/HTML structure stay intact. * * Usage: node scripts/translate-flyer.mjs [--lang da,sv,nb] */ @@ -20,43 +21,89 @@ const TARGETS = { nb: { code: 'NB', htmlLang: 'nb', file: 'beta-flyer.nb.html' } } -/** Extract translatable text segments from HTML (text nodes only). */ -function extractSegments(html) { +const LANG_LIST_BLOCK = `<span class="lang-list"><span class="lang-item"><svg class="feature-flag" viewBox="0 0 5 3" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect width="5" height="1" fill="#000"/><rect y="1" width="5" height="1" fill="#D00"/><rect y="2" width="5" height="1" fill="#FFCE00"/></svg>Deutsch</span><span class="lang-sep">·</span><span class="lang-item"><svg class="feature-flag" viewBox="0 0 60 30" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><clipPath id="gb-a"><path d="M0 0v30h60V0z"/></clipPath><clipPath id="gb-b"><path d="M30 15h30v15zv15H0z"/></clipPath><g clip-path="url(#gb-a)"><path d="M0 0v30h60V0z" fill="#012169"/><path d="M0 0l60 30m0-30L0 30" stroke="#fff" stroke-width="6"/><path d="M0 0l60 30m0-30L0 30" clip-path="url(#gb-b)" stroke="#C8102E" stroke-width="4"/><path d="M30 0v30M0 15h60" stroke="#fff" stroke-width="10"/><path d="M30 0v30M0 15h60" stroke="#C8102E" stroke-width="6"/></g></svg>English</span><span class="lang-sep">·</span><span class="lang-item"><svg class="feature-flag" viewBox="0 0 37 28" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect width="37" height="28" fill="#C8102E"/><rect x="12" width="4" height="28" fill="#fff"/><rect y="12" width="37" height="4" fill="#fff"/></svg>Dansk</span><span class="lang-sep">·</span><span class="lang-item"><svg class="feature-flag" viewBox="0 0 16 10" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect width="16" height="10" fill="#006AA7"/><rect x="5" width="2" height="10" fill="#FECC00"/><rect y="4" width="16" height="2" fill="#FECC00"/></svg>Svenska</span><span class="lang-sep">·</span><span class="lang-item"><svg class="feature-flag" viewBox="0 0 22 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect width="22" height="16" fill="#BA0C2F"/><rect x="6" width="4" height="16" fill="#fff"/><rect y="6" width="22" height="4" fill="#fff"/><rect x="7" width="2" height="16" fill="#00205B"/><rect y="7" width="22" height="2" fill="#00205B"/></svg>Norsk</span></span>` + +/** Pull translatable strings from visible content only (never from <style>). */ +function collectSegments(html) { const segments = [] - const re = />([^<]+)</g + + const titleMatch = html.match(/<title>([^<]*)<\/title>/i) + if (titleMatch?.[1]?.trim()) { + segments.push({ kind: 'title', original: titleMatch[1] }) + } + + const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i) + const bodyHtml = bodyMatch?.[1] ?? '' + + const textRe = />([^<]+)</g let match - while ((match = re.exec(html)) !== null) { + while ((match = textRe.exec(bodyHtml)) !== null) { const text = match[1] if (!text.trim()) continue - if (/^\s*$/.test(text)) continue - segments.push({ text, index: segments.length }) + // Legal imprint stays identical in all locales + if (text.includes('KnorrLabs')) continue + segments.push({ kind: 'text', original: text }) } + + const ariaRe = /aria-label="([^"]+)"/g + while ((match = ariaRe.exec(bodyHtml)) !== null) { + segments.push({ kind: 'aria', original: match[1] }) + } + + const altRe = /alt="([^"]+)"/g + while ((match = altRe.exec(bodyHtml)) !== null) { + segments.push({ kind: 'alt', original: match[1] }) + } + return segments } -function replaceSegments(html, originals, translated) { +function applySegments(html, segments, translated) { let result = html - for (let i = 0; i < originals.length; i++) { - const from = originals[i].text - const to = translated[i] - if (from === to) continue - result = result.replace(`>${from}<`, `>${to}<`) - } + + segments.forEach((segment, index) => { + const next = translated[index] + if (segment.original === next) return + + switch (segment.kind) { + case 'title': + result = result.replace( + `<title>${segment.original}`, + `${next}` + ) + break + case 'text': { + const needle = `>${segment.original}<` + const pos = result.indexOf(needle) + if (pos === -1) return + result = `${result.slice(0, pos)}>${next}<${result.slice(pos + needle.length)}` + break + } + case 'aria': + result = result.replace( + `aria-label="${segment.original}"`, + `aria-label="${next}"` + ) + break + case 'alt': + result = result.replace( + `alt="${segment.original}"`, + `alt="${next}"` + ) + break + default: + break + } + }) + return result } -function patchLanguageFeature(html, lang) { - const langBlocks = { - da: `Dansk`, - sv: `Svenska`, - nb: `Norsk` - } - - const deEnBlock = - /[\s\S]*?<\/span><\/span><\/div>/ - const replacement = `${langBlocks[lang]}&Deutsch&Engelsk
` - - return html.replace(deEnBlock, replacement) +function patchLanguageFeature(html) { + return html.replace( + /(
✦<\/span>)[\s\S]*?<\/span>(<\/div>)/, + `$1${LANG_LIST_BLOCK}$2` + ) } function parseArgs(argv) { @@ -73,8 +120,10 @@ async function main() { const langs = parseArgs(process.argv) const apiKey = loadEnvKey() const sourceHtml = await readFile(sourcePath, 'utf8') - const segments = extractSegments(sourceHtml) - const texts = segments.map((s) => s.text) + const segments = collectSegments(sourceHtml) + const texts = segments.map((s) => s.original) + + console.log(`Translating ${texts.length} visible strings per locale…`) for (const lang of langs) { const target = TARGETS[lang] @@ -90,13 +139,9 @@ async function main() { batchSize: 20 }) - let html = replaceSegments(sourceHtml, segments, translated) + let html = applySegments(sourceHtml, segments, translated) html = html.replace(//, ``) - html = html.replace( - /Kapteins Daagbok — Beta-Flyer<\/title>/, - `<title>Kapteins Daagbok — Beta-Flyer (${target.htmlLang.toUpperCase()})` - ) - html = patchLanguageFeature(html, lang) + html = patchLanguageFeature(html) const outPath = resolve(repoRoot, 'docs/marketing', target.file) await writeFile(outPath, html, 'utf8')