@learncard/email-templates 1.0.4 → 1.0.5

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 (92) hide show
  1. package/dist/components/Layout.d.ts +2 -0
  2. package/dist/components/Layout.d.ts.map +1 -1
  3. package/dist/components/Layout.js +6 -1
  4. package/dist/components/Layout.js.map +1 -1
  5. package/dist/components/LinkFallback.d.ts +2 -0
  6. package/dist/components/LinkFallback.d.ts.map +1 -1
  7. package/dist/components/LinkFallback.js +5 -1
  8. package/dist/components/LinkFallback.js.map +1 -1
  9. package/dist/i18n.d.ts +52 -0
  10. package/dist/i18n.d.ts.map +1 -0
  11. package/dist/i18n.js +77 -0
  12. package/dist/i18n.js.map +1 -0
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/render.d.ts +3 -1
  18. package/dist/render.d.ts.map +1 -1
  19. package/dist/render.js +33 -30
  20. package/dist/render.js.map +1 -1
  21. package/dist/sms.d.ts +6 -3
  22. package/dist/sms.d.ts.map +1 -1
  23. package/dist/sms.js +43 -7
  24. package/dist/sms.js.map +1 -1
  25. package/dist/templates/account-approved.d.ts +3 -1
  26. package/dist/templates/account-approved.d.ts.map +1 -1
  27. package/dist/templates/account-approved.js +55 -5
  28. package/dist/templates/account-approved.js.map +1 -1
  29. package/dist/templates/credential-awaiting-guardian.d.ts +3 -1
  30. package/dist/templates/credential-awaiting-guardian.d.ts.map +1 -1
  31. package/dist/templates/credential-awaiting-guardian.js +54 -4
  32. package/dist/templates/credential-awaiting-guardian.js.map +1 -1
  33. package/dist/templates/email-verification.d.ts +3 -1
  34. package/dist/templates/email-verification.d.ts.map +1 -1
  35. package/dist/templates/email-verification.js +57 -4
  36. package/dist/templates/email-verification.js.map +1 -1
  37. package/dist/templates/endorsement-request.d.ts +3 -1
  38. package/dist/templates/endorsement-request.d.ts.map +1 -1
  39. package/dist/templates/endorsement-request.js +53 -5
  40. package/dist/templates/endorsement-request.js.map +1 -1
  41. package/dist/templates/guardian-approval.d.ts +3 -1
  42. package/dist/templates/guardian-approval.d.ts.map +1 -1
  43. package/dist/templates/guardian-approval.js +55 -5
  44. package/dist/templates/guardian-approval.js.map +1 -1
  45. package/dist/templates/guardian-approved-claim.d.ts +3 -1
  46. package/dist/templates/guardian-approved-claim.d.ts.map +1 -1
  47. package/dist/templates/guardian-approved-claim.js +50 -4
  48. package/dist/templates/guardian-approved-claim.js.map +1 -1
  49. package/dist/templates/guardian-credential-approval.d.ts +3 -1
  50. package/dist/templates/guardian-credential-approval.d.ts.map +1 -1
  51. package/dist/templates/guardian-credential-approval.js +50 -4
  52. package/dist/templates/guardian-credential-approval.js.map +1 -1
  53. package/dist/templates/guardian-email-otp.d.ts +3 -1
  54. package/dist/templates/guardian-email-otp.d.ts.map +1 -1
  55. package/dist/templates/guardian-email-otp.js +55 -5
  56. package/dist/templates/guardian-email-otp.js.map +1 -1
  57. package/dist/templates/guardian-rejected-credential.d.ts +3 -1
  58. package/dist/templates/guardian-rejected-credential.d.ts.map +1 -1
  59. package/dist/templates/guardian-rejected-credential.js +45 -3
  60. package/dist/templates/guardian-rejected-credential.js.map +1 -1
  61. package/dist/templates/inbox-claim.d.ts +3 -1
  62. package/dist/templates/inbox-claim.d.ts.map +1 -1
  63. package/dist/templates/inbox-claim.js +63 -11
  64. package/dist/templates/inbox-claim.js.map +1 -1
  65. package/dist/templates/recovery-key.d.ts +3 -1
  66. package/dist/templates/recovery-key.d.ts.map +1 -1
  67. package/dist/templates/recovery-key.js +59 -3
  68. package/dist/templates/recovery-key.js.map +1 -1
  69. package/dist/templates/verification-code.d.ts +3 -1
  70. package/dist/templates/verification-code.d.ts.map +1 -1
  71. package/dist/templates/verification-code.js +153 -17
  72. package/dist/templates/verification-code.js.map +1 -1
  73. package/package.json +1 -1
  74. package/src/__tests__/i18n.test.ts +329 -0
  75. package/src/components/Layout.tsx +69 -60
  76. package/src/components/LinkFallback.tsx +19 -11
  77. package/src/i18n.ts +110 -0
  78. package/src/index.ts +15 -2
  79. package/src/render.ts +47 -28
  80. package/src/sms.ts +61 -6
  81. package/src/templates/account-approved.tsx +82 -29
  82. package/src/templates/credential-awaiting-guardian.tsx +113 -19
  83. package/src/templates/email-verification.tsx +87 -16
  84. package/src/templates/endorsement-request.tsx +142 -17
  85. package/src/templates/guardian-approval.tsx +106 -16
  86. package/src/templates/guardian-approved-claim.tsx +137 -17
  87. package/src/templates/guardian-credential-approval.tsx +112 -17
  88. package/src/templates/guardian-email-otp.tsx +103 -25
  89. package/src/templates/guardian-rejected-credential.tsx +137 -15
  90. package/src/templates/inbox-claim.tsx +180 -29
  91. package/src/templates/recovery-key.tsx +126 -30
  92. package/src/templates/verification-code.tsx +204 -38
