@contractspec/lib.content-gen 2.4.0 → 2.5.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.
Files changed (64) hide show
  1. package/dist/browser/generators/blog.js +943 -12
  2. package/dist/browser/generators/email.js +952 -14
  3. package/dist/browser/generators/index.js +1005 -46
  4. package/dist/browser/generators/landing-page.js +938 -15
  5. package/dist/browser/generators/social.js +926 -5
  6. package/dist/browser/i18n/catalogs/en.js +276 -0
  7. package/dist/browser/i18n/catalogs/es.js +276 -0
  8. package/dist/browser/i18n/catalogs/fr.js +276 -0
  9. package/dist/browser/i18n/catalogs/index.js +826 -0
  10. package/dist/browser/i18n/index.js +937 -0
  11. package/dist/browser/i18n/keys.js +85 -0
  12. package/dist/browser/i18n/locale.js +13 -0
  13. package/dist/browser/i18n/messages.js +838 -0
  14. package/dist/browser/index.js +1020 -50
  15. package/dist/browser/seo/index.js +933 -4
  16. package/dist/browser/seo/optimizer.js +933 -4
  17. package/dist/generators/blog.d.ts +1 -0
  18. package/dist/generators/blog.js +943 -12
  19. package/dist/generators/email.d.ts +1 -0
  20. package/dist/generators/email.js +952 -14
  21. package/dist/generators/index.js +1005 -46
  22. package/dist/generators/landing-page.d.ts +1 -0
  23. package/dist/generators/landing-page.js +938 -15
  24. package/dist/generators/social.d.ts +1 -0
  25. package/dist/generators/social.js +926 -5
  26. package/dist/i18n/catalogs/en.d.ts +8 -0
  27. package/dist/i18n/catalogs/en.js +277 -0
  28. package/dist/i18n/catalogs/es.d.ts +6 -0
  29. package/dist/i18n/catalogs/es.js +277 -0
  30. package/dist/i18n/catalogs/fr.d.ts +6 -0
  31. package/dist/i18n/catalogs/fr.js +277 -0
  32. package/dist/i18n/catalogs/index.d.ts +8 -0
  33. package/dist/i18n/catalogs/index.js +827 -0
  34. package/dist/i18n/i18n.test.d.ts +1 -0
  35. package/dist/i18n/index.d.ts +29 -0
  36. package/dist/i18n/index.js +938 -0
  37. package/dist/i18n/keys.d.ts +244 -0
  38. package/dist/i18n/keys.js +86 -0
  39. package/dist/i18n/locale.d.ts +8 -0
  40. package/dist/i18n/locale.js +14 -0
  41. package/dist/i18n/messages.d.ts +14 -0
  42. package/dist/i18n/messages.js +839 -0
  43. package/dist/index.js +1020 -50
  44. package/dist/node/generators/blog.js +943 -12
  45. package/dist/node/generators/email.js +952 -14
  46. package/dist/node/generators/index.js +1005 -46
  47. package/dist/node/generators/landing-page.js +938 -15
  48. package/dist/node/generators/social.js +926 -5
  49. package/dist/node/i18n/catalogs/en.js +276 -0
  50. package/dist/node/i18n/catalogs/es.js +276 -0
  51. package/dist/node/i18n/catalogs/fr.js +276 -0
  52. package/dist/node/i18n/catalogs/index.js +826 -0
  53. package/dist/node/i18n/index.js +937 -0
  54. package/dist/node/i18n/keys.js +85 -0
  55. package/dist/node/i18n/locale.js +13 -0
  56. package/dist/node/i18n/messages.js +838 -0
  57. package/dist/node/index.js +1020 -50
  58. package/dist/node/seo/index.js +933 -4
  59. package/dist/node/seo/optimizer.js +933 -4
  60. package/dist/seo/index.js +933 -4
  61. package/dist/seo/optimizer.d.ts +10 -1
  62. package/dist/seo/optimizer.js +933 -4
  63. package/dist/types.d.ts +2 -0
  64. package/package.json +145 -5
package/dist/index.js CHANGED
@@ -1,13 +1,933 @@
1
1
  // @bun
