Commit Graph

3 Commits

Author SHA1 Message Date
louispaulb
2c47d3269e feat(campaigns/translate): switch from literal to copywriter-mode AI prompt
User feedback: previous prompt produced robotic word-for-word output that
lost the marketing impact. The system prompt was too restrictive on
preservation, suppressing Gemini's natural rephrasing ability.

Rewrote the system prompt with three structural changes:

1. FRAME shifted from "translator" to "senior marketing copywriter"
   The opening line now says "You are NOT translating words — you are
   rewriting marketing copy that lands the same way for a different
   audience." This unlocks idiomatic rephrasing, sentence reorganization,
   active/passive switching, and cultural metaphor adaptation.

2. FEW-SHOT EXAMPLES showing the desired style
   4 FR→EN pairs in the prompt itself, with explicit "NOT: <literal>"
   anti-examples to show what to avoid:
     • "loyauté envers l'achat local" → "keeping it local"
       (not "loyalty to local shopping")
     • "connexions stables et relations durables" →
       "steady connections — both the fiber kind and the human kind"
       (not "stable connections and lasting relationships")
     • "On est juste à côté" → "We're right next door"
     • "Avec l'arrivée de l'été" → "Summer's here"
   These ground Gemini in the brand voice with concrete examples.

3. TONE constraint explicit
   "Warm, conversational, slightly playful. Like a neighbor explaining
   something — never corporate, never stiff." Use of contractions
   ("we're", "you'll") encouraged.

Plus: temperature bumped from 0.2 → 0.7 so Gemini actually exercises
creative rephrasing instead of staying glued to source word order.

Structural preservation rules (HTML, Mustache vars, brand names, emojis,
URLs, technical values like "3.5 Gbit/s") kept as HARD CONSTRAINTS but
clearly separated from the creative freedom on text content.

Live re-translation of gift-email-fr → gift-email-en applied:
  • 51s response time (similar to literal version)
  • 35,934 → 36,067 bytes (slight expansion, normal for EN)
  • Output markers confirm idiomatic phrasing landed:
    "Thanks for keeping it local", "steady connections — both kinds",
    "right next door", "lending a hand", "Summer's here"
  • Mustache vars + brand names + HTML preserved (verified)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 07:29:16 -04:00
louispaulb
d716e69ef6 feat(campaigns/templates): mirror user's FR edits to EN + drop legacy .mjml
User edited gift-email-fr in the Unlayer editor with richer marketing
copy (loyalty thanks, brand manifesto, 3.5 Gbit/s upsell, helpful CTA).
Mirror those edits to the EN template via a one-shot translation script
so the bilingual pair stays in sync for the next campaign send.

Translation strategy: plain-string find/replace mapping with FR
phrases in longest-first order to avoid partial matches. Applied to
BOTH the rendered .html (what the recipient sees) AND the .json
(Unlayer design tree — so re-opening the EN editor preserves the
matching structure).

Mapping coverage:
  • Intro paragraphs (greeting, gift announcement, loyalty thanks,
    brand manifesto, speed upsell, "we're around the corner")
  • Offer info pill (amount, instant activation, commitment)
  • CTA button labels (Activer → Redeem, Choisir → Pick)
  • Prorata refund disclaimer
  • Option 2 "do nothing" text
  • Signature ("Merci de faire rouler" → "Thanks for helping...thrive")
  • Footer contact info + "Tous droits réservés" → "All rights reserved"
  • <html lang="fr"> → <html lang="en">

23/28 translation rules matched; the 5 unused ones were for legacy
phrasing not present in the user's latest save (e.g. the old "Tu
choisis local" line that was replaced by the current intro).

Also: drop the obsolete .mjml source files. Now that Unlayer is the
canonical editor, the MJML→HTML compile pipeline is no longer used
on save (Unlayer outputs HTML directly). The .mjml files were stale
copies from the previous MJML-based editor. Removed from disk on
prod and from git history; rollback via git revert if needed.

Verified live: GET /campaigns/templates/gift-email-en returns the
translated content (9 EN markers detected in HTML). Test-send to
louis@targo.ca queued via Mailjet for visual QA.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 07:07:05 -04:00
louispaulb
448e62177e feat(campaigns): convert existing HTML templates to Unlayer JSON designs
Solve the "editor starts blank" problem by writing a one-time converter
that wraps each compiled .html template into a minimal Unlayer design
JSON (one Custom HTML block containing the entire body content). On
next editor load, Unlayer reads .json and renders the template in the
canvas — instant visual fidelity without manual reconstruction.

Strategy choice: Unlayer's "Import HTML" is a Pro-only feature. Building
a real HTML→Unlayer-blocks parser is several days of work. The minimal-
viable conversion (1 row + 1 Custom HTML block) gets the user 90% there
immediately:

  • Canvas shows the template visually (Unlayer renders the HTML)
  • Variables ({{firstname}}, {{gift_url}}, etc.) preserved as text
  • User can edit the HTML directly via the block's side panel
  • User can incrementally REPLACE the HTML block with native Unlayer
    blocks (Text, Image, Button) for any section they want decomposed —
    on their own schedule, not blocking the campaign send

New file: services/targo-hub/scripts/convert-html-to-unlayer.js
  • CLI: node scripts/convert-html-to-unlayer.js <template-name>
  • Reads templates/<name>.html, extracts <body> inner content, detects
    preheader from a hidden <div style="display:none">, builds Unlayer
    design JSON with brand-appropriate body.values (Targo Green link
    color #00C853, Plus Jakarta Sans font, F5FAF7 page background).
  • Backs up existing .json before overwriting.

Generated outputs (committed):
  templates/gift-email-fr.json — 34 KB (30 KB inner HTML + Unlayer chrome)
  templates/gift-email-en.json — 33 KB

Live verification: GET /campaigns/templates/gift-email-fr now returns
{ design: {...Unlayer JSON...} } alongside html. The editor's
onReady() callback in TemplateEditorPage detects data.design and calls
editor.loadDesign(design) → canvas populated immediately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 06:22:47 -04:00