@@ -6,12 +6,12 @@
{%- endif -%}
{%- endif -%}
{% endmacro %}
{% endmacro %}
<!doctype html>
<!doctype html>
< html lang = "de " >
< html lang = "{{ get_locale() }} " >
< head >
< head >
< meta charset = "utf-8" >
< meta charset = "utf-8" >
< title > {{ _('Elpatrons Datumsrechner – Open Source Kalender- und Datumsberechnungen') }}< / title >
< title > {{ _('Elpatrons Datumsrechner – Open Source Kalender- und Datumsberechnungen') }}< / title >
< meta name = "description" content = "{{ _('Elpatrons Datumsrechner: Open Source Web-App für Kalender- und Datumsberechnungen. Tage, Werktage, Wochen, Monate, Kalenderwochen, Wochentage und mehr berechnen – barrierefrei, werbefrei, trackingfrei, kostenlos.') }}" >
< meta name = "description" content = "{{ _('Elpatrons Datumsrechner: Open Source Web-App für Kalender- und Datumsberechnungen. Tage, Werktage, Wochen, Monate, Kalenderwochen, Wochentage und mehr berechnen – barrierefrei, werbefrei, trackingfrei, kostenlos.') }}" >
< meta name = "keywords" content = "{{ _('Datum, Kalender, Datumsrechner, Werktage, Tage zählen, Kalenderwoche, Wochentag, Open Source, Python, Flask, barrierefreiheit, barrierefrei, kostenlos, werbefrei, trackingfrei, cookiefrei') }}" >
< meta name = "keywords" content = "{{ _('Datum, Kalender, Datumsrechner, Werktage, Tage zählen, Kalenderwoche, Wochentag, Open Source, barrierefreiheit, barrierefrei, kostenlos, werbefrei, trackingfrei, cookiefrei, progressive web app, pwa ') }}" >
< meta property = "og:title" content = "{{ _('Elpatrons Datumsrechner – Open Source Kalender- und Datumsberechnungen') }}" >
< meta property = "og:title" content = "{{ _('Elpatrons Datumsrechner – Open Source Kalender- und Datumsberechnungen') }}" >
< meta property = "og:description" content = "{{ _('Open Source Web-App für Kalender- und Datumsberechnungen. Werbefrei, trackingfrei, kostenlos.') }}" >
< meta property = "og:description" content = "{{ _('Open Source Web-App für Kalender- und Datumsberechnungen. Werbefrei, trackingfrei, kostenlos.') }}" >
< meta property = "og:type" content = "website" >
< meta property = "og:type" content = "website" >
@@ -683,7 +683,8 @@ footer br + a {
function readAloud ( text , button ) {
function readAloud ( text , button ) {
// Stoppe vorherige Wiedergabe
// Stoppe vorherige Wiedergabe
if ( currentSpeech ) {
if ( currentSpeech ) {
currentSpeech . cancel ( ) ;
speechSynthesis . cancel ( ) ;
currentSpeech = null ;
}
}
// Entferne "playing" Klasse von allen Buttons
// Entferne "playing" Klasse von allen Buttons
@@ -692,12 +693,92 @@ footer br + a {
btn . textContent = '🔊' ;
btn . textContent = '🔊' ;
} ) ;
} ) ;
// Bestimme die aktuelle Sprache
let currentLang = 'de-DE' ; // Standard
// Methode 1: Prüfe URL-Parameter
const urlParams = new URLSearchParams ( window . location . search ) ;
const langParam = urlParams . get ( 'lang' ) ;
// Methode 2: Prüfe localStorage
const savedLang = localStorage . getItem ( 'preferred_language' ) ;
// Methode 3: Prüfe das HTML lang-Attribut
const htmlLang = document . documentElement . lang ;
// Debug-Ausgabe
console . log ( 'URL lang param:' , langParam ) ;
console . log ( 'Saved lang:' , savedLang ) ;
console . log ( 'HTML lang:' , htmlLang ) ;
// Verbesserte Spracherkennung - prüfe alle Quellen
if ( langParam === 'en' || savedLang === 'en' || htmlLang === 'en' ) {
// Prüfe, ob eine britische Stimme verfügbar ist
const voices = speechSynthesis . getVoices ( ) ;
const hasBritishVoice = voices . some ( voice => voice . lang === 'en-GB' ) ;
const hasAmericanVoice = voices . some ( voice => voice . lang === 'en-US' ) ;
if ( hasBritishVoice ) {
currentLang = 'en-GB' ;
console . log ( 'Setting language to British English:' , currentLang ) ;
} else if ( hasAmericanVoice ) {
currentLang = 'en-US' ;
console . log ( 'Setting language to American English:' , currentLang ) ;
} else {
// Fallback auf en-US, auch wenn keine Stimme verfügbar ist
currentLang = 'en-US' ;
console . log ( 'Setting language to English (no specific voice available):' , currentLang ) ;
}
} else {
console . log ( 'Setting language to German:' , currentLang ) ;
}
// Erstelle neue Sprachausgabe
// Erstelle neue Sprachausgabe
currentSpeech = new SpeechSynthesisUtterance ( text ) ;
currentSpeech = new SpeechSynthesisUtterance ( text ) ;
currentSpeech . lang = 'de-DE' ;
currentSpeech . lang = currentLang ;
currentSpeech . rate = 0.9 ;
currentSpeech . rate = 0.9 ;
currentSpeech . pitch = 1 ;
currentSpeech . pitch = 1 ;
// Versuche eine passende Stimme zu finden
const voices = speechSynthesis . getVoices ( ) ;
console . log ( 'Available voices:' , voices . map ( v => ` ${ v . name } ( ${ v . lang } ) ` ) ) ;
// Suche nach einer Stimme in der gewünschten Sprache
let preferredVoice = voices . find ( voice =>
voice . lang === currentLang
) ;
// Falls keine exakte Übereinstimmung, suche nach ähnlicher Sprache
if ( ! preferredVoice ) {
preferredVoice = voices . find ( voice =>
voice . lang . startsWith ( currentLang . split ( '-' ) [ 0 ] )
) ;
}
// Falls immer noch keine Stimme gefunden, suche nach englischen Stimmen für Englisch
if ( ! preferredVoice && ( currentLang === 'en-US' || currentLang === 'en-GB' ) ) {
// Bevorzuge britische Stimmen für en-GB
if ( currentLang === 'en-GB' ) {
preferredVoice = voices . find ( voice =>
voice . lang === 'en-GB' || voice . name . toLowerCase ( ) . includes ( 'british' )
) ;
}
// Falls keine britische Stimme, suche nach amerikanischen oder allgemeinen englischen Stimmen
if ( ! preferredVoice ) {
preferredVoice = voices . find ( voice =>
voice . lang . includes ( 'en' ) || voice . name . toLowerCase ( ) . includes ( 'english' )
) ;
}
}
if ( preferredVoice ) {
currentSpeech . voice = preferredVoice ;
console . log ( 'Using voice:' , preferredVoice . name , 'for language:' , currentLang ) ;
} else {
console . log ( 'No specific voice found for language:' , currentLang , '- using default' ) ;
}
// Button-Status aktualisieren
// Button-Status aktualisieren
button . classList . add ( 'playing' ) ;
button . classList . add ( 'playing' ) ;
button . textContent = '⏹️' ;
button . textContent = '⏹️' ;
@@ -720,6 +801,16 @@ footer br + a {
}
}
function readAloudFromElement ( button ) {
function readAloudFromElement ( button ) {
// Prüfe, ob bereits eine Wiedergabe läuft
if ( currentSpeech && speechSynthesis . speaking ) {
// Stoppe die aktuelle Wiedergabe
speechSynthesis . cancel ( ) ;
currentSpeech = null ;
button . classList . remove ( 'playing' ) ;
button . textContent = '🔊' ;
return ;
}
// Finde das Ergebnis-Element (das div mit class="result")
// Finde das Ergebnis-Element (das div mit class="result")
const resultElement = button . closest ( '.result' ) ;
const resultElement = button . closest ( '.result' ) ;
if ( ! resultElement ) return ;
if ( ! resultElement ) return ;
@@ -736,7 +827,7 @@ footer br + a {
function stopReading ( ) {
function stopReading ( ) {
if ( currentSpeech ) {
if ( currentSpeech ) {
currentSpeech . cancel ( ) ;
speechSynthesis . cancel ( ) ;
currentSpeech = null ;
currentSpeech = null ;
}
}
document . querySelectorAll ( '.read-aloud-btn' ) . forEach ( btn => {
document . querySelectorAll ( '.read-aloud-btn' ) . forEach ( btn => {
@@ -746,6 +837,13 @@ footer br + a {
}
}
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
// Stelle sicher, dass die Stimmen geladen sind
if ( speechSynthesis . onvoiceschanged !== undefined ) {
speechSynthesis . onvoiceschanged = function ( ) {
console . log ( 'Voices loaded:' , speechSynthesis . getVoices ( ) . length ) ;
} ;
}
// Prüfe localStorage für gespeicherte Sprachauswahl
// Prüfe localStorage für gespeicherte Sprachauswahl
const savedLanguage = localStorage . getItem ( 'preferred_language' ) ;
const savedLanguage = localStorage . getItem ( 'preferred_language' ) ;
if ( savedLanguage && ! window . location . search . includes ( 'lang=' ) ) {
if ( savedLanguage && ! window . location . search . includes ( 'lang=' ) ) {
@@ -881,7 +979,7 @@ footer br + a {
< / form >
< / form >
{% if tage is not none %}
{% if tage is not none %}
< div class = "result" aria-live = "polite" >
< div class = "result" aria-live = "polite" >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "Ergebnis vorlesen" title = "Ergebnis vorlesen" tabindex = "0" > 🔊< / button >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "{{ _(' Ergebnis vorlesen') }} " title = "{{ _(' Ergebnis vorlesen') }} " tabindex = "0" > 🔊< / button >
{% if request.form.get('werktage') %}
{% if request.form.get('werktage') %}
{{ _('Anzahl der Werktage zwischen') }} < b > {{ format_date(request.form.get('start1', '')) }}< / b > {{ _('und') }} < b > {{ format_date(request.form.get('end1', '')) }}:< / b > {% if request.form.get('bundesland') %} {{ _('(Feiertage:') }} {{ request.form.get('bundesland') }}){% endif %}: {{ tage }}
{{ _('Anzahl der Werktage zwischen') }} < b > {{ format_date(request.form.get('start1', '')) }}< / b > {{ _('und') }} < b > {{ format_date(request.form.get('end1', '')) }}:< / b > {% if request.form.get('bundesland') %} {{ _('(Feiertage:') }} {{ request.form.get('bundesland') }}){% endif %}: {{ tage }}
{% else %}
{% else %}
@@ -923,7 +1021,7 @@ footer br + a {
< / form >
< / form >
{% if wochentag is not none %}
{% if wochentag is not none %}
< div class = "result" aria-live = "polite" >
< div class = "result" aria-live = "polite" >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "Ergebnis vorlesen" title = "Ergebnis vorlesen" tabindex = "0" > 🔊< / button >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "{{ _(' Ergebnis vorlesen') }} " title = "{{ _(' Ergebnis vorlesen') }} " tabindex = "0" > 🔊< / button >
{{ _('Wochentag von') }} < b > {{ format_date(request.form.get('datum3', '')) }}< / b > : {{ wochentag }}
{{ _('Wochentag von') }} < b > {{ format_date(request.form.get('datum3', '')) }}< / b > : {{ wochentag }}
< / div >
< / div >
{% endif %}
{% endif %}
@@ -949,7 +1047,7 @@ footer br + a {
< / form >
< / form >
{% if kw_berechnen is not none %}
{% if kw_berechnen is not none %}
< div class = "result" aria-live = "polite" >
< div class = "result" aria-live = "polite" >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "Ergebnis vorlesen" title = "Ergebnis vorlesen" tabindex = "0" > 🔊< / button >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "{{ _(' Ergebnis vorlesen') }} " title = "{{ _(' Ergebnis vorlesen') }} " tabindex = "0" > 🔊< / button >
{{ _('Kalenderwoche von') }} < b > {{ format_date(request.form.get('datum6', '')) }}< / b > : {{ kw_berechnen }}
{{ _('Kalenderwoche von') }} < b > {{ format_date(request.form.get('datum6', '')) }}< / b > : {{ kw_berechnen }}
< / div >
< / div >
{% endif %}
{% endif %}
@@ -975,7 +1073,7 @@ footer br + a {
< / form >
< / form >
{% if kw_datum is not none %}
{% if kw_datum is not none %}
< div class = "result" aria-live = "polite" >
< div class = "result" aria-live = "polite" >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "Ergebnis vorlesen" title = "Ergebnis vorlesen" tabindex = "0" > 🔊< / button >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "{{ _(' Ergebnis vorlesen') }} " title = "{{ _(' Ergebnis vorlesen') }} " tabindex = "0" > 🔊< / button >
{{ _('Start-/Enddatum der KW') }} < b > {{ request.form.get('kw7', '') }}< / b > {{ _('im Jahr') }} < b > {{ request.form.get('jahr7', '') }}< / b > : {{ kw_datum }}
{{ _('Start-/Enddatum der KW') }} < b > {{ request.form.get('kw7', '') }}< / b > {{ _('im Jahr') }} < b > {{ request.form.get('jahr7', '') }}< / b > : {{ kw_datum }}
< / div >
< / div >
{% endif %}
{% endif %}
@@ -1021,7 +1119,7 @@ footer br + a {
< / form >
< / form >
{% if plusminus_result is not none %}
{% if plusminus_result is not none %}
< div class = "result" aria-live = "polite" >
< div class = "result" aria-live = "polite" >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "Ergebnis vorlesen" title = "Ergebnis vorlesen" tabindex = "0" > 🔊< / button >
< button type = "button" class = "read-aloud-btn" onclick = "readAloudFromElement(this)" onkeydown = "if(event.key==='Enter'||event.key===' ') { event.preventDefault(); readAloudFromElement(this); }" aria-label = "{{ _(' Ergebnis vorlesen') }} " title = "{{ _(' Ergebnis vorlesen') }} " tabindex = "0" > 🔊< / button >
{{ plusminus_result }}
{{ plusminus_result }}
< / div >
< / div >
{% endif %}
{% endif %}