@eifi1/ui-kit 0.44.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 (124) hide show
  1. package/README.md +10 -6
  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/components/data-table-mobile.js +17 -2
  17. package/dist/components/data-table-mobile.js.map +1 -1
  18. package/dist/components/data-table-shared.d.ts +2 -0
  19. package/dist/components/data-table-shared.js +8 -0
  20. package/dist/components/data-table-shared.js.map +1 -1
  21. package/dist/components/data-table.js +1 -6
  22. package/dist/components/data-table.js.map +1 -1
  23. package/dist/eslint/index.js +1 -1
  24. package/dist/i18n/defaults.js +9 -1
  25. package/dist/i18n/defaults.js.map +1 -1
  26. package/dist/i18n/german.js +40 -0
  27. package/dist/i18n/german.js.map +1 -1
  28. package/dist/i18n/kit-labels.d.ts +13 -0
  29. package/dist/i18n/kit-labels.js.map +1 -1
  30. package/dist/i18n/locales/en.js +57 -1
  31. package/dist/i18n/locales/en.js.map +3 -3
  32. package/dist/i18n/locales/es.js +40 -0
  33. package/dist/i18n/locales/es.js.map +1 -1
  34. package/dist/i18n/locales/fr.js +40 -0
  35. package/dist/i18n/locales/fr.js.map +1 -1
  36. package/dist/i18n/locales/hu.js +40 -0
  37. package/dist/i18n/locales/hu.js.map +1 -1
  38. package/dist/i18n/locales/it.js +40 -0
  39. package/dist/i18n/locales/it.js.map +1 -1
  40. package/dist/i18n/locales/zh.js +40 -0
  41. package/dist/i18n/locales/zh.js.map +1 -1
  42. package/dist/i18n/review.js +13 -1
  43. package/dist/i18n/review.js.map +1 -1
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.js +5 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/lib/format.d.ts +2 -1
  48. package/dist/lib/format.js +6 -1
  49. package/dist/lib/format.js.map +1 -1
  50. package/dist/offline/pwa-update-prompt-labels.d.ts +2 -0
  51. package/dist/offline/pwa-update-prompt-labels.js +9 -0
  52. package/dist/offline/pwa-update-prompt-labels.js.map +1 -0
  53. package/dist/offline/pwa-update-prompt.d.ts +32 -0
  54. package/dist/offline/pwa-update-prompt.js +36 -0
  55. package/dist/offline/pwa-update-prompt.js.map +1 -0
  56. package/dist/offline/query-persistence.d.ts +100 -0
  57. package/dist/offline/query-persistence.js +179 -0
  58. package/dist/offline/query-persistence.js.map +1 -0
  59. package/dist/offline/shared-computer-labels.d.ts +2 -0
  60. package/dist/offline/shared-computer-labels.js +9 -0
  61. package/dist/offline/shared-computer-labels.js.map +1 -0
  62. package/dist/offline/shared-computer.d.ts +44 -0
  63. package/dist/offline/shared-computer.js +51 -0
  64. package/dist/offline/shared-computer.js.map +1 -0
  65. package/dist/offline/sign-out-pending-dialog-labels.d.ts +2 -0
  66. package/dist/offline/sign-out-pending-dialog-labels.js +14 -0
  67. package/dist/offline/sign-out-pending-dialog-labels.js.map +1 -0
  68. package/dist/offline/sign-out-pending-dialog.d.ts +58 -0
  69. package/dist/offline/sign-out-pending-dialog.js +66 -0
  70. package/dist/offline/sign-out-pending-dialog.js.map +1 -0
  71. package/dist/offline.d.ts +8 -0
  72. package/dist/offline.js +24 -0
  73. package/dist/offline.js.map +1 -0
  74. package/dist/shell/admin-menu.d.ts +24 -0
  75. package/dist/shell/admin-menu.js +12 -3
  76. package/dist/shell/admin-menu.js.map +1 -1
  77. package/dist/shell.d.ts +2 -0
  78. package/dist/shell.js +2 -0
  79. package/dist/shell.js.map +1 -1
  80. package/dist/tour/tour-catalogue.d.ts +7 -1
  81. package/dist/tour/tour-catalogue.js.map +1 -1
  82. package/dist/tour/tour.js +4 -2
  83. package/dist/tour/tour.js.map +1 -1
  84. package/dist/tour/use-tour-progress.d.ts +99 -1
  85. package/dist/tour/use-tour-progress.js +228 -12
  86. package/dist/tour/use-tour-progress.js.map +1 -1
  87. package/dist/tour.d.ts +2 -0
  88. package/dist/tour.js +2 -0
  89. package/dist/tour.js.map +1 -1
  90. package/package.json +25 -1
  91. package/src/admin/admin-action-log-labels.ts +9 -0
  92. package/src/admin/admin-action-log.tsx +41 -5
  93. package/src/admin/admin-area-labels.ts +7 -0
  94. package/src/admin/mail-test-card-labels.ts +19 -0
  95. package/src/admin/mail-test-card.tsx +186 -0
  96. package/src/components/data-table-mobile.tsx +26 -3
  97. package/src/components/data-table-shared.tsx +18 -1
  98. package/src/components/data-table.tsx +1 -15
  99. package/src/i18n/defaults.ts +8 -0
  100. package/src/i18n/german.ts +46 -0
  101. package/src/i18n/kit-labels.tsx +13 -0
  102. package/src/i18n/locales/en.ts +12 -0
  103. package/src/i18n/locales/es.ts +46 -0
  104. package/src/i18n/locales/fr.ts +46 -0
  105. package/src/i18n/locales/hu.ts +42 -0
  106. package/src/i18n/locales/it.ts +46 -0
  107. package/src/i18n/locales/zh.ts +40 -0
  108. package/src/i18n/review.ts +12 -0
  109. package/src/index.ts +3 -0
  110. package/src/lib/format.ts +13 -2
  111. package/src/offline/pwa-update-prompt-labels.ts +9 -0
  112. package/src/offline/pwa-update-prompt.tsx +72 -0
  113. package/src/offline/query-persistence.ts +323 -0
  114. package/src/offline/shared-computer-labels.ts +8 -0
  115. package/src/offline/shared-computer.tsx +87 -0
  116. package/src/offline/sign-out-pending-dialog-labels.ts +21 -0
  117. package/src/offline/sign-out-pending-dialog.tsx +135 -0
  118. package/src/offline.ts +26 -0
  119. package/src/shell/admin-menu.tsx +40 -3
  120. package/src/shell.ts +4 -0
  121. package/src/tour/tour-catalogue.tsx +7 -1
  122. package/src/tour/tour.tsx +5 -2
  123. package/src/tour/use-tour-progress.ts +378 -12
  124. package/src/tour.ts +3 -0
