video-ready.js 4.14 KB
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('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;')
}