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