video-ready.js
4.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { EmailError } from '../map-error.js'
const COPY = Object.freeze({
'zh-CN': {
subject: title => `您的 VOKO 视频已生成:${title}`,
heading: '您的视频已生成',
intro: '视频和多语言文案均已处理完成。',
title: '视频标题',
summary: '视频简介',
watch: '观看视频',
footer: '感谢您使用 VOKO。',
},
en: {
subject: title => `Your VOKO video is ready: ${title}`,
heading: 'Your video is ready',
intro: 'Your video and localized details are ready.',
title: 'Video title',
summary: 'Video description',
watch: 'Watch your video',
footer: 'Thank you for using VOKO.',
},
ja: {
subject: title => `VOKOの動画が完成しました:${title}`,
heading: '動画が完成しました',
intro: '動画と多言語コンテンツの準備が完了しました。',
title: '動画タイトル',
summary: '動画の紹介',
watch: '動画を見る',
footer: 'VOKOをご利用いただきありがとうございます。',
},
ko: {
subject: title => `VOKO 동영상이 준비되었습니다: ${title}`,
heading: '동영상이 준비되었습니다',
intro: '동영상과 다국어 콘텐츠 처리가 모두 완료되었습니다.',
title: '동영상 제목',
summary: '동영상 소개',
watch: '동영상 보기',
footer: 'VOKO를 이용해 주셔서 감사합니다.',
},
es: {
subject: title => `Tu video de VOKO está listo: ${title}`,
heading: 'Tu video está listo',
intro: 'Tu video y sus textos localizados ya están listos.',
title: 'Título del video',
summary: 'Descripción del video',
watch: 'Ver tu video',
footer: 'Gracias por usar VOKO.',
},
pt: {
subject: title => `Seu vídeo do VOKO está pronto: ${title}`,
heading: 'Seu vídeo está pronto',
intro: 'Seu vídeo e os textos localizados estão prontos.',
title: 'Título do vídeo',
summary: 'Descrição do vídeo',
watch: 'Assistir ao vídeo',
footer: 'Obrigado por usar o VOKO.',
},
})
/**
* @param {{ locale: string, videoTitle: string, videoSummary: string, videoUrl: string }} data
*/
export function render(data) {
if (!data || typeof data !== 'object') fail('data is required')
const copy = COPY[data.locale]
if (!copy) fail(`unsupported locale: ${String(data.locale)}`)
const videoTitle = requiredText(data.videoTitle, 'videoTitle')
const videoSummary = requiredText(data.videoSummary, 'videoSummary')
const videoUrl = requiredUrl(data.videoUrl)
const subject = copy.subject(videoTitle)
const text = [
copy.heading,
'',
copy.intro,
'',
`${copy.title}: ${videoTitle}`,
`${copy.summary}: ${videoSummary}`,
`${copy.watch}: ${videoUrl}`,
'',
copy.footer,
].join('\n')
const html = [
'<!doctype html><html><body style="font-family:Arial,Helvetica,sans-serif;color:#1a1a1a;line-height:1.6;">',
`<h2 style="margin:0 0 16px;">${escapeHtml(copy.heading)}</h2>`,
`<p style="margin:0 0 16px;">${escapeHtml(copy.intro)}</p>`,
`<p style="margin:0 0 8px;"><strong>${escapeHtml(copy.title)}:</strong> ${escapeHtml(videoTitle)}</p>`,
`<p style="margin:0 0 16px;"><strong>${escapeHtml(copy.summary)}:</strong> ${escapeHtml(videoSummary)}</p>`,
`<p style="margin:0 0 16px;"><a href="${escapeHtml(videoUrl)}">${escapeHtml(copy.watch)}</a></p>`,
`<p style="margin:0;color:#666;">${escapeHtml(copy.footer)}</p>`,
'</body></html>',
].join('')
return { subject, html, text }
}
function requiredText(value, field) {
if (typeof value === 'string' && value.trim().length > 0) return value.trim()
fail(`missing field "${field}"`)
}
function requiredUrl(value) {
const url = requiredText(value, 'videoUrl')
if (new URL(url).protocol === 'https:') return url
fail('videoUrl must use https')
}
function fail(message) {
throw new EmailError({ provider: 'template', reason: 'badTemplate', retryable: false, message: `video-ready: ${message}` })
}
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''')
}