2
+ // src/i18n/catalogs/en.ts
3
+ import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
4
+ var enMessages = defineTranslation({
5
+ meta: {
6
+ key: "content-gen.messages",
7
+ version: "1.0.0",
8
+ domain: "content-gen",
9
+ description: "All user-facing, LLM-facing, and developer-facing strings for the content-gen package",
10
+ owners: ["platform"],
11
+ stability: "experimental"
12
+ },
13
+ locale: "en",
14
+ fallback: "en",
15
+ messages: {
16
+ "prompt.blog.system": {
17
+ value: "You are a product marketing writer. Produce JSON with title, subtitle, intro, sections[].heading/body/bullets, outro.",
18
+ description: "Blog generator LLM system prompt"
19
+ },
20
+ "prompt.email.system": {
21
+ value: "Draft product marketing email as JSON {subject, previewText, body, cta}.",
22
+ description: "Email generator LLM system prompt"
23
+ },
24
+ "prompt.landing.system": {
25
+ value: "Write JSON landing page copy with hero/highlights/socialProof/faq arrays.",
26
+ description: "Landing page generator LLM system prompt"
27
+ },
28
+ "prompt.social.system": {
29
+ value: "Create JSON array of social posts for twitter/linkedin/threads with body, hashtags, cta.",
30
+ description: "Social post generator LLM system prompt"
31
+ },
32
+ "blog.intro": {
33
+ value: "Operators like {role} teams face {problems}. {title} changes that by {summary}.",
34
+ description: "Blog post intro paragraph template",
35
+ placeholders: [
36
+ { name: "role", type: "string" },
37
+ { name: "problems", type: "string" },
38
+ { name: "title", type: "string" },
39
+ { name: "summary", type: "string" }
40
+ ]
41
+ },
42
+ "blog.heading.whyNow": {
43
+ value: "Why now",
44
+ description: "Blog section heading: why now"
45
+ },
46
+ "blog.heading.whatYouGet": {
47
+ value: "What you get",
48
+ description: "Blog section heading: what you get"
49
+ },
50
+ "blog.heading.proofItWorks": {
51
+ value: "Proof it works",
52
+ description: "Blog section heading: proof it works"
53
+ },
54
+ "blog.body.whatYouGet": {
55
+ value: "A focused stack built for policy-safe automation.",
56
+ description: "Blog section body: what you get"
57
+ },
58
+ "blog.body.proofItWorks": {
59
+ value: "Teams using the blueprint report measurable wins.",
60
+ description: "Blog section body: proof it works"
61
+ },
62
+ "blog.metric.launchWorkflows": {
63
+ value: "Launch workflows in minutes",
64
+ description: "Default metric: launch workflows"
65
+ },
66
+ "blog.metric.cutReviewTime": {
67
+ value: "Cut review time by 60%",
68
+ description: "Default metric: cut review time"
69
+ },
70
+ "blog.outro.default": {
71
+ value: "Ready to see it live? Spin up a sandbox in under 5 minutes.",
72
+ description: "Default blog outro / call to action"
73
+ },
74
+ "blog.whyNow": {
75
+ value: "{audience} teams are stuck with {pains}. {title} delivers guardrails without slowing shipping.",
76
+ description: "Blog why-now section body template",
77
+ placeholders: [
78
+ { name: "audience", type: "string" },
79
+ { name: "pains", type: "string" },
80
+ { name: "title", type: "string" }
81
+ ]
82
+ },
83
+ "blog.audience.industry": {
84
+ value: " in {industry}",
85
+ description: "Audience industry suffix for blog why-now",
86
+ placeholders: [{ name: "industry", type: "string" }]
87
+ },
88
+ "email.subject.announcement.launch": {
89
+ value: "Launch: {title}",
90
+ description: "Announcement email subject variant: launch",
91
+ placeholders: [{ name: "title", type: "string" }]
92
+ },
93
+ "email.subject.announcement.live": {
94
+ value: "{title} is live",
95
+ description: "Announcement email subject variant: live",
96
+ placeholders: [{ name: "title", type: "string" }]
97
+ },
98
+ "email.subject.announcement.new": {
99
+ value: "New: {title}",
100
+ description: "Announcement email subject variant: new",
101
+ placeholders: [{ name: "title", type: "string" }]
102
+ },
103
+ "email.subject.onboarding.getStarted": {
104
+ value: "Get started with {title}",
105
+ description: "Onboarding email subject variant: get started",
106
+ placeholders: [{ name: "title", type: "string" }]
107
+ },
108
+ "email.subject.onboarding.guide": {
109
+ value: "Your {title} guide",
110
+ description: "Onboarding email subject variant: guide",
111
+ placeholders: [{ name: "title", type: "string" }]
112
+ },
113
+ "email.subject.nurture.speeds": {
114
+ value: "How {title} speeds ops",
115
+ description: "Nurture email subject variant: speeds ops",
116
+ placeholders: [{ name: "title", type: "string" }]
117
+ },
118
+ "email.subject.nurture.proof": {
119
+ value: "Proof {title} works",
120
+ description: "Nurture email subject variant: proof",
121
+ placeholders: [{ name: "title", type: "string" }]
122
+ },
123
+ "email.subject.fallback": {
124
+ value: "{title} update",
125
+ description: "Fallback email subject line",
126
+ placeholders: [{ name: "title", type: "string" }]
127
+ },
128
+ "email.preview.defaultWin": {
129
+ value: "ship faster without policy gaps",
130
+ description: "Default win text for email preview"
131
+ },
132
+ "email.preview.template": {
133
+ value: "See how teams {win}.",
134
+ description: "Email preview text template",
135
+ placeholders: [{ name: "win", type: "string" }]
136
+ },
137
+ "email.body.greeting": {
138
+ value: "Hi there,",
139
+ description: "Email body greeting"
140
+ },
141
+ "email.body.reasons": {
142
+ value: "Top reasons teams adopt {title}:",
143
+ description: "Email body reasons intro",
144
+ placeholders: [{ name: "title", type: "string" }]
145
+ },
146
+ "email.cta.sandbox": {
147
+ value: "Spin up a sandbox",
148
+ description: "Default CTA: spin up a sandbox"
149
+ },
150
+ "email.cta.explore": {
151
+ value: "Explore the sandbox",
152
+ description: "Default CTA: explore the sandbox"
153
+ },
154
+ "email.hook.announcement": {
155
+ value: "{title} is live. {summary}",
156
+ description: "Announcement variant hook",
157
+ placeholders: [
158
+ { name: "title", type: "string" },
159
+ { name: "summary", type: "string" }
160
+ ]
161
+ },
162
+ "email.hook.onboarding": {
163
+ value: "Here is your next step to unlock {title}.",
164
+ description: "Onboarding variant hook",
165
+ placeholders: [{ name: "title", type: "string" }]
166
+ },
167
+ "email.hook.nurture": {
168
+ value: "Operators like {role} keep asking how to automate policy checks. Here is what works.",
169
+ description: "Nurture variant hook",
170
+ placeholders: [{ name: "role", type: "string" }]
171
+ },
172
+ "landing.eyebrow.defaultIndustry": {
173
+ value: "Operations",
174
+ description: "Default industry for landing page eyebrow"
175
+ },
176
+ "landing.eyebrow.template": {
177
+ value: "{industry} teams",
178
+ description: "Landing page eyebrow template",
179
+ placeholders: [{ name: "industry", type: "string" }]
180
+ },
181
+ "landing.cta.primary": {
182
+ value: "Launch a sandbox",
183
+ description: "Landing page primary CTA"
184
+ },
185
+ "landing.cta.secondary": {
186
+ value: "View docs",
187
+ description: "Landing page secondary CTA"
188
+ },
189
+ "landing.highlight.policySafe": {
190
+ value: "Policy-safe by default",
191
+ description: "Landing page highlight heading 1"
192
+ },
193
+ "landing.highlight.autoAdapts": {
194
+ value: "Auto-adapts per tenant",
195
+ description: "Landing page highlight heading 2"
196
+ },
197
+ "landing.highlight.launchReady": {
198
+ value: "Launch-ready in days",
199
+ description: "Landing page highlight heading 3"
200
+ },
201
+ "landing.highlight.fallback": {
202
+ value: "Key capability",
203
+ description: "Fallback highlight heading"
204
+ },
205
+ "landing.socialProof.heading": {
206
+ value: "Teams using ContractSpec",
207
+ description: "Social proof section heading"
208
+ },
209
+ "landing.socialProof.defaultQuote": {
210
+ value: '"We ship compliant workflows 5x faster while cutting ops toil in half."',
211
+ description: "Default social proof quote"
212
+ },
213
+ "landing.faq.policiesEnforced.heading": {
214
+ value: "How does this keep policies enforced?",
215
+ description: "FAQ heading: policies enforced"
216
+ },
217
+ "landing.faq.policiesEnforced.body": {
218
+ value: "All workflows compile from TypeScript specs and pass through PDP checks before execution, so no shadow logic slips through.",
219
+ description: "FAQ body: policies enforced"
220
+ },
221
+ "landing.faq.existingStack.heading": {
222
+ value: "Will it fit our existing stack?",
223
+ description: "FAQ heading: existing stack"
224
+ },
225
+ "landing.faq.existingStack.body": {
226
+ value: "Runtime adapters plug into REST, GraphQL, or MCP. Integrations stay vendor agnostic.",
227
+ description: "FAQ body: existing stack"
228
+ },
229
+ "landing.faq.compliance.heading": {
230
+ value: "What about compliance requirements?",
231
+ description: "FAQ heading: compliance requirements"
232
+ },
233
+ "social.cta.linkedin": {
234
+ value: "Book a 15-min run-through",
235
+ description: "LinkedIn post default CTA"
236
+ },
237
+ "social.cta.twitter": {
238
+ value: "\u2192 contractspec.io/sandbox",
239
+ description: "Twitter post default CTA"
240
+ },
241
+ "social.body.threads": {
242
+ value: "Ops + policy can move fast. {title} automates guardrails so teams ship daily.",
243
+ description: "Threads post body template",
244
+ placeholders: [{ name: "title", type: "string" }]
245
+ },
246
+ "social.body.twitter.connector": {
247
+ value: " in <60s. ",
248
+ description: "Twitter body connector between solutions"
249
+ },
250
+ "seo.metaTitle": {
251
+ value: "{title} | ContractSpec",
252
+ description: "SEO meta title template",
253
+ placeholders: [{ name: "title", type: "string" }]
254
+ },
255
+ "seo.metaDescription": {
256
+ value: "{summary} \u2014 built for {role}{industry}.",
257
+ description: "SEO meta description template",
258
+ placeholders: [
259
+ { name: "summary", type: "string" },
260
+ { name: "role", type: "string" },
261
+ { name: "industry", type: "string" }
262
+ ]
263
+ },
264
+ "seo.offer.default": {
265
+ value: "Start building with ContractSpec",
266
+ description: "Default offer description for schema markup"
267
+ },
268
+ "seo.audience.industry": {
269
+ value: " in {industry}",
270
+ description: "Audience industry suffix for SEO description",
271
+ placeholders: [{ name: "industry", type: "string" }]
272
+ }
273
+ }
274
+ });
275
+
276
+ // src/i18n/catalogs/fr.ts
277
+ import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
278
+ var frMessages = defineTranslation2({
279
+ meta: {
280
+ key: "content-gen.messages",
281
+ version: "1.0.0",
282
+ domain: "content-gen",
283
+ description: "French translations for the content-gen package",
284
+ owners: ["platform"],
285
+ stability: "experimental"
286
+ },
287
+ locale: "fr",
288
+ fallback: "en",
289
+ messages: {
290
+ "prompt.blog.system": {
291
+ value: "Vous \xEAtes un r\xE9dacteur marketing produit. Produisez du JSON avec title, subtitle, intro, sections[].heading/body/bullets, outro.",
292
+ description: "Blog generator LLM system prompt"
293
+ },
294
+ "prompt.email.system": {
295
+ value: "R\xE9digez un e-mail marketing produit en JSON {subject, previewText, body, cta}.",
296
+ description: "Email generator LLM system prompt"
297
+ },
298
+ "prompt.landing.system": {
299
+ value: "\xC9crivez du JSON pour une page d'atterrissage avec hero/highlights/socialProof/faq.",
300
+ description: "Landing page generator LLM system prompt"
301
+ },
302
+ "prompt.social.system": {
303
+ value: "Cr\xE9ez un tableau JSON de posts sociaux pour twitter/linkedin/threads avec body, hashtags, cta.",
304
+ description: "Social post generator LLM system prompt"
305
+ },
306
+ "blog.intro": {
307
+ value: "Les \xE9quipes comme {role} font face \xE0 {problems}. {title} change la donne gr\xE2ce \xE0 {summary}.",
308
+ description: "Blog post intro paragraph template",
309
+ placeholders: [
310
+ { name: "role", type: "string" },
311
+ { name: "problems", type: "string" },
312
+ { name: "title", type: "string" },
313
+ { name: "summary", type: "string" }
314
+ ]
315
+ },
316
+ "blog.heading.whyNow": {
317
+ value: "Pourquoi maintenant",
318
+ description: "Blog section heading: why now"
319
+ },
320
+ "blog.heading.whatYouGet": {
321
+ value: "Ce que vous obtenez",
322
+ description: "Blog section heading: what you get"
323
+ },
324
+ "blog.heading.proofItWorks": {
325
+ value: "La preuve que \xE7a marche",
326
+ description: "Blog section heading: proof it works"
327
+ },
328
+ "blog.body.whatYouGet": {
329
+ value: "Une pile cibl\xE9e con\xE7ue pour l'automatisation conforme aux politiques.",
330
+ description: "Blog section body: what you get"
331
+ },
332
+ "blog.body.proofItWorks": {
333
+ value: "Les \xE9quipes utilisant le mod\xE8le rapportent des gains mesurables.",
334
+ description: "Blog section body: proof it works"
335
+ },
336
+ "blog.metric.launchWorkflows": {
337
+ value: "Lancez des workflows en quelques minutes",
338
+ description: "Default metric: launch workflows"
339
+ },
340
+ "blog.metric.cutReviewTime": {
341
+ value: "R\xE9duisez le temps de revue de 60\xA0%",
342
+ description: "Default metric: cut review time"
343
+ },
344
+ "blog.outro.default": {
345
+ value: "Pr\xEAt \xE0 voir en direct\xA0? Lancez un bac \xE0 sable en moins de 5 minutes.",
346
+ description: "Default blog outro / call to action"
347
+ },
348
+ "blog.whyNow": {
349
+ value: "Les \xE9quipes {audience} sont bloqu\xE9es par {pains}. {title} fournit des garde-fous sans ralentir les livraisons.",
350
+ description: "Blog why-now section body template",
351
+ placeholders: [
352
+ { name: "audience", type: "string" },
353
+ { name: "pains", type: "string" },
354
+ { name: "title", type: "string" }
355
+ ]
356
+ },
357
+ "blog.audience.industry": {
358
+ value: " dans le secteur {industry}",
359
+ description: "Audience industry suffix for blog why-now",
360
+ placeholders: [{ name: "industry", type: "string" }]
361
+ },
362
+ "email.subject.announcement.launch": {
363
+ value: "Lancement\xA0: {title}",
364
+ description: "Announcement email subject variant: launch",
365
+ placeholders: [{ name: "title", type: "string" }]
366
+ },
367
+ "email.subject.announcement.live": {
368
+ value: "{title} est en ligne",
369
+ description: "Announcement email subject variant: live",
370
+ placeholders: [{ name: "title", type: "string" }]
371
+ },
372
+ "email.subject.announcement.new": {
373
+ value: "Nouveau\xA0: {title}",
374
+ description: "Announcement email subject variant: new",
375
+ placeholders: [{ name: "title", type: "string" }]
376
+ },
377
+ "email.subject.onboarding.getStarted": {
378
+ value: "D\xE9marrez avec {title}",
379
+ description: "Onboarding email subject variant: get started",
380
+ placeholders: [{ name: "title", type: "string" }]
381
+ },
382
+ "email.subject.onboarding.guide": {
383
+ value: "Votre guide {title}",
384
+ description: "Onboarding email subject variant: guide",
385
+ placeholders: [{ name: "title", type: "string" }]
386
+ },
387
+ "email.subject.nurture.speeds": {
388
+ value: "Comment {title} acc\xE9l\xE8re les op\xE9rations",
389
+ description: "Nurture email subject variant: speeds ops",
390
+ placeholders: [{ name: "title", type: "string" }]
391
+ },
392
+ "email.subject.nurture.proof": {
393
+ value: "La preuve que {title} fonctionne",
394
+ description: "Nurture email subject variant: proof",
395
+ placeholders: [{ name: "title", type: "string" }]
396
+ },
397
+ "email.subject.fallback": {
398
+ value: "Mise \xE0 jour {title}",
399
+ description: "Fallback email subject line",
400
+ placeholders: [{ name: "title", type: "string" }]
401
+ },
402
+ "email.preview.defaultWin": {
403
+ value: "livrent plus vite sans lacunes de conformit\xE9",
404
+ description: "Default win text for email preview"
405
+ },
406
+ "email.preview.template": {
407
+ value: "D\xE9couvrez comment les \xE9quipes {win}.",
408
+ description: "Email preview text template",
409
+ placeholders: [{ name: "win", type: "string" }]
410
+ },
411
+ "email.body.greeting": {
412
+ value: "Bonjour,",
413
+ description: "Email body greeting"
414
+ },
415
+ "email.body.reasons": {
416
+ value: "Les principales raisons pour lesquelles les \xE9quipes adoptent {title}\xA0:",
417
+ description: "Email body reasons intro",
418
+ placeholders: [{ name: "title", type: "string" }]
419
+ },
420
+ "email.cta.sandbox": {
421
+ value: "Lancez un bac \xE0 sable",
422
+ description: "Default CTA: spin up a sandbox"
423
+ },
424
+ "email.cta.explore": {
425
+ value: "Explorez le bac \xE0 sable",
426
+ description: "Default CTA: explore the sandbox"
427
+ },
428
+ "email.hook.announcement": {
429
+ value: "{title} est en ligne. {summary}",
430
+ description: "Announcement variant hook",
431
+ placeholders: [
432
+ { name: "title", type: "string" },
433
+ { name: "summary", type: "string" }
434
+ ]
435
+ },
436
+ "email.hook.onboarding": {
437
+ value: "Voici votre prochaine \xE9tape pour d\xE9bloquer {title}.",
438
+ description: "Onboarding variant hook",
439
+ placeholders: [{ name: "title", type: "string" }]
440
+ },
441
+ "email.hook.nurture": {
442
+ value: "Les op\xE9rateurs comme {role} demandent sans cesse comment automatiser les v\xE9rifications de conformit\xE9. Voici ce qui fonctionne.",
443
+ description: "Nurture variant hook",
444
+ placeholders: [{ name: "role", type: "string" }]
445
+ },
446
+ "landing.eyebrow.defaultIndustry": {
447
+ value: "Op\xE9rations",
448
+ description: "Default industry for landing page eyebrow"
449
+ },
450
+ "landing.eyebrow.template": {
451
+ value: "\xC9quipes {industry}",
452
+ description: "Landing page eyebrow template",
453
+ placeholders: [{ name: "industry", type: "string" }]
454
+ },
455
+ "landing.cta.primary": {
456
+ value: "Lancer un bac \xE0 sable",
457
+ description: "Landing page primary CTA"
458
+ },
459
+ "landing.cta.secondary": {
460
+ value: "Voir la documentation",
461
+ description: "Landing page secondary CTA"
462
+ },
463
+ "landing.highlight.policySafe": {
464
+ value: "Conforme aux politiques par d\xE9faut",
465
+ description: "Landing page highlight heading 1"
466
+ },
467
+ "landing.highlight.autoAdapts": {
468
+ value: "S'adapte automatiquement par locataire",
469
+ description: "Landing page highlight heading 2"
470
+ },
471
+ "landing.highlight.launchReady": {
472
+ value: "Pr\xEAt au lancement en quelques jours",
473
+ description: "Landing page highlight heading 3"
474
+ },
475
+ "landing.highlight.fallback": {
476
+ value: "Capacit\xE9 cl\xE9",
477
+ description: "Fallback highlight heading"
478
+ },
479
+ "landing.socialProof.heading": {
480
+ value: "\xC9quipes utilisant ContractSpec",
481
+ description: "Social proof section heading"
482
+ },
483
+ "landing.socialProof.defaultQuote": {
484
+ value: "\xAB\xA0Nous livrons des workflows conformes 5x plus vite tout en r\xE9duisant de moiti\xE9 les t\xE2ches op\xE9rationnelles.\xA0\xBB",
485
+ description: "Default social proof quote"
486
+ },
487
+ "landing.faq.policiesEnforced.heading": {
488
+ value: "Comment les politiques restent-elles appliqu\xE9es\xA0?",
489
+ description: "FAQ heading: policies enforced"
490
+ },
491
+ "landing.faq.policiesEnforced.body": {
492
+ value: "Tous les workflows sont compil\xE9s \xE0 partir de sp\xE9cifications TypeScript et passent par des v\xE9rifications PDP avant ex\xE9cution, emp\xEAchant toute logique non autoris\xE9e.",
493
+ description: "FAQ body: policies enforced"
494
+ },
495
+ "landing.faq.existingStack.heading": {
496
+ value: "Est-ce compatible avec notre pile existante\xA0?",
497
+ description: "FAQ heading: existing stack"
498
+ },
499
+ "landing.faq.existingStack.body": {
500
+ value: "Les adaptateurs se connectent \xE0 REST, GraphQL ou MCP. Les int\xE9grations restent agnostiques vis-\xE0-vis des fournisseurs.",
501
+ description: "FAQ body: existing stack"
502
+ },
503
+ "landing.faq.compliance.heading": {
504
+ value: "Qu'en est-il des exigences de conformit\xE9\xA0?",
505
+ description: "FAQ heading: compliance requirements"
506
+ },
507
+ "social.cta.linkedin": {
508
+ value: "R\xE9servez une d\xE9mo de 15 min",
509
+ description: "LinkedIn post default CTA"
510
+ },
511
+ "social.cta.twitter": {
512
+ value: "\u2192 contractspec.io/sandbox",
513
+ description: "Twitter post default CTA"
514
+ },
515
+ "social.body.threads": {
516
+ value: "Ops + conformit\xE9 peuvent avancer vite. {title} automatise les garde-fous pour que les \xE9quipes livrent quotidiennement.",
517
+ description: "Threads post body template",
518
+ placeholders: [{ name: "title", type: "string" }]
519
+ },
520
+ "social.body.twitter.connector": {
521
+ value: " en <60s. ",
522
+ description: "Twitter body connector between solutions"
523
+ },
524
+ "seo.metaTitle": {
525
+ value: "{title} | ContractSpec",
526
+ description: "SEO meta title template",
527
+ placeholders: [{ name: "title", type: "string" }]
528
+ },
529
+ "seo.metaDescription": {
530
+ value: "{summary} \u2014 con\xE7u pour {role}{industry}.",
531
+ description: "SEO meta description template",
532
+ placeholders: [
533
+ { name: "summary", type: "string" },
534
+ { name: "role", type: "string" },
535
+ { name: "industry", type: "string" }
536
+ ]
537
+ },
538
+ "seo.offer.default": {
539
+ value: "Commencez \xE0 construire avec ContractSpec",
540
+ description: "Default offer description for schema markup"
541
+ },
542
+ "seo.audience.industry": {
543
+ value: " dans le secteur {industry}",
544
+ description: "Audience industry suffix for SEO description",
545
+ placeholders: [{ name: "industry", type: "string" }]
546
+ }
547
+ }
548
+ });
549
+
550
+ // src/i18n/catalogs/es.ts
551
+ import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
552
+ var esMessages = defineTranslation3({
553
+ meta: {
554
+ key: "content-gen.messages",
555
+ version: "1.0.0",
556
+ domain: "content-gen",
557
+ description: "Spanish translations for the content-gen package",
558
+ owners: ["platform"],
559
+ stability: "experimental"
560
+ },
561
+ locale: "es",
562
+ fallback: "en",
563
+ messages: {
564
+ "prompt.blog.system": {
565
+ value: "Eres un redactor de marketing de producto. Produce JSON con title, subtitle, intro, sections[].heading/body/bullets, outro.",
566
+ description: "Blog generator LLM system prompt"
567
+ },
568
+ "prompt.email.system": {
569
+ value: "Redacta un correo de marketing de producto en JSON {subject, previewText, body, cta}.",
570
+ description: "Email generator LLM system prompt"
571
+ },
572
+ "prompt.landing.system": {
573
+ value: "Escribe JSON para una p\xE1gina de aterrizaje con hero/highlights/socialProof/faq.",
574
+ description: "Landing page generator LLM system prompt"
575
+ },
576
+ "prompt.social.system": {
577
+ value: "Crea un array JSON de publicaciones sociales para twitter/linkedin/threads con body, hashtags, cta.",
578
+ description: "Social post generator LLM system prompt"
579
+ },
580
+ "blog.intro": {
581
+ value: "Equipos como {role} enfrentan {problems}. {title} cambia eso gracias a {summary}.",
582
+ description: "Blog post intro paragraph template",
583
+ placeholders: [
584
+ { name: "role", type: "string" },
585
+ { name: "problems", type: "string" },
586
+ { name: "title", type: "string" },
587
+ { name: "summary", type: "string" }
588
+ ]
589
+ },
590
+ "blog.heading.whyNow": {
591
+ value: "Por qu\xE9 ahora",
592
+ description: "Blog section heading: why now"
593
+ },
594
+ "blog.heading.whatYouGet": {
595
+ value: "Lo que obtienes",
596
+ description: "Blog section heading: what you get"
597
+ },
598
+ "blog.heading.proofItWorks": {
599
+ value: "Prueba de que funciona",
600
+ description: "Blog section heading: proof it works"
601
+ },
602
+ "blog.body.whatYouGet": {
603
+ value: "Una pila enfocada construida para la automatizaci\xF3n segura con pol\xEDticas.",
604
+ description: "Blog section body: what you get"
605
+ },
606
+ "blog.body.proofItWorks": {
607
+ value: "Los equipos que usan el modelo reportan logros medibles.",
608
+ description: "Blog section body: proof it works"
609
+ },
610
+ "blog.metric.launchWorkflows": {
611
+ value: "Lanza flujos de trabajo en minutos",
612
+ description: "Default metric: launch workflows"
613
+ },
614
+ "blog.metric.cutReviewTime": {
615
+ value: "Reduce el tiempo de revisi\xF3n en un 60\xA0%",
616
+ description: "Default metric: cut review time"
617
+ },
618
+ "blog.outro.default": {
619
+ value: "\xBFListo para verlo en vivo? Lanza un sandbox en menos de 5 minutos.",
620
+ description: "Default blog outro / call to action"
621
+ },
622
+ "blog.whyNow": {
623
+ value: "Los equipos de {audience} est\xE1n atascados con {pains}. {title} ofrece barreras de protecci\xF3n sin ralentizar las entregas.",
624
+ description: "Blog why-now section body template",
625
+ placeholders: [
626
+ { name: "audience", type: "string" },
627
+ { name: "pains", type: "string" },
628
+ { name: "title", type: "string" }
629
+ ]
630
+ },
631
+ "blog.audience.industry": {
632
+ value: " en el sector {industry}",
633
+ description: "Audience industry suffix for blog why-now",
634
+ placeholders: [{ name: "industry", type: "string" }]
635
+ },
636
+ "email.subject.announcement.launch": {
637
+ value: "Lanzamiento: {title}",
638
+ description: "Announcement email subject variant: launch",
639
+ placeholders: [{ name: "title", type: "string" }]
640
+ },
641
+ "email.subject.announcement.live": {
642
+ value: "{title} ya est\xE1 disponible",
643
+ description: "Announcement email subject variant: live",
644
+ placeholders: [{ name: "title", type: "string" }]
645
+ },
646
+ "email.subject.announcement.new": {
647
+ value: "Nuevo: {title}",
648
+ description: "Announcement email subject variant: new",
649
+ placeholders: [{ name: "title", type: "string" }]
650
+ },
651
+ "email.subject.onboarding.getStarted": {
652
+ value: "Empieza con {title}",
653
+ description: "Onboarding email subject variant: get started",
654
+ placeholders: [{ name: "title", type: "string" }]
655
+ },
656
+ "email.subject.onboarding.guide": {
657
+ value: "Tu gu\xEDa de {title}",
658
+ description: "Onboarding email subject variant: guide",
659
+ placeholders: [{ name: "title", type: "string" }]
660
+ },
661
+ "email.subject.nurture.speeds": {
662
+ value: "C\xF3mo {title} acelera las operaciones",
663
+ description: "Nurture email subject variant: speeds ops",
664
+ placeholders: [{ name: "title", type: "string" }]
665
+ },
666
+ "email.subject.nurture.proof": {
667
+ value: "Prueba de que {title} funciona",
668
+ description: "Nurture email subject variant: proof",
669
+ placeholders: [{ name: "title", type: "string" }]
670
+ },
671
+ "email.subject.fallback": {
672
+ value: "Actualizaci\xF3n de {title}",
673
+ description: "Fallback email subject line",
674
+ placeholders: [{ name: "title", type: "string" }]
675
+ },
676
+ "email.preview.defaultWin": {
677
+ value: "entregan m\xE1s r\xE1pido sin brechas de cumplimiento",
678
+ description: "Default win text for email preview"
679
+ },
680
+ "email.preview.template": {
681
+ value: "Descubre c\xF3mo los equipos {win}.",
682
+ description: "Email preview text template",
683
+ placeholders: [{ name: "win", type: "string" }]
684
+ },
685
+ "email.body.greeting": {
686
+ value: "Hola,",
687
+ description: "Email body greeting"
688
+ },
689
+ "email.body.reasons": {
690
+ value: "Principales razones por las que los equipos adoptan {title}:",
691
+ description: "Email body reasons intro",
692
+ placeholders: [{ name: "title", type: "string" }]
693
+ },
694
+ "email.cta.sandbox": {
695
+ value: "Lanza un sandbox",
696
+ description: "Default CTA: spin up a sandbox"
697
+ },
698
+ "email.cta.explore": {
699
+ value: "Explora el sandbox",
700
+ description: "Default CTA: explore the sandbox"
701
+ },
702
+ "email.hook.announcement": {
703
+ value: "{title} ya est\xE1 disponible. {summary}",
704
+ description: "Announcement variant hook",
705
+ placeholders: [
706
+ { name: "title", type: "string" },
707
+ { name: "summary", type: "string" }
708
+ ]
709
+ },
710
+ "email.hook.onboarding": {
711
+ value: "Aqu\xED tienes tu siguiente paso para desbloquear {title}.",
712
+ description: "Onboarding variant hook",
713
+ placeholders: [{ name: "title", type: "string" }]
714
+ },
715
+ "email.hook.nurture": {
716
+ value: "Operadores como {role} siguen preguntando c\xF3mo automatizar las verificaciones de cumplimiento. Esto es lo que funciona.",
717
+ description: "Nurture variant hook",
718
+ placeholders: [{ name: "role", type: "string" }]
719
+ },
720
+ "landing.eyebrow.defaultIndustry": {
721
+ value: "Operaciones",
722
+ description: "Default industry for landing page eyebrow"
723
+ },
724
+ "landing.eyebrow.template": {
725
+ value: "Equipos de {industry}",
726
+ description: "Landing page eyebrow template",
727
+ placeholders: [{ name: "industry", type: "string" }]
728
+ },
729
+ "landing.cta.primary": {
730
+ value: "Lanzar un sandbox",
731
+ description: "Landing page primary CTA"
732
+ },
733
+ "landing.cta.secondary": {
734
+ value: "Ver documentaci\xF3n",
735
+ description: "Landing page secondary CTA"
736
+ },
737
+ "landing.highlight.policySafe": {
738
+ value: "Conforme a pol\xEDticas por defecto",
739
+ description: "Landing page highlight heading 1"
740
+ },
741
+ "landing.highlight.autoAdapts": {
742
+ value: "Se adapta autom\xE1ticamente por inquilino",
743
+ description: "Landing page highlight heading 2"
744
+ },
745
+ "landing.highlight.launchReady": {
746
+ value: "Listo para lanzar en d\xEDas",
747
+ description: "Landing page highlight heading 3"
748
+ },
749
+ "landing.highlight.fallback": {
750
+ value: "Capacidad clave",
751
+ description: "Fallback highlight heading"
752
+ },
753
+ "landing.socialProof.heading": {
754
+ value: "Equipos que usan ContractSpec",
755
+ description: "Social proof section heading"
756
+ },
757
+ "landing.socialProof.defaultQuote": {
758
+ value: "\xAB\xA0Entregamos flujos de trabajo conformes 5 veces m\xE1s r\xE1pido reduciendo a la mitad las tareas operativas.\xA0\xBB",
759
+ description: "Default social proof quote"
760
+ },
761
+ "landing.faq.policiesEnforced.heading": {
762
+ value: "\xBFC\xF3mo se mantienen las pol\xEDticas aplicadas?",
763
+ description: "FAQ heading: policies enforced"
764
+ },
765
+ "landing.faq.policiesEnforced.body": {
766
+ value: "Todos los flujos se compilan desde especificaciones TypeScript y pasan verificaciones PDP antes de ejecutarse, evitando l\xF3gica no autorizada.",
767
+ description: "FAQ body: policies enforced"
768
+ },
769
+ "landing.faq.existingStack.heading": {
770
+ value: "\xBFEs compatible con nuestra pila existente?",
771
+ description: "FAQ heading: existing stack"
772
+ },
773
+ "landing.faq.existingStack.body": {
774
+ value: "Los adaptadores se conectan a REST, GraphQL o MCP. Las integraciones son agn\xF3sticas respecto al proveedor.",
775
+ description: "FAQ body: existing stack"
776
+ },
777
+ "landing.faq.compliance.heading": {
778
+ value: "\xBFQu\xE9 pasa con los requisitos de cumplimiento?",
779
+ description: "FAQ heading: compliance requirements"
780
+ },
781
+ "social.cta.linkedin": {
782
+ value: "Reserva una demo de 15 min",
783
+ description: "LinkedIn post default CTA"
784
+ },
785
+ "social.cta.twitter": {
786
+ value: "\u2192 contractspec.io/sandbox",
787
+ description: "Twitter post default CTA"
788
+ },
789
+ "social.body.threads": {
790
+ value: "Ops + cumplimiento pueden avanzar r\xE1pido. {title} automatiza las barreras para que los equipos entreguen a diario.",
791
+ description: "Threads post body template",
792
+ placeholders: [{ name: "title", type: "string" }]
793
+ },
794
+ "social.body.twitter.connector": {
795
+ value: " en <60s. ",
796
+ description: "Twitter body connector between solutions"
797
+ },
798
+ "seo.metaTitle": {
799
+ value: "{title} | ContractSpec",
800
+ description: "SEO meta title template",
801
+ placeholders: [{ name: "title", type: "string" }]
802
+ },
803
+ "seo.metaDescription": {
804
+ value: "{summary} \u2014 dise\xF1ado para {role}{industry}.",
805
+ description: "SEO meta description template",
806
+ placeholders: [
807
+ { name: "summary", type: "string" },
808
+ { name: "role", type: "string" },
809
+ { name: "industry", type: "string" }
810
+ ]
811
+ },
812
+ "seo.offer.default": {
813
+ value: "Empieza a construir con ContractSpec",
814
+ description: "Default offer description for schema markup"
815
+ },
816
+ "seo.audience.industry": {
817
+ value: " en el sector {industry}",
818
+ description: "Audience industry suffix for SEO description",
819
+ placeholders: [{ name: "industry", type: "string" }]
820
+ }
821
+ }
822
+ });
823
+
824
+ // src/i18n/messages.ts
825
+ import {
826
+ createI18nFactory
827
+ } from "@contractspec/lib.contracts-spec/translations";
828
+ var factory = createI18nFactory({
829
+ specKey: "content-gen.messages",
830
+ catalogs: [enMessages, frMessages, esMessages]
831
+ });
832
+ var createContentGenI18n = factory.create;
833
+ var getDefaultI18n = factory.getDefault;
834
+ var resetI18nRegistry = factory.resetRegistry;
835
+
836
+ // src/i18n/locale.ts
837
+ import {
838
+ DEFAULT_LOCALE,
839
+ SUPPORTED_LOCALES,
840
+ resolveLocale,
841
+ isSupportedLocale
842
+ } from "@contractspec/lib.contracts-spec/translations";
843
+
844
+ // src/i18n/keys.ts
845
+ var PROMPT_KEYS = {
846
+ "prompt.blog.system": "prompt.blog.system",
847
+ "prompt.email.system": "prompt.email.system",
848
+ "prompt.landing.system": "prompt.landing.system",
849
+ "prompt.social.system": "prompt.social.system"
850
+ };
851
+ var BLOG_KEYS = {
852
+ "blog.intro": "blog.intro",
853
+ "blog.heading.whyNow": "blog.heading.whyNow",
854
+ "blog.heading.whatYouGet": "blog.heading.whatYouGet",
855
+ "blog.heading.proofItWorks": "blog.heading.proofItWorks",
856
+ "blog.body.whatYouGet": "blog.body.whatYouGet",
857
+ "blog.body.proofItWorks": "blog.body.proofItWorks",
858
+ "blog.metric.launchWorkflows": "blog.metric.launchWorkflows",
859
+ "blog.metric.cutReviewTime": "blog.metric.cutReviewTime",
860
+ "blog.outro.default": "blog.outro.default",
861
+ "blog.whyNow": "blog.whyNow",
862
+ "blog.audience.industry": "blog.audience.industry"
863
+ };
864
+ var EMAIL_KEYS = {
865
+ "email.subject.announcement.launch": "email.subject.announcement.launch",
866
+ "email.subject.announcement.live": "email.subject.announcement.live",
867
+ "email.subject.announcement.new": "email.subject.announcement.new",
868
+ "email.subject.onboarding.getStarted": "email.subject.onboarding.getStarted",
869
+ "email.subject.onboarding.guide": "email.subject.onboarding.guide",
870
+ "email.subject.nurture.speeds": "email.subject.nurture.speeds",
871
+ "email.subject.nurture.proof": "email.subject.nurture.proof",
872
+ "email.subject.fallback": "email.subject.fallback",
873
+ "email.preview.defaultWin": "email.preview.defaultWin",
874
+ "email.preview.template": "email.preview.template",
875
+ "email.body.greeting": "email.body.greeting",
876
+ "email.body.reasons": "email.body.reasons",
877
+ "email.cta.sandbox": "email.cta.sandbox",
878
+ "email.cta.explore": "email.cta.explore",
879
+ "email.hook.announcement": "email.hook.announcement",
880
+ "email.hook.onboarding": "email.hook.onboarding",
881
+ "email.hook.nurture": "email.hook.nurture"
882
+ };
883
+ var LANDING_KEYS = {
884
+ "landing.eyebrow.defaultIndustry": "landing.eyebrow.defaultIndustry",
885
+ "landing.eyebrow.template": "landing.eyebrow.template",
886
+ "landing.cta.primary": "landing.cta.primary",
887
+ "landing.cta.secondary": "landing.cta.secondary",
888
+ "landing.highlight.policySafe": "landing.highlight.policySafe",
889
+ "landing.highlight.autoAdapts": "landing.highlight.autoAdapts",
890
+ "landing.highlight.launchReady": "landing.highlight.launchReady",
891
+ "landing.highlight.fallback": "landing.highlight.fallback",
892
+ "landing.socialProof.heading": "landing.socialProof.heading",
893
+ "landing.socialProof.defaultQuote": "landing.socialProof.defaultQuote",
894
+ "landing.faq.policiesEnforced.heading": "landing.faq.policiesEnforced.heading",
895
+ "landing.faq.policiesEnforced.body": "landing.faq.policiesEnforced.body",
896
+ "landing.faq.existingStack.heading": "landing.faq.existingStack.heading",
897
+ "landing.faq.existingStack.body": "landing.faq.existingStack.body",
898
+ "landing.faq.compliance.heading": "landing.faq.compliance.heading"
899
+ };
900
+ var SOCIAL_KEYS = {
901
+ "social.cta.linkedin": "social.cta.linkedin",
902
+ "social.cta.twitter": "social.cta.twitter",
903
+ "social.body.threads": "social.body.threads",
904
+ "social.body.twitter.connector": "social.body.twitter.connector"
905
+ };
906
+ var SEO_KEYS = {
907
+ "seo.metaTitle": "seo.metaTitle",
908
+ "seo.metaDescription": "seo.metaDescription",
909
+ "seo.offer.default": "seo.offer.default",
910
+ "seo.audience.industry": "seo.audience.industry"
911
+ };
912
+ var I18N_KEYS = {
913
+ ...PROMPT_KEYS,
914
+ ...BLOG_KEYS,
915
+ ...EMAIL_KEYS,
916
+ ...LANDING_KEYS,
917
+ ...SOCIAL_KEYS,
918
+ ...SEO_KEYS
919
+ };
2
920
  // src/generators/blog.ts