@@ -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
 
@@ -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
 
@@ -1385,11 +1385,14 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1385
1385
  // ("Azienda creata"; Kurvenschmiede's "il team" is masculine, never "squadra"), a
1386
1386
  // member's with "account", as `actions` do. `budget` is keksdose's; `company` keeps
1387
1387
  // `actions.membership_remove`'s string.
1388
+ renamedFromTo: (from, to) => `da ${from} a ${to}`,
1388
1389
  groupActions: {
1389
1390
  group: {
1390
1391
  group_create: "Gruppo creato",
1391
1392
  group_rename: "Gruppo rinominato",
1392
1393
  group_delete: "Gruppo eliminato",
1394
+ group_deactivate: "Gruppo disattivato",
1395
+ group_reactivate: "Gruppo riattivato",
1393
1396
  membership_add: "Aggiunto al gruppo",
1394
1397
  membership_role: "Ruolo nel gruppo cambiato",
1395
1398
  membership_remove: "Rimosso dal gruppo",
@@ -1399,6 +1402,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1399
1402
  group_create: "Team creato",
1400
1403
  group_rename: "Team rinominato",
1401
1404
  group_delete: "Team eliminato",
1405
+ group_deactivate: "Team disattivato",
1406
+ group_reactivate: "Team riattivato",
1402
1407
  membership_add: "Aggiunto al team",
1403
1408
  membership_role: "Ruolo nel team cambiato",
1404
1409
  membership_remove: "Rimosso dal team",
@@ -1408,6 +1413,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1408
1413
  group_create: "Azienda creata",
1409
1414
  group_rename: "Azienda rinominata",
1410
1415
  group_delete: "Azienda eliminata",
1416
+ group_deactivate: "Azienda disattivata",
1417
+ group_reactivate: "Azienda riattivata",
1411
1418
  membership_add: "Aggiunto all’azienda",
1412
1419
  membership_role: "Ruolo nell’azienda cambiato",
1413
1420
  membership_remove: "Rimosso dall’azienda",
@@ -1417,6 +1424,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1417
1424
  group_create: "Budget creato",
1418
1425
  group_rename: "Budget rinominato",
1419
1426
  group_delete: "Budget eliminato",
1427
+ group_deactivate: "Budget disattivato",
1428
+ group_reactivate: "Budget riattivato",
1420
1429
  membership_add: "Aggiunto al budget",
1421
1430
  membership_role: "Ruolo nel budget cambiato",
1422
1431
  membership_remove: "Rimosso dal budget",
@@ -1850,9 +1859,21 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1850
1859
  },
1851
1860
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Lei", as
1852
1861
  // the rest of the catalogue.
1862
+ mailTestCard: {
1863
+ title: "Email di prova",
1864
+ description: "Invia un’email al Suo indirizzo, tramite il trasporto con cui questa installazione invia tutte le sue email.",
1865
+ transport: (backend) => `Trasporto: ${backend}`,
1866
+ action: "Inviami un’email di prova",
1867
+ sent: (address) => `Inviata a ${address}. Controlli la posta in arrivo.`,
1868
+ console: "Il trasporto console registra solo le email; non è stato inviato nulla.",
1869
+ failed: "Invio non riuscito. Il log del server contiene la risposta del provider.",
1870
+ rateLimited,
1871
+ keywords: "email prova test posta trasporto smtp resend console inviare consegna",
1872
+ },
1853
1873
  adminArea: {
1854
1874
  title: "Amministrazione",
1855
1875
  waiting: (count) => `${n(count)} in attesa`,
1876
+ waitingLabel: (label, count) => `${label}, ${n(count)} in attesa`,
1856
1877
  forbiddenTitle: "Solo per gli amministratori",
1857
1878
  forbiddenBody: (appName) =>
1858
1879
  `Qui gli amministratori gestiscono ${appName}. Il Suo account non può aprire questa pagina.`,
@@ -1921,6 +1942,31 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1921
1942
  billingOff: "La fatturazione è disattivata in locale: i piani non cambiano nulla",
1922
1943
  },
1923
1944
  },
