@learncard/email-templates 1.0.4 → 1.0.6
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/branding.d.ts.map +1 -1
- 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/dist/tenant-registry.d.ts.map +1 -1
- package/package.json +4 -3
- 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
|
@@ -15,6 +15,8 @@ import { DEFAULT_BRANDING } from '../branding';
|
|
|
15
15
|
import { Layout } from '../components/Layout';
|
|
16
16
|
import { EmailButton } from '../components/EmailButton';
|
|
17
17
|
import { LinkFallback } from '../components/LinkFallback';
|
|
18
|
+
import type { NotificationLocale } from '../i18n';
|
|
19
|
+
import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
|
|
18
20
|
|
|
19
21
|
export interface EmailVerificationProps {
|
|
20
22
|
branding: TenantBranding;
|
|
@@ -22,49 +24,118 @@ export interface EmailVerificationProps {
|
|
|
22
24
|
recipient?: {
|
|
23
25
|
name?: string;
|
|
24
26
|
};
|
|
27
|
+
/** Recipient locale (BCP-47). Defaults to English. */
|
|
28
|
+
locale?: string;
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
const STRINGS: Record<
|
|
32
|
+
NotificationLocale,
|
|
33
|
+
{
|
|
34
|
+
preview: string;
|
|
35
|
+
heading: string;
|
|
36
|
+
greeting: string;
|
|
37
|
+
greetingName: string;
|
|
38
|
+
paragraph: string;
|
|
39
|
+
passport: string;
|
|
40
|
+
button: string;
|
|
41
|
+
sincerely: string;
|
|
42
|
+
subject: string;
|
|
43
|
+
}
|
|
44
|
+
> = {
|
|
45
|
+
en: {
|
|
46
|
+
preview: 'Verify your email address',
|
|
47
|
+
heading: 'Verify Your Email',
|
|
48
|
+
greeting: 'Hello,',
|
|
49
|
+
greetingName: 'Hello {name},',
|
|
50
|
+
paragraph: 'Please click the button below to verify your email address.',
|
|
51
|
+
passport:
|
|
52
|
+
'{brandName} is your private, digital passport for learning and work. It lets you securely collect and share your verified skills and achievements online.',
|
|
53
|
+
button: 'Verify Your Email \u2192',
|
|
54
|
+
sincerely: 'Sincerely,',
|
|
55
|
+
subject: 'Verify Your Email',
|
|
56
|
+
},
|
|
57
|
+
es: {
|
|
58
|
+
preview: 'Verifica tu dirección de correo',
|
|
59
|
+
heading: 'Verifica tu correo',
|
|
60
|
+
greeting: 'Hola,',
|
|
61
|
+
greetingName: 'Hola {name},',
|
|
62
|
+
paragraph: 'Haz clic en el botón siguiente para verificar tu dirección de correo.',
|
|
63
|
+
passport:
|
|
64
|
+
'{brandName} es tu pasaporte digital y privado para el aprendizaje y el trabajo. Te permite recopilar y compartir de forma segura tus habilidades y logros verificados en línea.',
|
|
65
|
+
button: 'Verifica tu correo \u2192',
|
|
66
|
+
sincerely: 'Atentamente,',
|
|
67
|
+
subject: 'Verifica tu correo',
|
|
68
|
+
},
|
|
69
|
+
fr: {
|
|
70
|
+
preview: 'Vérifiez votre adresse e-mail',
|
|
71
|
+
heading: 'Vérifiez votre e-mail',
|
|
72
|
+
greeting: 'Bonjour,',
|
|
73
|
+
greetingName: 'Bonjour {name},',
|
|
74
|
+
paragraph: 'Veuillez cliquer sur le bouton ci-dessous pour vérifier votre adresse e-mail.',
|
|
75
|
+
passport:
|
|
76
|
+
'{brandName} est votre passeport numérique privé pour l\u2019apprentissage et le travail. Il vous permet de collecter et de partager en toute sécurité vos compétences et réalisations vérifiées en ligne.',
|
|
77
|
+
button: 'Vérifiez votre e-mail \u2192',
|
|
78
|
+
sincerely: 'Cordialement,',
|
|
79
|
+
subject: 'Vérifiez votre e-mail',
|
|
80
|
+
},
|
|
81
|
+
ar: {
|
|
82
|
+
preview: 'تأكيد عنوان بريدك الإلكتروني',
|
|
83
|
+
heading: 'تأكيد بريدك الإلكتروني',
|
|
84
|
+
greeting: 'مرحبًا،',
|
|
85
|
+
greetingName: 'مرحبًا {name}،',
|
|
86
|
+
paragraph: 'يرجى النقر على الزر أدناه لتأكيد عنوان بريدك الإلكتروني.',
|
|
87
|
+
passport:
|
|
88
|
+
'{brandName} هو جواز سفرك الرقمي والخاص للتعلّم والعمل. يتيح لك جمع ومشاركة مهاراتك وإنجازاتك الموثّقة بأمان عبر الإنترنت.',
|
|
89
|
+
button: 'تأكيد بريدك الإلكتروني ←',
|
|
90
|
+
sincerely: 'مع التحيات،',
|
|
91
|
+
subject: 'تأكيد بريدك الإلكتروني',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
27
95
|
export const EmailVerification: React.FC<EmailVerificationProps> = ({
|
|
28
96
|
branding,
|
|
29
97
|
verificationToken,
|
|
30
98
|
recipient,
|
|
99
|
+
locale,
|
|
31
100
|
}) => {
|
|
32
|
-
const
|
|
101
|
+
const s = STRINGS[resolveCatalogLocale(locale)];
|
|
102
|
+
const greeting = interpolate(recipient?.name ? s.greetingName : s.greeting, {
|
|
103
|
+
name: recipient?.name,
|
|
104
|
+
});
|
|
33
105
|
const verifyUrl = `${branding.appUrl}/verify-email?token=${verificationToken}`;
|
|
106
|
+
const passport = interpolate(s.passport, { brandName: branding.brandName });
|
|
34
107
|
|
|
35
108
|
return (
|
|
36
|
-
<Layout branding={branding} preview=
|
|
37
|
-
<Text style={heading}>
|
|
109
|
+
<Layout branding={branding} locale={locale} preview={s.preview}>
|
|
110
|
+
<Text style={heading}>{s.heading}</Text>
|
|
38
111
|
|
|
39
112
|
<Text style={paragraph}>{greeting}</Text>
|
|
40
113
|
|
|
41
|
-
<Text style={paragraph}>
|
|
42
|
-
Please click the button below to verify your email address.
|
|
43
|
-
</Text>
|
|
114
|
+
<Text style={paragraph}>{s.paragraph}</Text>
|
|
44
115
|
|
|
45
|
-
<Text style={paragraph}>
|
|
46
|
-
{branding.brandName} is your private, digital passport for learning and work.
|
|
47
|
-
It lets you securely collect and share your verified skills and achievements online.
|
|
48
|
-
</Text>
|
|
116
|
+
<Text style={paragraph}>{passport}</Text>
|
|
49
117
|
|
|
50
118
|
<Section style={buttonWrapper}>
|
|
51
119
|
<EmailButton href={verifyUrl} branding={branding}>
|
|
52
|
-
|
|
120
|
+
{s.button}
|
|
53
121
|
</EmailButton>
|
|
54
122
|
</Section>
|
|
55
123
|
|
|
56
124
|
<Text style={signOff}>
|
|
57
|
-
|
|
125
|
+
{s.sincerely}
|
|
126
|
+
<br />
|
|
127
|
+
{interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
|
|
128
|
+
brandName: branding.brandName,
|
|
129
|
+
})}
|
|
58
130
|
</Text>
|
|
59
131
|
|
|
60
|
-
<LinkFallback href={verifyUrl} />
|
|
132
|
+
<LinkFallback href={verifyUrl} locale={locale} />
|
|
61
133
|
</Layout>
|
|
62
134
|
);
|
|
63
135
|
};
|
|
64
136
|
|
|
65
|
-
export const getEmailVerificationSubject = (
|
|
66
|
-
|
|
67
|
-
): string => 'Verify Your Email';
|
|
137
|
+
export const getEmailVerificationSubject = (_branding: TenantBranding, locale?: string): string =>
|
|
138
|
+
STRINGS[resolveCatalogLocale(locale)].subject;
|
|
68
139
|
|
|
69
140
|
// ---------------------------------------------------------------------------
|
|
70
141
|
// Styles
|
|
@@ -13,6 +13,8 @@ import { Layout } from '../components/Layout';
|
|
|
13
13
|
import { EmailButton } from '../components/EmailButton';
|
|
14
14
|
import { IssuerLogo } from '../components/IssuerLogo';
|
|
15
15
|
import { LinkFallback } from '../components/LinkFallback';
|
|
16
|
+
import type { NotificationLocale } from '../i18n';
|
|
17
|
+
import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
|
|
16
18
|
|
|
17
19
|
export interface EndorsementRequestProps {
|
|
18
20
|
branding: TenantBranding;
|
|
@@ -30,8 +32,121 @@ export interface EndorsementRequestProps {
|
|
|
30
32
|
type?: string;
|
|
31
33
|
};
|
|
32
34
|
message?: string;
|
|
35
|
+
/** Recipient locale (BCP-47). Defaults to English. */
|
|
36
|
+
locale?: string;
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
const STRINGS: Record<
|
|
40
|
+
NotificationLocale,
|
|
41
|
+
{
|
|
42
|
+
preview: (issuerName: string) => string;
|
|
43
|
+
greeting: string;
|
|
44
|
+
greetingName: string;
|
|
45
|
+
body: (issuerName: string, credentialName: string | undefined) => React.ReactNode;
|
|
46
|
+
passport: string;
|
|
47
|
+
button: string;
|
|
48
|
+
sincerely: string;
|
|
49
|
+
subject: (issuerName: string) => string;
|
|
50
|
+
}
|
|
51
|
+
> = {
|
|
52
|
+
en: {
|
|
53
|
+
preview: i => `${i} is requesting your endorsement`,
|
|
54
|
+
greeting: 'Hello,',
|
|
55
|
+
greetingName: 'Hello {name},',
|
|
56
|
+
body: (issuerName, credentialName) => (
|
|
57
|
+
<>
|
|
58
|
+
<strong>{issuerName}</strong> has requested an endorsement from you
|
|
59
|
+
{credentialName ? (
|
|
60
|
+
<>
|
|
61
|
+
{' '}
|
|
62
|
+
for <strong>{credentialName}</strong>
|
|
63
|
+
</>
|
|
64
|
+
) : (
|
|
65
|
+
''
|
|
66
|
+
)}
|
|
67
|
+
.
|
|
68
|
+
</>
|
|
69
|
+
),
|
|
70
|
+
passport:
|
|
71
|
+
'{brandName} is your private, digital passport for learning and work. It lets you securely collect and share your verified skills and achievements online.',
|
|
72
|
+
button: 'Endorse \u2192',
|
|
73
|
+
sincerely: 'Sincerely,',
|
|
74
|
+
subject: i => `${i} is requesting your endorsement`,
|
|
75
|
+
},
|
|
76
|
+
es: {
|
|
77
|
+
preview: i => `${i} solicita tu validación`,
|
|
78
|
+
greeting: 'Hola,',
|
|
79
|
+
greetingName: 'Hola {name},',
|
|
80
|
+
body: (issuerName, credentialName) => (
|
|
81
|
+
<>
|
|
82
|
+
<strong>{issuerName}</strong> te ha solicitado una validación
|
|
83
|
+
{credentialName ? (
|
|
84
|
+
<>
|
|
85
|
+
{' '}
|
|
86
|
+
para <strong>{credentialName}</strong>
|
|
87
|
+
</>
|
|
88
|
+
) : (
|
|
89
|
+
''
|
|
90
|
+
)}
|
|
91
|
+
.
|
|
92
|
+
</>
|
|
93
|
+
),
|
|
94
|
+
passport:
|
|
95
|
+
'{brandName} es tu pasaporte digital y privado para el aprendizaje y el trabajo. Te permite recopilar y compartir de forma segura tus habilidades y logros verificados en línea.',
|
|
96
|
+
button: 'Validar \u2192',
|
|
97
|
+
sincerely: 'Atentamente,',
|
|
98
|
+
subject: i => `${i} solicita tu validación`,
|
|
99
|
+
},
|
|
100
|
+
fr: {
|
|
101
|
+
preview: i => `${i} vous demande une validation`,
|
|
102
|
+
greeting: 'Bonjour,',
|
|
103
|
+
greetingName: 'Bonjour {name},',
|
|
104
|
+
body: (issuerName, credentialName) => (
|
|
105
|
+
<>
|
|
106
|
+
<strong>{issuerName}</strong> vous a demandé une validation
|
|
107
|
+
{credentialName ? (
|
|
108
|
+
<>
|
|
109
|
+
{' '}
|
|
110
|
+
pour <strong>{credentialName}</strong>
|
|
111
|
+
</>
|
|
112
|
+
) : (
|
|
113
|
+
''
|
|
114
|
+
)}
|
|
115
|
+
.
|
|
116
|
+
</>
|
|
117
|
+
),
|
|
118
|
+
passport:
|
|
119
|
+
'{brandName} est votre passeport numérique privé pour l\u2019apprentissage et le travail. Il vous permet de collecter et de partager en toute sécurité vos compétences et réalisations vérifiées en ligne.',
|
|
120
|
+
button: 'Valider \u2192',
|
|
121
|
+
sincerely: 'Cordialement,',
|
|
122
|
+
subject: i => `${i} vous demande une validation`,
|
|
123
|
+
},
|
|
124
|
+
ar: {
|
|
125
|
+
preview: i => `${i} يطلب منك التأييد`,
|
|
126
|
+
greeting: 'مرحبًا،',
|
|
127
|
+
greetingName: 'مرحبًا {name}،',
|
|
128
|
+
body: (issuerName, credentialName) => (
|
|
129
|
+
<>
|
|
130
|
+
طلب <strong>{issuerName}</strong> منك تأييدًا
|
|
131
|
+
{credentialName ? (
|
|
132
|
+
<>
|
|
133
|
+
{' '}
|
|
134
|
+
لـ <strong>{credentialName}</strong>
|
|
135
|
+
</>
|
|
136
|
+
) : (
|
|
137
|
+
''
|
|
138
|
+
)}
|
|
139
|
+
.
|
|
140
|
+
</>
|
|
141
|
+
),
|
|
142
|
+
passport:
|
|
143
|
+
'{brandName} هو جواز سفرك الرقمي والخاص للتعلّم والعمل. يتيح لك جمع ومشاركة مهاراتك وإنجازاتك الموثّقة بأمان عبر الإنترنت.',
|
|
144
|
+
button: 'تأييد ←',
|
|
145
|
+
sincerely: 'مع التحيات،',
|
|
146
|
+
subject: i => `${i} يطلب منك التأييد`,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
35
150
|
export const EndorsementRequest: React.FC<EndorsementRequestProps> = ({
|
|
36
151
|
branding,
|
|
37
152
|
shareLink,
|
|
@@ -39,48 +154,58 @@ export const EndorsementRequest: React.FC<EndorsementRequestProps> = ({
|
|
|
39
154
|
issuer,
|
|
40
155
|
credential,
|
|
41
156
|
message,
|
|
157
|
+
locale,
|
|
42
158
|
}) => {
|
|
43
|
-
const
|
|
159
|
+
const s = STRINGS[resolveCatalogLocale(locale)];
|
|
160
|
+
const greeting = interpolate(recipient?.name ? s.greetingName : s.greeting, {
|
|
161
|
+
name: recipient?.name,
|
|
162
|
+
});
|
|
44
163
|
const issuerName = issuer?.name ?? 'Someone';
|
|
45
164
|
const credentialName = credential?.name;
|
|
46
165
|
|
|
47
166
|
return (
|
|
48
|
-
<Layout
|
|
49
|
-
|
|
167
|
+
<Layout
|
|
168
|
+
branding={branding}
|
|
169
|
+
locale={locale}
|
|
170
|
+
preview={s.preview(issuerName)}
|
|
171
|
+
showHeaderLogo={false}
|
|
172
|
+
>
|
|
173
|
+
<IssuerLogo
|
|
174
|
+
logoUrl={issuer?.logoUrl}
|
|
175
|
+
alt={issuerName ? `${issuerName} logo` : undefined}
|
|
176
|
+
/>
|
|
50
177
|
|
|
51
178
|
<Text style={paragraph}>{greeting}</Text>
|
|
52
179
|
|
|
53
|
-
<Text style={paragraph}>
|
|
54
|
-
<strong>{issuerName}</strong> has requested an endorsement from you
|
|
55
|
-
{credentialName ? <> for <strong>{credentialName}</strong></> : ''}.
|
|
56
|
-
</Text>
|
|
180
|
+
<Text style={paragraph}>{s.body(issuerName, credentialName)}</Text>
|
|
57
181
|
|
|
58
182
|
{message && (
|
|
59
183
|
<Section style={messageBox}>
|
|
60
184
|
<Text style={messageText}>{message}</Text>
|
|
61
185
|
|
|
62
|
-
{issuer?.name &&
|
|
63
|
-
<Text style={messageSender}>— {issuer.name}</Text>
|
|
64
|
-
)}
|
|
186
|
+
{issuer?.name && <Text style={messageSender}>— {issuer.name}</Text>}
|
|
65
187
|
</Section>
|
|
66
188
|
)}
|
|
67
189
|
|
|
68
190
|
<Text style={paragraph}>
|
|
69
|
-
{
|
|
70
|
-
It lets you securely collect and share your verified skills and achievements online.
|
|
191
|
+
{interpolate(s.passport, { brandName: branding.brandName })}
|
|
71
192
|
</Text>
|
|
72
193
|
|
|
73
194
|
<Section style={buttonWrapper}>
|
|
74
195
|
<EmailButton href={shareLink} branding={branding}>
|
|
75
|
-
|
|
196
|
+
{s.button}
|
|
76
197
|
</EmailButton>
|
|
77
198
|
</Section>
|
|
78
199
|
|
|
79
200
|
<Text style={signOff}>
|
|
80
|
-
|
|
201
|
+
{s.sincerely}
|
|
202
|
+
<br />
|
|
203
|
+
{interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
|
|
204
|
+
brandName: branding.brandName,
|
|
205
|
+
})}
|
|
81
206
|
</Text>
|
|
82
207
|
|
|
83
|
-
<LinkFallback href={shareLink} />
|
|
208
|
+
<LinkFallback href={shareLink} locale={locale} />
|
|
84
209
|
</Layout>
|
|
85
210
|
);
|
|
86
211
|
};
|
|
@@ -88,10 +213,10 @@ export const EndorsementRequest: React.FC<EndorsementRequestProps> = ({
|
|
|
88
213
|
export const getEndorsementRequestSubject = (
|
|
89
214
|
_branding: TenantBranding,
|
|
90
215
|
props: EndorsementRequestProps,
|
|
216
|
+
locale?: string
|
|
91
217
|
): string => {
|
|
92
218
|
const issuerName = props.issuer?.name ?? 'Someone';
|
|
93
|
-
|
|
94
|
-
return `${issuerName} is requesting your endorsement`;
|
|
219
|
+
return STRINGS[resolveCatalogLocale(locale)].subject(issuerName);
|
|
95
220
|
};
|
|
96
221
|
|
|
97
222
|
// ---------------------------------------------------------------------------
|
|
@@ -12,6 +12,8 @@ import { DEFAULT_BRANDING } from '../branding';
|
|
|
12
12
|
import { Layout } from '../components/Layout';
|
|
13
13
|
import { EmailButton } from '../components/EmailButton';
|
|
14
14
|
import { LinkFallback } from '../components/LinkFallback';
|
|
15
|
+
import type { NotificationLocale } from '../i18n';
|
|
16
|
+
import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
|
|
15
17
|
|
|
16
18
|
export interface GuardianApprovalProps {
|
|
17
19
|
branding: TenantBranding;
|
|
@@ -24,45 +26,133 @@ export interface GuardianApprovalProps {
|
|
|
24
26
|
guardian?: {
|
|
25
27
|
email?: string;
|
|
26
28
|
};
|
|
29
|
+
/** Recipient locale (BCP-47). Defaults to English. */
|
|
30
|
+
locale?: string;
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
const STRINGS: Record<
|
|
34
|
+
NotificationLocale,
|
|
35
|
+
{
|
|
36
|
+
preview: (requesterName: string, brandName: string) => string;
|
|
37
|
+
heading: string;
|
|
38
|
+
greeting: string;
|
|
39
|
+
body: (requesterName: string, brandName: string) => React.ReactNode;
|
|
40
|
+
passport: string;
|
|
41
|
+
ctaInstruction: string;
|
|
42
|
+
button: string;
|
|
43
|
+
thankYou: string;
|
|
44
|
+
subject: (requesterName: string, brandName: string) => string;
|
|
45
|
+
}
|
|
46
|
+
> = {
|
|
47
|
+
en: {
|
|
48
|
+
preview: (n, b) => `${n} needs your approval to join ${b}`,
|
|
49
|
+
heading: 'Account Approval Request',
|
|
50
|
+
greeting: 'Hello,',
|
|
51
|
+
body: (n, b) => (
|
|
52
|
+
<>
|
|
53
|
+
<strong>{n}</strong> has requested approval to use their {b} account.
|
|
54
|
+
</>
|
|
55
|
+
),
|
|
56
|
+
passport:
|
|
57
|
+
'{brandName} is a private, digital passport for learning and work. It lets users securely collect and share their verified skills and achievements online.',
|
|
58
|
+
ctaInstruction: 'Please click the button below to approve this account request.',
|
|
59
|
+
button: 'Approve Request \u2192',
|
|
60
|
+
thankYou: 'Thank you,',
|
|
61
|
+
subject: (n, b) => `${n} needs your approval to join ${b}`,
|
|
62
|
+
},
|
|
63
|
+
es: {
|
|
64
|
+
preview: (n, b) => `${n} necesita tu aprobación para unirse a ${b}`,
|
|
65
|
+
heading: 'Solicitud de aprobación de cuenta',
|
|
66
|
+
greeting: 'Hola,',
|
|
67
|
+
body: (n, b) => (
|
|
68
|
+
<>
|
|
69
|
+
<strong>{n}</strong> ha solicitado aprobación para usar su cuenta de {b}.
|
|
70
|
+
</>
|
|
71
|
+
),
|
|
72
|
+
passport:
|
|
73
|
+
'{brandName} es un pasaporte digital y privado para el aprendizaje y el trabajo. Permite a los usuarios recopilar y compartir de forma segura sus habilidades y logros verificados en línea.',
|
|
74
|
+
ctaInstruction: 'Haz clic en el botón siguiente para aprobar esta solicitud de cuenta.',
|
|
75
|
+
button: 'Aprobar solicitud \u2192',
|
|
76
|
+
thankYou: 'Gracias,',
|
|
77
|
+
subject: (n, b) => `${n} necesita tu aprobación para unirse a ${b}`,
|
|
78
|
+
},
|
|
79
|
+
fr: {
|
|
80
|
+
preview: (n, b) => `${n} a besoin de votre approbation pour rejoindre ${b}`,
|
|
81
|
+
heading: "Demande d'approbation de compte",
|
|
82
|
+
greeting: 'Bonjour,',
|
|
83
|
+
body: (n, b) => (
|
|
84
|
+
<>
|
|
85
|
+
<strong>{n}</strong> a demandé l\u2019approbation pour utiliser son compte {b}.
|
|
86
|
+
</>
|
|
87
|
+
),
|
|
88
|
+
passport:
|
|
89
|
+
'{brandName} est un passeport numérique privé pour l\u2019apprentissage et le travail. Il permet aux utilisateurs de collecter et de partager en toute sécurité leurs compétences et réalisations vérifiées en ligne.',
|
|
90
|
+
ctaInstruction:
|
|
91
|
+
'Veuillez cliquer sur le bouton ci-dessous pour approuver cette demande de compte.',
|
|
92
|
+
button: 'Approuver la demande \u2192',
|
|
93
|
+
thankYou: 'Merci,',
|
|
94
|
+
subject: (n, b) => `${n} a besoin de votre approbation pour rejoindre ${b}`,
|
|
95
|
+
},
|
|
96
|
+
ar: {
|
|
97
|
+
preview: (n, b) => `${n} بحاجة إلى موافقتك للانضمام إلى ${b}`,
|
|
98
|
+
heading: 'طلب الموافقة على الحساب',
|
|
99
|
+
greeting: 'مرحبًا،',
|
|
100
|
+
body: (n, b) => (
|
|
101
|
+
<>
|
|
102
|
+
طلب <strong>{n}</strong> الموافقة لاستخدام حسابه في {b}.
|
|
103
|
+
</>
|
|
104
|
+
),
|
|
105
|
+
passport:
|
|
106
|
+
'{brandName} هو جواز سفر رقمي خاص للتعلّم والعمل. يتيح للمستخدمين جمع ومشاركة مهاراتهم وإنجازاتهم الموثّقة بأمان عبر الإنترنت.',
|
|
107
|
+
ctaInstruction: 'يرجى النقر على الزر أدناه للموافقة على طلب الحساب هذا.',
|
|
108
|
+
button: 'الموافقة على الطلب ←',
|
|
109
|
+
thankYou: 'شكرًا،',
|
|
110
|
+
subject: (n, b) => `${n} بحاجة إلى موافقتك للانضمام إلى ${b}`,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
29
114
|
export const GuardianApproval: React.FC<GuardianApprovalProps> = ({
|
|
30
115
|
branding,
|
|
31
116
|
approvalUrl,
|
|
32
117
|
requester,
|
|
118
|
+
locale,
|
|
33
119
|
}) => {
|
|
120
|
+
const s = STRINGS[resolveCatalogLocale(locale)];
|
|
34
121
|
const requesterName = requester?.displayName ?? 'Someone';
|
|
35
122
|
|
|
36
123
|
return (
|
|
37
|
-
<Layout
|
|
38
|
-
|
|
124
|
+
<Layout
|
|
125
|
+
branding={branding}
|
|
126
|
+
locale={locale}
|
|
127
|
+
preview={s.preview(requesterName, branding.brandName)}
|
|
128
|
+
>
|
|
129
|
+
<Text style={heading}>{s.heading}</Text>
|
|
39
130
|
|
|
40
|
-
<Text style={paragraph}>
|
|
131
|
+
<Text style={paragraph}>{s.greeting}</Text>
|
|
41
132
|
|
|
42
|
-
<Text style={paragraph}>
|
|
43
|
-
<strong>{requesterName}</strong> has requested approval to use their {branding.brandName} account.
|
|
44
|
-
</Text>
|
|
133
|
+
<Text style={paragraph}>{s.body(requesterName, branding.brandName)}</Text>
|
|
45
134
|
|
|
46
135
|
<Text style={paragraph}>
|
|
47
|
-
{
|
|
48
|
-
It lets users securely collect and share their verified skills and achievements online.
|
|
136
|
+
{interpolate(s.passport, { brandName: branding.brandName })}
|
|
49
137
|
</Text>
|
|
50
138
|
|
|
51
|
-
<Text style={paragraph}>
|
|
52
|
-
Please click the button below to approve this account request.
|
|
53
|
-
</Text>
|
|
139
|
+
<Text style={paragraph}>{s.ctaInstruction}</Text>
|
|
54
140
|
|
|
55
141
|
<Section style={buttonWrapper}>
|
|
56
142
|
<EmailButton href={approvalUrl} branding={branding}>
|
|
57
|
-
|
|
143
|
+
{s.button}
|
|
58
144
|
</EmailButton>
|
|
59
145
|
</Section>
|
|
60
146
|
|
|
61
147
|
<Text style={signOff}>
|
|
62
|
-
|
|
148
|
+
{s.thankYou}
|
|
149
|
+
<br />
|
|
150
|
+
{interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
|
|
151
|
+
brandName: branding.brandName,
|
|
152
|
+
})}
|
|
63
153
|
</Text>
|
|
64
154
|
|
|
65
|
-
<LinkFallback href={approvalUrl} />
|
|
155
|
+
<LinkFallback href={approvalUrl} locale={locale} />
|
|
66
156
|
</Layout>
|
|
67
157
|
);
|
|
68
158
|
};
|
|
@@ -70,10 +160,10 @@ export const GuardianApproval: React.FC<GuardianApprovalProps> = ({
|
|
|
70
160
|
export const getGuardianApprovalSubject = (
|
|
71
161
|
branding: TenantBranding,
|
|
72
162
|
props: GuardianApprovalProps,
|
|
163
|
+
locale?: string
|
|
73
164
|
): string => {
|
|
74
165
|
const name = props.requester?.displayName ?? 'Someone';
|
|
75
|
-
|
|
76
|
-
return `${name} needs your approval to join ${branding.brandName}`;
|
|
166
|
+
return STRINGS[resolveCatalogLocale(locale)].subject(name, branding.brandName);
|
|
77
167
|
};
|
|
78
168
|
|
|
79
169
|
// ---------------------------------------------------------------------------
|