@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.
- package/dist/components/Layout.d.ts +2 -0
- package/dist/components/Layout.d.ts.map +1 -1
- package/dist/components/Layout.js +6 -1
- package/dist/components/Layout.js.map +1 -1
- package/dist/components/LinkFallback.d.ts +2 -0
- package/dist/components/LinkFallback.d.ts.map +1 -1
- package/dist/components/LinkFallback.js +5 -1
- package/dist/components/LinkFallback.js.map +1 -1
- package/dist/i18n.d.ts +52 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +77 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +3 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +33 -30
- package/dist/render.js.map +1 -1
- package/dist/sms.d.ts +6 -3
- package/dist/sms.d.ts.map +1 -1
- package/dist/sms.js +43 -7
- package/dist/sms.js.map +1 -1
- package/dist/templates/account-approved.d.ts +3 -1
- package/dist/templates/account-approved.d.ts.map +1 -1
- package/dist/templates/account-approved.js +55 -5
- package/dist/templates/account-approved.js.map +1 -1
- package/dist/templates/credential-awaiting-guardian.d.ts +3 -1
- package/dist/templates/credential-awaiting-guardian.d.ts.map +1 -1
- package/dist/templates/credential-awaiting-guardian.js +54 -4
- package/dist/templates/credential-awaiting-guardian.js.map +1 -1
- package/dist/templates/email-verification.d.ts +3 -1
- package/dist/templates/email-verification.d.ts.map +1 -1
- package/dist/templates/email-verification.js +57 -4
- package/dist/templates/email-verification.js.map +1 -1
- package/dist/templates/endorsement-request.d.ts +3 -1
- package/dist/templates/endorsement-request.d.ts.map +1 -1
- package/dist/templates/endorsement-request.js +53 -5
- package/dist/templates/endorsement-request.js.map +1 -1
- package/dist/templates/guardian-approval.d.ts +3 -1
- package/dist/templates/guardian-approval.d.ts.map +1 -1
- package/dist/templates/guardian-approval.js +55 -5
- package/dist/templates/guardian-approval.js.map +1 -1
- package/dist/templates/guardian-approved-claim.d.ts +3 -1
- package/dist/templates/guardian-approved-claim.d.ts.map +1 -1
- package/dist/templates/guardian-approved-claim.js +50 -4
- package/dist/templates/guardian-approved-claim.js.map +1 -1
- package/dist/templates/guardian-credential-approval.d.ts +3 -1
- package/dist/templates/guardian-credential-approval.d.ts.map +1 -1
- package/dist/templates/guardian-credential-approval.js +50 -4
- package/dist/templates/guardian-credential-approval.js.map +1 -1
- package/dist/templates/guardian-email-otp.d.ts +3 -1
- package/dist/templates/guardian-email-otp.d.ts.map +1 -1
- package/dist/templates/guardian-email-otp.js +55 -5
- package/dist/templates/guardian-email-otp.js.map +1 -1
- package/dist/templates/guardian-rejected-credential.d.ts +3 -1
- package/dist/templates/guardian-rejected-credential.d.ts.map +1 -1
- package/dist/templates/guardian-rejected-credential.js +45 -3
- package/dist/templates/guardian-rejected-credential.js.map +1 -1
- package/dist/templates/inbox-claim.d.ts +3 -1
- package/dist/templates/inbox-claim.d.ts.map +1 -1
- package/dist/templates/inbox-claim.js +63 -11
- package/dist/templates/inbox-claim.js.map +1 -1
- package/dist/templates/recovery-key.d.ts +3 -1
- package/dist/templates/recovery-key.d.ts.map +1 -1
- package/dist/templates/recovery-key.js +59 -3
- package/dist/templates/recovery-key.js.map +1 -1
- package/dist/templates/verification-code.d.ts +3 -1
- package/dist/templates/verification-code.d.ts.map +1 -1
- package/dist/templates/verification-code.js +153 -17
- package/dist/templates/verification-code.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/i18n.test.ts +329 -0
- package/src/components/Layout.tsx +69 -60
- package/src/components/LinkFallback.tsx +19 -11
- package/src/i18n.ts +110 -0
- package/src/index.ts +15 -2
- package/src/render.ts +47 -28
- package/src/sms.ts +61 -6
- package/src/templates/account-approved.tsx +82 -29
- package/src/templates/credential-awaiting-guardian.tsx +113 -19
- package/src/templates/email-verification.tsx +87 -16
- package/src/templates/endorsement-request.tsx +142 -17
- package/src/templates/guardian-approval.tsx +106 -16
- package/src/templates/guardian-approved-claim.tsx +137 -17
- package/src/templates/guardian-credential-approval.tsx +112 -17
- package/src/templates/guardian-email-otp.tsx +103 -25
- package/src/templates/guardian-rejected-credential.tsx +137 -15
- package/src/templates/inbox-claim.tsx +180 -29
- package/src/templates/recovery-key.tsx +126 -30
- 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>“{credentialName}”</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>“{credentialName}”</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>« {credentialName} »</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>«{credentialName}»</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=
|
|
38
|
-
<IssuerLogo
|
|
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}>
|
|
163
|
+
<Text style={heading}>{s.heading}</Text>
|
|
41
164
|
|
|
42
|
-
<Text style={paragraph}>
|
|
165
|
+
<Text style={paragraph}>{s.greatNews}</Text>
|
|
43
166
|
|
|
44
|
-
<Text style={paragraph}>
|
|
45
|
-
Your guardian has approved the credential{' '}
|
|
46
|
-
{credentialName ? <><strong>“{credentialName}”</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'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
|
-
|
|
173
|
+
{s.button(branding.brandName)}
|
|
59
174
|
</EmailButton>
|
|
60
175
|
</Section>
|
|
61
176
|
|
|
62
177
|
<Text style={signOff}>
|
|
63
|
-
|
|
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
|
-
|
|
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>“{credentialName}”</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 & 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>“{credentialName}”</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>« {credentialName} »</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 & 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>«{credentialName}»</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
|
|
47
|
-
|
|
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}>
|
|
146
|
+
<Text style={heading}>{s.heading}</Text>
|
|
50
147
|
|
|
51
|
-
<Text style={paragraph}>
|
|
148
|
+
<Text style={paragraph}>{s.greeting}</Text>
|
|
52
149
|
|
|
53
|
-
<Text style={paragraph}>
|
|
54
|
-
<strong>{issuerName}</strong> has issued the credential{' '}
|
|
55
|
-
<strong>“{credentialName}”</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
|
-
|
|
156
|
+
{s.button}
|
|
67
157
|
</EmailButton>
|
|
68
158
|
</Section>
|
|
69
159
|
|
|
70
160
|
<Text style={signOff}>
|
|
71
|
-
|
|
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
|
-
|
|
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}
|
|
27
|
-
<Text style={heading}>
|
|
114
|
+
<Layout branding={branding} locale={locale} preview={s.preview(verificationCode)}>
|
|
115
|
+
<Text style={heading}>{s.heading}</Text>
|
|
28
116
|
|
|
29
|
-
<Text style={paragraph}>
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|