@learncard/react 2.12.1 → 2.12.3

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 (127) hide show
  1. package/dist/cjs/{BoostGenericCard-71899305.js → BoostGenericCard-72cab860.js} +3 -3
  2. package/dist/cjs/{BoostGenericCard-71899305.js.map → BoostGenericCard-72cab860.js.map} +1 -1
  3. package/dist/cjs/{CertificateDisplayCard-f10b46b1.js → CertificateDisplayCard-a036cdc5.js} +20 -14
  4. package/dist/cjs/CertificateDisplayCard-a036cdc5.js.map +1 -0
  5. package/dist/cjs/{MeritBadgeDisplayCard-d50cfb5e.js → MeritBadgeDisplayCard-2e4edd20.js} +4 -4
  6. package/dist/cjs/MeritBadgeDisplayCard-2e4edd20.js.map +1 -0
  7. package/dist/cjs/{MeritBadgeSkillsCount-e46503f5.js → MeritBadgeSkillsCount-472542c1.js} +2 -2
  8. package/dist/cjs/{MeritBadgeSkillsCount-e46503f5.js.map → MeritBadgeSkillsCount-472542c1.js.map} +1 -1
  9. package/dist/cjs/{SchoolIDCard-2d3cbb2c.js → SchoolIDCard-10796199.js} +1184 -3
  10. package/dist/cjs/SchoolIDCard-10796199.js.map +1 -0
  11. package/dist/cjs/{VCCard-42479c97.js → VCCard-ebbbda88.js} +3 -3
  12. package/dist/cjs/{VCCard-42479c97.js.map → VCCard-ebbbda88.js.map} +1 -1
  13. package/dist/cjs/{VCDisplayBackFace-d1cf9f73.js → VCDisplayBackFace-13c73f23.js} +3 -3
  14. package/dist/cjs/{VCDisplayBackFace-d1cf9f73.js.map → VCDisplayBackFace-13c73f23.js.map} +1 -1
  15. package/dist/cjs/{VCDisplayCard-cf46c224.js → VCDisplayCard-3720145a.js} +3 -3
  16. package/dist/cjs/{VCDisplayCard-cf46c224.js.map → VCDisplayCard-3720145a.js.map} +1 -1
  17. package/dist/cjs/{VCDisplayCard2-4a201a8a.js → VCDisplayCard2-ae93ccd2.js} +38 -30
  18. package/dist/cjs/VCDisplayCard2-ae93ccd2.js.map +1 -0
  19. package/dist/cjs/{VCVerificationPill-6c9dcf80.js → VCVerificationPill-a1607dff.js} +2 -2
  20. package/dist/cjs/{VCVerificationPill-6c9dcf80.js.map → VCVerificationPill-a1607dff.js.map} +1 -1
  21. package/dist/cjs/{VerifierStateBadgeAndText-934239ab.js → VerifierStateBadgeAndText-5f10743e.js} +166 -107
  22. package/dist/cjs/VerifierStateBadgeAndText-5f10743e.js.map +1 -0
  23. package/dist/cjs/{credential.helpers-17a10601.js → credential.helpers-65ea507a.js} +2 -2
  24. package/dist/cjs/{credential.helpers-17a10601.js.map → credential.helpers-65ea507a.js.map} +1 -1
  25. package/dist/cjs/{i18n-8fd90b2f.js → i18n-ddbb18e7.js} +12 -1
  26. package/dist/cjs/{i18n-8fd90b2f.js.map → i18n-ddbb18e7.js.map} +1 -1
  27. package/dist/cjs/index.js +14 -15
  28. package/dist/cjs/index.js.map +1 -1
  29. package/dist/cjs/index14.js +14 -15
  30. package/dist/cjs/index14.js.map +1 -1
  31. package/dist/cjs/index22.js +6 -6
  32. package/dist/cjs/index31.js +1 -2
  33. package/dist/cjs/index31.js.map +1 -1
  34. package/dist/cjs/index39.js +11 -12
  35. package/dist/cjs/index39.js.map +1 -1
  36. package/dist/cjs/index4.js +4 -4
  37. package/dist/cjs/index40.js +4 -4
  38. package/dist/cjs/index41.js +5 -5
  39. package/dist/cjs/index42.js +7 -8
  40. package/dist/cjs/index42.js.map +1 -1
  41. package/dist/cjs/index43.js +1 -1
  42. package/dist/cjs/index46.js +2 -2
  43. package/dist/cjs/index7.js +5 -4
  44. package/dist/cjs/index7.js.map +1 -1
  45. package/dist/cjs/{types.esm-73624b33.js → types.esm-50b3eecd.js} +46 -3
  46. package/dist/cjs/types.esm-50b3eecd.js.map +1 -0
  47. package/dist/esm/{BoostGenericCard-3b3ff784.js → BoostGenericCard-2f586072.js} +3 -3
  48. package/dist/esm/{BoostGenericCard-3b3ff784.js.map → BoostGenericCard-2f586072.js.map} +1 -1
  49. package/dist/esm/{CertificateDisplayCard-f26ef278.js → CertificateDisplayCard-ff1ae869.js} +20 -14
  50. package/dist/esm/CertificateDisplayCard-ff1ae869.js.map +1 -0
  51. package/dist/esm/{MeritBadgeDisplayCard-0d0b28c7.js → MeritBadgeDisplayCard-b8b904d2.js} +4 -4
  52. package/dist/esm/MeritBadgeDisplayCard-b8b904d2.js.map +1 -0
  53. package/dist/esm/{MeritBadgeSkillsCount-09e211fc.js → MeritBadgeSkillsCount-46d9c24b.js} +2 -2
  54. package/dist/esm/{MeritBadgeSkillsCount-09e211fc.js.map → MeritBadgeSkillsCount-46d9c24b.js.map} +1 -1
  55. package/dist/esm/{SchoolIDCard-fbd1cf6c.js → SchoolIDCard-f82e40bc.js} +1183 -2
  56. package/dist/esm/SchoolIDCard-f82e40bc.js.map +1 -0
  57. package/dist/esm/{VCCard-7e5e00d7.js → VCCard-e2ebb922.js} +3 -3
  58. package/dist/esm/{VCCard-7e5e00d7.js.map → VCCard-e2ebb922.js.map} +1 -1
  59. package/dist/esm/{VCDisplayBackFace-ae74476d.js → VCDisplayBackFace-4f2d700d.js} +3 -3
  60. package/dist/esm/{VCDisplayBackFace-ae74476d.js.map → VCDisplayBackFace-4f2d700d.js.map} +1 -1
  61. package/dist/esm/{VCDisplayCard-ac1f367a.js → VCDisplayCard-32e126f8.js} +3 -3
  62. package/dist/esm/{VCDisplayCard-ac1f367a.js.map → VCDisplayCard-32e126f8.js.map} +1 -1
  63. package/dist/esm/{VCDisplayCard2-7011596e.js → VCDisplayCard2-ec2177de.js} +38 -30
  64. package/dist/esm/VCDisplayCard2-ec2177de.js.map +1 -0
  65. package/dist/esm/{VCVerificationPill-04ffdf56.js → VCVerificationPill-0c336075.js} +2 -2
  66. package/dist/esm/{VCVerificationPill-04ffdf56.js.map → VCVerificationPill-0c336075.js.map} +1 -1
  67. package/dist/esm/{VerifierStateBadgeAndText-750a6c24.js → VerifierStateBadgeAndText-0a4e0c63.js} +167 -108
  68. package/dist/esm/VerifierStateBadgeAndText-0a4e0c63.js.map +1 -0
  69. package/dist/esm/{credential.helpers-4fbdd8e8.js → credential.helpers-61005784.js} +2 -2
  70. package/dist/esm/{credential.helpers-4fbdd8e8.js.map → credential.helpers-61005784.js.map} +1 -1
  71. package/dist/esm/{i18n-08b606cb.js → i18n-35209bfa.js} +12 -1
  72. package/dist/esm/{i18n-08b606cb.js.map → i18n-35209bfa.js.map} +1 -1
  73. package/dist/esm/index.js +14 -15
  74. package/dist/esm/index.js.map +1 -1
  75. package/dist/esm/index14.js +14 -15
  76. package/dist/esm/index14.js.map +1 -1
  77. package/dist/esm/index22.js +6 -6
  78. package/dist/esm/index31.js +1 -2
  79. package/dist/esm/index31.js.map +1 -1
  80. package/dist/esm/index39.js +11 -12
  81. package/dist/esm/index39.js.map +1 -1
  82. package/dist/esm/index4.js +4 -4
  83. package/dist/esm/index40.js +4 -4
  84. package/dist/esm/index41.js +5 -5
  85. package/dist/esm/index42.js +7 -8
  86. package/dist/esm/index42.js.map +1 -1
  87. package/dist/esm/index43.js +1 -1
  88. package/dist/esm/index46.js +2 -2
  89. package/dist/esm/index7.js +5 -4
  90. package/dist/esm/index7.js.map +1 -1
  91. package/dist/esm/{types.esm-5f75bd4a.js → types.esm-2762569e.js} +42 -4
  92. package/dist/esm/types.esm-2762569e.js.map +1 -0
  93. package/dist/index.d.ts +20 -2
  94. package/dist/main.css +1 -1
  95. package/dist/main.js +1 -1
  96. package/package.json +6 -10
  97. package/src/assets/styles/main.css +24 -18
  98. package/src/components/CertificateDisplayCard/AlignmentRow.tsx +4 -2
  99. package/src/components/CertificateDisplayCard/AlignmentsBox.tsx +12 -9
  100. package/src/components/CertificateDisplayCard/InfoBox.tsx +3 -1
  101. package/src/components/CertificateDisplayCard/VerifierStateBadgeAndText.tsx +10 -6
  102. package/src/components/CircleIcon/__tests__/CircleIcon.test.tsx +1 -1
  103. package/src/components/MeritBadgeDisplayCard/MediaAttachmentsBox.tsx +3 -2
  104. package/src/components/VCDisplayCard2/InfoBox.tsx +3 -1
  105. package/src/components/VCDisplayCard2/TruncateTextBox.tsx +4 -2
  106. package/src/components/VCDisplayCard2/VC2BackFace.tsx +6 -4
  107. package/src/components/VCDisplayCard2/VC2FrontFaceInfo.tsx +22 -22
  108. package/src/components/VCDisplayCard2/VCDisplayCard2.tsx +10 -2
  109. package/src/components/VCDisplayCard2/__tests__/VC2LocalizedLabels.test.tsx +122 -0
  110. package/src/helpers/credential.helpers.test.ts +1 -1
  111. package/src/i18n/index.tsx +12 -0
  112. package/dist/cjs/CertificateDisplayCard-f10b46b1.js.map +0 -1
  113. package/dist/cjs/MeritBadgeDisplayCard-d50cfb5e.js.map +0 -1
  114. package/dist/cjs/SchoolIDCard-2d3cbb2c.js.map +0 -1
  115. package/dist/cjs/VCDisplayCard2-4a201a8a.js.map +0 -1
  116. package/dist/cjs/VerifierStateBadgeAndText-934239ab.js.map +0 -1
  117. package/dist/cjs/index-47efbf5b.js +0 -1186
  118. package/dist/cjs/index-47efbf5b.js.map +0 -1
  119. package/dist/cjs/types.esm-73624b33.js.map +0 -1
  120. package/dist/esm/CertificateDisplayCard-f26ef278.js.map +0 -1
  121. package/dist/esm/MeritBadgeDisplayCard-0d0b28c7.js.map +0 -1
  122. package/dist/esm/SchoolIDCard-fbd1cf6c.js.map +0 -1
  123. package/dist/esm/VCDisplayCard2-7011596e.js.map +0 -1
  124. package/dist/esm/VerifierStateBadgeAndText-750a6c24.js.map +0 -1
  125. package/dist/esm/index-cbd2e9db.js +0 -1184
  126. package/dist/esm/index-cbd2e9db.js.map +0 -1
  127. package/dist/esm/types.esm-5f75bd4a.js.map +0 -1
