wineater-bot 4.0.0 → 4.1.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.
@@ -1,5 +1,5 @@
1
- <div id="loading-indicator">
2
- <div id="typing-indicator" class="typing-indicator">
1
+ <div class="wr-Loader">
2
+ <div class="wr-Loader-indicator">
3
3
  <span></span>
4
4
  <span></span>
5
5
  <span></span>
@@ -7,7 +7,7 @@
7
7
  </div>
8
8
 
9
9
  <style>
10
- #loading-indicator {
10
+ .wr-Loader {
11
11
  position: absolute;
12
12
  top: 50%;
13
13
  left: 50%;
@@ -16,7 +16,7 @@
16
16
  text-align: center;
17
17
  }
18
18
 
19
- .typing-indicator {
19
+ .wr-Loader-indicator {
20
20
  display: flex;
21
21
  justify-content: center;
22
22
  align-items: center;
@@ -25,7 +25,7 @@
25
25
  background-color: white;
26
26
  }
27
27
 
28
- .typing-indicator span {
28
+ .wr-Loader-indicator span {
29
29
  display: block;
30
30
  width: 8px;
31
31
  height: 8px;
@@ -48,15 +48,15 @@
48
48
  }
49
49
  }
50
50
 
51
- .typing-indicator span:nth-child(1) {
51
+ .wr-Loader-indicator span:nth-child(1) {
52
52
  animation-delay: 0s;
53
53
  }
54
54
 
55
- .typing-indicator span:nth-child(2) {
55
+ .wr-Loader-indicator span:nth-child(2) {
56
56
  animation-delay: 0.15s;
57
57
  }
58
58
 
59
- .typing-indicator span:nth-child(3) {
59
+ .wr-Loader-indicator span:nth-child(3) {
60
60
  animation-delay: 0.3s;
61
61
  }
62
62
  </style>
@@ -2,6 +2,7 @@
2
2
  import { onMount } from 'svelte';
3
3
  import { appStore } from '../services/store/app-store.svelte';
4
4
  import { getTranslation } from '../services/i18n/i18n';
5
+ import Action from './ui/Action.svelte';
5
6
 
6
7
  interface Props {
7
8
  searchQuery: string;
@@ -106,23 +107,23 @@
106
107
  });
107
108
  </script>
108
109
 
109
- <form id="wine-widget-search" onsubmit={onSubmit}>
110
- <span class="search-icon"></span>
110
+ <form class="wr-Search" onsubmit={onSubmit}>
111
+ <span class="wr-Search-icon"></span>
111
112
  <input
112
- id="wine-search-input"
113
+ class="wr-Search-input"
113
114
  bind:this={searchEl}
114
115
  type="text"
115
116
  bind:value={searchQuery}
116
117
  onfocus={onSearchFocus}
117
118
  onblur={onSearchBlur}
118
119
  />
119
- <button id="wine-search-button" type="submit">
120
+ <Action className="wr-Search-button" type="submit" variant="primary">
120
121
  {getTranslation({ key: 'searchButton', lang })}
121
- </button>
122
+ </Action>
122
123
  </form>
123
124
 
124
125
  <style>
125
- #wine-widget-search {
126
+ .wr-Search {
126
127
  position: relative;
127
128
  margin: 12px auto;
128
129
  padding: 0 6px;
@@ -132,7 +133,7 @@
132
133
  border-radius: 10px;
133
134
  }
134
135
 
135
- .search-icon {
136
+ .wr-Search-icon {
136
137
  left: 12px;
137
138
  width: 24px;
138
139
  height: 24px;
@@ -143,7 +144,7 @@
143
144
  z-index: 2;
144
145
  }
145
146
 
146
- #wine-search-input {
147
+ .wr-Search-input {
147
148
  flex: 1 1 0%;
148
149
  min-width: 0;
149
150
  padding: 12px;
@@ -154,7 +155,7 @@
154
155
  transition: border 0.2s ease;
155
156
  }
156
157
 
157
- #wine-search-input::placeholder {
158
+ .wr-Search-input::placeholder {
158
159
  color: var(--wineater-color);
159
160
  opacity: 1;
160
161
  transition:
@@ -162,27 +163,15 @@
162
163
  opacity 0.3s ease-in-out;
163
164
  }
164
165
 
165
- #wine-search-input:focus {
166
+ .wr-Search-input:focus {
166
167
  border-color: var(--wineater-color);
167
168
  }
168
169
 
169
- #wine-search-input:focus::placeholder {
170
+ .wr-Search-input:focus::placeholder {
170
171
  opacity: 0.5;
171
172
  }
172
173
 
173
- #wine-search-button {
174
+ .wr-Search-button {
174
175
  flex: 0 0 auto;
175
- background-color: var(--wineater-color);
176
- color: #fff;
177
- border: none;
178
- border-radius: 6px;
179
- padding: 8px 16px;
180
- font-size: 16px;
181
- cursor: pointer;
182
- transition: opacity 0.2s ease;
183
- }
184
-
185
- #wine-search-button:hover {
186
- opacity: 0.8;
187
176
  }
188
177
  </style>
@@ -21,29 +21,29 @@
21
21
  </script>
22
22
 
23
23
  {#if mobile}
24
- <div class="mobile-fullscreen-wrapper">
25
- <div class="mobile-fullscreen-overlay" onclick={onClose}></div>
26
- <div class="mobile-fullscreen-popup">
27
- <div class="card-popup">
28
- <div class="card-popup-header">
29
- <h2>{title}</h2>
30
- <button class="card-popup-close" onclick={onClose}>&times;</button>
24
+ <div class="wr-WinePopupMobile">
25
+ <div class="wr-WinePopupMobile-overlay" onclick={onClose}></div>
26
+ <div class="wr-WinePopupMobile-wrapper">
27
+ <div class="wr-WinePopup js-WinePopup">
28
+ <div class="wr-WinePopup-header">
29
+ <h2 class="wr-WinePopup-title">{title}</h2>
30
+ <button class="wr-WinePopup-close js-WinePopupClose" onclick={onClose}>&times;</button>
31
31
  </div>
32
32
 
33
33
  {#if shortDescription}
34
- <p class="popup-short-desc">{shortDescription}</p>
34
+ <p class="wr-WinePopup-desc wr-WinePopup-text">{shortDescription}</p>
35
35
  {/if}
36
36
 
37
37
  {#if reason}
38
- <div class="popup-reason">{reason}</div>
38
+ <div class="wr-WinePopup-reason">{reason}</div>
39
39
  {:else if pairing}
40
- <p class="popup-pairing">{pairing}</p>
40
+ <p class="wr-WinePopup-pairing wr-WinePopup-text">{pairing}</p>
41
41
  {/if}
42
42
 
43
43
  {#if tags && tags.length}
44
- <div class="popup-tags">
44
+ <div class="wr-WinePopup-tags">
45
45
  {#each tags as tag}
46
- <span class="popup-tag">{tag}</span>
46
+ <span class="wr-WinePopup-tag">{tag}</span>
47
47
  {/each}
48
48
  </div>
49
49
  {/if}
@@ -51,26 +51,26 @@
51
51
  </div>
52
52
  </div>
53
53
  {:else}
54
- <div class="card-popup">
55
- <div class="card-popup-header">
56
- <h2>{title}</h2>
57
- <button class="card-popup-close" onclick={onClose}>&times;</button>
54
+ <div class="wr-WinePopup">
55
+ <div class="wr-WinePopup-header">
56
+ <h2 class="wr-WinePopup-title">{title}</h2>
57
+ <button class="wr-WinePopup-close" onclick={onClose}>&times;</button>
58
58
  </div>
59
59
 
60
60
  {#if shortDescription}
61
- <p class="popup-short-desc">{shortDescription}</p>
61
+ <p class="wr-WinePopup-desc wr-WinePopup-text">{shortDescription}</p>
62
62
  {/if}
63
63
 
64
64
  {#if reason}
65
- <div class="popup-reason">{reason}</div>
65
+ <div class="wr-WinePopup-reason">{reason}</div>
66
66
  {:else if pairing}
67
- <p class="popup-pairing">{pairing}</p>
67
+ <p class="wr-WinePopup-pairing wr-WinePopup-text">{pairing}</p>
68
68
  {/if}
69
69
 
70
70
  {#if tags && tags.length}
71
- <div class="popup-tags">
71
+ <div class="wr-WinePopup-tags">
72
72
  {#each tags as tag}
73
- <span class="popup-tag">{tag}</span>
73
+ <span class="wr-WinePopup-tag">{tag}</span>
74
74
  {/each}
75
75
  </div>
76
76
  {/if}
@@ -78,7 +78,7 @@
78
78
  {/if}
79
79
 
80
80
  <style>
81
- .card-popup {
81
+ .wr-WinePopup {
82
82
  position: absolute;
83
83
  width: 100%;
84
84
  background-color: white;
@@ -92,18 +92,18 @@
92
92
  flex-direction: column;
93
93
  }
94
94
 
95
- .card-popup p {
95
+ .wr-WinePopup-text {
96
96
  margin: 5px 0;
97
97
  }
98
98
 
99
- .card-popup-header {
99
+ .wr-WinePopup-header {
100
100
  display: flex;
101
101
  justify-content: space-between;
102
102
  align-items: flex-start;
103
103
  gap: 12px;
104
104
  }
105
105
 
106
- .card-popup-header h2 {
106
+ .wr-WinePopup-title {
107
107
  font-size: 16px;
108
108
  font-weight: 600;
109
109
  margin: 0;
@@ -111,7 +111,7 @@
111
111
  line-height: 1.3;
112
112
  }
113
113
 
114
- .card-popup-close {
114
+ .wr-WinePopup-close {
115
115
  font-size: 20px;
116
116
  font-weight: bold;
117
117
  cursor: pointer;
@@ -121,26 +121,26 @@
121
121
  line-height: 1;
122
122
  }
123
123
 
124
- .card-popup-close:hover {
124
+ .wr-WinePopup-close:hover {
125
125
  color: #000;
126
126
  }
127
127
 
128
- .popup-short-desc {
128
+ .wr-WinePopup-desc {
129
129
  margin-top: 8px;
130
130
  }
131
131
 
132
- .popup-reason {
132
+ .wr-WinePopup-reason {
133
133
  font-size: 14px;
134
134
  font-weight: 400;
135
135
  color: #333;
136
136
  margin-top: 8px;
137
137
  }
138
138
 
139
- .popup-pairing {
139
+ .wr-WinePopup-pairing {
140
140
  margin-top: 8px;
141
141
  }
142
142
 
143
- .popup-tags {
143
+ .wr-WinePopup-tags {
144
144
  display: flex;
145
145
  flex-wrap: wrap;
146
146
  gap: 8px;
@@ -148,7 +148,7 @@
148
148
  justify-content: center;
149
149
  }
150
150
 
151
- .popup-tag {
151
+ .wr-WinePopup-tag {
152
152
  display: inline-block;
153
153
  padding: 4px 8px;
154
154
  border-radius: 12px;
@@ -159,7 +159,7 @@
159
159
  text-transform: capitalize;
160
160
  }
161
161
 
162
- .mobile-fullscreen-wrapper {
162
+ .wr-WinePopupMobile {
163
163
  position: fixed;
164
164
  top: 0;
165
165
  left: 0;
@@ -168,7 +168,7 @@
168
168
  z-index: 10000;
169
169
  }
170
170
 
171
- .mobile-fullscreen-overlay {
171
+ .wr-WinePopupMobile-overlay {
172
172
  position: absolute;
173
173
  top: 0;
174
174
  left: 0;
@@ -177,7 +177,7 @@
177
177
  background: rgba(0, 0, 0, 0.5);
178
178
  }
179
179
 
180
- .mobile-fullscreen-popup {
180
+ .wr-WinePopupMobile-wrapper {
181
181
  position: absolute;
182
182
  width: 100%;
183
183
  height: 100%;
@@ -4,6 +4,8 @@
4
4
  import WinePopup from './WinePopup.svelte';
5
5
  import { onMount } from 'svelte';
6
6
  import { appStore } from '../services/store/app-store.svelte';
7
+ import Action from './ui/Action.svelte';
8
+
7
9
  let lang = $derived(appStore.getAppLanguage());
8
10
 
9
11
  let {
@@ -80,42 +82,55 @@
80
82
 
81
83
  <!-- svelte-ignore a11y_no_static_element_interactions -->
82
84
  <!-- svelte-ignore a11y_click_events_have_key_events -->
83
- <div
84
- class="wine-recommendation-tile {isAnimating ? 'ww-slide-up' : ''}"
85
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
86
+ <article
87
+ class="wr-WineTile {isAnimating ? 'ww-slide-up' : ''}"
85
88
  onmouseenter={() => onTileEnter(wine)}
86
89
  onmouseleave={() => onTileLeave(wine)}
87
90
  onclick={(event) => onTileClick(event, wine)}
88
91
  >
89
- <div class="tile-top">
92
+ <header class="wr-WineTile-header">
90
93
  {#if wine.image}
91
- <img src={wine.image} alt={wine.title} class="wine-image" />
94
+ <img src={wine.image} alt={wine.title} class="wr-WineTile-image" />
92
95
  {/if}
93
96
  {#if wine.total}
94
- <div class="wine-price">
97
+ <div class="wr-WineTile-price">
95
98
  {formatPrice(wine.total)}
96
99
  </div>
97
100
  {/if}
98
101
 
99
- <h3 class="wine-title">{wine.title}</h3>
100
- </div>
102
+ <h3 class="wr-WineTile-title">{wine.title}</h3>
103
+ </header>
101
104
 
102
105
  {#if joinParams(wine, ['type', 'country', 'region', 'grape'])}
103
- <div class="wine-params">
106
+ <div class="wr-WineTile-params">
104
107
  {joinParams(wine, ['type', 'country', 'region', 'grape'])}
105
108
  </div>
106
109
  {/if}
107
110
 
108
- <div class="bottom-row">
109
- {#if wine.url}
110
- <a class="buy-btn" href={wine.url} target="_blank">
111
- {getTranslation({ key: 'wineDetails.buy', lang })}
112
- </a>
113
- {:else}
114
- <button class="buy-btn" type="button" onclick={() => onBuyClick(wine, index)}>
115
- {getTranslation({ key: 'moreInfo', lang })}
116
- </button>
117
- {/if}
118
- </div>
111
+ <footer class="wr-WineTile-footer">
112
+ <!-- We need this wrapper for top margin -->
113
+ <div class="wr-WineTile-actions">
114
+ {#if wine.url}
115
+ <Action
116
+ href={wine.url}
117
+ className="wr-WineTile-action wr-WineTile-action--link js-WineTileAction"
118
+ target="_blank"
119
+ variant="outlined"
120
+ >
121
+ {getTranslation({ key: 'wineDetails.buy', lang })}
122
+ </Action>
123
+ {:else}
124
+ <Action
125
+ handleClick={() => onBuyClick(wine, index)}
126
+ variant="outlined"
127
+ className="wr-WineTile-action wr-WineTile-action--button js-WineTileAction"
128
+ >
129
+ {getTranslation({ key: 'moreInfo', lang })}
130
+ </Action>
131
+ {/if}
132
+ </div>
133
+ </footer>
119
134
 
120
135
  {#if isPopupOpen(wine.id) && !isMobile}
121
136
  <WinePopup
@@ -130,9 +145,9 @@
130
145
  {/if}
131
146
 
132
147
  {#if hasHoverPopupContent(wine)}
133
- <div class="wine-hover-popup {hoveredWineId === wine.id ? 'slide-in visible' : ''}">
148
+ <div class="wr-WineTilePopup {hoveredWineId === wine.id ? 'slide-in visible' : ''}">
134
149
  {#if translatedPopup?.reason || translatedPopup?.shortDescription || wine.reason || wine.shortDescription}
135
- <div class="popup-reason">
150
+ <div class="wr-WineTilePopup-reason">
136
151
  {translatedPopup?.reason ||
137
152
  translatedPopup?.shortDescription ||
138
153
  wine.reason ||
@@ -140,15 +155,15 @@
140
155
  </div>
141
156
  {/if}
142
157
  {#if translatedPopup?.tags || translatedPopup?.Tags || wine.tags}
143
- <div class="popup-tags">
158
+ <div class="wr-WineTilePopup-tags">
144
159
  {#each getTags(translatedPopup, wine) as tag}
145
- <span class="popup-tag">{tag}</span>
160
+ <span class="wr-WineTilePopup-tag">{tag}</span>
146
161
  {/each}
147
162
  </div>
148
163
  {/if}
149
164
  </div>
150
165
  {/if}
151
- </div>
166
+ </article>
152
167
 
153
168
  {#if isPopupOpen(wine.id) && isMobile}
154
169
  <WinePopup
@@ -163,7 +178,7 @@
163
178
  {/if}
164
179
 
165
180
  <style>
166
- .wine-recommendation-tile {
181
+ .wr-WineTile {
167
182
  display: flex;
168
183
  flex-direction: column;
169
184
  align-items: center;
@@ -180,30 +195,30 @@
180
195
  will-change: transform, opacity;
181
196
  }
182
197
 
183
- .wine-recommendation-tile:last-child {
198
+ .wr-WineTile:last-child {
184
199
  border-right: none;
185
200
  }
186
201
 
187
- .wine-recommendation-tile.ww-slide-up {
202
+ .wr-WineTile.ww-slide-up {
188
203
  transform: translateY(-28px);
189
204
  opacity: 0;
190
205
  }
191
206
 
192
- .tile-top {
207
+ .wr-WineTile-header {
193
208
  display: flex;
194
209
  flex-direction: column;
195
210
  align-items: center;
196
211
  justify-content: flex-start;
197
212
  }
198
213
 
199
- .wine-image {
214
+ .wr-WineTile-image {
200
215
  height: 200px;
201
216
  object-fit: contain;
202
217
  margin: 0 auto 16px;
203
218
  display: block;
204
219
  }
205
220
 
206
- .wine-title {
221
+ .wr-WineTile-title {
207
222
  font-size: 15px;
208
223
  font-weight: 500;
209
224
  margin: 0 0 8px;
@@ -213,14 +228,14 @@
213
228
  color: #000;
214
229
  }
215
230
 
216
- .wine-params {
231
+ .wr-WineTile-params {
217
232
  font-size: 14px;
218
233
  color: #666;
219
234
  white-space: normal;
220
235
  text-align: center;
221
236
  }
222
237
 
223
- .bottom-row {
238
+ .wr-WineTile-footer {
224
239
  margin-top: auto;
225
240
  display: flex;
226
241
  align-items: center;
@@ -228,39 +243,19 @@
228
243
  justify-content: center;
229
244
  }
230
245
 
231
- .wine-price {
246
+ .wr-WineTile-price {
232
247
  font-size: 16px;
233
248
  font-weight: 400;
234
249
  margin-bottom: 8px;
235
250
  color: #333;
236
251
  }
237
252
 
238
- .buy-btn {
253
+ .wr-WineTile-actions {
239
254
  margin: 16px auto 0;
240
- background-color: transparent;
241
- color: var(--wineater-color);
242
- border: 2px solid var(--wineater-color);
243
- border-radius: 6px;
244
- padding: 8px 16px;
245
- font-size: 14px;
246
- font-weight: 500;
247
- cursor: pointer;
248
- display: block;
249
- text-decoration: none;
250
- transition:
251
- background-color 0.2s,
252
- color 0.2s,
253
- box-shadow 0.2s;
254
- }
255
-
256
- .buy-btn:hover {
257
- box-shadow: 0 4px 8px rgba(123, 43, 235, 0.25);
258
- background-color: var(--wineater-color);
259
- color: #fff;
260
255
  }
261
256
 
262
257
  /* Hover popup inside tile */
263
- .wine-hover-popup {
258
+ .wr-WineTilePopup {
264
259
  display: none;
265
260
  position: absolute;
266
261
  top: 0;
@@ -275,22 +270,22 @@
275
270
  z-index: 999;
276
271
  }
277
272
 
278
- .wine-hover-popup.visible {
273
+ .wr-WineTilePopup.visible {
279
274
  display: block;
280
275
  }
281
276
 
282
- .wine-hover-popup.slide-in {
277
+ .wr-WineTilePopup.slide-in {
283
278
  animation: whPopup 0.25s ease;
284
279
  }
285
280
 
286
- .popup-reason {
281
+ .wr-WineTilePopup-reason {
287
282
  font-size: 14px;
288
283
  font-weight: 400;
289
284
  color: #333;
290
285
  margin-bottom: 8px;
291
286
  }
292
287
 
293
- .popup-tags {
288
+ .wr-WineTilePopup-tags {
294
289
  display: flex;
295
290
  flex-wrap: wrap;
296
291
  gap: 8px;
@@ -298,7 +293,7 @@
298
293
  justify-content: center;
299
294
  }
300
295
 
301
- .popup-tag {
296
+ .wr-WineTilePopup-tag {
302
297
  display: inline-block;
303
298
  padding: 4px 8px;
304
299
  border-radius: 12px;
@@ -321,7 +316,7 @@
321
316
  }
322
317
 
323
318
  @media screen and (max-width: 768px) {
324
- .wine-recommendation-tile {
319
+ .wr-WineTile {
325
320
  flex: 0 0 auto;
326
321
  scroll-snap-align: center;
327
322
  width: 240px;
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ import { appStore } from '../../services/store/app-store.svelte';
3
+
4
+ type ActionProps = {
5
+ type?: 'button' | 'submit' | 'reset';
6
+ handleClick?: () => void;
7
+ variant?: 'primary' | 'outlined' | 'text';
8
+ className?: string;
9
+ id?: string;
10
+ href?: string;
11
+ target?: string;
12
+ };
13
+ let {
14
+ type = 'button',
15
+ handleClick,
16
+ variant = 'primary',
17
+ className,
18
+ id,
19
+ href,
20
+ target = '_self'
21
+ }: ActionProps = $props();
22
+ let appUiConfig = appStore.getUiConfig();
23
+ let calculatedClassName =
24
+ `wr-Action ${variant} ${className || ''} ${appUiConfig.buttonUppercase ? 'wr-Action--uppercase' : ''}`.trim();
25
+ </script>
26
+
27
+ {#if href}
28
+ <a class={calculatedClassName} {href} {id} {target} rel="noopener noreferrer">
29
+ <slot></slot>
30
+ </a>
31
+ {:else}
32
+ <button class={calculatedClassName} {type} onclick={handleClick} {id}>
33
+ <slot></slot>
34
+ </button>
35
+ {/if}
36
+
37
+ <style>
38
+ .wr-Action {
39
+ background: transparent;
40
+ border: none;
41
+ font-family: inherit;
42
+ cursor: pointer;
43
+ display: block;
44
+ text-decoration: none;
45
+ transition:
46
+ background 0.2s,
47
+ color 0.2s,
48
+ box-shadow 0.2s;
49
+ }
50
+
51
+ .wr-Action.primary {
52
+ background: var(--wineater-color);
53
+ color: #fff;
54
+ border-radius: 6px;
55
+ padding: 8px 16px;
56
+ font-size: 14px;
57
+ font-weight: 500;
58
+
59
+ &:hover {
60
+ opacity: 0.8;
61
+ }
62
+ }
63
+
64
+ .wr-Action.outlined {
65
+ background: transparent;
66
+ color: var(--wineater-color);
67
+ border: 2px solid var(--wineater-color);
68
+ border-radius: 6px;
69
+ padding: 8px 16px;
70
+ font-size: 14px;
71
+ font-weight: 500;
72
+
73
+ &:hover {
74
+ box-shadow: 0 4px 8px rgba(123, 43, 235, 0.25);
75
+ background-color: var(--wineater-color);
76
+ color: #fff;
77
+ }
78
+ }
79
+
80
+ .wr-Action--uppercase {
81
+ text-transform: uppercase;
82
+ }
83
+ </style>
@@ -1,4 +1,6 @@
1
1
  import translationsJson from './translations.json';
2
+ // TODO: use DI to get custom translations to avoid high coupling
3
+ import { appStore } from '../store/app-store.svelte';
2
4
 
3
5
  export type LanguageCode = keyof typeof translationsJson;
4
6
 
@@ -38,7 +40,13 @@ export function formatCurrency(amount: number, currencyCode: CurrencyCode, local
38
40
  // yes: "product.description.short": "..."
39
41
  // no: "product": { "description": { "short": "..." } }
40
42
  export function getTranslation({ key, lang }): string {
41
- const translations = translationsJson as Record<LanguageCode, Record<string, string>>;
43
+ const translations = translationsJson as Partial<Record<LanguageCode, Record<string, string>>>;
44
+ const appState = appStore.getUiConfig();
45
+ const projectTranslations = appState?.translations || {};
46
+ if (projectTranslations[lang] && projectTranslations[lang][key]) {
47
+ return projectTranslations[lang][key];
48
+ }
49
+
42
50
  if (translations[lang] && translations[lang][key]) {
43
51
  return translations[lang][key];
44
52
  }