1945
+ // 0.45.0 — docs/local-first-harmonization.md §1.6 (`@eifi1/ui-kit/offline`): keksdose's
1946
+ // reviewed words (nav.logout_queued_*, pwa.*) where it has the language.
1947
+ signOutPending: {
1948
+ title: "Alcune modifiche non sono ancora state salvate sul server",
1949
+ lost: (count) =>
1950
+ count === 1
1951
+ ? `${n(count)} modifica è ancora in attesa di sincronizzazione. Uscendo, la copia su questo dispositivo viene cancellata, ed è l’unica.`
1952
+ : `${n(count)} modifiche sono ancora in attesa di sincronizzazione. Uscendo, la copia su questo dispositivo viene cancellata, ed è l’unica.`,
1953
+ kept: (count) =>
1954
+ count === 1
1955
+ ? `${n(count)} modifica è ancora in attesa di sincronizzazione. Resta per Lei su questo dispositivo e verrà sincronizzata al Suo prossimo accesso qui.`
1956
+ : `${n(count)} modifiche sono ancora in attesa di sincronizzazione. Restano per Lei su questo dispositivo e verranno sincronizzate al Suo prossimo accesso qui.`,
1957
+ syncNow: "Sincronizza ora",
1958
+ signOutAnyway: "Esci comunque",
1959
+ discardWaiting: "Scarta le modifiche in attesa",
1960
+ cancel: "Annulla",
1961
+ },
1962
+ pwaUpdate: {
1963
+ updateAvailable: "È disponibile una nuova versione.",
1964
+ reload: "Ricarica",
1965
+ },
1966
+ sharedComputer: {
1967
+ label: "Questo è un computer condiviso",
1968
+ hint: "Su questo dispositivo non resta alcuna copia dei Suoi dati. Le pagine si aprono quindi solo con una connessione.",
1969
+ },
1924
1970
  };