@@ -394,8 +394,9 @@
394
394
  font-display: swap;
395
395
  src: url(https://fonts.gstatic.com/s/mousememoirs/v13/t5tmIRoSNJ-PH0WNNgDYxdSb3T7Prw.woff2)
396
396
  format('woff2');
397
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
398
- U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
397
+ unicode-range:
398
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074,
399
+ U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
399
400
  }
400
401
  /* latin-ext */
401
402
  @font-face {
@@ -405,8 +406,9 @@
405
406
  font-display: swap;
406
407
  src: url(https://fonts.gstatic.com/s/mousememoirs/v13/t5tmIRoSNJ-PH0WNNgDYxdSb3TDPr6OH.woff2)
407
408
  format('woff2');
408
- unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113,
409
- U+2C60-2C7F, U+A720-A7FF;
409
+ unicode-range:
410
+ U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F,
411
+ U+A720-A7FF;
410
412
  }
411
413
 
412
414
  /* Montserrat (removed cyrillic-ext, cyrillic, vietnamese) */
@@ -418,8 +420,9 @@
418
420
  font-display: swap;
419
421
  src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459Wdhyzbi.woff2)
420
422
  format('woff2');
421
- unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113,
422
- U+2C60-2C7F, U+A720-A7FF;
423
+ unicode-range:
424
+ U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F,
425
+ U+A720-A7FF;
423
426
  }
