@hotosm/hanko-auth 0.3.5 → 0.4.1

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/src/hanko-auth.ts CHANGED
@@ -12,6 +12,8 @@
12
12
  import { LitElement, html, css } from "lit";
13
13
  import { customElement, property, state } from "lit/decorators.js";
14
14
  import { register } from "@teamhanko/hanko-elements";
15
+ import { styles } from "./hanko-auth.styles";
16
+ import { translations } from "./translations";
15
17
  //Icons
16
18
  import accountIcon from "../assets/icon-account.svg";
17
19
  import logoutIcon from "../assets/icon-logout.svg";
@@ -50,6 +52,7 @@ interface OSMData {
50
52
 
51
53
  @customElement("hotosm-auth")
52
54
  export class HankoAuth extends LitElement {
55
+ static styles = styles;
53
56
  // Properties (from attributes)
54
57
  @property({ type: String, attribute: "hanko-url" }) hankoUrlAttr = "";
55
58
  @property({ type: String, attribute: "base-path" }) basePath = "";
@@ -74,6 +77,18 @@ export class HankoAuth extends LitElement {
74
77
  @property({ type: String, attribute: "app-id" }) appId = "";
75
78
  // Custom login page URL (for standalone mode - overrides ${hankoUrl}/app)
76
79
  @property({ type: String, attribute: "login-url" }) loginUrl = "";
80
+ // Language code (en, es, fr, pt, etc.)
81
+ @property({ type: String }) lang = "en";
82
+ // Button variant (filled, outline, plain)
83
+ @property({ type: String, attribute: "button-variant" }) buttonVariant:
84
+ | "filled"
85
+ | "outline"
86
+ | "plain" = "plain";
87
+ // Button color (primary, neutral, danger)
88
+ @property({ type: String, attribute: "button-color" }) buttonColor:
89
+ | "primary"
90
+ | "neutral"
91
+ | "danger" = "primary";
77
92
 
78
93
  // Internal state
79
94
  @state() private user: UserState | null = null;
@@ -84,15 +99,26 @@ export class HankoAuth extends LitElement {
84
99
  @state() private error: string | null = null;
85
100
  @state() private profileDisplayName: string = "";
86
101
  @state() private hasAppMapping = false; // True if user has mapping in the app
102
+ @state() private userProfileLanguage: string | null = null; // Language from user profile
87
103
  // dropdown
88
104
  @state() private isOpen = false;
89
105
 
90
106
  private toggleDropdown() {
91
107
  this.isOpen = !this.isOpen;
108
+ if (this.isOpen) {
109
+ // Add listener when dropdown opens
110
+ setTimeout(() => {
111
+ document.addEventListener("click", this.handleOutsideClick);
112
+ }, 0);
113
+ } else {
114
+ // Remove listener when dropdown closes
115
+ document.removeEventListener("click", this.handleOutsideClick);
116
+ }
92
117
  }
93
118
 
94
119
  private closeDropdown() {
95
120
  this.isOpen = false;
121
+ document.removeEventListener("click", this.handleOutsideClick);
96
122
  }
97
123
  private handleOutsideClick = (event: MouseEvent) => {
98
124
  if (!this.contains(event.target as Node)) {
@@ -107,332 +133,6 @@ export class HankoAuth extends LitElement {
107
133
  private _hanko: any = null;
108
134
  private _isPrimary = false; // Is this the primary instance?
109
135
 
110
- static styles = css`
111
- :host {
112
- display: block;
113
- font-family: var(--hot-font-sans);
114
- }
115
-
116
- .container {
117
- max-width: 400px;
118
- margin: 0 auto;
119
- padding: var(--hot-spacing-large);
120
- }
121
-
122
- .loading {
123
- text-align: center;
124
- padding: var(--hot-spacing-3x-large);
125
- color: var(--hot-color-gray-600);
126
- }
127
-
128
- .osm-connecting {
129
- display: flex;
130
- flex-direction: column;
131
- align-items: center;
132
- gap: var(--hot-spacing-small);
133
- padding: var(--hot-spacing-large);
134
- }
135
-
136
- .spinner {
137
- width: var(--hot-spacing-3x-large);
138
- height: var(--hot-spacing-3x-large);
139
- border: var(--hot-spacing-2x-small) solid var(--hot-color-gray-50);
140
- border-top: var(--hot-spacing-2x-small) solid var(--hot-color-red-600);
141
- border-radius: 50%;
142
- animation: spin 1s linear infinite;
143
- }
144
-
145
- @keyframes spin {
146
- 0% {
147
- transform: rotate(0deg);
148
- }
149
- 100% {
150
- transform: rotate(360deg);
151
- }
152
- }
153
-
154
- .connecting-text {
155
- font-size: var(--hot-font-size-small);
156
- color: var(--hot-color-gray-600);
157
- font-weight: var(--hot-font-weight-semibold);
158
- }
159
-
160
- button {
161
- width: 100%;
162
- padding: 12px 20px;
163
- border: none;
164
- border-radius: 6px;
165
- font-size: 14px;
166
- font-weight: 500;
167
- cursor: pointer;
168
- transition: all 0.2s;
169
- }
170
-
171
- .btn-primary {
172
- background: var(--hot-color-gray-700);
173
- color: white;
174
- }
175
-
176
- .btn-primary:hover {
177
- background: var(--hot-color-gray-600);
178
- }
179
-
180
- .btn-secondary {
181
- border: 1px solid var(--hot-color-gray-700);
182
- border-radius: var(--hot-border-radius-medium);
183
- background-color: white;
184
- color: var(--hot-color-gray-700);
185
- margin-top: 8px;
186
- }
187
-
188
- .btn-secondary:hover {
189
- background: var(--hot-color-gray-50);
190
- }
191
-
192
- .error {
193
- background: var(--hot-color-red-50);
194
- border: var(--hot-border-width, 1px) solid var(--hot-color-red-200);
195
- border-radius: var(--hot-border-radius-medium);
196
- padding: var(--hot-spacing-small);
197
- color: var(--hot-color-red-700);
198
- margin-bottom: var(--hot-spacing-medium);
199
- }
200
-
201
- .profile {
202
- background: var(--hot-color-gray-50);
203
- border-radius: var(--hot-border-radius-large);
204
- padding: var(--hot-spacing-large);
205
- margin-bottom: var(--hot-spacing-medium);
206
- }
207
-
208
- .profile-header {
209
- display: flex;
210
- align-items: center;
211
- gap: var(--hot-spacing-small);
212
- margin-bottom: var(--hot-spacing-medium);
213
- }
214
-
215
- .profile-avatar {
216
- width: var(--hot-spacing-3x-large);
217
- height: var(--hot-spacing-3x-large);
218
- border-radius: 50%;
219
- background: var(--hot-color-gray-200);
220
- display: flex;
221
- align-items: center;
222
- justify-content: center;
223
- font-size: var(--hot-font-size-large);
224
- font-weight: var(--hot-font-weight-bold);
225
- color: var(--hot-color-gray-600);
226
- }
227
-
228
- .profile-info {
229
- padding: var(--hot-spacing-x-small) var(--hot-spacing-medium);
230
- }
231
-
232
- .profile-email {
233
- font-size: var(--hot-font-size-small);
234
- font-weight: var(--hot-font-weight-bold);
235
- }
236
-
237
- .osm-section {
238
- border-top: var(--hot-border-width, 1px) solid var(--hot-color-gray-100);
239
- padding-top: var(--hot-spacing-medium);
240
- padding-bottom: var(--hot-spacing-small);
241
- margin-top: var(--hot-spacing-medium);
242
- text-align: center;
243
- }
244
-
245
- .osm-connected {
246
- display: flex;
247
- align-items: center;
248
- justify-content: center;
249
- padding: var(--hot-spacing-small);
250
- background: linear-gradient(
251
- 135deg,
252
- var(--hot-color-success-50) 0%,
253
- var(--hot-color-success-50) 100%
254
- );
255
- border-radius: var(--hot-border-radius-large);
256
- border: var(--hot-border-width, 1px) solid var(--hot-color-success-200);
257
- }
258
-
259
- .osm-badge {
260
- display: flex;
261
- align-items: center;
262
- gap: var(--hot-spacing-x-small);
263
- color: var(--hot-color-success-800);
264
- font-weight: var(--hot-font-weight-semibold);
265
- font-size: var(--hot-font-size-small);
266
- text-align: left;
267
- }
268
-
269
- .osm-badge-icon {
270
- font-size: var(--hot-font-size-medium);
271
- }
272
-
273
- .osm-username {
274
- font-size: var(--hot-font-size-x-small);
275
- color: var(--hot-color-success-700);
276
- margin-top: var(--hot-spacing-2x-small);
277
- }
278
- .osm-prompt {
279
- background: var(--hot-color-warning-50);
280
- border: var(--hot-border-width, 1px) solid var(--hot-color-warning-200);
281
- border-radius: var(--hot-border-radius-large);
282
- padding: var(--hot-spacing-large);
283
- margin-bottom: var(--hot-spacing-medium);
284
- text-align: center;
285
- }
286
-
287
- .osm-prompt-title {
288
- font-weight: var(--hot-font-weight-semibold);
289
- font-size: var(--hot-font-size-medium);
290
- margin-bottom: var(--hot-spacing-small);
291
- color: var(--hot-color-gray-900);
292
- text-align: center;
293
- }
294
-
295
- .osm-prompt-text {
296
- font-size: var(--hot-font-size-small);
297
- color: var(--hot-color-gray-600);
298
- margin-bottom: var(--hot-spacing-medium);
299
- line-height: var(--hot-line-height-normal);
300
- text-align: center;
301
- }
302
-
303
- .osm-status-badge {
304
- position: absolute;
305
- top: 2px;
306
- right: 2px;
307
- width: var(--hot-font-size-small);
308
- height: var(--hot-font-size-small);
309
- border-radius: 50%;
310
- border: var(--hot-spacing-3x-small) solid white;
311
- display: flex;
312
- align-items: center;
313
- justify-content: center;
314
- font-size: var(--hot-font-size-2x-small);
315
- color: white;
316
- font-weight: var(--hot-font-weight-bold);
317
- }
318
-
319
- .osm-status-badge.connected {
320
- background-color: var(--hot-color-success-600);
321
- }
322
-
323
- .osm-status-badge.required {
324
- background-color: var(--hot-color-warning-600);
325
- }
326
- .header-avatar {
327
- width: var(--hot-spacing-2x-large);
328
- height: var(--hot-spacing-2x-large);
329
- border-radius: 50%;
330
- background: var(--hot-color-gray-800);
331
- display: inline-flex;
332
- align-items: center;
333
- justify-content: center;
334
- font-size: var(--hot-font-size-small);
335
- font-weight: var(--hot-font-weight-semibold);
336
- color: white;
337
- }
338
-
339
- .login-link {
340
- color: var(--hot-color-neutral-950);
341
- font-size: var(--hot-font-size-small);
342
- border-radius: var(--hot-border-radius-medium);
343
- text-decoration: none;
344
- padding: 14px;
345
- }
346
- .login-link:hover {
347
- background: var(--hot-color-gray-50);
348
- }
349
- /* Dropdown styles */
350
- .dropdown {
351
- position: relative;
352
- display: inline-block;
353
- }
354
- .dropdown-trigger {
355
- background: none;
356
- border: none;
357
- padding: var(--hot-spacing-x-small);
358
- cursor: pointer;
359
- position: relative;
360
- }
361
-
362
- .dropdown-trigger.no-hover:hover,
363
- .dropdown-trigger.no-hover:active,
364
- .dropdown-trigger.no-hover:focus {
365
- background: none;
366
- outline: none;
367
- }
368
- .dropdown-content {
369
- position: absolute;
370
- right: 0;
371
- background: white;
372
- border: 1px solid var(--hot-color-gray-100);
373
- border-radius: var(--hot-border-radius-medium);
374
- z-index: 1000;
375
- opacity: 0;
376
- visibility: hidden;
377
- transform: translateY(-10px);
378
- transition:
379
- opacity 0.2s ease,
380
- visibility 0.2s ease,
381
- transform 0.2s ease;
382
- }
383
- @media (max-width: 768px) {
384
- .dropdown-content {
385
- position: fixed;
386
- width: 100%;
387
- }
388
- }
389
-
390
- .dropdown-content.open {
391
- opacity: 1;
392
- visibility: visible;
393
- transform: translateY(0);
394
- }
395
-
396
- .dropdown-content button {
397
- display: flex;
398
- align-items: center;
399
- width: 100%;
400
- padding: var(--hot-spacing-small) var(--hot-spacing-medium);
401
- background: none;
402
- border: none;
403
- cursor: pointer;
404
- text-align: left;
405
- transition: background-color 0.2s ease;
406
- gap: var(--hot-spacing-small);
407
- font-size: var(--hot-font-size-small);
408
- color: var(--hot-color-gray-900);
409
- }
410
-
411
- .dropdown-content button:hover {
412
- background-color: var(--hot-color-gray-50);
413
- }
414
-
415
- .dropdown-content button:focus {
416
- background-color: var(--hot-color-gray-50);
417
- outline: 2px solid var(--hot-color-gray-500);
418
- outline-offset: -2px;
419
- }
420
-
421
- .dropdown-content .profile-info {
422
- padding: var(--hot-spacing-small) var(--hot-spacing-medium);
423
- }
424
-
425
- .dropdown-content .profile-email {
426
- font-size: var(--hot-font-size-small);
427
- font-weight: var(--hot-font-weight-bold);
428
- }
429
-
430
- .icon {
431
- width: 20px;
432
- height: 20px;
433
- }
434
- `;
435
-
436
136
  // Get computed hankoUrl (priority: attribute > meta tag > window.HANKO_URL > origin)
437
137
  get hankoUrl(): string {
438
138
  if (this.hankoUrlAttr) {
@@ -619,6 +319,21 @@ export class HankoAuth extends LitElement {
619
319
  }
620
320
  }
621
321
 
322
+ /**
323
+ * Get translated string for the current language
324
+ * Falls back to English if translation not found
325
+ * When user is logged in, uses their profile language instead of the lang prop
326
+ */
327
+ private t(key: keyof typeof translations.en): string {
328
+ // When user is logged in, use their profile language
329
+ const effectiveLang =
330
+ this.user && this.userProfileLanguage
331
+ ? this.userProfileLanguage
332
+ : this.lang;
333
+ const langTranslations = translations[effectiveLang] || translations.en;
334
+ return langTranslations[key] || translations.en[key] || key;
335
+ }
336
+
622
337
  private warn(...args: any[]) {
623
338
  console.warn(...args);
624
339
  }
@@ -648,6 +363,7 @@ export class HankoAuth extends LitElement {
648
363
  return path;
649
364
  }
650
365
 
366
+ // styles injected to ensure global availability
651
367
  private injectHotStyles() {
652
368
  const stylesheets = [
653
369
  {
@@ -1072,7 +788,7 @@ export class HankoAuth extends LitElement {
1072
788
  }
1073
789
  }
1074
790
 
1075
- // Fetch profile display name from login backend
791
+ // Fetch profile display name and language from login backend
1076
792
  private async fetchProfileDisplayName() {
1077
793
  try {
1078
794
  const profileUrl = `${this.hankoUrl}/api/profile/me`;
@@ -1091,6 +807,12 @@ export class HankoAuth extends LitElement {
1091
807
  `${profile.first_name || ""} ${profile.last_name || ""}`.trim();
1092
808
  this.log("👤 Display name set to:", this.profileDisplayName);
1093
809
  }
810
+
811
+ // Set language from user profile if available
812
+ if (profile.language) {
813
+ this.userProfileLanguage = profile.language;
814
+ this.log("🌐 Language set from profile:", this.userProfileLanguage);
815
+ }
1094
816
  }
1095
817
  } catch (error) {
1096
818
  this.log("⚠️ Could not fetch profile:", error);
@@ -1426,6 +1148,7 @@ export class HankoAuth extends LitElement {
1426
1148
  this.osmConnected = false;
1427
1149
  this.osmData = null;
1428
1150
  this.hasAppMapping = false;
1151
+ this.userProfileLanguage = null; // Clear user's language preference
1429
1152
 
1430
1153
  // Broadcast state changes to other instances
1431
1154
  if (this._isPrimary) {
@@ -1590,7 +1313,7 @@ export class HankoAuth extends LitElement {
1590
1313
  );
1591
1314
 
1592
1315
  if (this.loading) {
1593
- return html` <button disabled>Log in</button> `;
1316
+ return html`<div class="spinner-small"></div>`;
1594
1317
  }
1595
1318
 
1596
1319
  if (this.error) {
@@ -1631,7 +1354,9 @@ export class HankoAuth extends LitElement {
1631
1354
  ${this.osmRequired && this.osmLoading
1632
1355
  ? html`
1633
1356
  <div class="osm-section">
1634
- <div class="loading">Checking OSM connection...</div>
1357
+ <div class="loading">
1358
+ ${this.t("checkingOsmConnection")}
1359
+ </div>
1635
1360
  </div>
1636
1361
  `
1637
1362
  : this.osmRequired && this.osmConnected
@@ -1641,7 +1366,7 @@ export class HankoAuth extends LitElement {
1641
1366
  <div class="osm-badge">
1642
1367
  <span class="osm-badge-icon">🗺️</span>
1643
1368
  <div>
1644
- <div>Connected to OpenStreetMap</div>
1369
+ <div>${this.t("connectedToOpenStreetMap")}</div>
1645
1370
  ${this.osmData?.osm_username
1646
1371
  ? html`
1647
1372
  <div class="osm-username">
@@ -1663,20 +1388,22 @@ export class HankoAuth extends LitElement {
1663
1388
  <div class="osm-connecting">
1664
1389
  <div class="spinner"></div>
1665
1390
  <div class="connecting-text">
1666
- 🗺️ Connecting to OpenStreetMap...
1391
+ 🗺️ ${this.t("connectingToOpenStreetMap")}
1667
1392
  </div>
1668
1393
  </div>
1669
1394
  `
1670
1395
  : html`
1671
- <div class="osm-prompt-title">🌍 OSM Required</div>
1396
+ <div class="osm-prompt-title">
1397
+ 🌍 ${this.t("osmRequired")}
1398
+ </div>
1672
1399
  <div class="osm-prompt-text">
1673
- This endpoint requires OSM connection.
1400
+ ${this.t("osmRequiredText")}
1674
1401
  </div>
1675
1402
  <button
1676
1403
  @click=${this.handleOSMConnect}
1677
1404
  class="btn-primary"
1678
1405
  >
1679
- Connect OSM Account
1406
+ ${this.t("connectOsmAccount")}
1680
1407
  </button>
1681
1408
  `}
1682
1409
  </div>
@@ -1684,7 +1411,7 @@ export class HankoAuth extends LitElement {
1684
1411
  : ""}
1685
1412
 
1686
1413
  <button @click=${this.handleLogout} class="btn-secondary">
1687
- Log out
1414
+ ${this.t("logOut")}
1688
1415
  </button>
1689
1416
  </div>
1690
1417
  </div>
@@ -1695,7 +1422,7 @@ export class HankoAuth extends LitElement {
1695
1422
  <div class="dropdown">
1696
1423
  <button
1697
1424
  @click=${this.toggleDropdown}
1698
- aria-label="Open account menu"
1425
+ aria-label="${this.t("openAccountMenu")}"
1699
1426
  aria-expanded=${this.isOpen}
1700
1427
  aria-haspopup="true"
1701
1428
  class="dropdown-trigger"
@@ -1706,7 +1433,8 @@ export class HankoAuth extends LitElement {
1706
1433
  ? html`
1707
1434
  <span
1708
1435
  class="osm-status-badge connected"
1709
- title="Connected to OSM as @${this.osmData?.osm_username}"
1436
+ title="${this.t("connectedToOsmAs")} @${this.osmData
1437
+ ?.osm_username}"
1710
1438
  >✓</span
1711
1439
  >
1712
1440
  `
@@ -1714,7 +1442,7 @@ export class HankoAuth extends LitElement {
1714
1442
  ? html`
1715
1443
  <span
1716
1444
  class="osm-status-badge required"
1717
- title="OSM connection required"
1445
+ title="${this.t("osmConnectionRequired")}"
1718
1446
  >!</span
1719
1447
  >
1720
1448
  `
@@ -1728,14 +1456,15 @@ export class HankoAuth extends LitElement {
1728
1456
  </div>
1729
1457
  <button data-action="profile" @click=${this.handleDropdownSelect}>
1730
1458
  <img src="${accountIcon}" class="icon" alt="Account icon" />
1731
- My HOT Account
1459
+ ${this.t("myHotAccount")}
1732
1460
  </button>
1733
1461
  ${this.osmRequired
1734
1462
  ? this.osmConnected
1735
1463
  ? html`
1736
1464
  <button class="osm-connected" disabled>
1737
1465
  <img src="${checkIcon}" alt="Check icon" class="icon" />
1738
- Connected to OSM (@${this.osmData?.osm_username})
1466
+ ${this.t("connectedToOsm")}
1467
+ (@${this.osmData?.osm_username})
1739
1468
  </button>
1740
1469
  `
1741
1470
  : html`
@@ -1744,13 +1473,13 @@ export class HankoAuth extends LitElement {
1744
1473
  @click=${this.handleDropdownSelect}
1745
1474
  >
1746
1475
  <img src="${mapIcon}" alt="Check icon" class="icon" />
1747
- Connect to OSM
1476
+ ${this.t("connectToOsm")}
1748
1477
  </button>
1749
1478
  `
1750
1479
  : ""}
1751
1480
  <button data-action="logout" @click=${this.handleDropdownSelect}>
1752
1481
  <img src="${logoutIcon}" alt="Log out icon" class="icon" />
1753
- Log Out
1482
+ ${this.t("logOut")}
1754
1483
  </button>
1755
1484
  </div>
1756
1485
  </div>
@@ -1810,9 +1539,13 @@ export class HankoAuth extends LitElement {
1810
1539
  const loginBase = this.loginUrl || `${baseUrl}/app`;
1811
1540
  const loginUrl = `${loginBase}?return_to=${encodeURIComponent(
1812
1541
  returnTo,
1813
- )}${this.osmRequired ? "&osm_required=true" : ""}${autoConnectParam}`;
1542
+ )}${this.osmRequired ? "&osm_required=true" : ""}${autoConnectParam}&lang=${this.lang}`;
1814
1543
 
1815
- return html`<a class="login-link" href="${loginUrl}">Log in</a> `;
1544
+ return html`<a
1545
+ class="login-link ${this.buttonVariant} ${this.buttonColor}"
1546
+ href="${loginUrl}"
1547
+ >${this.t("logIn")}</a
1548
+ > `;
1816
1549
  }
1817
1550
  }
1818
1551
  }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Translations for the hotosm-auth web component
3
+ **/
4
+
5
+ export interface Translations {
6
+ logIn: string;
7
+ logOut: string;
8
+ myHotAccount: string;
9
+ connectToOsm: string;
10
+ connectedToOsm: string;
11
+ connectedToOpenStreetMap: string;
12
+ connectingToOpenStreetMap: string;
13
+ checkingOsmConnection: string;
14
+ osmRequired: string;
15
+ osmRequiredText: string;
16
+ connectOsmAccount: string;
17
+ openAccountMenu: string;
18
+ connectedToOsmAs: string;
19
+ osmConnectionRequired: string;
20
+ }
21
+
22
+ export const translations: Record<string, Translations> = {
23
+ en: {
24
+ logIn: "Log in",
25
+ logOut: "Log Out",
26
+ myHotAccount: "My HOT Account",
27
+ connectToOsm: "Connect to OSM",
28
+ connectedToOsm: "Connected to OSM",
29
+ connectedToOpenStreetMap: "Connected to OpenStreetMap",
30
+ connectingToOpenStreetMap: "Connecting to OpenStreetMap...",
31
+ checkingOsmConnection: "Checking OSM connection...",
32
+ osmRequired: "OSM Required",
33
+ osmRequiredText: "This endpoint requires OSM connection.",
34
+ connectOsmAccount: "Connect OSM Account",
35
+ openAccountMenu: "Open account menu",
36
+ connectedToOsmAs: "Connected to OSM as",
37
+ osmConnectionRequired: "OSM connection required",
38
+ },
39
+ es: {
40
+ logIn: "Iniciar sesión",
41
+ logOut: "Cerrar sesión",
42
+ myHotAccount: "Mi cuenta HOT",
43
+ connectToOsm: "Conectar a OSM",
44
+ connectedToOsm: "Conectado a OSM",
45
+ connectedToOpenStreetMap: "Conectado a OpenStreetMap",
46
+ connectingToOpenStreetMap: "Conectando a OpenStreetMap...",
47
+ checkingOsmConnection: "Verificando conexión OSM...",
48
+ osmRequired: "OSM Requerido",
49
+ osmRequiredText: "Este endpoint requiere conexión OSM.",
50
+ connectOsmAccount: "Conectar cuenta OSM",
51
+ openAccountMenu: "Abrir menú de cuenta",
52
+ connectedToOsmAs: "Conectado a OSM como",
53
+ osmConnectionRequired: "Se requiere conexión OSM",
54
+ },
55
+ fr: {
56
+ logIn: "Se connecter",
57
+ logOut: "Se déconnecter",
58
+ myHotAccount: "Mon compte HOT",
59
+ connectToOsm: "Connecter à OSM",
60
+ connectedToOsm: "Connecté à OSM",
61
+ connectedToOpenStreetMap: "Connecté à OpenStreetMap",
62
+ connectingToOpenStreetMap: "Connexion à OpenStreetMap...",
63
+ checkingOsmConnection: "Vérification de la connexion OSM...",
64
+ osmRequired: "OSM requis",
65
+ osmRequiredText: "Ce point de terminaison nécessite une connexion OSM.",
66
+ connectOsmAccount: "Connecter le compte OSM",
67
+ openAccountMenu: "Ouvrir le menu du compte",
68
+ connectedToOsmAs: "Connecté à OSM en tant que",
69
+ osmConnectionRequired: "Connexion OSM requise",
70
+ },
71
+ pt: {
72
+ logIn: "Entrar",
73
+ logOut: "Sair",
74
+ myHotAccount: "Minha conta HOT",
75
+ connectToOsm: "Conectar ao OSM",
76
+ connectedToOsm: "Conectado ao OSM",
77
+ connectedToOpenStreetMap: "Conectado ao OpenStreetMap",
78
+ connectingToOpenStreetMap: "Conectando ao OpenStreetMap...",
79
+ checkingOsmConnection: "Verificando conexão OSM...",
80
+ osmRequired: "OSM Necessário",
81
+ osmRequiredText: "Este endpoint requer conexão OSM.",
82
+ connectOsmAccount: "Conectar conta OSM",
83
+ openAccountMenu: "Abrir menu da conta",
84
+ connectedToOsmAs: "Conectado ao OSM como",
85
+ osmConnectionRequired: "Conexão OSM necessária",
86
+ },
87
+ };