@@ -13,6 +13,8 @@ import { DEFAULT_BRANDING } from '../branding';
13
13
  import { Layout } from '../components/Layout';
14
14
  import { EmailButton } from '../components/EmailButton';
15
15
  import { IssuerLogo } from '../components/IssuerLogo';
16
+ import type { NotificationLocale } from '../i18n';
17
+ import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
16
18
 
17
19
  export interface GuardianApprovedClaimProps {
18
20
  branding: TenantBranding;
@@ -23,44 +25,161 @@ export interface GuardianApprovedClaimProps {
23
25
  credential?: {
24
26
  name?: string;
25
27
  };
28
+ /** Recipient locale (BCP-47). Defaults to English. */
29
+ locale?: string;
26
30
  }
27
31
 
32
+ const STRINGS: Record<
33
+ NotificationLocale,
34
+ {
35
+ preview: string;
36
+ heading: string;
37
+ greatNews: string;
38
+ body: (
39
+ issuerName: string,
40
+ credentialName: string | undefined,
41
+ brandName: string
42
+ ) => React.ReactNode;
43
+ openApp: string;
44
+ button: (brandName: string) => string;
45
+ sincerely: string;
46
+ subject: string;
47
+ }
48
+ > = {
49
+ en: {
50
+ preview: 'Your credential has been approved by your guardian!',
51
+ heading: 'Your credential has been approved!',
52
+ greatNews: 'Great news!',
53
+ body: (issuerName, credentialName, brandName) => (
54
+ <>
55
+ Your guardian has approved the credential{' '}
56
+ {credentialName ? (
57
+ <>
58
+ <strong>&ldquo;{credentialName}&rdquo;</strong>{' '}
59
+ </>
60
+ ) : (
61
+ ''
62
+ )}
63
+ issued by <strong>{issuerName}</strong>. It is now available in your {brandName}{' '}
64
+ account.
65
+ </>
66
+ ),
67
+ openApp:
68
+ 'Open the app to see it \u2014 your credential should appear automatically. If you don\u2019t see it right away, try refreshing.',
69
+ button: b => `Open ${b} \u2192`,
70
+ sincerely: 'Sincerely,',
71
+ subject: 'Your Credential Has Been Approved!',
72
+ },
73
+ es: {
74
+ preview: '¡Tu credencial ha sido aprobada por tu tutor!',
75
+ heading: '¡Tu credencial ha sido aprobada!',
76
+ greatNews: '¡Buenas noticias!',
77
+ body: (issuerName, credentialName, brandName) => (
78
+ <>
79
+ Tu tutor ha aprobado la credencial{' '}
80
+ {credentialName ? (
81
+ <>
82
+ <strong>&ldquo;{credentialName}&rdquo;</strong>{' '}
83
+ </>
84
+ ) : (
85
+ ''
86
+ )}
87
+ emitida por <strong>{issuerName}</strong>. Ya está disponible en tu cuenta de{' '}
88
+ {brandName}.
89
+ </>
90
+ ),
91
+ openApp:
92
+ 'Abre la app para verla \u2014 tu credencial debería aparecer automáticamente. Si no la ves de inmediato, intenta actualizar.',
93
+ button: b => `Abrir ${b} \u2192`,
94
+ sincerely: 'Atentamente,',
95
+ subject: '¡Tu credencial ha sido aprobada!',
96
+ },
97
+ fr: {
98
+ preview: 'Votre titre a été approuvé par votre tuteur !',
99
+ heading: 'Votre titre a été approuvé !',
100
+ greatNews: 'Excellente nouvelle !',
101
+ body: (issuerName, credentialName, brandName) => (
102
+ <>
103
+ Votre tuteur a approuvé le titre{' '}
104
+ {credentialName ? (
105
+ <>
106
+ <strong>&laquo;&nbsp;{credentialName}&nbsp;&raquo;</strong>{' '}
107
+ </>
108
+ ) : (
109
+ ''
110
+ )}
111
+ émis par <strong>{issuerName}</strong>. Il est désormais disponible dans votre
112
+ compte {brandName}.
113
+ </>
114
+ ),
115
+ openApp:
116
+ 'Ouvrez l\u2019application pour le voir \u2014 votre titre devrait apparaître automatiquement. Si vous ne le voyez pas tout de suite, essayez d\u2019actualiser.',
117
+ button: b => `Ouvrir ${b} \u2192`,
118
+ sincerely: 'Cordialement,',
119
+ subject: 'Votre titre a été approuvé !',
120
+ },
121
+ ar: {
122
+ preview: 'تمت الموافقة على شهادتك من قبل ولي أمرك!',
123
+ heading: 'تمت الموافقة على شهادتك!',
124
+ greatNews: 'أخبار رائعة!',
125
+ body: (issuerName, credentialName, brandName) => (
126
+ <>
127
+ وافق ولي أمرك على الشهادة{' '}
128
+ {credentialName ? (
129
+ <>
130
+ <strong>&laquo;{credentialName}&raquo;</strong>{' '}
131
+ </>
132
+ ) : (
133
+ ''
134
+ )}
135
+ الصادرة من <strong>{issuerName}</strong>. وهي متاحة الآن في حسابك على {brandName}.
136
+ </>
137
+ ),
138
+ openApp:
139
+ 'افتح التطبيق لرؤيتها \u2014 ينبغي أن تظهر شهادتك تلقائيًا. إذا لم تظهرها على الفور، حاول التحديث.',
140
+ button: b => `فتح ${b} ←`,
141
+ sincerely: 'مع التحيات،',
142
+ subject: 'تمت الموافقة على شهادتك!',
143
+ },
144
+ };
145
+
28
146
  export const GuardianApprovedClaim: React.FC<GuardianApprovedClaimProps> = ({
29
147
  branding,
30
148
  issuer,
31
149
  credential,
150
+ locale,
32
151
  }) => {
152
+ const s = STRINGS[resolveCatalogLocale(locale)];
33
153
  const issuerName = issuer?.name ?? 'An issuer';
34
154
  const credentialName = credential?.name;
35
155
 
36
156
  return (
37
- <Layout branding={branding} preview="Your credential has been approved by your guardian!" showHeaderLogo={false}>
38
- <IssuerLogo logoUrl={issuer?.logoUrl} alt={issuerName ? `${issuerName} logo` : undefined} />
157
+ <Layout branding={branding} locale={locale} preview={s.preview} showHeaderLogo={false}>
158
+ <IssuerLogo
159
+ logoUrl={issuer?.logoUrl}
160
+ alt={issuerName ? `${issuerName} logo` : undefined}
161
+ />
39
162
 
40
- <Text style={heading}>Your credential has been approved!</Text>
163
+ <Text style={heading}>{s.heading}</Text>
41
164
 
42
- <Text style={paragraph}>Great news!</Text>
165
+ <Text style={paragraph}>{s.greatNews}</Text>
43
166
 
44
- <Text style={paragraph}>
45
- Your guardian has approved the credential{' '}
46
- {credentialName ? <><strong>&ldquo;{credentialName}&rdquo;</strong> </> : ''}
47
- issued by <strong>{issuerName}</strong>. It is now available in
48
- your {branding.brandName} account.
49
- </Text>
167
+ <Text style={paragraph}>{s.body(issuerName, credentialName, branding.brandName)}</Text>
50
168
 
51
- <Text style={paragraph}>
52
- Open the app to see it — your credential should appear automatically.
53
- If you don&apos;t see it right away, try refreshing.
54
- </Text>
169
+ <Text style={paragraph}>{s.openApp}</Text>
55
170
 
56
171
  <Section style={buttonWrapper}>
57
172
  <EmailButton href={branding.appUrl} branding={branding}>
58
- Open {branding.brandName} &rarr;
173
+ {s.button(branding.brandName)}
59
174
  </EmailButton>
60
175
  </Section>
61
176
 
62
177
  <Text style={signOff}>
63
- Sincerely,<br />The {branding.brandName} Team
178
+ {s.sincerely}
179
+ <br />
180
+ {interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
181
+ brandName: branding.brandName,
182
+ })}
64
183
  </Text>
65
184
  </Layout>
66
185
  );
