wineater-bot 3.8.0 → 3.8.1

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.1",
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
@@ -399,59 +399,64 @@ export class WineWidget {
399
399
  if (translator.language === translator.storeLanguage || this.widgetType !== 'bar') {
400
400
  return;
401
401
  }
402
+
402
403
  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);
404
+ const firstWine = wines[0];
405
+
406
+ const fieldsToTranslate = possibleFields.filter(field => field in firstWine);
407
+ const batchSize = 4;
408
+
409
+ for (let i = 0; i < wines.length; i += batchSize) {
410
+ const batch = wines.slice(i, i + batchSize);
411
+
412
+ const translationPromises = batch.map(async (wine, index) => {
413
+ const globalIndex = i + index;
414
+
415
+ // Сбор оригинальных текстов в формате [{ key, text }]
416
+ const entries = fieldsToTranslate
417
+ .map(field => {
418
+ const val = wine[field] || wine[field.toLowerCase()];
419
+ if (!val) return null;
420
+ return { key: field, text: Array.isArray(val) ? val.join(', ') : val };
421
+ })
422
+ .filter(Boolean);
423
+
424
+ if (entries.length === 0) return;
425
+
426
+ // Перевод
427
+ const translatedArray = await translator.translateTexts(
428
+ Object.fromEntries(entries.map(entry => [entry.key, entry.text]))
429
+ );
430
+
431
+ const popup = document.querySelector(`.card-popup[data-wine-index="${globalIndex}"]`);
432
+ if (!popup) return;
433
+
434
+ // Применение перевода
435
+ if (translatedArray['Short Description']) {
436
+ const el = popup.querySelector('.popup-short-desc');
437
+ if (el) el.textContent = translatedArray['Short Description'];
438
+ }
439
+
440
+ if (translatedArray.reason) {
441
+ const el = popup.querySelector('.popup-reason');
442
+ if (el) el.textContent = translatedArray.reason;
443
+ }
444
+
445
+ if (translatedArray.pairing_recommendation) {
446
+ const el = popup.querySelector('.popup-pairing');
447
+ if (el) el.textContent = translatedArray.pairing_recommendation;
448
+ }
449
+
450
+ const tags = translatedArray.tags || translatedArray.Tags;
451
+ if (tags) {
452
+ const tagArray = tags.split(',').map(tag => tag.trim());
453
+ const tagHTML = tagArray.map(tag => `<span class="popup-tag">${tag}</span>`).join('');
454
+ const tagsContainer = popup.querySelector('.popup-tags');
455
+ if (tagsContainer) tagsContainer.innerHTML = tagHTML;
456
+ }
457
+ });
458
+
459
+ await Promise.all(translationPromises);
455
460
  }
456
461
  }
457
462
 
@@ -515,9 +520,8 @@ export class WineWidget {
515
520
  createLanguageSwitcher() {
516
521
  const langContainer = document.querySelector('#powered-by-widget .powered-by-lang');
517
522
  const languages = translator.getSupportedLanguages();
518
- if (!languages) {
519
- return;
520
- }
523
+ if (!languages) return;
524
+
521
525
  const currentLanguage = translator.language;
522
526
 
523
527
  const dropdown = document.createElement('select');
@@ -525,18 +529,25 @@ createLanguageSwitcher() {
525
529
  dropdown.className = 'language-dropdown';
526
530
 
527
531
  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);
532
+ const option = document.createElement('option');
533
+ option.value = lang;
534
+ option.textContent = lang.toUpperCase();
535
+ option.selected = lang === currentLanguage;
536
+ dropdown.appendChild(option);
533
537
  });
534
538
 
535
539
  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
540
+ const selectedLang = event.target.value;
541
+
542
+ // Обновляем URL без перезагрузки страницы
543
+ const url = new URL(window.location.href);
544
+ url.searchParams.set('lang', selectedLang);
545
+
546
+ // Обновляем localStorage тоже, на всякий случай
547
+ localStorage.setItem('selectedLanguage', selectedLang);
548
+
549
+ // Перенаправляем пользователя с обновлённым URL
550
+ window.location.href = url.toString();
540
551
  };
541
552
 
542
553
  langContainer.appendChild(dropdown);