@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.
Files changed (94) hide show
  1. package/dist/branding.d.ts.map +1 -1
  2. package/dist/components/Layout.d.ts +2 -0
  3. package/dist/components/Layout.d.ts.map +1 -1
  4. package/dist/components/Layout.js +6 -1
  5. package/dist/components/Layout.js.map +1 -1
  6. package/dist/components/LinkFallback.d.ts +2 -0
  7. package/dist/components/LinkFallback.d.ts.map +1 -1
  8. package/dist/components/LinkFallback.js +5 -1
  9. package/dist/components/LinkFallback.js.map +1 -1
  10. package/dist/i18n.d.ts +52 -0
  11. package/dist/i18n.d.ts.map +1 -0
  12. package/dist/i18n.js +77 -0
  13. package/dist/i18n.js.map +1 -0
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/render.d.ts +3 -1
  19. package/dist/render.d.ts.map +1 -1
  20. package/dist/render.js +33 -30
  21. package/dist/render.js.map +1 -1
  22. package/dist/sms.d.ts +6 -3
  23. package/dist/sms.d.ts.map +1 -1
  24. package/dist/sms.js +43 -7
  25. package/dist/sms.js.map +1 -1
  26. package/dist/templates/account-approved.d.ts +3 -1
  27. package/dist/templates/account-approved.d.ts.map +1 -1
  28. package/dist/templates/account-approved.js +55 -5
  29. package/dist/templates/account-approved.js.map +1 -1
  30. package/dist/templates/credential-awaiting-guardian.d.ts +3 -1
  31. package/dist/templates/credential-awaiting-guardian.d.ts.map +1 -1
  32. package/dist/templates/credential-awaiting-guardian.js +54 -4
  33. package/dist/templates/credential-awaiting-guardian.js.map +1 -1
  34. package/dist/templates/email-verification.d.ts +3 -1
  35. package/dist/templates/email-verification.d.ts.map +1 -1
  36. package/dist/templates/email-verification.js +57 -4
  37. package/dist/templates/email-verification.js.map +1 -1
  38. package/dist/templates/endorsement-request.d.ts +3 -1
  39. package/dist/templates/endorsement-request.d.ts.map +1 -1
  40. package/dist/templates/endorsement-request.js +53 -5
  41. package/dist/templates/endorsement-request.js.map +1 -1
  42. package/dist/templates/guardian-approval.d.ts +3 -1
  43. package/dist/templates/guardian-approval.d.ts.map +1 -1
  44. package/dist/templates/guardian-approval.js +55 -5
  45. package/dist/templates/guardian-approval.js.map +1 -1
  46. package/dist/templates/guardian-approved-claim.d.ts +3 -1
  47. package/dist/templates/guardian-approved-claim.d.ts.map +1 -1
  48. package/dist/templates/guardian-approved-claim.js +50 -4
  49. package/dist/templates/guardian-approved-claim.js.map +1 -1
  50. package/dist/templates/guardian-credential-approval.d.ts +3 -1
  51. package/dist/templates/guardian-credential-approval.d.ts.map +1 -1
  52. package/dist/templates/guardian-credential-approval.js +50 -4
  53. package/dist/templates/guardian-credential-approval.js.map +1 -1
  54. package/dist/templates/guardian-email-otp.d.ts +3 -1
  55. package/dist/templates/guardian-email-otp.d.ts.map +1 -1
  56. package/dist/templates/guardian-email-otp.js +55 -5
  57. package/dist/templates/guardian-email-otp.js.map +1 -1
  58. package/dist/templates/guardian-rejected-credential.d.ts +3 -1
  59. package/dist/templates/guardian-rejected-credential.d.ts.map +1 -1
  60. package/dist/templates/guardian-rejected-credential.js +45 -3
  61. package/dist/templates/guardian-rejected-credential.js.map +1 -1
  62. package/dist/templates/inbox-claim.d.ts +3 -1
  63. package/dist/templates/inbox-claim.d.ts.map +1 -1
  64. package/dist/templates/inbox-claim.js +63 -11
  65. package/dist/templates/inbox-claim.js.map +1 -1
  66. package/dist/templates/recovery-key.d.ts +3 -1
  67. package/dist/templates/recovery-key.d.ts.map +1 -1
  68. package/dist/templates/recovery-key.js +59 -3
  69. package/dist/templates/recovery-key.js.map +1 -1
  70. package/dist/templates/verification-code.d.ts +3 -1
  71. package/dist/templates/verification-code.d.ts.map +1 -1
  72. package/dist/templates/verification-code.js +153 -17
  73. package/dist/templates/verification-code.js.map +1 -1
  74. package/dist/tenant-registry.d.ts.map +1 -1
  75. package/package.json +4 -3
  76. package/src/__tests__/i18n.test.ts +329 -0
  77. package/src/components/Layout.tsx +69 -60
  78. package/src/components/LinkFallback.tsx +19 -11
  79. package/src/i18n.ts +110 -0
  80. package/src/index.ts +15 -2
  81. package/src/render.ts +47 -28
  82. package/src/sms.ts +61 -6
  83. package/src/templates/account-approved.tsx +82 -29
  84. package/src/templates/credential-awaiting-guardian.tsx +113 -19
  85. package/src/templates/email-verification.tsx +87 -16
  86. package/src/templates/endorsement-request.tsx +142 -17
  87. package/src/templates/guardian-approval.tsx +106 -16
  88. package/src/templates/guardian-approved-claim.tsx +137 -17
  89. package/src/templates/guardian-credential-approval.tsx +112 -17
  90. package/src/templates/guardian-email-otp.tsx +103 -25
  91. package/src/templates/guardian-rejected-credential.tsx +137 -15
  92. package/src/templates/inbox-claim.tsx +180 -29
  93. package/src/templates/recovery-key.tsx +126 -30
  94. 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 greeting = recipient?.name ? `Hello ${recipient.name},` : 'Hello,';
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="Verify your email address">
37
- <Text style={heading}>Verify Your Email</Text>
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
- Verify Your Email &rarr;
120
+ {s.button}
53
121
  </EmailButton>
54
122
  </Section>
55
123
 
56
124
  <Text style={signOff}>
57
- Sincerely,<br />The {branding.brandName} Team
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
- _branding: TenantBranding,
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 greeting = recipient?.name ? `Hello ${recipient.name},` : 'Hello,';
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 branding={branding} preview={`${issuerName} is requesting your endorsement`} showHeaderLogo={false}>
49
- <IssuerLogo logoUrl={issuer?.logoUrl} alt={issuerName ? `${issuerName} logo` : undefined} />
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
- {branding.brandName} is your private, digital passport for learning and work.
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
- Endorse &rarr;
196
+ {s.button}
76
197
  </EmailButton>
77
198
  </Section>
78
199
 
79
200
  <Text style={signOff}>
80
- Sincerely,<br />The {branding.brandName} Team
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 branding={branding} preview={`${requesterName} needs your approval to join ${branding.brandName}`}>
38
- <Text style={heading}>Account Approval Request</Text>
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}>Hello,</Text>
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
- {branding.brandName} is a private, digital passport for learning and work.
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
- Approve Request &rarr;
143
+ {s.button}
58
144
  </EmailButton>
59
145
  </Section>
60
146
 
61
147
  <Text style={signOff}>
62
- Thank you,<br />The {branding.brandName} Team
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
  // ---------------------------------------------------------------------------