@@ -68,7 +187,8 @@ export const GuardianApprovedClaim: React.FC<GuardianApprovedClaimProps> = ({
68
187
 
69
188
  export const getGuardianApprovedClaimSubject = (
70
189
  _branding: TenantBranding,
71
- ): string => 'Your Credential Has Been Approved!';
190
+ locale?: string
191
+ ): string => STRINGS[resolveCatalogLocale(locale)].subject;
72
192
 
73
193
  // ---------------------------------------------------------------------------
74
194
  // Styles
@@ -14,6 +14,8 @@ import { Layout } from '../components/Layout';
14
14
  import { EmailButton } from '../components/EmailButton';
15
15
  import { IssuerLogo } from '../components/IssuerLogo';
16
16
  import { LinkFallback } from '../components/LinkFallback';
17
+ import type { NotificationLocale } from '../i18n';
18
+ import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
17
19
 
18
20
  export interface GuardianCredentialApprovalProps {
19
21
  branding: TenantBranding;
@@ -29,56 +31,149 @@ export interface GuardianCredentialApprovalProps {
29
31
  recipient?: {
30
32
  email?: string;
31
33
  };
34
+ /** Recipient locale (BCP-47). Defaults to English. */
35
+ locale?: string;
32
36
  }
33
37
 
38
+ const STRINGS: Record<
39
+ NotificationLocale,
40
+ {
41
+ preview: (recipientEmail: string) => string;
42
+ heading: string;
43
+ greeting: string;
44
+ body: (
45
+ issuerName: string,
46
+ credentialName: string,
47
+ recipientEmail: string
48
+ ) => React.ReactNode;
49
+ review: string;
50
+ button: string;
51
+ thankYou: string;
52
+ subject: string;
53
+ }
54
+ > = {
55
+ en: {
56
+ preview: r => `A credential needs your approval for ${r}`,
57
+ heading: 'A credential needs your approval',
58
+ greeting: 'Hello,',
59
+ body: (issuerName, credentialName, recipientEmail) => (
60
+ <>
61
+ <strong>{issuerName}</strong> has issued the credential{' '}
62
+ <strong>&ldquo;{credentialName}&rdquo;</strong> to <strong>{recipientEmail}</strong>
63
+ .
64
+ </>
65
+ ),
66
+ review: 'As their guardian, your approval is required before they can claim it. Please review the credential and approve or decline below.',
67
+ button: 'Review &amp; Approve \u2192',
68
+ thankYou: 'Thank you,',
69
+ subject: 'A credential needs your approval',
70
+ },
71
+ es: {
72
+ preview: r => `Una credencial necesita tu aprobación para ${r}`,
73
+ heading: 'Una credencial necesita tu aprobación',
74
+ greeting: 'Hola,',
75
+ body: (issuerName, credentialName, recipientEmail) => (
76
+ <>
77
+ <strong>{issuerName}</strong> ha emitido la credencial{' '}
78
+ <strong>&ldquo;{credentialName}&rdquo;</strong> para{' '}
79
+ <strong>{recipientEmail}</strong>.
80
+ </>
81
+ ),
82
+ review: 'Como su tutor, se requiere tu aprobación antes de que pueda reclamarla. Revisa la credencial y apruébala o recházala a continuación.',
83
+ button: 'Revisar y aprobar \u2192',
84
+ thankYou: 'Gracias,',
85
+ subject: 'Una credencial necesita tu aprobación',
86
+ },
87
+ fr: {
88
+ preview: r => `Un titre nécessite votre approbation pour ${r}`,
89
+ heading: 'Un titre nécessite votre approbation',
90
+ greeting: 'Bonjour,',
91
+ body: (issuerName, credentialName, recipientEmail) => (
92
+ <>
93
+ <strong>{issuerName}</strong> a émis le titre{' '}
94
+ <strong>&laquo;&nbsp;{credentialName}&nbsp;&raquo;</strong> pour{' '}
95
+ <strong>{recipientEmail}</strong>.
96
+ </>
97
+ ),
98
+ review: 'En tant que son tuteur, votre approbation est requise avant qu\u2019il puisse le réclamer. Veuillez vérifier le titre et l\u2019approuver ou le refuser ci-dessous.',
99
+ button: 'Vérifier &amp; approuver \u2192',
100
+ thankYou: 'Merci,',
101
+ subject: 'Un titre nécessite votre approbation',
102
+ },
103
+ ar: {
104
+ preview: r => `شهادة بحاجة إلى موافقتك من أجل ${r}`,
105
+ heading: 'شهادة بحاجة إلى موافقتك',
106
+ greeting: 'مرحبًا،',
107
+ body: (issuerName, credentialName, recipientEmail) => (
108
+ <>
109
+ أصدر <strong>{issuerName}</strong> الشهادة{' '}
110
+ <strong>&laquo;{credentialName}&raquo;</strong> لـ <strong>{recipientEmail}</strong>
111
+ .
112
+ </>
113
+ ),
114
+ review: 'بصفتك ولي أمره، موافقتك مطلوبة قبل أن يتمكن من المطالبة بها. يرجى مراجعة الشهادة والموافقة عليها أو رفضها أدناه.',
115
+ button: 'مراجعة وموافقة ←',
116
+ thankYou: 'شكرًا،',
117
+ subject: 'شهادة بحاجة إلى موافقتك',
118
+ },
119
+ };
120
+
34
121
  export const GuardianCredentialApproval: React.FC<GuardianCredentialApprovalProps> = ({
35
122
  branding,
36
123
  approvalUrl,
37
124
  issuer,
38
125
  credential,
39
126
  recipient,
127
+ locale,
40
128
  }) => {
129
+ const s = STRINGS[resolveCatalogLocale(locale)];
41
130
  const issuerName = issuer?.name ?? 'An issuer';
42
131
  const credentialName = credential?.name ?? 'a credential';
43
132
  const recipientEmail = recipient?.email ?? 'a student';
44
133
 
45
134
  return (
46
- <Layout branding={branding} preview={`A credential needs your approval for ${recipientEmail}`} showHeaderLogo={false}>
47
- <IssuerLogo logoUrl={issuer?.logoUrl} alt={issuerName ? `${issuerName} logo` : undefined} />
135
+ <Layout
136
+ branding={branding}
137
+ locale={locale}
138
+ preview={s.preview(recipientEmail)}
139
+ showHeaderLogo={false}
140
+ >
141
+ <IssuerLogo
142
+ logoUrl={issuer?.logoUrl}
143
+ alt={issuerName ? `${issuerName} logo` : undefined}
144
+ />
48
145
 
49
- <Text style={heading}>A credential needs your approval</Text>
146
+ <Text style={heading}>{s.heading}</Text>
50
147
 
51
- <Text style={paragraph}>Hello,</Text>
148
+ <Text style={paragraph}>{s.greeting}</Text>
52
149
 
53
- <Text style={paragraph}>
54
- <strong>{issuerName}</strong> has issued the credential{' '}
55
- <strong>&ldquo;{credentialName}&rdquo;</strong> to{' '}
56
- <strong>{recipientEmail}</strong>.
57
- </Text>
150
+ <Text style={paragraph}>{s.body(issuerName, credentialName, recipientEmail)}</Text>
58
151
 
59
- <Text style={paragraph}>
60
- As their guardian, your approval is required before they can claim it.
61
- Please review the credential and approve or decline below.
62
- </Text>
152
+ <Text style={paragraph}>{s.review}</Text>
63
153
 
64
154
  <Section style={buttonWrapper}>
65
155
  <EmailButton href={approvalUrl} branding={branding}>
66
- Review &amp; Approve &rarr;
156
+ {s.button}
67
157
  </EmailButton>
68
158
  </Section>
69
159
 
70
160
  <Text style={signOff}>
71
- Thank you,<br />The {branding.brandName} Team
161
+ {s.thankYou}
162
+ <br />
163
+ {interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
164
+ brandName: branding.brandName,
165
+ })}
72
166
  </Text>
73
167
 
74
- <LinkFallback href={approvalUrl} />
168
+ <LinkFallback href={approvalUrl} locale={locale} />
75
169
  </Layout>
76
170
  );
77
171
  };
