438 lines
23 KiB
JavaScript
438 lines
23 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Original nightTab source (input) lives next to this script.
|
|
const root = path.join(__dirname, 'quelle-nightTab');
|
|
// Output HTML: explicit arg, else default to the WebView2 project.
|
|
const out = process.argv[2] || path.join(__dirname, 'webview2', 'app-index.html');
|
|
const APPNAME = 'MyStart';
|
|
|
|
let css = fs.readFileSync(path.join(root, 'index.41b2d2a8cee846b032b5.css'), 'utf8');
|
|
|
|
const mime = { woff2: 'font/woff2', woff: 'font/woff', ttf: 'font/ttf' };
|
|
css = css.replace(/url\((font\/[^)]+)\)/g, (m, rel) => {
|
|
const p = path.join(root, rel);
|
|
const ext = rel.split('.').pop();
|
|
const b64 = fs.readFileSync(p).toString('base64');
|
|
return `url(data:${mime[ext] || 'application/octet-stream'};base64,${b64})`;
|
|
});
|
|
|
|
let initJs = fs.readFileSync(path.join(root, 'initialBackground.js'), 'utf8');
|
|
let mainJs = fs.readFileSync(path.join(root, 'index.09153abdbaa791fa4f0e.js'), 'utf8');
|
|
|
|
// --- 1) Remove the "Support" and "Coffee" entries from the settings menu ---
|
|
const navSnippet = '{name:"Support",active:!1,overscroll:!1},{name:"Coffee",active:!1,overscroll:!1},';
|
|
if (!mainJs.includes(navSnippet)) throw new Error('navData snippet not found — aborting');
|
|
mainJs = mainJs.replace(navSnippet, '');
|
|
|
|
// --- 1b) Rebuild the About page: logo + name + version + own website & git links ---
|
|
const aStart = 'const t=new Pa({text:"GitHub.';
|
|
const aEndMarker = ',s,o,n]))';
|
|
const i = mainJs.indexOf(aStart);
|
|
if (i < 0) throw new Error('About block start not found — aborting');
|
|
const j = mainJs.indexOf(aEndMarker, i);
|
|
if (j < 0) throw new Error('About block end not found — aborting');
|
|
const aboutBody =
|
|
'const t=new Pa({text:"m-viper.de",href:"https://m-viper.de",openNew:!0}),' +
|
|
'a=new Pa({text:"git.viper.ipv64.net",href:"https://git.viper.ipv64.net",openNew:!0}),' +
|
|
's=y("p");s.innerHTML=`Website: ${t.link().outerHTML}`;' +
|
|
'const o=y("p");o.innerHTML=`Git: ${a.link().outerHTML}`,' +
|
|
'e.appendChild(y("div",[y("div|class:version",[_t.render(),' +
|
|
'y("div|class:version-details",[y("h1:MyStart|class:version-app-name"),' +
|
|
'y("p:Version 1.0.1|class:version-number")])]),y("hr"),s,o,y("div|id:mystart-autostart-row,class:mystart-autostart-row")]))';
|
|
mainJs = mainJs.slice(0, i) + aboutBody + mainJs.slice(j + aEndMarker.length);
|
|
|
|
// --- 2) Rename branding (visible text + storage keys) consistently ---
|
|
const rename = (s) => s.split('nightTab').join(APPNAME);
|
|
mainJs = rename(mainJs);
|
|
initJs = rename(initJs);
|
|
|
|
// --- 3) Import: accept BOTH original nightTab backups and MyStart backups ---
|
|
// After rename the two validation gates only accept the "MyStart" marker key;
|
|
// re-add acceptance of the original "nightTab" marker so old backups import too.
|
|
const importGates = [
|
|
['JSON.parse(t).MyStart||JSON.parse(t)[nt.toLowerCase()]',
|
|
'JSON.parse(t).MyStart||JSON.parse(t).nightTab||JSON.parse(t)[nt.toLowerCase()]'],
|
|
['JSON.parse(r.target.result).MyStart||JSON.parse(r.target.result)[nt.toLowerCase()]',
|
|
'JSON.parse(r.target.result).MyStart||JSON.parse(r.target.result).nightTab||JSON.parse(r.target.result)[nt.toLowerCase()]'],
|
|
];
|
|
for (const [from, to] of importGates) {
|
|
if (!mainJs.includes(from)) throw new Error('import gate not found: ' + from);
|
|
mainJs = mainJs.replace(from, to);
|
|
}
|
|
|
|
// --- 4) German UI: translate the DISPLAY of menu tabs/sub-tabs/headers only.
|
|
// The underlying name stays English because it is used as a routing key
|
|
// (makeId = first word lowercased). __TR maps display text, keyed by that id.
|
|
const menuMap = {
|
|
theme:'Design', layout:'Layout', header:'Kopfzeile', bookmark:'Lesezeichen',
|
|
group:'Gruppe', toolbar:'Werkzeugleiste', data:'Daten', mystart:'MyStart',
|
|
preset:'Vorlage', saved:'Gespeichert', style:'Stil', colour:'Farbe',
|
|
accent:'Akzent', font:'Schrift', radius:'Radius', shadow:'Schatten',
|
|
shade:'Schattierung', opacity:'Deckkraft', background:'Hintergrund',
|
|
search:'Suche', scaling:'Skalierung', area:'Bereich', padding:'Innenabstand',
|
|
gutter:'Abstand', alignment:'Ausrichtung', page:'Seite', greeting:'Begrüßung',
|
|
transitional:'Übergangswörter', clock:'Uhr', date:'Datum', general:'Allgemein',
|
|
orientation:'Ausrichtung', sort:'Sortierung', name:'Name', size:'Größe',
|
|
location:'Ort', position:'Position', controls:'Steuerung',
|
|
restore:'Wiederherstellen', backup:'Sichern', clear:'Löschen',
|
|
};
|
|
const trInfra =
|
|
'window.__MT=' + JSON.stringify(menuMap) + ';' +
|
|
'window.__TR=function(s){if(s==null)return s;var k=String(s).split(" ")[0].toLowerCase();return window.__MT[k]||s;};';
|
|
|
|
// Autostart toggle: renders a CUSTOM slider switch into the About page's
|
|
// #mystart-autostart-row (native checkboxes are hidden by nightTab's CSS:
|
|
// opacity:0/position:absolute/pointer-events:none). Talks to the native host
|
|
// via postMessage; the host reads/writes the HKCU Run key.
|
|
const autostartJs = `(function(){
|
|
var A={enabled:false,supported:!!(window.chrome&&window.chrome.webview)};
|
|
window.__mystartAutostart=A;
|
|
function paint(){
|
|
var tr=document.getElementById('mystart-autostart-track');
|
|
var kn=document.getElementById('mystart-autostart-knob');
|
|
if(!tr||!kn)return;
|
|
tr.style.background=A.enabled?'#2ea043':'#565d68';
|
|
kn.style.left=A.enabled?'23px':'3px';
|
|
}
|
|
if(A.supported){
|
|
window.chrome.webview.addEventListener('message',function(ev){
|
|
var d=ev.data;if(d&&d.type==='autostart'){A.enabled=!!d.enabled;paint();}
|
|
});
|
|
}
|
|
function render(row){
|
|
var wrap=document.createElement('div');
|
|
wrap.style.cssText='display:flex;align-items:center;gap:10px;margin-top:18px;cursor:pointer;user-select:none;';
|
|
var track=document.createElement('div');
|
|
track.id='mystart-autostart-track';
|
|
track.style.cssText='position:relative;width:46px;height:26px;border-radius:13px;background:#565d68;flex:0 0 auto;transition:background .15s;box-sizing:border-box;';
|
|
var knob=document.createElement('div');
|
|
knob.id='mystart-autostart-knob';
|
|
knob.style.cssText='position:absolute;top:3px;left:3px;width:20px;height:20px;border-radius:50%;background:#fff;transition:left .15s;box-shadow:0 1px 2px rgba(0,0,0,.4);box-sizing:border-box;';
|
|
track.appendChild(knob);
|
|
var txt=document.createElement('span');
|
|
txt.textContent='Mit Windows starten (minimiert)';
|
|
if(!A.supported){wrap.style.opacity='0.5';wrap.style.cursor='default';wrap.title='Nur in der MyStart-App verfügbar.';}
|
|
wrap.appendChild(track);wrap.appendChild(txt);row.appendChild(wrap);
|
|
paint();
|
|
wrap.addEventListener('click',function(){
|
|
if(!A.supported)return;
|
|
A.enabled=!A.enabled;paint();
|
|
try{window.chrome.webview.postMessage({type:'autostart-set',value:A.enabled});}catch(e){}
|
|
});
|
|
if(A.supported){try{window.chrome.webview.postMessage({type:'autostart-get'});}catch(e){}}
|
|
}
|
|
function ensure(){
|
|
var row=document.getElementById('mystart-autostart-row');
|
|
if(row&&!document.getElementById('mystart-autostart-track'))render(row);
|
|
}
|
|
new MutationObserver(ensure).observe(document.documentElement,{childList:true,subtree:true});
|
|
ensure();
|
|
})();`;
|
|
|
|
// Frameless-window controls (minimize / maximize-restore / close) in the top-right
|
|
// corner. Only shown inside the app (WebView2 host); they postMessage to the host.
|
|
const windowControlsJs = `(function(){
|
|
if(!(window.chrome&&window.chrome.webview))return;
|
|
function send(t){try{window.chrome.webview.postMessage({type:t});}catch(e){}}
|
|
function mk(glyph,title,type,isClose){
|
|
var b=document.createElement('div');
|
|
b.textContent=glyph;b.title=title;
|
|
b.style.cssText='width:44px;height:30px;display:flex;align-items:center;justify-content:center;font-family:"Segoe UI",system-ui,sans-serif;font-size:14px;line-height:1;color:#d0d4db;cursor:pointer;transition:background .1s,color .1s;-webkit-user-select:none;user-select:none;';
|
|
b.onmouseenter=function(){b.style.background=isClose?'#e81123':'rgba(255,255,255,0.18)';b.style.color='#fff';};
|
|
b.onmouseleave=function(){b.style.background='transparent';b.style.color='#d0d4db';};
|
|
b.onclick=function(){send(type);};
|
|
return b;
|
|
}
|
|
function mkGrip(){
|
|
var g=document.createElement('div');
|
|
g.title='Fenster verschieben (ziehen) — oder Alt + ziehen';
|
|
g.style.cssText='width:34px;height:30px;display:flex;align-items:center;justify-content:center;color:#9aa0aa;cursor:move;-webkit-user-select:none;user-select:none;';
|
|
g.innerHTML='<svg width="10" height="16" viewBox="0 0 10 16" fill="currentColor"><circle cx="2" cy="2" r="1.4"/><circle cx="8" cy="2" r="1.4"/><circle cx="2" cy="8" r="1.4"/><circle cx="8" cy="8" r="1.4"/><circle cx="2" cy="14" r="1.4"/><circle cx="8" cy="14" r="1.4"/></svg>';
|
|
g.onmouseenter=function(){g.style.color='#fff';};
|
|
g.onmouseleave=function(){g.style.color='#9aa0aa';};
|
|
g.addEventListener('mousedown',function(ev){if(ev.button!==0)return;ev.preventDefault();send('window-drag');});
|
|
return g;
|
|
}
|
|
function add(){
|
|
if(!document.body||document.getElementById('mystart-winctrls'))return;
|
|
var bar=document.createElement('div');
|
|
bar.id='mystart-winctrls';
|
|
bar.style.cssText='position:fixed;top:0;right:0;display:flex;align-items:center;z-index:2147483647;';
|
|
bar.appendChild(mkGrip());
|
|
bar.appendChild(mk('\\u2013','Minimieren','window-min',false));
|
|
bar.appendChild(mk('\\u25A1','Maximieren / Wiederherstellen','window-max',false));
|
|
bar.appendChild(mk('\\u2715','Schließen','window-close',true));
|
|
document.body.appendChild(bar);
|
|
}
|
|
if(document.body)add();else document.addEventListener('DOMContentLoaded',add);
|
|
new MutationObserver(function(){if(!document.getElementById('mystart-winctrls'))add();}).observe(document.documentElement,{childList:true,subtree:true});
|
|
// Alt + left-drag anywhere -> move the window (non-blocking).
|
|
document.addEventListener('mousedown',function(ev){
|
|
if(ev.button===0&&ev.altKey){ev.preventDefault();send('window-drag');}
|
|
},true);
|
|
// Edge/corner resize: the WebView fills the whole window, so the OS never
|
|
// sees the real window border to hit-test for a resize drag -- every mouse
|
|
// event lands in the page first. Watch the pointer ourselves near the
|
|
// viewport edges and hand off to the native resize loop, same trick as
|
|
// the drag grip above.
|
|
var RM=6; // px from the true edge that counts as a resize zone
|
|
var CURSOR={n:'ns-resize',s:'ns-resize',e:'ew-resize',w:'ew-resize',
|
|
ne:'nesw-resize',sw:'nesw-resize',nw:'nwse-resize',se:'nwse-resize'};
|
|
function edgeAt(x,y){
|
|
var w=window.innerWidth,h=window.innerHeight;
|
|
var l=x<=RM,r=x>=w-RM,t=y<=RM,b=y>=h-RM;
|
|
if(t&&l)return'nw';if(t&&r)return'ne';if(b&&l)return'sw';if(b&&r)return'se';
|
|
if(l)return'w';if(r)return'e';if(t)return'n';if(b)return's';
|
|
return null;
|
|
}
|
|
document.addEventListener('mousemove',function(ev){
|
|
if(ev.buttons)return; // an active drag (e.g. scrollbar) is in progress -> leave it alone
|
|
var onCtrls=ev.target.closest&&ev.target.closest('#mystart-winctrls');
|
|
var edge=onCtrls?null:edgeAt(ev.clientX,ev.clientY);
|
|
document.documentElement.style.cursor=edge?CURSOR[edge]:'';
|
|
},true);
|
|
document.addEventListener('mousedown',function(ev){
|
|
if(ev.button!==0)return;
|
|
if(ev.target.closest&&ev.target.closest('#mystart-winctrls'))return;
|
|
var edge=edgeAt(ev.clientX,ev.clientY);
|
|
if(edge){ev.preventDefault();send2('window-resize',{edge:edge});}
|
|
},true);
|
|
document.addEventListener('mouseleave',function(){document.documentElement.style.cursor='';},true);
|
|
function send2(t,extra){try{window.chrome.webview.postMessage(Object.assign({type:t},extra));}catch(e){}}
|
|
})();`;
|
|
|
|
mainJs = trInfra + autostartJs + windowControlsJs + mainJs;
|
|
|
|
const trPatches = [
|
|
// panel header (top-level AND sub, both go through element.header)
|
|
['y("h1:"+e+"|class:menu-item-header-text")',
|
|
'y("h1:"+window.__TR(e)+"|class:menu-item-header-text")'],
|
|
// top-level nav tab label
|
|
['new Fe({text:e.name,style:["link"],block:!0,classList:["menu-nav-tab"]',
|
|
'new Fe({text:window.__TR(e.name),style:["link"],block:!0,classList:["menu-nav-tab"]'],
|
|
// sub-level nav label (href keeps makeId -> English routing key)
|
|
['y("a:"+e+"|href:#menu-content-item-"+this.makeId(e)',
|
|
'y("a:"+window.__TR(e)+"|href:#menu-content-item-"+this.makeId(e)'],
|
|
];
|
|
for (const [from, to] of trPatches) {
|
|
if (!mainJs.includes(from)) throw new Error('translate patch not found: ' + from);
|
|
mainJs = mainJs.replace(from, to);
|
|
}
|
|
|
|
// --- 5) Translate safe, unique display strings (search box, greeting, key texts).
|
|
// Tolerant: each is applied only if the exact literal is present. Structural
|
|
// names and icon labels are deliberately NOT touched.
|
|
const dict = [
|
|
// search box placeholder (base + engine name is appended after it)
|
|
['?"Find bookmarks or search":"Search"', '?"Lesezeichen finden oder suchen mit":"Suchen mit"'],
|
|
// greeting words
|
|
['"Good morning"', '"Guten Morgen"'],
|
|
['"Good afternoon"', '"Guten Tag"'],
|
|
['"Good evening"', '"Guten Abend"'],
|
|
['"Good night"', '"Gute Nacht"'],
|
|
['"Hello"', '"Hallo"'],
|
|
['"Welcome"', '"Willkommen"'],
|
|
['"Greetings"', '"Grüße"'],
|
|
// Data panel / confirmation descriptions (post-rename -> contain MyStart)
|
|
['"Restore a previously exported MyStart backup."', '"Eine zuvor exportierte MyStart-Sicherung wiederherstellen."'],
|
|
['"Restoring from a MyStart backup"', '"Aus einer MyStart-Sicherung wiederherstellen"'],
|
|
['"Clear all data to reset MyStart to the default state."', '"Alle Daten löschen, um MyStart auf den Ausgangszustand zurückzusetzen."'],
|
|
['"Clear MyStart data except bookmarks?"', '"MyStart-Daten außer Lesezeichen löschen?"'],
|
|
['"Or drop a MyStart backup file here."', '"Oder ziehe eine MyStart-Sicherungsdatei hierher."'],
|
|
// universal button
|
|
['text:"Add"', 'text:"Hinzufügen"'],
|
|
];
|
|
for (const [from, to] of dict) {
|
|
if (mainJs.includes(from)) mainJs = mainJs.split(from).join(to);
|
|
}
|
|
|
|
// --- 6) Full German UI: translate every remaining visible control/label/tooltip.
|
|
// Applied ONLY to value positions (:"...") so routing names (name:"Theme",
|
|
// sub:["Style"], case"layout") and the icon database stay untouched. The button
|
|
// behaviour lives in func()/keys, never in the display text, so this is safe.
|
|
const chrome = Object.entries(JSON.parse(fs.readFileSync(path.join(__dirname, 'lang', 'de.json'), 'utf8')));
|
|
const _unusedChrome = [
|
|
['Add', 'Hinzufügen'],
|
|
['Add 10 random bookmarks', '10 zufällige Lesezeichen hinzufügen'],
|
|
['Add a group', 'Gruppe hinzufügen'],
|
|
['Add a new Bookmark', 'Neues Lesezeichen hinzufügen'],
|
|
['Add a new Group', 'Neue Gruppe hinzufügen'],
|
|
['Add unique accent to each Bookmark', 'Jedem Lesezeichen eigene Akzentfarbe geben'],
|
|
['Adjust Visual Element position', 'Position des Symbols anpassen'],
|
|
['Area alignment', 'Ausrichtung des Bereichs'],
|
|
['Bold', 'Fett'],
|
|
['Bookmark area alignment', 'Ausrichtung des Lesezeichen-Bereichs'],
|
|
['Bookmark area background colour', 'Hintergrundfarbe des Lesezeichen-Bereichs'],
|
|
['By icon', 'Nach Icon'],
|
|
['By letter', 'Nach Buchstabe'],
|
|
['By name', 'Nach Name'],
|
|
['Clear all data', 'Alle Daten löschen'],
|
|
['Clear all except bookmarks', 'Alles außer Lesezeichen löschen'],
|
|
['Clear all MyStart data?', 'Alle MyStart-Daten löschen?'],
|
|
['Clear search', 'Suche leeren'],
|
|
['Close settings menu', 'Einstellungen schließen'],
|
|
['Collapse this Group', 'Diese Gruppe einklappen'],
|
|
['Copy to clipboard', 'In die Zwischenablage kopieren'],
|
|
['Drag bookmark to reorder', 'Lesezeichen ziehen zum Umsortieren'],
|
|
['Drag group to reorder', 'Gruppe ziehen zum Umsortieren'],
|
|
['Drag header item to reorder', 'Kopfzeilen-Element ziehen zum Umsortieren'],
|
|
['Edit saved themes', 'Gespeicherte Designs bearbeiten'],
|
|
['Edit this bookmark', 'Dieses Lesezeichen bearbeiten'],
|
|
['Edit this group', 'Diese Gruppe bearbeiten'],
|
|
['Edit this saved theme', 'Dieses gespeicherte Design bearbeiten'],
|
|
['Edit unnamed bookmark', 'Unbenanntes Lesezeichen bearbeiten'],
|
|
['Edit unnamed group', 'Unbenannte Gruppe bearbeiten'],
|
|
['Enter edit bookmark mode', 'Bearbeitungsmodus für Lesezeichen'],
|
|
['Export data', 'Daten exportieren'],
|
|
['Google Font', 'Google-Schriftart'],
|
|
['Google font name', 'Name der Google-Schriftart'],
|
|
['Group details area alignment', 'Ausrichtung des Gruppen-Detailbereichs'],
|
|
['Header area alignment', 'Ausrichtung des Kopfzeilen-Bereichs'],
|
|
['Header background colour', 'Hintergrundfarbe der Kopfzeile'],
|
|
['Header item alignment', 'Ausrichtung der Kopfzeilen-Elemente'],
|
|
['Hide all', 'Alle ausblenden'],
|
|
['How to link to a video file.', 'So verlinkst du eine Videodatei.'],
|
|
['Import', 'Importieren'],
|
|
['Import from clipboard', 'Aus Zwischenablage importieren'],
|
|
['Large button', 'Große Schaltfläche'],
|
|
['Layout Direction', 'Layout-Richtung'],
|
|
['Layout background colour', 'Hintergrundfarbe des Layouts'],
|
|
['Light', 'Dünn'],
|
|
['Line button', 'Linien-Schaltfläche'],
|
|
['Link button', 'Link-Schaltfläche'],
|
|
['Medium button', 'Mittlere Schaltfläche'],
|
|
['More controls', 'Mehr Optionen'],
|
|
['Move this bookmark left', 'Dieses Lesezeichen nach links'],
|
|
['Move this bookmark right', 'Dieses Lesezeichen nach rechts'],
|
|
['Move this group down', 'Diese Gruppe nach unten'],
|
|
['Move this group right', 'Diese Gruppe nach rechts'],
|
|
['Move this group up', 'Diese Gruppe nach oben'],
|
|
['Name hide', 'Name ausblenden'],
|
|
['Name show', 'Name anzeigen'],
|
|
['New Bookmark', 'Neues Lesezeichen'],
|
|
['New Group', 'Neue Gruppe'],
|
|
['New Tab', 'Neuer Tab'],
|
|
['Only icons', 'Nur Icons'],
|
|
['Only images', 'Nur Bilder'],
|
|
['Only letters', 'Nur Buchstaben'],
|
|
['Open all Bookmarks in this Group', 'Alle Lesezeichen dieser Gruppe öffnen'],
|
|
['Open settings menu', 'Einstellungen öffnen'],
|
|
['Random Group name', 'Zufälliger Gruppenname'],
|
|
['Random colour', 'Zufällige Farbe'],
|
|
['Random theme name', 'Zufälliger Designname'],
|
|
['Randomise now', 'Jetzt zufällig'],
|
|
['Regular', 'Normal'],
|
|
['Remove', 'Entfernen'],
|
|
['Remove all accent overrides', 'Alle Akzent-Überschreibungen entfernen'],
|
|
['Remove icon', 'Icon entfernen'],
|
|
['Remove this bookmark', 'Dieses Lesezeichen entfernen'],
|
|
['Remove this group', 'Diese Gruppe entfernen'],
|
|
['Remove this saved theme', 'Dieses gespeicherte Design entfernen'],
|
|
['Remove unnamed bookmark', 'Unbenanntes Lesezeichen entfernen'],
|
|
['Reset to default', 'Auf Standard zurücksetzen'],
|
|
['Ring button', 'Ring-Schaltfläche'],
|
|
['Save', 'Speichern'],
|
|
['Save current theme', 'Aktuelles Design speichern'],
|
|
['Scrollbar', 'Bildlaufleiste'],
|
|
['Search Bookmarks or', 'Lesezeichen durchsuchen oder'],
|
|
['Search box size', 'Größe des Suchfelds'],
|
|
['Search box width', 'Breite des Suchfelds'],
|
|
['Search engine', 'Suchmaschine'],
|
|
['Search engine name', 'Name der Suchmaschine'],
|
|
['Search text alignment', 'Textausrichtung der Suche'],
|
|
['Show all', 'Alle anzeigen'],
|
|
['Small button', 'Kleine Schaltfläche'],
|
|
['Start of the week', 'Wochenbeginn'],
|
|
['Toolbar position', 'Position der Werkzeugleiste'],
|
|
['Visual Element and Name alignment', 'Ausrichtung von Symbol und Name'],
|
|
['Why has this changed?', 'Warum hat sich das geändert?'],
|
|
['Word length', 'Wortlänge'],
|
|
['Wording', 'Formulierung'],
|
|
['You can restore all or parts of a backup file. The following data will be restored:',
|
|
'Du kannst eine Sicherung ganz oder teilweise wiederherstellen. Folgende Daten werden wiederhergestellt:'],
|
|
// placeholders
|
|
['Example', 'Beispiel'],
|
|
['Example group', 'Beispielgruppe'],
|
|
['Example theme', 'Beispiel-Design'],
|
|
['FontAwesome Brands or Icons', 'FontAwesome Brands oder Icons'],
|
|
['Hex code', 'Hex-Code'],
|
|
['Nickname, alias or superhero name', 'Spitzname, Alias oder Heldenname'],
|
|
];
|
|
// Locate the FontAwesome icon array (mr=[{name:"500px"...]) so we NEVER touch it.
|
|
const iconStart = mainJs.indexOf('mr=[{name:"500px"');
|
|
let iconEnd = -1;
|
|
if (iconStart >= 0) {
|
|
const bracket = mainJs.indexOf('[', iconStart);
|
|
let depth = 0;
|
|
for (let i = bracket; i < mainJs.length; i++) {
|
|
const c = mainJs[i];
|
|
if (c === '[') depth++;
|
|
else if (c === ']') { depth--; if (depth === 0) { iconEnd = i + 1; break; } }
|
|
else if (c === '"') { i++; while (i < mainJs.length && mainJs[i] !== '"') { if (mainJs[i] === '\\') i++; i++; } }
|
|
}
|
|
}
|
|
if (iconStart < 0 || iconEnd < 0) throw new Error('icon region not found — aborting translation');
|
|
|
|
const before = mainJs.slice(0, iconStart);
|
|
const iconRegion = mainJs.slice(iconStart, iconEnd);
|
|
const after = mainJs.slice(iconEnd);
|
|
|
|
// Only translate genuine DISPLAY properties, and only outside the icon data.
|
|
const displayProps = ['labelText', 'text', 'description', 'label', 'title', 'placeholder', 'heading', 'successText', 'tabText'];
|
|
const jsEsc = (v) => v.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
const translateChrome = (s) => {
|
|
for (const [en, de] of chrome) {
|
|
const deEsc = jsEsc(de);
|
|
for (const p of displayProps) {
|
|
const from = p + ':"' + en + '"';
|
|
if (s.includes(from)) s = s.split(from).join(p + ':"' + deEsc + '"');
|
|
}
|
|
}
|
|
return s;
|
|
};
|
|
mainJs = translateChrome(before) + iconRegion + translateChrome(after);
|
|
|
|
// --- 7) Long help/feedback/description sentences (y-DSL `y("p:..")`, text:[..] arrays,
|
|
// and description:"..."). Safe via exact match because these strings are long & unique.
|
|
const de2 = Object.entries(JSON.parse(fs.readFileSync(path.join(__dirname, 'lang', 'de2.json'), 'utf8')));
|
|
for (const [en, de] of de2) {
|
|
const deEsc = jsEsc(de);
|
|
// quoted literal (arrays, description:"...", any "..." occurrence)
|
|
mainJs = mainJs.split('"' + en + '"').join('"' + deEsc + '"');
|
|
// y-DSL with trailing attributes: y("p:TEXT|class:..")
|
|
mainJs = mainJs.split(':' + en + '|').join(':' + deEsc + '|');
|
|
// y-DSL without attributes: y("p:TEXT")
|
|
mainJs = mainJs.split(':' + en + '"').join(':' + deEsc + '"');
|
|
}
|
|
|
|
// --- 8) Two font-field hints that contain escaped quotes in the source literal. ---
|
|
const fontHints = [
|
|
['Clear the field to use the default font "Fjalla One".', 'Feld leeren, um die Standardschrift "Fjalla One" zu verwenden.'],
|
|
['Clear the field to use the default font "Open Sans".', 'Feld leeren, um die Standardschrift "Open Sans" zu verwenden.'],
|
|
];
|
|
for (const [en, de] of fontHints) {
|
|
if (mainJs.includes(en)) mainJs = mainJs.split(en).join(de);
|
|
}
|
|
|
|
const favSvg = fs.readFileSync(path.join(root, 'icon/favicon.svg'));
|
|
const favData = 'data:image/svg+xml;base64,' + favSvg.toString('base64');
|
|
|
|
const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"><title>${APPNAME}</title><link rel="icon" type="image/svg+xml" class="favicon" href="${favData}"/><style media="screen">html,body {background-color: rgb(0, 0, 0);}@media (prefers-color-scheme: dark) {html,body {background-color: rgb(0, 0, 0);}}@media (prefers-color-scheme: light) {html,body {background-color: rgb(255, 255, 255);}}</style>
|
|
<style>${css}</style>
|
|
<style>/* Scrollbalken ans dunkle MyStart-Design anpassen (schlank, dunkel, ohne Pfeile) */
|
|
::-webkit-scrollbar{width:10px;height:10px;}
|
|
::-webkit-scrollbar-track{background:transparent;}
|
|
::-webkit-scrollbar-thumb{background-color:rgba(255,255,255,0.22);border-radius:8px;border:2px solid transparent;background-clip:padding-box;}
|
|
::-webkit-scrollbar-thumb:hover{background-color:rgba(255,255,255,0.38);background-clip:padding-box;}
|
|
::-webkit-scrollbar-button{display:none;height:0;width:0;}
|
|
::-webkit-scrollbar-corner{background:transparent;}</style>
|
|
<script>${initJs}</script>
|
|
</head><body>
|
|
<script>${mainJs}</script>
|
|
</body></html>`;
|
|
|
|
fs.writeFileSync(out, html);
|
|
const kb = (fs.statSync(out).size / 1024).toFixed(0);
|
|
console.log('Written: ' + out + ' (' + kb + ' KB) [Support/Coffee removed, renamed to ' + APPNAME + ']');
|