@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/README.md +32 -0
- package/dist/hanko-auth.esm.js +2515 -1861
- package/dist/hanko-auth.iife.js +121 -86
- package/dist/hanko-auth.umd.js +121 -86
- package/package.json +3 -2
- package/src/hanko-auth.styles.ts +64 -40
- package/src/hanko-auth.ts +91 -27
- package/src/hanko-i18n-es.ts +229 -0
- package/src/hanko-i18n-fr.ts +229 -0
- package/src/hanko-i18n-pt.ts +229 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotosm/hanko-auth",
|
|
3
|
-
"version": "0.4.
|
|
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",
|
package/src/hanko-auth.styles.ts
CHANGED
|
@@ -13,7 +13,10 @@ export const styles = css`
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.loading {
|
|
16
|
-
|
|
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:
|
|
31
|
-
height:
|
|
32
|
-
border:
|
|
33
|
-
border-top:
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
border
|
|
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/
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
//
|
|
776
|
+
// If user already completed onboarding this session, skip the check
|
|
730
777
|
const onboardingKey = getSessionOnboardingKey(window.location.hostname);
|
|
731
|
-
const
|
|
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 -
|
|
761
|
-
sessionStorage.
|
|
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`<
|
|
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
|
-
|
|
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
|
+
};
|