@hotosm/hanko-auth 0.4.1 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotosm/hanko-auth",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Web component for HOTOSM SSO authentication with Hanko and OSM integration",
5
5
  "type": "module",
6
6
  "main": "dist/hanko-auth.umd.js",
@@ -10,7 +10,8 @@
10
10
  "import": "./dist/hanko-auth.esm.js",
11
11
  "require": "./dist/hanko-auth.umd.js"
12
12
  },
13
- "./dist/*": "./dist/*"
13
+ "./dist/*": "./dist/*",
14
+ "./src/*": "./src/*"
14
15
  },
15
16
  "files": [
16
17
  "dist",
@@ -13,7 +13,10 @@ export const styles = css`
13
13
  }
14
14
 
15
15
  .loading {
16
- text-align: center;
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ min-height: 200px;
17
20
  padding: var(--hot-spacing-3x-large);
18
21
  color: var(--hot-color-gray-600);
19
22
  }
@@ -27,18 +30,38 @@ export const styles = css`
27
30
  }
28
31
 
29
32
  .spinner {
30
- width: var(--hot-spacing-3x-large);
31
- height: var(--hot-spacing-3x-large);
32
- border: var(--hot-spacing-2x-small) solid var(--hot-color-gray-50);
33
- border-top: var(--hot-spacing-2x-small) solid var(--hot-color-red-600);
33
+ width: clamp(40px, 10%, 60px);
34
+ height: clamp(40px, 10%, 60px);
35
+ border: 4px solid var(--hot-color-gray-50);
36
+ border-top: 4px solid var(--hot-color-red-600);
34
37
  border-radius: 50%;
35
38
  animation: spin 1s linear infinite;
39
+ margin: 0 auto;
40
+ }
41
+ /* Container that mimics the login button dimensions */
42
+ .loading-placeholder {
43
+ display: inline-grid;
44
+ place-items: center;
45
+ /* Use same styling as login-link button */
46
+ padding: var(--login-btn-padding, var(--hot-spacing-x-small) var(--hot-spacing-medium));
47
+ margin: var(--login-btn-margin, 0);
48
+ font-size: var(--login-btn-text-size, var(--hot-font-size-medium));
49
+ font-family: var(--login-btn-font-family, inherit);
50
+ border-radius: var(--login-btn-border-radius, var(--hot-border-radius-medium));
51
+ }
52
+
53
+ /* Invisible text to reserve button width */
54
+ .loading-placeholder-text {
55
+ visibility: hidden;
56
+ grid-area: 1 / 1;
36
57
  }
58
+
37
59
  .spinner-small {
38
- width: var(--hot-spacing-x-large);
39
- height: var(--hot-spacing-x-large);
40
- border: var(--hot-spacing-2x-small) solid var(--hot-color-gray-50);
41
- border-top: var(--hot-spacing-2x-small) solid var(--hot-color-gray-600);
60
+ grid-area: 1 / 1;
61
+ width: 1em;
62
+ height: 1em;
63
+ border: 2px solid var(--hot-color-gray-200);
64
+ border-top: 2px solid var(--hot-color-gray-600);
42
65
  border-radius: 50%;
43
66
  animation: spin 1s linear infinite;
44
67
  }
@@ -230,15 +253,17 @@ export const styles = css`
230
253
  }
231
254
 
232
255
  .login-link {
233
- color: white;
234
- font-size: var(--hot-font-size-medium);
235
- border-radius: var(--hot-border-radius-medium);
256
+ color: var(--login-btn-text-color, white);
257
+ font-size: var(--login-btn-text-size, var(--hot-font-size-medium));
258
+ border-radius: var(--login-btn-border-radius, var(--hot-border-radius-medium));
236
259
  text-decoration: none;
237
- padding: var(--hot-spacing-x-small) var(--hot-spacing-medium);
260
+ padding: var(--login-btn-padding, var(--hot-spacing-x-small) var(--hot-spacing-medium));
261
+ margin: var(--login-btn-margin, 0);
238
262
  display: inline-block;
239
263
  cursor: pointer;
240
264
  transition: all 0.2s;
241
265
  font-weight: var(--hot-font-weight-medium);
266
+ font-family: var(--login-btn-font-family, inherit);
242
267
  }
243
268
 
244
269
  /* Button variants - filled */
@@ -246,77 +271,76 @@ export const styles = css`
246
271
  border: none;
247
272
  }
