wineater-bot 3.9.3 → 4.0.0-beta.3
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/.prettierignore +9 -0
- package/.prettierrc +16 -0
- package/README.md +86 -0
- package/dist/wineater-chatbot.css +1 -0
- package/dist/wineater-chatbot.mjs +6494 -5123
- package/dist/wineater-chatbot.umd.js +40 -985
- package/docs/SVELTE.md +146 -0
- package/e2e/demo.test.ts +6 -0
- package/eslint.config.js +41 -0
- package/index.html +11 -3
- package/package.json +30 -5
- package/playwright.config.ts +9 -0
- package/pnpm-workspace.yaml +2 -0
- package/src/App.svelte +394 -0
- package/src/App.svelte.test.ts +13 -0
- package/src/components/Footer.svelte +131 -0
- package/src/components/Header.svelte +92 -0
- package/src/components/Loader.svelte +62 -0
- package/src/components/Search.svelte +188 -0
- package/src/components/WinePopup.svelte +192 -0
- package/src/components/WineTile.svelte +331 -0
- package/src/demo.spec.ts +13 -0
- package/src/index.js +4 -36
- package/src/services/analytics/analytics.ts +103 -0
- package/src/{js/widget/widget-events.js → services/analytics/constants.ts} +0 -2
- package/src/services/api/api.ts +112 -0
- package/src/services/api/deserialize.ts +102 -0
- package/src/services/i18n/i18n.ts +134 -0
- package/src/services/i18n/translations.json +521 -0
- package/src/services/store/app-store.svelte.ts +84 -0
- package/tsconfig.json +23 -0
- package/vite.config.js +69 -19
- package/vitest-setup-client.ts +2 -0
- package/src/js/analytics.js +0 -117
- package/src/js/api.js +0 -138
- package/src/js/i18n.js +0 -703
- package/src/js/widget/widget-language.js +0 -107
- package/src/js/widget/widget-search.js +0 -68
- package/src/js/widget/widget-styles.js +0 -855
- package/src/js/widget/widget-ui.js +0 -186
- package/src/js/widget.js +0 -227
package/src/js/i18n.js
DELETED
|
@@ -1,703 +0,0 @@
|
|
|
1
|
-
import { Api } from './api.js';
|
|
2
|
-
|
|
3
|
-
class Translator {
|
|
4
|
-
constructor() {
|
|
5
|
-
this.api = new Api();
|
|
6
|
-
this.language = 'en'; // Default to English
|
|
7
|
-
this.currency = 'USD';
|
|
8
|
-
this.currencies = {
|
|
9
|
-
'USD': {
|
|
10
|
-
label: '$'
|
|
11
|
-
},
|
|
12
|
-
'EUR': {
|
|
13
|
-
label: '€'
|
|
14
|
-
},
|
|
15
|
-
'MXN': {
|
|
16
|
-
label: '$'
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
this.translations = {
|
|
20
|
-
fr: {
|
|
21
|
-
lang: 'fr',
|
|
22
|
-
welcomeMessage: "Quel plat allez-vous déguster ? Décrivez-le-moi et je vous proposerai le vin qui l'accompagnera parfaitement.",
|
|
23
|
-
noResultsFound: "Aucun résultat trouvé.",
|
|
24
|
-
somethingWentWrong: "Désolé, quelque chose a mal tourné.",
|
|
25
|
-
chatButton1: "Trouvez l'accord parfait",
|
|
26
|
-
chatButton2: "Associez le vin",
|
|
27
|
-
chatButton3: "Obtenez des conseils",
|
|
28
|
-
chatTitle: "Accord Mets & Vins 🍷",
|
|
29
|
-
chatSubtitle: "Décrivez votre plat et obtenez une recommandation en 30 secondes",
|
|
30
|
-
inputPlaceholder: "ex., Salade de thon",
|
|
31
|
-
sendButton: "Accorder",
|
|
32
|
-
poweredBy: "Propulsé par",
|
|
33
|
-
price: "Prix",
|
|
34
|
-
region: "Région",
|
|
35
|
-
type: "Type",
|
|
36
|
-
reason: "Suggestion",
|
|
37
|
-
moreInfo: "Plus d'infos",
|
|
38
|
-
winePairingIntro: "Pour ce vin, voici quelques suggestions de plats :",
|
|
39
|
-
askForDinner: "Que voulez-vous manger ce soir?",
|
|
40
|
-
clearChatButton: "Effacer le chat",
|
|
41
|
-
widgetTitle: "TROUVEZ VOTRE VIN PARFAIT",
|
|
42
|
-
widgetSubtitle: "Partagez tout ce que vous avez en tête—votre repas, humeur, budget ou occasion—et notre sommelier IA vous recommandera le vin parfait en quelques secondes",
|
|
43
|
-
searchPlaceholder: "Entrez un mot-clé ou une description",
|
|
44
|
-
wineDetails: {
|
|
45
|
-
reason: "Raison",
|
|
46
|
-
price: "Prix",
|
|
47
|
-
region: "Région",
|
|
48
|
-
type: "Type",
|
|
49
|
-
moreInfo: "Plus d'infos",
|
|
50
|
-
sommSelection: "Sélection Somm",
|
|
51
|
-
code: "Code",
|
|
52
|
-
buy: "Acheter"
|
|
53
|
-
},
|
|
54
|
-
searchButton: "Rechercher",
|
|
55
|
-
priceRange: "Gamme de prix",
|
|
56
|
-
wineColor: "Couleur du vin",
|
|
57
|
-
intensity: "Intensité",
|
|
58
|
-
red: "Rouge",
|
|
59
|
-
white: "Blanc",
|
|
60
|
-
rose: "Rosé",
|
|
61
|
-
light: "Léger",
|
|
62
|
-
medium: "Moyen",
|
|
63
|
-
full: "Puissant",
|
|
64
|
-
|
|
65
|
-
/* -- Новые ключи для bar-режима -- */
|
|
66
|
-
barTitle: "Sélection du bar",
|
|
67
|
-
barSubtitle: "Choisissez vos plats ci-dessous et cliquez sur «Envoyer»",
|
|
68
|
-
barSendButton: "Envoyer",
|
|
69
|
-
barResetButton: "Réinitialiser",
|
|
70
|
-
barName: "Nom du bar",
|
|
71
|
-
barNotesPlaceholder: "Entrez vos souhaits ici...",
|
|
72
|
-
placeholderSuggestions: [
|
|
73
|
-
"Vin pour les pâtes",
|
|
74
|
-
"Parfait avec du poisson",
|
|
75
|
-
"Recommandé pour une soirée"
|
|
76
|
-
],
|
|
77
|
-
headerLine1: "Partagez tout ce qui vous passe par la tête —",
|
|
78
|
-
headerLine2: "— et obtenez des recommandations de vin parfait en quelques secondes",
|
|
79
|
-
headerMeal: "repas",
|
|
80
|
-
headerMood: "humeur",
|
|
81
|
-
headerBudget: "budget",
|
|
82
|
-
headerOccasion: "occasion",
|
|
83
|
-
headerOr: "ou",
|
|
84
|
-
},
|
|
85
|
-
es: {
|
|
86
|
-
lang: 'es',
|
|
87
|
-
welcomeMessage: "¿Qué vas a comer? Cuéntame tu plato y te recomendaré el maridaje perfecto.",
|
|
88
|
-
noResultsFound: "No se encontraron resultados.",
|
|
89
|
-
somethingWentWrong: "Lo sentimos, algo salió mal.",
|
|
90
|
-
chatButton1: "Encuentra tu maridaje",
|
|
91
|
-
chatButton2: "Combina vino ya",
|
|
92
|
-
chatButton3: "Recibe asesoría ahora",
|
|
93
|
-
chatTitle: "Maridaje de Vinos 🍷",
|
|
94
|
-
chatSubtitle: "Describe tu comida y obtén una recomendación en 30 segundos",
|
|
95
|
-
inputPlaceholder: "ej., Ensalada de atún",
|
|
96
|
-
sendButton: "Combinar",
|
|
97
|
-
poweredBy: "Desarrollado por",
|
|
98
|
-
price: "Precio",
|
|
99
|
-
region: "Región",
|
|
100
|
-
type: "Tipo",
|
|
101
|
-
reason: "Sugerencia",
|
|
102
|
-
moreInfo: "Más info",
|
|
103
|
-
winePairingIntro: "Para este vino, aquí tienes algunas sugerencias de platos:",
|
|
104
|
-
askForDinner: "¿Qué te gustaría cenar?",
|
|
105
|
-
clearChatButton: "Limpiar el chat",
|
|
106
|
-
widgetTitle: "ENCUENTRA TU VINO PERFECTO",
|
|
107
|
-
widgetSubtitle: "Comparte cualquier cosa que tengas en mente—tu comida, estado de ánimo, presupuesto o evento—y nuestro sommelier de IA te recomendará el vino perfecto en segundos",
|
|
108
|
-
searchPlaceholder: "Escribe una palabra clave o descripción",
|
|
109
|
-
wineDetails: {
|
|
110
|
-
reason: "Razón",
|
|
111
|
-
price: "Precio",
|
|
112
|
-
region: "Región",
|
|
113
|
-
type: "Tipo",
|
|
114
|
-
code: "Código",
|
|
115
|
-
moreInfo: "Más información",
|
|
116
|
-
sommSelection: "Selección Somm",
|
|
117
|
-
buy: "Comprar"
|
|
118
|
-
},
|
|
119
|
-
searchButton: "Buscar",
|
|
120
|
-
priceRange: "Rango de precios",
|
|
121
|
-
wineColor: "Color del vino",
|
|
122
|
-
intensity: "Intensidad",
|
|
123
|
-
red: "Tinto",
|
|
124
|
-
white: "Blanco",
|
|
125
|
-
rose: "Rosado",
|
|
126
|
-
light: "Ligero",
|
|
127
|
-
medium: "Medio",
|
|
128
|
-
full: "Intenso",
|
|
129
|
-
|
|
130
|
-
/* -- Новые ключи bar -- */
|
|
131
|
-
barTitle: "Platos de Bar",
|
|
132
|
-
barSubtitle: "Selecciona de la lista y presiona «Enviar»",
|
|
133
|
-
barSendButton: "Enviar",
|
|
134
|
-
barResetButton: "Reiniciar",
|
|
135
|
-
barName: "Nombre del bar",
|
|
136
|
-
barNotesPlaceholder: "Agrega tus comentarios aquí...",
|
|
137
|
-
placeholderSuggestions: [
|
|
138
|
-
"Vino para pasta",
|
|
139
|
-
"Perfecto con pescado",
|
|
140
|
-
"Recomendado para una fiesta"
|
|
141
|
-
],
|
|
142
|
-
headerLine1: "Comparte cualquier cosa que tengas en mente —",
|
|
143
|
-
headerLine2: "— y obtén recomendaciones del vino perfecto en cuestión de segundos",
|
|
144
|
-
headerMeal: "comida",
|
|
145
|
-
headerMood: "estado de ánimo",
|
|
146
|
-
headerBudget: "presupuesto",
|
|
147
|
-
headerOccasion: "ocasión",
|
|
148
|
-
headerOr: "o",
|
|
149
|
-
},
|
|
150
|
-
en: {
|
|
151
|
-
lang: 'en',
|
|
152
|
-
welcomeMessage: "What are you planning to eat? Share your dish and I'll recommend the perfect wine pairing for your meal.",
|
|
153
|
-
noResultsFound: "No results found.",
|
|
154
|
-
somethingWentWrong: "Sorry, something went wrong.",
|
|
155
|
-
chatButton1: "Find Your Pairing",
|
|
156
|
-
chatButton2: "Pair Wine Now",
|
|
157
|
-
chatButton3: "Get Wine Advice",
|
|
158
|
-
chatTitle: "Wine Pairing Assistant 🍷",
|
|
159
|
-
chatSubtitle: "Describe your meal and get a recommendation in 30 seconds",
|
|
160
|
-
inputPlaceholder: "e.g., Tuna salad",
|
|
161
|
-
sendButton: "Match",
|
|
162
|
-
poweredBy: "Powered by",
|
|
163
|
-
price: "Price",
|
|
164
|
-
region: "Region",
|
|
165
|
-
type: "Type",
|
|
166
|
-
reason: "Suggestion",
|
|
167
|
-
moreInfo: "More info",
|
|
168
|
-
winePairingIntro: "For this wine, here are some suggested dishes:",
|
|
169
|
-
askForDinner: "What would you like to have for dinner?",
|
|
170
|
-
clearChatButton: "Clear Chat",
|
|
171
|
-
widgetTitle: "FIND YOUR PERFECT WINE",
|
|
172
|
-
widgetSubtitle: "Share anything on your mind—your meal, mood, budget, or occasion—and our AI sommelier will recommend the perfect wine in seconds",
|
|
173
|
-
searchPlaceholder: "Enter a keyword or description",
|
|
174
|
-
wineDetails: {
|
|
175
|
-
price: "Price",
|
|
176
|
-
region: "Region",
|
|
177
|
-
type: "Type",
|
|
178
|
-
moreInfo: "More info",
|
|
179
|
-
reason: "Reason",
|
|
180
|
-
sommSelection: "Somm Selection",
|
|
181
|
-
code: "Code",
|
|
182
|
-
buy: "Buy"
|
|
183
|
-
},
|
|
184
|
-
searchButton: "Search",
|
|
185
|
-
priceRange: "Price Range",
|
|
186
|
-
wineColor: "Wine Color",
|
|
187
|
-
intensity: "Intensity",
|
|
188
|
-
red: "Red",
|
|
189
|
-
white: "White",
|
|
190
|
-
rose: "Rosé",
|
|
191
|
-
light: "Light",
|
|
192
|
-
medium: "Medium",
|
|
193
|
-
full: "Full",
|
|
194
|
-
|
|
195
|
-
/* -- Новые ключи bar -- */
|
|
196
|
-
barTitle: "Bar Dishes",
|
|
197
|
-
barSubtitle: "Select dishes below and click 'Send'",
|
|
198
|
-
barSendButton: "Send",
|
|
199
|
-
barResetButton: "Reset",
|
|
200
|
-
barName: "Bar Name",
|
|
201
|
-
barNotesPlaceholder: "Add your wishes here...",
|
|
202
|
-
placeholderSuggestions: [
|
|
203
|
-
"Wine for pasta",
|
|
204
|
-
"Perfect with fish",
|
|
205
|
-
"Recommended for a party"
|
|
206
|
-
],
|
|
207
|
-
headerLine1: "Share anything on your mind —",
|
|
208
|
-
headerLine2: "— and get recommendations of the perfect wine in seconds",
|
|
209
|
-
headerMeal: "meal",
|
|
210
|
-
headerMood: "mood",
|
|
211
|
-
headerBudget: "budget",
|
|
212
|
-
headerOccasion: "occasion",
|
|
213
|
-
headerOr: "or",
|
|
214
|
-
},
|
|
215
|
-
"sr": {
|
|
216
|
-
"lang": "sr",
|
|
217
|
-
"welcomeMessage": "Šta planirate da jedete? Podelite svoje jelo i preporučiću vam savršeno vino uz obrok.",
|
|
218
|
-
"noResultsFound": "Nema rezultata.",
|
|
219
|
-
"somethingWentWrong": "Izvinite, nešto je pošlo po zlu.",
|
|
220
|
-
"chatButton1": "Pronađi vino",
|
|
221
|
-
"chatButton2": "Upari vino sada",
|
|
222
|
-
"chatButton3": "Zatraži preporuku",
|
|
223
|
-
"chatTitle": "Asistent za uparivanje vina 🍷",
|
|
224
|
-
"chatSubtitle": "Opišite svoj obrok i dobijte preporuku za 30 sekundi",
|
|
225
|
-
"inputPlaceholder": "npr. Tuna salata",
|
|
226
|
-
"sendButton": "Upari",
|
|
227
|
-
"poweredBy": "Pokreće",
|
|
228
|
-
"price": "Cena",
|
|
229
|
-
"region": "Regija",
|
|
230
|
-
"type": "Tip",
|
|
231
|
-
"reason": "Preporuka",
|
|
232
|
-
"moreInfo": "Više informacija",
|
|
233
|
-
"winePairingIntro": "Za ovo vino, preporučuju se sledeća jela:",
|
|
234
|
-
"askForDinner": "Šta biste želeli za večeru?",
|
|
235
|
-
"clearChatButton": "Obriši razgovor",
|
|
236
|
-
"widgetTitle": "PRONAĐITE SAVRŠENO VINO",
|
|
237
|
-
"widgetSubtitle": "Podelite bilo šta — obrok, raspoloženje, budžet ili priliku — i naš AI somelijer će vam za par sekundi preporučiti vino",
|
|
238
|
-
"searchPlaceholder": "Unesite ključnu reč ili opis",
|
|
239
|
-
"wineDetails": {
|
|
240
|
-
"price": "Cena",
|
|
241
|
-
"region": "Regija",
|
|
242
|
-
"type": "Tip",
|
|
243
|
-
"moreInfo": "Više informacija",
|
|
244
|
-
"reason": "Razlog",
|
|
245
|
-
"sommSelection": "Izbor somelijera",
|
|
246
|
-
"code": "Šifra",
|
|
247
|
-
"buy": "Kupi"
|
|
248
|
-
},
|
|
249
|
-
"searchButton": "Pretraga",
|
|
250
|
-
"priceRange": "Cenovni raspon",
|
|
251
|
-
"wineColor": "Boja vina",
|
|
252
|
-
"intensity": "Intenzitet",
|
|
253
|
-
"red": "Crveno",
|
|
254
|
-
"white": "Belo",
|
|
255
|
-
"rose": "Roze",
|
|
256
|
-
"light": "Lako",
|
|
257
|
-
"medium": "Srednje",
|
|
258
|
-
"full": "Puno",
|
|
259
|
-
|
|
260
|
-
/* -- Novi ključevi za bar -- */
|
|
261
|
-
"barTitle": "Jela iz bara",
|
|
262
|
-
"barSubtitle": "Izaberite jela ispod i kliknite 'Pošalji'",
|
|
263
|
-
"barSendButton": "Pošalji",
|
|
264
|
-
"barResetButton": "Resetuj",
|
|
265
|
-
"barName": "Naziv bara",
|
|
266
|
-
"barNotesPlaceholder": "Dodajte svoje želje ovde...",
|
|
267
|
-
"placeholderSuggestions": [
|
|
268
|
-
"Vino uz pastu",
|
|
269
|
-
"Savršeno uz ribu",
|
|
270
|
-
"Preporuka za žurku"
|
|
271
|
-
],
|
|
272
|
-
"headerLine1": "Podelite bilo šta što vam je na umu —",
|
|
273
|
-
"headerLine2": "— i dobijte preporuke za savršeno vino za par sekundi",
|
|
274
|
-
"headerMeal": "obrok",
|
|
275
|
-
"headerMood": "raspoloženje",
|
|
276
|
-
"headerBudget": "budžet",
|
|
277
|
-
"headerOccasion": "prilika",
|
|
278
|
-
"headerOr": "ili"
|
|
279
|
-
},
|
|
280
|
-
de: {
|
|
281
|
-
lang: 'de',
|
|
282
|
-
welcomeMessage: "Was möchten Sie essen? Beschreiben Sie Ihr Gericht und ich empfehle Ihnen den perfekten Wein dazu.",
|
|
283
|
-
noResultsFound: "Keine Ergebnisse gefunden.",
|
|
284
|
-
somethingWentWrong: "Entschuldigung, etwas ist schief gelaufen.",
|
|
285
|
-
chatButton1: "Finde Dein Pairing",
|
|
286
|
-
chatButton2: "Wein jetzt wählen",
|
|
287
|
-
chatButton3: "Erhalte Weinberatung",
|
|
288
|
-
chatTitle: "Wein & Speisen 🍷",
|
|
289
|
-
chatSubtitle: "Beschreiben Sie Ihr Gericht und erhalten Sie in 30 Sekunden eine Empfehlung",
|
|
290
|
-
inputPlaceholder: "z.B., Thunfischsalat",
|
|
291
|
-
sendButton: "Kombinieren",
|
|
292
|
-
poweredBy: "Unterstützt von",
|
|
293
|
-
price: "Preis",
|
|
294
|
-
region: "Region",
|
|
295
|
-
type: "Typ",
|
|
296
|
-
reason: "Anregung",
|
|
297
|
-
moreInfo: "Mehr Info",
|
|
298
|
-
winePairingIntro: "Für diesen Wein gibt es folgende Essensvorschläge:",
|
|
299
|
-
askForDinner: "Was möchten Sie zum Abendessen essen?",
|
|
300
|
-
clearChatButton: "Chat löschen",
|
|
301
|
-
widgetTitle: "FINDEN SIE IHREN PERFEKTEN WEIN",
|
|
302
|
-
widgetSubtitle: "Teilen Sie alles, was Ihnen in den Sinn kommt—Ihre Mahlzeit, Stimmung, Ihr Budget oder Anlass—und unser KI-Sommelier empfiehlt Ihnen in Sekunden den perfekten Wein",
|
|
303
|
-
placeholderSuggestions: [
|
|
304
|
-
"Wein für Pasta",
|
|
305
|
-
"Perfekt zu Fisch",
|
|
306
|
-
"Empfohlen für eine Party"
|
|
307
|
-
],
|
|
308
|
-
searchPlaceholder: "Geben Sie ein Stichwort oder eine Beschreibung ein",
|
|
309
|
-
wineDetails: {
|
|
310
|
-
price: "Preis",
|
|
311
|
-
region: "Region",
|
|
312
|
-
type: "Typ",
|
|
313
|
-
moreInfo: "Mehr Infos",
|
|
314
|
-
sommSelection: "Somm-Auswahl",
|
|
315
|
-
reason: "Grund",
|
|
316
|
-
code: "Code",
|
|
317
|
-
buy: "Kaufen"
|
|
318
|
-
|
|
319
|
-
},
|
|
320
|
-
searchButton: "Suche",
|
|
321
|
-
priceRange: "Preisspanne",
|
|
322
|
-
wineColor: "Weinfarbe",
|
|
323
|
-
intensity: "Intensität",
|
|
324
|
-
red: "Rot",
|
|
325
|
-
white: "Weiß",
|
|
326
|
-
rose: "Rosé",
|
|
327
|
-
light: "Leicht",
|
|
328
|
-
medium: "Mittel",
|
|
329
|
-
full: "Voll",
|
|
330
|
-
|
|
331
|
-
/* -- Новые ключи bar -- */
|
|
332
|
-
barTitle: "Bar-Auswahl",
|
|
333
|
-
barSubtitle: "Wählen Sie Ihre Gerichte und klicken Sie auf 'Senden'",
|
|
334
|
-
barSendButton: "Senden",
|
|
335
|
-
barResetButton: "Zurücksetzen",
|
|
336
|
-
barName: "Name der Bar",
|
|
337
|
-
headerLine1: "Teilen Sie alles, was Ihnen durch den Kopf geht —",
|
|
338
|
-
headerLine2: "— und erhalten Sie in Sekundenschnelle Empfehlungen für den perfekten Wein",
|
|
339
|
-
headerMeal: "Mahlzeit",
|
|
340
|
-
headerMood: "Stimmung",
|
|
341
|
-
headerBudget: "Budget",
|
|
342
|
-
headerOccasion: "Anlass",
|
|
343
|
-
headerOr: "oder",
|
|
344
|
-
},
|
|
345
|
-
ukr: {
|
|
346
|
-
lang: 'ukr',
|
|
347
|
-
welcomeMessage: "Що ви збираєтесь їсти? Опишіть свою страву, і я порекомендую вам ідеальне вино.",
|
|
348
|
-
noResultsFound: "Результатів не знайдено.",
|
|
349
|
-
somethingWentWrong: "Вибачте, щось пішло не так.",
|
|
350
|
-
chatButton1: "Знайти ідеальне поєднання",
|
|
351
|
-
chatButton2: "Підібрати вино",
|
|
352
|
-
chatButton3: "Отримати пораду",
|
|
353
|
-
chatTitle: "Сомельє-помічник 🍷",
|
|
354
|
-
chatSubtitle: "Опишіть свою страву і отримайте рекомендацію за 30 секунд",
|
|
355
|
-
inputPlaceholder: "наприклад, Тунець із салатом",
|
|
356
|
-
sendButton: "Підібрати",
|
|
357
|
-
poweredBy: "Розроблено на основі",
|
|
358
|
-
price: "Ціна",
|
|
359
|
-
region: "Регіон",
|
|
360
|
-
type: "Тип",
|
|
361
|
-
reason: "Рекомендація",
|
|
362
|
-
moreInfo: "Докладніше",
|
|
363
|
-
winePairingIntro: "До цього вина підходять такі страви:",
|
|
364
|
-
askForDinner: "Що ви хочете на вечерю?",
|
|
365
|
-
clearChatButton: "Очистити чат",
|
|
366
|
-
widgetTitle: "ЗНАЙДІТЬ ІДЕАЛЬНЕ ВИНО",
|
|
367
|
-
widgetSubtitle: "Поділіться тим, що у вас на думці—їжа, настрій, бюджет чи подія—і наш AI-сомельє порекомендує вам ідеальне вино за секунди",
|
|
368
|
-
searchPlaceholder: "Введіть ключове слово або опис",
|
|
369
|
-
wineDetails: {
|
|
370
|
-
reason: "Причина",
|
|
371
|
-
price: "Ціна",
|
|
372
|
-
region: "Регіон",
|
|
373
|
-
type: "Тип",
|
|
374
|
-
moreInfo: "Докладніше",
|
|
375
|
-
sommSelection: "Вибір сомельє",
|
|
376
|
-
code: "Код",
|
|
377
|
-
buy: "Купити"
|
|
378
|
-
},
|
|
379
|
-
searchButton: "Пошук",
|
|
380
|
-
priceRange: "Діапазон цін",
|
|
381
|
-
wineColor: "Колір вина",
|
|
382
|
-
intensity: "Інтенсивність",
|
|
383
|
-
red: "Червоне",
|
|
384
|
-
white: "Біле",
|
|
385
|
-
rose: "Рожеве",
|
|
386
|
-
light: "Легке",
|
|
387
|
-
medium: "Середнє",
|
|
388
|
-
full: "Повне",
|
|
389
|
-
barTitle: "Закуски до вина",
|
|
390
|
-
barSubtitle: "Виберіть страви нижче та натисніть «Надіслати»",
|
|
391
|
-
barSendButton: "Надіслати",
|
|
392
|
-
barResetButton: "Скинути",
|
|
393
|
-
barName: "Назва бару",
|
|
394
|
-
barNotesPlaceholder: "Додайте ваші побажання тут...",
|
|
395
|
-
placeholderSuggestions: [
|
|
396
|
-
"Вино до пасти",
|
|
397
|
-
"Ідеально до риби",
|
|
398
|
-
"Рекомендуємо для вечірки"
|
|
399
|
-
],
|
|
400
|
-
headerLine1: "Поділіться будь-якими вашими думками —",
|
|
401
|
-
headerLine2: "— і отримайте рекомендації щодо ідеального вина за секунди",
|
|
402
|
-
headerMeal: "страва",
|
|
403
|
-
headerMood: "настрій",
|
|
404
|
-
headerBudget: "бюджет",
|
|
405
|
-
headerOccasion: "подія",
|
|
406
|
-
headerOr: "або",
|
|
407
|
-
},
|
|
408
|
-
ru: {
|
|
409
|
-
lang: 'ru',
|
|
410
|
-
welcomeMessage: "Что вы планируете поесть? Напишите ваше блюдо, и я подберу идеальное вино к нему.",
|
|
411
|
-
noResultsFound: "Результаты не найдены.",
|
|
412
|
-
somethingWentWrong: "Извините, что-то пошло не так.",
|
|
413
|
-
chatButton1: "Найти сочетание",
|
|
414
|
-
chatButton2: "Подобрать вино",
|
|
415
|
-
chatButton3: "Получить совет",
|
|
416
|
-
chatTitle: "Ассистент по подбору вина 🍷",
|
|
417
|
-
chatSubtitle: "Опишите ваше блюдо и получите рекомендацию за 30 секунд",
|
|
418
|
-
inputPlaceholder: "например, Тунец с салатом",
|
|
419
|
-
sendButton: "Подобрать",
|
|
420
|
-
poweredBy: "Powered by",
|
|
421
|
-
price: "Цена",
|
|
422
|
-
region: "Регион",
|
|
423
|
-
type: "Тип",
|
|
424
|
-
reason: "Рекомендация",
|
|
425
|
-
moreInfo: "Подробнее",
|
|
426
|
-
winePairingIntro: "К этому вину подходят следующие блюда:",
|
|
427
|
-
askForDinner: "Что бы вы хотели на ужин?",
|
|
428
|
-
clearChatButton: "Очистить чат",
|
|
429
|
-
widgetTitle: "НАЙДИТЬ ИДЕАЛЬНОЕ ВИНО",
|
|
430
|
-
widgetSubtitle: "Поделитесь вашими предпочтениями — блюдом, настроением, бюджетом или случаем — и наш AI-сомелье подберёт идеальное вино за секунды",
|
|
431
|
-
searchPlaceholder: "Введите ключевое слово или описание",
|
|
432
|
-
wineDetails: {
|
|
433
|
-
price: "Цена",
|
|
434
|
-
region: "Регион",
|
|
435
|
-
type: "Тип",
|
|
436
|
-
moreInfo: "Подробнее",
|
|
437
|
-
reason: "Причина",
|
|
438
|
-
sommSelection: "Выбор сомелье",
|
|
439
|
-
code: "Код",
|
|
440
|
-
buy: "Купить"
|
|
441
|
-
},
|
|
442
|
-
searchButton: "Поиск",
|
|
443
|
-
priceRange: "Диапазон цен",
|
|
444
|
-
wineColor: "Цвет вина",
|
|
445
|
-
intensity: "Интенсивность",
|
|
446
|
-
red: "Красное",
|
|
447
|
-
white: "Белое",
|
|
448
|
-
rose: "Розовое",
|
|
449
|
-
light: "Лёгкое",
|
|
450
|
-
medium: "Среднее",
|
|
451
|
-
full: "Полное",
|
|
452
|
-
|
|
453
|
-
/* -- Новые ключи для бара -- */
|
|
454
|
-
barTitle: "Закуски к вину",
|
|
455
|
-
barSubtitle: "Выберите блюда ниже и нажмите 'Отправить'",
|
|
456
|
-
barSendButton: "Отправить",
|
|
457
|
-
barResetButton: "Сбросить",
|
|
458
|
-
barName: "Название бара",
|
|
459
|
-
barNotesPlaceholder: "Добавьте ваши пожелания здесь...",
|
|
460
|
-
placeholderSuggestions: [
|
|
461
|
-
"Вино для пасты",
|
|
462
|
-
"Идеально к рыбе",
|
|
463
|
-
"Рекомендуем для вечеринки"
|
|
464
|
-
],
|
|
465
|
-
headerLine1: "Поделитесь любыми вашими мыслями —",
|
|
466
|
-
headerLine2: "— и получите рекомендации по идеальному вину за считанные секунды",
|
|
467
|
-
headerMeal: "блюдо",
|
|
468
|
-
headerMood: "настроение",
|
|
469
|
-
headerBudget: "бюджет",
|
|
470
|
-
headerOccasion: "случай",
|
|
471
|
-
headerOr: "или",
|
|
472
|
-
},
|
|
473
|
-
kk: {
|
|
474
|
-
lang: 'kk',
|
|
475
|
-
welcomeMessage: "Сіз не жегіңіз келеді? Тағамыңызды сипаттаңыз, мен оған керемет шарап ұсынамын.",
|
|
476
|
-
noResultsFound: "Нәтижелер табылмады.",
|
|
477
|
-
somethingWentWrong: "Кешіріңіз, бірдеңе дұрыс болмады.",
|
|
478
|
-
chatButton1: "Сәйкестік табу",
|
|
479
|
-
chatButton2: "Шарап таңдау",
|
|
480
|
-
chatButton3: "Кеңес алу",
|
|
481
|
-
chatTitle: "Шарап таңдаушы көмекші 🍷",
|
|
482
|
-
chatSubtitle: "Тағамыңызды сипаттаңыз — 30 секундта ұсыныс алыңыз",
|
|
483
|
-
inputPlaceholder: "мысалы, тунец салатымен",
|
|
484
|
-
sendButton: "Таңдау",
|
|
485
|
-
poweredBy: "Powered by",
|
|
486
|
-
price: "Бағасы",
|
|
487
|
-
region: "Өңір",
|
|
488
|
-
type: "Түрі",
|
|
489
|
-
reason: "Ұсыныс",
|
|
490
|
-
moreInfo: "Толығырақ",
|
|
491
|
-
winePairingIntro: "Бұл шарапқа келесі тағамдар сәйкес келеді:",
|
|
492
|
-
askForDinner: "Кешкі асқа не қалайсыз?",
|
|
493
|
-
clearChatButton: "Чатты тазалау",
|
|
494
|
-
widgetTitle: "КЕРЕМЕТ ШАРАПТЫ ТАБЫҢЫЗ",
|
|
495
|
-
widgetSubtitle: "Таңдауларыңызды бөлісіңіз — тағам, көңіл-күй, бюджет немесе жағдай — және біздің AI-сомелье сізге бірнеше секундта шарап ұсынады",
|
|
496
|
-
searchPlaceholder: "Кілт сөзді немесе сипаттаманы енгізіңіз",
|
|
497
|
-
wineDetails: {
|
|
498
|
-
price: "Бағасы",
|
|
499
|
-
region: "Өңір",
|
|
500
|
-
type: "Түрі",
|
|
501
|
-
moreInfo: "Толығырақ",
|
|
502
|
-
reason: "Себебі",
|
|
503
|
-
sommSelection: "Сомелье таңдауы",
|
|
504
|
-
code: "Код",
|
|
505
|
-
buy: "Сатып алу"
|
|
506
|
-
},
|
|
507
|
-
searchButton: "Іздеу",
|
|
508
|
-
priceRange: "Баға диапазоны",
|
|
509
|
-
wineColor: "Шарап түсі",
|
|
510
|
-
intensity: "Қанықтылық",
|
|
511
|
-
red: "Қызыл",
|
|
512
|
-
white: "Ақ",
|
|
513
|
-
rose: "Розалық",
|
|
514
|
-
light: "Жеңіл",
|
|
515
|
-
medium: "Орташа",
|
|
516
|
-
full: "Толық",
|
|
517
|
-
|
|
518
|
-
/* -- Барға арналған жаңа кілттер -- */
|
|
519
|
-
barTitle: "Шарапқа арналған тіскебасарлар",
|
|
520
|
-
barSubtitle: "Төмендегі тағамдарды таңдаңыз және 'Жіберу' батырмасын басыңыз",
|
|
521
|
-
barSendButton: "Жіберу",
|
|
522
|
-
barResetButton: "Қалпына келтіру",
|
|
523
|
-
barName: "Бар атауы",
|
|
524
|
-
barNotesPlaceholder: "Мұнда өз тілектеріңізді жазыңыз...",
|
|
525
|
-
placeholderSuggestions: [
|
|
526
|
-
"Пастаға арналған шарап",
|
|
527
|
-
"Балыққа тамаша үйлеседі",
|
|
528
|
-
"Кешке ұсынуға болады"
|
|
529
|
-
],
|
|
530
|
-
headerLine1: "Ойларыңызбен бөлісіңіз —",
|
|
531
|
-
headerLine2: "— және бірнеше секундта мінсіз шарап ұсынысын алыңыз",
|
|
532
|
-
headerMeal: "тағам",
|
|
533
|
-
headerMood: "көңіл-күй",
|
|
534
|
-
headerBudget: "бюджет",
|
|
535
|
-
headerOccasion: "жағдай",
|
|
536
|
-
headerOr: "немесе",
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
async fetchConfig(storeName) {
|
|
542
|
-
console.log('storeName', storeName);
|
|
543
|
-
try {
|
|
544
|
-
const config = await this.api.getStoreConfig(storeName);
|
|
545
|
-
this.storeLanguage = config.lang;
|
|
546
|
-
this.storeId = config.id;
|
|
547
|
-
this.supportedLanguages = config.available_languages;
|
|
548
|
-
this.OpenAIApiKey = config.open_ai_key;
|
|
549
|
-
console.log('this.storeId', this.storeId);
|
|
550
|
-
|
|
551
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
552
|
-
const urlLang = urlParams.get('lang') || urlParams.get('language');
|
|
553
|
-
|
|
554
|
-
if (urlLang && this.translations[urlLang]) {
|
|
555
|
-
this.language = urlLang;
|
|
556
|
-
} else if (config.lang && this.translations[config.lang]) {
|
|
557
|
-
this.language = config.lang;
|
|
558
|
-
} else {
|
|
559
|
-
this.language = 'en';
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
this.currency = config.currency || 'USD';
|
|
563
|
-
return config;
|
|
564
|
-
} catch (e) {
|
|
565
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
566
|
-
const urlLang = urlParams.get('lang') || urlParams.get('language');
|
|
567
|
-
|
|
568
|
-
this.language = this.translations[urlLang] ? urlLang : 'en';
|
|
569
|
-
this.currency = 'USD';
|
|
570
|
-
return config;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
getStoreId() {
|
|
575
|
-
return this.storeId;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
getCurrency(key) {
|
|
579
|
-
const keys = key.split('.'); // Разбиваем ключ на части
|
|
580
|
-
let value = this.currencies[this.currency];
|
|
581
|
-
|
|
582
|
-
for (const k of keys) {
|
|
583
|
-
if (value && typeof value === 'object' && k in value) {
|
|
584
|
-
value = value[k]; // Переходим к следующему уровню
|
|
585
|
-
} else {
|
|
586
|
-
return this.currency; // Если ключ не найден, возвращаем сам ключ
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
return value;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
translate(key) {
|
|
594
|
-
const keys = key.split('.'); // Разбиваем ключ на части
|
|
595
|
-
let value = this.translations[this.language];
|
|
596
|
-
|
|
597
|
-
for (const k of keys) {
|
|
598
|
-
if (value && typeof value === 'object' && k in value) {
|
|
599
|
-
value = value[k]; // Переходим к следующему уровню
|
|
600
|
-
} else {
|
|
601
|
-
return key; // Если ключ не найден, возвращаем сам ключ
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
return value;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
getSupportedLanguages() {
|
|
609
|
-
return this.supportedLanguages;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
getLangFromURL() {
|
|
613
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
614
|
-
const lang = urlParams.get('lang') || urlParams.get('language');
|
|
615
|
-
return lang && this.translations[lang] ? lang : null;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
async translateTexts(textObject) {
|
|
619
|
-
if (this.storeLanguage === this.language || !this.supportedLanguages) {
|
|
620
|
-
console.log('gривет');
|
|
621
|
-
return textObject;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
const apiKey = this.OpenAIApiKey;
|
|
625
|
-
const url = 'https://api.openai.com/v1/chat/completions';
|
|
626
|
-
|
|
627
|
-
const headers = {
|
|
628
|
-
'Content-Type': 'application/json',
|
|
629
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
const entries = Object.entries(textObject).map(([key, text]) => ({
|
|
633
|
-
key,
|
|
634
|
-
text,
|
|
635
|
-
}));
|
|
636
|
-
|
|
637
|
-
const prompt = `
|
|
638
|
-
You are a professional translator.
|
|
639
|
-
|
|
640
|
-
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}".
|
|
641
|
-
|
|
642
|
-
⚠️ DO NOT leave the original text.
|
|
643
|
-
⚠️ DO NOT skip any entry.
|
|
644
|
-
⚠️ Preserve the original order and keys exactly.
|
|
645
|
-
|
|
646
|
-
Translate only the "text" fields of the JSON array. Return an array of the same length and structure, like this:
|
|
647
|
-
|
|
648
|
-
Input:
|
|
649
|
-
[
|
|
650
|
-
{ "key": "shortDescription", "text": "Red wine with rich cherry notes." },
|
|
651
|
-
{ "key": "tags", "text": "fruity, dry, elegant" }
|
|
652
|
-
]
|
|
653
|
-
|
|
654
|
-
Output:
|
|
655
|
-
[
|
|
656
|
-
{ "key": "shortDescription", "text": "..." },
|
|
657
|
-
{ "key": "tags", "text": "..." }
|
|
658
|
-
]
|
|
659
|
-
|
|
660
|
-
Now translate the following input:
|
|
661
|
-
${JSON.stringify(entries, null, 2)}
|
|
662
|
-
`;
|
|
663
|
-
|
|
664
|
-
const body = JSON.stringify({
|
|
665
|
-
model: 'gpt-3.5-turbo',
|
|
666
|
-
messages: [
|
|
667
|
-
{ role: 'system', content: 'You are a professional translator.' },
|
|
668
|
-
{ role: 'user', content: prompt },
|
|
669
|
-
],
|
|
670
|
-
temperature: 0.2,
|
|
671
|
-
max_tokens: 1000,
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
try {
|
|
675
|
-
const response = await fetch(url, {
|
|
676
|
-
method: 'POST',
|
|
677
|
-
headers,
|
|
678
|
-
body,
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
const data = await response.json();
|
|
682
|
-
const translatedRaw = data.choices?.[0]?.message?.content?.trim();
|
|
683
|
-
|
|
684
|
-
if (!translatedRaw) throw new Error('No response from OpenAI');
|
|
685
|
-
|
|
686
|
-
const translatedArray = JSON.parse(translatedRaw);
|
|
687
|
-
|
|
688
|
-
const translatedObject = {};
|
|
689
|
-
translatedArray.forEach(entry => {
|
|
690
|
-
if (entry.key && entry.text !== undefined) {
|
|
691
|
-
translatedObject[entry.key] = entry.text;
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
return translatedObject;
|
|
696
|
-
} catch (error) {
|
|
697
|
-
console.error('Error translating texts:', error);
|
|
698
|
-
return textObject;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
export default new Translator();
|