1925
1971
  }
1926
1972
 
@@ -1321,11 +1321,14 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1321
1321
  },
1322
1322
  // 0.34 (§11.5): whole phrases per noun. `team` is Kurvenschmiede's "团队"; `company`
1323
1323
  // keeps `actions.membership_remove`'s string.
1324
+ renamedFromTo: (from, to) => `从 ${from} 改为 ${to}`,
1324
1325
  groupActions: {
1325
1326
  group: {
1326
1327
  group_create: "群组已创建",
1327
1328
  group_rename: "群组已重命名",
1328
1329
  group_delete: "群组已删除",
1330
+ group_deactivate: "群组已停用",
1331
+ group_reactivate: "群组已重新激活",
1329
1332
  membership_add: "已添加到群组",
1330
1333
  membership_role: "群组中的角色已更改",
1331
1334
  membership_remove: "已从群组移除",
@@ -1335,6 +1338,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1335
1338
  group_create: "团队已创建",
1336
1339
  group_rename: "团队已重命名",
1337
1340
  group_delete: "团队已删除",
1341
+ group_deactivate: "团队已停用",
1342
+ group_reactivate: "团队已重新激活",
1338
1343
  membership_add: "已添加到团队",
1339
1344
  membership_role: "团队中的角色已更改",
1340
1345
  membership_remove: "已从团队移除",
@@ -1344,6 +1349,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1344
1349
  group_create: "公司已创建",
1345
1350
  group_rename: "公司已重命名",
1346
1351
  group_delete: "公司已删除",
1352
+ group_deactivate: "公司已停用",
1353
+ group_reactivate: "公司已重新激活",
1347
1354
  membership_add: "已添加到公司",
1348
1355
  membership_role: "公司中的角色已更改",
1349
1356
  membership_remove: "已从公司移除",
@@ -1353,6 +1360,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1353
1360
  group_create: "预算已创建",
1354
1361
  group_rename: "预算已重命名",
1355
1362
  group_delete: "预算已删除",
1363
+ group_deactivate: "预算已停用",
1364
+ group_reactivate: "预算已重新激活",
1356
1365
  membership_add: "已添加到预算",
1357
1366
  membership_role: "预算中的角色已更改",
1358
1367
  membership_remove: "已从预算移除",
@@ -1754,9 +1763,21 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1754
1763
  moreTitle: "更多页面",
1755
1764
  },
1756
1765
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words.
1766
+ mailTestCard: {
1767
+ title: "测试邮件",
1768
+ description: "通过本安装发送所有邮件所用的传输方式,向您自己的地址发送一封邮件。",
1769
+ transport: (backend) => `传输方式:${backend}`,
1770
+ action: "给我发送测试邮件",
1771
+ sent: (address) => `已发送至 ${address}。请查看收件箱。`,
1772
+ console: "控制台传输只记录邮件,未发送任何邮件。",
1773
+ failed: "无法发送。服务器日志中有服务商的回复。",
1774
+ rateLimited,
1775
+ keywords: "测试邮件 邮件 电子邮件 传输 smtp resend 控制台 发送 投递",
1776
+ },
1757
1777
  adminArea: {
1758
1778
  title: "管理",
1759
1779
  waiting: (count) => `${n(count)} 项待处理`,
1780
+ waitingLabel: (label, count) => `${label},${n(count)} 项待处理`,
1760
1781
  forbiddenTitle: "仅限管理员",
1761
1782
  forbiddenBody: (appName) => `管理员在这里管理 ${appName}。您的账户无法打开此页面。`,
1762
1783
  forbiddenHome: "前往首页",
@@ -1822,6 +1843,25 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1822
1843
  billingOff: "本地已关闭计费:切换套餐不会改变任何内容",
1823
1844
  },
1824
1845
  },