248
273
  .login-link.filled.primary {
249
- background: var(--hot-color-primary-1000);
250
- color: white;
274
+ background: var(--login-btn-bg-color, var(--hot-color-primary-1000));
275
+ color: var(--login-btn-text-color, white);
251
276
  }
252
277
  .login-link.filled.primary:hover {
253
- background: var(--hot-color-primary-900);
278
+ background: var(--login-btn-hover-bg-color, var(--hot-color-primary-900));
254
279
  }
255
280
  .login-link.filled.neutral {
256
- background: var(--hot-color-neutral-600);
257
- color: white;
281
+ background: var(--login-btn-bg-color, var(--hot-color-neutral-600));
282
+ color: var(--login-btn-text-color, white);
258
283
  }
259
284
  .login-link.filled.neutral:hover {
260
- background: var(--hot-color-neutral-700);
285
+ background: var(--login-btn-hover-bg-color, var(--hot-color-neutral-700));
261
286
  }
262
287
  .login-link.filled.danger {
263
- background: var(--hot-color-red-600);
264
- color: white;
288
+ background: var(--login-btn-bg-color, var(--hot-color-red-600));
289
+ color: var(--login-btn-text-color, white);
265
290
  }
266
291
  .login-link.filled.danger:hover {
267
- background: var(--hot-color-red-700);
292
+ background: var(--login-btn-hover-bg-color, var(--hot-color-red-700));
268
293
  }
269
294
 
270
295
  /* Button variants - outline */
271
296
  .login-link.outline {
272
- background: transparent;
297
+ background: var(--login-btn-bg-color, transparent);
273
298
  border: 1px solid;
274
299
  }
275
300
  .login-link.outline.primary {
276
- border-color: var(--hot-color-primary-1000);
277
- color: var(--hot-color-primary-1000);
301
+ border-color: var(--login-btn-bg-color, var(--hot-color-primary-1000));
302
+ color: var(--login-btn-text-color, var(--hot-color-primary-1000));
278
303
  }
279
304
  .login-link.outline.primary:hover {
280
- background: var(--hot-color-primary-50);
305
+ background: var(--login-btn-hover-bg-color, var(--hot-color-primary-50));
281
306
  }
282
307
  .login-link.outline.neutral {
283
- border-color: var(--hot-color-neutral-700);
284
- color: var(--hot-color-neutral-700);
308
+ border-color: var(--login-btn-bg-color, var(--hot-color-neutral-700));
309
+ color: var(--login-btn-text-color, var(--hot-color-neutral-700));
285
310
  }
286
311
  .login-link.outline.neutral:hover {
287
- background: var(--hot-color-neutral-50);
312
+ background: var(--login-btn-hover-bg-color, var(--hot-color-neutral-50));
288
313
  }
289
314
  .login-link.outline.danger {
290
- border-color: var(--hot-color-red-600);
291
- color: var(--hot-color-red-600);
315
+ border-color: var(--login-btn-bg-color, var(--hot-color-red-600));
316
+ color: var(--login-btn-text-color, var(--hot-color-red-600));
292
317
  }
293
318
  .login-link.outline.danger:hover {
294
- background: var(--hot-color-red-50);
319
+ background: var(--login-btn-hover-bg-color, var(--hot-color-red-50));
295
320
  }
296
321
 
297
322
  /* Button variants - plain */
298
323
  .login-link.plain {
299
- background: transparent;
324
+ background: var(--login-btn-bg-color, transparent);
300
325
  border: none;
301
- padding: var(--hot-spacing-x-small) var(--hot-spacing-medium);
302
326
  }
303
327
  .login-link.plain.primary {
304
- color: var(--hot-color-primary-1000);
328
+ color: var(--login-btn-text-color, var(--hot-color-primary-1000));
305
329
  }
306
330
  .login-link.plain.primary:hover {
307
- background: var(--hot-color-primary-50);
331
+ background: var(--login-btn-hover-bg-color, var(--hot-color-primary-50));
308
332
  }
309
333
  .login-link.plain.neutral {
310
- color: var(--hot-color-neutral-700);
334
+ color: var(--login-btn-text-color, var(--hot-color-neutral-700));
311
335
  }
312
336
  .login-link.plain.neutral:hover {
313
- background: var(--hot-color-neutral-50);
337
+ background: var(--login-btn-hover-bg-color, var(--hot-color-neutral-50));
314
338
  }
315
339
  .login-link.plain.danger {
316
- color: var(--hot-color-red-600);
340
+ color: var(--login-btn-text-color, var(--hot-color-red-600));
317
341
  }
