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/dist/wineater-chatbot.mjs +651 -619
- package/dist/wineater-chatbot.umd.js +62 -39
- package/package.json +1 -1
- package/src/js/i18n.js +69 -33
- package/src/js/widget.js +75 -64
package/package.json
CHANGED
package/src/js/i18n.js
CHANGED
|
@@ -470,8 +470,8 @@ class Translator {
|
|
|
470
470
|
headerOccasion: "случай",
|
|
471
471
|
headerOr: "или",
|
|
472
472
|
},
|
|
473
|
-
|
|
474
|
-
lang: '
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
563
|
-
|
|
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
|
-
|
|
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
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
623
|
+
const entries = Object.entries(textObject).map(([key, text]) => ({
|
|
624
|
+
key,
|
|
625
|
+
text,
|
|
626
|
+
}));
|
|
618
627
|
|
|
619
|
-
|
|
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
|
|
677
|
+
const translatedArray = JSON.parse(translatedRaw);
|
|
644
678
|
|
|
645
679
|
const translatedObject = {};
|
|
646
|
-
|
|
647
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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);
|