1846
+ // 0.45.0 — docs/local-first-harmonization.md §1.6 (`@eifi1/ui-kit/offline`): keksdose's
1847
+ // reviewed words (nav.logout_queued_*, pwa.*) where it has the language.
1848
+ signOutPending: {
1849
+ title: "部分修改尚未保存到服务器",
1850
+ lost: (count) => `${n(count)} 项修改仍在等待同步。退出登录会清除此设备上的副本,而这是唯一的副本。`,
1851
+ kept: (count) => `${n(count)} 项修改仍在等待同步。它们会为您保留在此设备上,并在您下次在此登录时同步。`,
1852
+ syncNow: "立即同步",
1853
+ signOutAnyway: "仍然退出登录",
1854
+ discardWaiting: "放弃等待中的修改",
1855
+ cancel: "取消",
1856
+ },
1857
+ pwaUpdate: {
1858
+ updateAvailable: "有新版本可用。",
1859
+ reload: "重新加载",
1860
+ },
1861
+ sharedComputer: {
1862
+ label: "这是一台共用电脑",
1863
+ hint: "此设备上不会保留您的数据副本。页面将只能在联网时打开。",
1864
+ },
1825
1865
  };
1826
1866
  }
1827
1867
 
@@ -353,6 +353,8 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
353
353
  "adminActionLog.by": [["{{actor}}"]],
354
354
  "adminActionLog.filteredTo": [["{{target}}"]],
355
355
  "adminActionLog.filterBy": [["{{target}}"]],
356
+ // 0.45.0 (admin §9.4): a company rename that logged both names.
357
+ "adminActionLog.renamedFromTo": [["{{from}}", "{{to}}"]],
356
358
  "transferOwnership.title": [["{{name}}"]],
357
359
  "transferOwnership.unavailable": [["{{account}}", "{{reason}}"]],
358
360
  "invitations.sent": [["{{date}}"]],
@@ -406,6 +408,12 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
406
408
  // 0.42.0 (docs/admin-harmonization.md §3.1): the menu row's count, the 403 page's app,
407
409
  // the users tile's new accounts.
408
410
  "adminArea.waiting": one,
411
+ // 0.45.0 (admin §9.3.4): an IconButton's name with its waiting count.
412
+ "adminArea.waitingLabel": [["{{label}}", 1], ["{{label}}", 3]],
413
+ // 0.45.0 (admin §9.2.4): the test mail's transport, masked address and limiter.
414
+ "mailTestCard.transport": [["{{backend}}"]],
415
+ "mailTestCard.sent": [["{{address}}"]],
416
+ "mailTestCard.rateLimited": [[], [30], [300]],
409
417
  "adminArea.forbiddenBody": [["{{app}}"]],
410
418
  "adminArea.tiles.usersNew": one,
411
419
  // 0.42.0 (docs/top-bar-harmonization.md §3.1): the sync indicator's counts, its time, the
@@ -431,4 +439,8 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
431
439
  "tourCatalogue.steps": one,
