- Warte auf Signatur-Station...
+ Warte auf Signatur-Station...
-
PDF Dokument auswählen
-
Wähle eine PDF-Datei aus, die unterschrieben werden soll
+
PDF Dokument auswählen
+
Wähle eine PDF-Datei aus, die unterschrieben werden soll
-
-
+
Warte auf Unterschrift von der Signatur-Station...
-
-
📝 Unterschrift positionieren: Ziehe die Unterschrift an die gewünschte Position im PDF. Nutze den Griff rechts unten zum Skalieren.
+
📝 Unterschrift positionieren:Ziehe die Unterschrift an die gewünschte Position im PDF. Nutze den Griff rechts unten zum Skalieren.
-
Auf welchen Seiten platzieren?
+
Auf welchen Seiten platzieren?
- Nur aktuelle Seite (1)
+ Nur aktuelle Seite (1)
- Alle Seiten
+ Alle Seiten
-
+
🗑️ Unterschrift entfernen
-
+
✓ Unterschrift & Text platzieren & PDF erstellen
@@ -109,14 +115,14 @@
-
+
⬇ Unterschriebene PDF herunterladen
-
+
🗑️ Verwerfen
-
+
PDF wurde erstellt und ist bereit zum Download
diff --git a/html/signature.html b/html/signature.html
index cf64a2a..8ca8e4b 100644
--- a/html/signature.html
+++ b/html/signature.html
@@ -6,69 +6,74 @@
Unterschrift - PDF Signatur
+
+
+
+ DE
+
+
-
✍️ Unterschriften-Station
-
Bereit für Unterschriften
+
✍️ Unterschriften-Station
+
Bereit für Unterschriften
-
Verbinde mit Server...
+
Verbinde mit Server...
📄
-
Warte auf PDF...
-
Bereit zum Unterschreiben. Lade ein PDF auf der Master-Station hoch.
+
Warte auf PDF...
+
Bereit zum Unterschreiben. Lade ein PDF auf der Master-Station hoch.
⚠️
- Hinweis: Bereits eine andere Signatur-Station ist mit dem Master verbunden.
- Diese Station kann nicht verwendet werden, solange die andere Station aktiv ist.
+ Hinweis:Bereits eine andere Signatur-Station ist mit dem Master verbunden. Diese Station kann nicht verwendet werden, solange die andere Station aktiv ist.
-
📄 Zu unterschreibendes Dokument
-
+
📄 Zu unterschreibendes Dokument
+
Seite 1 von 1
-
Scrolle nach unten um alle Seiten zu sehen
+
Scrolle nach unten um alle Seiten zu sehen
- 📝 Anleitung: Zeichnen Sie Ihre Unterschrift mit dem Finger oder der Maus im Feld unten. Drücken Sie dann auf "Unterschrift senden".
+ 📝 Anleitung:Zeichnen Sie Ihre Unterschrift mit dem Finger oder der Maus im Feld unten. Drücken Sie dann auf "Unterschrift senden".
-
Unterschriftsfeld:
+
Unterschriftsfeld:
-
+
🗑️ Löschen
-
+
✓ Unterschrift senden
-
✅ Erfolgreich!
-
Ihre Unterschrift wurde erfolgreich übertragen.
+
✅ Erfolgreich!
+
Ihre Unterschrift wurde erfolgreich übertragen.
-
+
❌ Fehler beim Senden der Unterschrift. Bitte versuchen Sie es erneut.
diff --git a/js/i18n.js b/js/i18n.js
new file mode 100644
index 0000000..26a0c88
--- /dev/null
+++ b/js/i18n.js
@@ -0,0 +1,248 @@
+// Internationalization (i18n) system
+let currentLanguage = 'de';
+
+// Translations
+const translations = {
+ de: {
+ master: {
+ title: '📄 Master Station',
+ subtitle: 'PDF hochladen und Unterschrift empfangen',
+ waitingForStation: 'Warte auf Signatur-Station...',
+ connectedToStation: '✓ Verbunden mit Signatur-Station',
+ selectPdf: 'PDF Dokument auswählen',
+ selectPdfDesc: 'Wähle eine PDF-Datei aus, die unterschrieben werden soll',
+ selectPdfButton: 'PDF auswählen',
+ waitForConnection: '⚠️ Bitte warten Sie, bis die Signatur-Station verbunden ist.',
+ waitingForSignature: 'Warte auf Unterschrift von der Signatur-Station...',
+ signatureReceived: '✅ Unterschrift erhalten! Unterschrift oben sichtbar. Ziehe sie auf das PDF.',
+ textOverlayLabel: '📝 Text für Unterschrifts-Overlay (wird oben links angezeigt):',
+ prevPage: '◀ Zurück',
+ nextPage: 'Weiter ▶',
+ pageInfo: 'Seite 1 von 1',
+ pagePlaceholder: 'Seite',
+ jumpToPage: 'Springe zu Seite',
+ signaturePlaceholder: 'Empfangene Unterschrift wird hier angezeigt',
+ positionSignature: '📝 Unterschrift positionieren:',
+ positionSignatureDesc: 'Ziehe die Unterschrift an die gewünschte Position im PDF. Nutze den Griff rechts unten zum Skalieren.',
+ whichPages: 'Auf welchen Seiten platzieren?',
+ currentPageOnly: 'Nur aktuelle Seite',
+ allPages: 'Alle Seiten',
+ removeSignature: '🗑️ Unterschrift entfernen',
+ placeSignature: '✓ Unterschrift & Text platzieren & PDF erstellen',
+ downloadPdf: '⬇ Unterschriebene PDF herunterladen',
+ discard: '🗑️ Verwerfen',
+ downloadReady: 'PDF wurde erstellt und ist bereit zum Download',
+ pdfCreated: '✅ PDF wurde erstellt! Bereit zum Download.',
+ discardConfirm: 'Möchten Sie wirklich verwerfen? Alle Änderungen gehen verloren.'
+ },
+ signature: {
+ title: '✍️ Unterschriften-Station',
+ subtitle: 'Bereit für Unterschriften',
+ connecting: 'Verbinde mit Server...',
+ waitingForPdf: 'Warte auf PDF...',
+ waitingDesc: 'Bereit zum Unterschreiben. Lade ein PDF auf der Master-Station hoch.',
+ warning: 'Hinweis:',
+ warningText: 'Bereits eine andere Signatur-Station ist mit dem Master verbunden. Diese Station kann nicht verwendet werden, solange die andere Station aktiv ist.',
+ documentToSign: '📄 Zu unterschreibendes Dokument',
+ pageInfo: 'Seite 1 von 1',
+ scrollHint: 'Scrolle nach unten um alle Seiten zu sehen',
+ instruction: '📝 Anleitung:',
+ instructionText: 'Zeichnen Sie Ihre Unterschrift mit dem Finger oder der Maus im Feld unten. Drücken Sie dann auf "Unterschrift senden".',
+ signatureField: 'Unterschriftsfeld:',
+ clear: '🗑️ Löschen',
+ send: '✓ Unterschrift senden',
+ successTitle: '✅ Erfolgreich!',
+ successText: 'Ihre Unterschrift wurde erfolgreich übertragen.',
+ error: '❌ Fehler beim Senden der Unterschrift. Bitte versuchen Sie es erneut.'
+ }
+ },
+ en: {
+ master: {
+ title: '📄 Master Station',
+ subtitle: 'Upload PDF and receive signature',
+ waitingForStation: 'Waiting for signature station...',
+ connectedToStation: '✓ Connected to signature station',
+ selectPdf: 'Select PDF Document',
+ selectPdfDesc: 'Choose a PDF file to be signed',
+ selectPdfButton: 'Select PDF',
+ waitForConnection: '⚠️ Please wait until the signature station is connected.',
+ waitingForSignature: 'Waiting for signature from signature station...',
+ signatureReceived: '✅ Signature received! Signature visible above. Drag it onto the PDF.',
+ textOverlayLabel: '📝 Text for signature overlay (displayed top left):',
+ prevPage: '◀ Back',
+ nextPage: 'Next ▶',
+ pageInfo: 'Page 1 of 1',
+ pagePlaceholder: 'Page',
+ jumpToPage: 'Jump to page',
+ signaturePlaceholder: 'Received signature will be displayed here',
+ positionSignature: '📝 Position signature:',
+ positionSignatureDesc: 'Drag the signature to the desired position in the PDF. Use the handle at the bottom right to scale.',
+ whichPages: 'On which pages to place?',
+ currentPageOnly: 'Current page only',
+ allPages: 'All pages',
+ removeSignature: '🗑️ Remove signature',
+ placeSignature: '✓ Place signature & text & create PDF',
+ downloadPdf: '⬇ Download signed PDF',
+ discard: '🗑️ Discard',
+ downloadReady: 'PDF has been created and is ready for download',
+ pdfCreated: '✅ PDF has been created! Ready for download.',
+ discardConfirm: 'Do you really want to discard? All changes will be lost.'
+ },
+ signature: {
+ title: '✍️ Signature Station',
+ subtitle: 'Ready for signatures',
+ connecting: 'Connecting to server...',
+ waitingForPdf: 'Waiting for PDF...',
+ waitingDesc: 'Ready to sign. Upload a PDF on the Master station.',
+ warning: 'Note:',
+ warningText: 'Another signature station is already connected to the master. This station cannot be used as long as the other station is active.',
+ documentToSign: '📄 Document to sign',
+ pageInfo: 'Page 1 of 1',
+ scrollHint: 'Scroll down to see all pages',
+ instruction: '📝 Instructions:',
+ instructionText: 'Draw your signature with your finger or mouse in the field below. Then press "Send signature".',
+ signatureField: 'Signature field:',
+ clear: '🗑️ Clear',
+ send: '✓ Send signature',
+ successTitle: '✅ Success!',
+ successText: 'Your signature has been successfully transmitted.',
+ error: '❌ Error sending signature. Please try again.'
+ }
+ }
+};
+
+// Detect system language (default to German)
+function detectLanguage() {
+ // Always default to German
+ return 'de';
+}
+
+// Initialize language
+function initLanguage() {
+ // Check if language is stored in localStorage
+ const storedLang = localStorage.getItem('language');
+ if (storedLang && (storedLang === 'de' || storedLang === 'en')) {
+ currentLanguage = storedLang;
+ } else {
+ // Detect from browser
+ currentLanguage = detectLanguage();
+ localStorage.setItem('language', currentLanguage);
+ }
+ updateLanguageDisplay();
+ translatePage();
+}
+
+// Update language display button
+function updateLanguageDisplay() {
+ const langDisplay = document.getElementById('langDisplay');
+ if (langDisplay) {
+ langDisplay.textContent = currentLanguage.toUpperCase();
+ }
+}
+
+// Toggle language
+function toggleLanguage() {
+ currentLanguage = currentLanguage === 'de' ? 'en' : 'de';
+ localStorage.setItem('language', currentLanguage);
+ updateLanguageDisplay();
+ translatePage();
+ updateDynamicTranslations();
+
+ // Update HTML lang attribute
+ document.documentElement.lang = currentLanguage;
+
+ // Update text in overlay if it exists
+ if (typeof updateTextInOverlay === 'function') {
+ updateTextInOverlay();
+ }
+
+ // Update text input with date
+ if (typeof updateTextInputWithDate === 'function') {
+ updateTextInputWithDate();
+ }
+}
+
+// Translate a single element
+function translateElement(element) {
+ const key = element.getAttribute('data-i18n');
+ if (!key) return;
+
+ const keys = key.split('.');
+ let translation = translations[currentLanguage];
+
+ for (const k of keys) {
+ if (translation && translation[k]) {
+ translation = translation[k];
+ } else {
+ return; // Translation not found
+ }
+ }
+
+ if (typeof translation === 'string') {
+ element.textContent = translation;
+ }
+}
+
+// Translate placeholder
+function translatePlaceholder(element) {
+ const key = element.getAttribute('data-i18n-placeholder');
+ if (!key) return;
+
+ const keys = key.split('.');
+ let translation = translations[currentLanguage];
+
+ for (const k of keys) {
+ if (translation && translation[k]) {
+ translation = translation[k];
+ } else {
+ return;
+ }
+ }
+
+ if (typeof translation === 'string') {
+ element.placeholder = translation;
+ }
+}
+
+// Translate entire page
+function translatePage() {
+ // Translate all elements with data-i18n
+ document.querySelectorAll('[data-i18n]').forEach(translateElement);
+
+ // Translate placeholders
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(translatePlaceholder);
+
+ // Update dynamic content
+ updateDynamicTranslations();
+}
+
+// Update dynamic translations (for content that changes)
+function updateDynamicTranslations() {
+ // This will be called from master.js and signature.js for dynamic content
+ if (typeof window.updateDynamicTranslationsCustom === 'function') {
+ window.updateDynamicTranslationsCustom();
+ }
+}
+
+// Get translation by key
+function t(key) {
+ const keys = key.split('.');
+ let translation = translations[currentLanguage];
+
+ for (const k of keys) {
+ if (translation && translation[k]) {
+ translation = translation[k];
+ } else {
+ return key; // Return key if translation not found
+ }
+ }
+
+ return typeof translation === 'string' ? translation : key;
+}
+
+// Initialize on page load
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initLanguage);
+} else {
+ initLanguage();
+}
diff --git a/js/master.js b/js/master.js
index c26de19..7590f8c 100644
--- a/js/master.js
+++ b/js/master.js
@@ -33,7 +33,7 @@ function updateConnectionStatus(isConnected) {
if (isConnected) {
statusIndicator.classList.remove('disconnected');
statusIndicator.classList.add('connected');
- connectionText.textContent = '✓ Verbunden mit Signatur-Station';
+ connectionText.textContent = t('master.connectedToStation');
// Enable PDF selection
pdfInput.disabled = false;
@@ -42,7 +42,7 @@ function updateConnectionStatus(isConnected) {
} else {
statusIndicator.classList.remove('connected');
statusIndicator.classList.add('disconnected');
- connectionText.textContent = '⏳ Warte auf Signatur-Station...';
+ connectionText.textContent = t('master.waitingForStation');
// Disable PDF selection
pdfInput.disabled = true;
@@ -358,7 +358,8 @@ async function renderAllPages(pdfBytes) {
// Add page separator
const separator = document.createElement('div');
separator.className = 'pdf-page-separator';
- separator.textContent = `Seite ${pageNum} von ${numPages}`;
+ const pageText = currentLanguage === 'de' ? 'Seite' : 'Page';
+ separator.textContent = `${pageText} ${pageNum} ${currentLanguage === 'de' ? 'von' : 'of'} ${numPages}`;
container.appendChild(separator);
// Create canvas for this page
@@ -404,7 +405,8 @@ function updatePageNavigation() {
if (totalPages > 1) {
pageNav.style.display = 'flex';
- pageInfo.textContent = `Seite ${currentPageNum} von ${totalPages}`;
+ const pageText = currentLanguage === 'de' ? 'Seite' : 'Page';
+ pageInfo.textContent = `${pageText} ${currentPageNum} ${currentLanguage === 'de' ? 'von' : 'of'} ${totalPages}`;
prevBtn.disabled = currentPageNum <= 1;
nextBtn.disabled = currentPageNum >= totalPages;
@@ -591,7 +593,7 @@ async function showSignatureOverlay(signatureDataUrl) {
}
document.getElementById('statusMessage').className = 'status signed';
- document.getElementById('statusMessage').textContent = '✅ Unterschrift erhalten! Unterschrift oben sichtbar. Ziehe sie auf das PDF.';
+ document.getElementById('statusMessage').textContent = t('master.signatureReceived');
document.getElementById('downloadButton').disabled = true;
document.getElementById('downloadHint').style.display = 'none';
@@ -903,10 +905,19 @@ function updateTextInputWithDate() {
const textInput = document.getElementById('textInput');
if (textInput) {
const currentDate = getCurrentDateString();
- textInput.value = `Abgeholt am ${currentDate} durch:`;
+ if (currentLanguage === 'de') {
+ textInput.value = `Abgeholt am ${currentDate} durch:`;
+ } else {
+ textInput.value = `Picked up on ${currentDate} by:`;
+ }
+ // Update overlay text immediately
+ updateTextInOverlay();
}
}
+// Make function globally accessible for i18n.js
+window.updateTextInputWithDate = updateTextInputWithDate;
+
function updateTextInOverlay() {
const textInput = document.getElementById('textInput');
const text = textInput ? textInput.value : '';
@@ -923,6 +934,9 @@ function updateTextInOverlay() {
}
}
+// Make function globally accessible for i18n.js
+window.updateTextInOverlay = updateTextInOverlay;
+
function removeSignatureOverlay() {
// Only remove the overlay, keep signedPdfBytes intact
const overlay = document.getElementById('signatureOverlay');
@@ -965,7 +979,7 @@ function removeSignature() {
placeholderContent.style.display = 'flex';
document.getElementById('statusMessage').className = 'status waiting';
- document.getElementById('statusMessage').textContent = 'Warte auf Unterschrift von der Signatur-Station...';
+ document.getElementById('statusMessage').textContent = t('master.waitingForSignature');
document.getElementById('downloadButton').disabled = true;
document.getElementById('downloadHint').style.display = 'none';
signatureDataUrl = null;
@@ -987,7 +1001,7 @@ document.getElementById('placeSignatureButton').addEventListener('click', async
// Show loading state
const btn = document.getElementById('placeSignatureButton');
const originalText = btn.textContent;
- btn.textContent = '⏳ PDF wird erstellt...';
+ btn.textContent = '⏳ ' + (currentLanguage === 'de' ? 'PDF wird erstellt...' : 'PDF is being created...');
btn.disabled = true;
// Add signature to PDF
@@ -1002,7 +1016,7 @@ document.getElementById('placeSignatureButton').addEventListener('click', async
// Update status
document.getElementById('statusMessage').className = 'status ready';
- document.getElementById('statusMessage').textContent = '✅ PDF wurde erstellt! Bereit zum Download.';
+ document.getElementById('statusMessage').textContent = t('master.pdfCreated');
// Enable download
document.getElementById('downloadButton').disabled = false;
@@ -1098,7 +1112,7 @@ document.getElementById('downloadButton').addEventListener('click', async () =>
// Reset place signature button
const placeBtn = document.getElementById('placeSignatureButton');
- placeBtn.textContent = '✓ Unterschrift platzieren & PDF erstellen';
+ placeBtn.textContent = t('master.placeSignature');
placeBtn.disabled = false;
// Reset page navigation and scrollable state
@@ -1165,7 +1179,7 @@ function resetToStart() {
// Reset status message
document.getElementById('statusMessage').className = 'status waiting';
- document.getElementById('statusMessage').textContent = 'Warte auf Unterschrift von der Signatur-Station...';
+ document.getElementById('statusMessage').textContent = t('master.waitingForSignature');
// Clear all data
removeSignature();
@@ -1222,5 +1236,29 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
+// Update translations when language changes (called from i18n.js)
+window.updateDynamicTranslationsCustom = function() {
+ // Update page info if visible
+ if (totalPages > 0) {
+ const pageInfo = document.getElementById('pageInfo');
+ if (pageInfo) {
+ const pageText = currentLanguage === 'de' ? 'Seite' : 'Page';
+ pageInfo.textContent = `${pageText} ${currentPageNum} ${currentLanguage === 'de' ? 'von' : 'of'} ${totalPages}`;
+ }
+ }
+
+ // Re-translate connection status
+ const statusIndicator = document.getElementById('statusIndicator');
+ if (statusIndicator) {
+ const isConnected = statusIndicator.classList.contains('connected');
+ updateConnectionStatus(isConnected);
+ }
+
+ // Update text input default value
+ if (typeof updateTextInputWithDate === 'function') {
+ updateTextInputWithDate();
+ }
+};
+
// Connect WebSocket on load
connectWebSocket();
diff --git a/js/signature.js b/js/signature.js
index 4d8c4bf..ad01792 100644
--- a/js/signature.js
+++ b/js/signature.js
@@ -128,7 +128,11 @@ async function renderPdfPreview(pdfBytes, pageNum = null) {
totalPages = pdfDoc.numPages;
// Update page info
- document.getElementById('pageInfoSignature').textContent = `${totalPages} Seite${totalPages > 1 ? 'n' : ''}`;
+ const pageText = currentLanguage === 'de' ? 'Seite' : 'Page';
+ const pagesText = currentLanguage === 'de' ? 'Seiten' : 'Pages';
+ document.getElementById('pageInfoSignature').textContent = totalPages === 1
+ ? `${pageText} 1 ${currentLanguage === 'de' ? 'von' : 'of'} 1`
+ : `${totalPages} ${pagesText}`;
// Get container
const container = document.getElementById('pdfPagesContainer');
@@ -141,13 +145,15 @@ async function renderPdfPreview(pdfBytes, pageNum = null) {
if (pageNum > 1) {
const separator = document.createElement('div');
separator.className = 'page-separator';
- separator.textContent = `Seite ${pageNum}`;
+ const pageText = currentLanguage === 'de' ? 'Seite' : 'Page';
+ separator.textContent = `${pageText} ${pageNum}`;
container.appendChild(separator);
} else {
// First page indicator
const separator = document.createElement('div');
separator.className = 'page-separator';
- separator.textContent = `Seite 1`;
+ const pageText = currentLanguage === 'de' ? 'Seite' : 'Page';
+ separator.textContent = `${pageText} 1`;
container.appendChild(separator);
}