wineater-bot 3.8.0 → 3.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wineater-bot",
3
- "version": "3.8.0",
3
+ "version": "3.8.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.js",
package/src/js/i18n.js CHANGED
@@ -470,8 +470,8 @@ class Translator {
470
470
  headerOccasion: "случай",
471
471
  headerOr: "или",
472
472
  },
473
- kz: {
474
- lang: 'kz',
473
+ kk: {
474
+ lang: 'kk',
475
475
  welcomeMessage: "Сіз не жегіңіз келеді? Тағамыңызды сипаттаңыз, мен оған керемет шарап ұсынамын.",
476
476
  noResultsFound: "Нәтижелер табылмады.",
477
477
  somethingWentWrong: "Кешіріңіз, бірдеңе дұрыс болмады.",
@@ -540,29 +540,31 @@ class Translator {
540
540
 
541
541
  async fetchConfig(storeName) {
542
542
  try {
543
- const config = await this.api.getStoreConfig(storeName);
544
- this.storeLanguage = config.lang;
545
- this.supportedLanguages = config.available_languages;
546
- this.OpenAIApiKey = config.open_ai_key;
547
- // ✅ Если язык уже сохранён в localStorage — использовать его
548
- const storedLang = localStorage.getItem('selectedLanguage');
549
- if (storedLang && this.translations[storedLang]) {
550
- this.language = storedLang;
551
- } else if (config.lang && this.translations[config.lang]) {
552
- // иначе использовать с бэкенда
553
- this.language = config.lang;
554
- localStorage.setItem('selectedLanguage', config.lang);
555
- } else {
556
- this.language = 'en';
557
- }
558
-
559
- this.currency = config.currency || 'USD';
560
-
543
+ const config = await this.api.getStoreConfig(storeName);
544
+ this.storeLanguage = config.lang;
545
+ this.supportedLanguages = config.available_languages;
546
+ this.OpenAIApiKey = config.open_ai_key;
547
+
548
+ const urlParams = new URLSearchParams(window.location.search);
549
+ const urlLang = urlParams.get('lang') || urlParams.get('language');
550
+
551
+ if (urlLang && this.translations[urlLang]) {
552
+ this.language = urlLang;
553
+ } else if (config.lang && this.translations[config.lang]) {
554
+ this.language = config.lang;
555
+ } else {
556
+ this.language = 'en';
557
+ }
558
+
559
+ this.currency = config.currency || 'USD';
561
560
  } catch (e) {
562
- this.language = localStorage.getItem('selectedLanguage') || 'en';
563
- this.currency = 'USD';
561
+ const urlParams = new URLSearchParams(window.location.search);
562
+ const urlLang = urlParams.get('lang') || urlParams.get('language');
563
+
564
+ this.language = this.translations[urlLang] ? urlLang : 'en';
565
+ this.currency = 'USD';
564
566
  }
565
- }
567
+ }
566
568
 
567
569
  getCurrency(key) {
568
570
  const keys = key.split('.'); // Разбиваем ключ на части
@@ -598,12 +600,19 @@ class Translator {
598
600
  return this.supportedLanguages;
599
601
  }
600
602
 
601
- async translateTexts(textObject) {
603
+ getLangFromURL() {
604
+ const urlParams = new URLSearchParams(window.location.search);
605
+ const lang = urlParams.get('lang') || urlParams.get('language');
606
+ return lang && this.translations[lang] ? lang : null;
607
+ }
608
+
609
+ async translateTexts(textObject) {
602
610
  if (this.storeLanguage === this.language || !this.supportedLanguages) {
611
+ console.log('gривет');
603
612
  return textObject;
604
613
  }
605
614
 
606
- const apiKey = this.OpenAIApiKey; // ✅ Заменить на твой реальный ключ
615
+ const apiKey = this.OpenAIApiKey;
607
616
  const url = 'https://api.openai.com/v1/chat/completions';
608
617
 
609
618
  const headers = {
@@ -611,12 +620,37 @@ class Translator {
611
620
  'Authorization': `Bearer ${apiKey}`,
612
621
  };
613
622
 
614
- const texts = Object.values(textObject);
615
- const joinedText = texts.join(' ||| ');
616
-
617
- const prompt = `Translate the following texts to language with the next code: ${this.language}. Return only the translated texts separated by " ||| " in the same order:
623
+ const entries = Object.entries(textObject).map(([key, text]) => ({
624
+ key,
625
+ text,
626
+ }));
618
627
 
619
- ${joinedText}`;
628
+ const prompt = `
629
+ You are a professional translator.
630
+
631
+ The input texts are written in language with code "${this.storeLanguage}". Your task is to translate them strictly to the target language with code "${this.language}".
632
+
633
+ ⚠️ DO NOT leave the original text.
634
+ ⚠️ DO NOT skip any entry.
635
+ ⚠️ Preserve the original order and keys exactly.
636
+
637
+ Translate only the "text" fields of the JSON array. Return an array of the same length and structure, like this:
638
+
639
+ Input:
640
+ [
641
+ { "key": "shortDescription", "text": "Red wine with rich cherry notes." },
642
+ { "key": "tags", "text": "fruity, dry, elegant" }
643
+ ]
644
+
645
+ Output:
646
+ [
647
+ { "key": "shortDescription", "text": "..." },
648
+ { "key": "tags", "text": "..." }
649
+ ]
650
+
651
+ Now translate the following input:
652
+ ${JSON.stringify(entries, null, 2)}
653
+ `;
620
654
 
621
655
  const body = JSON.stringify({
622
656
  model: 'gpt-3.5-turbo',
@@ -640,11 +674,13 @@ class Translator {
640
674
 
641
675
  if (!translatedRaw) throw new Error('No response from OpenAI');
642
676
 
643
- const translatedTexts = translatedRaw.split(' ||| ').map(t => t.trim());
677
+ const translatedArray = JSON.parse(translatedRaw);
644
678
 
645
679
  const translatedObject = {};
646
- Object.keys(textObject).forEach((key, index) => {
647
- translatedObject[key] = translatedTexts[index] || textObject[key];
680
+ translatedArray.forEach(entry => {
681
+ if (entry.key && entry.text !== undefined) {
682
+ translatedObject[entry.key] = entry.text;
683
+ }
648
684
  });
649
685
 
650
686
  return translatedObject;
package/src/js/widget.js CHANGED
@@ -311,31 +311,43 @@ export class WineWidget {
311
311
  document.querySelectorAll('.mobile-fullscreen-wrapper').forEach(wrapper => wrapper.remove());
312
312
  }
313
313
  });
314
-
315
- buyBtn?.addEventListener('click', (e) => {
316
- e.stopPropagation();
317
-
318
- // Закрыть все открытые popup'ы
319
- document.querySelectorAll('.card-popup').forEach(p => p.classList.add('hidden'));
320
- document.querySelectorAll('.mobile-fullscreen-wrapper').forEach(p => p.remove());
321
-
322
- if (window.innerWidth <= 768) {
323
- const popupContent = popup.innerHTML;
324
-
325
- const overlay = document.createElement('div');
326
- overlay.className = 'mobile-fullscreen-wrapper';
327
- overlay.innerHTML = `
328
- <div class="mobile-fullscreen-overlay"></div>
329
- <div class="card-popup mobile-fullscreen-popup">${popupContent}</div>
330
- `;
331
-
332
- overlay.querySelector('.card-popup-close')?.addEventListener('click', () => overlay.remove());
333
-
334
- document.body.appendChild(overlay);
335
- } else {
314
+
315
+ if (this.widgetType === 'store') {
316
+ // For store type, show popup on hover
317
+ currentCard.addEventListener('mouseenter', () => {
336
318
  popup.classList.remove('hidden');
337
- }
338
- });
319
+ });
320
+
321
+ currentCard.addEventListener('mouseleave', () => {
322
+ popup.classList.add('hidden');
323
+ });
324
+ } else {
325
+ // For bar type, show popup on click
326
+ buyBtn?.addEventListener('click', (e) => {
327
+ e.stopPropagation();
328
+
329
+ // Close all open popups
330
+ document.querySelectorAll('.card-popup').forEach(p => p.classList.add('hidden'));
331
+ document.querySelectorAll('.mobile-fullscreen-wrapper').forEach(p => p.remove());
332
+
333
+ if (window.innerWidth <= 768) {
334
+ const popupContent = popup.innerHTML;
335
+
336
+ const overlay = document.createElement('div');
337
+ overlay.className = 'mobile-fullscreen-wrapper';
338
+ overlay.innerHTML = `
339
+ <div class="mobile-fullscreen-overlay"></div>
340
+ <div class="card-popup mobile-fullscreen-popup">${popupContent}</div>
341
+ `;
342
+
343
+ overlay.querySelector('.card-popup-close')?.addEventListener('click', () => overlay.remove());
344
+
345
+ document.body.appendChild(overlay);
346
+ } else {
347
+ popup.classList.remove('hidden');
348
+ }
349
+ });
350
+ }
339
351
  }
340
352
 
341
353
  setTimeout(() => {
@@ -399,59 +411,64 @@ export class WineWidget {
399
411
  if (translator.language === translator.storeLanguage || this.widgetType !== 'bar') {
400
412
  return;
401
413
  }
414
+
402
415
  const possibleFields = ['Short Description', 'reason', 'pairing_recommendation', 'Tags', 'tags'];
403
- const firstWine = wines[0];
404
-
405
- // Фильтруем по реально существующим ключам в первом объекте
406
- const fieldsToTranslate = possibleFields.filter(field => field in firstWine);
407
-
408
- const language = translator.language;
409
- const batchSize = 4;
410
-
411
- for (let i = 0; i < wines.length; i += batchSize) {
412
- const batch = wines.slice(i, i + batchSize);
413
-
414
- const translationPromises = batch.map(async (wine, index) => {
415
- const globalIndex = i + index;
416
- const textObject = {};
417
-
418
- fieldsToTranslate.forEach(field => {
419
- const val = wine[field] || wine[field.toLowerCase()];
420
- if (val) textObject[field] = Array.isArray(val) ? val.join(', ') : val;
421
- });
422
-
423
- if (Object.keys(textObject).length === 0) return;
424
-
425
- const translated = await translator.translateTexts(textObject);
426
-
427
- const popup = document.querySelector(`.card-popup[data-wine-index="${globalIndex}"]`);
428
- if (!popup) return;
429
-
430
- if (translated['Short Description']) {
431
- const el = popup.querySelector('.popup-short-desc');
432
- if (el) el.textContent = translated['Short Description'];
433
- }
434
-
435
- if (translated.reason) {
436
- const el = popup.querySelector('.popup-reason');
437
- if (el) el.textContent = translated.reason;
438
- }
439
-
440
- if (translated.pairing_recommendation) {
441
- const el = popup.querySelector('.popup-pairing');
442
- if (el) el.textContent = translated.pairing_recommendation;
443
- }
444
-
445
- if (translated.tags || translated.Tags) {
446
- const tagsString = translated.tags || translated.Tags;
447
- const tagsArray = tagsString.split(',').map(tag => tag.trim());
448
- const tagHTML = tagsArray.map(tag => `<span class="popup-tag">${tag}</span>`).join('');
449
- const tagsContainer = popup.querySelector('.popup-tags');
450
- if (tagsContainer) tagsContainer.innerHTML = tagHTML;
451
- }
452
- });
453
-
454
- await Promise.all(translationPromises);
416
+ const firstWine = wines[0];
417
+
418
+ const fieldsToTranslate = possibleFields.filter(field => field in firstWine);
419
+ const batchSize = 4;
420
+
421
+ for (let i = 0; i < wines.length; i += batchSize) {
422
+ const batch = wines.slice(i, i + batchSize);
423
+
424
+ const translationPromises = batch.map(async (wine, index) => {
425
+ const globalIndex = i + index;
426
+
427
+ // Сбор оригинальных текстов в формате [{ key, text }]
428
+ const entries = fieldsToTranslate
429
+ .map(field => {
430
+ const val = wine[field] || wine[field.toLowerCase()];
431
+ if (!val) return null;
432
+ return { key: field, text: Array.isArray(val) ? val.join(', ') : val };
433
+ })
434
+ .filter(Boolean);
435
+
436
+ if (entries.length === 0) return;
437
+
438
+ // Перевод
439
+ const translatedArray = await translator.translateTexts(
440
+ Object.fromEntries(entries.map(entry => [entry.key, entry.text]))
441
+ );
442
+
443
+ const popup = document.querySelector(`.card-popup[data-wine-index="${globalIndex}"]`);
444
+ if (!popup) return;
445
+
446
+ // Применение перевода
447
+ if (translatedArray['Short Description']) {
448
+ const el = popup.querySelector('.popup-short-desc');
449
+ if (el) el.textContent = translatedArray['Short Description'];
450
+ }
451
+
452
+ if (translatedArray.reason) {
453
+ const el = popup.querySelector('.popup-reason');
454
+ if (el) el.textContent = translatedArray.reason;
455
+ }
456
+
457
+ if (translatedArray.pairing_recommendation) {
458
+ const el = popup.querySelector('.popup-pairing');
459
+ if (el) el.textContent = translatedArray.pairing_recommendation;
460
+ }
461
+
462
+ const tags = translatedArray.tags || translatedArray.Tags;
463
+ if (tags) {
464
+ const tagArray = tags.split(',').map(tag => tag.trim());
465
+ const tagHTML = tagArray.map(tag => `<span class="popup-tag">${tag}</span>`).join('');
466
+ const tagsContainer = popup.querySelector('.popup-tags');
467
+ if (tagsContainer) tagsContainer.innerHTML = tagHTML;
468
+ }
469
+ });
470
+
471
+ await Promise.all(translationPromises);
455
472
  }
456
473
  }
457
474
 
@@ -515,9 +532,8 @@ export class WineWidget {
515
532
  createLanguageSwitcher() {
516
533
  const langContainer = document.querySelector('#powered-by-widget .powered-by-lang');
517
534
  const languages = translator.getSupportedLanguages();
518
- if (!languages) {
519
- return;
520
- }
535
+ if (!languages) return;
536
+
521
537
  const currentLanguage = translator.language;
522
538
 
523
539
  const dropdown = document.createElement('select');
@@ -525,18 +541,25 @@ createLanguageSwitcher() {
525
541
  dropdown.className = 'language-dropdown';
526
542
 
527
543
  languages.forEach(lang => {
528
- const option = document.createElement('option');
529
- option.value = lang;
530
- option.textContent = lang.toUpperCase();
531
- option.selected = lang === currentLanguage;
532
- dropdown.appendChild(option);
544
+ const option = document.createElement('option');
545
+ option.value = lang;
546
+ option.textContent = lang.toUpperCase();
547
+ option.selected = lang === currentLanguage;
548
+ dropdown.appendChild(option);
533
549
  });
534
550
 
535
551
  dropdown.onchange = (event) => {
536
- const selectedLang = event.target.value;
537
- translator.language = selectedLang;
538
- localStorage.setItem('selectedLanguage', selectedLang);
539
- location.reload(); // Reload to apply language change
552
+ const selectedLang = event.target.value;
553
+
554
+ // Обновляем URL без перезагрузки страницы
555
+ const url = new URL(window.location.href);
556
+ url.searchParams.set('lang', selectedLang);
557
+
558
+ // Обновляем localStorage тоже, на всякий случай
559
+ localStorage.setItem('selectedLanguage', selectedLang);
560
+
561
+ // Перенаправляем пользователя с обновлённым URL
562
+ window.location.href = url.toString();
540
563
  };
541
564
 
542
565
  langContainer.appendChild(dropdown);