{"id":8455,"date":"2025-05-10T10:09:03","date_gmt":"2025-05-10T10:09:03","guid":{"rendered":"https:\/\/textsnapper.com\/?page_id=8455"},"modified":"2025-06-29T04:29:27","modified_gmt":"2025-06-29T04:29:27","slug":"tts","status":"publish","type":"page","link":"https:\/\/textsnapper.com\/en\/tts\/","title":{"rendered":"tts"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"8455\" class=\"elementor elementor-8455\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f042cbc e-flex e-con-boxed e-con e-parent\" data-id=\"f042cbc\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6d06b84 elementor-widget elementor-widget-heading\" data-id=\"6d06b84\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Text-to-Speech with SSML<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1a3bf8a elementor-widget elementor-widget-text-editor\" data-id=\"1a3bf8a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>What is SSML and how do the buttons work?<\/h3><p><strong>SSML<\/strong>\u00a0(<em>Speech Synthesis Markup Language<\/em>) is a special markup language that allows you to influence how text is read aloud by the artificial voice. For example, you can insert pauses, switch voices, or emphasize certain words.<br \/>Here's a description: <span style=\"text-decoration: underline;\"><a href=\"#SSML-Buttons\">SSML Button Description<\/a><\/span><\/p><p>Activate marking mode to specifically select words. Click on one or more words, then use a function button to perform the desired action on the highlighted text.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-430e5dd elementor-widget elementor-widget-html\" data-id=\"430e5dd\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\n<!-- Elementor-kompatibles HTML-Widget -->\n<div id=\"ttsWidget\" style=\"font-family: Arial, sans-serif; background: #f9f9f9; padding: 20px; border-radius: 8px; text-align: center;\" data-no-translation=\"\">\n  <div style=\"margin-bottom: 8px;\">\n  <button id=\"toggleEditModeBtn\" class=\"btn alt-btn\" type=\"button\" style=\"background:#2F5591;\">\n    \ud83d\udd8a\ufe0f Markierungsmodus aktivieren\n  <\/button>\n<\/div>\n\n  <div id=\"ssmlInput\" contenteditable=\"true\" style=\"width:100%; min-height:150px; padding:10px; border-radius:4px; border:1px solid #ccc; background:#fff; box-sizing:border-box; text-align:left;\"><\/div>\n\n\n  <button id=\"detectLangBtn\" style=\"background:#607D8B; color:white; padding:8px 12px; border:none; border-radius:4px; cursor:pointer; margin-top:10px;\">\n    \ud83c\udf10 <span id=\"detectLangBtnLabel\">Sprache erkennen<\/span>\n  <\/button>\n\n  <div class=\"ssml-buttons\" style=\"margin-top:15px;\">\n\n    <button onclick=\"applySSMLTagInteractive('voice')\" class=\"ssml-btn\">\ud83c\udfa4 Voice<\/button>\n  <button onclick=\"applySSMLTagInteractive('break')\" class=\"ssml-btn\">\u23f8\ufe0f Break<\/button>\n  <button onclick=\"applySSMLTagInteractive('prosody')\" class=\"ssml-btn\">\ud83c\udf9b\ufe0f Prosody<\/button>\n  <button onclick=\"applySSMLTagInteractive('say-as')\" class=\"ssml-btn\">\ud83d\udcac Say-as<\/button>\n  <button onclick=\"applySSMLTagInteractive('sub')\" class=\"ssml-btn\">\ud83d\udd04 Substitution<\/button>\n  <button onclick=\"applySSMLTagInteractive('audio')\" class=\"ssml-btn\">\ud83d\udd0a Audio<\/button>\n  <button onclick=\"applySSMLTagInteractive('emphasis')\" class=\"ssml-btn\">\u2728 Emphasis<\/button>\n  <button onclick=\"applySSMLTagInteractive('prosody-pitch')\" class=\"ssml-btn\">\ud83c\udfb5 Pitch<\/button>\n  <button onclick=\"applySSMLTagInteractive('prosody-volume')\" class=\"ssml-btn\">\ud83d\udd08 Volume<\/button>\n  <button onclick=\"applySSMLTagInteractive('google-cheerful')\" class=\"ssml-btn\">\ud83d\ude0a Cheerful<\/button>\n  <button onclick=\"applySSMLTagInteractive('google-conversational')\" class=\"ssml-btn\">\ud83d\udcac Conversational<\/button>\n  <button onclick=\"applySSMLTagInteractive('google-news')\" class=\"ssml-btn\">\ud83d\udcf0 News<\/button>\n  <\/div>\n\n  <div class=\"controls\">\n    <button id=\"playBtn\" class=\"action-btn\">\u25b6\ufe0f Vorlesen<\/button>\n    <button id=\"stopBtn\" class=\"action-btn stop\">\u23f9\ufe0f Stop<\/button>\n  <\/div>\n\n  <div id=\"extraButtons\" style=\"margin-top:15px; text-align:center;\">\n    <button id=\"saveTextBtn\" class=\"btn alt-btn\">\ud83d\udcbe <span id=\"saveTextBtnLabel\">Text speichern<\/span><\/button>\n    <button id=\"saveAudioBtn\" class=\"btn alt-btn\">\ud83d\udcbe <span id=\"saveAudioBtnLabel\">Audio speichern<\/span><\/button>\n  <\/div>\n\n  <div id=\"ttsSettings\" style=\"margin-top:15px; display:flex; flex-direction:column; align-items:center;\">\n    <div style=\"width:auto; text-align:center;\">\n      <div style=\"margin-bottom:10px;\">\n        <label for=\"languageSelect\" id=\"labelSprache\">Sprache:<\/label><br>\n        <select id=\"languageSelect\" style=\"padding:8px; width:250px;\">\n          <option disabled selected><\/option>\n        <\/select>\n      <\/div>\n\n      <div style=\"margin-bottom:10px;\">\n        <label for=\"dialectSelect\" id=\"labelDialekt\">Dialekt:<\/label><br>\n        <select id=\"dialectSelect\" style=\"padding:8px; width:250px;\">\n          <option disabled selected><\/option>\n        <\/select>\n      <\/div>\n\n      <div style=\"margin-bottom:10px;\">\n        <label for=\"genderSelect\" id=\"labelGeschlecht\">Geschlecht:<\/label><br>\n        <select id=\"genderSelect\" style=\"padding:8px; width:250px;\">\n          <option disabled selected><\/option>\n        <\/select>\n      <\/div>\n\n      <div style=\"margin-bottom:10px;\">\n        <label for=\"qualitySelect\" id=\"labelQualitaet\">Qualit\u00e4t:<\/label><br>\n        <select id=\"qualitySelect\" style=\"padding:8px; width:250px;\">\n          <option disabled selected><\/option>\n        <\/select>\n      <\/div>\n\n      <div>\n        <label for=\"voiceSelect\" id=\"labelStimme\">Stimme:<\/label><br>\n        <select id=\"voiceSelect\" style=\"padding:8px; width:250px;\">\n          <option disabled selected><\/option>\n        <\/select>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n\n<style>\n.ssml-btn {\n  padding: 10px 15px;\n  border-radius: 4px;\n  color: #fff;\n  min-width: 120px;\n  text-align: center;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 14px;\n  cursor: pointer;\n  white-space: nowrap;\n}\n\nbutton[onclick^=\"applySSMLTagInteractive\"] {\n  flex: 0 1 auto;\n}\n\n.controls {\n  display: flex;\n  justify-content: center;\n  gap: 35px;\n  flex-wrap: wrap;\n  margin-top: 60px;\n  margin-bottom: 60px;\n}\n\n#extraButtons, .ssml-buttons {\n  display: flex;\n  justify-content: center;\n  gap: 10px;\n  flex-wrap: wrap;\n}\n\n\n\n.action-btn, .btn.alt-btn, #detectLangBtn {\n  padding: 10px 20px;\n  min-width: 140px;\n  font-size: 15px;\n  border-radius: 4px;\n  cursor: pointer;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n}\n\n#ttsWidget {\n  max-width: 800px;\n  margin: 0 auto;\n}\n\n#ttsSettings select {\n  width: 250px;\n  padding: 8px;\n  box-sizing: border-box;\n  text-align: center;\n}\n\nlabel {\n  font-weight: bold;\n  font-size: 14px;\n}\n\n  #ssmlInput span.word { cursor: pointer; }\n  #ssmlInput span.selected { background-color: #D0E9FF; }\n  .btn.alt-btn {\n    background-color: #333333 !important;\n    color: #fff;\n  }\n\n  .btn.alt-btn:hover {\n    background-color: #435943 !important;\n  }\n  #ssmlInput span.space {\n  cursor: pointer;\n}\n#ssmlInput span.space.selected {\n  background-color: #FFEB3B; \/* Gelbe Markierung *\/\n}\n.ssml-btn {\n  padding:8px; border-radius:4px; color:#fff;\n}\n.action-btn {\n  background:#009688; color:white; padding:10px 15px; border:none; border-radius:4px; cursor:pointer;\n}\n.action-btn.stop {\n  background:#f44336;\n}\n\n#toggleEditModeBtn {\n  margin-bottom: 12px;\n  background: #2F5591;\n  color: #fff;\n  font-weight: bold;\n  border: none;\n  border-radius: 4px;\n  padding: 10px 18px;\n  cursor: pointer;\n  transition: background 0.2s;\n}\n#toggleEditModeBtn.active {\n  background: #607D8B;\n}\n\n\/* SSML-Funktionsbuttons (Voice, Break, Prosody, Say-as, usw.) *\/\nbutton[onclick^=\"applySSMLTagInteractive\"] {\n  padding: 8px;\n  border-radius: 4px;\n  color: #fff;\n  border: none;\n  cursor: pointer;\n}\n\n\/* \u25b6\ufe0f Vorlesen Button (Originalfarbe bleibt erhalten) *\/\nbutton[onclick=\"speakText()\"] {\n  padding: 10px 15px;\n  border-radius: 4px;\n  border: none;\n  color: #fff;\n  cursor: pointer;\n}\n\n\/* \u23f9\ufe0f Stop Button (Originalfarbe bleibt erhalten) *\/\nbutton[onclick=\"stopSpeech()\"] {\n  padding: 10px 15px;\n  border-radius: 4px;\n  border: none;\n  color: #fff;\n  cursor: pointer;\n}\n\/* Farben der SSML-Tag Buttons entsprechend Vorgabe *\/\nbutton[onclick=\"applySSMLTagInteractive('voice')\"] {\n  background-color: #673AB7 !important;\n  color: #fff;\n}\n\nbutton[onclick=\"applySSMLTagInteractive('break')\"] {\n  background-color: #3F51B5 !important;\n  color: #fff;\n}\n\nbutton[onclick=\"applySSMLTagInteractive('prosody')\"] {\n  background-color: #2196F3 !important;\n  color: #fff;\n}\n\nbutton[onclick=\"applySSMLTagInteractive('say-as')\"] {\n  background-color: #00BCD4 !important;\n  color: #fff;\n}\n\nbutton[onclick=\"applySSMLTagInteractive('sub')\"] {\n  background-color: #009688 !important;\n  color: #fff;\n}\n\nbutton[onclick=\"applySSMLTagInteractive('audio')\"] {\n  background-color: #FFC107 !important;\n  color: #fff;\n}\n\nbutton[onclick=\"applySSMLTagInteractive('emphasis')\"] {\n  background-color: #FF5722 !important;\n  color: #fff;\n}\n\nbutton[onclick=\"applySSMLTagInteractive('prosody-rate')\"] {\n  background-color: #4CAF50 !important; color: #fff;\n}\nbutton[onclick=\"applySSMLTagInteractive('prosody-pitch')\"] {\n  background-color: #795548 !important; color: #fff;\n}\nbutton[onclick=\"applySSMLTagInteractive('prosody-volume')\"] {\n  background-color: #607D8B !important; color: #fff;\n}\nbutton[onclick^=\"applySSMLTagInteractive('google-')\"] {\n  background-color: #FF9800 !important; color: #fff;\n}\n\n\n\n\/* \ud83d\udcbe Text speichern & \ud83d\udcbe Audio speichern Buttons (Originalfarbe bleibt erhalten) *\/\n.btn.alt-btn {\n  background-color: #333333 !important;\n  color: #fff;\n  padding: 10px 15px;\n  border-radius: 4px;\n  cursor: pointer;\n}\n\n.btn.alt-btn:hover {\n  background-color: #435943 !important;\n}\n\n\/* Popup Buttons (Einf\u00fcgen & Abbrechen) *\/\n#ssmlPopup button {\n  padding: 6px 12px;\n  border-radius: 2px;\n  border: none;\n  color: #fff;\n  cursor: pointer;\n}\n\n\/* Optional: Entfernt standardm\u00e4\u00dfige Browser-Styles f\u00fcr alle Buttons *\/\nbutton {\n  outline: none;\n}\n#ssmlPopup {\n  display: none;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%,-50%);\n  background: #fff; \n  border: 2px solid #666;\n  padding: 15px; \n  border-radius: 6px;\n  box-shadow: 0 4px 8px rgba(0,0,0,0.3);\n  z-index: 9999;\n}\n\n#ssmlPopupContent select,\n#ssmlPopupContent input {\n  width: 100%;\n  padding: 6px;\n  margin-top: 8px;\n  margin-bottom: 8px;\n  box-sizing: border-box;\n}\n\n#ssmlPopup button {\n  padding: 6px 12px;\n  margin-top: 10px;\n  border: none;\n  border-radius: 2px;\n  cursor: pointer;\n}\n\n#ssmlPopup button#ssmlInsertBtn {\n  background: #2F5591;\n  color: #fff;\n}\n\n#ssmlPopup button#ssmlCancelBtn {\n  background: #555;\n  color: #fff;\n}\n\n\n#ssmlInput span.word,\n#ssmlInput span.space {\n  user-select: text;\n  -webkit-user-select: text;\n}\n\n#ssmlInput span.space:hover {\n  background-color: #ffe066;\n  cursor: pointer;\n}\n\n<\/style>\n  \n<!-- Popup f\u00fcr SSML-Optionen -->\n<!-- Popup f\u00fcr SSML-Optionen -->\n<div id=\"ssmlPopup\" style=\"  display:none;  position:fixed;  top:50%; left:50%;  transform:translate(-50%,-50%);  background:#fff;   border:2px solid #666;  padding:15px;   border-radius:6px;  box-shadow:0 4px 8px rgba(0,0,0,0.3);  z-index:9999;\">\n\n  <h4 id=\"ssmlPopupTitle\">Option ausw\u00e4hlen<\/h4>\n  <div id=\"ssmlPopupContent\"><\/div>\n\n  <button id=\"ssmlInsertBtn\" style=\"padding:6px 12px; margin-top:10px; border:none; border-radius:2px; background:#2F5591; color:#fff; cursor:pointer;\">\n    Einf\u00fcgen\n  <\/button>\n  <button id=\"ssmlCancelBtn\" style=\"padding:6px 12px; margin-top:10px; border:none; border-radius:2px; background:#555; color:#fff; cursor:pointer;\">\n    Abbrechen\n  <\/button>\n<\/div>\n\n<div id=\"chirpWarning\" style=\"display:none; margin-top:10px; color:#D32F2F; font-weight:bold; text-align:center;\">\n  \u26a0\ufe0f Hinweis: Chirp- und Chirp3-Stimmen unterst\u00fctzen derzeit keine SSML-Tags.\n<\/div>\n<div id=\"ssmlPopupOverlay\" style=\"  display: none;   position: fixed;   top: 0;   left: 0;   width: 100%;   height: 100%;   background: rgba(0,0,0,0.4);   z-index: 9998;\">\n<\/div>\n\n\n\n  <script>\n    let translationsEn = {};\n    let lastAudioContent = null;\n    let currentTag = '';\n    let googleStimmen = [];\n    let languageDetectionTimeout;\n    let availableVoices = {};\n    let audioPlayer = new Audio();\n    let markMode = false;\n    const ssmlInput = document.getElementById('ssmlInput');\n    const toggleEditModeBtn = document.getElementById('toggleEditModeBtn');\n    toggleEditModeBtn.addEventListener('click', function() {\n  markMode = !markMode;\n  setEditMarkMode(markMode);\n});\n\n\n    audioPlayer.crossOrigin = \"anonymous\";\n    \n    const translations = {\n  \"de\": {\n    \"sprache\": \"Sprache w\u00e4hlen\",\n    \"dialekt\": \"Dialekt w\u00e4hlen\",\n    \"geschlecht\": \"Geschlecht w\u00e4hlen\",\n    \"qualitaet\": \"Qualit\u00e4t w\u00e4hlen\",\n    \"stimme\": \"Stimme w\u00e4hlen\",\n    \"labelSprache\": \"Sprache:\",\n    \"labelDialekt\": \"Dialekt:\",\n    \"labelGeschlecht\": \"Geschlecht:\",\n    \"labelQualitaet\": \"Qualit\u00e4t:\",\n    \"labelStimme\": \"Stimme:\",\n    \"starkeBetonung\": \"Starke Betonung\",\n    \"moderateBetonung\": \"Moderate Betonung\",\n    \"schwacheBetonung\": \"Schwache Betonung\",\n    \"vorlesen\": \"\u25b6\ufe0f Vorlesen\",\n    \"stop\": \"\u23f9\ufe0f Stop\",\n    \"spracheErkennen\": \" Sprache erkennen\",\n    \"placeholder\": \"Schreibe hier deinen Text oder f\u00fcge SSML-Tags hinzu...\",\n    \"weiblich\": \"WEIBLICH\",\n    \"maennlich\": \"M\u00c4NNLICH\",\n    \"chooseGender\": \"Geschlecht w\u00e4hlen\",\n    \"chooseQuality\": \"Qualit\u00e4t w\u00e4hlen\",\n    \"chooseVoice\": \"Stimme w\u00e4hlen\",\n    \"editMode\": \"\u270f\ufe0f Bearbeitungsmodus aktivieren\",\n    \"markMode\": \"\ud83d\udd8a\ufe0f Markierungsmodus aktivieren\",\n    \"detectLang\": \"\ud83c\udf10 Sprache erkennen\",\n    \"saveText\": \" Text speichern\",\n    \"saveAudio\": \" Audio speichern\",\n    \"labelDialekt\": \"Dialekt:\",\n    \"labelGeschlecht\": \"Geschlecht:\",\n    \"labelQualitaet\": \"Qualit\u00e4t:\",\n    \"labelStimme\": \"Stimme:\"\n  },\n  \"en\": {\n    \"sprache\": \"Select Language\",\n    \"dialekt\": \"Select Dialect\",\n    \"geschlecht\": \"Select Gender\",\n    \"qualitaet\": \"Select Quality\",\n    \"stimme\": \"Select Voice\",\n    \"labelSprache\": \"Language:\",\n    \"labelDialekt\": \"Dialect:\",\n    \"labelGeschlecht\": \"Gender:\",\n    \"labelQualitaet\": \"Quality:\",\n    \"labelStimme\": \"Voice:\",\n    \"starkeBetonung\": \"Strong emphasis\",\n    \"moderateBetonung\": \"Moderate emphasis\",\n    \"schwacheBetonung\": \"Reduced emphasis\",\n    \"vorlesen\": \"\u25b6\ufe0f Play\",\n    \"stop\": \"\u23f9\ufe0f Stop\",\n    \"spracheErkennen\": \" Detect Language\",\n    \"placeholder\": \"Write your text here or add SSML tags...\",\n    \"weiblich\": \"FEMALE\",\n    \"maennlich\": \"MALE\",\n    \"chooseGender\": \"Select Gender\",\n    \"chooseQuality\": \"Select Quality\",\n    \"chooseVoice\": \"Select Voice\",\n    \"editMode\": \"\u270f\ufe0f Activate editing mode\",\n    \"markMode\": \"\ud83d\udd8a\ufe0f Activate marking mode\",\n    \"detectLang\": \"\ud83c\udf10 Detect language\",\n    \"saveText\": \" Save Text\",\n    \"saveAudio\": \" Save Audio\",\n    \"labelDialekt\": \"Dialect:\",\n    \"labelGeschlecht\": \"Gender:\",\n    \"labelQualitaet\": \"Quality:\",\n    \"labelStimme\": \"Voice:\"\n  }\n};\n\n\nconst currentLang = document.documentElement.lang.startsWith(\"en\") ? \"en\" : \"de\";\n\nfunction setEditMarkMode(isMark) {\n  const lang = document.documentElement.lang.startsWith('en') ? 'en' : 'de';\n\n  if (isMark) {\n    ssmlInput.setAttribute('contenteditable', 'false');\n    toggleEditModeBtn.textContent = translations[lang].editMode;\n    toggleEditModeBtn.classList.add('active');\n    wrapWordsForMarking();\n  } else {\n    ssmlInput.setAttribute('contenteditable', 'true');\n    toggleEditModeBtn.textContent = translations[lang].markMode;\n    toggleEditModeBtn.classList.remove('active');\n    let newText = ssmlInput.innerText.replace(\/\\u200B\/g, \"\");\n    ssmlInput.innerHTML = '';\n    ssmlInput.textContent = newText;\n  }\n}\n\n\nfunction wrapWordsForMarking() {\n  const text = ssmlInput.innerText.trim();\n  const wordsAndSpaces = text.split(\/(\\s+)\/);\n  ssmlInput.innerHTML = wordsAndSpaces.map(seg =>\n    \/^\\s+$\/.test(seg)\n      ? `<span class=\"space\">${seg}<\/span>`\n      : `<span class=\"word\">${seg}<\/span>`\n  ).join('');\n\n  \/\/ Markierfunktion pro Wort aktivieren\n  ssmlInput.querySelectorAll('span.word, span.space').forEach(span => {\n    span.onclick = function(e) {\n      e.stopPropagation();\n      this.classList.toggle('selected');\n    };\n    span.ontouchend = function(e) {\n      e.stopPropagation();\n      this.classList.toggle('selected');\n    };\n  });\n}\n\n    \n    async function fetchVoices() {\n      let res = await fetch(window.ajaxurl, {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application\/x-www-form-urlencoded\" },\n        body: new URLSearchParams({ action: \"get_available_voices\" })\n      });\n      let data = await res.json();\n      if (data.success) availableVoices = data.data;\n      fillLanguageDropdown();\n    }\n\nfunction fillLanguageDropdown() {\n  const langSelect = document.getElementById('languageSelect');\n  langSelect.innerHTML = `<option disabled selected>${translations[currentLang].sprache}<\/option>`;\n\n  const languages = new Set();\n  const languageNames = new Intl.DisplayNames([currentLang], { type: 'language' });\n\n  googleStimmen.forEach(item => {\n    const langCode = item.Translation_Code.split('-')[0]; \/\/ z.B. \"de\" aus \"de-DE\"\n    \n    if (!languages.has(langCode)) {\n      languages.add(langCode);\n      const translatedLanguage = languageNames.of(langCode);\n      langSelect.appendChild(new Option(translatedLanguage, langCode));\n    }\n  });\n\n  langSelect.onchange = () => fillDialectDropdown(langSelect.value);\n}\n\n\nfunction fillDialectDropdown(langCode) {\n  const dialectSelect = document.getElementById('dialectSelect');\n  dialectSelect.innerHTML = `<option disabled selected>${translations[currentLang].dialekt}<\/option>`;\n\n  const dialects = new Set();\n  const regionNames = new Intl.DisplayNames([currentLang], { type: 'region' });\n\n  googleStimmen.filter(v => v.Translation_Code === langCode)\n    .forEach(item => {\n      let dialectName = item.Dialekt_Name;\n\n      if (currentLang === 'en') {\n        const languageName = translationsEn[langCode]?.en || item.Sprache.split('(')[0].trim();\n        \n        dialectName = dialectName.replace(item.Sprache.split('(')[0].trim(), languageName);\n\n        \/\/ Land anhand ISO-Codes \u00fcbersetzen (aus Dialektcode z.B. de-DE, en-US etc.)\n        const countryMatch = item.code.match(\/-([A-Z]{2})$\/);\n        if (countryMatch && countryMatch[1]) {\n          const countryCode = countryMatch[1];\n          const translatedCountry = regionNames.of(countryCode);\n          dialectName = `${languageName} (${translatedCountry})`;\n        }\n      }\n\n      if (!dialects.has(dialectName)) {\n        dialects.add(dialectName);\n        dialectSelect.appendChild(new Option(dialectName, item.code));\n      }\n    });\n\n  dialectSelect.onchange = () => fillGenderDropdown(dialectSelect.value);\n}\n\n\n\nfunction fillGenderDropdown(dialectCode) {\n  const genderSelect = document.getElementById('genderSelect');\n  genderSelect.innerHTML = `<option disabled selected>${translations[currentLang].chooseGender}<\/option>`;\n\n  const genders = new Set(googleStimmen.filter(v => v.code === dialectCode).map(v => v.Geschlecht));\n\n  genders.forEach(g => {\n    let genderTranslated;\n    if (currentLang === 'en') {\n      if (g.toLowerCase() === 'weiblich') genderTranslated = translationsEn.ui.gender.female.en;\n      else if (g.toLowerCase() === 'm\u00e4nnlich') genderTranslated = translationsEn.ui.gender.male.en;\n      else genderTranslated = translationsEn.ui.gender.any.en;\n    } else {\n      genderTranslated = translations[currentLang][g.toLowerCase()] || g;\n    }\n\n    genderSelect.appendChild(new Option(genderTranslated, g));\n  });\n\n  genderSelect.onchange = () => fillQualityDropdown(dialectCode, genderSelect.value);\n}\n\n\nfunction fillQualityDropdown(dialectCode, gender) {\n  const qualitySelect = document.getElementById('qualitySelect');\n  qualitySelect.innerHTML = `<option disabled selected>${translations[currentLang].chooseQuality}<\/option>`;\n\n  const qualities = new Set(\n    googleStimmen\n      .filter(v => v.code === dialectCode && v.Geschlecht === gender)\n      .map(v => v.Qualit\u00e4t)\n  );\n\n  qualities.forEach(q => qualitySelect.appendChild(new Option(q, q)));\n\n  qualitySelect.onchange = () => fillVoiceDropdown(dialectCode, gender, qualitySelect.value);\n}\n\n\nfunction fillVoiceDropdown(dialectCode, gender, quality) {\n  const voiceSelect = document.getElementById('voiceSelect');\n  voiceSelect.innerHTML = `<option disabled selected>${translations[currentLang].chooseVoice}<\/option>`;\n\n  googleStimmen.filter(v =>\n    v.code === dialectCode &&\n    v.Geschlecht === gender &&\n    v.Qualit\u00e4t === quality\n  ).forEach(v => {\n    voiceSelect.appendChild(new Option(v.code2, v.code2));\n  });\n\n  voiceSelect.onchange = () => {\n    const chirpWarning = document.getElementById('chirpWarning');\n    if (isChirpVoice(voiceSelect.value)) {\n      chirpWarning.style.display = 'block';\n    } else {\n      chirpWarning.style.display = 'none';\n    }\n  };\n}\n\n\n\/\/ Bestehende speakText() erweitern, um Audio zu speichern\nasync function speakText() {\n  const ssmlInputDiv = document.getElementById('ssmlInput');\n  let ssml = ssmlInputDiv.innerHTML;\n\n  ssml = ssml\n    .replace(\/<span.*?>(.*?)<\\\/span>\/g, '$1')\n    .replace(\/&lt;\/g, '<')\n    .replace(\/&gt;\/g, '>')\n    .replace(\/&nbsp;\/g, ' ')\n    .replace(\/&amp;\/g, '&')\n    .trim();\n\n  const voiceSelect = document.getElementById('voiceSelect');\n  const qualitySelect = document.getElementById('qualitySelect'); \n  const defaultVoiceId = voiceSelect.value;\n  const voiceQuality = qualitySelect.value.toLowerCase();   \n\n  if (!defaultVoiceId) {\n    alert(currentLang === 'de' ? \"Bitte eine Stimme w\u00e4hlen.\" : \"Please select a voice.\");\n    return;\n  }\n\n  const parts = parseVoiceSSML(ssml, defaultVoiceId);\n  \n  for (const part of parts) {\n    const useSSML = \/<\\\/?[a-z][\\s\\S]*>\/i.test(part.text.trim());\n\n    const fd = new URLSearchParams({\n      action: \"process_tts\",\n      voiceId: part.voiceId,\n      voice_quality: voiceQuality,\n      [useSSML && !isChirpVoice(part.voiceId) ? 'ssml' : 'text']: useSSML && !isChirpVoice(part.voiceId) ? `<speak>${part.text}<\/speak>` : part.text\n    });\n\n    try {\n      const res = await fetch(window.ajaxurl, {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application\/x-www-form-urlencoded\" },\n        body: fd\n      });\n\n      if (!res.ok) {\n        alert(`Fehler beim Vorlesen: ${res.statusText}`);\n        return;\n      }\n\n      const result = await res.json();\n\n      if (result.success && result.data.audioContent) {\n        lastAudioContent = result.data.audioContent;  \/\/ Speichern f\u00fcr sp\u00e4ter\n        audioPlayer.src = \"data:audio\/mp3;base64,\" + result.data.audioContent;\n        await audioPlayer.play();\n        await new Promise(resolve => audioPlayer.onended = resolve);\n        document.dispatchEvent(new CustomEvent('ttsUsageUpdated'));\n      } else {\n        alert(\"Fehler beim Vorlesen: \" + (result.data.error || \"Unbekannter Fehler\"));\n        console.error(\"API-Fehler:\", result);\n        return;\n      }\n    } catch (err) {\n      alert(\"Netzwerkfehler: \" + err.message);\n      console.error(\"Netzwerkfehler beim TTS-Aufruf:\", err);\n      return;\n    }\n  }\n}\n\n    function stopSpeech() {\n      audioPlayer.pause();\n      audioPlayer.currentTime = 0;\n    }\n\nfunction applyEmphasis(level) {\n  const selectedSpan = document.querySelector('#ssmlInput span.selected');\n  if (!selectedSpan) {\n    alert(\"Bitte zuerst ein Wort anklicken.\");\n    return;\n  }\n\n  \/\/ SSML-Tag sichtbar als HTML-Entity hinzuf\u00fcgen\n  const emphasisHTML = `&lt;emphasis level=\"${level}\"&gt;${selectedSpan.textContent}&lt;\/emphasis&gt;`;\n\n  \/\/ Als HTML einsetzen, sodass es sichtbar bleibt\n  const emphasisNode = document.createElement('span');\n  emphasisNode.innerHTML = emphasisHTML;\n\n  selectedSpan.replaceWith(emphasisNode);\n\n}\n\n\n\n    function clearSelection() {\n      document.querySelectorAll('#ssmlInput span.selected').forEach(span => span.classList.remove('selected'));\n    }\n\n\n\n\nfunction getSelectedSpans() {\n  return Array.from(document.querySelectorAll('#ssmlInput span.selected'));\n}\n\n\nasync function detectLanguage(text) {\n  try {\n    const resp = await fetch('https:\/\/api.textsnapper.com\/translate', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\/json' },\n      body: JSON.stringify({ \n        q: text, \n        source: 'auto', \n        target: 'en', \n        format: 'text'       \/\/ << WICHTIG hinzugef\u00fcgt!\n      })\n    });\n\n    if (!resp.ok) throw new Error(`API-Fehler: ${resp.status}`);\n\n    const data = await resp.json();\n    \n    console.log(\"Schritt 2 - API Antwort:\", data);\n\n    if (data && data.detectedLanguage && data.detectedLanguage.language) {\n      return data.detectedLanguage.language;\n    } else {\n      return 'en'; \n    }\n  } catch (error) {\n    console.error(\"Fehler bei Spracherkennung:\", error);\n    return 'en';\n  }\n}\n\nasync function preselectDropdowns(langCode) {\n  console.log(\"Schritt 5 - Dropdown Optionen vorhanden?\", document.getElementById('languageSelect').options.length);\n  console.log(\"Schritt 3 - Erkannter Sprachcode:\", langCode);\n\n  const langSelect = document.getElementById('languageSelect');\n  const dialectSelect = document.getElementById('dialectSelect');\n  const genderSelect = document.getElementById('genderSelect');\n  const qualitySelect = document.getElementById('qualitySelect');\n  const voiceSelect = document.getElementById('voiceSelect');\n\n  \/\/ Finde Sprache oder Dialekt\n  let matchedLang = null;\n\n  if ([...langSelect.options].some(option => option.value === langCode)) {\n    matchedLang = langCode;\n  } else {\n    matchedLang = [...langSelect.options].find(option => option.value.startsWith(langCode + '-'))?.value;\n  }\n\n  if (matchedLang) {\n    console.log(\"Sprache\/Dialekt im Dropdown gefunden:\", matchedLang);\n    langSelect.value = matchedLang;\n    await fillDialectDropdown(matchedLang);\n\n    \/\/ Ersten Dialekt automatisch setzen\n    dialectSelect.selectedIndex = 1;\n    const selectedDialect = dialectSelect.value;\n    await fillGenderDropdown(selectedDialect);\n\n    \/\/ Geschlecht automatisch setzen (erstes verf\u00fcgbares)\n    genderSelect.selectedIndex = 1;\n    const selectedGender = genderSelect.value;\n    await fillQualityDropdown(selectedDialect, selectedGender);\n\n    \/\/ Qualit\u00e4t automatisch immer \"Standard\" setzen, falls verf\u00fcgbar\n    const qualityOptions = [...qualitySelect.options].map(opt => opt.value);\n    if (qualityOptions.includes(\"Standard\")) {\n      qualitySelect.value = \"Standard\";\n    } else {\n      qualitySelect.selectedIndex = 1;\n    }\n    const selectedQuality = qualitySelect.value;\n    await fillVoiceDropdown(selectedDialect, selectedGender, selectedQuality);\n\n    \/\/ Erste Stimme automatisch setzen\n    voiceSelect.selectedIndex = 1;\n  } else {\n    console.warn(\"Keine passende Sprache\/Dialekt im Dropdown gefunden f\u00fcr:\", langCode);\n  }\n}\n\n\nasync function loadGoogleStimmen() {\n  const res = await fetch('\/wp-content\/textsnapper\/google_stimmen.json');\n  googleStimmen = await res.json();\n}\n\nasync function detectAndSetLanguage() {\n  const inputField = document.getElementById('ssmlInput');\n  \n  \/\/ Text nur f\u00fcr Spracheerkennung sauber extrahieren, ohne HTML-Tags oder extra Spaces\n  const text = inputField.innerText.replace(\/\\s+\/g, ' ').trim();\n\n  if (!text) return;\n\n  const langCode = await detectLanguage(text);\n  await preselectDropdowns(langCode);\n\n  \/\/ Inhalt NICHT ver\u00e4ndern oder neu setzen!\n}\n\nasync function loadTranslationsEn() {\n  const res = await fetch('\/wp-content\/textsnapper\/translate_en.json');\n  translationsEn = await res.json();\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", async () => {\n\n  \/\/ Lade Google-Stimmen und verf\u00fcgbare Stimmen\n  await loadGoogleStimmen();\n  await fetchVoices();\n  await loadTranslationsEn();\n\n  \/\/ Initialisiere Editor\n  const ssmlInput = document.getElementById('ssmlInput');\n  ssmlInput.setAttribute('contenteditable', 'true');\n\n  if (ssmlInput.textContent.trim() === '') {\n    ssmlInput.textContent = translations[currentLang].placeholder;\n  }\n\n  \n\n  ssmlInput.onfocus = () => {\n    if (ssmlInput.textContent.trim() === translations[currentLang].placeholder) {\n      ssmlInput.textContent = '';\n      \n    }\n  };\n\n  ssmlInput.onpaste = (e) => {\n    e.preventDefault();\n    const text = (e.clipboardData || window.clipboardData).getData('text\/plain');\n    document.execCommand(\"insertHTML\", false, text);\n    \n  };\n\n  \/\/ Button-Event-Handler\n  document.getElementById('detectLangBtn').onclick = detectAndSetLanguage;\n  document.getElementById('playBtn').onclick = speakText;\n  document.getElementById('stopBtn').onclick = stopSpeech;\n\n  document.getElementById('saveTextBtn').onclick = async () => {\n    const text = getEditorText();\n    if (!text) {\n      alert(getTranslation('no-text-to-save'));\n      return;\n    }\n    let fileName = prompt(getTranslation('text-filename-prompt'), \"my-text\");\n    if (!fileName || fileName.trim() === \"\") fileName = \"text-dokument\";\n\n    try {\n      const resp = await fetch(window.ajaxurl, {\n        method: \"POST\",\n        body: new URLSearchParams({\n          action: \"save_txt_with_existing_thumbnail\",\n          text_data: text,\n          text_filename: fileName,\n          image_url: \"https:\/\/textsnapper.com\/wp-content\/uploads\/2025\/05\/text-icon.jpg\"\n        })\n      });\n\n      const result = await resp.json();\n      if (!result.success) throw new Error(result.data || getTranslation('text-save-error'));\n      alert(getTranslation('text-save-success'));\n    } catch (err) {\n      alert(getTranslation('text-save-error'));\n      console.error(\"Speicherfehler:\", err);\n    }\n  };\n\n  document.getElementById('saveAudioBtn').onclick = async () => {\n    if (!lastAudioContent) {\n      alert(currentLang === 'de' ? \"Bitte zuerst Audio generieren!\" : \"Please generate audio first!\");\n      return;\n    }\n\n    let audioFilename = prompt(currentLang === 'de' ? \"Bitte gib einen Audio-Dateinamen ein:\" : \"Please enter an audio filename:\", \"my-audio\");\n    if (!audioFilename || audioFilename.trim() === \"\") audioFilename = \"audio-datei\";\n\n    const formData = new URLSearchParams({\n      action: \"save_audio\",\n      audio_data: lastAudioContent,\n      audio_filename: audioFilename\n    });\n\n    try {\n      const resp = await fetch(window.ajaxurl, {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application\/x-www-form-urlencoded\" },\n        body: formData\n      });\n\n      const result = await resp.json();\n      if (!result.success) throw new Error(result.data || \"Fehler beim Speichern des Audios.\");\n      alert(currentLang === 'de' ? \"Audio erfolgreich gespeichert!\" : \"Audio saved successfully!\");\n    } catch (err) {\n      alert(currentLang === 'de' ? \"Fehler beim Speichern des Audios.\" : \"Error saving audio.\");\n      console.error(\"Speicherfehler:\", err);\n    }\n  };\n\n  \/\/ SSML Popup-Button Events\n  const insertBtn = document.getElementById('ssmlInsertBtn');\n  const cancelBtn = document.getElementById('ssmlCancelBtn');\n\n  if (insertBtn && cancelBtn) {\n    insertBtn.onclick = insertSSMLTag;\n    cancelBtn.onclick = closeSSMLPopup;\n  }\n\n  \/\/ Alle UI-Texte \u00fcbersetzen\n  translateWidgetTexts();\n  \/\/ Setze initial den richtigen Modus-Button-Text (je nach Sprache und Status)\n  setEditMarkMode(markMode);\n});\n\n\nfunction applySSMLTagInteractive(tag) {\n  const selectedSpans = getSelectedSpans();\n\n  \/\/ Nur bei break: Nur Spaces erlauben!\n  if (tag === 'break') {\n    \/\/ Mindestens ein ausgew\u00e4hltes Element und ALLE m\u00fcssen classList \"space\" haben\n    if (\n      selectedSpans.length === 0 ||\n      selectedSpans.some(span => !span.classList.contains('space'))\n    ) {\n      alert(currentLang === 'de'\n        ? \"Bitte nur einen Zwischenraum (Space) markieren.\"\n        : \"Please select a space between words.\"\n      );\n      return;\n    }\n  } else {\n    \/\/ Alle anderen Tags: wie bisher, mindestens ein Wort ODER Space ausgew\u00e4hlt\n    if (selectedSpans.length === 0) {\n      alert(currentLang === 'de'\n        ? \"Bitte zuerst mindestens ein Wort anklicken.\"\n        : \"Please click at least one word first.\"\n      );\n      return;\n    }\n  }\n\n\n  const voiceSelect = document.getElementById('voiceSelect');\n  if (isChirpVoice(voiceSelect.value)) {\n    alert(currentLang === 'de'\n      ? \"SSML-Tags werden derzeit nicht von Chirp-Stimmen unterst\u00fctzt.\"\n      : \"SSML tags are currently not supported by Chirp voices.\"\n    );\n    return;\n  }\n\n  currentTag = tag;\n  const popup = document.getElementById('ssmlPopup');\n  const popupTitle = document.getElementById('ssmlPopupTitle');\n  const popupContent = document.getElementById('ssmlPopupContent');\n  popupContent.innerHTML = '';\n\n  switch(tag) {\n   case 'voice':\n  popupTitle.innerText = currentLang === 'de' ? \"Stimme ausw\u00e4hlen\" : \"Select Voice\";\n  popupContent.innerHTML = `\n    ${currentLang === 'de' ? \"Sprache:\" : \"Language:\"} <select id=\"popupLanguageSelect\"><\/select><br>\n    ${currentLang === 'de' ? \"Dialekt:\" : \"Dialect:\"} <select id=\"popupDialectSelect\"><\/select><br>\n    ${currentLang === 'de' ? \"Geschlecht:\" : \"Gender:\"} <select id=\"popupGenderSelect\"><\/select><br>\n    ${currentLang === 'de' ? \"Qualit\u00e4t:\" : \"Quality:\"} <select id=\"popupQualitySelect\"><\/select><br>\n    ${currentLang === 'de' ? \"Stimme:\" : \"Voice:\"} <select id=\"popupVoiceSelect\"><\/select>`;\n  initializePopupDropdowns();\n  break;\n\n    case 'break':\n      popupTitle.innerText = currentLang === 'de' ? \"Pause ausw\u00e4hlen\" : \"Select Break\";\n      popupContent.innerHTML = `\n        <select id=\"ssmlOption\">\n          <option value=\"250ms\">250ms<\/option>\n          <option value=\"500ms\" selected>500ms<\/option>\n          <option value=\"750ms\">750ms<\/option>\n          <option value=\"1s\">1 Sekunde<\/option>\n          <option value=\"2s\">2 Sekunden<\/option>\n        <\/select><br>\n        Eigene Zeit: <input type=\"text\" id=\"customBreakTime\" placeholder=\"z.B. 1.5s\">`;\n      break;\n\n    case 'sub':\n      popupTitle.innerText = currentLang === 'de' ? \"Ersatztext eingeben\" : \"Enter substitution text\";\n      popupContent.innerHTML = `<input type=\"text\" id=\"ssmlOption\" placeholder=\"z.B. Doktor\">`;\n      break;\n\n    case 'audio':\n      popupTitle.innerText = currentLang === 'de' ? \"Audio URL eingeben\" : \"Enter audio URL\";\n      popupContent.innerHTML = `<input type=\"url\" id=\"ssmlOption\" placeholder=\"https:\/\/example.com\/audio.mp3\">`;\n      break;\n\n    case 'emphasis':\n      popupTitle.innerText = currentLang === 'de' ? \"Betonung w\u00e4hlen\" : \"Select emphasis\";\n      popupContent.innerHTML = `\n        <select id=\"ssmlOption\">\n          <option value=\"strong\">Stark<\/option>\n          <option value=\"moderate\" selected>Mittel<\/option>\n          <option value=\"reduced\">Schwach<\/option>\n        <\/select>`;\n      break;\n      \ncase 'prosody':\n    popupTitle.innerText = currentLang === 'de' ? \"Sprechgeschwindigkeit w\u00e4hlen\" : \"Select Prosody Rate\";\n    popupContent.innerHTML = `\n      <select id=\"ssmlOption\">\n        <option value=\"x-slow\">Sehr langsam<\/option>\n        <option value=\"slow\">Langsam<\/option>\n        <option value=\"medium\" selected>Mittel<\/option>\n        <option value=\"fast\">Schnell<\/option>\n        <option value=\"x-fast\">Sehr schnell<\/option>\n      <\/select>`;\n    break;\n\n      case 'say-as':\n    popupTitle.innerText = currentLang === 'de' ? \"Interpretationsart w\u00e4hlen\" : \"Select Interpret-As Type\";\n    popupContent.innerHTML = `\n      <select id=\"ssmlOption\">\n        <option value=\"characters\">Buchstaben<\/option>\n        <option value=\"cardinal\">Kardinalzahl<\/option>\n        <option value=\"ordinal\">Ordinalzahl<\/option>\n        <option value=\"date\">Datum<\/option>\n        <option value=\"time\">Zeit<\/option>\n      <\/select>`;\n    break;\n      \n    case 'lang':\n    popupTitle.innerText = currentLang === 'de' ? \"Sprache w\u00e4hlen\" : \"Select Language\";\n    popupContent.innerHTML = `\n      <select id=\"ssmlOption\">\n        <option value=\"en-US\">Englisch (US)<\/option>\n        <option value=\"en-GB\">Englisch (UK)<\/option>\n        <option value=\"de-DE\">Deutsch<\/option>\n        <option value=\"fr-FR\">Franz\u00f6sisch<\/option>\n        <option value=\"es-ES\">Spanisch<\/option>\n      <\/select>`;\n    break;\n    \n      case 'prosody-rate':\n    popupTitle.innerText = currentLang === 'de' ? \"Geschwindigkeit w\u00e4hlen\" : \"Select Speed\";\n    popupContent.innerHTML = `\n      <select id=\"ssmlOption\">\n        <option value=\"x-slow\">Sehr langsam<\/option>\n        <option value=\"slow\">Langsam<\/option>\n        <option value=\"medium\" selected>Mittel<\/option>\n        <option value=\"fast\">Schnell<\/option>\n        <option value=\"x-fast\">Sehr schnell<\/option>\n      <\/select>`;\n    break;\n\n  case 'prosody-pitch':\n    popupTitle.innerText = currentLang === 'de' ? \"Tonh\u00f6he w\u00e4hlen\" : \"Select Pitch\";\n    popupContent.innerHTML = `\n      <select id=\"ssmlOption\">\n        <option value=\"x-low\">Sehr tief<\/option>\n        <option value=\"low\">Tief<\/option>\n        <option value=\"medium\" selected>Mittel<\/option>\n        <option value=\"high\">Hoch<\/option>\n        <option value=\"x-high\">Sehr hoch<\/option>\n      <\/select>`;\n    break;\n\n  case 'prosody-volume':\n    popupTitle.innerText = currentLang === 'de' ? \"Lautst\u00e4rke w\u00e4hlen\" : \"Select Volume\";\n    popupContent.innerHTML = `\n      <select id=\"ssmlOption\">\n        <option value=\"silent\">Stumm<\/option>\n        <option value=\"x-soft\">Sehr leise<\/option>\n        <option value=\"soft\">Leise<\/option>\n        <option value=\"medium\" selected>Mittel<\/option>\n        <option value=\"loud\">Laut<\/option>\n        <option value=\"x-loud\">Sehr laut<\/option>\n      <\/select>`;\n    break;\n\n  case 'google-cheerful':\n  case 'google-conversational':\n  case 'google-news':\n    const styleLabel = tag.split('-')[1];\n    popupTitle.innerText = `${styleLabel.charAt(0).toUpperCase() + styleLabel.slice(1)} Style`;\n    popupContent.innerHTML = currentLang === 'de' ?\n      `Text wird im ${styleLabel}-Stil vorgelesen.` :\n      `Text will be read in ${styleLabel} style.`;\n    break;\n\n  }\n\n  popup.style.display = 'block';\n  document.getElementById('ssmlPopupOverlay').style.display = 'block';\n\n}\n\nfunction initializePopupDropdowns() {\n  const popupLanguageSelect = document.getElementById('popupLanguageSelect');\n  const popupDialectSelect = document.getElementById('popupDialectSelect');\n  const popupGenderSelect = document.getElementById('popupGenderSelect');\n  const popupQualitySelect = document.getElementById('popupQualitySelect');\n  const popupVoiceSelect = document.getElementById('popupVoiceSelect');\n\n  \/\/ Sprache Dropdown bef\u00fcllen\n  popupLanguageSelect.innerHTML = `<option disabled selected>${translations[currentLang].sprache}<\/option>`;\n  const languages = {};\n  googleStimmen.forEach(item => {\n    const langCode = item.Translation_Code;\n    const sprache = item.Sprache.split('(')[0].trim();\n    if (!languages[langCode]) {\n      languages[langCode] = sprache;\n      popupLanguageSelect.appendChild(new Option(sprache, langCode));\n    }\n  });\n\n  \/\/ Events hinzuf\u00fcgen\n  popupLanguageSelect.onchange = () => {\n    popupDialectSelect.innerHTML = `<option disabled selected>${translations[currentLang].dialekt}<\/option>`;\n    const dialects = new Map();\n    googleStimmen.filter(v => v.Translation_Code === popupLanguageSelect.value).forEach(item => {\n      dialects.set(item.code, item.Dialekt_Name);\n    });\n    dialects.forEach((name, code) => popupDialectSelect.appendChild(new Option(name, code)));\n    popupGenderSelect.innerHTML = `<option disabled selected>${translations[currentLang].geschlecht}<\/option>`;\n    popupQualitySelect.innerHTML = `<option disabled selected>${translations[currentLang].qualitaet}<\/option>`;\n    popupVoiceSelect.innerHTML = `<option disabled selected>${translations[currentLang].stimme}<\/option>`;\n  };\n\n  popupDialectSelect.onchange = () => {\n    popupGenderSelect.innerHTML = `<option disabled selected>${translations[currentLang].geschlecht}<\/option>`;\n    const genders = new Set(googleStimmen.filter(v => v.code === popupDialectSelect.value).map(v => v.Geschlecht));\n    genders.forEach(g => popupGenderSelect.appendChild(new Option(translations[currentLang][g.toLowerCase()] || g, g)));\n    popupQualitySelect.innerHTML = `<option disabled selected>${translations[currentLang].qualitaet}<\/option>`;\n    popupVoiceSelect.innerHTML = `<option disabled selected>${translations[currentLang].stimme}<\/option>`;\n  };\n\n  popupGenderSelect.onchange = () => {\n    popupQualitySelect.innerHTML = `<option disabled selected>${translations[currentLang].qualitaet}<\/option>`;\n    const qualities = new Set(\n      googleStimmen.filter(v =>\n        v.code === popupDialectSelect.value &&\n        v.Geschlecht === popupGenderSelect.value\n      ).map(v => v.Qualit\u00e4t)\n    );\n    qualities.forEach(q => popupQualitySelect.appendChild(new Option(q, q)));\n    popupVoiceSelect.innerHTML = `<option disabled selected>${translations[currentLang].stimme}<\/option>`;\n  };\n\n  popupQualitySelect.onchange = () => {\n  popupVoiceSelect.innerHTML = `<option disabled selected>${translations[currentLang].stimme}<\/option>`;\n  \n  googleStimmen.filter(v =>\n    v.code === popupDialectSelect.value &&\n    v.Geschlecht === popupGenderSelect.value &&\n    v.Qualit\u00e4t === popupQualitySelect.value &&\n    !\/chirp3?\/i.test(v.code2) \/\/ <-- Chirp & Chirp3 Stimmen ausschlie\u00dfen\n  ).forEach(v => {\n    popupVoiceSelect.appendChild(new Option(v.code2, v.code2));\n  });\n};\n\n  \/\/ Setze Standardwerte aus Hauptdropdowns (falls bereits ausgew\u00e4hlt)\n  const mainLanguage = document.getElementById('languageSelect').value;\n  if (mainLanguage) {\n    popupLanguageSelect.value = mainLanguage;\n    popupLanguageSelect.dispatchEvent(new Event('change'));\n    setTimeout(() => {\n      const mainDialect = document.getElementById('dialectSelect').value;\n      if (mainDialect) {\n        popupDialectSelect.value = mainDialect;\n        popupDialectSelect.dispatchEvent(new Event('change'));\n        setTimeout(() => {\n          const mainGender = document.getElementById('genderSelect').value;\n          if (mainGender) {\n            popupGenderSelect.value = mainGender;\n            popupGenderSelect.dispatchEvent(new Event('change'));\n            setTimeout(() => {\n              const mainQuality = document.getElementById('qualitySelect').value;\n              if (mainQuality) {\n                popupQualitySelect.value = mainQuality;\n                popupQualitySelect.dispatchEvent(new Event('change'));\n                setTimeout(() => {\n                  const mainVoice = document.getElementById('voiceSelect').value;\n                  if (mainVoice) {\n                    popupVoiceSelect.value = mainVoice;\n                  }\n                }, 200);\n              }\n            }, 200);\n          }\n        }, 200);\n      }\n    }, 200);\n  }\n}\n\nfunction insertSSMLTag() {\n  const selectedSpans = getSelectedSpans();\nif (selectedSpans.length === 0) {\n  alert(currentLang === 'de' ? \"Bitte zuerst mindestens ein Wort anklicken.\" : \"Please click at least one word first.\");\n  return;\n}\n\n\n  let attribute = '';\n  let ssmlContent = selectedSpans.map(span => span.textContent).join(' ');\n\n  switch (currentTag) {\n    case 'voice':\n      const voiceValue = document.getElementById('popupVoiceSelect').value;\n      attribute = ` name=\"${voiceValue}\"`;\n      break;\n\n    case 'break':\n      const customTime = document.getElementById('customBreakTime').value;\n      const selectedTime = document.getElementById('ssmlOption').value;\n      attribute = ` time=\"${customTime || selectedTime}\"`;\n      break;\n\n    case 'sub':\n      attribute = ` alias=\"${document.getElementById('ssmlOption').value}\"`;\n      break;\n\n    case 'audio':\n      attribute = ` src=\"${document.getElementById('ssmlOption').value}\"`;\n      ssmlContent = ''; \/\/ Audio hat keinen Textinhalt\n      break;\n\n    case 'emphasis':\n      attribute = ` level=\"${document.getElementById('ssmlOption').value}\"`;\n      break;\n\n    case 'prosody':\n      attribute = ` rate=\"${document.getElementById('ssmlOption').value}\"`;\n      break;\n\n    case 'say-as':\n      attribute = ` interpret-as=\"${document.getElementById('ssmlOption').value}\"`;\n      break;\n\n    case 'lang':\n      attribute = ` xml:lang=\"${document.getElementById('ssmlOption').value}\"`;\n      break;\n      \n    case 'prosody-rate':\n    attribute = ` rate=\"${document.getElementById('ssmlOption').value}\"`;\n    currentTag = 'prosody';\n    break;\n\n  case 'prosody-pitch':\n    attribute = ` pitch=\"${document.getElementById('ssmlOption').value}\"`;\n    currentTag = 'prosody';\n    break;\n\n  case 'prosody-volume':\n    attribute = ` volume=\"${document.getElementById('ssmlOption').value}\"`;\n    currentTag = 'prosody';\n    break;\n\n  case 'google-cheerful':\n  case 'google-conversational':\n  case 'google-news':\n    const style = currentTag.split('-')[1];\n    attribute = ` style=\"${style}\"`;\n    currentTag = 'google:style';\n    break;\n  }\n\n  \/\/ Erstelle die SSML-Tags\nconst openingTag = `&lt;${currentTag}${attribute}&gt;`;\nconst closingTag = `&lt;\/${currentTag}&gt;`;\n\n\/\/ Erstelle einen Wrapper f\u00fcr die ausgew\u00e4hlten W\u00f6rter\nconst wrapper = document.createElement('span');\nwrapper.innerHTML = openingTag + ssmlContent + closingTag;\n\n\/\/ Ersetze das erste ausgew\u00e4hlte Wort mit dem kompletten SSML-Block\nselectedSpans[0].replaceWith(wrapper);\n\n\/\/ L\u00f6sche die restlichen ausgew\u00e4hlten Spans, da sie jetzt im Wrapper sind\nselectedSpans.slice(1).forEach(span => span.remove());\n\n\n\/\/ Popup schlie\u00dfen\ncloseSSMLPopup();\n\n\n  clearSelection();\n  closeSSMLPopup(); \/\/ Automatisches Schlie\u00dfen des Popups nach Einf\u00fcgen!\n}\n\n\n\nfunction closeSSMLPopup() {\n  const popup = document.getElementById('ssmlPopup');\n  if (popup) {\n    popup.style.display = 'none';\n    document.getElementById('ssmlPopupOverlay').style.display = 'none';\n\n    currentTag = '';  \/\/ Optional: Resette den aktuellen Tag nach Schlie\u00dfen\n  }\n}\n\nfunction parseVoiceSSML(ssml, defaultVoiceId) {\n  const voiceSelect = document.getElementById('voiceSelect');\n  const selectedVoiceId = voiceSelect.value || defaultVoiceId;\n\n  if (isChirpVoice(selectedVoiceId)) {\n    \/\/ Entferne alle SSML-Tags, falls Chirp gew\u00e4hlt ist\n    ssml = ssml.replace(\/<[^>]*>\/g, '');\n  }\n\n  const regex = \/<voice name=\"([^\"]+)\">([\\s\\S]*?)<\\\/voice>\/g;\n  const parts = [];\n  let lastIndex = 0;\n  let match;\n\n  while ((match = regex.exec(ssml)) !== null) {\n    const voiceId = isChirpVoice(match[1]) ? defaultVoiceId : match[1];\n    if (match.index > lastIndex) {\n      parts.push({\n        text: ssml.substring(lastIndex, match.index),\n        voiceId: defaultVoiceId\n      });\n    }\n    parts.push({\n      text: match[2],\n      voiceId: voiceId\n    });\n    lastIndex = regex.lastIndex;\n  }\n\n  if (lastIndex < ssml.length) {\n    parts.push({\n      text: ssml.substring(lastIndex),\n      voiceId: defaultVoiceId\n    });\n  }\n\n  return parts.filter(p => p.text.trim().length > 0);\n}\n\n\nfunction isChirpVoice(voiceId) {\n  return \/chirp3?\/i.test(voiceId);\n}\n\n\/\/ Base64 f\u00fcr ein Standard Text-Icon (JPG 64x64)\n\/\/ Neues getestetes Base64 PNG (64x64 Pixel):\nconst textIconBase64 = \n\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAABZ0RVh0Q3JlYXRpb24gVGltZQAyMC8wNS8xN+ZsRNUAAAAlwSFlzAAALEwAACxMBAJqcGAAABEpJREFUeJztm01TFFEUh79TIhEkYAgJEQsKCQmQkEiTFCgkIm9UQUFwBQEJgYB5jEnDw0EEIWBi5jJMIMgFREHBhZWVEMjTCVkSEmZi0xGnnmbd1bM13vdP3zay5e3fX3em9l\/\/e47\/e7\/O9e5K1USGDyRvQC7gAbgCLgfHAF0GYwAL4BH4FLjKg4ZB8C8wCjwPHQDxgHPBC9vncs\/Oe6El+Az8BR4L0H7EmzWXOH1FsQG9I76D+UCuRO7HzyMExCT5dpAvQPqA9ZAfxLzjzWfIm1TTxPoq+D\/+wOBToa+is0DvQLNDikIDcBZ0Ap0AFIBb6WeeM8sEY\/O7exDniKrA0GqCFpUQ4cAZ0GuxnWTS\/hPv2K3QQYoKF6PCX6hxDB3XqwAe47l2tCf05axE3ACdgP3gPXQsEdLQEMC1y4V\/VwEDDCU5AYSLhdUPFTU7kPg2NQ0MDugZVFb4B16FNcYBN4ywwN6a4SkJ8JG9aYCHjR+VjchQ\/o8V7GZvAMqga\/jW5gIEpEdWQE9GiL1eD3YMXoOGtKZhDkiPXkkquBcYOuSqImdh3z5AHRLvjmgpdFJjqSA5NdSrjU4APgrYBhyEpjaQMLAZeDlMpmLofz+r8GVGv31MNJTmASeDoayroGxi3AbuxKSVF2V2yPcB24EtpzE\/AVTAV3AUsBNcOSu4hHg4gfsygDB+XHK68CvwlFLHif3wF3JilADcB5YMTeLEP4JRoV\/rGvHgJHAq8DNwLrhphEynOP+lO7As2AOM07U+5uJJ7gEbjX+PvyR3TWoQzwQewwRsb+QUxCWwJrlF3MIswzzDJ7wD7gMeD1edz\/Kgw04nI\/oQzS\/w4AZcD3E3CkbCkbyQfAtOS+lK3Q+yLQ58FfG7Axz8rZzXJmXt4BHDyymLCA2oPgJ3ITpI8QaQA6ym6hWYK19HzyeQZkA\/5EqYA7xTPA5ltH5UldFPts7v4wTIEvpd\/gi2SYeRPNbdjL9g35U2mcX+TToGv0J3AWcBE4E58D\/ZkGLwKrgLeBG6K6EEuJe+Ko+x30P3AReBi5I7H8AL4CVyIR3Au1IE9hO8pICv3Q8U8CJwLvyK\/bj7ywg+T3Q8biHXkPYJ\/JwYvgPlh4Hzgi5a8XAFfwFdG9IkDL9k+AAAAAElFTkSuQmCC\";\n\n\/\/ Text holen aus Editor\nfunction getEditorText() {\n  return document.getElementById('ssmlInput').innerText.trim();\n}\n\n\/\/ \u00dcbersetzungen holen\nfunction getTranslation(key) {\n  const translations = {\n    'no-text-to-save': {\n      de: 'Kein Text zum Speichern vorhanden.',\n      en: 'No text available to save.'\n    },\n    'text-filename-prompt': {\n      de: 'Bitte gib einen Dateinamen ein:',\n      en: 'Please enter a file name:'\n    },\n    'text-save-error': {\n      de: 'Fehler beim Speichern des Textes.',\n      en: 'Error saving text.'\n    },\n    'text-save-success': {\n      de: 'Text erfolgreich gespeichert!',\n      en: 'Text saved successfully!'\n    }\n  };\n  return translations[key][currentLang] || translations[key].de;\n}\n\n\n\nfunction translateWidgetTexts() {\n  const lang = document.documentElement.lang.startsWith('en') ? 'en' : 'de';\n\n  \/\/ Setze Texte\n  const el = (id) => document.getElementById(id);\n\n  if (el('widgetTitle')) el('widgetTitle').textContent = (lang === 'de') ? \"Text-to-Speech mit SSML\" : \"Text-to-Speech with SSML\";\n  if (el('toggleEditModeBtn')) el('toggleEditModeBtn').textContent = markMode ? translations[lang].editMode : translations[lang].markMode;\n  if (el('detectLangBtnLabel')) el('detectLangBtnLabel').textContent = translations[lang].detectLang;\n  if (el('labelSprache')) el('labelSprache').textContent = translations[lang].labelSprache;\n  if (el('labelDialekt')) el('labelDialekt').textContent = translations[lang].labelDialekt;\n  if (el('labelGeschlecht')) el('labelGeschlecht').textContent = translations[lang].labelGeschlecht;\n  if (el('labelQualitaet')) el('labelQualitaet').textContent = translations[lang].labelQualitaet;\n  if (el('labelStimme')) el('labelStimme').textContent = translations[lang].labelStimme;\n  if (el('playBtn')) el('playBtn').textContent = translations[lang].vorlesen;\n  if (el('stopBtn')) el('stopBtn').textContent = translations[lang].stop;\n  if (el('saveTextBtnLabel')) el('saveTextBtnLabel').textContent = translations[lang].saveText;\n  if (el('saveAudioBtnLabel')) el('saveAudioBtnLabel').textContent = translations[lang].saveAudio;\n\n  \/\/ Dropdown-Placeholders (setzt die Option [0] von jedem Select neu)\n  [\"languageSelect\", \"dialectSelect\", \"genderSelect\", \"qualitySelect\", \"voiceSelect\"].forEach((id, index) => {\n    const placeholderKeys = [\"sprache\", \"dialekt\", \"chooseGender\", \"chooseQuality\", \"chooseVoice\"];\n    const select = el(id);\n    if (select && select.options.length > 0) {\n      select.options[0].textContent = translations[lang][placeholderKeys[index]];\n    }\n  });\n}\n\n\n\/\/ Pr\u00fcft, ob die Stimme grunds\u00e4tzlich SSML unterst\u00fctzt (alle au\u00dfer Chirp\/Chirp3)\nfunction supportsSSML(voiceId) {\n  return !\/chirp3?\/i.test(voiceId);\n}\n\n\/\/ Pr\u00fcft, ob die Stimme spezielle Google-Stile unterst\u00fctzt (nur Wavenet, Neural2, Studio)\nfunction supportsGoogleStyle(voiceQuality) {\n  return [\"wavenet\", \"neural2\", \"studio\"].includes(voiceQuality.toLowerCase());\n}\n\n  <\/script>\n\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-795c2d7 elementor-widget elementor-widget-html\" data-id=\"795c2d7\" data-element_type=\"widget\" data-e-type=\"widget\" id=\"SSML-Buttons\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!-- Erkl\u00e4rung aller verf\u00fcgbaren SSML-Buttons -->\n<div style=\"font-family: 'Atkinson Hyperlegible', Arial, sans-serif; font-size:20px; background:#f9f9f9; padding:20px; border-radius:8px; color:#333;\">\n\n  <h3 style=\"font-size:22px; margin-bottom:15px; text-align:center;\">\n    Explanation of each SSML button\n  <\/h3>\n\n  <ul style=\"list-style:none; padding-left:0;\">\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#673AB7;\">\ud83c\udfa4 Voice:<\/strong> Temporarily changes the voice to read individual words or sentences with a different voice.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#3F51B5;\">\u23f8\ufe0f Break:<\/strong> Inserts a break after the selected word to naturally adjust the reading speed.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#2196F3;\">\ud83c\udf9b\ufe0f Prosody:<\/strong> Changes the speaking rate, pitch, or volume of the selected text for a more dynamic reading experience.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#00BCD4;\">\ud83d\udcac Say-as:<\/strong> Determines how text is interpreted, e.g., as a date, time, or number.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#009688;\">\ud83d\udd04 Substitution:<\/strong> Replaces the selected word with another during playback, without altering the original text.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#FFC107;\">\ud83d\udd0a Audio:<\/strong> Allows inserting external audio files (e.g., sound effects) directly into the text flow.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#FF5722;\">\u2728 Emphasis:<\/strong> Highlights important words or sentences through increased or reduced emphasis.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#795548;\">\ud83c\udfb5 Pitch:<\/strong> Adjusts the pitch to create emotional effects or special accents.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#607D8B;\">\ud83d\udd08 Volume:<\/strong> Passt die Lautst\u00e4rke an, um bestimmte Textstellen hervorzuheben oder zu d\u00e4mpfen.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#FF9800;\">\ud83d\ude0a Cheerful (Google Style):<\/strong> Reads the text cheerfully and vividly.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#FF9800;\">\ud83d\udcac Conversational (Google Style):<\/strong> Gives the text a natural, conversational emphasis.\n    <\/li>\n\n    <li style=\"margin-bottom:10px;\">\n      <strong style=\"color:#FF9800;\">\ud83d\udcf0 News (Google Style):<\/strong> Reads the text in a typical newscaster style.\n    <\/li>\n  <\/ul>\n\n<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d0a6e1e elementor-widget elementor-widget-html\" data-id=\"d0a6e1e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div id=\"credit-count-notice-widget\" style=\"text-align:center; margin-top:20px;\">\n  <span id=\"credits-left-notice\">Lade Credits...<\/span>\n  <br>\n  <span id=\"ocr-left-notice\" style=\"display:none;\">Lade OCR-Limits...<\/span>\n  <div id=\"upgrade-button-container\" style=\"display:none;\">\n    <a href=\"https:\/\/textsnapper.com\/en\/pricing\/\" class=\"upgrade-btn\">upgrade now<\/a>\n  <\/div>\n<\/div>\n\n<style>\n  #credits-left-notice, #ocr-left-notice {\n    font-family: 'Atkinson Hyperlegible', Arial, sans-serif;\n    font-size: 16px;\n    color: #555;\n  }\n\n  .warning {\n    color: #f44336;\n    font-weight: bold;\n  }\n\n  .unlimited {\n    color: #4CAF50;\n    font-weight: bold;\n  }\n\n  .upgrade-btn {\n    display: inline-block;\n    padding: 10px 20px;\n    background: #2F5591;\n    color: #FFFFFF;\n    font-weight: bold;\n    border: none;\n    border-radius: 4px;\n    text-decoration: none;\n    cursor: pointer;\n    transition: background 0.3s, transform 0.2s ease;\n    margin-top: 10px;\n  }\n\n  .upgrade-btn:hover {\n    background: #24467a;\n    transform: translateY(-2px);\n  }\n<\/style>\n\n<script>\ndocument.addEventListener('DOMContentLoaded', async function () {\n  const lang = document.documentElement.lang.startsWith('en') ? 'en' : 'de';\n  const noticeEl = document.getElementById('credits-left-notice');\n  const ocrEl = document.getElementById('ocr-left-notice');\n  const upgradeBtnContainer = document.getElementById('upgrade-button-container');\n\n  const translations = {\n    de: {\n      loading: 'Lade Credits...',\n      base_monthly: 'Monatliche Credits verf\u00fcgbar',\n      base_package: 'Paket-Credits verf\u00fcgbar (g\u00fcltig bis',\n      unlimited: 'Verf\u00fcgbare uploads: unbegrenzt \u2714\ufe0f',\n      warning: '\u26a0\ufe0f Limit fast erreicht!',\n      error: 'Fehler beim Laden.',\n      no_credits: 'Keine Credits verf\u00fcgbar.',\n      upgrade_now: 'Jetzt upgraden',\n      ocr_loading: 'Lade OCR-Limits...',\n      ocr_available: 'OCR-Uploads verf\u00fcgbar diesen Monat',\n      ocr_no_available: 'Keine OCR-Uploads mehr verf\u00fcgbar.'\n    },\n    en: {\n      loading: 'Loading credits...',\n      base_monthly: 'Monthly credits available',\n      base_package: 'Package credits available (valid until',\n      unlimited: 'Available uploads: unlimited \u2714\ufe0f',\n      warning: '\u26a0\ufe0f Limit almost reached!',\n      error: 'Error loading.',\n      no_credits: 'No credits available.',\n      upgrade_now: 'Upgrade now',\n      ocr_loading: 'Loading OCR limits...',\n      ocr_available: 'OCR uploads available this month',\n      ocr_no_available: 'No OCR uploads left.'\n    }\n  };\n\n  function t(key) {\n    return translations[lang][key];\n  }\n\n  async function fetchAjax(action) {\n    const res = await fetch(window.ajaxurl || '\/wp-admin\/admin-ajax.php', {\n      method: 'POST',\n      credentials: 'include',\n      headers: { \"Content-Type\": \"application\/x-www-form-urlencoded\" },\n      body: new URLSearchParams({ action })\n    });\n    return res.json();\n  }\n\n  let showUpgradeButton = false;\n\n  async function updateCreditNotice() {\n    noticeEl.classList.remove('warning', 'unlimited');\n    noticeEl.textContent = t('loading');\n\n    try {\n      const [monthlyRes, packageRes] = await Promise.all([\n        fetchAjax('get_user_credit_count'),\n        fetchAjax('get_user_package_credits')\n      ]);\n\n      if (!monthlyRes.success || !packageRes.success) {\n        noticeEl.textContent = t('error');\n        return;\n      }\n\n      const monthlyLeft = monthlyRes.data.monthly_credits_remaining;\n      const packageLeft = packageRes.data.credits;\n      const packageExpires = packageRes.data.expires;\n\n      let totalCredits = monthlyLeft + packageLeft;\n      let messages = [];\n\n      if (monthlyLeft === -1) {\n        noticeEl.textContent = t('unlimited');\n        noticeEl.classList.add('unlimited');\n        return;\n      }\n\n      messages.push(`${t('base_monthly')}: ${monthlyLeft}`);\n\n      if (packageLeft > 0) {\n        messages.push(`${t('base_package')} ${packageExpires}): ${packageLeft}`);\n      }\n\n      noticeEl.textContent = messages.join(' | ');\n\n      if (totalCredits <= 500) {\n        noticeEl.classList.add('warning');\n        noticeEl.innerHTML += `<br>${t('warning')}`;\n        showUpgradeButton = true;\n      }\n\n      if (totalCredits <= 0) {\n        noticeEl.classList.add('warning');\n        noticeEl.textContent = `${t('no_credits')}`;\n        showUpgradeButton = true;\n      }\n\n      toggleUpgradeButton();\n    } catch (e) {\n      noticeEl.textContent = t('error');\n    }\n  }\n\n  async function updateOCRNotice() {\n    ocrEl.style.display = 'inline';\n    ocrEl.textContent = t('ocr_loading');\n    ocrEl.classList.remove('warning');\n\n    try {\n      const res = await fetchAjax('get_user_ocr_count');\n      if (!res.success) {\n        ocrEl.textContent = t('error');\n        return;\n      }\n\n      const ocrLeft = res.data.ocr_limit - res.data.used_ocr;\n\n      if (res.data.ocr_limit === -1) {\n        ocrEl.textContent = t('unlimited');\n        ocrEl.classList.add('unlimited');\n        return;\n      }\n\n      if (ocrLeft <= 0) {\n        ocrEl.textContent = t('ocr_no_available');\n        ocrEl.classList.add('warning');\n        showUpgradeButton = true;\n      } else {\n        ocrEl.textContent = `${t('ocr_available')}: ${ocrLeft}`;\n        if (ocrLeft <= 1) ocrEl.classList.add('warning');\n      }\n\n      toggleUpgradeButton();\n    } catch (e) {\n      ocrEl.textContent = t('error');\n    }\n  }\n\n  function toggleUpgradeButton() {\n    upgradeBtnContainer.style.display = showUpgradeButton ? 'block' : 'none';\n  }\n\n  updateCreditNotice();\n  updateOCRNotice();\n\n  document.addEventListener('ttsUsageUpdated', updateCreditNotice);\n  document.addEventListener('ocrUsageUpdated', updateOCRNotice);\n});\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>Text-zu-Sprache mit SSML Was ist SSML und wie funktionieren die Buttons? SSML\u00a0(Speech Synthesis Markup Language) ist eine spezielle Auszeichnungssprache, mit der du beeinflussen kannst, wie Text von der k\u00fcnstlichen Stimme vorgelesen wird. Du kannst damit z.\u00a0B. Pausen setzen, Stimmen wechseln oder W\u00f6rter besonders betonen.Eine Beschreibung findest du hier: SSML Button Beschreibung Aktiviere den Markierungsmodus, um&hellip;&nbsp;<a href=\"https:\/\/textsnapper.com\/en\/tts\/\" rel=\"bookmark\">Read More \u00bb<span class=\"screen-reader-text\">tts<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-templates\/template-pagebuilder-full-width.php","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"class_list":["post-8455","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>tts - TextSnapper<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/textsnapper.com\/en\/tts\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"tts - TextSnapper\" \/>\n<meta property=\"og:description\" content=\"Text-zu-Sprache mit SSML Was ist SSML und wie funktionieren die Buttons? SSML\u00a0(Speech Synthesis Markup Language) ist eine spezielle Auszeichnungssprache, mit der du beeinflussen kannst, wie Text von der k\u00fcnstlichen Stimme vorgelesen wird. Du kannst damit z.\u00a0B. Pausen setzen, Stimmen wechseln oder W\u00f6rter besonders betonen.Eine Beschreibung findest du hier: SSML Button Beschreibung Aktiviere den Markierungsmodus, um&hellip;&nbsp;Read More &raquo;tts\" \/>\n<meta property=\"og:url\" content=\"https:\/\/textsnapper.com\/en\/tts\/\" \/>\n<meta property=\"og:site_name\" content=\"TextSnapper\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-29T04:29:27+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/tts\\\/\",\"url\":\"https:\\\/\\\/textsnapper.com\\\/tts\\\/\",\"name\":\"tts - TextSnapper\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#website\"},\"datePublished\":\"2025-05-10T10:09:03+00:00\",\"dateModified\":\"2025-06-29T04:29:27+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/textsnapper.com\\\/tts\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/textsnapper.com\\\/tts\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/tts\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/textsnapper.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"tts\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#website\",\"url\":\"https:\\\/\\\/textsnapper.com\\\/\",\"name\":\"TextSnapper\",\"description\":\"Texte aus Bildern vorlesen, \u00fcbersetzen und einfach h\u00f6ren.\",\"publisher\":{\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/textsnapper.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#organization\",\"name\":\"Textsnapper\",\"url\":\"https:\\\/\\\/textsnapper.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/textsnapper.com\\\/wp-content\\\/uploads\\\/2024\\\/12\\\/cropped-cropped-cropped-textsnapper.png\",\"contentUrl\":\"https:\\\/\\\/textsnapper.com\\\/wp-content\\\/uploads\\\/2024\\\/12\\\/cropped-cropped-cropped-textsnapper.png\",\"width\":200,\"height\":200,\"caption\":\"Textsnapper\"},\"image\":{\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"tts - TextSnapper","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/textsnapper.com\/en\/tts\/","og_locale":"en_US","og_type":"article","og_title":"tts - TextSnapper","og_description":"Text-zu-Sprache mit SSML Was ist SSML und wie funktionieren die Buttons? SSML\u00a0(Speech Synthesis Markup Language) ist eine spezielle Auszeichnungssprache, mit der du beeinflussen kannst, wie Text von der k\u00fcnstlichen Stimme vorgelesen wird. Du kannst damit z.\u00a0B. Pausen setzen, Stimmen wechseln oder W\u00f6rter besonders betonen.Eine Beschreibung findest du hier: SSML Button Beschreibung Aktiviere den Markierungsmodus, um&hellip;&nbsp;Read More &raquo;tts","og_url":"https:\/\/textsnapper.com\/en\/tts\/","og_site_name":"TextSnapper","article_modified_time":"2025-06-29T04:29:27+00:00","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/textsnapper.com\/tts\/","url":"https:\/\/textsnapper.com\/tts\/","name":"tts - TextSnapper","isPartOf":{"@id":"https:\/\/textsnapper.com\/#website"},"datePublished":"2025-05-10T10:09:03+00:00","dateModified":"2025-06-29T04:29:27+00:00","breadcrumb":{"@id":"https:\/\/textsnapper.com\/tts\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/textsnapper.com\/tts\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/textsnapper.com\/tts\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/textsnapper.com\/"},{"@type":"ListItem","position":2,"name":"tts"}]},{"@type":"WebSite","@id":"https:\/\/textsnapper.com\/#website","url":"https:\/\/textsnapper.com\/","name":"TextSnapper","description":"Texte aus Bildern vorlesen, \u00fcbersetzen und einfach h\u00f6ren.","publisher":{"@id":"https:\/\/textsnapper.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/textsnapper.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/textsnapper.com\/#organization","name":"Textsnapper","url":"https:\/\/textsnapper.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/textsnapper.com\/#\/schema\/logo\/image\/","url":"https:\/\/textsnapper.com\/wp-content\/uploads\/2024\/12\/cropped-cropped-cropped-textsnapper.png","contentUrl":"https:\/\/textsnapper.com\/wp-content\/uploads\/2024\/12\/cropped-cropped-cropped-textsnapper.png","width":200,"height":200,"caption":"Textsnapper"},"image":{"@id":"https:\/\/textsnapper.com\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/pages\/8455","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/comments?post=8455"}],"version-history":[{"count":500,"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/pages\/8455\/revisions"}],"predecessor-version":[{"id":10599,"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/pages\/8455\/revisions\/10599"}],"wp:attachment":[{"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/media?parent=8455"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}