432
440
  "tourCatalogue.duration": one,
433
441
  "tourCatalogue.completedOn": [["{{date}}"]],
442
+ // 0.45.0 (docs/local-first-harmonization.md §1.6): the sign-out question's count, in
443
+ // both word sets.
444
+ "signOutPending.lost": one,
445
+ "signOutPending.kept": one,
434
446
  };
package/src/index.ts CHANGED
@@ -811,6 +811,9 @@ export type {
811
811
  } from "./admin/admin-overview";
812
812
  export { AdminToolLinks } from "./admin/admin-tool-links";
813
813
  export type { AdminToolLink, AdminToolLinksProps } from "./admin/admin-tool-links";
814
+ // 0.45.0 (docs/admin-harmonization.md §9.2.4): "Send a test mail to me".
815
+ export { MailTestCard, DEFAULT_MAIL_TEST_CARD_LABELS, MAIL_TEST_ANCHOR, maskMailAddress } from "./admin/mail-test-card";
816
+ export type { MailTestCardProps, MailTestCardLabels, MailTestAnswer } from "./admin/mail-test-card";
814
817
  // 0.33.0: the operator's plan parts (docs/billing-harmonization.md §14.12).
815
818
  export {
816
819
  planColumn,
package/src/lib/format.ts CHANGED
@@ -182,10 +182,14 @@ const DATE_STYLES: Record<FormatDateStyle, Intl.DateTimeFormatOptions> = {
182
182
  };
183
183
 
184
184
  export interface FormatDateOptions extends BaseOptions {
185
- /** An IANA zone ("Europe/Zurich") for a timestamp. Left out: the device's. */
185
+ /** An IANA zone ("Europe/Zurich") for a timestamp. Left out: the device's. A bare
186
+ * "YYYY-MM-DD" ignores it: a calendar day is the day it names in every zone. */
186
187
  timeZone?: string;
187
188
  }
188
189
 
190
+ /** A bare ISO calendar day, `"YYYY-MM-DD"`. */
191
+ const BARE_ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
192
+
189
193
  /** What {@link formatDate} and {@link formatRelativeTime} accept. */
190
194
  export type DateInput = string | number | Date | null | undefined;
191
195
 
@@ -197,7 +201,7 @@ export type DateInput = string | number | Date | null | undefined;
197
201
  */
198
202
  export function toDate(value: DateInput): Date | null {
199
203
  if (value === null || value === undefined || value === "") return null;
200
- if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) return parseIsoDate(value);
204
+ if (typeof value === "string" && BARE_ISO_DAY.test(value)) return parseIsoDate(value);
201
205
  const d = value instanceof Date ? value : new Date(value);
202
206
  return Number.isNaN(d.getTime()) ? null : d;
203
207
  }
@@ -229,6 +233,13 @@ export function formatDate(
229
233
  const d = toDate(value);
230
234
  if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;
231
235
  const base = typeof style === "string" ? DATE_STYLES[style] : style;
236
+ // A bare "YYYY-MM-DD" is a calendar day, which has no zone: it is the day it names
237
+ // wherever it is read, so a `timeZone` (in `options` or the style) never moves it — the
238
+ // device's local midnight shown in a zone west of it was the day before (round 0.45, N9).
239
+ if (typeof value === "string" && BARE_ISO_DAY.test(value)) {
240
+ const day = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
241
+ return dateFormatter(options.locale, { ...base, timeZone: "UTC" }).format(day);
242
+ }
232
243
  return dateFormatter(options.locale, options.timeZone ? { ...base, timeZone: options.timeZone } : base).format(d);
233
244
  }
234
245
 