318
342
  .login-link.plain.danger:hover {
319
- background: var(--hot-color-red-50);
343
+ background: var(--login-btn-hover-bg-color, var(--hot-color-red-50));
320
344
  }
321
345
  /* Dropdown styles */
322
346
  .dropdown {
package/src/hanko-auth.ts CHANGED
@@ -11,15 +11,54 @@
11
11
 
12
12
  import { LitElement, html, css } from "lit";
13
13
  import { customElement, property, state } from "lit/decorators.js";
14
+ import { keyed } from "lit/directives/keyed.js";
14
15
  import { register } from "@teamhanko/hanko-elements";
15
16
  import { styles } from "./hanko-auth.styles";
17
+ // hanko ui translations
18
+ import { en } from "@teamhanko/hanko-elements/i18n/en";
19
+ import { es } from "./hanko-i18n-es";
20
+ import { fr } from "./hanko-i18n-fr";
21
+ import { pt } from "./hanko-i18n-pt";
22
+ // custom component translations
16
23
  import { translations } from "./translations";
24
+
17
25
  //Icons
18
26
  import accountIcon from "../assets/icon-account.svg";
19
27
  import logoutIcon from "../assets/icon-logout.svg";
20
28
  import mapIcon from "../assets/icon-map.svg";
21
29
  import checkIcon from "../assets/icon-check.svg";
22
30
 
31
+ // Track if Hanko has been registered globally
32
+ let hankoRegistered = false;
33
+ let hankoRegistrationPromise: Promise<void> | null = null;
34
+
35
+ // Pre-register translations at module load time to prevent 404 errors
36
+ // This will be called again with hankoUrl when component initializes
37
+ async function ensureHankoRegistered(hankoUrl: string): Promise<void> {
38
+ if (hankoRegistered) return;
39
+ if (hankoRegistrationPromise) return hankoRegistrationPromise;
40
+
41
+ hankoRegistrationPromise = (async () => {
42
+ console.log("[hanko-auth] Pre-registering Hanko translations...");
43
+ try {
44
+ await register(hankoUrl, {
45
+ enablePasskeys: false,
46
+ hidePasskeyButtonOnLogin: true,
47
+ translations: { en, es, fr, pt },
48
+ fallbackLanguage: "en",
49
+ });
50
+ hankoRegistered = true;
51
+ console.log("[hanko-auth] Hanko registration complete");
52
+ } catch (error) {
53
+ console.error("[hanko-auth] Hanko registration failed:", error);
54
+ hankoRegistrationPromise = null;
55
+ throw error;
56
+ }
57
+ })();
58
+
59
+ return hankoRegistrationPromise;
60
+ }
61
+
23
62
  // Module-level singleton state - shared across all instances
24
63
  const sharedAuth = {
25
64
  primary: null as any, // The primary instance that makes API calls
@@ -31,6 +70,7 @@ const sharedAuth = {
31
70
  initialized: false,
32
71
  instances: new Set<any>(),
33
72
  profileDisplayName: "", // Shared profile display name
73
+ hankoReady: false, // used for translations
34
74
  };
35
75
 
36
76
  // Session storage key generators to avoid duplication
@@ -78,7 +118,7 @@ export class HankoAuth extends LitElement {
78
118
  // Custom login page URL (for standalone mode - overrides ${hankoUrl}/app)
79
119
  @property({ type: String, attribute: "login-url" }) loginUrl = "";
80
120
  // Language code (en, es, fr, pt, etc.)
81
- @property({ type: String }) lang = "en";
121
+ @property({ type: String, reflect: true }) lang = "en";
82
122
  // Button variant (filled, outline, plain)
83
123
  @property({ type: String, attribute: "button-variant" }) buttonVariant:
84
124
  | "filled"
@@ -97,6 +137,7 @@ export class HankoAuth extends LitElement {
97
137
  @state() private osmLoading = false;
98
138
  @state() private loading = true;
99
139
  @state() private error: string | null = null;
140
+ @state() private hankoReady = false; // Tracks when Hanko registration is complete
100
141
  @state() private profileDisplayName: string = "";
101
142
  @state() private hasAppMapping = false; // True if user has mapping in the app
102
143
  @state() private userProfileLanguage: string | null = null; // Language from user profile
@@ -241,6 +282,8 @@ export class HankoAuth extends LitElement {
241
282
  if (this._hanko !== sharedAuth.hanko) this._hanko = sharedAuth.hanko;
242
283
  if (this.profileDisplayName !== sharedAuth.profileDisplayName)
243
284
  this.profileDisplayName = sharedAuth.profileDisplayName;
285
+ if (this.hankoReady !== sharedAuth.hankoReady)
286
+ this.hankoReady = sharedAuth.hankoReady;
244
287
  }
245
288
 
246
289
  // Update shared state and broadcast to all instances
@@ -250,6 +293,7 @@ export class HankoAuth extends LitElement {
250
293
  sharedAuth.osmData = this.osmData;
251
294
  sharedAuth.loading = this.loading;
252
295
  sharedAuth.profileDisplayName = this.profileDisplayName;
296
+ sharedAuth.hankoReady = this.hankoReady;
253
297
 
254
298
  // Sync to all other instances
255
299
  sharedAuth.instances.forEach((instance) => {
@@ -368,7 +412,7 @@ export class HankoAuth extends LitElement {
368
412
  const stylesheets = [
369
413
  {
370
414
  id: "hot-design-system",
371
- href: "https://cdn.jsdelivr.net/npm/hotosm-ui-design@latest/dist/hot.css",
415
+ href: "https://cdn.jsdelivr.net/npm/@hotosm/ui-design@latest/dist/hot.css",
372
416
  },
373
417
  {
374
418
  id: "google-font-archivo",
@@ -395,10 +439,13 @@ export class HankoAuth extends LitElement {
395
439
  }
396
440
 
397
441
  try {
398
- await register(this.hankoUrl, {
399
- enablePasskeys: false,
400
- hidePasskeyButtonOnLogin: true,
401
- });
442
+ this.log(
443
+ "Ensuring Hanko is registered with translations for: en, es, fr, pt",
444
+ );
445
+ this.log("Current lang prop:", this.lang);
446
+ await ensureHankoRegistered(this.hankoUrl);
447
+ this.hankoReady = true;
448
+ this._broadcastState();
402
449
 
403
450
  // Create persistent Hanko instance and set up session event listeners
404
451
  const { Hanko } = await import("@teamhanko/hanko-elements");
@@ -726,9 +773,14 @@ export class HankoAuth extends LitElement {
726
773
  return true; // No check needed, proceed normally
727
774
  }
728
775
 
729
- // Prevent redirect loops - if we already tried onboarding this session, don't redirect again
776
+ // If user already completed onboarding this session, skip the check
730
777
  const onboardingKey = getSessionOnboardingKey(window.location.hostname);
731
- const alreadyTriedOnboarding = sessionStorage.getItem(onboardingKey);
778
+ const onboardingCompleted = sessionStorage.getItem(onboardingKey);
779
+ if (onboardingCompleted) {
780
+ this.log("✅ Onboarding already completed this session, skipping check");
781
+ this.hasAppMapping = true;
782
+ return true;
783
+ }
732
784
 
733
785
  this.log("🔍 Checking app mapping at:", this.mappingCheckUrl);
734
786
 
@@ -742,36 +794,23 @@ export class HankoAuth extends LitElement {
742
794
  this.log("📡 Mapping check response:", data);
743
795
 
744
796
  if (data.needs_onboarding) {
745
- if (alreadyTriedOnboarding) {
746
- this.log(
747
- "⚠️ Already tried onboarding this session, skipping redirect",
748
- );
749
- return true; // Don't loop, let user continue
750
- }
751
797
  // User has Hanko session but no app mapping - redirect to onboarding
798
+ // Don't set flag here - only set it when onboarding completes
752
799
  this.log("⚠️ User needs onboarding, redirecting...");
753
- sessionStorage.setItem(onboardingKey, "true");
754
800
  const returnTo = encodeURIComponent(window.location.origin);
755
801
  const appParam = this.appId ? `onboarding=${this.appId}` : "";
756
802
  window.location.href = `${this.hankoUrl}/app?${appParam}&return_to=${returnTo}`;
757
803
  return false; // Redirect in progress, don't proceed
758
804
  }
759
805
 
760
- // User has mapping - clear the onboarding flag
761
- sessionStorage.removeItem(onboardingKey);
806
+ // User has mapping - mark onboarding as completed
807
+ sessionStorage.setItem(onboardingKey, "true");
762
808
  this.hasAppMapping = true;
763
- this.log("✅ User has app mapping");
809
+ this.log("✅ User has app mapping, onboarding marked complete");
764
810
  return true;
765
811
  } else if (response.status === 401 || response.status === 403) {
766
- if (alreadyTriedOnboarding) {
767
- this.log(
768
- "⚠️ Already tried onboarding this session, skipping redirect",
769
- );
770
- return true;
771
- }
772
812
  // Needs onboarding
773
813
  this.log("⚠️ 401/403 - User needs onboarding, redirecting...");
774
- sessionStorage.setItem(onboardingKey, "true");
775
814
  const returnTo = encodeURIComponent(window.location.origin);
776
815
  const appParam = this.appId ? `onboarding=${this.appId}` : "";
777
816
  window.location.href = `${this.hankoUrl}/app?${appParam}&return_to=${returnTo}`;
@@ -1310,10 +1349,15 @@ export class HankoAuth extends LitElement {
1310
1349
  !!this.user,
1311
1350
  "loading:",
1312
1351
  this.loading,
1352
+ "lang:",
1353
+ this.lang,
1313
1354
  );
1314
1355
 
1315
1356
  if (this.loading) {
1316
- return html`<div class="spinner-small"></div>`;
1357
+ return html`<span class="loading-placeholder"
1358
+ ><span class="loading-placeholder-text">${this.t("logIn")}</span
1359
+ ><span class="spinner-small"></span
1360
+ ></span>`;
1317
1361
  }
1318
1362
 
1319
1363
  if (this.error) {
@@ -1489,6 +1533,16 @@ export class HankoAuth extends LitElement {
1489
1533
  // Not logged in
1490
1534
  if (this.showProfile) {
1491
1535
  // On login page - show full Hanko auth form
1536
+ // Don't render until Hanko is registered to prevent 404 errors
1537
+ if (!this.hankoReady) {
1538
+ this.log(
1539
+ "⏳ Waiting for Hanko registration before rendering form...",
1540
+ );
1541
+ return html`<span class="loading-placeholder"
1542
+ ><span class="loading-placeholder-text">${this.t("logIn")}</span
1543
+ ><span class="spinner-small"></span
1544
+ ></span>`;
1545
+ }
1492
1546
  return html`
1493
1547
  <div
1494
1548
  class="container"
@@ -1514,7 +1568,10 @@ export class HankoAuth extends LitElement {
1514
1568
  --headline2-font-weight: var(--hot-font-weight-semibold);
1515
1569
  "
1516
1570
  >
1517
- <hanko-auth></hanko-auth>
1571
+ ${keyed(
1572
+ this.lang,
1573
+ html`<hanko-auth lang="${this.lang}"></hanko-auth>`,
1574
+ )}
1518
1575
  </div>
1519
1576
  `;
1520
1577
  } else {
@@ -1544,6 +1601,10 @@ export class HankoAuth extends LitElement {
1544
1601
  return html`<a
1545
1602
  class="login-link ${this.buttonVariant} ${this.buttonColor}"
1546
1603
  href="${loginUrl}"
1604
+ @click=${(e: Event) => {
1605
+ e.preventDefault();
1606
+ window.location.href = loginUrl;
1607
+ }}
1547
1608
  >${this.t("logIn")}</a
1548
1609
  > `;
1549
1610
  }
@@ -1556,3 +1617,6 @@ declare global {
1556
1617
  "hotosm-auth": HankoAuth;
1557
1618
  }
1558
1619
  }
1620
+
1621
+ // Re-export Hanko translations for use by consuming apps
1622
+ export { en, es, fr, pt };
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Spanish (es) translation for Hanko Elements
3
+ * Based on the English translation structure
4
+ */
5
+
6
+ export const es = {
7
+ headlines: {
8
+ error: "Ha ocurrido un error",
9
+ loginEmail: "Iniciar sesión o crear cuenta",
10
+ loginEmailNoSignup: "Iniciar sesión",
11
+ loginFinished: "Inicio de sesión exitoso",
12
+ loginPasscode: "Ingrese el código de acceso",
13
+ loginPassword: "Ingrese la contraseña",
14
+ registerAuthenticator: "Crear una llave de acceso",
15
+ registerConfirm: "¿Crear cuenta?",
16
+ registerPassword: "Establecer nueva contraseña",
17
+ otpSetUp: "Configurar aplicación de autenticación",
18
+ profileEmails: "Correos electrónicos",
19
+ profilePassword: "Contraseña",
20
+ profilePasskeys: "Llaves de acceso",
21
+ isPrimaryEmail: "Dirección de correo principal",
22
+ setPrimaryEmail: "Establecer correo principal",
23
+ createEmail: "Ingrese un nuevo correo",
24
+ createUsername: "Ingrese un nuevo nombre de usuario",
25
+ emailVerified: "Verificado",
26
+ emailUnverified: "No verificado",
27
+ emailDelete: "Eliminar",
28
+ renamePasskey: "Renombrar llave de acceso",
29
+ deletePasskey: "Eliminar llave de acceso",
30
+ lastUsedAt: "Último uso",
31
+ createdAt: "Creado",
32
+ connectedAccounts: "Cuentas conectadas",
33
+ deleteAccount: "Eliminar cuenta",
34
+ accountNotFound: "Cuenta no encontrada",
35
+ signIn: "Iniciar sesión",
36
+ signUp: "Crear cuenta",
37
+ selectLoginMethod: "Seleccionar método de inicio de sesión",
38
+ setupLoginMethod: "Configurar método de inicio de sesión",
39
+ lastUsed: "Visto por última vez",
40
+ ipAddress: "Dirección IP",
41
+ revokeSession: "Revocar sesión",
42
+ profileSessions: "Sesiones",
43
+ mfaSetUp: "Configurar MFA",
44
+ securityKeySetUp: "Agregar clave de seguridad",
45
+ securityKeyLogin: "Clave de seguridad",
46
+ otpLogin: "Código de autenticación",
47
+ renameSecurityKey: "Renombrar clave de seguridad",
48
+ deleteSecurityKey: "Eliminar clave de seguridad",
49
+ securityKeys: "Claves de seguridad",
50
+ authenticatorApp: "Aplicación de autenticación",
51
+ authenticatorAppAlreadySetUp: "La aplicación de autenticación está configurada",
52
+ authenticatorAppNotSetUp: "Configurar aplicación de autenticación",
53
+ trustDevice: "¿Confiar en este navegador?",
54
+ },
55
+ texts: {
56
+ enterPasscode: 'Ingrese el código que se envió a "{emailAddress}".',
57
+ enterPasscodeNoEmail:
58
+ "Ingrese el código que se envió a su dirección de correo principal.",
59
+ setupPasskey:
60
+ "Inicie sesión en su cuenta fácil y seguramente con una llave de acceso. Nota: Sus datos biométricos solo se almacenan en sus dispositivos y nunca se compartirán con nadie.",
61
+ createAccount:
62
+ 'No existe una cuenta para "{emailAddress}". ¿Desea crear una nueva cuenta?',
63
+ otpEnterVerificationCode:
64
+ "Ingrese la contraseña de un solo uso (OTP) obtenida de su aplicación de autenticación a continuación:",
65
+ otpScanQRCode:
66
+ "Escanee el código QR usando su aplicación de autenticación (como Google Authenticator o cualquier otra aplicación TOTP). Alternativamente, puede ingresar manualmente la clave secreta OTP en la aplicación.",
67
+ otpSecretKey: "Clave secreta OTP",
68
+ passwordFormatHint:
69
+ "Debe tener entre {minLength} y {maxLength} caracteres.",
70
+ securityKeySetUp:
71
+ "Use una clave de seguridad dedicada a través de USB, Bluetooth o NFC, o su teléfono móvil. Conecte o active su clave de seguridad, luego haga clic en el botón a continuación y siga las indicaciones para completar el registro.",
72
+ setPrimaryEmail:
73
+ "Establezca esta dirección de correo para ser usada para contactarlo.",
74
+ isPrimaryEmail:
75
+ "Esta dirección de correo se utilizará para contactarlo si es necesario.",
76
+ emailVerified: "Esta dirección de correo ha sido verificada.",
77
+ emailUnverified: "Esta dirección de correo no ha sido verificada.",
78
+ emailDelete:
79
+ "Si elimina esta dirección de correo, ya no podrá usarla para iniciar sesión.",
80
+ renamePasskey: "Establecer un nombre para la llave de acceso.",
81
+ deletePasskey: "Eliminar esta llave de acceso de su cuenta.",
82
+ deleteAccount:
83
+ "¿Está seguro de que desea eliminar esta cuenta? Todos los datos se eliminarán inmediatamente y no se podrán recuperar.",
84
+ noAccountExists: 'No existe una cuenta para "{emailAddress}".',
85
+ selectLoginMethodForFutureLogins:
86
+ "Seleccione uno de los siguientes métodos de inicio de sesión para usar en futuros inicios de sesión.",
87
+ howDoYouWantToLogin: "¿Cómo desea iniciar sesión?",
88
+ mfaSetUp:
89
+ "Proteja su cuenta con autenticación multifactor (MFA). MFA agrega un paso adicional a su proceso de inicio de sesión, asegurando que incluso si su contraseña o cuenta de correo está comprometida, su cuenta permanezca segura.",
90
+ securityKeyLogin:
91
+ "Conecte o active su clave de seguridad, luego haga clic en el botón a continuación. Una vez listo, úselo a través de USB, NFC o su teléfono móvil. Siga las indicaciones para completar el proceso de inicio de sesión.",
92
+ otpLogin:
93
+ "Abra su aplicación de autenticación para obtener la contraseña de un solo uso (OTP). Ingrese el código en el campo a continuación para completar su inicio de sesión.",
94
+ renameSecurityKey: "Establecer un nombre para la clave de seguridad.",
95
+ deleteSecurityKey: "Eliminar esta clave de seguridad de su cuenta.",
96
+ authenticatorAppAlreadySetUp:
97
+ "Su cuenta está protegida con una aplicación de autenticación que genera contraseñas de un solo uso basadas en tiempo (TOTP) para autenticación multifactor.",
98
+ authenticatorAppNotSetUp:
99
+ "Proteja su cuenta con una aplicación de autenticación que genera contraseñas de un solo uso basadas en tiempo (TOTP) para autenticación multifactor.",
100
+ trustDevice:
101
+ "Si confía en este navegador, no necesitará ingresar su OTP (contraseña de un solo uso) o usar su clave de seguridad para la autenticación multifactor (MFA) la próxima vez que inicie sesión.",
102
+ },
103
+ labels: {
104
+ or: "o",
105
+ no: "no",
106
+ yes: "sí",
107
+ email: "Correo electrónico",
108
+ continue: "Continuar",
109
+ copied: "copiado",
110
+ skip: "Omitir",
111
+ save: "Guardar",
112
+ password: "Contraseña",
113
+ passkey: "Llave de acceso",
114
+ passcode: "Código de acceso",
115
+ signInPassword: "Iniciar sesión con contraseña",
116
+ signInPasscode: "Iniciar sesión con código",
117
+ forgotYourPassword: "¿Olvidó su contraseña?",
118
+ back: "Atrás",
119
+ signInPasskey: "Iniciar sesión con llave de acceso",
120
+ registerAuthenticator: "Crear una llave de acceso",
121
+ signIn: "Iniciar sesión",
122
+ signUp: "Crear cuenta",
123
+ sendNewPasscode: "Enviar nuevo código",
124
+ passwordRetryAfter: "Reintentar en {passwordRetryAfter}",
125
+ passcodeResendAfter: "Solicitar nuevo código en {passcodeResendAfter}",
126
+ unverifiedEmail: "no verificado",
127
+ primaryEmail: "principal",
128
+ setAsPrimaryEmail: "Establecer como principal",
129
+ verify: "Verificar",
130
+ delete: "Eliminar",
131
+ newEmailAddress: "Nueva dirección de correo",
132
+ newPassword: "Nueva contraseña",
133
+ rename: "Renombrar",
134
+ newPasskeyName: "Nuevo nombre de llave de acceso",
135
+ addEmail: "Agregar correo",
136
+ createPasskey: "Crear una llave de acceso",
137
+ webauthnUnsupported:
138
+ "Las llaves de acceso no son compatibles con su navegador",
139
+ signInWith: "Iniciar sesión con {provider}",
140
+ deleteAccount: "Sí, eliminar esta cuenta.",
141
+ emailOrUsername: "Correo o nombre de usuario",
142
+ username: "Nombre de usuario",
143
+ optional: "opcional",
144
+ dontHaveAnAccount: "¿No tiene una cuenta?",
145
+ alreadyHaveAnAccount: "¿Ya tiene una cuenta?",
146
+ changeUsername: "Cambiar nombre de usuario",
147
+ setUsername: "Establecer nombre de usuario",
148
+ changePassword: "Cambiar contraseña",
149
+ setPassword: "Establecer contraseña",
150
+ revoke: "Revocar",
151
+ currentSession: "Sesión actual",
152
+ authenticatorApp: "Aplicación de autenticación",
153
+ securityKey: "Clave de seguridad",
154
+ securityKeyUse: "Usar clave de seguridad",
155
+ newSecurityKeyName: "Nuevo nombre de clave de seguridad",
156
+ createSecurityKey: "Agregar una clave de seguridad",
157
+ authenticatorAppManage: "Administrar aplicación de autenticación",
158
+ authenticatorAppAdd: "Configurar",
159
+ configured: "configurado",
160
+ useAnotherMethod: "Usar otro método",
161
+ lastUsed: "Último uso",
162
+ trustDevice: "Confiar en este navegador",
163
+ staySignedIn: "Mantener sesión iniciada",
164
+ },
165
+ errors: {
166
+ somethingWentWrong:
167
+ "Ha ocurrido un error técnico. Por favor, inténtelo de nuevo más tarde.",
168
+ requestTimeout: "La solicitud ha expirado.",
169
+ invalidPassword: "Correo o contraseña incorrectos.",
170
+ invalidPasscode: "El código proporcionado no es correcto.",
171
+ passcodeAttemptsReached:
172
+ "El código se ha ingresado incorrectamente demasiadas veces. Por favor, solicite un nuevo código.",
173
+ tooManyRequests:
174
+ "Se han realizado demasiadas solicitudes. Por favor, espere para repetir la operación solicitada.",
175
+ unauthorized:
176
+ "Su sesión ha expirado. Por favor, inicie sesión nuevamente.",
177
+ invalidWebauthnCredential: "Esta llave de acceso ya no se puede usar.",
178
+ passcodeExpired: "El código ha expirado. Por favor, solicite uno nuevo.",
179
+ userVerification:
180
+ "Se requiere verificación de usuario. Asegúrese de que su dispositivo de autenticación esté protegido con un PIN o biometría.",
181
+ emailAddressAlreadyExistsError: "La dirección de correo ya existe.",
182
+ maxNumOfEmailAddressesReached:
183
+ "No se pueden agregar más direcciones de correo.",
184
+ thirdPartyAccessDenied:
185
+ "Acceso denegado. La solicitud fue cancelada por el usuario o el proveedor ha denegado el acceso por otras razones.",
186
+ thirdPartyMultipleAccounts:
187
+ "No se puede identificar la cuenta. La dirección de correo es usada por múltiples cuentas.",
188
+ thirdPartyUnverifiedEmail:
189
+ "Se requiere verificación de correo. Por favor, verifique la dirección de correo usada con su proveedor.",
190
+ signupDisabled: "El registro de cuentas está deshabilitado.",
191
+ handlerNotFoundError:
192
+ "El paso actual en su proceso no es compatible con esta versión de la aplicación. Inténtelo de nuevo más tarde o contacte al soporte si el problema persiste.",
193
+ },
194
+ flowErrors: {
195
+ technical_error:
196
+ "Ha ocurrido un error técnico. Por favor, inténtelo de nuevo más tarde.",
197
+ flow_expired_error:
198
+ "La sesión ha expirado, haga clic en el botón para reiniciar.",
199
+ value_invalid_error: "El valor ingresado no es válido.",
200
+ passcode_invalid: "El código proporcionado no es correcto.",
201
+ passkey_invalid: "Esta llave de acceso ya no se puede usar.",
202
+ passcode_max_attempts_reached:
203
+ "El código se ha ingresado incorrectamente demasiadas veces. Por favor, solicite un nuevo código.",
204
+ rate_limit_exceeded:
205
+ "Se han realizado demasiadas solicitudes. Por favor, espere para repetir la operación solicitada.",
206
+ unknown_username_error: "El nombre de usuario es desconocido.",
207
+ unknown_email_error: "La dirección de correo es desconocida.",
208
+ username_already_exists: "El nombre de usuario ya está en uso.",
209
+ invalid_username_error:
210
+ "El nombre de usuario solo debe contener letras, números y guiones bajos.",
211
+ email_already_exists: "El correo ya está en uso.",
212
+ not_found: "No se encontró el recurso solicitado.",
213
+ operation_not_permitted_error: "La operación no está permitida.",
214
+ flow_discontinuity_error:
215
+ "El proceso no se puede continuar debido a la configuración del usuario o del proveedor.",
216
+ form_data_invalid_error:
217
+ "Los datos del formulario enviados contienen errores.",
218
+ unauthorized: "Su sesión ha expirado. Por favor, inicie sesión nuevamente.",
219
+ value_missing_error: "Falta el valor.",
220
+ value_too_long_error: "El valor es demasiado largo.",
221
+ value_too_short_error: "El valor es demasiado corto.",
222
+ webauthn_credential_invalid_mfa_only:
223
+ "Esta credencial solo se puede usar como clave de seguridad de segundo factor.",
224
+ webauthn_credential_already_exists:
225
+ "La solicitud expiró, se canceló o el dispositivo ya está registrado. Inténtelo de nuevo o intente usar otro dispositivo.",
226
+ platform_authenticator_required:
227
+ "Su cuenta está configurada para usar autenticadores de plataforma, pero su dispositivo o navegador actual no admite esta función. Inténtelo de nuevo con un dispositivo o navegador compatible.",
228
+ },
229
+ };