@eifi1/ui-kit 0.43.0 → 0.45.0

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 (134) hide show
  1. package/README.md +11 -7
  2. package/dist/admin/admin-action-log-labels.js +9 -0
  3. package/dist/admin/admin-action-log-labels.js.map +1 -1
  4. package/dist/admin/admin-action-log.d.ts +18 -1
  5. package/dist/admin/admin-action-log.js +25 -16
  6. package/dist/admin/admin-action-log.js.map +1 -1
  7. package/dist/admin/admin-area-labels.d.ts +6 -0
  8. package/dist/admin/admin-area-labels.js +1 -0
  9. package/dist/admin/admin-area-labels.js.map +1 -1
  10. package/dist/admin/mail-test-card-labels.d.ts +3 -0
  11. package/dist/admin/mail-test-card-labels.js +17 -0
  12. package/dist/admin/mail-test-card-labels.js.map +1 -0
  13. package/dist/admin/mail-test-card.d.ts +74 -0
  14. package/dist/admin/mail-test-card.js +80 -0
  15. package/dist/admin/mail-test-card.js.map +1 -0
  16. package/dist/billing/provider-page.d.ts +9 -0
  17. package/dist/billing/provider-page.js.map +1 -1
  18. package/dist/components/data-table-mobile.js +17 -2
  19. package/dist/components/data-table-mobile.js.map +1 -1
  20. package/dist/components/data-table-shared.d.ts +2 -0
  21. package/dist/components/data-table-shared.js +8 -0
  22. package/dist/components/data-table-shared.js.map +1 -1
  23. package/dist/components/data-table.js +1 -6
  24. package/dist/components/data-table.js.map +1 -1
  25. package/dist/eslint/index.js +1 -1
  26. package/dist/i18n/defaults.js +9 -1
  27. package/dist/i18n/defaults.js.map +1 -1
  28. package/dist/i18n/german.js +42 -2
  29. package/dist/i18n/german.js.map +1 -1
  30. package/dist/i18n/kit-labels.d.ts +13 -0
  31. package/dist/i18n/kit-labels.js.map +1 -1
  32. package/dist/i18n/locales/en.js +60 -4
  33. package/dist/i18n/locales/en.js.map +3 -3
  34. package/dist/i18n/locales/es.js +40 -0
  35. package/dist/i18n/locales/es.js.map +1 -1
  36. package/dist/i18n/locales/fr.js +42 -2
  37. package/dist/i18n/locales/fr.js.map +1 -1
  38. package/dist/i18n/locales/hu.js +40 -0
  39. package/dist/i18n/locales/hu.js.map +1 -1
  40. package/dist/i18n/locales/it.js +41 -1
  41. package/dist/i18n/locales/it.js.map +1 -1
  42. package/dist/i18n/locales/zh.js +40 -0
  43. package/dist/i18n/locales/zh.js.map +1 -1
  44. package/dist/i18n/review.js +13 -1
  45. package/dist/i18n/review.js.map +1 -1
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.js +5 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/lib/format.d.ts +2 -1
  50. package/dist/lib/format.js +6 -1
  51. package/dist/lib/format.js.map +1 -1
  52. package/dist/offline/pwa-update-prompt-labels.d.ts +2 -0
  53. package/dist/offline/pwa-update-prompt-labels.js +9 -0
  54. package/dist/offline/pwa-update-prompt-labels.js.map +1 -0
  55. package/dist/offline/pwa-update-prompt.d.ts +32 -0
  56. package/dist/offline/pwa-update-prompt.js +36 -0
  57. package/dist/offline/pwa-update-prompt.js.map +1 -0
  58. package/dist/offline/query-persistence.d.ts +100 -0
  59. package/dist/offline/query-persistence.js +179 -0
  60. package/dist/offline/query-persistence.js.map +1 -0
  61. package/dist/offline/shared-computer-labels.d.ts +2 -0
  62. package/dist/offline/shared-computer-labels.js +9 -0
  63. package/dist/offline/shared-computer-labels.js.map +1 -0
  64. package/dist/offline/shared-computer.d.ts +44 -0
  65. package/dist/offline/shared-computer.js +51 -0
  66. package/dist/offline/shared-computer.js.map +1 -0
  67. package/dist/offline/sign-out-pending-dialog-labels.d.ts +2 -0
  68. package/dist/offline/sign-out-pending-dialog-labels.js +14 -0
  69. package/dist/offline/sign-out-pending-dialog-labels.js.map +1 -0
  70. package/dist/offline/sign-out-pending-dialog.d.ts +58 -0
  71. package/dist/offline/sign-out-pending-dialog.js +66 -0
  72. package/dist/offline/sign-out-pending-dialog.js.map +1 -0
  73. package/dist/offline.d.ts +8 -0
  74. package/dist/offline.js +24 -0
  75. package/dist/offline.js.map +1 -0
  76. package/dist/shell/admin-menu.d.ts +24 -0
  77. package/dist/shell/admin-menu.js +12 -3
  78. package/dist/shell/admin-menu.js.map +1 -1
  79. package/dist/shell.d.ts +2 -0
  80. package/dist/shell.js +2 -0
  81. package/dist/shell.js.map +1 -1
  82. package/dist/tour/tour-catalogue-labels.js +2 -2
  83. package/dist/tour/tour-catalogue-labels.js.map +1 -1
  84. package/dist/tour/tour-catalogue.d.ts +7 -1
  85. package/dist/tour/tour-catalogue.js.map +1 -1
  86. package/dist/tour/tour-info.d.ts +2 -2
  87. package/dist/tour/tour-info.js.map +1 -1
  88. package/dist/tour/tour.d.ts +38 -2
  89. package/dist/tour/tour.js +42 -3
  90. package/dist/tour/tour.js.map +1 -1
  91. package/dist/tour/use-tour-progress.d.ts +165 -12
  92. package/dist/tour/use-tour-progress.js +271 -25
  93. package/dist/tour/use-tour-progress.js.map +1 -1
  94. package/dist/tour.d.ts +3 -1
  95. package/dist/tour.js +4 -1
  96. package/dist/tour.js.map +1 -1
  97. package/package.json +25 -1
  98. package/src/admin/admin-action-log-labels.ts +9 -0
  99. package/src/admin/admin-action-log.tsx +41 -5
  100. package/src/admin/admin-area-labels.ts +7 -0
  101. package/src/admin/mail-test-card-labels.ts +19 -0
  102. package/src/admin/mail-test-card.tsx +186 -0
  103. package/src/billing/provider-page.ts +9 -0
  104. package/src/components/data-table-mobile.tsx +26 -3
  105. package/src/components/data-table-shared.tsx +18 -1
  106. package/src/components/data-table.tsx +1 -15
  107. package/src/i18n/defaults.ts +8 -0
  108. package/src/i18n/german.ts +48 -2
  109. package/src/i18n/kit-labels.tsx +13 -0
  110. package/src/i18n/locales/en.ts +13 -1
  111. package/src/i18n/locales/es.ts +46 -0
  112. package/src/i18n/locales/fr.ts +48 -2
  113. package/src/i18n/locales/hu.ts +42 -0
  114. package/src/i18n/locales/it.ts +47 -1
  115. package/src/i18n/locales/zh.ts +40 -0
  116. package/src/i18n/review.ts +12 -0
  117. package/src/index.ts +3 -0
  118. package/src/lib/format.ts +13 -2
  119. package/src/offline/pwa-update-prompt-labels.ts +9 -0
  120. package/src/offline/pwa-update-prompt.tsx +72 -0
  121. package/src/offline/query-persistence.ts +323 -0
  122. package/src/offline/shared-computer-labels.ts +8 -0
  123. package/src/offline/shared-computer.tsx +87 -0
  124. package/src/offline/sign-out-pending-dialog-labels.ts +21 -0
  125. package/src/offline/sign-out-pending-dialog.tsx +135 -0
  126. package/src/offline.ts +26 -0
  127. package/src/shell/admin-menu.tsx +40 -3
  128. package/src/shell.ts +4 -0
  129. package/src/tour/tour-catalogue-labels.ts +2 -2
  130. package/src/tour/tour-catalogue.tsx +7 -1
  131. package/src/tour/tour-info.ts +2 -2
  132. package/src/tour/tour.tsx +87 -3
  133. package/src/tour/use-tour-progress.ts +501 -34
  134. package/src/tour.ts +5 -1