424
427
  /* latin */
425
428
  @font-face {
@@ -429,8 +432,9 @@
429
432
  font-display: swap;
430
433
  src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459Wlhyw.woff2)
431
434
  format('woff2');
432
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
433
- U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
435
+ unicode-range:
436
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074,
437
+ U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
434
438
  }
435
439
 
436
440
  /* Poppins (removed devanagari and latin-ext)*/
@@ -442,8 +446,9 @@
442
446
  font-display: swap;
443
447
  src: url(https://fonts.gstatic.com/s/poppins/v20/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2)
444
448
  format('woff2');
445
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
446
- U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
449
+ unicode-range:
450
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074,
451
+ U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
447
452
  }
448
453
 
449
454
  /* latin */
@@ -454,9 +459,9 @@
454
459
  font-display: swap;
455
460
  src: url(https://fonts.gstatic.com/s/jacquesfrancois/v24/ZXu9e04ZvKeOOHIe1TMahbcIU2cgqcTgoA.woff2)
456
461
  format('woff2');
457
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
458
- U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
459
- U+FFFD;
462
+ unicode-range:
463
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
464
+ U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
460
465
  }
461
466
 
462
467
  /* latin */
@@ -467,9 +472,9 @@
467
472
  font-display: swap;
468
473
  src: url(https://fonts.gstatic.com/s/sacramento/v15/buEzpo6gcdjy0EiZMBUG4C0f_Q.woff2)
469
474
  format('woff2');
470
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
471
- U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
472
- U+FFFD;
475
+ unicode-range:
476
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
477
+ U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
473
478
  }
474
479
 
475
480
  /* latin-ext */
@@ -480,6 +485,7 @@
480
485
  font-display: swap;
481
486
  src: url(https://fonts.gstatic.com/s/sacramento/v15/buEzpo6gcdjy0EiZMBUG4CMf_exL.woff2)
482
487
  format('woff2');
483
- unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
484
- U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
488
+ unicode-range:
489
+ U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
490
+ U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
485
491
  }
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { useT } from '../../i18n';
2
3
 