@@ -0,0 +1,9 @@
1
+ // The English words of ./pwa-update-prompt, in a module with no component code (0.34, KS U8).
2
+ // keksdose's reviewed words (pwa.updateAvailable, pwa.reload) are the canon.
3
+
4
+ import type { PwaUpdatePromptLabels } from "./pwa-update-prompt";
5
+
6
+ export const DEFAULT_PWA_UPDATE_PROMPT_LABELS: PwaUpdatePromptLabels = {
7
+ updateAvailable: "A new version is available.",
8
+ reload: "Reload",
9
+ };
@@ -0,0 +1,72 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
3
+ import { toast } from "../components/toast";
4
+ import { DEFAULT_PWA_UPDATE_PROMPT_LABELS } from "./pwa-update-prompt-labels";
5
+
6
+ /**
7
+ * "A new version is available. — Reload": keksdose's service-worker toast
8
+ * (app/pwa-update-prompt.tsx) as a kit component (docs/local-first-harmonization.md §1.5
9
+ * D8). The app keeps `useRegisterSW` (vite-plugin-pwa, `registerType: "prompt"`) and hands
10
+ * its state in: `needRefresh` from it, `onReload` = `updateServiceWorker(true)`,
11
+ * `onDismiss` = `setNeedRefresh(false)`.
12
+ *
13
+ * `hold` defers the prompt while a reload would hurt — a handover being signed, a tour
14
+ * running. Nothing shows while it holds; the toast appears once it drops. A hold that
15
+ * starts while the toast is up takes it down again until it drops.
16
+ *
17
+ * Renders nothing itself: the toast needs a mounted `<Toaster>`.
18
+ */
19
+ export interface PwaUpdatePromptLabels {
20
+ updateAvailable: string;
21
+ reload: string;
22
+ }
23
+
24
+ // Defined in ./pwa-update-prompt-labels, which holds no component code (0.34, KS U8).
25
+ export { DEFAULT_PWA_UPDATE_PROMPT_LABELS };
26
+
27
+ export interface PwaUpdatePromptProps {
28
+ /** A new service worker is waiting. */
29
+ needRefresh: boolean;
30
+ /** Activates it and reloads — `updateServiceWorker(true)`. */
31
+ onReload: () => void;
32
+ /** The toast was closed without reloading. */
33
+ onDismiss?: () => void;
34
+ /** Wait: a handover is open, a tour runs. */
35
+ hold?: boolean;
36
+ labels?: LabelOverride<PwaUpdatePromptLabels>;
37
+ }
38
+
39
+ /** The toast's id: one prompt at a time, replaced in place. */
40
+ const PWA_UPDATE_TOAST_ID = "pwa-update";
41
+
42
+ export function PwaUpdatePrompt({ needRefresh, onReload, onDismiss, hold = false, labels: labelsProp }: PwaUpdatePromptProps) {
43
+ const labels = useKitLabels("pwaUpdate", DEFAULT_PWA_UPDATE_PROMPT_LABELS, labelsProp);
44
+ // The callbacks are read at click time, so a new function each render does not
45
+ // re-raise the toast.
46
+ const handlers = useRef({ onReload, onDismiss });
47
+ useEffect(() => {
48
+ handlers.current = { onReload, onDismiss };
49
+ });
50
+
51
+ const show = needRefresh && !hold;
52
+ useEffect(() => {
53
+ if (!show) return;
54
+ // sonner calls `onDismiss` for a programmatic dismiss too: the one this effect makes
55
+ // when a hold starts must not tell the app the person waved the update away.
56
+ let ours = false;
57
+ toast(labels.updateAvailable, {
58
+ id: PWA_UPDATE_TOAST_ID,
59
+ duration: Infinity,
60
+ action: { label: labels.reload, onClick: () => handlers.current.onReload() },
61
+ onDismiss: () => {
62
+ if (!ours) handlers.current.onDismiss?.();
63
+ },
64
+ });
65
+ return () => {
66
+ ours = true;
67
+ toast.dismiss(PWA_UPDATE_TOAST_ID);
68
+ };
69
+ }, [show, labels.updateAvailable, labels.reload]);
70
+
71
+ return null;
72
+ }