@@ -1,5 +1,5 @@
1
1
  import { Fragment, useEffect, useId, useMemo, useRef, useState } from "react";
2
- import type { ReactNode } from "react";
2
+ import type { MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, ReactNode } from "react";
3
3
  import { ArrowDownWideNarrow, ArrowUpNarrowWide, ChevronDown, ChevronRight, Filter } from "lucide-react";
4
4
  import { FOCUS_RING } from "./focus-ring";
5
5
  import { IconButton } from "./icon-button";
@@ -15,7 +15,7 @@ import { DialogFrame } from "./dialog-frame";
15
15
  import { Checkbox } from "./checkbox";
16
16
  import { useLongPress } from "../hooks/use-long-press";
17
17
  import type { DataTableLabels } from "./data-table-labels";
18
- import { RowLink, cleanAttrs, columnLabel, fromOutside } from "./data-table-shared";
18
+ import { RowLink, cleanAttrs, columnLabel, fromOutside, fromOwnControl } from "./data-table-shared";
19
19
  import type {
20
20
  DataTableCellProps,
21
21
  DataTableColumn,
@@ -383,7 +383,25 @@ export function MobileRow<T>({
383
383
  // Attached whenever the table selects, the hook's `enabled` deciding whether a press
384
384
  // arms: its pointerdown also clears a stale swallow latch, which must happen in the
385
385
  // mode too, or a long press whose click never came would eat the first toggle.
386
- const press = select ? longPress.handlers : undefined;
386
+ //
387
+ // A press that starts on a control in a cell (or in a portal one opened) is that
388
+ // control's, not a way into the mode (keksdose live #428): it only clears the latch.
389
+ const press = select
390
+ ? {
391
+ ...longPress.handlers,
392
+ onPointerDown: (e: ReactPointerEvent<HTMLElement>) => {
393
+ if (fromOutside(e.target, e.currentTarget) || fromOwnControl(e.target, e.currentTarget)) {
394
+ longPress.consumeClick();
395
+ return;
396
+ }
397
+ longPress.handlers.onPointerDown(e);
398
+ },
399
+ onContextMenu: (e: ReactMouseEvent<HTMLElement>) => {
400
+ if (fromOutside(e.target, e.currentTarget) || fromOwnControl(e.target, e.currentTarget)) return;
401
+ longPress.handlers.onContextMenu(e);
402
+ },
403
+ }
404
+ : undefined;
387
405
  const body = selecting ? (
388
406
  // In the mode a card is a toggle — kk's register row: `role="button"` with
389
407
  // `aria-pressed`, the box beside it only a picture of that state (the card is the
@@ -399,6 +417,8 @@ export function MobileRow<T>({
399
417
  {...press}
400
418
  onClick={(e) => {
401
419
  if (!select!.selectable || fromOutside(e.target, e.currentTarget)) return;
420
+ // A tap on a control in a cell is that control's (keksdose live #428).
421
+ if (fromOwnControl(e.target, e.currentTarget)) return;
402
422
  select!.onToggle(!select!.selected);
403
423
  }}
404
424
  onKeyDown={(e) => {
@@ -455,6 +475,9 @@ export function MobileRow<T>({
455
475
  interactive
456
476
  ? (e) => {
457
477
  if (fromOutside(e.target, e.currentTarget)) return;
478
+ // A tap on a control in a cell (a select, a button) is that
479
+ // control's, as on the desktop `<tr>` (keksdose live #428).
480
+ if (fromOwnControl(e.target, e.currentTarget)) return;
458
481
  onRowClick!(row);
459
482
  }
460
483
  : undefined
@@ -147,6 +147,21 @@ export function fromOutside(target: EventTarget | null, row: HTMLElement): boole
147
147
  return !(target instanceof Node) || !row.contains(target);
148
148
  }
149
149
 
150
+ /**
151
+ * What counts as a control of its own inside a clickable row: a click or key press
152
+ * that lands on one of these belongs to it, not to the row's `onRowClick`. The row's
153
+ * own link (`rowHref`, marked `data-row-link`) is the exception — it IS the row.
154
+ */
155
+ const OWN_CONTROL =
156
+ 'a[href],button,input,select,textarea,label,summary,[role="button"],[role="link"],[role="checkbox"],[role="switch"],[role="menuitem"],[role="option"],[contenteditable=""],[contenteditable="true"],[tabindex]:not([tabindex="-1"])';
157
+
158
+ /** Whether an event on `row` started on a control nested inside it. */
159
+ export function fromOwnControl(target: EventTarget | null, row: HTMLElement): boolean {
160
+ if (!(target instanceof Element)) return false;
161
+ const hit = target.closest(OWN_CONTROL);
162
+ return !!hit && hit !== row && row.contains(hit) && !hit.hasAttribute("data-row-link");
163
+ }
164
+
150
165
  /** A click the APP owns. Anything else — middle, ⌘/Ctrl, Shift, Alt — belongs to
151
166
  * the browser, and the whole feature is not touching it. */
152
167
  const isPlainLeftClick = (e: React.MouseEvent) =>
@@ -215,7 +230,9 @@ export function RowLink({
215
230
  }
216
231
  // Already cancelled above; this phase only decides what opens. A nested
217
232
  // control that stopped propagation deliberately never reaches it, which is
218
- // exactly how the row behaved before it became a link.
233
+ // exactly how the row behaved before it became a link — and one that did not
234
+ // (a select in a phone card's cell, keksdose live #428) is still its own.
235
+ if (fromOwnControl(e.target, e.currentTarget)) return;
219
236
  onActivate?.();
220
237
  }}
221
238
  >
@@ -10,6 +10,7 @@ import {
10
10
  cleanAttrs,
11
11
  columnLabel,
12
12
  fromOutside,
13
+ fromOwnControl,
13
14
  logicalAlign,
14
15
  resolveDataTableLabels,
15
16
  splitCellProps,
@@ -702,21 +703,6 @@ export const DEFAULT_DATA_TABLE_SORT_LABELS: DataTableSortLabels = {
702
703
 
703
704
  const flipDir = (d: SortDir): SortDir => (d === "asc" ? "desc" : "asc");
704
705
 
705
- /**
706
- * What counts as a control of its own inside a clickable row: a click or key press
707
- * that lands on one of these belongs to it, not to the row's `onRowClick`. The row's
708
- * own link (`rowHref`, marked `data-row-link`) is the exception — it IS the row.
709
- */
710
- const OWN_CONTROL =
711
- 'a[href],button,input,select,textarea,label,summary,[role="button"],[role="link"],[role="checkbox"],[role="switch"],[role="menuitem"],[role="option"],[contenteditable=""],[contenteditable="true"],[tabindex]:not([tabindex="-1"])';
712
-
713
- /** Whether an event on `row` started on a control nested inside it. */
714
- function fromOwnControl(target: EventTarget | null, row: HTMLElement): boolean {
715
- if (!(target instanceof Element)) return false;
716
- const hit = target.closest(OWN_CONTROL);
717
- return !!hit && hit !== row && row.contains(hit) && !hit.hasAttribute("data-row-link");
718
- }
719
-
720
706
  /** Arrow/Home/End between the focusable rows of one `<tbody>` — the roving half of
721
707
  * the keyboard-activatable rows (see the row's `onKeyDown`). */
722
708
  function moveRowFocus(from: HTMLElement, key: string): boolean {
@@ -100,6 +100,10 @@ import { DEFAULT_ROW_ACTIONS_LABELS } from "../components/row-actions-labels";
100
100
  import { DEFAULT_APP_SHELL_MORE_LABELS } from "../shell/app-shell-labels";
101
101
  import { DEFAULT_ACCOUNT_STATE_LABELS } from "../components/account-chips-labels";
102
102
  import { DEFAULT_ADMIN_AREA_LABELS } from "../admin/admin-area-labels";
103
+ import { DEFAULT_MAIL_TEST_CARD_LABELS } from "../admin/mail-test-card-labels";
104
+ import { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from "../offline/sign-out-pending-dialog-labels";
105
+ import { DEFAULT_PWA_UPDATE_PROMPT_LABELS } from "../offline/pwa-update-prompt-labels";
106
+ import { DEFAULT_SHARED_COMPUTER_LABELS } from "../offline/shared-computer-labels";
103
107
  import { DEFAULT_TOP_BAR_PARTS_LABELS } from "../shell/top-bar-parts-labels";
104
108
  import { DEFAULT_SHARE_CARD_LABELS } from "../components/share-card-labels";
105
109
  import { DEFAULT_REAUTH_DIALOG_LABELS } from "../components/reauth-dialog-labels";
@@ -262,4 +266,8 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
262
266
  notifications: DEFAULT_NOTIFICATIONS_LABELS,
263
267
  adminArea: DEFAULT_ADMIN_AREA_LABELS,
264
268
  topBarParts: DEFAULT_TOP_BAR_PARTS_LABELS,
269
+ mailTestCard: DEFAULT_MAIL_TEST_CARD_LABELS,
270
+ signOutPending: DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS,
271
+ pwaUpdate: DEFAULT_PWA_UPDATE_PROMPT_LABELS,
272
+ sharedComputer: DEFAULT_SHARED_COMPUTER_LABELS,
265
273
  };
@@ -639,12 +639,12 @@ export function germanLabels(numberLocale: string): UiKitLabels {
639
639
  stateDone: (count) => `Abgeschlossen (${n(count)})`,
640
640
  showing: (shown, total) => `${n(shown)} von ${n(total)} Touren`,
641
641
  noMatches: "Keine Tour passt",
642
- noMatchesHint: "Versuchen Sie ein anderes Wort, oder setzen Sie die Filter zurück.",
642
+ noMatchesHint: "Anderes Wort versuchen oder Filter zurücksetzen.",
643
643
  clearFilters: "Filter zurücksetzen",
644
644
  badgeNew: "Neu",
645
645
  badgeUpdated: "Aktualisiert",
646
646
  resetOn: (date, reason) => (reason ? `Zurückgesetzt am ${date}: ${reason}` : `Zurückgesetzt am ${date}`),
647
- points: (count) => `+${n(count)} Pkt.`,
647
+ points: (count) => `+${n(count)} Pkt`,
648
648
  steps: (count) => (count === 1 ? `${n(count)} Schritt` : `${n(count)} Schritte`),
649
649
  duration: (minutes) => `Etwa ${n(minutes)} Min.`,
650
650
  completedOn: (date) => `Abgeschlossen am ${date}`,
@@ -1409,11 +1409,14 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1409
1409
  // 0.34 (§11.5): whole phrases per noun. `team` in Kurvenschmiede's own words ("Team
1410
1410
  // angelegt", "aufnehmen"), `budget` in keksdose's, `company` with the string
1411
1411
  // `actions.membership_remove` held until 0.34.
1412
+ renamedFromTo: (from, to) => `von ${from} in ${to}`,
1412
1413
  groupActions: {
1413
1414
  group: {
1414
1415
  group_create: "Gruppe erstellt",
1415
1416
  group_rename: "Gruppe umbenannt",
1416
1417
  group_delete: "Gruppe gelöscht",
1418
+ group_deactivate: "Gruppe deaktiviert",
1419
+ group_reactivate: "Gruppe reaktiviert",
1417
1420
  membership_add: "Zur Gruppe hinzugefügt",
1418
1421
  membership_role: "Rolle in der Gruppe geändert",
1419
1422
  membership_remove: "Aus der Gruppe entfernt",
@@ -1423,6 +1426,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1423
1426
  group_create: "Team angelegt",
1424
1427
  group_rename: "Team umbenannt",
1425
1428
  group_delete: "Team gelöscht",
1429
+ group_deactivate: "Team deaktiviert",
1430
+ group_reactivate: "Team reaktiviert",
1426
1431
  membership_add: "Ins Team aufgenommen",
1427
1432
  membership_role: "Rolle im Team geändert",
1428
1433
  membership_remove: "Aus dem Team entfernt",
@@ -1432,6 +1437,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1432
1437
  group_create: "Unternehmen erstellt",
1433
1438
  group_rename: "Unternehmen umbenannt",
1434
1439
  group_delete: "Unternehmen gelöscht",
1440
+ group_deactivate: "Unternehmen deaktiviert",
1441
+ group_reactivate: "Unternehmen reaktiviert",
1435
1442
  membership_add: "Zum Unternehmen hinzugefügt",
1436
1443
  membership_role: "Rolle im Unternehmen geändert",
1437
1444
  membership_remove: "Aus dem Unternehmen entfernt",
@@ -1441,6 +1448,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1441
1448
  group_create: "Budget erstellt",
1442
1449
  group_rename: "Budget umbenannt",
1443
1450
  group_delete: "Budget gelöscht",
1451
+ group_deactivate: "Budget deaktiviert",
1452
+ group_reactivate: "Budget reaktiviert",
1444
1453
  membership_add: "Zum Budget hinzugefügt",
1445
1454
  membership_role: "Rolle im Budget geändert",
1446
1455
  membership_remove: "Aus dem Budget entfernt",
@@ -1892,9 +1901,21 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1892
1901
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Verwaltung"
1893
1902
  // as settings §5 names the page; "mit Administratorrechten" rather than "für
1894
1903
  // Administratoren", which would give the reader a gender (as `transferOwnership`).
1904
+ mailTestCard: {
1905
+ title: "Testmail",
1906
+ description: "Sendet eine Mail an Ihre eigene Adresse, über den Versand, mit dem diese Installation alle Mails verschickt.",
1907
+ transport: (backend) => `Versand: ${backend}`,
1908
+ action: "Testmail an mich senden",
1909
+ sent: (address) => `An ${address} gesendet. Bitte prüfen Sie den Posteingang.`,
1910
+ console: "Der Konsolen-Versand schreibt Mails nur ins Log; es wurde nichts gesendet.",
1911
+ failed: "Senden fehlgeschlagen. Die Antwort des Anbieters steht im Server-Log.",
1912
+ rateLimited,
1913
+ keywords: "testmail test e-mail mail versand transport smtp resend konsole senden zustellen",
1914
+ },
1895
1915
  adminArea: {
1896
1916
  title: "Verwaltung",
1897
1917
  waiting: (count) => `${n(count)} offen`,
1918
+ waitingLabel: (label, count) => `${label}, ${n(count)} offen`,
1898
1919
  forbiddenTitle: "Nur mit Administratorrechten",
1899
1920
  forbiddenBody: (appName) => `Hier wird ${appName} verwaltet. Ihr Konto kann diese Seite nicht öffnen.`,
1900
1921
  forbiddenHome: "Zur Startseite",
@@ -1966,5 +1987,30 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1966
1987
  billingOff: "Die Abrechnung ist lokal aus: Tarife ändern nichts",
1967
1988
  },
1968
1989
  },
1990
+ // 0.45.0 — docs/local-first-harmonization.md §1.6 (`@eifi1/ui-kit/offline`): keksdose's
1991
+ // reviewed words (nav.logout_queued_*, pwa.*) where it has the language.
1992
+ signOutPending: {
1993
+ title: "Einige Änderungen sind noch nicht auf dem Server",
1994
+ lost: (count) =>
1995
+ count === 1
1996
+ ? `${n(count)} Änderung wartet noch auf die Synchronisierung. Beim Abmelden wird die Kopie auf diesem Gerät gelöscht – und das ist die einzige.`
1997
+ : `${n(count)} Änderungen warten noch auf die Synchronisierung. Beim Abmelden wird die Kopie auf diesem Gerät gelöscht – und das ist die einzige.`,
1998
+ kept: (count) =>
1999
+ count === 1
2000
+ ? `${n(count)} Änderung wartet noch auf die Synchronisierung. Sie bleibt für Sie auf diesem Gerät und wird bei Ihrer nächsten Anmeldung hier synchronisiert.`
2001
+ : `${n(count)} Änderungen warten noch auf die Synchronisierung. Sie bleiben für Sie auf diesem Gerät und werden bei Ihrer nächsten Anmeldung hier synchronisiert.`,
2002
+ syncNow: "Jetzt synchronisieren",
2003
+ signOutAnyway: "Trotzdem abmelden",
2004
+ discardWaiting: "Wartende Änderungen verwerfen",
2005
+ cancel: "Abbrechen",
2006
+ },
2007
+ pwaUpdate: {
2008
+ updateAvailable: "Eine neue Version ist verfügbar.",
2009
+ reload: "Neu laden",
2010
+ },
2011
+ sharedComputer: {
2012
+ label: "Dies ist ein gemeinsam genutzter Computer",
2013
+ hint: "Auf diesem Gerät bleibt keine Kopie Ihrer Daten. Seiten öffnen sich dann nur mit einer Verbindung.",
2014
+ },
1969
2015
  };
1970
2016
  }
@@ -120,7 +120,11 @@ import type { SignChipLabels } from "../components/sign-chip";
120
120
  import type { ColumnMapperLabels } from "../components/column-mapper";
121
121
  import type { NotificationsLabels } from "../notifications/notifications-labels";
122
122
  import type { AdminAreaLabels } from "../admin/admin-area-labels";
123
+ import type { MailTestCardLabels } from "../admin/mail-test-card";
123
124
  import type { TopBarPartsLabels } from "../shell/top-bar-parts-labels";
125
+ import type { SignOutPendingDialogLabels } from "../offline/sign-out-pending-dialog";
126
+ import type { PwaUpdatePromptLabels } from "../offline/pwa-update-prompt";
127
+ import type { SharedComputerLabels } from "../offline/shared-computer";
124
128
 
125
129
  /**
126
130
  * EVERY string the kit renders, as one typed tree — and an optional provider that
@@ -487,6 +491,15 @@ export interface UiKitLabels {
487
491
  /** 0.42.0: the top bar's shared parts — `TourMenu`, `SyncStatusIndicator`, `DevSwitchers`
488
492
  * (docs/top-bar-harmonization.md §3.1). */
489
493
  topBarParts: TopBarPartsLabels;
494
+ /** 0.45.0: "Send a test mail to me" (docs/admin-harmonization.md §9.2.4). */
495
+ mailTestCard: MailTestCardLabels;
496
+ /** 0.45.0: the sign-out question while changes wait to sync (`@eifi1/ui-kit/offline`,
497
+ * docs/local-first-harmonization.md §1.6). */
498
+ signOutPending: SignOutPendingDialogLabels;
499
+ /** 0.45.0: the service-worker update toast (local-first §1.5 D8). */
500
+ pwaUpdate: PwaUpdatePromptLabels;
501
+ /** 0.45.0: "This is a shared computer" on the sign-in form (local-first §1.5 D6). */
502
+ sharedComputer: SharedComputerLabels;
490
503
  }
491
504
 
492
505
  /**
@@ -220,6 +220,7 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
220
220
  adminArea: {
221
221
  ...d.adminArea,
222
222
  waiting: (count) => `${n(count)} waiting`,
223
+ waitingLabel: (label, count) => `${label}, ${n(count)} waiting`,
223
224
  tiles: { ...d.adminArea.tiles, usersNew: (count) => `${n(count)} new in 7 days` },
224
225
  },
225
226
  // 0.43.0: the tours page's counts (docs/tours-harmonization.md §3).
@@ -228,7 +229,7 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
228
229
  groupAll: (count) => `All topics (${n(count)})`,
229
230
  groupOption: (group, count) => `${group} (${n(count)})`,
230
231
  stateAll: (count) => `All (${n(count)})`,
231
- stateTodo: (count) => `To do (${n(count)})`,
232
+ stateTodo: (count) => `Not taken (${n(count)})`,
232
233
  stateDone: (count) => `Completed (${n(count)})`,
233
234
  showing: (shown, total) => `Showing ${n(shown)} of ${n(total)} tours`,
234
235
  points: (count) => `+${n(count)} pts`,
@@ -266,5 +267,16 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
266
267
  waitingChanges: (count) =>
267
268
  count === 1 ? "1 change waiting for a plan" : `${n(count)} changes waiting for a plan`,
268
269
  },
270
+ signOutPending: {
271
+ ...d.signOutPending,
272
+ lost: (count) =>
273
+ count === 1
274
+ ? d.signOutPending.lost(1)
275
+ : `${n(count)} changes are still waiting to sync. Logging out clears this device’s copy, and it is the only copy.`,
276
+ kept: (count) =>
277
+ count === 1
278
+ ? d.signOutPending.kept(1)
279
+ : `${n(count)} changes are still waiting to sync. They stay on this device for you and sync the next time you log in here.`,
280
+ },
269
281
  };
270
282
  }
@@ -1375,11 +1375,14 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1375
1375
  // 0.34 (§11.5): whole phrases per noun. The group's own agree with the noun
1376
1376
  // ("Empresa creada"), a member's with "cuenta", as `actions` do. `team` is
1377
1377
  // Kurvenschmiede's "equipo"; `company` keeps `actions.membership_remove`'s string.
1378
+ renamedFromTo: (from, to) => `de ${from} a ${to}`,
1378
1379
  groupActions: {
1379
1380
  group: {
1380
1381
  group_create: "Grupo creado",
1381
1382
  group_rename: "Grupo renombrado",
1382
1383
  group_delete: "Grupo eliminado",
1384
+ group_deactivate: "Grupo desactivado",
1385
+ group_reactivate: "Grupo reactivado",
1383
1386
  membership_add: "Añadida al grupo",
1384
1387
  membership_role: "Rol en el grupo cambiado",
1385
1388
  membership_remove: "Quitada del grupo",
@@ -1389,6 +1392,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1389
1392
  group_create: "Equipo creado",
1390
1393
  group_rename: "Equipo renombrado",
1391
1394
  group_delete: "Equipo eliminado",
1395
+ group_deactivate: "Equipo desactivado",
1396
+ group_reactivate: "Equipo reactivado",
1392
1397
  membership_add: "Añadida al equipo",
1393
1398
  membership_role: "Rol en el equipo cambiado",
1394
1399
  membership_remove: "Quitada del equipo",
@@ -1398,6 +1403,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1398
1403
  group_create: "Empresa creada",
1399
1404
  group_rename: "Empresa renombrada",
1400
1405
  group_delete: "Empresa eliminada",
1406
+ group_deactivate: "Empresa desactivada",
1407
+ group_reactivate: "Empresa reactivada",
1401
1408
  membership_add: "Añadida a la empresa",
1402
1409
  membership_role: "Rol en la empresa cambiado",
1403
1410
  membership_remove: "Quitada de la empresa",
@@ -1407,6 +1414,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1407
1414
  group_create: "Presupuesto creado",
1408
1415
  group_rename: "Presupuesto renombrado",
1409
1416
  group_delete: "Presupuesto eliminado",
1417
+ group_deactivate: "Presupuesto desactivado",
1418
+ group_reactivate: "Presupuesto reactivado",
1410
1419
  membership_add: "Añadida al presupuesto",
1411
1420
  membership_role: "Rol en el presupuesto cambiado",
1412
1421
  membership_remove: "Quitada del presupuesto",
@@ -1831,9 +1840,21 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1831
1840
  },
1832
1841
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Usted",
1833
1842
  // as the rest of the catalogue.
1843
+ mailTestCard: {
1844
+ title: "Correo de prueba",
1845
+ description: "Envía un correo a su propia dirección, por el mismo transporte con el que esta instalación envía todo su correo.",
1846
+ transport: (backend) => `Transporte: ${backend}`,
1847
+ action: "Enviarme un correo de prueba",
1848
+ sent: (address) => `Enviado a ${address}. Revise la bandeja de entrada.`,
1849
+ console: "El transporte de consola solo registra el correo; no se ha enviado nada.",
1850
+ failed: "No se pudo enviar. El registro del servidor tiene la respuesta del proveedor.",
1851
+ rateLimited,
1852
+ keywords: "correo prueba email e-mail transporte smtp resend consola enviar entrega",
1853
+ },
1834
1854
  adminArea: {
1835
1855
  title: "Administración",
1836
1856
  waiting: (count) => `${n(count)} ${plural(count, "pendiente", "pendientes")}`,
1857
+ waitingLabel: (label, count) => `${label}, ${n(count)} ${plural(count, "pendiente", "pendientes")}`,
1837
1858
  forbiddenTitle: "Solo para administradores",
1838
1859
  forbiddenBody: (appName) =>
1839
1860
  `Aquí es donde los administradores gestionan ${appName}. Su cuenta no puede abrir esta página.`,
@@ -1902,6 +1923,31 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1902
1923
  billingOff: "La facturación está desactivada en local: los planes no cambian nada",
1903
1924
  },
1904
1925
  },
1926
+ // 0.45.0 — docs/local-first-harmonization.md §1.6 (`@eifi1/ui-kit/offline`): keksdose's
1927
+ // reviewed words (nav.logout_queued_*, pwa.*) where it has the language.
1928
+ signOutPending: {
1929
+ title: "Algunos cambios aún no se han guardado en el servidor",
1930
+ lost: (count) =>
1931
+ count === 1
1932
+ ? `${n(count)} cambio sigue pendiente de sincronizar. Al cerrar sesión se borra la copia de este dispositivo, y es la única.`
1933
+ : `${n(count)} cambios siguen pendientes de sincronizar. Al cerrar sesión se borra la copia de este dispositivo, y es la única.`,
1934
+ kept: (count) =>
1935
+ count === 1
1936
+ ? `${n(count)} cambio sigue pendiente de sincronizar. Se queda en este dispositivo para usted y se sincronizará la próxima vez que inicie sesión aquí.`
1937
+ : `${n(count)} cambios siguen pendientes de sincronizar. Se quedan en este dispositivo para usted y se sincronizarán la próxima vez que inicie sesión aquí.`,
1938
+ syncNow: "Sincronizar ahora",
1939
+ signOutAnyway: "Cerrar sesión de todos modos",
1940
+ discardWaiting: "Descartar los cambios en espera",
1941
+ cancel: "Cancelar",
1942
+ },
1943
+ pwaUpdate: {
1944
+ updateAvailable: "Hay una nueva versión disponible.",
1945
+ reload: "Recargar",
1946
+ },
1947
+ sharedComputer: {
1948
+ label: "Este es un ordenador compartido",
1949
+ hint: "No queda ninguna copia de sus datos en este dispositivo. Las páginas solo se abren entonces con conexión.",
1950
+ },
1905
1951
  };
1906
1952
  }
1907
1953
 
@@ -633,11 +633,11 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
633
633
  groupOption: (group, count) => `${group} (${n(count)})`,
634
634
  stateLabel: "Filtrer les visites",
635
635
  stateAll: (count) => `Toutes (${n(count)})`,
636
- stateTodo: (count) => `À faire (${n(count)})`,
636
+ stateTodo: (count) => `Non suivies (${n(count)})`,
637
637
  stateDone: (count) => `Terminées (${n(count)})`,
638
638
  showing: (shown, total) => `${n(shown)} visites sur ${n(total)}`,
639
639
  noMatches: "Aucune visite ne correspond",
640
- noMatchesHint: "Essayez un autre mot, ou effacez les filtres.",
640
+ noMatchesHint: "Essayez un autre mot, ou effacez le filtre.",
641
641
  clearFilters: "Effacer les filtres",
642
642
  badgeNew: "Nouveau",
643
643
  badgeUpdated: "Mise à jour",
@@ -1403,11 +1403,14 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1403
1403
  // ("Équipe créée", Kurvenschmiede's feminine "équipe"), a member's with "compte",
1404
1404
  // as `actions` do. `budget` is keksdose's; `company` keeps
1405
1405
  // `actions.membership_remove`'s string.
1406
+ renamedFromTo: (from, to) => `de ${from} en ${to}`,
1406
1407
  groupActions: {
1407
1408
  group: {
1408
1409
  group_create: "Groupe créé",
1409
1410
  group_rename: "Groupe renommé",
1410
1411
  group_delete: "Groupe supprimé",
1412
+ group_deactivate: "Groupe désactivé",
1413
+ group_reactivate: "Groupe réactivé",
1411
1414
  membership_add: "Ajouté au groupe",
1412
1415
  membership_role: "Rôle dans le groupe modifié",
1413
1416
  membership_remove: "Retiré du groupe",
@@ -1417,6 +1420,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1417
1420
  group_create: "Équipe créée",
1418
1421
  group_rename: "Équipe renommée",
1419
1422
  group_delete: "Équipe supprimée",
1423
+ group_deactivate: "Équipe désactivée",
1424
+ group_reactivate: "Équipe réactivée",
1420
1425
  membership_add: "Ajouté à l’équipe",
1421
1426
  membership_role: "Rôle dans l’équipe modifié",
1422
1427
  membership_remove: "Retiré de l’équipe",
@@ -1426,6 +1431,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1426
1431
  group_create: "Entreprise créée",
1427
1432
  group_rename: "Entreprise renommée",
1428
1433
  group_delete: "Entreprise supprimée",
1434
+ group_deactivate: "Entreprise désactivée",
1435
+ group_reactivate: "Entreprise réactivée",
1429
1436
  membership_add: "Ajouté à l’entreprise",
1430
1437
  membership_role: "Rôle dans l’entreprise modifié",
1431
1438
  membership_remove: "Retiré de l’entreprise",
@@ -1435,6 +1442,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1435
1442
  group_create: "Budget créé",
1436
1443
  group_rename: "Budget renommé",
1437
1444
  group_delete: "Budget supprimé",
1445
+ group_deactivate: "Budget désactivé",
1446
+ group_reactivate: "Budget réactivé",
1438
1447
  membership_add: "Ajouté au budget",
1439
1448
  membership_role: "Rôle dans le budget modifié",
1440
1449
  membership_remove: "Retiré du budget",
@@ -1870,9 +1879,21 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1870
1879
  moreTitle: "Autres pages",
1871
1880
  },
1872
1881
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words.
1882
+ mailTestCard: {
1883
+ title: "E-mail de test",
1884
+ description: "Envoie un e-mail à votre propre adresse, par le transport avec lequel cette installation envoie tous ses e-mails.",
1885
+ transport: (backend) => `Transport : ${backend}`,
1886
+ action: "M’envoyer un e-mail de test",
1887
+ sent: (address) => `Envoyé à ${address}. Vérifiez la boîte de réception.`,
1888
+ console: "Le transport console ne fait que journaliser les e-mails ; rien n’a été envoyé.",
1889
+ failed: "Envoi impossible. Le journal du serveur contient la réponse du fournisseur.",
1890
+ rateLimited,
1891
+ keywords: "e-mail test courriel transport smtp resend console envoyer distribution",
1892
+ },
1873
1893
  adminArea: {
1874
1894
  title: "Administration",
1875
1895
  waiting: (count) => `${n(count)} en attente`,
1896
+ waitingLabel: (label, count) => `${label}, ${n(count)} en attente`,
1876
1897
  forbiddenTitle: "Réservé aux administrateurs",
1877
1898
  forbiddenBody: (appName) =>
1878
1899
  `C’est ici que les administrateurs gèrent ${appName}. Votre compte ne peut pas ouvrir cette page.`,
@@ -1941,6 +1962,31 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1941
1962
  billingOff: "La facturation est désactivée en local\u00a0: les formules ne changent rien",
1942
1963
  },
1943
1964
  },
1965
+ // 0.45.0 — docs/local-first-harmonization.md §1.6 (`@eifi1/ui-kit/offline`): keksdose's
1966
+ // reviewed words (nav.logout_queued_*, pwa.*) where it has the language.
1967
+ signOutPending: {
1968
+ title: "Certaines modifications ne sont pas encore enregistrées sur le serveur",
1969
+ lost: (count) =>
1970
+ count < 2
1971
+ ? `${n(count)} modification attend encore la synchronisation. La déconnexion efface la copie de cet appareil, et c’est la seule.`
1972
+ : `${n(count)} modifications attendent encore la synchronisation. La déconnexion efface la copie de cet appareil, et c’est la seule.`,
1973
+ kept: (count) =>
1974
+ count < 2
1975
+ ? `${n(count)} modification attend encore la synchronisation. Elle reste sur cet appareil pour vous et sera synchronisée à votre prochaine connexion ici.`
1976
+ : `${n(count)} modifications attendent encore la synchronisation. Elles restent sur cet appareil pour vous et seront synchronisées à votre prochaine connexion ici.`,
1977
+ syncNow: "Synchroniser maintenant",
1978
+ signOutAnyway: "Se déconnecter quand même",
1979
+ discardWaiting: "Abandonner les modifications en attente",
1980
+ cancel: "Annuler",
1981
+ },
1982
+ pwaUpdate: {
1983
+ updateAvailable: "Une nouvelle version est disponible.",
1984
+ reload: "Recharger",
1985
+ },
1986
+ sharedComputer: {
1987
+ label: "C’est un ordinateur partagé",
1988
+ hint: "Aucune copie de vos données ne reste sur cet appareil. Les pages ne s’ouvrent alors qu’avec une connexion.",
1989
+ },
1944
1990
  };
1945
1991
  }
1946
1992
 
@@ -1376,11 +1376,14 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1376
1376
  // 0.34 (§11.5): whole phrases per noun, each noun with its own suffix ("a
1377
1377
  // csapatból", "a cégből"). `team` is Kurvenschmiede's "csapat"; `company` keeps
1378
1378
  // `actions.membership_remove`'s string.
1379
+ renamedFromTo: (from, to) => `${from} → ${to}`,
1379
1380
  groupActions: {
1380
1381
  group: {
1381
1382
  group_create: "Csoport létrehozva",
1382
1383
  group_rename: "Csoport átnevezve",
1383
1384
  group_delete: "Csoport törölve",
1385
+ group_deactivate: "Csoport inaktiválva",
1386
+ group_reactivate: "Csoport újraaktiválva",
1384
1387
  membership_add: "Hozzáadva a csoporthoz",
1385
1388
  membership_role: "Szerepkör a csoportban módosítva",
1386
1389
  membership_remove: "Eltávolítva a csoportból",
@@ -1390,6 +1393,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1390
1393
  group_create: "Csapat létrehozva",
1391
1394
  group_rename: "Csapat átnevezve",
1392
1395
  group_delete: "Csapat törölve",
1396
+ group_deactivate: "Csapat inaktiválva",
1397
+ group_reactivate: "Csapat újraaktiválva",
1393
1398
  membership_add: "Hozzáadva a csapathoz",
1394
1399
  membership_role: "Szerepkör a csapatban módosítva",
1395
1400
  membership_remove: "Eltávolítva a csapatból",
@@ -1399,6 +1404,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1399
1404
  group_create: "Cég létrehozva",
1400
1405
  group_rename: "Cég átnevezve",
1401
1406
  group_delete: "Cég törölve",
1407
+ group_deactivate: "Cég inaktiválva",
1408
+ group_reactivate: "Cég újraaktiválva",
1402
1409
  membership_add: "Hozzáadva a céghez",
1403
1410
  membership_role: "Szerepkör a cégben módosítva",
1404
1411
  membership_remove: "Eltávolítva a cégből",
@@ -1408,6 +1415,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1408
1415
  group_create: "Költségvetés létrehozva",
1409
1416
  group_rename: "Költségvetés átnevezve",
1410
1417
  group_delete: "Költségvetés törölve",
1418
+ group_deactivate: "Költségvetés inaktiválva",
1419
+ group_reactivate: "Költségvetés újraaktiválva",
1411
1420
  membership_add: "Hozzáadva a költségvetéshez",
1412
1421
  membership_role: "Szerepkör a költségvetésben módosítva",
1413
1422
  membership_remove: "Eltávolítva a költségvetésből",
@@ -1837,9 +1846,21 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1837
1846
  },
1838
1847
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Ön", as
1839
1848
  // the rest of the catalogue.
1849
+ mailTestCard: {
1850
+ title: "Tesztlevél",
1851
+ description: "Egy levelet küld a saját címére, azon a csatornán, amelyen ez a telepítés minden levelét küldi.",
1852
+ transport: (backend) => `Küldés módja: ${backend}`,
1853
+ action: "Tesztlevél küldése magamnak",
1854
+ sent: (address) => `Elküldve ide: ${address}. Nézze meg a beérkező leveleket.`,
1855
+ console: "A konzolos küldés csak naplózza a leveleket; semmi sem lett elküldve.",
1856
+ failed: "A küldés nem sikerült. A szolgáltató válasza a szervernaplóban található.",
1857
+ rateLimited,
1858
+ keywords: "tesztlevél teszt e-mail levél küldés smtp resend konzol kézbesítés",
1859
+ },
1840
1860
  adminArea: {
1841
1861
  title: "Adminisztráció",
1842
1862
  waiting: (count) => `${n(count)} várakozik`,
1863
+ waitingLabel: (label, count) => `${label}, ${n(count)} várakozik`,
1843
1864
  forbiddenTitle: "Csak adminisztrátoroknak",
1844
1865
  forbiddenBody: (appName) =>
1845
1866
  `Itt kezelik az adminisztrátorok a(z) ${appName} alkalmazást. Az Ön fiókja nem nyithatja meg ezt az oldalt.`,
@@ -1906,6 +1927,27 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1906
1927
  billingOff: "A számlázás helyben ki van kapcsolva: a csomagok semmin nem változtatnak",
1907
1928
  },
1908
1929
  },
1930
+ // 0.45.0 — docs/local-first-harmonization.md §1.6 (`@eifi1/ui-kit/offline`): keksdose's
1931
+ // reviewed words (nav.logout_queued_*, pwa.*) where it has the language.
1932
+ signOutPending: {
1933
+ title: "Néhány módosítás még nincs elmentve a szerveren",
1934
+ lost: (count) =>
1935
+ `${n(count)} módosítás még szinkronizálásra vár. Kijelentkezéskor törlődik az eszközön lévő másolat, pedig ez az egyetlen.`,
1936
+ kept: (count) =>
1937
+ `${n(count)} módosítás még szinkronizálásra vár. Ezen az eszközön megmarad az Ön számára, és a következő itteni bejelentkezésekor szinkronizálódik.`,
1938
+ syncNow: "Szinkronizálás most",
1939
+ signOutAnyway: "Kijelentkezés mindenképp",
1940
+ discardWaiting: "Várakozó módosítások elvetése",
1941
+ cancel: "Mégse",
1942
+ },
1943
+ pwaUpdate: {
1944
+ updateAvailable: "Új verzió érhető el.",
1945
+ reload: "Újratöltés",
1946
+ },
1947
+ sharedComputer: {
1948
+ label: "Ez egy közös használatú számítógép",
1949
+ hint: "Az adatairól nem marad másolat ezen az eszközön. Az oldalak így csak kapcsolattal nyílnak meg.",
1950
+ },
1909
1951
  };
1910
1952
  }
1911
1953