3
4
  type AlignmentsRowProps = {
4
5
  url?: string;
@@ -50,6 +51,7 @@ const AlignmentRow: React.FC<AlignmentsRowProps> = ({
50
51
  description,
51
52
  verified,
52
53
  }) => {
54
+ const t = useT();
53
55
  const title = name || code || 'Framework alignment';
54
56
 
55
57
  return (
@@ -68,7 +70,7 @@ const AlignmentRow: React.FC<AlignmentsRowProps> = ({
68
70
  {verified && (
69
71
  <span className="inline-flex items-center gap-1 rounded-full bg-emerald-50 border border-emerald-100 px-[6px] py-[2px] text-[10px] font-medium text-emerald-700 shrink-0">
70
72
  <ShieldCheckIcon />
71
- Verified
73
+ {t('credential.verifiedLabel')}
72
74
  </span>
73
75
  )}
74
76
  </div>
@@ -79,7 +81,7 @@ const AlignmentRow: React.FC<AlignmentsRowProps> = ({
79
81
  rel="noreferrer"
80
82
  onClick={e => e.stopPropagation()}
81
83
  className="text-grayscale-400 hover:text-grayscale-700 transition-colors shrink-0"
82
- aria-label="Open alignment source"
84
+ aria-label={t('credential.openAlignmentSource')}
83
85
  >
84
86
  <ExternalLinkIcon />
85
87
  </a>
@@ -3,6 +3,7 @@ import React, { useState } from 'react';
3
3
  import AlignmentRow from './AlignmentRow';
4
4
  import InfoIcon from '../svgs/InfoIcon';
5
5
  import InfoBox from './InfoBox';
6
+ import { useT } from '../../i18n';
6
7
 
7
8
  type Alignment = {
8
9
  targetUrl: string;
@@ -22,11 +23,8 @@ const isCredentialEngine = (a: Alignment): boolean =>
22
23
  a.targetType === 'ceterms:Credential' || a.targetFramework === 'Credential Engine Registry';
23
24
 
24
25
  const AlignmentsBox: React.FC<AlignmentsBoxProps> = ({ alignment, style }) => {
26
+ const t = useT();
25
27
  const [showInfo, setShowInfo] = useState(false);
26
- const alignmentText = `
27
- Alignments in your Open Badge credential link your achievement to established frameworks, standards, or competencies.
28
- Each alignment shows how your boost directly relates to skills, knowledge areas, and professional standards that are recognized in your field.
29
- `;
30
28
 
31
29
  const list = Array.isArray(alignment) ? alignment : [alignment];
32
30
  if (list.length === 0) return null;
@@ -53,10 +51,10 @@ const AlignmentsBox: React.FC<AlignmentsBoxProps> = ({ alignment, style }) => {
53
51
  className={
54
52
  style === 'Certificate'
55
53
  ? 'text-[17px] text-grayscale-900 font-poppins'
56
- : 'text-[22px] font-mouse'
54
+ : 'text-[20px] leading-[20px] text-grayscale-900'
57
55
  }
58
56
  >
59
- Alignments
57
+ {t('credential.alignments')}
60
58
  </h3>
61
59
  <button
62
60
  className="ml-auto"
@@ -68,12 +66,17 @@ const AlignmentsBox: React.FC<AlignmentsBoxProps> = ({ alignment, style }) => {
68
66
  <InfoIcon color={showInfo ? '#00BA88' : undefined} />
69
67
  </button>
70
68
  </div>
71
- {showInfo && <InfoBox text={alignmentText} handleClose={() => setShowInfo(false)} />}
69
+ {showInfo && (
70
+ <InfoBox
71
+ text={t('credential.alignmentInfo')}
72
+ handleClose={() => setShowInfo(false)}
73
+ />
74
+ )}
72
75
 
73
76
  {verified.length > 0 && (
74
77
  <div className="flex flex-col gap-[8px] w-full">
75
78
  <span className="text-[11px] font-medium text-grayscale-500 font-poppins">
76
- Verified source
79
+ {t('credential.verifiedSource')}
77
80
  </span>
78
81
  {verified.map((object, index) => renderRow(object, index, true))}
79
82
  </div>
@@ -83,7 +86,7 @@ const AlignmentsBox: React.FC<AlignmentsBoxProps> = ({ alignment, style }) => {
83
86
  <div className="flex flex-col gap-[8px] w-full">
84
87
  {verified.length > 0 && (
85
88
  <span className="text-[11px] font-medium text-grayscale-500 font-poppins">
86
- Occupations &amp; programs
89
+ {t('credential.occupationsPrograms')}
87
90
  </span>
88
91
  )}
89
92
  {other.map((object, index) => renderRow(object, index, false))}
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { useT } from '../../i18n';
2
3
 
3
4
  type InfoBoxProps = {
4
5
  text: string;
@@ -7,6 +8,7 @@ type InfoBoxProps = {
7
8
  };
8
9
 
9
10
  const InfoBox: React.FC<InfoBoxProps> = ({ text, handleClose, backgroundColor = '#6366F1' }) => {
11
+ const t = useT();
10
12
  const bgColorWithOpacity = `${backgroundColor}1F`; // 12% opacity
11
13
  return (
12
14
  <div
@@ -21,7 +23,7 @@ const InfoBox: React.FC<InfoBoxProps> = ({ text, handleClose, backgroundColor =
21
23
  }}
22
24
  className="text-indigo-500 font-[700] select-none"
23
25
  >
24
- Close
26
+ {t('credential.close')}
25
27
  </button>
26
28
  </div>
27
29
  );
@@ -3,6 +3,7 @@ import UnknownVerifierBadge from '../svgs/UnknownVerifierBadge';
3
3
  import VerifiedBadge from '../svgs/VerifiedBadge';
4
4
  import PersonBadge from '../svgs/PersonBadge';
5
5
  import RedFlag from '../svgs/RedFlag';
6
+ import { useT } from '../../i18n';
6
7
 
7
8
  export const VERIFIER_STATES = {
8
9
  selfVerified: 'Self Issued',
@@ -22,6 +23,7 @@ export type VerifierStateBadgeAndTextProps = {
22
23
 
23
24
  const VerifierStateBadgeAndText = forwardRef<HTMLButtonElement, VerifierStateBadgeAndTextProps>(
24
25
  ({ verifierState, className = '', unknownVerifierTitle, onClick }, ref) => {
26
+ const t = useT();
25
27
  // For Scouts: if we have a role-based title, show it with trusted styling
26
28
  // BUT don't overwrite self-issued or untrusted status
27
29
  const effectiveState =
@@ -37,35 +39,37 @@ const VerifierStateBadgeAndText = forwardRef<HTMLButtonElement, VerifierStateBad
37
39
  {effectiveState === VERIFIER_STATES.selfVerified && (
38
40
  <div className="flex items-center gap-0.5 font-poppins font-[500] text-[12px] leading-tight text-green-dark">
39
41
  <PersonBadge />
40
- <span className="whitespace-nowrap">Self Issued</span>
42
+ <span className="whitespace-nowrap">{t('verification.selfIssued')}</span>
41
43
  </div>
42
44
  )}
43
45
  {effectiveState === VERIFIER_STATES.trustedVerifier && (
44
46
  <div className="flex items-center gap-0.5 font-poppins font-[500] text-[12px] leading-tight text-green-600">
45
47
  <VerifiedBadge />
46
48
  <span className="whitespace-nowrap">
47
- {unknownVerifierTitle ?? 'Trusted Issuer'}
49
+ {unknownVerifierTitle ?? t('verification.trustedIssuer')}
48
50
  </span>
49
51
  </div>
50
52
  )}
51
53
  {effectiveState === VERIFIER_STATES.unknownVerifier && (
52
- <div className="flex items-center gap-0.5 font-poppins font-[500] text-[12px] leading-tight text-orange-500">
54
+ <div className="flex items-center gap-0.5 font-poppins font-[500] text-[12px] leading-tight text-orange-800">
53
55
  <UnknownVerifierBadge />
54
56
  <span className="whitespace-nowrap">
55
- {unknownVerifierTitle ?? VERIFIER_STATES.unknownVerifier}
57
+ {unknownVerifierTitle ?? t('verification.unknownIssuer')}
56
58
  </span>
57
59
  </div>
58
60
  )}
59
61
  {effectiveState === VERIFIER_STATES.appIssuer && (
60
62
  <div className="flex items-center gap-0.5 font-poppins font-[500] text-[12px] leading-tight text-cyan-600">
61
63
  <VerifiedBadge />
62
- <span className="whitespace-nowrap">App Issuer</span>
64
+ <span className="whitespace-nowrap">{t('verification.appIssuer')}</span>
63
65
  </div>
64
66
  )}
65
67
  {effectiveState === VERIFIER_STATES.untrustedVerifier && (
66
68
  <div className="flex items-center gap-0.5 font-poppins font-[500] text-[12px] leading-tight text-red-mastercard">
67
69
  <RedFlag />
68
- <span className="whitespace-nowrap">Untrusted Issuer</span>
70
+ <span className="whitespace-nowrap">
71
+ {t('verification.untrustedIssuer')}
72
+ </span>
69
73
  </div>
70
74
  )}
71
75
  </>
@@ -26,7 +26,7 @@ describe('Running Tests for CircleIcon', () => {
26
26
  // });
27
27
 
28
28
  // it('calls onClick', async () => {
29
- // const handleOnClick = jest.fn();
29
+ // const handleOnClick = vi.fn();
30
30
  // const user = userEvent.setup();
31
31
 
32
32
  // const { container } = render(<CircleIcon onClick={handleOnClick} />);
@@ -184,8 +184,9 @@ const MediaAttachmentsBox: React.FC<MediaAttachmentsBoxProps> = ({
184
184
  className="bg-cover bg-no-repeat bg-center relative font-poppins text-white text-[12px] font-[400] leading-[17px] flex flex-col justify-end items-start p-[10px] text-left bg-rose-600 rounded-[15px] h-full"
185
185
  style={{
186
186
  backgroundImage: metadata?.imageUrl
187
- ? `linear-gradient(180deg, rgba(0, 0, 0, 0) 44.20%, rgba(0, 0, 0, 0.6) 69%), url(${metadata?.imageUrl ?? ''
188
- })`
187
+ ? `linear-gradient(180deg, rgba(0, 0, 0, 0) 44.20%, rgba(0, 0, 0, 0.6) 69%), url(${
188
+ metadata?.imageUrl ?? ''
189
+ })`
189
190
  : undefined,
190
191
  }}
191
192
  >
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { useT } from '../../i18n';
2
3
 
3
4
  type InfoBoxProps = {
4
5
  text: string;
@@ -7,6 +8,7 @@ type InfoBoxProps = {
7
8
  };
8
9
 
9
10
  const InfoBox: React.FC<InfoBoxProps> = ({ text, handleClose, backgroundColor = '#6366F1' }) => {
11
+ const t = useT();
10
12
  const bgColorWithOpacity = `${backgroundColor}1F`; // 12% opacity
11
13
  return (
12
14
  <div
@@ -21,7 +23,7 @@ const InfoBox: React.FC<InfoBoxProps> = ({ text, handleClose, backgroundColor =
21
23
  }}
22
24
  className="text-indigo-500 font-[700] select-none"
23
25
  >
24
- Close
26
+ {t('credential.close')}
25
27
  </button>
26
28
  </div>
27
29
  );
@@ -1,4 +1,5 @@
1
1
  import React, { useState } from 'react';
2
+ import { useT } from '../../i18n';
2
3
 
3
4
  type TruncateTextBoxProps = {
4
5
  headerText?: string;
@@ -29,6 +30,7 @@ const TruncateTextBox: React.FC<TruncateTextBoxProps> = ({
29
30
  containerClassName = '',
30
31
  textClassName = '',
31
32
  }) => {
33
+ const t = useT();
32
34
  const needsTruncate = text?.length > truncateThreshold;
33
35
  const [showFullText, setShowFullText] = useState(false);
34
36
  const truncated = needsTruncate && !showFullText;
@@ -61,7 +63,7 @@ const TruncateTextBox: React.FC<TruncateTextBoxProps> = ({
61
63
  setShowFullText(true);
62
64
  }}
63
65
  >
64
- More
66
+ {t('credential.more')}
65
67
  </button>
66
68
  </>
67
69
  )}
@@ -75,7 +77,7 @@ const TruncateTextBox: React.FC<TruncateTextBoxProps> = ({
75
77
  setShowFullText(false);
76
78
  }}
77
79
  >
78
- Close
80
+ {t('credential.close')}
79
81
  </button>
80
82
  </>
81
83
  )}
@@ -16,6 +16,7 @@ import {
16
16
  VideoMetadata,
17
17
  } from '../../types';
18
18
  import LeftArrow from '../svgs/LeftArrow';
19
+ import { useT } from '../../i18n';
19
20
 
20
21
  type FlippedComponentProps = React.PropsWithChildren<{
21
22
  flipId?: string;
@@ -69,6 +70,7 @@ const VC2BackFace: React.FC<VC2BackFaceProps> = ({
69
70
  enableLightbox,
70
71
  customLinkedCredentialsComponent,
71
72
  }) => {
73
+ const t = useT();
72
74
  const expiration = credential.expirationDate
73
75
  ? format(new Date(credential.expirationDate), 'MMM dd, yyyy')
74
76
  : undefined;
@@ -107,7 +109,7 @@ const VC2BackFace: React.FC<VC2BackFaceProps> = ({
107
109
 
108
110
  {customDescription && (
109
111
  <TruncateTextBox
110
- headerText="About"
112
+ headerText={t('credential.about')}
111
113
  text={description}
112
114
  className="description-box"
113
115
  >
@@ -116,7 +118,7 @@ const VC2BackFace: React.FC<VC2BackFaceProps> = ({
116
118
  )}
117
119
  {!customDescription && (description || expiration) && (
118
120
  <TruncateTextBox
119
- headerText="About"
121
+ headerText={t('credential.about')}
120
122
  text={description}
121
123
  className="description-box"
122
124
  >
@@ -130,7 +132,7 @@ const VC2BackFace: React.FC<VC2BackFaceProps> = ({
130
132
 
131
133
  {customCriteria && (
132
134
  <TruncateTextBox
133
- headerText="Criteria"
135
+ headerText={t('credential.criteria')}
134
136
  text={description}
135
137
  className="description-box"
136
138
  >
@@ -139,7 +141,7 @@ const VC2BackFace: React.FC<VC2BackFaceProps> = ({
139
141
  )}
140
142
  {!customCriteria && criteria && (
141
143
  <TruncateTextBox
142
- headerText="Criteria"
144
+ headerText={t('credential.criteria')}
143
145
  text={criteria}
144
146
  className="criteria-box"
145
147
  />
@@ -13,6 +13,7 @@ import VerifierStateBadgeAndText, {
13
13
  } from '../CertificateDisplayCard/VerifierStateBadgeAndText';
14
14
  import { BoostAchievementCredential } from '../../types';
15
15
  import { KnownDIDRegistryType } from '../../types';
16
+ import { useT } from '../../i18n';
16
17
 
17
18
  type FlippedComponentProps = React.PropsWithChildren<{
18
19
  flipId?: string;
@@ -65,6 +66,22 @@ const BadgeThumbnailPlaceholder: React.FC = () => (
65
66
  />
66
67
  </svg>
67
68
  );
69
+ const BadgeThumbnail: React.FC<{ imageUrl: string }> = ({ imageUrl }) => {
70
+ const [errored, setErrored] = React.useState(false);
71
+
72
+ return errored ? (
73
+ <div className="vc-front-image h-[130px] w-[130px] rounded-[10px] bg-grayscale-100 flex items-center justify-center">
74
+ <BadgeThumbnailPlaceholder />
75
+ </div>
76
+ ) : (
77
+ <img
78
+ className="vc-front-image h-[130px] w-[130px] rounded-[10px] bg-white object-cover"
79
+ src={imageUrl}
80
+ alt="credential thumbnail"
81
+ onError={() => setErrored(true)}
82
+ />
83
+ );
84
+ };
68
85
 
69
86
  const VC2FrontFaceInfo: React.FC<VC2FrontFaceInfoProps> = ({
70
87
  credential,
@@ -82,16 +99,10 @@ const VC2FrontFaceInfo: React.FC<VC2FrontFaceInfoProps> = ({
82
99
  unknownVerifierTitle,
83
100
  onVerifierClick,
84
101
  }) => {
102
+ const t = useT();
85
103
  const issuerImage = getImageFromProfile(issuer ?? '');
86
104
  const issueeImage = getImageFromProfile(issuee ?? '');
87
105
 
88
- const [thumbErrored, setThumbErrored] = React.useState(false);
89
-
90
- // Reset the broken-image flag whenever the thumbnail source changes.
91
- React.useEffect(() => {
92
- setThumbErrored(false);
93
- }, [imageUrl]);
94
-
95
106
  const issueeDisplay = resolveProfileDisplay(issuee, '');
96
107
  const issuerDisplay = resolveProfileDisplay(issuer, '');
97
108
 
@@ -182,20 +193,9 @@ const VC2FrontFaceInfo: React.FC<VC2FrontFaceInfoProps> = ({
182
193
  return (
183
194
  <Flipped inverseFlipId="card">
184
195
  <section className="vc-front-face w-full px-[15px] flex flex-col items-center gap-[15px]">
185
- {imageUrl &&
186
- !customThumbComponent &&
187
- (thumbErrored ? (
188
- <div className="vc-front-image h-[130px] w-[130px] rounded-[10px] bg-grayscale-100 flex items-center justify-center">
189
- <BadgeThumbnailPlaceholder />
190
- </div>
191
- ) : (
192
- <img
193
- className="vc-front-image h-[130px] w-[130px] rounded-[10px] bg-white object-cover"
194
- src={imageUrl}
195
- alt="credential thumbnail"
196
- onError={() => setThumbErrored(true)}
197
- />
198
- ))}
196
+ {imageUrl && !customThumbComponent && (
197
+ <BadgeThumbnail key={imageUrl} imageUrl={imageUrl} />
198
+ )}
199
199
 
200
200
  {customThumbComponent && customThumbComponent}
201
201
  <div className="vc-issue-info-box bg-white flex flex-col items-center gap-[5px] rounded-[20px] shadow-bottom px-[15px] py-[20px] w-full">
@@ -241,7 +241,7 @@ const VC2FrontFaceInfo: React.FC<VC2FrontFaceInfoProps> = ({
241
241
  {!issuerDisplay.isMissing && (
242
242
  <span className="issued-by max-w-full break-words text-center line-clamp-2 text-[14px]">
243
243
  <span className="font-medium text-grayscale-900">
244
- By
244
+ {t('credential.by')}
245
245
  </span>{' '}
246
246
  {issuerDisplay.isDidValue ? (
247
247
  <span className="font-[600] font-poppins break-words">
@@ -29,6 +29,7 @@ import {
29
29
  import { CertificateDisplayCard } from '../CertificateDisplayCard';
30
30
  import { MeritBadgeDisplayCard } from '../MeritBadgeDisplayCard';
31
31
  import { KnownDIDRegistryType } from '../../types';
32
+ import { useT } from '../../i18n';
32
33
 
33
34
  type FlippedComponentProps = React.PropsWithChildren<{
34
35
  flipId?: string;
@@ -169,6 +170,7 @@ export const VCDisplayCard2: React.FC<VCDisplayCard2Props> = ({
169
170
  hideFrontFaceDetails,
170
171
  onVerifierClick,
171
172
  }) => {
173
+ const t = useT();
172
174
  const {
173
175
  title = '',
174
176
  createdAt,
@@ -537,12 +539,18 @@ export const VCDisplayCard2: React.FC<VCDisplayCard2Props> = ({
537
539
  />
538
540
  )}
539
541
  <div className="vc-footer-text font-montserrat flex flex-col items-center justify-center text-[12px] font-[700] leading-[15px] select-none">
540
- <span className="text-[#4F4F4F]">Verified Credential</span>
542
+ <span className="text-[#4F4F4F]">
543
+ {t('credential.verified')}
544
+ </span>
541
545
  <span
542
546
  className="vc-footer-status uppercase"
543
547
  style={{ color: statusColor }}
544
548
  >
545
- {worstVerificationStatus}
549
+ {t(
550
+ `verification.status.${String(
551
+ worstVerificationStatus
552
+ ).toLowerCase()}`
553
+ )}
546
554
  </span>
547
555
  </div>
548
556
  <div
@@ -0,0 +1,122 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import React from 'react';
4
+ import { fireEvent, render, screen, within } from '@testing-library/react';
5
+ import { describe, expect, it, vi } from 'vitest';
6
+
7
+ import { VerificationStatusEnum } from '@learncard/types';
8
+
9
+ import { I18nProvider } from '../../../i18n';
10
+ import VerifierStateBadgeAndText, {
11
+ VERIFIER_STATES,
12
+ } from '../../CertificateDisplayCard/VerifierStateBadgeAndText';
13
+ import VC2BackFace from '../VC2BackFace';
14
+ import VCDisplayCard2 from '../VCDisplayCard2';
15
+
16
+ vi.mock('react-flip-toolkit', () => ({
17
+ Flipper: ({ children }: React.PropsWithChildren) => <>{children}</>,
18
+ Flipped: ({ children }: React.PropsWithChildren) => <>{children}</>,
19
+ }));
20
+
21
+ const translations: Record<string, string> = {
22
+ 'credential.about': 'Acerca de',
23
+ 'credential.criteria': 'Criterios',
24
+ 'credential.alignments': 'Alineaciones',
25
+ 'credential.alignmentInfo': 'Las alineaciones vinculan este logro con marcos establecidos.',
26
+ 'credential.verifiedSource': 'Fuente verificada',
27
+ 'credential.verifiedLabel': 'Verificada',
28
+ 'credential.openAlignmentSource': 'Abrir la fuente de la alineación',
29
+ 'credential.occupationsPrograms': 'Ocupaciones y programas',
30
+ 'credential.more': 'Más',
31
+ 'credential.close': 'Cerrar',
32
+ 'credential.by': 'Por',
33
+ 'credential.verified': 'Credencial verificada',
34
+ 'verification.selfIssued': 'Autoemitido',
35
+ 'verification.status.success': 'Éxito',
36
+ };
37
+
38
+ const renderLocalized = (node: React.ReactNode) =>
39
+ render(
40
+ <I18nProvider resolve={key => translations[key]} locale="es">
41
+ {node}
42
+ </I18nProvider>
43
+ );
44
+
45
+ const credential = {
46
+ issuer: 'did:example:issuer',
47
+ issuanceDate: '2026-08-14T00:00:00.000Z',
48
+ credentialSubject: {
49
+ id: 'did:example:subject',
50
+ achievement: {
51
+ name: 'Critical Thinking',
52
+ description:
53
+ 'A long description that deliberately exceeds the truncation threshold so the translated expansion control is rendered for this regression test.',
54
+ criteria: { narrative: 'Complete the critical-thinking requirements.' },
55
+ alignment: [
56
+ {
57
+ targetUrl: 'https://example.com/framework/critical-thinking',
58
+ targetName: 'Critical Thinking',
59
+ targetFramework: 'Example Framework',
60
+ targetType: 'ceterms:Credential',
61
+ },
62
+ ],
63
+ },
64
+ },
65
+ display: { displayType: 'badge' },
66
+ };
67
+
68
+ describe('VCDisplayCard2 localized labels', () => {
69
+ it('localizes back-face headings, alignment copy, and truncation controls', () => {
70
+ const { container } = renderLocalized(
71
+ <VC2BackFace
72
+ credential={credential as never}
73
+ verificationItems={[]}
74
+ showFrontFace={vi.fn()}
75
+ />
76
+ );
77
+
78
+ expect(screen.getByRole('heading', { name: 'Acerca de' })).toBeTruthy();
79
+ expect(screen.getByRole('heading', { name: 'Criterios' })).toBeTruthy();
80
+
81
+ const alignmentHeading = screen.getByRole('heading', { name: 'Alineaciones' });
82
+ expect(alignmentHeading.className).toContain('text-[20px]');
83
+ expect(alignmentHeading.className).not.toContain('text-[22px]');
84
+
85
+ fireEvent.click(screen.getByRole('button', { name: 'Más' }));
86
+ expect(screen.getByRole('button', { name: 'Cerrar' })).toBeTruthy();
87
+
88
+ const alignmentBox = alignmentHeading.closest('div.bg-white');
89
+ expect(alignmentBox).not.toBeNull();
90
+ fireEvent.click(within(alignmentBox as HTMLElement).getByRole('button'));
91
+
92
+ expect(
93
+ screen.getByText('Las alineaciones vinculan este logro con marcos establecidos.')
94
+ ).toBeTruthy();
95
+ expect(screen.getByText('Fuente verificada')).toBeTruthy();
96
+ expect(screen.getByText('Verificada')).toBeTruthy();
97
+ expect(screen.getByRole('link', { name: 'Abrir la fuente de la alineación' })).toBeTruthy();
98
+ expect(container.textContent).not.toContain('Alignments');
99
+ });
100
+
101
+ it('localizes the verifier badge used on the credential front face', () => {
102
+ renderLocalized(<VerifierStateBadgeAndText verifierState={VERIFIER_STATES.selfVerified} />);
103
+
104
+ expect(screen.getByText('Autoemitido')).toBeTruthy();
105
+ expect(screen.queryByText('Self Issued')).toBeNull();
106
+ });
107
+
108
+ it('localizes the verified-credential footer and verification status', () => {
109
+ renderLocalized(
110
+ <VCDisplayCard2
111
+ credential={credential as never}
112
+ verificationItems={[{ status: VerificationStatusEnum.Success } as never]}
113
+ hideNavButtons
114
+ />
115
+ );
116
+
117
+ expect(screen.getByText('Credencial verificada')).toBeTruthy();
118
+ expect(screen.getByText('Éxito')).toBeTruthy();
119
+ expect(screen.getByText('Por')).toBeTruthy();
120
+ expect(screen.queryByText('Verified Credential')).toBeNull();
121
+ });
122
+ });
@@ -88,7 +88,7 @@ describe('Credential Helpers', () => {
88
88
  ...JffCredential,
89
89
  issuanceDate: 'invalid-date-string',
90
90
  };
91
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
91
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
92
92
  const result = getInfoFromCredential(credentialWithInvalidDate as any);
93
93
  expect(result.createdAt).toEqual('');
94
94
  expect(consoleSpy).toHaveBeenCalledWith(