@playpilot/tpi 1.3.0 → 1.4.0
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/link-injections.js +7 -7
- package/package.json +1 -1
- package/src/lib/api.js +3 -0
- package/src/lib/data/translations.js +238 -0
- package/src/lib/enums/Language.js +4 -0
- package/src/lib/localization.js +36 -0
- package/src/lib/meta.js +82 -0
- package/src/main.js +3 -1
- package/src/routes/+layout.svelte +2 -0
- package/src/routes/components/AfterArticlePlaylinks.svelte +6 -5
- package/src/routes/components/Editorial/Editor.svelte +1 -1
- package/src/routes/components/Genres.svelte +8 -3
- package/src/routes/components/Modal.svelte +1 -1
- package/src/routes/components/Playlinks.svelte +8 -7
- package/src/routes/components/Popover.svelte +1 -1
- package/src/routes/components/Title.svelte +3 -2
- package/src/tests/lib/api.test.js +5 -0
- package/src/tests/lib/localization.test.js +67 -0
- package/src/tests/lib/meta.test.js +201 -0
- package/src/typedefs.js +8 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Language } from '$lib/enums/Language.js'
|
|
2
|
+
|
|
3
|
+
export const translations = {
|
|
4
|
+
'Where To Stream Online': {
|
|
5
|
+
[Language.English]: 'Where to stream online',
|
|
6
|
+
[Language.Swedish]: 'Var kan man streama online',
|
|
7
|
+
},
|
|
8
|
+
'Affiliate Disclaimer': {
|
|
9
|
+
[Language.English]: 'We may earn a commission if you make a purchase through these links. In collaboration with',
|
|
10
|
+
[Language.Swedish]: 'Vi kan få provision om du gör ett köp via dessa länkar. I samarbete med',
|
|
11
|
+
},
|
|
12
|
+
'An Error Occurred': {
|
|
13
|
+
[Language.English]: 'An error occurred',
|
|
14
|
+
[Language.Swedish]: 'Ett fel inträffade',
|
|
15
|
+
},
|
|
16
|
+
'Title Unavailable': {
|
|
17
|
+
[Language.English]: 'This title is not currently available to stream.',
|
|
18
|
+
[Language.Swedish]: 'Den här titeln går inte att streama just nu.',
|
|
19
|
+
},
|
|
20
|
+
'Title Unavailable Suffix': {
|
|
21
|
+
[Language.English]: 'is not currently available to stream.',
|
|
22
|
+
[Language.Swedish]: 'går inte att streama just nu.',
|
|
23
|
+
},
|
|
24
|
+
'Is Available To Stream On': {
|
|
25
|
+
[Language.English]: 'is available to stream on',
|
|
26
|
+
[Language.Swedish]: 'finns att streama på',
|
|
27
|
+
},
|
|
28
|
+
'Is Available To Stream': {
|
|
29
|
+
[Language.English]: 'is available to stream.',
|
|
30
|
+
[Language.Swedish]: 'finns att streama.',
|
|
31
|
+
},
|
|
32
|
+
'View Streaming Options': {
|
|
33
|
+
[Language.English]: 'View streaming options',
|
|
34
|
+
[Language.Swedish]: 'Se streamingalternativ',
|
|
35
|
+
},
|
|
36
|
+
'And': {
|
|
37
|
+
[Language.English]: 'and',
|
|
38
|
+
[Language.Swedish]: 'och',
|
|
39
|
+
},
|
|
40
|
+
'Minutes': {
|
|
41
|
+
[Language.English]: 'minutes',
|
|
42
|
+
[Language.Swedish]: 'minuter',
|
|
43
|
+
},
|
|
44
|
+
'Type: movie': {
|
|
45
|
+
[Language.English]: 'Movie',
|
|
46
|
+
[Language.Swedish]: 'Film',
|
|
47
|
+
},
|
|
48
|
+
'Type: series': {
|
|
49
|
+
[Language.English]: 'Series',
|
|
50
|
+
[Language.Swedish]: 'Serie',
|
|
51
|
+
},
|
|
52
|
+
'Stream': {
|
|
53
|
+
[Language.English]: 'Stream',
|
|
54
|
+
[Language.Swedish]: 'Strömma',
|
|
55
|
+
},
|
|
56
|
+
'Buy': {
|
|
57
|
+
[Language.English]: 'Buy',
|
|
58
|
+
[Language.Swedish]: 'Köp',
|
|
59
|
+
},
|
|
60
|
+
'Rent': {
|
|
61
|
+
[Language.English]: 'Rent',
|
|
62
|
+
[Language.Swedish]: 'Hyra',
|
|
63
|
+
},
|
|
64
|
+
'Rent Or Buy': {
|
|
65
|
+
[Language.English]: 'Rent or Buy',
|
|
66
|
+
[Language.Swedish]: 'Hyra',
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Genres
|
|
70
|
+
'All': {
|
|
71
|
+
[Language.English]: 'All',
|
|
72
|
+
[Language.Swedish]: 'Alla',
|
|
73
|
+
},
|
|
74
|
+
'Unscripted': {
|
|
75
|
+
[Language.English]: 'Unscripted',
|
|
76
|
+
[Language.Swedish]: 'Oscripterat',
|
|
77
|
+
},
|
|
78
|
+
'Independent': {
|
|
79
|
+
[Language.English]: 'Independent',
|
|
80
|
+
[Language.Swedish]: 'Indie',
|
|
81
|
+
},
|
|
82
|
+
'Game Show': {
|
|
83
|
+
[Language.English]: 'Game Show',
|
|
84
|
+
[Language.Swedish]: 'Gameshow',
|
|
85
|
+
},
|
|
86
|
+
'Film Noir': {
|
|
87
|
+
[Language.English]: 'Film Noir',
|
|
88
|
+
[Language.Swedish]: 'Film noir',
|
|
89
|
+
},
|
|
90
|
+
'Entertainment': {
|
|
91
|
+
[Language.English]: 'Entertainment',
|
|
92
|
+
[Language.Swedish]: 'Underhållning',
|
|
93
|
+
},
|
|
94
|
+
'News': {
|
|
95
|
+
[Language.English]: 'News',
|
|
96
|
+
[Language.Swedish]: 'Nyheter',
|
|
97
|
+
},
|
|
98
|
+
'Short': {
|
|
99
|
+
[Language.English]: 'Short',
|
|
100
|
+
[Language.Swedish]: 'Kortfilm',
|
|
101
|
+
},
|
|
102
|
+
'Bollywood': {
|
|
103
|
+
[Language.English]: 'Bollywood',
|
|
104
|
+
[Language.Swedish]: 'Bollywood',
|
|
105
|
+
},
|
|
106
|
+
'Concert': {
|
|
107
|
+
[Language.English]: 'Concert',
|
|
108
|
+
[Language.Swedish]: 'Konsert',
|
|
109
|
+
},
|
|
110
|
+
'Arthouse': {
|
|
111
|
+
[Language.English]: 'Arthouse',
|
|
112
|
+
[Language.Swedish]: 'Arthouse',
|
|
113
|
+
},
|
|
114
|
+
'Alternate Version': {
|
|
115
|
+
[Language.English]: 'Alternate Version',
|
|
116
|
+
[Language.Swedish]: 'Alternativ version',
|
|
117
|
+
},
|
|
118
|
+
'Reality TV': {
|
|
119
|
+
[Language.English]: 'Reality TV',
|
|
120
|
+
[Language.Swedish]: 'Reality-TV',
|
|
121
|
+
},
|
|
122
|
+
'Culture': {
|
|
123
|
+
[Language.English]: 'Culture',
|
|
124
|
+
[Language.Swedish]: 'Kultur',
|
|
125
|
+
},
|
|
126
|
+
'Drama': {
|
|
127
|
+
[Language.English]: 'Drama',
|
|
128
|
+
[Language.Swedish]: 'Drama',
|
|
129
|
+
},
|
|
130
|
+
'Crime': {
|
|
131
|
+
[Language.English]: 'Crime',
|
|
132
|
+
[Language.Swedish]: 'Kriminal',
|
|
133
|
+
},
|
|
134
|
+
'Western': {
|
|
135
|
+
[Language.English]: 'Western',
|
|
136
|
+
[Language.Swedish]: 'Western',
|
|
137
|
+
},
|
|
138
|
+
'Thriller': {
|
|
139
|
+
[Language.English]: 'Thriller',
|
|
140
|
+
[Language.Swedish]: 'Thriller',
|
|
141
|
+
},
|
|
142
|
+
'Animation': {
|
|
143
|
+
[Language.English]: 'Animation',
|
|
144
|
+
[Language.Swedish]: 'Animerat',
|
|
145
|
+
},
|
|
146
|
+
'Mystery': {
|
|
147
|
+
[Language.English]: 'Mystery',
|
|
148
|
+
[Language.Swedish]: 'Mysterium',
|
|
149
|
+
},
|
|
150
|
+
'Science': {
|
|
151
|
+
[Language.English]: 'Science',
|
|
152
|
+
[Language.Swedish]: 'Vetenskap',
|
|
153
|
+
},
|
|
154
|
+
'Nature': {
|
|
155
|
+
[Language.English]: 'Nature',
|
|
156
|
+
[Language.Swedish]: 'Natur',
|
|
157
|
+
},
|
|
158
|
+
'War': {
|
|
159
|
+
[Language.English]: 'War',
|
|
160
|
+
[Language.Swedish]: 'Krig',
|
|
161
|
+
},
|
|
162
|
+
'Sci-Fi': {
|
|
163
|
+
[Language.English]: 'Sci-Fi',
|
|
164
|
+
[Language.Swedish]: 'Sci-fi',
|
|
165
|
+
},
|
|
166
|
+
'Documentary': {
|
|
167
|
+
[Language.English]: 'Documentary',
|
|
168
|
+
[Language.Swedish]: 'Dokumentär',
|
|
169
|
+
},
|
|
170
|
+
'Stand-up': {
|
|
171
|
+
[Language.English]: 'Stand-up',
|
|
172
|
+
[Language.Swedish]: 'Standup',
|
|
173
|
+
},
|
|
174
|
+
'Talk Show': {
|
|
175
|
+
[Language.English]: 'Talk Show',
|
|
176
|
+
[Language.Swedish]: 'Talkshow',
|
|
177
|
+
},
|
|
178
|
+
'Fantasy': {
|
|
179
|
+
[Language.English]: 'Fantasy',
|
|
180
|
+
[Language.Swedish]: 'Fantasy',
|
|
181
|
+
},
|
|
182
|
+
'History': {
|
|
183
|
+
[Language.English]: 'History',
|
|
184
|
+
[Language.Swedish]: 'Historia',
|
|
185
|
+
},
|
|
186
|
+
'Adventure': {
|
|
187
|
+
[Language.English]: 'Adventure',
|
|
188
|
+
[Language.Swedish]: 'Äventyr',
|
|
189
|
+
},
|
|
190
|
+
'Action': {
|
|
191
|
+
[Language.English]: 'Action',
|
|
192
|
+
[Language.Swedish]: 'Action',
|
|
193
|
+
},
|
|
194
|
+
'Horror': {
|
|
195
|
+
[Language.English]: 'Horror',
|
|
196
|
+
[Language.Swedish]: 'Skräck',
|
|
197
|
+
},
|
|
198
|
+
'Comedy': {
|
|
199
|
+
[Language.English]: 'Comedy',
|
|
200
|
+
[Language.Swedish]: 'Komedi',
|
|
201
|
+
},
|
|
202
|
+
'Biography': {
|
|
203
|
+
[Language.English]: 'Biography',
|
|
204
|
+
[Language.Swedish]: 'Biografi',
|
|
205
|
+
},
|
|
206
|
+
'Music': {
|
|
207
|
+
[Language.English]: 'Music',
|
|
208
|
+
[Language.Swedish]: 'Musik',
|
|
209
|
+
},
|
|
210
|
+
'Sport': {
|
|
211
|
+
[Language.English]: 'Sport',
|
|
212
|
+
[Language.Swedish]: 'Sport',
|
|
213
|
+
},
|
|
214
|
+
'Romance': {
|
|
215
|
+
[Language.English]: 'Romance',
|
|
216
|
+
[Language.Swedish]: 'Romantik',
|
|
217
|
+
},
|
|
218
|
+
'Kids': {
|
|
219
|
+
[Language.English]: 'Kids',
|
|
220
|
+
[Language.Swedish]: 'Barn',
|
|
221
|
+
},
|
|
222
|
+
'Lifestyle': {
|
|
223
|
+
[Language.English]: 'Lifestyle',
|
|
224
|
+
[Language.Swedish]: 'Livsstil',
|
|
225
|
+
},
|
|
226
|
+
'Musical': {
|
|
227
|
+
[Language.English]: 'Musical',
|
|
228
|
+
[Language.Swedish]: 'Musikal',
|
|
229
|
+
},
|
|
230
|
+
'Anime': {
|
|
231
|
+
[Language.English]: 'Anime',
|
|
232
|
+
[Language.Swedish]: 'Anime',
|
|
233
|
+
},
|
|
234
|
+
'Family': {
|
|
235
|
+
[Language.English]: 'Family',
|
|
236
|
+
[Language.Swedish]: 'Familj',
|
|
237
|
+
},
|
|
238
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { translations } from '$lib/data/translations'
|
|
2
|
+
import { Language } from '$lib/enums/Language'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Super basic implementation to get a string for the given key and language
|
|
6
|
+
* @param {string} key Key of the wanted translation
|
|
7
|
+
* @param {LanguageCode} language Language code
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export function t(key, language = getLanguage()) {
|
|
11
|
+
// @ts-ignore It's ok if a key is not found.
|
|
12
|
+
return translations[key]?.[language] || key
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @returns {LanguageCode}
|
|
17
|
+
*/
|
|
18
|
+
export function getLanguage() {
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
const configLanguage = window.PlayPilotLinkInjections?.language
|
|
21
|
+
const languageCodes = /** @type {string[]} */ (Object.values(Language))
|
|
22
|
+
|
|
23
|
+
if (configLanguage) {
|
|
24
|
+
if (languageCodes.includes(configLanguage)) return configLanguage
|
|
25
|
+
|
|
26
|
+
console.warn(`PlayPilot Link Injections: ${configLanguage} is not an accepted language`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const documentLanguage = document.querySelector('html')?.getAttribute('lang')
|
|
30
|
+
|
|
31
|
+
if (documentLanguage && languageCodes.includes(documentLanguage)) {
|
|
32
|
+
return /** @type {LanguageCode} */ (documentLanguage)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return Language.English
|
|
36
|
+
}
|
package/src/lib/meta.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { getLinkInjectionsParentElement } from './linkInjection'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns meta data to be included with injections.
|
|
5
|
+
* @returns {ArticleMetaData}
|
|
6
|
+
*/
|
|
7
|
+
export function getPageMetaData() {
|
|
8
|
+
const parent = getLinkInjectionsParentElement()
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
heading: getPageHeading(parent),
|
|
12
|
+
modified_time: getPageModifiedTime(parent),
|
|
13
|
+
published_time: getPageModifiedTime(parent),
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns the most relevant `h1` content on the page.
|
|
19
|
+
* A page should only ever contain exactly 1 `h1` tag, but we don't control these things.
|
|
20
|
+
* @param {HTMLElement} parent
|
|
21
|
+
* @returns {string | null}
|
|
22
|
+
*/
|
|
23
|
+
export function getPageHeading(parent) {
|
|
24
|
+
const heading = parent.querySelector('h1') || document.querySelector('h1')
|
|
25
|
+
|
|
26
|
+
return heading?.innerText.trim() || null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the datetime for the article, intended to be the last time the article was modified.
|
|
31
|
+
* @param {HTMLElement} parent
|
|
32
|
+
* @returns {string | null}
|
|
33
|
+
*/
|
|
34
|
+
export function getPageModifiedTime(parent) {
|
|
35
|
+
const element =
|
|
36
|
+
/** @type {HTMLElement | null} */
|
|
37
|
+
(document.querySelector('meta[property*="modified_time"]') ||
|
|
38
|
+
parent.querySelector('[itemprop="dateModified"]') ||
|
|
39
|
+
document.querySelector('[itemprop="dateModified"]') || null)
|
|
40
|
+
|
|
41
|
+
const datetime = element?.getAttribute('content') || element?.getAttribute('datetime') || element?.innerText
|
|
42
|
+
|
|
43
|
+
if (!datetime) return null
|
|
44
|
+
|
|
45
|
+
return getDatetime(datetime)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns the datetime for the article, intended to be the date the article was first published
|
|
50
|
+
* @param {HTMLElement} parent
|
|
51
|
+
* @returns {string | null}
|
|
52
|
+
*/
|
|
53
|
+
export function getPagePublishedTime(parent) {
|
|
54
|
+
const element =
|
|
55
|
+
/** @type {HTMLElement | null} */
|
|
56
|
+
(document.querySelector('meta[property*="published_time"]') ||
|
|
57
|
+
parent.querySelector('[itemprop="datePublished"]') ||
|
|
58
|
+
document.querySelector('[itemprop="datePublished"]') ||
|
|
59
|
+
parent.querySelector('time') ||
|
|
60
|
+
document.querySelector('time') ||
|
|
61
|
+
parent.querySelector('[datetime]') ||
|
|
62
|
+
document.querySelector('[datetime]') || null)
|
|
63
|
+
|
|
64
|
+
const datetime = element?.getAttribute('content') || element?.getAttribute('datetime') || element?.innerText
|
|
65
|
+
|
|
66
|
+
if (!datetime) return null
|
|
67
|
+
|
|
68
|
+
return getDatetime(datetime)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get datetime string as ISO datetime string
|
|
73
|
+
* @param {string} datetime
|
|
74
|
+
* @returns {string | null}
|
|
75
|
+
*/
|
|
76
|
+
function getDatetime(datetime) {
|
|
77
|
+
try {
|
|
78
|
+
return new Date(datetime).toISOString()
|
|
79
|
+
} catch {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/main.js
CHANGED
|
@@ -8,9 +8,10 @@ window.PlayPilotLinkInjections = {
|
|
|
8
8
|
token: '',
|
|
9
9
|
editorial_token: '',
|
|
10
10
|
selector: '',
|
|
11
|
+
language: null,
|
|
11
12
|
app: null,
|
|
12
13
|
|
|
13
|
-
initialize(config = { token: '', selector: '', editorial_token: '' }) {
|
|
14
|
+
initialize(config = { token: '', selector: '', language: null, editorial_token: '' }) {
|
|
14
15
|
if (!config.token) {
|
|
15
16
|
console.error('An API token is required.')
|
|
16
17
|
return
|
|
@@ -19,6 +20,7 @@ window.PlayPilotLinkInjections = {
|
|
|
19
20
|
this.token = config.token
|
|
20
21
|
this.editorial_token = config.editorial_token
|
|
21
22
|
this.selector = config.selector
|
|
23
|
+
this.language = config.language
|
|
22
24
|
|
|
23
25
|
if (this.app) this.destroy()
|
|
24
26
|
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
<div>
|
|
23
23
|
{#key Math.random()}
|
|
24
24
|
<article use:noClass>
|
|
25
|
+
<h1 use:noClass>Some heading</h1>
|
|
26
|
+
<time datetime="13:00">1 hour ago</time>
|
|
25
27
|
<p use:noClass>Following the success of John M. Chu's 2018 romantic-comedy Crazy Rich Asians, Quan was inspired to return to acting. He first scored a supporting role in the Netflix movie Finding 'Ohana, before securing a starring role in the absurdist comedy-drama Everything Everywhere all At Once. A critical and commercial success, the film earned $143 million against a budget of $14-25 million, and saw Quan win the Academy Award for Best Supporting Actor. Following his win, Quan struggled to choose projects he was satisfied with, passing on an action-comedy three times, before finally taking his first leading role in it, following advice from Spielberg.</p>
|
|
26
28
|
<p use:noClass>In an interview with Epire & Magazine, Quan reveals he quested starring in Love Hurts, which sees him in the leading role of a former assassin turned successful realtor, whose past returns when his brother attempts to hunt him down. The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody, and Quan discussed how he was reluctant to take the part due to his conditioned beliefs about how an action hero should look. But he reveals that he changed his mind following a meeting with Spielberg, who convinced him to do it.</p>
|
|
27
29
|
</article>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
3
|
+
import { t } from '$lib/localization'
|
|
3
4
|
import { track } from '$lib/tracking'
|
|
4
5
|
|
|
5
6
|
/** @type {{ linkInjections: LinkInjection[], onclickmodal?: (linkInjection: LinkInjection) => void }} */
|
|
@@ -34,18 +35,18 @@
|
|
|
34
35
|
<span data-playpilot-injection-key={key}>
|
|
35
36
|
{#if playlinks.length}
|
|
36
37
|
{#if after_article_style === 'modal_button'}
|
|
37
|
-
"{title}"
|
|
38
|
+
"{title}" {t('Is Available To Stream')}
|
|
38
39
|
|
|
39
40
|
<span>
|
|
40
41
|
<button onclick={() => openModal(/** @type {TitleData} */ (title_details), linkInjection)}>
|
|
41
|
-
View
|
|
42
|
+
{t('View Streaming Options')}
|
|
42
43
|
</button>
|
|
43
44
|
</span>
|
|
44
45
|
{:else}
|
|
45
|
-
"{title}"
|
|
46
|
+
"{title}" {t('Is Available To Stream On')}
|
|
46
47
|
{#each playlinks as { name, url }, i}
|
|
47
48
|
{#if i}
|
|
48
|
-
{i === playlinks.length - 1 ?
|
|
49
|
+
{i === playlinks.length - 1 ? `, ${t('And')}` : ','}
|
|
49
50
|
{/if}
|
|
50
51
|
|
|
51
52
|
<a onclick={() => onclick(/** @type {TitleData} */ (title_details), name)} href={url} target="_blank">
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
{/if}
|
|
56
57
|
<br>
|
|
57
58
|
{:else}
|
|
58
|
-
"{title}"
|
|
59
|
+
"{title}" {t('Title Unavailable Suffix')} <br>
|
|
59
60
|
{/if}
|
|
60
61
|
</span>
|
|
61
62
|
{/each}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import genreData from '$lib/genres.json'
|
|
3
|
+
import { t } from '$lib/localization'
|
|
3
4
|
|
|
4
5
|
/** @type {{ genres: string[] }} */
|
|
5
6
|
const { genres } = $props()
|
|
@@ -9,9 +10,13 @@
|
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
12
|
{#each shownGenres as genre}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
{@const genreName = genreData.find(g => g.slug === genre)?.name}
|
|
14
|
+
|
|
15
|
+
{#if genreName}
|
|
16
|
+
<div class="genre">
|
|
17
|
+
{t(genreName)}
|
|
18
|
+
</div>
|
|
19
|
+
{/if}
|
|
15
20
|
{/each}
|
|
16
21
|
|
|
17
22
|
{#if !expanded && genres.length > 1}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
3
|
+
import { t } from '$lib/localization'
|
|
3
4
|
import { mergePlaylinks } from '$lib/playlink'
|
|
4
5
|
import { track } from '$lib/tracking'
|
|
5
6
|
import IconContinue from './Icons/IconContinue.svelte'
|
|
@@ -15,10 +16,10 @@
|
|
|
15
16
|
const mergedPlaylink = $derived(mergePlaylinks(filteredPlaylinks))
|
|
16
17
|
|
|
17
18
|
const categoryStrings = {
|
|
18
|
-
SVOD: 'Stream',
|
|
19
|
-
BUY: 'Buy',
|
|
20
|
-
RENT: 'Rent',
|
|
21
|
-
TVOD: 'Rent
|
|
19
|
+
SVOD: t('Stream'),
|
|
20
|
+
BUY: t('Buy'),
|
|
21
|
+
RENT: t('Rent'),
|
|
22
|
+
TVOD: t('Rent Or Buy'),
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
}
|
|
30
31
|
</script>
|
|
31
32
|
|
|
32
|
-
<h2>Where
|
|
33
|
+
<h2>{t('Where To Stream Online')}</h2>
|
|
33
34
|
|
|
34
35
|
<div class="playlinks" class:list>
|
|
35
36
|
{#each mergedPlaylink as { name, url, logo_url, extra_info: { category } }}
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
|
|
39
40
|
<div>
|
|
40
41
|
<span class="name">{name}</span>
|
|
41
|
-
<div class="category">{categoryStrings[category] || 'Stream'}</div>
|
|
42
|
+
<div class="category">{categoryStrings[category] || t('Stream')}</div>
|
|
42
43
|
</div>
|
|
43
44
|
|
|
44
45
|
{#if list}
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
|
|
52
53
|
{#if !playlinks.length}
|
|
53
54
|
<div class="playlink empty">
|
|
54
|
-
|
|
55
|
+
{t('Title Unavailable')}
|
|
55
56
|
</div>
|
|
56
57
|
{/if}
|
|
57
58
|
</div>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import Description from './Description.svelte'
|
|
5
5
|
import Participants from './Participants.svelte'
|
|
6
6
|
import IconIMDb from './Icons/IconIMDb.svelte'
|
|
7
|
+
import { t } from '$lib/localization'
|
|
7
8
|
|
|
8
9
|
/** @type {{ title: TitleData, small?: boolean, compact?: boolean }} */
|
|
9
10
|
const { title, small = false, compact = false } = $props()
|
|
@@ -28,10 +29,10 @@
|
|
|
28
29
|
<Genres genres={title.genres} />
|
|
29
30
|
|
|
30
31
|
<div>{title.year}</div>
|
|
31
|
-
<div class="capitalize">{title.type}</div>
|
|
32
|
+
<div class="capitalize">{t(`Type: ${title.type}`)}</div>
|
|
32
33
|
|
|
33
34
|
{#if title.length}
|
|
34
|
-
<div>{title.length}
|
|
35
|
+
<div>{title.length} {t('Minutes')}</div>
|
|
35
36
|
{/if}
|
|
36
37
|
</div>
|
|
37
38
|
</header>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest'
|
|
2
|
+
import { getLanguage, t } from '$lib/localization'
|
|
3
|
+
import { Language } from '$lib/enums/Language'
|
|
4
|
+
import { translations } from '$lib/data/translations'
|
|
5
|
+
|
|
6
|
+
describe('localization.js', () => {
|
|
7
|
+
describe('t', () => {
|
|
8
|
+
it('Should return the value for the given language and key', () => {
|
|
9
|
+
expect(t(Object.keys(translations)[0], Language.English)).toBe(Object.values(translations)[0][Language.English])
|
|
10
|
+
expect(t(Object.keys(translations)[0], Language.Swedish)).toBe(Object.values(translations)[0][Language.Swedish])
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('Should return the key if key is not found in the object', () => {
|
|
14
|
+
expect(t('Some Random Key')).toBe('Some Random Key')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('Should return the key if key is not found for the given language', () => {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
expect(t(Object.keys(translations)[0], 'Some Invalid Language')).toBe(Object.keys(translations)[0])
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('getLanguage', () => {
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
// @ts-expect-error
|
|
26
|
+
window.PlayPilotLinkInjections = {}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('Should return the language given in config object before document language', () => {
|
|
30
|
+
// @ts-expect-error
|
|
31
|
+
window.PlayPilotLinkInjections = { language: 'sv-SE' }
|
|
32
|
+
|
|
33
|
+
const html = /** @type {HTMLElement} */ (document.querySelector('html'))
|
|
34
|
+
html.setAttribute('lang', 'en-US')
|
|
35
|
+
|
|
36
|
+
expect(getLanguage()).toBe('sv-SE')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Should return the language given as document language when config language is invalid', () => {
|
|
40
|
+
// @ts-expect-error
|
|
41
|
+
window.PlayPilotLinkInjections = { language: 'no' }
|
|
42
|
+
|
|
43
|
+
const html = /** @type {HTMLElement} */ (document.querySelector('html'))
|
|
44
|
+
html.setAttribute('lang', 'sv-SE')
|
|
45
|
+
|
|
46
|
+
expect(getLanguage()).toBe('sv-SE')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('Should return the language given as document language when no config object is set', () => {
|
|
50
|
+
const html = /** @type {HTMLElement} */ (document.querySelector('html'))
|
|
51
|
+
html.setAttribute('lang', 'sv-SE')
|
|
52
|
+
|
|
53
|
+
expect(getLanguage()).toBe('sv-SE')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('Should return the default language when document language is invalid', () => {
|
|
57
|
+
const html = /** @type {HTMLElement} */ (document.querySelector('html'))
|
|
58
|
+
html.setAttribute('lang', 'no')
|
|
59
|
+
|
|
60
|
+
expect(getLanguage()).toBe('en-US')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('Should return the default language no document language or config language is given', () => {
|
|
64
|
+
expect(getLanguage()).toBe('en-US')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
})
|