3
921
  class BlogGenerator {
4
922
  llm;
5
923
  model;
6
924
  temperature;
925
+ i18n;
7
926
  constructor(options) {
8
927
  this.llm = options?.llm;
9
928
  this.model = options?.model;
10
929
  this.temperature = options?.temperature ?? 0.4;
930
+ this.i18n = createContentGenI18n(options?.locale);
11
931
  }
12
932
  async generate(brief) {
13
933
  if (this.llm) {
@@ -25,7 +945,7 @@ class BlogGenerator {
25
945
  content: [
26
946
  {
27
947
  type: "text",
28
- text: "You are a product marketing writer. Produce JSON with title, subtitle, intro, sections[].heading/body/bullets, outro."
948
+ text: this.i18n.t("prompt.blog.system")
29
949
  }
30
950
  ]
31
951
  },
@@ -50,23 +970,29 @@ class BlogGenerator {
50
970
  return this.generateDeterministic(brief);
51
971
  }
52
972
  generateDeterministic(brief) {
53
- const intro = `Operators like ${brief.audience.role} teams face ${brief.problems.slice(0, 2).join(" and ")}. ${brief.title} changes that by ${brief.summary}.`;
973
+ const { t } = this.i18n;
974
+ const intro = t("blog.intro", {
975
+ role: brief.audience.role,
976
+ problems: brief.problems.slice(0, 2).join(" and "),
977
+ title: brief.title,
978
+ summary: brief.summary
979
+ });
54
980
  const sections = [
55
981
  {
56
- heading: "Why now",
982
+ heading: t("blog.heading.whyNow"),
57
983
  body: this.renderWhyNow(brief)
58
984
  },
59
985
  {
60
- heading: "What you get",
61
- body: "A focused stack built for policy-safe automation.",
986
+ heading: t("blog.heading.whatYouGet"),
987
+ body: t("blog.body.whatYouGet"),
62
988
  bullets: brief.solutions
63
989
  },
64
990
  {
65
- heading: "Proof it works",
66
- body: "Teams using the blueprint report measurable wins.",
991
+ heading: t("blog.heading.proofItWorks"),
992
+ body: t("blog.body.proofItWorks"),
67
993
  bullets: brief.metrics ?? [
68
- "Launch workflows in minutes",
69
- "Cut review time by 60%"
994
+ t("blog.metric.launchWorkflows"),
995
+ t("blog.metric.cutReviewTime")
70
996
  ]
71
997
  }
72
998
  ];
@@ -75,13 +1001,18 @@ class BlogGenerator {
75
1001
  subtitle: brief.summary,
76
1002
  intro,
77
1003
  sections,
78
- outro: brief.callToAction ?? "Ready to see it live? Spin up a sandbox in under 5 minutes."
1004
+ outro: brief.callToAction ?? t("blog.outro.default")
79
1005
  };
80
1006
  }
81
1007
  renderWhyNow(brief) {
82
- const audience = `${brief.audience.role}${brief.audience.industry ? ` in ${brief.audience.industry}` : ""}`;
1008
+ const { t } = this.i18n;
1009
+ const audience = `${brief.audience.role}${brief.audience.industry ? t("blog.audience.industry", { industry: brief.audience.industry }) : ""}`;
83
1010
  const pains = brief.problems.slice(0, 2).join("; ");
84
- return `${audience} teams are stuck with ${pains}. ${brief.title} delivers guardrails without slowing shipping.`;
1011
+ return t("blog.whyNow", {
1012
+ audience,
1013
+ pains,
1014
+ title: brief.title
1015
+ });
85
1016
  }
86
1017
  }
87
1018
 
@@ -90,10 +1021,12 @@ class EmailCampaignGenerator {
90
1021
  llm;
91
1022
  model;
92
1023
  temperature;
1024
+ i18n;
93
1025
  constructor(options) {
94
1026
  this.llm = options?.llm;
95
1027
  this.model = options?.model;
96
1028
  this.temperature = options?.temperature ?? 0.6;
1029
+ this.i18n = createContentGenI18n(options?.locale);
97
1030
  }
98
1031
  async generate(input) {
99
1032
  if (this.llm) {
@@ -112,7 +1045,7 @@ class EmailCampaignGenerator {
112
1045
  content: [
113
1046
  {
114
1047
  type: "text",
115
- text: "Draft product marketing email as JSON {subject, previewText, body, cta}."
1048
+ text: this.i18n.t("prompt.email.system")
116
1049
  }
117
1050
  ]
118
1051
  },
@@ -141,30 +1074,44 @@ class EmailCampaignGenerator {
141
1074
  }
142
1075
  generateFallback(input) {
143
1076
  const { brief, variant } = input;
144
- const subject = this.subjects(brief.title, variant)[0] ?? `${brief.title} update`;
1077
+ const { t } = this.i18n;
1078
+ const subject = this.subjects(brief.title, variant)[0] ?? t("email.subject.fallback", { title: brief.title });
145
1079
  const previewText = this.defaultPreview(input);
146
1080
  const body = this.renderBody(input);
147
- const cta = brief.callToAction ?? "Explore the sandbox";
1081
+ const cta = brief.callToAction ?? t("email.cta.explore");
148
1082
  return { subject, previewText, body, cta, variant };
149
1083
  }
150
1084
  subjects(title, variant) {
1085
+ const { t } = this.i18n;
151
1086
  switch (variant) {
152
1087
  case "announcement":
153
- return [`Launch: ${title}`, `${title} is live`, `New: ${title}`];
1088
+ return [
1089
+ t("email.subject.announcement.launch", { title }),
1090
+ t("email.subject.announcement.live", { title }),
1091
+ t("email.subject.announcement.new", { title })
1092
+ ];
154
1093
  case "onboarding":
155
- return [`Get started with ${title}`, `Your ${title} guide`];
1094
+ return [
1095
+ t("email.subject.onboarding.getStarted", { title }),
1096
+ t("email.subject.onboarding.guide", { title })
1097
+ ];
156
1098
  case "nurture":
157
1099
  default:
158
- return [`How ${title} speeds ops`, `Proof ${title} works`];
1100
+ return [
1101
+ t("email.subject.nurture.speeds", { title }),
1102
+ t("email.subject.nurture.proof", { title })
1103
+ ];
159
1104
  }
160
1105
  }
161
1106
  defaultPreview(input) {
162
- const win = input.brief.metrics?.[0] ?? "ship faster without policy gaps";
163
- return `See how teams ${win}.`;
1107
+ const { t } = this.i18n;
1108
+ const win = input.brief.metrics?.[0] ?? t("email.preview.defaultWin");
1109
+ return t("email.preview.template", { win });
164
1110
  }
165
1111
  renderBody(input) {
166
1112
  const { brief, variant } = input;
167
- const greeting = "Hi there,";
1113
+ const { t } = this.i18n;
1114
+ const greeting = t("email.body.greeting");
168
1115
  const hook = this.variantHook(variant, brief);
169
1116
  const proof = brief.metrics?.map((metric) => `\u2022 ${metric}`).join(`
170
1117
  `) ?? "";
@@ -172,24 +1119,28 @@ class EmailCampaignGenerator {
172
1119
 
173
1120
  ${hook}
174
1121
 
175
- Top reasons teams adopt ${brief.title}:
1122
+ ${t("email.body.reasons", { title: brief.title })}
176
1123
  ${brief.solutions.map((solution) => `\u2022 ${solution}`).join(`
177
1124
  `)}
178
1125
 
179
1126
  ${proof}
180
1127
 
181
- ${brief.callToAction ?? "Spin up a sandbox"} \u2192 ${(input.cadenceDay ?? 0) + 1}
1128
+ ${brief.callToAction ?? t("email.cta.sandbox")} \u2192 ${(input.cadenceDay ?? 0) + 1}
182
1129
  `;
183
1130
  }
184
1131
  variantHook(variant, brief) {
1132
+ const { t } = this.i18n;
185
1133
  switch (variant) {
186
1134
  case "announcement":
187
- return `${brief.title} is live. ${brief.summary}`;
1135
+ return t("email.hook.announcement", {
1136
+ title: brief.title,
1137
+ summary: brief.summary
1138
+ });
188
1139
  case "onboarding":
189
- return `Here is your next step to unlock ${brief.title}.`;
1140
+ return t("email.hook.onboarding", { title: brief.title });
190
1141
  case "nurture":
191
1142
  default:
192
- return `Operators like ${brief.audience.role} keep asking how to automate policy checks. Here is what works.`;
1143
+ return t("email.hook.nurture", { role: brief.audience.role });
193
1144
  }
194
1145
  }
195
1146
  }
@@ -199,10 +1150,12 @@ class LandingPageGenerator {
199
1150
  options;
200
1151
  llm;
201
1152
  model;
1153
+ i18n;
202
1154
  constructor(options) {
203
1155
  this.options = options;
204
1156
  this.llm = options?.llm;
205
1157
  this.model = options?.model;
1158
+ this.i18n = createContentGenI18n(options?.locale);
206
1159
  }
207
1160
  async generate(brief) {
208
1161
  if (this.llm) {
@@ -220,7 +1173,7 @@ class LandingPageGenerator {
220
1173
  content: [
221
1174
  {
222
1175
  type: "text",
223
- text: "Write JSON landing page copy with hero/highlights/socialProof/faq arrays."
1176
+ text: this.i18n.t("prompt.landing.system")
224
1177
  }
225
1178
  ]
226
1179
  },
@@ -240,44 +1193,47 @@ class LandingPageGenerator {
240
1193
  return this.generateFallback(brief);
241
1194
  }
242
1195
  generateFallback(brief) {
1196
+ const { t } = this.i18n;
1197
+ const industry = brief.audience.industry ?? t("landing.eyebrow.defaultIndustry");
243
1198
  return {
244
1199
  hero: {
245
- eyebrow: `${brief.audience.industry ?? "Operations"} teams`,
1200
+ eyebrow: t("landing.eyebrow.template", { industry }),
246
1201
  title: brief.title,
247
1202
  subtitle: brief.summary,
248
- primaryCta: brief.callToAction ?? "Launch a sandbox",
249
- secondaryCta: "View docs"
1203
+ primaryCta: brief.callToAction ?? t("landing.cta.primary"),
1204
+ secondaryCta: t("landing.cta.secondary")
250
1205
  },
251
1206
  highlights: brief.solutions.slice(0, 3).map((solution, index) => ({
252
1207
  heading: [
253
- "Policy-safe by default",
254
- "Auto-adapts per tenant",
255
- "Launch-ready in days"
256
- ][index] ?? "Key capability",
1208
+ t("landing.highlight.policySafe"),
1209
+ t("landing.highlight.autoAdapts"),
1210
+ t("landing.highlight.launchReady")
1211
+ ][index] ?? t("landing.highlight.fallback"),
257
1212
  body: solution
258
1213
  })),
259
1214
  socialProof: {
260
- heading: "Teams using ContractSpec",
1215
+ heading: t("landing.socialProof.heading"),
261
1216
  body: brief.proofPoints?.join(`
262
- `) ?? "\u201CWe ship compliant workflows 5x faster while cutting ops toil in half.\u201D"
1217
+ `) ?? t("landing.socialProof.defaultQuote")
263
1218
  },
264
1219
  faq: this.buildFaq(brief)
265
1220
  };
266
1221
  }
267
1222
  buildFaq(brief) {
1223
+ const { t } = this.i18n;
268
1224
  const faqs = [
269
1225
  {
270
- heading: "How does this keep policies enforced?",
271
- body: "All workflows compile from TypeScript specs and pass through PDP checks before execution, so no shadow logic slips through."
1226
+ heading: t("landing.faq.policiesEnforced.heading"),
1227
+ body: t("landing.faq.policiesEnforced.body")
272
1228
  },
273
1229
  {
274
- heading: "Will it fit our existing stack?",
275
- body: "Runtime adapters plug into REST, GraphQL, or MCP. Integrations stay vendor agnostic."
1230
+ heading: t("landing.faq.existingStack.heading"),
1231
+ body: t("landing.faq.existingStack.body")
276
1232
  }
277
1233
  ];
278
1234
  if (brief.complianceNotes?.length) {
279
1235
  faqs.push({
280
- heading: "What about compliance requirements?",
1236
+ heading: t("landing.faq.compliance.heading"),
281
1237
  body: brief.complianceNotes.join(" ")
282
1238
  });
283
1239
  }
@@ -289,9 +1245,11 @@ class LandingPageGenerator {
289
1245
  class SocialPostGenerator {
290
1246
  llm;
291
1247
  model;
1248
+ i18n;
292
1249
  constructor(options) {
293
1250
  this.llm = options?.llm;
294
1251
  this.model = options?.model;
1252
+ this.i18n = createContentGenI18n(options?.locale);
295
1253
  }
296
1254
  async generate(brief) {
297
1255
  if (this.llm) {
@@ -310,7 +1268,7 @@ class SocialPostGenerator {
310
1268
  content: [
311
1269
  {
312
1270
  type: "text",
313
- text: "Create JSON array of social posts for twitter/linkedin/threads with body, hashtags, cta."
1271
+ text: this.i18n.t("prompt.social.system")
314
1272
  }
315
1273
  ]
316
1274
  },
@@ -325,6 +1283,7 @@ class SocialPostGenerator {
325
1283
  return JSON.parse(part.text);
326
1284
  }
327
1285
  generateFallback(brief) {
1286
+ const { t } = this.i18n;
328
1287
  const hashtags = this.buildHashtags(brief);
329
1288
  return [
330
1289
  {
@@ -332,17 +1291,17 @@ class SocialPostGenerator {
332
1291
  body: `${brief.title}: ${brief.summary}
333
1292
  ${brief.problems[0]} \u2192 ${brief.solutions[0]}`,
334
1293
  hashtags,
335
- cta: brief.callToAction ?? "Book a 15-min run-through"
1294
+ cta: brief.callToAction ?? t("social.cta.linkedin")
336
1295
  },
337
1296
  {
338
1297
  channel: "twitter",
339
- body: `${brief.solutions[0]} in <60s. ${brief.solutions[1] ?? ""}`.trim(),
1298
+ body: `${brief.solutions[0]}${t("social.body.twitter.connector")}${brief.solutions[1] ?? ""}`.trim(),
340
1299
  hashtags: hashtags.slice(0, 3),
341
- cta: "\u2192 contractspec.io/sandbox"
1300
+ cta: t("social.cta.twitter")
342
1301
  },
343
1302
  {
344
1303
  channel: "threads",
345
- body: `Ops + policy can move fast. ${brief.title} automates guardrails so teams ship daily.`,
1304
+ body: t("social.body.threads", { title: brief.title }),
346
1305
  hashtags: hashtags.slice(1, 4)
347
1306
  }
348
1307
  ];
@@ -362,10 +1321,20 @@ function camel(text) {
362
1321
  }
363
1322
  // src/seo/optimizer.ts
364
1323
  class SeoOptimizer {
1324
+ i18n;
1325
+ constructor(options) {
1326
+ this.i18n = createContentGenI18n(options?.locale);
1327
+ }
365
1328
  optimize(brief) {
1329
+ const { t } = this.i18n;
366
1330
  const keywords = this.keywords(brief);
367
- const metaTitle = `${brief.title} | ContractSpec`;
368
- const metaDescription = `${brief.summary} \u2014 built for ${brief.audience.role}${brief.audience.industry ? ` in ${brief.audience.industry}` : ""}.`;
1331
+ const metaTitle = t("seo.metaTitle", { title: brief.title });
1332
+ const industrySuffix = brief.audience.industry ? t("seo.audience.industry", { industry: brief.audience.industry }) : "";
1333
+ const metaDescription = t("seo.metaDescription", {
1334
+ summary: brief.summary,
1335
+ role: brief.audience.role,
1336
+ industry: industrySuffix
1337
+ });
369
1338
  const slug = this.slugify(brief.title);
370
1339
  const schemaMarkup = this.schema(brief, metaDescription, keywords);
371
1340
  return { metaTitle, metaDescription, keywords, slug, schemaMarkup };
@@ -377,9 +1346,10 @@ class SeoOptimizer {
377
1346
  ].filter((word) => word.length > 3).slice(0, 12);
378
1347
  }
379
1348
  slugify(text) {
380
- return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1349
+ return text.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
381
1350
  }
382
1351
  schema(brief, description, keywords) {
1352
+ const { t } = this.i18n;
383
1353
  return {
384
1354
  "@context": "https://schema.org",
385
1355
  "@type": "Product",
@@ -392,7 +1362,7 @@ class SeoOptimizer {
392
1362
  },
393
1363
  offers: {
394
1364
  "@type": "Offer",
395
- description: brief.callToAction ?? "Start building with ContractSpec"
1365
+ description: brief.callToAction ?? t("seo.offer.default")
396
1366
  },
397
1367
  keywords: keywords.join(", "),
398
1368
  citation: brief.references?.map((ref) => ref.url)