78
172
 
79
173
  export const getGuardianCredentialApprovalSubject = (
80
174
  _branding: TenantBranding,
81
- ): string => 'A credential needs your approval';
175
+ locale?: string
176
+ ): string => STRINGS[resolveCatalogLocale(locale)].subject;
82
177
 
83
178
  // ---------------------------------------------------------------------------
84
179
  // Styles
@@ -12,48 +12,131 @@ import type { TenantBranding } from '../branding';
12
12
  import { DEFAULT_BRANDING } from '../branding';
13
13
  import { Layout } from '../components/Layout';
14
14
  import { CodeBlock } from '../components/CodeBlock';
15
+ import type { NotificationLocale } from '../i18n';
16
+ import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
15
17
 
16
18
  export interface GuardianEmailOtpProps {
17
19
  branding: TenantBranding;
18
20
  verificationCode: string;
21
+ /** Recipient locale (BCP-47). Defaults to English. */
22
+ locale?: string;
19
23
  }
20
24
 
25
+ const STRINGS: Record<
26
+ NotificationLocale,
27
+ {
28
+ preview: (code: string) => string;
29
+ heading: string;
30
+ greeting: string;
31
+ body: string;
32
+ codeLabel: string;
33
+ expiry: () => React.ReactNode;
34
+ notRequested: string;
35
+ sincerely: string;
36
+ subject: string;
37
+ }
38
+ > = {
39
+ en: {
40
+ preview: c => `Your verification code: ${c}`,
41
+ heading: 'Your verification code',
42
+ greeting: 'Hi there,',
43
+ body: 'Here is your verification code to confirm your identity as guardian and complete your review:',
44
+ codeLabel: 'Verification code',
45
+ expiry: () => (
46
+ <>
47
+ This code expires in <strong style={{ color: '#374151' }}>1 hour</strong>. Do not
48
+ share it with anyone.
49
+ </>
50
+ ),
51
+ notRequested: 'If you did not request this code, you can safely ignore this email.',
52
+ sincerely: 'Sincerely,',
53
+ subject: 'Your Verification Code',
54
+ },
55
+ es: {
56
+ preview: c => `Tu código de verificación: ${c}`,
57
+ heading: 'Tu código de verificación',
58
+ greeting: 'Hola,',
59
+ body: 'Aquí tienes tu código de verificación para confirmar tu identidad como tutor y completar tu revisión:',
60
+ codeLabel: 'Código de verificación',
61
+ expiry: () => (
62
+ <>
63
+ Este código caduca en <strong style={{ color: '#374151' }}>1 hora</strong>. No lo
64
+ compartas con nadie.
65
+ </>
66
+ ),
67
+ notRequested: 'Si no solicitaste este código, puedes ignorar este correo con seguridad.',
68
+ sincerely: 'Atentamente,',
69
+ subject: 'Tu código de verificación',
70
+ },
71
+ fr: {
72
+ preview: c => `Votre code de vérification : ${c}`,
73
+ heading: 'Votre code de vérification',
74
+ greeting: 'Bonjour,',
75
+ body: 'Voici votre code de vérification pour confirmer votre identité en tant que tuteur et terminer votre révision :',
76
+ codeLabel: 'Code de vérification',
77
+ expiry: () => (
78
+ <>
79
+ Ce code expire dans <strong style={{ color: '#374151' }}>1 heure</strong>. Ne le
80
+ partagez avec personne.
81
+ </>
82
+ ),
83
+ notRequested:
84
+ 'Si vous n\u2019êtes pas à l\u2019origine de cette demande, vous pouvez ignorer cet e-mail en toute sécurité.',
85
+ sincerely: 'Cordialement,',
86
+ subject: 'Votre code de vérification',
87
+ },
88
+ ar: {
89
+ preview: c => `رمز التحقق الخاص بك: ${c}`,
90
+ heading: 'رمز التحقق الخاص بك',
91
+ greeting: 'مرحبًا،',
92
+ body: 'إليك رمز التحقق لتأكيد هويتك كولي أمر وإكمال مراجعتك:',
93
+ codeLabel: 'رمز التحقق',
94
+ expiry: () => (
95
+ <>
96
+ تنتهي صلاحية هذا الرمز خلال <strong style={{ color: '#374151' }}>ساعة واحدة</strong>
97
+ . لا تشاركه مع أي شخص.
98
+ </>
99
+ ),
100
+ notRequested: 'إذا لم تطلب هذا الرمز، يمكنك تجاهل هذه الرسالة بأمان.',
101
+ sincerely: 'مع التحيات،',
102
+ subject: 'رمز التحقق الخاص بك',
103
+ },
104
+ };
105
+
21
106
  export const GuardianEmailOtp: React.FC<GuardianEmailOtpProps> = ({
22
107
  branding,
23
108
  verificationCode,
109
+ locale,
24
110
  }) => {
111
+ const s = STRINGS[resolveCatalogLocale(locale)];
112
+
25
113
  return (
26
- <Layout branding={branding} preview={`Your verification code: ${verificationCode}`}>
27
- <Text style={heading}>Your verification code</Text>
114
+ <Layout branding={branding} locale={locale} preview={s.preview(verificationCode)}>
115
+ <Text style={heading}>{s.heading}</Text>
28
116
 
29
- <Text style={paragraph}>Hi there,</Text>
117
+ <Text style={paragraph}>{s.greeting}</Text>
30
118
 
31
- <Text style={paragraph}>
32
- Here is your verification code to confirm your identity as guardian and
33
- complete your review:
34
- </Text>
119
+ <Text style={paragraph}>{s.body}</Text>
35
120
 
36
- <CodeBlock code={verificationCode} label="Verification code" />
121
+ <CodeBlock code={verificationCode} label={s.codeLabel} />
37
122
 
38
- <Text style={muted}>
39
- This code expires in <strong style={{ color: '#374151' }}>1 hour</strong>.
40
- Do not share it with anyone.
41
- </Text>
123
+ <Text style={muted}>{s.expiry()}</Text>
42
124
 
43
- <Text style={muted}>
44
- If you did not request this code, you can safely ignore this email.
45
- </Text>
125
+ <Text style={muted}>{s.notRequested}</Text>
46
126
 
47
127
  <Text style={signOff}>
48
- Sincerely,<br />The {branding.brandName} Team
128
+ {s.sincerely}
129
+ <br />
130
+ {interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
131
+ brandName: branding.brandName,
132
+ })}
49
133
  </Text>
50
134
  </Layout>
51
135
  );
52
136
  };
53
137
 
54
- export const getGuardianEmailOtpSubject = (
55
- _branding: TenantBranding,
56
- ): string => 'Your Verification Code';
138
+ export const getGuardianEmailOtpSubject = (_branding: TenantBranding, locale?: string): string =>
139
+ STRINGS[resolveCatalogLocale(locale)].subject;
57
140
 
58
141
  // ---------------------------------------------------------------------------
59
142
  // Styles
@@ -92,10 +175,5 @@ const signOff: React.CSSProperties = {
92
175
  // ---------------------------------------------------------------------------
93
176
 
94
177
  export default function Preview() {
95
- return (
96
- <GuardianEmailOtp
97
- branding={DEFAULT_BRANDING}
98
- verificationCode="847293"
99
- />
100
- );
178
+ return <GuardianEmailOtp branding={DEFAULT_BRANDING} verificationCode="847293" />;
101
179
  }