PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.44
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.44
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
king-addons / includes / admin / js / ai-page-translator.js

ai-page-translator.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.44, at includes/admin/js/ai-page-translator.js

2,985 lines 131.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function($, elementor) {
2 'use strict';
3
4 // Check if Elementor and our AI settings exist
5 if (!elementor || !window.KingAddonsAiField) {
6 return;
7 }
8
9 // Translation progress tracking
10 var translationState = {
11 isTranslating: false,
12 totalElements: 0,
13 translatedElements: 0,
14 failedElements: 0,
15 currentElement: null,
16 fromLang: '',
17 toLang: '',
18 isCancelled: false,
19 currentRequests: [] // Store active AJAX requests to cancel them
20 };
21
22 // Language options
23 var languages = {
24 'en': 'English',
25 'es': 'Spanish (Español)',
26 'fr': 'French (Français)',
27 'de': 'German (Deutsch)',
28 'it': 'Italian (Italiano)',
29 'pt': 'Portuguese (Português)',
30 'ru': 'Russian (Русский)',
31 'ja': 'Japanese (日本語)',
32 'ko': 'Korean (한국어)',
33 'zh': 'Chinese (中文)',
34 'ar': 'Arabic (العربية)',
35 'hi': 'Hindi (हिन्दी)',
36 'nl': 'Dutch (Nederlands)',
37 'pl': 'Polish (Polski)',
38 'tr': 'Turkish (Türkçe)',
39 'uk': 'Ukrainian (Українська)',
40 'cs': 'Czech (Čeština)',
41 'sv': 'Swedish (Svenska)',
42 'no': 'Norwegian (Norsk)',
43 'da': 'Danish (Dansk)',
44 'fi': 'Finnish (Suomi)'
45 };
46
47 /**
48 * Check if premium version is active
49 */
50 function isPremiumActive() {
51 // Check for premium indicators
52 return !!(
53 window.KingAddonsPro ||
54 window.kingAddonsPro ||
55 (window.KingAddonsAiField && window.KingAddonsAiField.is_pro) ||
56 (window.KingAddonsAiField && window.KingAddonsAiField.premium_active) ||
57 document.querySelector('body.king-addons-pro') ||
58 (typeof jQuery !== 'undefined' && jQuery('body').hasClass('king-addons-pro'))
59 );
60 }
61
62 /**
63 * Inject CSS styles for the translator
64 */
65 function injectTranslatorStyles() {
66 if ($('#king-addons-ai-translator-styles').length === 0) {
67 const styles = `
68 <style id="king-addons-ai-translator-styles">
69 /* Translator Button Styles */
70 .king-addons-ai-translator-btn {
71 background: linear-gradient(135deg, #E1CBFF, #5B03FF) !important;
72 border: none !important;
73 color: white !important;
74 padding: 8px 12px !important;
75 border-radius: 6px !important;
76 font-size: 12px !important;
77 font-weight: 500 !important;
78 cursor: pointer !important;
79 display: inline-flex !important;
80 align-items: center !important;
81 gap: 6px !important;
82 transition: all 0.3s ease !important;
83 margin: 8px !important;
84 position: relative !important;
85 z-index: 10 !important;
86 text-decoration: none !important;
87 outline: none !important;
88 box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
89 }
90 .king-addons-ai-translator-btn:hover {
91 background: linear-gradient(135deg, #d4a3ff, #4f00e6) !important;
92 box-shadow: 0 4px 12px rgba(91,3,255,0.3) !important;
93 transform: translateY(-1px) !important;
94 }
95 .king-addons-ai-translator-btn:active {
96 transform: translateY(0) !important;
97 box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
98 }
99 .king-addons-ai-translator-btn img {
100 width: 16px !important;
101 height: 16px !important;
102 flex-shrink: 0 !important;
103 }
104 .king-addons-ai-translator-btn span {
105 white-space: nowrap !important;
106 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
107 }
108
109 /* Location-specific styles */
110
111 /* In panel header */
112 .king-addons-translator-location-panel-header {
113 position: absolute !important;
114 top: 50% !important;
115 right: 16px !important;
116 transform: translateY(-50%) !important;
117 margin: 0 !important;
118 z-index: 1000 !important;
119 }
120 .king-addons-translator-location-panel-header:hover {
121 transform: translateY(-50%) translateY(-1px) !important;
122 }
123
124 /* In header within panel */
125 .king-addons-translator-location-header-in-panel {
126 margin-left: auto !important;
127 margin-right: 8px !important;
128 }
129
130 /* At top of panel */
131 .king-addons-translator-location-panel-top {
132 width: calc(100% - 16px) !important;
133 margin: 8px !important;
134 justify-content: center !important;
135 }
136
137 /* In general elementor panel */
138 .king-addons-translator-location-elementor-panel {
139 margin: 8px !important;
140 align-self: flex-end !important;
141 }
142
143 /* Toolbar button group integration styles */
144 .king-addons-translator-location-left-group,
145 .king-addons-translator-location-toolbar-stack,
146 .king-addons-translator-location-toolbar,
147 .king-addons-translator-location-grid-stack {
148 /* Material UI button styling is handled in the HTML structure */
149 display: inline-flex !important;
150 }
151
152 /* Additional spacing for toolbar button */
153 .king-addons-translator-location-left-group .king-addons-ai-translator-btn,
154 .king-addons-translator-location-toolbar-stack .king-addons-ai-translator-btn,
155 .king-addons-translator-location-grid-stack .king-addons-ai-translator-btn {
156 margin-left: 8px !important;
157 }
158
159 /* Compact popup styles */
160 .king-addons-translator-popup.compact .king-addons-translator-progress-text {
161 font-size: 14px;
162 margin-bottom: 8px;
163 }
164
165 .king-addons-translator-popup.compact .king-addons-translator-current-element {
166 font-size: 12px;
167 margin-top: 8px;
168 color: #666;
169 overflow: hidden;
170 text-overflow: ellipsis;
171 white-space: nowrap;
172 }
173
174 .king-addons-translator-popup.compact .king-addons-translator-stats {
175 margin: 12px 0;
176 }
177
178 .king-addons-translator-popup.compact .king-addons-translator-stat {
179 margin: 0 8px;
180 }
181
182 .king-addons-translator-popup.compact .king-addons-translator-stat-number {
183 font-size: 18px;
184 }
185
186 .king-addons-translator-popup.compact .king-addons-translator-stat-label {
187 font-size: 11px;
188 }
189
190 /* Compact mode adjustments for custom fields */
191 .king-addons-translator-popup.compact .king-addons-prompt-examples {
192 padding: 6px;
193 margin-top: 4px;
194 }
195
196 .king-addons-translator-popup.compact .king-addons-prompt-examples small {
197 font-size: 10px;
198 }
199
200 .king-addons-translator-popup.compact .king-addons-pro-info {
201 font-size: 11px;
202 margin-top: 8px;
203 padding: 6px 8px;
204 background: #f8f9fa;
205 border-radius: 4px;
206 border-left: 3px solid #5B03FF;
207 }
208
209 /* Element highlighting styles moved to preview iframe */
210
211 /* Ensure button appears properly in all panel locations */
212 #elementor-panel .king-addons-ai-translator-btn,
213 .elementor-panel .king-addons-ai-translator-btn {
214 max-width: 200px !important;
215 overflow: hidden !important;
216 }
217
218 /* Responsive behavior */
219 @media (max-width: 600px) {
220 /* Hide text in panel buttons on small screens */
221 .king-addons-ai-translator-btn span {
222 display: none !important;
223 }
224 .king-addons-ai-translator-btn {
225 padding: 8px !important;
226 min-width: 32px !important;
227 }
228 }
229
230 /* Popup Overlay */
231 .king-addons-translator-overlay {
232 position: fixed;
233 top: 0;
234 left: 0;
235 right: 0;
236 bottom: 0;
237 background: rgba(0,0,0,0.5);
238 z-index: 999999;
239 display: flex;
240 align-items: center;
241 justify-content: center;
242 transition: opacity 0.3s ease;
243 }
244
245 .king-addons-translator-overlay.hiding {
246 opacity: 0;
247 pointer-events: none;
248 }
249
250 /* Popup Container */
251 .king-addons-translator-popup {
252 background: white;
253 padding: 24px;
254 border-radius: 8px;
255 box-shadow: 0 10px 40px rgba(0,0,0,0.3);
256 width: 90%;
257 max-width: 500px;
258 max-height: 80vh;
259 overflow-y: auto;
260 transition: all 0.3s ease;
261 transform: scale(1);
262 }
263
264 /* Compact popup for top-right positioning */
265 .king-addons-translator-popup.compact {
266 position: fixed;
267 top: 80px;
268 right: 20px;
269 width: 350px;
270 max-width: 350px;
271 padding: 16px;
272 z-index: 999999;
273 max-height: 400px;
274 transform: scale(1);
275 box-shadow: 0 8px 32px rgba(0,0,0,0.4);
276 }
277
278 /* Compact popup header */
279 .king-addons-translator-popup.compact h3 {
280 font-size: 16px;
281 margin: 0 0 12px 0;
282 display: flex;
283 justify-content: space-between;
284 align-items: center;
285 }
286
287 /* Close button for compact popup */
288 .king-addons-translator-close-btn {
289 background: none;
290 border: none;
291 font-size: 18px;
292 cursor: pointer;
293 color: #999;
294 width: 24px;
295 height: 24px;
296 display: flex;
297 align-items: center;
298 justify-content: center;
299 border-radius: 3px;
300 }
301
302 .king-addons-translator-close-btn:hover {
303 background: #f0f0f0;
304 color: #333;
305 }
306
307 /* Animation states */
308 .king-addons-translator-popup.moving {
309 transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
310 }
311
312 /* Notification banner animation */
313 @keyframes slideDown {
314 0% {
315 transform: translateY(-100%);
316 opacity: 0;
317 }
318 100% {
319 transform: translateY(0);
320 opacity: 1;
321 }
322 }
323
324 /* Pulse animation for success numbers */
325 @keyframes pulse {
326 0% {
327 transform: scale(1);
328 opacity: 1;
329 }
330 50% {
331 transform: scale(1.1);
332 opacity: 0.8;
333 }
334 100% {
335 transform: scale(1);
336 opacity: 1;
337 }
338 }
339
340 .king-addons-translator-popup h3 {
341 margin: 0 0 20px 0;
342 font-size: 18px;
343 color: #23282d;
344 display: flex;
345 align-items: center;
346 gap: 8px;
347 }
348
349 .king-addons-translator-form {
350 display: flex;
351 flex-direction: column;
352 gap: 16px;
353 }
354
355 .king-addons-translator-field {
356 display: flex;
357 flex-direction: column;
358 gap: 6px;
359 }
360
361 .king-addons-translator-field label {
362 font-weight: 500;
363 color: #555;
364 font-size: 14px;
365 }
366
367 .king-addons-translator-field select {
368 padding: 8px 12px;
369 border: 1px solid #ddd;
370 border-radius: 4px;
371 font-size: 14px;
372 height: auto;
373 }
374
375 .king-addons-translator-field select:focus {
376 border-color: #5B03FF;
377 box-shadow: 0 0 0 1px rgba(91,3,255,0.3);
378 outline: none;
379 }
380
381 .king-addons-translator-field input[type="text"] {
382 padding: 8px 12px;
383 border: 1px solid #ddd;
384 border-radius: 4px;
385 font-size: 14px;
386 margin-top: 6px;
387 transition: border-color 0.3s ease, box-shadow 0.3s ease;
388 }
389
390 .king-addons-translator-field input[type="text"]:focus {
391 border-color: #5B03FF;
392 box-shadow: 0 0 0 1px rgba(91,3,255,0.3);
393 outline: none;
394 }
395
396 .king-addons-custom-language-field {
397 margin-top: 8px;
398 display: none;
399 animation: slideDown 0.3s ease-out;
400 }
401
402 .king-addons-custom-language-field.show {
403 display: block;
404 }
405
406 .king-addons-custom-language-field input {
407 width: 100%;
408 box-sizing: border-box;
409 }
410
411 .king-addons-custom-language-field label {
412 font-size: 13px;
413 color: #666;
414 margin-bottom: 4px;
415 display: block;
416 }
417
418 .king-addons-pro-badge {
419 background: linear-gradient(135deg, #FFD700, #FFA500);
420 color: #333;
421 font-size: 10px;
422 font-weight: bold;
423 padding: 2px 6px;
424 border-radius: 3px;
425 margin-left: 6px;
426 vertical-align: middle;
427 }
428
429 /* Style for disabled custom option when not premium */
430 .king-addons-translator-field select option[value="custom"]:disabled {
431 color: #999;
432 background-color: #f5f5f5;
433 }
434
435 /* Enhanced styling for custom language fields */
436 .king-addons-custom-language-field.show input:focus {
437 border-color: #5B03FF;
438 box-shadow: 0 0 0 2px rgba(91,3,255,0.1);
439 }
440
441 /* Info text for premium features */
442 .king-addons-pro-info {
443 font-size: 13px;
444 color: #666;
445 margin-top: 4px;
446 font-style: italic;
447 line-height: 1.4;
448 }
449
450 .king-addons-pro-info a {
451 color: #5B03FF;
452 text-decoration: none;
453 font-weight: 500;
454 }
455
456 .king-addons-pro-info a:hover {
457 color: #4f00e6;
458 text-decoration: underline;
459 }
460
461 /* Prompt examples styling */
462 .king-addons-prompt-examples {
463 margin-top: 6px;
464 padding: 8px;
465 background: #f8f9fa;
466 border-left: 3px solid #5B03FF;
467 border-radius: 0 4px 4px 0;
468 }
469
470 .king-addons-prompt-examples small {
471 color: #666;
472 font-size: 11px;
473 line-height: 1.4;
474 display: block;
475 }
476
477 @keyframes slideDown {
478 from {
479 opacity: 0;
480 max-height: 0;
481 transform: translateY(-10px);
482 }
483 to {
484 opacity: 1;
485 max-height: 100px;
486 transform: translateY(0);
487 }
488 }
489
490 /* Loading spinner animation */
491 @keyframes rotate {
492 from {
493 transform: rotate(0deg);
494 }
495 to {
496 transform: rotate(360deg);
497 }
498 }
499
500 /* Error popup specific styles */
501 .king-addons-translator-popup .king-addons-error-icon {
502 width: 60px;
503 height: 60px;
504 background: #f44336;
505 border-radius: 50%;
506 margin: 0 auto 16px;
507 display: flex;
508 align-items: center;
509 justify-content: center;
510 animation: errorPulse 2s ease-in-out infinite;
511 }
512
513 @keyframes errorPulse {
514 0%, 100% {
515 transform: scale(1);
516 box-shadow: 0 0 0 0 rgba(244, 67, 54, 0.4);
517 }
518 50% {
519 transform: scale(1.05);
520 box-shadow: 0 0 0 8px rgba(244, 67, 54, 0.1);
521 }
522 }
523
524 .king-addons-translator-actions {
525 display: flex;
526 gap: 12px;
527 margin-top: 8px;
528 }
529
530 .king-addons-translator-btn-primary {
531 background: linear-gradient(135deg, #E1CBFF, #5B03FF);
532 border: none;
533 color: white;
534 padding: 10px 20px;
535 border-radius: 4px;
536 font-size: 14px;
537 font-weight: 500;
538 cursor: pointer;
539 flex: 1;
540 transition: all 0.3s ease;
541 }
542
543 .king-addons-translator-btn-primary:hover {
544 box-shadow: 0 0 12px rgba(91,3,255,0.5);
545 color: #fff;
546 }
547
548 .king-addons-translator-btn-primary:disabled {
549 background: #ccc;
550 cursor: not-allowed;
551 box-shadow: none;
552 }
553
554 .king-addons-translator-btn-secondary {
555 background: #f1f1f1;
556 border: 1px solid #ddd;
557 color: #555;
558 padding: 10px 20px;
559 border-radius: 4px;
560 font-size: 14px;
561 cursor: pointer;
562 flex: 1;
563 transition: all 0.3s ease;
564 }
565
566 .king-addons-translator-btn-secondary:hover {
567 background: #e8e8e8;
568 }
569
570 /* Progress Styles */
571 .king-addons-translator-progress {
572 margin-top: 16px;
573 padding: 16px;
574 background: #f8f9fa;
575 border-radius: 6px;
576 border-left: 4px solid #5B03FF;
577 }
578
579 .king-addons-translator-progress-text {
580 font-size: 14px;
581 color: #555;
582 margin-bottom: 8px;
583 }
584
585 .king-addons-translator-progress-bar {
586 width: 100%;
587 height: 8px;
588 background: #e0e0e0;
589 border-radius: 4px;
590 overflow: hidden;
591 margin-bottom: 8px;
592 }
593
594 .king-addons-translator-progress-fill {
595 height: 100%;
596 background: linear-gradient(90deg, #5B03FF, #E1CBFF);
597 width: 0%;
598 transition: width 0.3s ease;
599 }
600
601 .king-addons-translator-current-element {
602 font-size: 12px;
603 color: #777;
604 font-style: italic;
605 }
606
607 /* Stats Styles */
608 .king-addons-translator-stats {
609 margin-top: 16px;
610 display: grid;
611 grid-template-columns: repeat(3, 1fr);
612 gap: 12px;
613 }
614
615 .king-addons-translator-stat {
616 text-align: center;
617 padding: 12px;
618 background: #f8f9fa;
619 border-radius: 6px;
620 }
621
622 .king-addons-translator-stat-number {
623 font-size: 20px;
624 font-weight: bold;
625 color: #5B03FF;
626 }
627
628 .king-addons-translator-stat-label {
629 font-size: 12px;
630 color: #777;
631 margin-top: 4px;
632 }
633
634 /* Element animations handled in preview iframe */
635 </style>
636 `;
637 $('head').append(styles);
638 }
639 }
640
641 /**
642 * Add translator button to Elementor panel
643 */
644 function addTranslatorButton() {
645 // Check if button already exists
646 if (document.querySelector('.king-addons-ai-translator-btn')) {
647 console.log('AI Translator button already exists');
648 return;
649 }
650
651 console.log('Attempting to add AI Translator button...');
652
653 // Strategy 1: Try to find the left button group in the top toolbar (after initial buttons)
654 var $leftButtonGroup = $('#elementor-editor-wrapper-v2 .MuiStack-root.eui-1g5sxhh:first');
655 if ($leftButtonGroup.length) {
656 console.log('Found left button group in top toolbar, adding button');
657 return addButtonToElement($leftButtonGroup, 'left-group');
658 }
659
660 // Strategy 2: Try to find the first stack group in the toolbar
661 var $toolbarStack = $('#elementor-editor-wrapper-v2 .MuiStack-root');
662 if ($toolbarStack.length) {
663 console.log('Found toolbar stack, adding button to first stack');
664 return addButtonToElement($toolbarStack.first(), 'toolbar-stack');
665 }
666
667 // Strategy 2.5: Try to find grid container with stacks
668 var $gridContainer = $('#elementor-editor-wrapper-v2 .MuiGrid-container:first');
669 if ($gridContainer.length) {
670 var $firstStack = $gridContainer.find('.MuiStack-root:first');
671 if ($firstStack.length) {
672 console.log('Found first stack in grid container, adding button');
673 return addButtonToElement($firstStack, 'grid-stack');
674 }
675 }
676
677 // Strategy 3: Try to find the toolbar itself
678 var $toolbar = $('#elementor-editor-wrapper-v2 .MuiToolbar-root');
679 if ($toolbar.length) {
680 console.log('Found toolbar, adding button');
681 return addButtonToElement($toolbar, 'toolbar');
682 }
683
684 // Strategy 4: Try to find the Elementor panel header (fallback)
685 var $panelHeader = $('#elementor-panel-header');
686 if ($panelHeader.length) {
687 console.log('Fallback: Found #elementor-panel-header, adding button');
688 return addButtonToElement($panelHeader, 'panel-header');
689 }
690
691 // Strategy 5: Try to find the main panel (further fallback)
692 var $panel = $('#elementor-panel');
693 if ($panel.length) {
694 console.log('Fallback: Adding button to panel');
695 return addButtonToElement($panel, 'panel-fallback');
696 }
697
698 console.log('Could not find suitable location for AI Translator button');
699 return false;
700 }
701
702 /**
703 * Helper function to add button to specific element
704 */
705 function addButtonToElement($target, location) {
706 // Try using the custom icon, fallback to the standard AI icon
707 var iconUrl = KingAddonsAiField.plugin_url + 'includes/admin/img/ai.svg';
708 var fallbackIconUrl = KingAddonsAiField.plugin_url + 'includes/admin/img/ai.svg';
709
710 var $translatorBtn;
711
712 // Create button with appropriate styling based on location
713 if (location === 'left-group' || location === 'toolbar-stack' || location === 'toolbar' || location === 'grid-stack') {
714 // Material UI style button for toolbar with text and custom icon
715 $translatorBtn = $('<span class="MuiBox-root eui-0">' +
716 '<button class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textInherit MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorInherit king-addons-ai-translator-btn eui-17yw4pm" ' +
717 'tabindex="0" type="button" aria-label="AI Page Translator" title="AI Page Translator">' +
718 '<span class="MuiButton-startIcon MuiButton-iconSizeSmall" style="margin-right: 4px;">' +
719 '<img src="' + iconUrl + '" alt="AI" onerror="this.src=\'' + fallbackIconUrl + '\'" style="width: 20px; height: 20px;" />' +
720 '</span>' +
721 '<span class="MuiStack-root" style="color: white;">AI Page Translator</span>' +
722 '</button>' +
723 '</span>');
724 } else {
725 // Original button style for panel locations
726 $translatorBtn = $('<button class="king-addons-ai-translator-btn" title="AI Page Translator">' +
727 '<img src="' + iconUrl + '" alt="AI Page Translator" onerror="this.src=\'' + fallbackIconUrl + '\'"/>' +
728 '<span>AI Page Translator</span>' +
729 '</button>');
730 }
731
732 // Add location-specific class for different styling if needed
733 $translatorBtn.addClass('king-addons-translator-location-' + location);
734
735 // Add to the target element based on location
736 if (location === 'panel-top' || location === 'panel-fallback') {
737 $target.prepend($translatorBtn);
738 } else if (location === 'left-group' || location === 'toolbar-stack' || location === 'grid-stack') {
739 // Add after existing buttons in the left group
740 $target.append($translatorBtn);
741 } else {
742 $target.append($translatorBtn);
743 }
744
745 // Bind click event (works for both button structures)
746 $translatorBtn.find('button').length ?
747 $translatorBtn.find('button').on('click', handleButtonClick) :
748 $translatorBtn.on('click', handleButtonClick);
749
750 function handleButtonClick(e) {
751 e.preventDefault();
752 // Check if button is disabled or translation is in progress
753 if (translationState.isTranslating || $(e.currentTarget).prop('disabled')) {
754 return;
755 }
756 showTranslatorPopup();
757 }
758
759 // Add debug info to button
760 var $btn = $translatorBtn.find('button').length ? $translatorBtn.find('button') : $translatorBtn;
761 $btn.attr('data-location', location);
762 $btn.attr('data-target', $target.prop('tagName') + ($target.attr('id') ? '#' + $target.attr('id') : '') + ($target.attr('class') ? '.' + $target.attr('class').split(' ').join('.') : ''));
763
764 console.log('AI Translator button added to:', location, 'Target:', $target);
765 return true;
766 }
767
768 /**
769 * Show the translator popup
770 */
771 function showTranslatorPopup() {
772 // Check API key first
773 checkApiKeyAndShowPopup();
774 }
775
776 /**
777 * Check API key before showing popup
778 */
779 function checkApiKeyAndShowPopup() {
780 // Show loading state briefly
781 var $loadingOverlay = showLoadingOverlay();
782
783 $.post(KingAddonsAiField.ajax_url, {
784 action: 'king_addons_ai_check_tokens',
785 nonce: KingAddonsAiField.generate_nonce
786 }, function(response) {
787 $loadingOverlay.remove();
788
789 if (!response.success) {
790 if (response.data && response.data.message) {
791 var errorMessage = response.data.message;
792
793 // Check for token limit errors first
794 if (errorMessage.toLowerCase().includes('token limit') ||
795 errorMessage.toLowerCase().includes('daily limit') ||
796 errorMessage.toLowerCase().includes('limit reached') ||
797 errorMessage.toLowerCase().includes('quota exceeded') ||
798 errorMessage.toLowerCase().includes('rate limit')) {
799
800 showTokenLimitError(errorMessage);
801 return;
802 }
803
804 showApiKeyError('API Error', errorMessage);
805 } else {
806 showApiKeyError('Connection Issue', 'Unable to connect right now. Please check your internet connection and try again.');
807 }
808 return;
809 }
810
811 if (!response.data.api_key_valid) {
812 var errorMessage = response.data.error_message || 'API key is missing or invalid';
813
814 // Check for token limit errors first
815 if (errorMessage.toLowerCase().includes('token limit') ||
816 errorMessage.toLowerCase().includes('daily limit') ||
817 errorMessage.toLowerCase().includes('limit reached') ||
818 errorMessage.toLowerCase().includes('quota exceeded') ||
819 errorMessage.toLowerCase().includes('rate limit') ||
820 errorMessage.toLowerCase().includes('too many requests')) {
821
822 showTokenLimitError(errorMessage);
823 return;
824 }
825
826 showApiKeyError('API Key Required', errorMessage);
827 return;
828 }
829
830 createAndShowPopup();
831 }).fail(function(xhr) {
832 $loadingOverlay.remove();
833
834 if (xhr.status === 0) {
835 showApiKeyError('Connection Issue', 'Network connection failed. Please check your internet connection and try again.');
836 } else {
837 showApiKeyError('Temporary Issue', 'Server is temporarily unavailable. Please try again in a few minutes.');
838 }
839 });
840 }
841
842 /**
843 * Show loading overlay
844 */
845 function showLoadingOverlay() {
846 var $overlay = $('<div class="king-addons-translator-overlay"></div>');
847 var $popup = $('<div class="king-addons-translator-popup" style="text-align: center; padding: 40px;"></div>');
848
849 var loadingHtml = `
850 <div style="margin-bottom: 16px;">
851 <img src="${KingAddonsAiField.plugin_url}includes/admin/img/ai.svg" style="width:40px;height:40px;filter: invert(1); animation: rotate 1s linear infinite;"/>
852 </div>
853 <div style="font-size: 16px; color: #333; margin-bottom: 8px;">🔍 Verifying Setup...</div>
854 <div style="font-size: 12px; color: #666;">Just checking that everything is ready for translation</div>
855 `;
856
857 $popup.html(loadingHtml);
858 $overlay.append($popup);
859 $('body').append($overlay);
860
861 return $overlay;
862 }
863
864 /**
865 * Show API key error with detailed information
866 */
867 function showApiKeyError(title, message) {
868 // Aggressively remove any existing popups/overlays
869 $('.king-addons-translator-overlay').remove();
870 $('.king-addons-translator-popup').remove();
871
872 // Wait a bit to ensure cleanup is complete
873 setTimeout(function() {
874 showApiKeyErrorDelayed(title, message);
875 }, 100);
876 }
877
878 function showApiKeyErrorDelayed(title, message) {
879 var settingsUrl = window.KingAddonsAiField && window.KingAddonsAiField.settings_url
880 ? window.KingAddonsAiField.settings_url
881 : '/wp-admin/admin.php?page=king-addons-ai-settings';
882
883 var $overlay = $('<div class="king-addons-translator-overlay"></div>');
884 var $popup = $('<div class="king-addons-translator-popup"></div>');
885
886 var errorHtml = `
887 <div style="text-align: center; margin-bottom: 32px;">
888 <h3 style="margin: 0 0 16px 0; color: #2d3748; font-size: 20px; text-align: center;justify-content: center;">AI Translator Setup Required</h3>
889 <p style="color: #718096; margin: 0; font-size: 14px;">This is completely safe and takes only 2 minutes!</p>
890 </div>
891
892 <div style="background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); border: 1px solid #e2e8f0; padding: 20px; border-radius: 12px; margin-bottom: 20px;">
893 <h4 style="color: #2d3748; margin: 0 0 12px 0; font-size: 16px;">📋 What you need to do:</h4>
894
895 <div style="margin-bottom: 12px; display: flex; align-items: center;">
896 <span style="background: #48bb78; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; margin-right: 12px; font-size: 12px; font-weight: bold;max-width: 24px;flex-basis: 46px;">1</span>
897 <span style="color: #4a5568;">Get an API key from <a href="https://platform.openai.com/api-keys" target="_blank" style="color: #5B03FF; text-decoration: none;line-height: 1.4;">OpenAI Platform</a> and top up your OpenAI account balance by at least $5</span>
898 </div>
899
900 <div style="margin-bottom: 12px; display: flex; align-items: center;">
901 <span style="background: #48bb78; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; margin-right: 12px; font-size: 12px; font-weight: bold;">2</span>
902 <span style="color: #4a5568;">Paste it in AI Settings</span>
903 </div>
904
905 <div style="margin-bottom: 12px; display: flex; align-items: center;">
906 <span style="background: #48bb78; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; margin-right: 12px; font-size: 12px; font-weight: bold;">3</span>
907 <span style="color: #4a5568;">Done! Translate as much as you want 🎉</span>
908 </div>
909 </div>
910
911 <div style="background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 16px; margin-bottom: 20px;">
912 <div style="display: flex; align-items: center; margin-bottom: 8px;">
913 <span style="color: #495057; margin-right: 8px; font-size: 16px;">💰</span>
914 <strong style="color: #495057;">How much does it cost?</strong>
915 </div>
916 <p style="color: #6c757d; margin: 0; font-size: 14px; line-height: 1.4;">
917 Translation costs pennies (about $0.01 per full page).
918 </p>
919 </div>
920
921 <div style="display: flex; gap: 12px;">
922 <button class="king-addons-translator-btn-secondary" id="king-addons-error-close" style="flex: 1;">
923 Not now
924 </button>
925 <a href="${settingsUrl}" class="king-addons-translator-btn-primary" style="flex: 1; text-decoration: none; display: flex; align-items: center; justify-content: center;">
926 Set up now
927 </a>
928 </div>
929 `;
930
931 $popup.html(errorHtml);
932 $overlay.append($popup);
933 $('body').append($overlay);
934
935 // Bind close event
936 $('#king-addons-error-close').on('click', function() {
937 $overlay.remove();
938 });
939
940 // Close on overlay click
941 $overlay.on('click', function(e) {
942 if (e.target === $overlay[0]) {
943 $overlay.remove();
944 }
945 });
946 }
947
948 /**
949 * Show token limit error popup
950 */
951 function showTokenLimitError(message) {
952 // Remove any existing popups first
953 $('.king-addons-translator-overlay').remove();
954 $('.king-addons-translator-popup').remove();
955
956 // Wait a bit to ensure cleanup is complete
957 setTimeout(function() {
958 showTokenLimitErrorDelayed(message);
959 }, 100);
960 }
961
962 function showTokenLimitErrorDelayed(message) {
963 var settingsUrl = window.KingAddonsAiField && window.KingAddonsAiField.settings_url
964 ? window.KingAddonsAiField.settings_url
965 : '/wp-admin/admin.php?page=king-addons-ai-settings';
966
967 var $overlay = $('<div class="king-addons-translator-overlay"></div>');
968 var $popup = $('<div class="king-addons-translator-popup"></div>');
969
970 var errorHtml = `
971 <div style="text-align: center; margin-bottom: 32px;">
972 <h3 style="margin: 0 0 16px 0; color: #2d3748; font-size: 20px; text-align: center;justify-content: center;">🛡️ Daily Limit Reached</h3>
973 <p style="color: #718096; margin: 0; font-size: 14px;">Your safety limit is protecting your account!</p>
974 </div>
975
976 <div style="background: linear-gradient(135deg, #e8f5e8 0%, #f0f8f0 100%); border: 1px solid #c3e6cb; padding: 20px; border-radius: 12px; margin-bottom: 20px;">
977 <h4 style="color: #155724; margin: 0 0 12px 0; font-size: 16px;">🎯 What's happening?</h4>
978 <p style="color: #155724; margin: 0 0 16px 0; font-size: 14px; line-height: 1.5;">
979 You have a <strong>"Daily Token Limit"</strong> setting that prevents accidental overspending.
980 This is a <em>good thing</em> - it's working as intended to protect your account!
981 </p>
982
983 <h4 style="color: #155724; margin: 0 0 12px 0; font-size: 16px;">⚙️ Easy solutions:</h4>
984
985 <div style="margin-bottom: 12px; display: flex; align-items: center;">
986 <span style="background: #28a745; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; margin-right: 12px; font-size: 12px; font-weight: bold;">1</span>
987 <span style="color: #155724;"><strong>Increase the "Daily Token Limit"</strong> in AI Settings (recommended)</span>
988 </div>
989
990 <div style="margin-bottom: 12px; display: flex; align-items: center;">
991 <span style="background: #28a745; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; margin-right: 12px; font-size: 12px; font-weight: bold;">2</span>
992 <span style="color: #155724;">Or wait until tomorrow (limit automatically resets)</span>
993 </div>
994 </div>
995
996 <div style="background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 16px; margin-bottom: 20px;">
997 <div style="display: flex; align-items: center; margin-bottom: 8px;">
998 <span style="color: #495057; margin-right: 8px; font-size: 16px;">💡</span>
999 <strong style="color: #495057;">Quick tip</strong>
1000 </div>
1001 <p style="color: #6c757d; margin: 0; font-size: 14px; line-height: 1.4;">
1002 Just go to <strong>AI Settings → Daily Token Limit</strong> and set a higher number.
1003 For regular use, try setting it to <strong>50,000 or 100,000 tokens</strong>.
1004 </p>
1005 </div>
1006
1007 <div style="display: flex; gap: 12px;">
1008 <button class="king-addons-translator-btn-secondary" id="king-addons-limit-close" style="flex: 1;">
1009 I understand
1010 </button>
1011 <a href="${settingsUrl}" class="king-addons-translator-btn-primary" style="flex: 1; text-decoration: none; display: flex; align-items: center; justify-content: center;">
1012 Open AI Settings
1013 </a>
1014 </div>
1015 `;
1016
1017 $popup.html(errorHtml);
1018 $overlay.append($popup);
1019 $('body').append($overlay);
1020
1021 // Bind close event
1022 $('#king-addons-limit-close').on('click', function() {
1023 $overlay.remove();
1024 });
1025
1026 // Close on overlay click
1027 $overlay.on('click', function(e) {
1028 if (e.target === $overlay[0]) {
1029 $overlay.remove();
1030 }
1031 });
1032 }
1033
1034 /**
1035 * Disable/enable the AI Translator button
1036 */
1037 function toggleTranslatorButton(disabled) {
1038 var $button = $('.king-addons-ai-translator-btn');
1039
1040 if (disabled) {
1041 $button.prop('disabled', true);
1042 $button.css('opacity', '0.5');
1043 $button.css('cursor', 'not-allowed');
1044 } else {
1045 $button.prop('disabled', false);
1046 $button.css('opacity', '1');
1047 $button.css('cursor', 'pointer');
1048 }
1049 }
1050
1051 /**
1052 * Stop the translation process
1053 */
1054 function stopTranslationProcess() {
1055 // Prevent multiple calls
1056 if (translationState.isCancelled) {
1057 console.log('⚠️ Translation already cancelled, ignoring duplicate stop request');
1058 return;
1059 }
1060
1061 console.log('🛑 Stopping translation process...');
1062 translationState.isCancelled = true;
1063 translationState.isTranslating = false;
1064
1065 // Cancel all active AJAX requests
1066 if (translationState.currentRequests.length > 0) {
1067 console.log('Cancelling ' + translationState.currentRequests.length + ' active AJAX requests');
1068 translationState.currentRequests.forEach(function(request) {
1069 if (request && request.abort) {
1070 request.abort();
1071 }
1072 });
1073 translationState.currentRequests = [];
1074 }
1075
1076 // Remove any highlighting from current element
1077 if (translationState.currentElement) {
1078 highlightElementInPreview(translationState.currentElement.elementId, false);
1079 }
1080
1081 // Remove any existing popups/overlays
1082 $('.king-addons-translator-overlay').remove();
1083
1084 // Show cancellation message
1085 console.log('Translation process cancelled by user');
1086
1087 // Re-enable the button
1088 toggleTranslatorButton(false);
1089 }
1090
1091 /**
1092 * Animate popup to top-right corner
1093 */
1094 function movePopupToCorner($popup, $overlay) {
1095 return new Promise(function(resolve) {
1096 // Add moving class for smooth animation
1097 $popup.addClass('moving');
1098
1099 // Hide overlay with fade
1100 $overlay.addClass('hiding');
1101
1102 // Calculate current position and target position
1103 var currentRect = $popup[0].getBoundingClientRect();
1104 var targetTop = 80;
1105 var targetRight = 20;
1106 var targetLeft = window.innerWidth - 350 - 20;
1107
1108 // Move popup from overlay to body with current position
1109 $popup.css({
1110 'position': 'fixed',
1111 'top': currentRect.top + 'px',
1112 'left': currentRect.left + 'px',
1113 'width': currentRect.width + 'px',
1114 'margin': '0',
1115 'transform': 'none',
1116 'z-index': 999999
1117 });
1118
1119 // Append popup to body (remove from overlay)
1120 $('body').append($popup);
1121
1122 // Wait for overlay to fade, then animate popup
1123 setTimeout(function() {
1124 // Force reflow
1125 $popup[0].offsetHeight;
1126
1127 // Animate to final position
1128 $popup.css({
1129 'top': targetTop + 'px',
1130 'left': targetLeft + 'px',
1131 'width': '350px',
1132 'padding': '16px'
1133 });
1134
1135 // Add compact class after animation and remove overlay
1136 setTimeout(function() {
1137 $popup.removeClass('moving').addClass('compact');
1138 $overlay.remove(); // Remove overlay completely
1139 resolve();
1140 }, 500);
1141
1142 }, 300);
1143 });
1144 }
1145
1146 /**
1147 * Create and show the main popup
1148 */
1149 function createAndShowPopup() {
1150 var $overlay = $('<div class="king-addons-translator-overlay"></div>');
1151 var $popup = $('<div class="king-addons-translator-popup"></div>');
1152
1153 var isPro = isPremiumActive();
1154 var customOptionHtml = isPro ?
1155 '<option value="custom">Custom language or prompt (PRO)</option>' :
1156 '<option value="custom" disabled>Custom language or prompt (PRO)</option>';
1157
1158 var upgradeUrl = 'https://kingaddons.com/pricing/?utm_source=ai-translator&utm_medium=plugin&utm_campaign=custom-prompts';
1159 var proInfoHtml = isPro ?
1160 '<div class="king-addons-pro-info" style="color: #4CAF50; border-left-color: #4CAF50;">�
1161 PRO Active: Use custom languages and translation prompts!</div>' :
1162 '<div class="king-addons-pro-info">💎 <a href="' + upgradeUrl + '" target="_blank" style="color: #5B03FF; text-decoration: none;">Upgrade to PRO</a> to use custom languages, regional dialects and custom translation prompts (formal tone, technical style, etc.)!</div>';
1163
1164 var popupContent = `
1165 <h3>
1166 <img src="${KingAddonsAiField.plugin_url}includes/admin/img/ai.svg" style="width:20px;height:20px;filter: invert(1);"/>
1167 AI Page Translator
1168 </h3>
1169 <div style="font-size: 12px; color: #666; margin-bottom: 16px; line-height: 1.4;">
1170 Translate to any language or transform text style (formal, casual, technical, etc.)
1171 </div>
1172 <div class="king-addons-translator-form">
1173 <div class="king-addons-translator-field">
1174 <label>From Language</label>
1175 <select id="king-addons-from-lang">
1176 <option value="auto">Auto-detect</option>
1177 ${Object.keys(languages).map(code =>
1178 `<option value="${code}">${languages[code]}</option>`
1179 ).join('')}
1180 ${customOptionHtml}
1181 </select>
1182 <div class="king-addons-custom-language-field" id="king-addons-custom-from-field">
1183 <label>Custom language or translation prompt</label>
1184 <input type="text" id="king-addons-custom-from-lang" placeholder="e.g., Klingon, Old English, formal business tone, medical terminology..." />
1185 <div class="king-addons-prompt-examples">
1186 <small>Examples: "Klingon", "Shakespeare English", "formal business style", "casual conversational tone"</small>
1187 </div>
1188 </div>
1189 </div>
1190 <div class="king-addons-translator-field">
1191 <label>To Language</label>
1192 <select id="king-addons-to-lang">
1193 ${Object.keys(languages).map(code =>
1194 `<option value="${code}" ${code === 'en' ? 'selected' : ''}>${languages[code]}</option>`
1195 ).join('')}
1196 ${customOptionHtml}
1197 </select>
1198 <div class="king-addons-custom-language-field" id="king-addons-custom-to-field">
1199 <label>Custom language or translation style</label>
1200 <input type="text" id="king-addons-custom-to-lang" placeholder="e.g., Dothraki, Academic writing, pirate speak, baby talk..." />
1201 <div class="king-addons-prompt-examples">
1202 <small>Examples: "Dothraki", "academic paper style", "pirate language", "simplified for children"</small>
1203 </div>
1204 </div>
1205 </div>
1206 ${proInfoHtml}
1207 <div class="king-addons-translator-actions">
1208 <button class="king-addons-translator-btn-secondary" id="king-addons-cancel-translation">
1209 Cancel
1210 </button>
1211 <button class="king-addons-translator-btn-primary" id="king-addons-start-translation">
1212 Start Translation
1213 </button>
1214 </div>
1215 </div>
1216 `;
1217
1218 $popup.html(popupContent);
1219 $overlay.append($popup);
1220 $('body').append($overlay);
1221
1222 // Store references globally
1223 window.currentTranslatorPopup = $popup;
1224 window.currentTranslatorOverlay = $overlay;
1225
1226 // Bind events for language selection
1227 bindLanguageSelectionEvents();
1228
1229 // Bind events
1230 $('#king-addons-cancel-translation').on('click', function() {
1231 if (!translationState.isTranslating) {
1232 $overlay.remove();
1233 }
1234 });
1235
1236 $('#king-addons-start-translation').on('click', function() {
1237 var result = getSelectedLanguages();
1238
1239 if (!result.valid) {
1240 alert(result.error);
1241 return;
1242 }
1243
1244 // Double-check API key before starting translation
1245 var $button = $(this);
1246 $button.prop('disabled', true).text('🔍 Verifying...');
1247
1248 $.post(KingAddonsAiField.ajax_url, {
1249 action: 'king_addons_ai_check_tokens',
1250 nonce: KingAddonsAiField.generate_nonce
1251 }, function(response) {
1252 $button.prop('disabled', false).text('Start Translation');
1253
1254 if (!response.success || !response.data.api_key_valid) {
1255 var errorMessage = 'API key verification failed. Please check your API key in settings.';
1256 if (response.data && response.data.error_message) {
1257 errorMessage = response.data.error_message;
1258 }
1259
1260 // Check for token limit errors first
1261 if (errorMessage.toLowerCase().includes('token limit') ||
1262 errorMessage.toLowerCase().includes('daily limit') ||
1263 errorMessage.toLowerCase().includes('limit reached') ||
1264 errorMessage.toLowerCase().includes('quota exceeded') ||
1265 errorMessage.toLowerCase().includes('rate limit') ||
1266 errorMessage.toLowerCase().includes('too many requests')) {
1267
1268 // Show token limit error popup
1269 showTokenLimitError(errorMessage);
1270 return;
1271 }
1272
1273 // Show error popup (will handle cleanup automatically)
1274 showApiKeyError('Setup Required', errorMessage);
1275 return;
1276 }
1277
1278 // API key is valid, proceed with translation
1279 startTranslation(result.fromLang, result.toLang, $popup, $overlay);
1280
1281 }).fail(function() {
1282 $button.prop('disabled', false).text('Start Translation');
1283
1284 // Show error popup (will handle cleanup automatically)
1285 showApiKeyError('Connection Issue', 'Failed to connect right now. Please check your connection and try again.');
1286 });
1287 });
1288
1289 // Close on overlay click only if not translating
1290 $overlay.on('click', function(e) {
1291 if (e.target === $overlay[0] && !translationState.isTranslating) {
1292 $overlay.remove();
1293 }
1294 });
1295 }
1296
1297 /**
1298 * Bind events for language selection dropdowns
1299 */
1300 function bindLanguageSelectionEvents() {
1301 // Handle From Language selection
1302 $('#king-addons-from-lang').on('change', function() {
1303 var selectedValue = $(this).val();
1304 var $customField = $('#king-addons-custom-from-field');
1305
1306 if (selectedValue === 'custom') {
1307 if (!isPremiumActive()) {
1308 // Reset to previous value and show upgrade message
1309 $(this).val('auto');
1310 alert('Custom languages and translation prompts are a PRO feature. Please upgrade to King Addons PRO to use custom languages or translation styles.');
1311 return;
1312 }
1313 $customField.addClass('show');
1314 $('#king-addons-custom-from-lang').focus();
1315 } else {
1316 $customField.removeClass('show');
1317 }
1318 });
1319
1320 // Handle To Language selection
1321 $('#king-addons-to-lang').on('change', function() {
1322 var selectedValue = $(this).val();
1323 var $customField = $('#king-addons-custom-to-field');
1324
1325 if (selectedValue === 'custom') {
1326 if (!isPremiumActive()) {
1327 // Reset to previous value and show upgrade message
1328 $(this).val('en');
1329 alert('Custom languages and translation prompts are a PRO feature. Please upgrade to King Addons PRO to use custom languages or translation styles.');
1330 return;
1331 }
1332 $customField.addClass('show');
1333 $('#king-addons-custom-to-lang').focus();
1334 } else {
1335 $customField.removeClass('show');
1336 }
1337 });
1338 }
1339
1340 /**
1341 * Get selected languages with validation
1342 */
1343 function getSelectedLanguages() {
1344 var fromLang = $('#king-addons-from-lang').val();
1345 var toLang = $('#king-addons-to-lang').val();
1346 var customFromLang = $('#king-addons-custom-from-lang').val().trim();
1347 var customToLang = $('#king-addons-custom-to-lang').val().trim();
1348
1349 // Handle custom from language
1350 if (fromLang === 'custom') {
1351 if (!customFromLang) {
1352 return {
1353 valid: false,
1354 error: 'Please enter a custom source language or translation prompt.'
1355 };
1356 }
1357 fromLang = customFromLang;
1358 }
1359
1360 // Handle custom to language
1361 if (toLang === 'custom') {
1362 if (!customToLang) {
1363 return {
1364 valid: false,
1365 error: 'Please enter a custom target language or translation style.'
1366 };
1367 }
1368 toLang = customToLang;
1369 }
1370
1371 // Validate languages are different (except auto-detect)
1372 if (fromLang === toLang && fromLang !== 'auto') {
1373 return {
1374 valid: false,
1375 error: 'Source and target languages cannot be the same.'
1376 };
1377 }
1378
1379 return {
1380 valid: true,
1381 fromLang: fromLang,
1382 toLang: toLang
1383 };
1384 }
1385
1386 /**
1387 * Start the translation process
1388 */
1389 function startTranslation(fromLang, toLang, $popup, $overlay) {
1390 translationState.isTranslating = true;
1391 translationState.isCancelled = false; // Reset cancellation flag
1392 translationState.currentRequests = []; // Clear any previous requests
1393 translationState.fromLang = fromLang;
1394 translationState.toLang = toLang;
1395 translationState.translatedElements = 0;
1396 translationState.failedElements = 0;
1397
1398 // Inject animation styles into preview iframe immediately
1399 injectPreviewStyles();
1400
1401 // Disable the AI Translator button
1402 toggleTranslatorButton(true);
1403
1404 // Get all translatable elements
1405 var elements = getTranslatableElements();
1406 translationState.totalElements = elements.length;
1407
1408 if (elements.length === 0) {
1409 alert('No translatable text elements found on this page.');
1410 translationState.isTranslating = false;
1411 toggleTranslatorButton(false);
1412 return;
1413 }
1414
1415 console.log('Starting translation of ' + elements.length + ' elements from ' + fromLang + ' to ' + toLang);
1416
1417 // Update popup to show progress
1418 showProgressInPopup($popup);
1419
1420 // Animate popup to corner and start translation
1421 movePopupToCorner($popup, $overlay).then(function() {
1422 // Start translating elements one by one
1423 translateElementsSequentially(elements, 0, $popup);
1424 });
1425 }
1426
1427 /**
1428 * Get all translatable text elements
1429 */
1430 function getTranslatableElements() {
1431 var elements = [];
1432 // Get the main document container using Elementor 3.0+ API
1433 var documentContainer = elementor.documents.getCurrent().container;
1434 var elementorElements = [];
1435
1436 // Use the new API to get children - for Elementor 3.0+
1437 if (documentContainer.children && typeof documentContainer.children.models !== 'undefined') {
1438 // Backbone collection - extract models
1439 elementorElements = documentContainer.children.models || [];
1440 } else if (documentContainer.elements && typeof documentContainer.elements.models !== 'undefined') {
1441 // Alternative property name in some Elementor versions
1442 elementorElements = documentContainer.elements.models || [];
1443 } else if (Array.isArray(documentContainer.children)) {
1444 // Fallback for older API
1445 elementorElements = documentContainer.children;
1446 } else {
1447 console.warn('🚨 Unable to find container children using any known API');
1448 elementorElements = [];
1449 }
1450
1451 function processContainer(container) {
1452 var model = container.model;
1453 var elementType = model.get('elType');
1454 var widgetType = model.get('widgetType');
1455
1456 // Process text-based widgets
1457 if (widgetType) {
1458 // First, check if this widget type should be skipped entirely
1459 var nonTextWidgets = [
1460 'spacer', 'divider', 'html', 'shortcode', 'sidebar',
1461 'menu-anchor', 'read-more', 'google_maps', 'paypal_button',
1462 'stripe_button', 'facebook_button', 'facebook_page',
1463 'video', 'audio', 'iframe', 'code', 'wp-widget',
1464 'map', 'rating', 'progress', 'counter', 'countdown',
1465 'social-icons', 'share-buttons', 'login', 'lottie',
1466 'image' // Image widget should be skipped
1467 ];
1468
1469 if (nonTextWidgets.indexOf(widgetType) !== -1) {
1470 console.log('⚪ Skipping non-text widget:', widgetType, '(blacklisted widget type)');
1471 return; // Exit early for blacklisted widgets
1472 }
1473
1474 var settings = model.get('settings').attributes;
1475
1476 // Now check for text fields in remaining widgets
1477 var textFields = getTextFieldsForWidget(widgetType, settings, container);
1478
1479 // If we found text fields, process the widget
1480 if (textFields.length > 0) {
1481 console.log('�
1482 Processing widget:', widgetType, 'with', textFields.length, 'text fields');
1483 elements.push({
1484 container: container,
1485 widgetType: widgetType,
1486 textFields: textFields,
1487 elementId: model.get('id')
1488 });
1489 return;
1490 } else {
1491 console.log('⚪ No text fields found in widget:', widgetType);
1492 }
1493 }
1494
1495 // Process child containers recursively using Elementor 3.0+ API
1496 if (container.children && container.children.length > 0) {
1497 // Check if children is a Backbone collection
1498 if (typeof container.children.models !== 'undefined') {
1499 container.children.models.forEach(processContainer);
1500 } else if (Array.isArray(container.children)) {
1501 container.children.forEach(processContainer);
1502 }
1503 }
1504 }
1505
1506 elementorElements.forEach(processContainer);
1507 return elements;
1508 }
1509
1510 /**
1511 * Get text fields for a specific widget type using Elementor control types
1512 */
1513 function getTextFieldsForWidget(widgetType, settings, container) {
1514 console.log('🔍 Analyzing widget:', widgetType);
1515 console.log('🔍 Widget settings keys:', Object.keys(settings));
1516
1517 var textFields = [];
1518
1519 // Try to get widget controls schema from Elementor
1520 var controls = getWidgetControls(widgetType, container);
1521
1522 if (controls && Object.keys(controls).length > 0) {
1523 console.log('📋 Found widget controls schema with', Object.keys(controls).length, 'controls');
1524
1525 // Look for text-based controls
1526 Object.keys(controls).forEach(function(controlName) {
1527 var control = controls[controlName];
1528 var controlType = control.type;
1529 var settingValue = settings[controlName];
1530
1531 // Check if this is a text-based control type
1532 var textControlTypes = [
1533 'text', 'textarea', 'wysiwyg', 'url', 'email',
1534 'password', 'search', 'tel', 'date', 'time',
1535 'datetime-local', 'month', 'week'
1536 ];
1537
1538 if (textControlTypes.includes(controlType)) {
1539 // Check if the field has a non-empty string value
1540 if (settingValue && typeof settingValue === 'string' && settingValue.trim()) {
1541 // Skip obviously non-translatable fields
1542 var skipFields = [
1543 '_element_id', '_css_classes', 'link', 'url', 'href',
1544 'custom_css', 'css_id', 'anchor', 'html_tag'
1545 ];
1546
1547 if (!skipFields.includes(controlName)) {
1548 console.log('�
1549 Found text control:', controlName, 'Type:', controlType, 'Value:', settingValue.substring(0, 50) + '...');
1550
1551 textFields.push({
1552 field: controlName,
1553 value: settingValue,
1554 type: controlType === 'wysiwyg' ? 'wysiwyg' : 'text'
1555 });
1556 } else {
1557 console.log('⚪ Skipping non-translatable field:', controlName, 'Type:', controlType);
1558 }
1559 }
1560 }
1561
1562 // Also check for repeater controls
1563 if (controlType === 'repeater' && settingValue) {
1564 console.log('🔍 Found repeater control:', controlName);
1565 checkRepeaterFieldsByType(controlName, control, settingValue, textFields);
1566 }
1567 });
1568 } else {
1569 console.log('⚠️ No widget controls schema found, falling back to manual detection');
1570
1571 // Fallback: Use the original method for widgets without accessible controls
1572 var commonTextFields = [
1573 'title', 'text', 'content', 'description', 'subtitle', 'button_text',
1574 'heading_title', 'heading_subtitle', 'testimonial_content', 'testimonial_name',
1575 'title_text', 'description_text', 'content_text', 'editor'
1576 ];
1577
1578 commonTextFields.forEach(function(field) {
1579 if (settings[field] && typeof settings[field] === 'string' && settings[field].trim()) {
1580 textFields.push({
1581 field: field,
1582 value: settings[field],
1583 type: field === 'editor' ? 'wysiwyg' : 'text'
1584 });
1585 }
1586 });
1587
1588 // Check for repeater fields using the old method
1589 checkRepeaterFields(settings, textFields);
1590 }
1591
1592 console.log('🔍 Final result for widget', widgetType + ':', textFields.length, 'text fields found');
1593 if (textFields.length > 0) {
1594 console.log('📝 All text fields:', textFields.map(f => f.field + ' = ' + f.value.substring(0, 30) + '...'));
1595 }
1596
1597 return textFields;
1598 }
1599
1600 /**
1601 * Get widget controls schema from Elementor
1602 */
1603 function getWidgetControls(widgetType, container) {
1604 try {
1605 // Method 1: Try to get controls from container model
1606 if (container && container.model && container.model.get) {
1607 var model = container.model;
1608
1609 // Try to get controls from the model's widget config
1610 if (model.config && model.config.controls) {
1611 console.log('📋 Found controls from model.config');
1612 return model.config.controls;
1613 }
1614
1615 // Try to get controls from the container settings
1616 if (container.settings && container.settings.controls) {
1617 console.log('📋 Found controls from container.settings');
1618 return container.settings.controls;
1619 }
1620 }
1621
1622 // Method 2: Try to get controls from Elementor widgets registry
1623 if (window.elementor && elementor.widgets) {
1624 var widgetConfig = elementor.widgets.getWidgetType(widgetType);
1625 if (widgetConfig && widgetConfig.controls) {
1626 console.log('📋 Found controls from widgets registry');
1627 return widgetConfig.controls;
1628 }
1629 }
1630
1631 // Method 3: Try to get controls from elements manager
1632 if (window.elementor && elementor.elementsManager) {
1633 var elementView = elementor.elementsManager.getElementView(container.model.get('id'));
1634 if (elementView && elementView.model && elementView.model.controls) {
1635 console.log('📋 Found controls from element view');
1636 return elementView.model.controls;
1637 }
1638 }
1639
1640 console.log('⚠️ Could not find controls schema for widget:', widgetType);
1641 return null;
1642
1643 } catch (error) {
1644 console.warn('⚠️ Error getting widget controls:', error);
1645 return null;
1646 }
1647 }
1648
1649 /**
1650 * Check repeater fields using control type information
1651 */
1652 function checkRepeaterFieldsByType(repeaterName, repeaterControl, repeaterData, textFields) {
1653 try {
1654 console.log('🔍 Analyzing repeater:', repeaterName, 'with control:', repeaterControl);
1655
1656 // Get the fields schema for this repeater
1657 var repeaterFields = repeaterControl.fields || repeaterControl.controls || {};
1658
1659 // Find text-based fields in the repeater schema
1660 var textFieldNames = [];
1661 Object.keys(repeaterFields).forEach(function(fieldName) {
1662 var fieldControl = repeaterFields[fieldName];
1663 var textControlTypes = ['text', 'textarea', 'wysiwyg', 'url', 'email'];
1664
1665 if (textControlTypes.includes(fieldControl.type)) {
1666 textFieldNames.push(fieldName);
1667 }
1668 });
1669
1670 console.log('🔍 Found text fields in repeater schema:', textFieldNames);
1671
1672 if (textFieldNames.length === 0) {
1673 console.log('⚠️ No text fields found in repeater schema');
1674 return;
1675 }
1676
1677 // Process repeater data (same as before)
1678 if (repeaterData && typeof repeaterData === 'object' && repeaterData.models) {
1679 // Backbone collection
1680 const models = repeaterData.models || [];
1681 for (let i = 0; i < models.length; i++) {
1682 const model = models[i];
1683 const modelData = model.attributes || model.toJSON();
1684
1685 for (const fieldName of textFieldNames) {
1686 if (modelData[fieldName] && typeof modelData[fieldName] === 'string' && modelData[fieldName].trim()) {
1687 const fieldKey = `${repeaterName}[${i}][${fieldName}]`;
1688 const fieldValue = modelData[fieldName];
1689
1690 console.log(`�
1691 Found repeater text field: ${fieldKey} = ${fieldValue}`);
1692 textFields.push({
1693 field: fieldKey,
1694 value: fieldValue,
1695 type: 'text',
1696 isRepeater: true,
1697 repeaterKey: repeaterName,
1698 repeaterIndex: i,
1699 repeaterField: fieldName
1700 });
1701 }
1702 }
1703 }
1704 } else if (Array.isArray(repeaterData)) {
1705 // Regular array
1706 for (let i = 0; i < repeaterData.length; i++) {
1707 const item = repeaterData[i];
1708 for (const fieldName of textFieldNames) {
1709 if (item[fieldName] && typeof item[fieldName] === 'string' && item[fieldName].trim()) {
1710 const fieldKey = `${repeaterName}[${i}][${fieldName}]`;
1711 const fieldValue = item[fieldName];
1712
1713 console.log(`�
1714 Found repeater text field: ${fieldKey} = ${fieldValue}`);
1715 textFields.push({
1716 field: fieldKey,
1717 value: fieldValue,
1718 type: 'text',
1719 isRepeater: true,
1720 repeaterKey: repeaterName,
1721 repeaterIndex: i,
1722 repeaterField: fieldName
1723 });
1724 }
1725 }
1726 }
1727 }
1728
1729 } catch (error) {
1730 console.warn('⚠️ Error processing repeater by type:', error);
1731 // Fallback to old method
1732 var repeaterConfig = {};
1733 repeaterConfig[repeaterName] = ['content', 'text', 'title', 'description'];
1734 checkRepeaterFields({[repeaterName]: repeaterData}, textFields);
1735 }
1736 }
1737
1738 /**
1739 * Check for repeater fields in settings (fallback method)
1740 */
1741 function checkRepeaterFields(settings, textFields) {
1742 // King Addons specific repeater configurations
1743 const repeaterConfigs = {
1744 'kng_styled_txt_content_items': ['kng_styled_txt_content'],
1745 'kng_tabs_items': ['kng_tabs_title', 'kng_tabs_content'],
1746 'kng_accordion_items': ['kng_accordion_title', 'kng_accordion_content'],
1747 'kng_testimonials_items': ['kng_testimonials_content', 'kng_testimonials_name'],
1748 'kng_team_members': ['kng_team_name', 'kng_team_position', 'kng_team_description'],
1749 'kng_price_list_items': ['kng_price_title', 'kng_price_description'],
1750 'kng_business_hours_items': ['kng_business_day', 'kng_business_hours'],
1751 // Standard Elementor repeaters
1752 'tabs': ['tab_title', 'tab_content'],
1753 'icon_list': ['text'],
1754 'slides': ['heading', 'description', 'button_text'],
1755 'list_items': ['text'],
1756 'testimonials': ['testimonial_content', 'testimonial_name'],
1757 'items': ['item_title', 'item_description', 'item_content'],
1758 'price_list': ['price_title', 'price_description']
1759 };
1760
1761 console.log('🔍 Starting fallback repeater field check for settings:', Object.keys(settings));
1762
1763 for (const [repeaterKey, fieldNames] of Object.entries(repeaterConfigs)) {
1764 if (settings[repeaterKey]) {
1765 console.log(`🔍 Found repeater key: ${repeaterKey}`);
1766
1767 let repeaterData = settings[repeaterKey];
1768
1769 // Handle Backbone Collections (common in King Addons and some Elementor widgets)
1770 if (repeaterData && typeof repeaterData === 'object' && repeaterData.models) {
1771 console.log(`🔍 BACKBONE COLLECTION detected for ${repeaterKey}:`, repeaterData);
1772 console.log(`🔍 Collection length:`, repeaterData.length || repeaterData.models.length);
1773
1774 // Extract models from Backbone collection
1775 const models = repeaterData.models || [];
1776 for (let i = 0; i < models.length; i++) {
1777 const model = models[i];
1778 const modelData = model.attributes || model.toJSON();
1779 console.log(`🔍 Processing Backbone model ${i}:`, modelData);
1780
1781 for (const fieldName of fieldNames) {
1782 if (modelData[fieldName] && typeof modelData[fieldName] === 'string' && modelData[fieldName].trim()) {
1783 const fieldKey = `${repeaterKey}[${i}][${fieldName}]`;
1784 const fieldValue = modelData[fieldName];
1785
1786 console.log(`�
1787 Found repeater text field: ${fieldKey} = ${fieldValue}`);
1788 textFields.push({
1789 field: fieldKey,
1790 value: fieldValue,
1791 type: 'text',
1792 isRepeater: true,
1793 repeaterKey: repeaterKey,
1794 repeaterIndex: i,
1795 repeaterField: fieldName
1796 });
1797 }
1798 }
1799 }
1800 }
1801 // Handle regular arrays
1802 else if (Array.isArray(repeaterData)) {
1803 console.log(`🔍 ARRAY detected for ${repeaterKey}, length:`, repeaterData.length);
1804
1805 for (let i = 0; i < repeaterData.length; i++) {
1806 const item = repeaterData[i];
1807 for (const fieldName of fieldNames) {
1808 if (item[fieldName] && typeof item[fieldName] === 'string' && item[fieldName].trim()) {
1809 const fieldKey = `${repeaterKey}[${i}][${fieldName}]`;
1810 const fieldValue = item[fieldName];
1811
1812 console.log(`�
1813 Found repeater text field: ${fieldKey} = ${fieldValue}`);
1814 textFields.push({
1815 field: fieldKey,
1816 value: fieldValue,
1817 type: 'text',
1818 isRepeater: true,
1819 repeaterKey: repeaterKey,
1820 repeaterIndex: i,
1821 repeaterField: fieldName
1822 });
1823 }
1824 }
1825 }
1826 }
1827 // Handle objects with numbered keys (alternative format)
1828 else if (repeaterData && typeof repeaterData === 'object') {
1829 console.log(`🔍 OBJECT detected for ${repeaterKey}:`, repeaterData);
1830 const keys = Object.keys(repeaterData).filter(key => /^\d+$/.test(key));
1831 console.log(`🔍 Found numeric keys:`, keys);
1832
1833 for (const key of keys) {
1834 const item = repeaterData[key];
1835 for (const fieldName of fieldNames) {
1836 if (item[fieldName] && typeof item[fieldName] === 'string' && item[fieldName].trim()) {
1837 const fieldKey = `${repeaterKey}[${key}][${fieldName}]`;
1838 const fieldValue = item[fieldName];
1839
1840 console.log(`�
1841 Found repeater text field: ${fieldKey} = ${fieldValue}`);
1842 textFields.push({
1843 field: fieldKey,
1844 value: fieldValue,
1845 type: 'text',
1846 isRepeater: true,
1847 repeaterKey: repeaterKey,
1848 repeaterIndex: key,
1849 repeaterField: fieldName
1850 });
1851 }
1852 }
1853 }
1854 }
1855 }
1856 }
1857 }
1858
1859 /**
1860 * Show progress UI in popup
1861 */
1862 function showProgressInPopup($popup) {
1863 // Update header for compact mode with close button
1864 var headerHtml = `
1865 <img src="${KingAddonsAiField.plugin_url}includes/admin/img/ai.svg" style="width:20px;height:20px;"/>
1866 AI Translation in Progress
1867 <button class="king-addons-translator-close-btn" title="Close">×</button>
1868 `;
1869
1870 var progressHtml = `
1871 <div class="king-addons-translator-progress">
1872 <div class="king-addons-translator-progress-text">
1873 Translating page elements... <span id="king-addons-progress-count">0 / ${translationState.totalElements}</span>
1874 </div>
1875 <div class="king-addons-translator-progress-bar">
1876 <div class="king-addons-translator-progress-fill" id="king-addons-progress-fill"></div>
1877 </div>
1878 <div class="king-addons-translator-current-element" id="king-addons-current-element">
1879 Starting translation...
1880 </div>
1881 </div>
1882 `;
1883
1884 // Update header
1885 $popup.find('h3').html(headerHtml);
1886
1887 // Hide the subtitle/description text during translation
1888 $popup.find('h3').next('div').hide();
1889
1890 $popup.find('.king-addons-translator-form').html(progressHtml);
1891
1892 // Note: Close button events are handled by global document handler to prevent duplicates
1893 }
1894
1895 /**
1896 * Translate elements sequentially
1897 */
1898 function translateElementsSequentially(elements, index, $popup) {
1899 // Check if translation was cancelled
1900 if (translationState.isCancelled) {
1901 console.log('Translation cancelled, stopping process');
1902 return;
1903 }
1904
1905 if (index >= elements.length) {
1906 console.log('🏁 Translation sequence complete! Index:', index, 'Total elements:', elements.length);
1907 console.log('🎯 Calling showTranslationComplete with popup:', $popup && $popup.length > 0 ? 'exists' : 'missing');
1908 showTranslationComplete($popup);
1909 return;
1910 }
1911
1912 var element = elements[index];
1913 translationState.currentElement = element;
1914
1915 // Update progress UI
1916 updateProgressUI(index + 1, element);
1917
1918 // Highlight current element in preview
1919 highlightElementInPreview(element.elementId, true);
1920
1921 // Translate all text fields for this element
1922 translateElementFields(element, function(success) {
1923 // Remove highlight
1924 highlightElementInPreview(element.elementId, false);
1925
1926 if (success) {
1927 translationState.translatedElements++;
1928 showElementSuccess(element.elementId);
1929 } else {
1930 translationState.failedElements++;
1931 }
1932
1933 // Continue with next element after a short delay
1934 setTimeout(function() {
1935 // Check if translation was cancelled before proceeding
1936 if (!translationState.isCancelled) {
1937 translateElementsSequentially(elements, index + 1, $popup);
1938 }
1939 }, 500);
1940 });
1941 }
1942
1943 /**
1944 * Translate all text fields for an element
1945 */
1946 function translateElementFields(element, callback) {
1947 // Check if translation was cancelled before starting
1948 if (translationState.isCancelled) {
1949 console.log('Translation cancelled, skipping element fields');
1950 callback(false);
1951 return;
1952 }
1953
1954 var fieldsToTranslate = element.textFields.slice();
1955 var translatedFields = {};
1956 var completedFields = 0;
1957 var hasErrors = false;
1958
1959 if (fieldsToTranslate.length === 0) {
1960 callback(true);
1961 return;
1962 }
1963
1964 function translateNextField() {
1965 // Check if translation was cancelled before processing next field
1966 if (translationState.isCancelled) {
1967 console.log('Translation cancelled, stopping field translation');
1968 callback(false);
1969 return;
1970 }
1971
1972 if (completedFields >= fieldsToTranslate.length) {
1973 // All fields translated, update the element
1974 if (Object.keys(translatedFields).length > 0 && !translationState.isCancelled) {
1975 updateElementSettings(element.container, translatedFields);
1976 }
1977 callback(!hasErrors);
1978 return;
1979 }
1980
1981 var field = fieldsToTranslate[completedFields];
1982 translateSingleField(field.value, function(translatedText, success) {
1983 // Check if translation was cancelled while waiting for response
1984 if (translationState.isCancelled) {
1985 console.log('Translation cancelled, ignoring field response');
1986 callback(false);
1987 return;
1988 }
1989
1990 if (success && translatedText) {
1991 translatedFields[field.field] = translatedText;
1992 } else {
1993 hasErrors = true;
1994 }
1995
1996 completedFields++;
1997 setTimeout(translateNextField, 200); // Small delay between field translations
1998 });
1999 }
2000
2001 translateNextField();
2002 }
2003
2004 /**
2005 * Translate a single text field
2006 */
2007 function translateSingleField(text, callback) {
2008 // Check if translation was cancelled before making request
2009 if (translationState.isCancelled) {
2010 console.log('Translation cancelled, skipping AJAX request');
2011 callback(text, false);
2012 return;
2013 }
2014
2015 var request = $.post(KingAddonsAiField.ajax_url, {
2016 action: 'king_addons_ai_translate_text',
2017 nonce: KingAddonsAiField.generate_nonce,
2018 text: text,
2019 from_lang: translationState.fromLang,
2020 to_lang: translationState.toLang
2021 }, function(response) {
2022 // Remove request from active requests list
2023 var index = translationState.currentRequests.indexOf(request);
2024 if (index > -1) {
2025 translationState.currentRequests.splice(index, 1);
2026 }
2027
2028 // Check if translation was cancelled while request was in progress
2029 if (translationState.isCancelled) {
2030 console.log('Translation cancelled, ignoring AJAX response');
2031 callback(text, false);
2032 return;
2033 }
2034
2035 if (response.success && response.data.translated_text) {
2036 callback(response.data.translated_text, true);
2037 } else {
2038 // Handle API errors with more detail
2039 var errorMessage = 'Translation failed';
2040 if (response.data && response.data.message) {
2041 errorMessage = response.data.message;
2042 } else if (!response.success && response.data) {
2043 errorMessage = typeof response.data === 'string' ? response.data : 'API Error';
2044 }
2045
2046 console.error('Translation API Error:', errorMessage);
2047
2048 // Check for token limit errors first
2049 if (errorMessage.toLowerCase().includes('token limit') ||
2050 errorMessage.toLowerCase().includes('daily limit') ||
2051 errorMessage.toLowerCase().includes('limit reached') ||
2052 errorMessage.toLowerCase().includes('quota exceeded') ||
2053 errorMessage.toLowerCase().includes('rate limit') ||
2054 errorMessage.toLowerCase().includes('too many requests')) {
2055
2056 // Stop translation process for limit errors
2057 stopTranslationProcess();
2058
2059 // Show token limit error popup
2060 setTimeout(function() {
2061 showTokenLimitError(errorMessage);
2062 }, 500);
2063 return;
2064 }
2065
2066 // Show error notification for API key issues
2067 if (errorMessage.toLowerCase().includes('api key') ||
2068 errorMessage.toLowerCase().includes('invalid') ||
2069 errorMessage.toLowerCase().includes('unauthorized')) {
2070
2071 // Stop translation process for critical errors
2072 stopTranslationProcess();
2073
2074 // Show API key error popup
2075 setTimeout(function() {
2076 showApiKeyError('Setup Required', errorMessage + '\n\nPlease check your API key in settings and try again.');
2077 }, 500);
2078 return;
2079 }
2080
2081 callback(text, false);
2082 }
2083 }).fail(function(xhr, textStatus, errorThrown) {
2084 // Remove request from active requests list
2085 var index = translationState.currentRequests.indexOf(request);
2086 if (index > -1) {
2087 translationState.currentRequests.splice(index, 1);
2088 }
2089
2090 // Don't process failed requests if cancelled
2091 if (translationState.isCancelled) {
2092 return;
2093 }
2094
2095 // Handle different types of network errors
2096 var errorMessage = 'Network error occurred';
2097 var shouldShowError = false;
2098
2099 if (xhr.status === 0) {
2100 errorMessage = 'Network connection failed. Please check your internet connection.';
2101 shouldShowError = true;
2102 } else if (xhr.status === 401) {
2103 errorMessage = 'API key is invalid or expired. Please check your API key in settings.';
2104 shouldShowError = true;
2105 } else if (xhr.status === 403) {
2106 errorMessage = 'Access forbidden. Please check your API key permissions.';
2107 shouldShowError = true;
2108 } else if (xhr.status === 429) {
2109 // Try to get detailed error message from response
2110 var detailedMessage = 'Rate limit exceeded. Please wait a moment and try again.';
2111 if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
2112 detailedMessage = xhr.responseJSON.data.message;
2113 } else if (xhr.responseText) {
2114 try {
2115 var response = JSON.parse(xhr.responseText);
2116 if (response && response.data && response.data.message) {
2117 detailedMessage = response.data.message;
2118 }
2119 } catch (e) {
2120 // Keep default message if parsing fails
2121 }
2122 }
2123 errorMessage = detailedMessage;
2124 shouldShowError = true;
2125 } else if (xhr.status >= 500) {
2126 errorMessage = 'Server error. Please try again later.';
2127 shouldShowError = true;
2128 }
2129
2130 console.error('Translation Network Error:', {
2131 status: xhr.status,
2132 statusText: textStatus,
2133 error: errorThrown,
2134 message: errorMessage,
2135 responseText: xhr.responseText,
2136 responseJSON: xhr.responseJSON
2137 });
2138
2139 // Log specifically for token limit debugging
2140 if (xhr.status === 429 || errorMessage.toLowerCase().includes('limit')) {
2141 console.log('🔍 Potential token limit error detected:', {
2142 status: xhr.status,
2143 message: errorMessage,
2144 fullResponse: xhr.responseText
2145 });
2146 }
2147
2148 // Show error popup for critical network issues
2149 if (shouldShowError) {
2150 stopTranslationProcess();
2151
2152 setTimeout(function() {
2153 // Check for token limit errors first (including 429 status)
2154 if (errorMessage.toLowerCase().includes('token limit') ||
2155 errorMessage.toLowerCase().includes('daily limit') ||
2156 errorMessage.toLowerCase().includes('limit reached') ||
2157 errorMessage.toLowerCase().includes('quota exceeded') ||
2158 errorMessage.toLowerCase().includes('rate limit exceeded') ||
2159 errorMessage.toLowerCase().includes('too many requests') ||
2160 (xhr.status === 429 && (
2161 errorMessage.toLowerCase().includes('limit') ||
2162 errorMessage.toLowerCase().includes('exceeded') ||
2163 errorMessage.toLowerCase().includes('quota')
2164 ))) {
2165
2166 showTokenLimitError(errorMessage);
2167 return;
2168 }
2169
2170 if (xhr.status === 401 || xhr.status === 403) {
2171 showApiKeyError('Authentication Error', errorMessage);
2172 } else {
2173 showApiKeyError('Network Error', errorMessage);
2174 }
2175 }, 500);
2176 return;
2177 }
2178
2179 callback(text, false);
2180 });
2181
2182 // Store the request so we can cancel it if needed
2183 translationState.currentRequests.push(request);
2184 }
2185
2186 /**
2187 * Update element settings with translated text
2188 */
2189 function updateElementSettings(container, translatedFields) {
2190 try {
2191 console.log('🔄 Updating element settings for widget:', container.model.get('widgetType'));
2192 console.log('🔄 All translated fields:', translatedFields);
2193
2194 // Separate regular fields from repeater fields
2195 var regularFields = {};
2196 var repeaterUpdates = {};
2197
2198 Object.keys(translatedFields).forEach(function(fieldKey) {
2199 var translatedValue = translatedFields[fieldKey];
2200
2201 // Check if this is a repeater field
2202 var repeaterMatch = fieldKey.match(/^(.+)\[(\d+)\]\[(.+)\]$/);
2203 if (repeaterMatch) {
2204 // This is a repeater field: repeaterKey[index][fieldName]
2205 var repeaterKey = repeaterMatch[1];
2206 var itemIndex = parseInt(repeaterMatch[2]);
2207 var itemField = repeaterMatch[3];
2208
2209 console.log('📝 Found repeater field:', {
2210 fullKey: fieldKey,
2211 repeaterKey: repeaterKey,
2212 itemIndex: itemIndex,
2213 itemField: itemField,
2214 value: translatedValue
2215 });
2216
2217 if (!repeaterUpdates[repeaterKey]) {
2218 repeaterUpdates[repeaterKey] = {};
2219 }
2220 if (!repeaterUpdates[repeaterKey][itemIndex]) {
2221 repeaterUpdates[repeaterKey][itemIndex] = {};
2222 }
2223 repeaterUpdates[repeaterKey][itemIndex][itemField] = translatedValue;
2224 } else {
2225 // Regular field
2226 console.log('📝 Found regular field:', fieldKey, '=', translatedValue);
2227 regularFields[fieldKey] = translatedValue;
2228 }
2229 });
2230
2231 // Apply regular field updates
2232 if (Object.keys(regularFields).length > 0) {
2233 console.log('🔧 Applying regular field updates:', regularFields);
2234 $e.run('document/elements/settings', {
2235 container: container,
2236 settings: regularFields
2237 });
2238 console.log('�
2239 Regular field updates applied successfully');
2240 }
2241
2242 // Apply repeater field updates
2243 Object.keys(repeaterUpdates).forEach(function(repeaterKey) {
2244 console.log('🔧 Updating repeater field:', repeaterKey, repeaterUpdates[repeaterKey]);
2245 var currentSettings = container.settings.get(repeaterKey);
2246
2247 // Handle Backbone Collections (King Addons and some Elementor widgets)
2248 if (currentSettings && typeof currentSettings.models !== 'undefined') {
2249 console.log('📦 Found Backbone collection for:', repeaterKey, 'Length:', currentSettings.length);
2250
2251 // Work with Backbone collection
2252 Object.keys(repeaterUpdates[repeaterKey]).forEach(function(itemIndex) {
2253 var index = parseInt(itemIndex);
2254 if (currentSettings.models[index]) {
2255 var model = currentSettings.models[index];
2256
2257 // Update the specific fields in this repeater item
2258 Object.keys(repeaterUpdates[repeaterKey][itemIndex]).forEach(function(fieldName) {
2259 var oldValue = model.get(fieldName);
2260 var newValue = repeaterUpdates[repeaterKey][itemIndex][fieldName];
2261
2262 console.log('📝 Updating Backbone model [' + index + '][' + fieldName + ']:', oldValue, '->', newValue);
2263
2264 // Update the model attribute
2265 model.set(fieldName, newValue);
2266 });
2267 }
2268 });
2269
2270 // Use Elementor's proper API to notify of changes instead of direct trigger
2271 try {
2272 // Method 1: Use Elementor's run command to update the entire repeater
2273 var backboneData = currentSettings.toJSON ? currentSettings.toJSON() :
2274 currentSettings.models.map(function(model) {
2275 return model.toJSON ? model.toJSON() : model.attributes;
2276 });
2277
2278 var repeaterSettings = {};
2279 repeaterSettings[repeaterKey] = backboneData;
2280
2281 console.log('🔧 Applying Backbone collection update via Elementor API:', {
2282 repeaterKey: repeaterKey,
2283 dataLength: backboneData.length,
2284 settingsObject: repeaterSettings
2285 });
2286
2287 $e.run('document/elements/settings', {
2288 container: container,
2289 settings: repeaterSettings
2290 });
2291
2292 console.log('�
2293 Backbone collection updates applied via Elementor API for:', repeaterKey);
2294 } catch (e) {
2295 console.warn('⚠️ Error updating via Elementor API, trying alternative method:', e);
2296
2297 // Fallback: Try to manually trigger save without change events
2298 try {
2299 if (typeof container.saveSettings === 'function') {
2300 container.saveSettings();
2301 console.log('📡 Triggered saveSettings as fallback for:', repeaterKey);
2302 }
2303 } catch (e2) {
2304 console.warn('⚠️ Fallback method also failed:', e2);
2305 }
2306 }
2307
2308 console.log('�
2309 Backbone collection updates applied successfully for:', repeaterKey);
2310 }
2311 // Handle regular arrays (standard Elementor repeaters)
2312 else if (Array.isArray(currentSettings)) {
2313 console.log('📦 Found array for:', repeaterKey, 'Length:', currentSettings.length);
2314
2315 var updatedRepeater = currentSettings.slice(); // Clone array
2316
2317 Object.keys(repeaterUpdates[repeaterKey]).forEach(function(itemIndex) {
2318 var index = parseInt(itemIndex);
2319 if (updatedRepeater[index]) {
2320 // Update the specific fields in this repeater item
2321 Object.keys(repeaterUpdates[repeaterKey][itemIndex]).forEach(function(fieldName) {
2322 var oldValue = updatedRepeater[index][fieldName];
2323 var newValue = repeaterUpdates[repeaterKey][itemIndex][fieldName];
2324 updatedRepeater[index][fieldName] = newValue;
2325 console.log('📝 Updated array item [' + index + '][' + fieldName + ']:', oldValue, '->', newValue);
2326 });
2327 }
2328 });
2329
2330 // Update the entire repeater field
2331 var repeaterSettings = {};
2332 repeaterSettings[repeaterKey] = updatedRepeater;
2333
2334 console.log('🔧 Applying array repeater settings update:', {
2335 repeaterKey: repeaterKey,
2336 originalLength: currentSettings.length,
2337 updatedLength: updatedRepeater.length,
2338 settingsObject: repeaterSettings
2339 });
2340
2341 $e.run('document/elements/settings', {
2342 container: container,
2343 settings: repeaterSettings
2344 });
2345 console.log('�
2346 Array repeater updates applied successfully for:', repeaterKey);
2347 }
2348 // Handle objects with numbered keys
2349 else if (currentSettings && typeof currentSettings === 'object') {
2350 console.log('📦 Found object for:', repeaterKey, 'Keys:', Object.keys(currentSettings));
2351
2352 var updatedObject = Object.assign({}, currentSettings); // Clone object
2353
2354 Object.keys(repeaterUpdates[repeaterKey]).forEach(function(itemIndex) {
2355 if (updatedObject[itemIndex]) {
2356 // Update the specific fields in this repeater item
2357 Object.keys(repeaterUpdates[repeaterKey][itemIndex]).forEach(function(fieldName) {
2358 var oldValue = updatedObject[itemIndex][fieldName];
2359 var newValue = repeaterUpdates[repeaterKey][itemIndex][fieldName];
2360 updatedObject[itemIndex][fieldName] = newValue;
2361 console.log('📝 Updated object item [' + itemIndex + '][' + fieldName + ']:', oldValue, '->', newValue);
2362 });
2363 }
2364 });
2365
2366 // Update the entire repeater field
2367 var repeaterSettings = {};
2368 repeaterSettings[repeaterKey] = updatedObject;
2369
2370 $e.run('document/elements/settings', {
2371 container: container,
2372 settings: repeaterSettings
2373 });
2374 console.log('�
2375 Object repeater updates applied successfully for:', repeaterKey);
2376 } else {
2377 console.log('⚠️ Repeater field not found or unsupported type:', repeaterKey, typeof currentSettings, currentSettings);
2378 }
2379 });
2380
2381 } catch (error) {
2382 console.error('❌ Error updating element settings:', error);
2383 console.error('Error details:', {
2384 message: error.message,
2385 stack: error.stack,
2386 translatedFields: translatedFields,
2387 widgetType: container.model.get('widgetType')
2388 });
2389 }
2390 }
2391
2392 /**
2393 * Update progress UI
2394 */
2395 function updateProgressUI(current, element) {
2396 var percentage = (current / translationState.totalElements) * 100;
2397
2398 $('#king-addons-progress-count').text(current + ' / ' + translationState.totalElements);
2399 $('#king-addons-progress-fill').css('width', percentage + '%');
2400 $('#king-addons-current-element').text('Translating: ' + element.widgetType + ' (' + element.textFields.length + ' fields)');
2401 }
2402
2403 /**
2404 * Inject animation styles into preview iframe
2405 */
2406 function injectPreviewStyles() {
2407 if (!elementor || !elementor.$preview) return;
2408
2409 var $previewDoc = elementor.$preview.contents();
2410 var $previewHead = $previewDoc.find('head');
2411
2412 if ($previewHead.length && !$previewDoc.find('#king-addons-preview-translator-styles').length) {
2413 var previewStyles = `
2414 <style id="king-addons-preview-translator-styles">
2415 /* Element highlighting animation for translation */
2416 .king-addons-translating-element {
2417 position: relative !important;
2418 border: 3px solid #2196F3 !important;
2419 box-shadow: 0 0 20px rgba(33, 150, 243, 0.4) !important;
2420 border-radius: 4px !important;
2421 animation: king-addons-translate-pulse 1.5s infinite ease-in-out !important;
2422 z-index: 999 !important;
2423 }
2424
2425 .king-addons-translating-element::before {
2426 content: "🔄 Translating..." !important;
2427 position: absolute !important;
2428 top: -35px !important;
2429 left: 50% !important;
2430 transform: translateX(-50%) !important;
2431 background: #2196F3 !important;
2432 color: white !important;
2433 padding: 6px 12px !important;
2434 border-radius: 20px !important;
2435 font-size: 12px !important;
2436 font-weight: 600 !important;
2437 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
2438 z-index: 10000 !important;
2439 animation: king-addons-translate-bounce 0.8s ease-out !important;
2440 box-shadow: 0 3px 10px rgba(33, 150, 243, 0.3) !important;
2441 white-space: nowrap !important;
2442 }
2443
2444 .king-addons-translated-element {
2445 position: relative !important;
2446 border: 3px solid #4CAF50 !important;
2447 box-shadow: 0 0 20px rgba(76, 175, 80, 0.4) !important;
2448 border-radius: 4px !important;
2449 animation: king-addons-translate-success 1.2s ease-out !important;
2450 z-index: 999 !important;
2451 }
2452
2453 .king-addons-translated-element::before {
2454 content: "�
2455 Translated!" !important;
2456 position: absolute !important;
2457 top: -35px !important;
2458 left: 50% !important;
2459 transform: translateX(-50%) !important;
2460 background: #4CAF50 !important;
2461 color: white !important;
2462 padding: 6px 12px !important;
2463 border-radius: 20px !important;
2464 font-size: 12px !important;
2465 font-weight: 600 !important;
2466 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
2467 z-index: 10000 !important;
2468 animation: king-addons-translate-bounce 0.8s ease-out !important;
2469 box-shadow: 0 3px 10px rgba(76, 175, 80, 0.3) !important;
2470 white-space: nowrap !important;
2471 }
2472
2473 /* Pulsing animation for translating elements */
2474 @keyframes king-addons-translate-pulse {
2475 0% {
2476 box-shadow: 0 0 0 0 rgba(33, 150, 243, 0.7),
2477 0 0 20px rgba(33, 150, 243, 0.4);
2478 transform: scale(1);
2479 }
2480 50% {
2481 box-shadow: 0 0 0 8px rgba(33, 150, 243, 0.2),
2482 0 0 30px rgba(33, 150, 243, 0.6);
2483 transform: scale(1.02);
2484 }
2485 100% {
2486 box-shadow: 0 0 0 0 rgba(33, 150, 243, 0),
2487 0 0 20px rgba(33, 150, 243, 0.4);
2488 transform: scale(1);
2489 }
2490 }
2491
2492 /* Success animation for completed elements */
2493 @keyframes king-addons-translate-success {
2494 0% {
2495 box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7),
2496 0 0 20px rgba(76, 175, 80, 0.4);
2497 transform: scale(1);
2498 }
2499 20% {
2500 box-shadow: 0 0 0 12px rgba(76, 175, 80, 0.3),
2501 0 0 40px rgba(76, 175, 80, 0.6);
2502 transform: scale(1.05);
2503 }
2504 40% {
2505 transform: scale(0.98);
2506 }
2507 60% {
2508 transform: scale(1.02);
2509 }
2510 80% {
2511 transform: scale(0.99);
2512 }
2513 100% {
2514 box-shadow: 0 0 0 0 rgba(76, 175, 80, 0),
2515 0 0 20px rgba(76, 175, 80, 0.2);
2516 transform: scale(1);
2517 }
2518 }
2519
2520 /* Bounce animation for labels */
2521 @keyframes king-addons-translate-bounce {
2522 0% {
2523 transform: translateX(-50%) translateY(-10px) scale(0.8);
2524 opacity: 0;
2525 }
2526 50% {
2527 transform: translateX(-50%) translateY(-2px) scale(1.1);
2528 opacity: 1;
2529 }
2530 70% {
2531 transform: translateX(-50%) translateY(-1px) scale(0.95);
2532 }
2533 100% {
2534 transform: translateX(-50%) translateY(0) scale(1);
2535 opacity: 1;
2536 }
2537 }
2538 </style>
2539 `;
2540 $previewHead.append(previewStyles);
2541 console.log('Preview animation styles injected into iframe');
2542 }
2543 }
2544
2545 /**
2546 * Highlight element in preview
2547 */
2548 function highlightElementInPreview(elementId, highlight) {
2549 // Ensure preview styles are injected
2550 injectPreviewStyles();
2551
2552 // Find element in preview iframe
2553 if (!elementor || !elementor.$preview) {
2554 console.log('Preview not available');
2555 return;
2556 }
2557
2558 var $previewDoc = elementor.$preview.contents();
2559 var $previewElement = $previewDoc.find('[data-id="' + elementId + '"]');
2560
2561 if ($previewElement.length === 0) {
2562 console.log('Element not found in preview:', elementId);
2563 return;
2564 }
2565
2566 if (highlight) {
2567 // Remove any existing classes first
2568 $previewElement.removeClass('king-addons-translated-element');
2569 $previewElement.addClass('king-addons-translating-element');
2570 console.log('Added translating highlight to element:', elementId);
2571 } else {
2572 $previewElement.removeClass('king-addons-translating-element');
2573 console.log('Removed translating highlight from element:', elementId);
2574 }
2575 }
2576
2577 /**
2578 * Show element success animation
2579 */
2580 function showElementSuccess(elementId) {
2581 // Ensure preview styles are injected
2582 injectPreviewStyles();
2583
2584 // Find element in preview iframe
2585 if (!elementor || !elementor.$preview) {
2586 console.log('Preview not available for success animation');
2587 return;
2588 }
2589
2590 var $previewDoc = elementor.$preview.contents();
2591 var $previewElement = $previewDoc.find('[data-id="' + elementId + '"]');
2592
2593 if ($previewElement.length === 0) {
2594 console.log('Element not found in preview for success animation:', elementId);
2595 return;
2596 }
2597
2598 // Remove translating class and add translated class
2599 $previewElement.removeClass('king-addons-translating-element');
2600 $previewElement.addClass('king-addons-translated-element');
2601 console.log('Added success animation to element:', elementId);
2602
2603 // Remove the success animation after it completes
2604 setTimeout(function() {
2605 $previewElement.removeClass('king-addons-translated-element');
2606 console.log('Removed success animation from element:', elementId);
2607 }, 1200);
2608 }
2609
2610 /**
2611 * Show translation complete with stats (stays open until manually closed)
2612 */
2613 function showTranslationComplete($popup) {
2614 console.log('🎉 showTranslationComplete called');
2615 console.log('🔍 Popup exists:', $popup && $popup.length > 0);
2616 console.log('🔍 Popup classes:', $popup && $popup.length > 0 ? $popup.attr('class') : 'N/A');
2617 console.log('📊 Final stats:', {
2618 total: translationState.totalElements,
2619 translated: translationState.translatedElements,
2620 failed: translationState.failedElements
2621 });
2622
2623 if (!$popup || $popup.length === 0) {
2624 console.error('❌ Cannot show translation results: popup not found');
2625 return;
2626 }
2627
2628 var statsHtml = `
2629 <div class="king-addons-translator-progress">
2630 <div class="king-addons-translator-progress-text">
2631 �
2632 Translation Complete!
2633 </div>
2634 <div class="king-addons-translator-stats">
2635 <div class="king-addons-translator-stat">
2636 <div class="king-addons-translator-stat-number" style="font-size: 32px; font-weight: bold; color: #2196F3;">${translationState.totalElements}</div>
2637 <div class="king-addons-translator-stat-label">Total Elements</div>
2638 </div>
2639 <div class="king-addons-translator-stat">
2640 <div class="king-addons-translator-stat-number" style="font-size: 32px; font-weight: bold; color: #4CAF50; animation: pulse 2s infinite;">${translationState.translatedElements}</div>
2641 <div class="king-addons-translator-stat-label">�
2642 Translated</div>
2643 </div>
2644 <div class="king-addons-translator-stat">
2645 <div class="king-addons-translator-stat-number" style="font-size: 32px; font-weight: bold; color: ${translationState.failedElements > 0 ? '#F44336' : '#999'};">${translationState.failedElements}</div>
2646 <div class="king-addons-translator-stat-label">${translationState.failedElements > 0 ? '❌' : '⚪'} Failed</div>
2647 </div>
2648 </div>
2649 <div class="king-addons-translator-actions" style="margin-top: 20px; text-align: center;">
2650 <button class="king-addons-translator-btn-primary" id="king-addons-close-stats" style="background: #4CAF50; color: white; border: none; padding: 12px 30px; font-size: 16px; font-weight: bold; border-radius: 6px; cursor: pointer; box-shadow: 0 2px 4px rgba(0,0,0,0.2); min-width: 120px;">
2651 Close Results
2652 </button>
2653 </div>
2654 </div>
2655 `;
2656
2657 console.log('📝 Setting stats HTML into popup form');
2658 var $form = $popup.find('.king-addons-translator-form');
2659 console.log('🔍 Form element found:', $form.length > 0);
2660
2661 $form.html(statsHtml);
2662
2663 console.log('�
2664 Stats HTML set, popup should now show results');
2665
2666 // Play success sound (Web Audio API)
2667 try {
2668 var audioContext = new (window.AudioContext || window.webkitAudioContext)();
2669 var oscillator = audioContext.createOscillator();
2670 var gainNode = audioContext.createGain();
2671
2672 oscillator.connect(gainNode);
2673 gainNode.connect(audioContext.destination);
2674
2675 oscillator.frequency.setValueAtTime(800, audioContext.currentTime);
2676 oscillator.frequency.setValueAtTime(1000, audioContext.currentTime + 0.1);
2677 oscillator.frequency.setValueAtTime(1200, audioContext.currentTime + 0.2);
2678
2679 gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
2680 gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
2681
2682 oscillator.start(audioContext.currentTime);
2683 oscillator.stop(audioContext.currentTime + 0.3);
2684 } catch (e) {
2685 console.log('Audio notification not available');
2686 }
2687
2688 // Show temporary notification to attract attention
2689 var $notificationBanner = $('<div style="position: fixed; top: 0; left: 0; right: 0; background: linear-gradient(135deg, #4CAF50, #2E7D32); color: white; padding: 12px; text-align: center; font-size: 16px; font-weight: bold; z-index: 1000000; box-shadow: 0 2px 10px rgba(0,0,0,0.3); animation: slideDown 0.5s ease;">🎉 Translation Complete! Check the results popup below.</div>');
2690 $('body').append($notificationBanner);
2691
2692 // Remove notification after 5 seconds
2693 setTimeout(function() {
2694 $notificationBanner.fadeOut(300, function() {
2695 $notificationBanner.remove();
2696 });
2697 }, 5000);
2698
2699 // Log popup state for debugging
2700 console.log('🔍 Final popup state check:');
2701 console.log('- Popup visible:', $popup.is(':visible'));
2702 console.log('- Popup display:', $popup.css('display'));
2703 console.log('- Popup opacity:', $popup.css('opacity'));
2704 console.log('- Popup z-index:', $popup.css('z-index'));
2705 console.log('- Popup position:', $popup.css('position'));
2706 console.log('- Overlay exists:', $popup.closest('.king-addons-translator-overlay').length > 0);
2707 console.log('- Browser dimensions:', window.innerWidth + 'x' + window.innerHeight);
2708
2709 // Update header to show completion
2710 var completionHeaderHtml = `
2711 <img src="${KingAddonsAiField.plugin_url}includes/admin/img/ai.svg" style="width:20px;height:20px;filter: invert(1);"/>
2712 AI Translation Complete
2713 `;
2714 $popup.find('h3').html(completionHeaderHtml);
2715
2716 // Show the subtitle/description text again
2717 $popup.find('h3').next('div').show();
2718
2719 // If popup is in compact mode, move it back to center for better visibility
2720 if ($popup.hasClass('compact')) {
2721 console.log('📐 Moving popup from compact mode to center for results display');
2722
2723 // Remove compact class and positioning
2724 $popup.removeClass('compact moving');
2725 $popup.css({
2726 'position': 'fixed',
2727 'top': '50%',
2728 'left': '50%',
2729 'transform': 'translate(-50%, -50%)',
2730 'right': 'auto',
2731 'bottom': 'auto',
2732 'width': '500px',
2733 'max-width': '90vw',
2734 'z-index': '999999',
2735 'background': 'white',
2736 'border-radius': '8px',
2737 'box-shadow': '0 10px 25px rgba(0,0,0,0.2)',
2738 'opacity': '1',
2739 'visibility': 'visible'
2740 });
2741
2742 // Re-add overlay if it doesn't exist
2743 if (!$popup.closest('.king-addons-translator-overlay').length) {
2744 var $overlay = $('<div class="king-addons-translator-overlay"></div>').css({
2745 'position': 'fixed',
2746 'top': '0',
2747 'left': '0',
2748 'width': '100%',
2749 'height': '100%',
2750 'background': 'rgba(0, 0, 0, 0.5)',
2751 'z-index': '999998',
2752 'display': 'flex',
2753 'align-items': 'center',
2754 'justify-content': 'center'
2755 });
2756 $popup.wrap($overlay);
2757 } else {
2758 // Make sure existing overlay is visible
2759 $popup.closest('.king-addons-translator-overlay').css({
2760 'z-index': '999998',
2761 'display': 'flex'
2762 });
2763 }
2764
2765 // Add entrance animation
2766 $popup.css('opacity', '0').animate({'opacity': '1'}, 300);
2767 } else {
2768 // For non-compact popups, ensure they're also properly visible
2769 console.log('📐 Ensuring non-compact popup is visible');
2770 $popup.css({
2771 'z-index': '999999',
2772 'opacity': '1',
2773 'visibility': 'visible',
2774 'position': 'fixed'
2775 });
2776
2777 // Make sure overlay is visible
2778 var $overlay = $popup.closest('.king-addons-translator-overlay');
2779 if ($overlay.length) {
2780 $overlay.css({
2781 'z-index': '999998',
2782 'display': 'block',
2783 'opacity': '1',
2784 'visibility': 'visible'
2785 });
2786 }
2787
2788 // Add entrance animation
2789 $popup.css('opacity', '0').animate({'opacity': '1'}, 300);
2790 }
2791
2792 $('#king-addons-close-stats').on('click', function() {
2793 // For compact popup, just remove it directly since overlay is already gone
2794 if ($popup.hasClass('compact')) {
2795 $popup.remove();
2796 } else {
2797 $popup.closest('.king-addons-translator-overlay').remove();
2798 }
2799 });
2800
2801 // Reset translation state and re-enable button
2802 translationState.isTranslating = false;
2803 translationState.isCancelled = false;
2804 translationState.currentRequests = []; // Clear any remaining requests
2805 toggleTranslatorButton(false);
2806
2807 // Note: Auto-close removed by user request - popup stays open until manually closed
2808 }
2809
2810 /**
2811 * Handle Elementor initialization
2812 */
2813 function onElementorInit() {
2814 // Check if AI Page Translator is enabled
2815 if (typeof KingAddonsAiField !== 'undefined' && KingAddonsAiField.translator_enabled === false) {
2816 console.log('AI Page Translator is disabled in settings');
2817 return;
2818 }
2819
2820 // Inject styles first
2821 injectTranslatorStyles();
2822
2823 // Try to inject preview styles (will work when preview is available)
2824 setTimeout(function() {
2825 injectPreviewStyles();
2826 }, 1000);
2827
2828 // Add button immediately
2829 addTranslatorButton();
2830
2831 // Also add button when panel opens
2832 if (typeof elementor !== 'undefined' && elementor.hooks) {
2833 elementor.hooks.addAction('panel/open_editor/widget', function() {
2834 setTimeout(addTranslatorButton, 100);
2835 });
2836
2837 // Add button when navigator opens
2838 elementor.hooks.addAction('navigator/init', function() {
2839 setTimeout(addTranslatorButton, 100);
2840 });
2841
2842 // Inject preview styles when preview loads
2843 elementor.hooks.addAction('preview/loaded', function() {
2844 console.log('Preview loaded, injecting animation styles');
2845 injectPreviewStyles();
2846 });
2847 }
2848
2849 // Monitor for panel changes
2850 observePanelChanges();
2851 }
2852
2853 /**
2854 * Observe panel changes to re-add button if needed
2855 */
2856 function observePanelChanges() {
2857 function createObserver() {
2858 return new MutationObserver(function(mutations) {
2859 var shouldCheck = false;
2860
2861 mutations.forEach(function(mutation) {
2862 if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
2863 shouldCheck = true;
2864 }
2865 });
2866
2867 if (shouldCheck) {
2868 setTimeout(addTranslatorButton, 300);
2869 }
2870 });
2871 }
2872
2873 // Observe changes in the top toolbar (priority)
2874 var topToolbar = document.querySelector('#elementor-editor-wrapper-v2 .MuiToolbar-root');
2875 if (topToolbar) {
2876 var topObserver = createObserver();
2877 topObserver.observe(topToolbar, {
2878 childList: true,
2879 subtree: true
2880 });
2881 console.log('Observing changes in top toolbar');
2882 }
2883
2884 // Also observe the main editor wrapper for structural changes
2885 var editorWrapper = document.querySelector('#elementor-editor-wrapper-v2');
2886 if (editorWrapper) {
2887 var wrapperObserver = createObserver();
2888 wrapperObserver.observe(editorWrapper, {
2889 childList: true,
2890 subtree: false
2891 });
2892 console.log('Observing changes in editor wrapper');
2893 }
2894
2895 // Observe changes in the main panel (fallback)
2896 var panel = document.querySelector('#elementor-panel');
2897 if (panel) {
2898 var panelObserver = createObserver();
2899 panelObserver.observe(panel, {
2900 childList: true,
2901 subtree: true
2902 });
2903 console.log('Observing changes in elementor panel');
2904 }
2905
2906 // Observe the main Elementor editor area
2907 var editorArea = document.querySelector('#elementor-editor-wrapper, .elementor-editor-wrapper');
2908 if (editorArea) {
2909 var editorObserver = createObserver();
2910 editorObserver.observe(editorArea, {
2911 childList: true,
2912 subtree: true
2913 });
2914 console.log('Observing changes in editor area');
2915 }
2916 }
2917
2918 /**
2919 * Initialize the translator
2920 */
2921 function initTranslator() {
2922 // Wait for Elementor to be fully loaded
2923 $(window).on('elementor:init', function() {
2924 console.log('Elementor initialized, setting up AI Translator');
2925 // Add small delay to ensure Material UI is rendered
2926 setTimeout(onElementorInit, 500);
2927 });
2928
2929 // Fallback if elementor:init doesn't fire
2930 setTimeout(function() {
2931 console.log('Fallback initialization for AI Translator');
2932 onElementorInit();
2933 }, 3000);
2934
2935 // Additional fallback for when Material UI components are ready
2936 setTimeout(function() {
2937 if (!document.querySelector('.king-addons-ai-translator-btn')) {
2938 console.log('Material UI fallback initialization for AI Translator');
2939 onElementorInit();
2940 }
2941 }, 5000);
2942 }
2943
2944 // Initialize when DOM is ready
2945 $(document).ready(function() {
2946 console.log('DOM ready, initializing AI Translator');
2947 initTranslator();
2948
2949 // Global event handler for close buttons (backup protection)
2950 $(document).off('click.aiTranslatorGlobal').on('click.aiTranslatorGlobal', '.king-addons-translator-close-btn', function(e) {
2951 console.log('🚫 Global close button clicked');
2952 if (translationState.isTranslating) {
2953 console.log('🛑 Stopping translation via global handler');
2954 stopTranslationProcess();
2955
2956 // Show cancellation notice
2957 var $notice = $('<div style="position: fixed; top: 120px; right: 20px; background: #ff9800; color: white; padding: 8px 12px; border-radius: 4px; font-size: 14px; z-index: 1000000;">Translation cancelled</div>');
2958 $('body').append($notice);
2959 setTimeout(function() {
2960 $notice.fadeOut(300, function() {
2961 $notice.remove();
2962 });
2963 }, 2000);
2964 }
2965
2966 // Close popup/overlay
2967 var $popup = $(this).closest('.king-addons-translator-popup');
2968 if ($popup.hasClass('compact')) {
2969 $popup.remove();
2970 } else {
2971 $popup.closest('.king-addons-translator-overlay').remove();
2972 }
2973
2974 // Reset state and re-enable button
2975 translationState.isTranslating = false;
2976 translationState.isCancelled = false;
2977 translationState.currentRequests = [];
2978 toggleTranslatorButton(false);
2979
2980 e.preventDefault();
2981 e.stopPropagation();
2982 });
2983 });
2984
2985 })(jQuery, window.elementor);