@eifi1/ui-kit 0.44.0 → 0.46.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.
- package/README.md +29 -14
- package/dist/admin/admin-action-log-labels.js +9 -0
- package/dist/admin/admin-action-log-labels.js.map +1 -1
- package/dist/admin/admin-action-log.d.ts +18 -1
- package/dist/admin/admin-action-log.js +25 -16
- package/dist/admin/admin-action-log.js.map +1 -1
- package/dist/admin/admin-area-labels.d.ts +15 -0
- package/dist/admin/admin-area-labels.js +6 -1
- package/dist/admin/admin-area-labels.js.map +1 -1
- package/dist/admin/admin-overview.d.ts +16 -1
- package/dist/admin/admin-overview.js +12 -1
- package/dist/admin/admin-overview.js.map +1 -1
- package/dist/admin/mail-test-card-labels.d.ts +3 -0
- package/dist/admin/mail-test-card-labels.js +17 -0
- package/dist/admin/mail-test-card-labels.js.map +1 -0
- package/dist/admin/mail-test-card.d.ts +75 -0
- package/dist/admin/mail-test-card.js +82 -0
- package/dist/admin/mail-test-card.js.map +1 -0
- package/dist/components/appearance-labels.d.ts +10 -0
- package/dist/components/appearance-labels.js +6 -1
- package/dist/components/appearance-labels.js.map +1 -1
- package/dist/components/data-table-column-settings.js +2 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-head.js +2 -2
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-mobile.js +17 -2
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +18 -2
- package/dist/components/data-table-row-actions.js +86 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +2 -0
- package/dist/components/data-table-shared.js +17 -2
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +43 -2
- package/dist/components/data-table.js +10 -10
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-format-setting.d.ts +29 -0
- package/dist/components/date-format-setting.js +36 -0
- package/dist/components/date-format-setting.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +8 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/tabs.d.ts +3 -2
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -0
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-labels.d.ts +5 -0
- package/dist/components/translation-review-labels.js +2 -0
- package/dist/components/translation-review-labels.js.map +1 -1
- package/dist/components/translation-review.d.ts +18 -8
- package/dist/components/translation-review.js +64 -7
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/write-lock-labels.js +2 -1
- package/dist/components/write-lock-labels.js.map +1 -1
- package/dist/components/write-lock.d.ts +33 -3
- package/dist/components/write-lock.js +9 -0
- package/dist/components/write-lock.js.map +1 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +4 -2
- package/dist/feedback/feedback-row-detail.js +17 -26
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +18 -0
- package/dist/feedback/feedback-swipe.js +10 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/i18n/defaults.js +9 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +81 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +25 -1
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +97 -7
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +81 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +81 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +81 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +81 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +81 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +16 -2
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/date-format.d.ts +81 -0
- package/dist/lib/date-format.js +56 -0
- package/dist/lib/date-format.js.map +1 -0
- package/dist/lib/format.d.ts +5 -1
- package/dist/lib/format.js +11 -5
- package/dist/lib/format.js.map +1 -1
- package/dist/notifications/device-zone-report.d.ts +27 -0
- package/dist/notifications/device-zone-report.js +24 -0
- package/dist/notifications/device-zone-report.js.map +1 -0
- package/dist/notifications/feedback-notification-types-labels.d.ts +7 -1
- package/dist/notifications/feedback-notification-types-labels.js +4 -1
- package/dist/notifications/feedback-notification-types-labels.js.map +1 -1
- package/dist/notifications/feedback-notification-types.d.ts +11 -5
- package/dist/notifications/feedback-notification-types.js +2 -1
- package/dist/notifications/feedback-notification-types.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +33 -0
- package/dist/notifications/notification-bell.js +74 -0
- package/dist/notifications/notification-bell.js.map +1 -0
- package/dist/notifications/notification-inbox.d.ts +77 -0
- package/dist/notifications/notification-inbox.js +206 -0
- package/dist/notifications/notification-inbox.js.map +1 -0
- package/dist/notifications/notification-types-setting.d.ts +6 -4
- package/dist/notifications/notification-types-setting.js +103 -45
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notification-types.d.ts +19 -7
- package/dist/notifications/notifications-labels.d.ts +41 -7
- package/dist/notifications/notifications-labels.js +18 -1
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +20 -3
- package/dist/notifications/push-devices-setting.js +93 -33
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/push-worker.d.ts +108 -0
- package/dist/notifications/push-worker.js +62 -0
- package/dist/notifications/push-worker.js.map +1 -0
- package/dist/notifications/quiet-hours-setting.d.ts +29 -17
- package/dist/notifications/quiet-hours-setting.js +9 -41
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/time-zone-select.d.ts +5 -2
- package/dist/notifications/time-zone-select.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +4 -17
- package/dist/notifications/use-push-device.js +7 -15
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/local-first.d.ts +33 -0
- package/dist/offline/local-first.js +39 -0
- package/dist/offline/local-first.js.map +1 -0
- package/dist/offline/pwa-update-prompt-labels.d.ts +2 -0
- package/dist/offline/pwa-update-prompt-labels.js +9 -0
- package/dist/offline/pwa-update-prompt-labels.js.map +1 -0
- package/dist/offline/pwa-update-prompt.d.ts +32 -0
- package/dist/offline/pwa-update-prompt.js +36 -0
- package/dist/offline/pwa-update-prompt.js.map +1 -0
- package/dist/offline/query-persistence.d.ts +125 -0
- package/dist/offline/query-persistence.js +183 -0
- package/dist/offline/query-persistence.js.map +1 -0
- package/dist/offline/service-worker-updates.d.ts +46 -0
- package/dist/offline/service-worker-updates.js +39 -0
- package/dist/offline/service-worker-updates.js.map +1 -0
- package/dist/offline/shared-computer-labels.d.ts +2 -0
- package/dist/offline/shared-computer-labels.js +9 -0
- package/dist/offline/shared-computer-labels.js.map +1 -0
- package/dist/offline/shared-computer.d.ts +44 -0
- package/dist/offline/shared-computer.js +51 -0
- package/dist/offline/shared-computer.js.map +1 -0
- package/dist/offline/sign-out-pending-dialog-labels.d.ts +2 -0
- package/dist/offline/sign-out-pending-dialog-labels.js +14 -0
- package/dist/offline/sign-out-pending-dialog-labels.js.map +1 -0
- package/dist/offline/sign-out-pending-dialog.d.ts +58 -0
- package/dist/offline/sign-out-pending-dialog.js +66 -0
- package/dist/offline/sign-out-pending-dialog.js.map +1 -0
- package/dist/offline.d.ts +11 -0
- package/dist/offline.js +33 -0
- package/dist/offline.js.map +1 -0
- package/dist/push-worker.d.ts +2 -0
- package/dist/push-worker.js +14 -0
- package/dist/push-worker.js.map +1 -0
- package/dist/shell/account-menu-text-size.d.ts +33 -0
- package/dist/shell/account-menu-text-size.js +51 -0
- package/dist/shell/account-menu-text-size.js.map +1 -0
- package/dist/shell/admin-menu.d.ts +24 -0
- package/dist/shell/admin-menu.js +12 -3
- package/dist/shell/admin-menu.js.map +1 -1
- package/dist/shell/top-bar-actions.d.ts +6 -2
- package/dist/shell/top-bar-actions.js +1 -0
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell.d.ts +4 -0
- package/dist/shell.js +4 -0
- package/dist/shell.js.map +1 -1
- package/dist/sync/db.d.ts +76 -0
- package/dist/sync/db.js +24 -0
- package/dist/sync/db.js.map +1 -0
- package/dist/sync/engine.d.ts +124 -0
- package/dist/sync/engine.js +474 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/hooks.d.ts +30 -0
- package/dist/sync/hooks.js +67 -0
- package/dist/sync/hooks.js.map +1 -0
- package/dist/sync/testing.d.ts +28 -0
- package/dist/sync/testing.js +41 -0
- package/dist/sync/testing.js.map +1 -0
- package/dist/sync/wire.d.ts +50 -0
- package/dist/sync/wire.js +2 -0
- package/dist/sync/wire.js.map +1 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.js +22 -0
- package/dist/sync.js.map +1 -0
- package/dist/tour/tour-catalogue.d.ts +7 -1
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour-labels.js +1 -0
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +30 -0
- package/dist/tour/tour.js +54 -8
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-progress.d.ts +99 -1
- package/dist/tour/use-tour-progress.js +228 -12
- package/dist/tour/use-tour-progress.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +2 -0
- package/dist/tour.js.map +1 -1
- package/package.json +43 -1
- package/src/admin/admin-action-log-labels.ts +9 -0
- package/src/admin/admin-action-log.tsx +41 -5
- package/src/admin/admin-area-labels.ts +20 -0
- package/src/admin/admin-overview.tsx +37 -2
- package/src/admin/mail-test-card-labels.ts +19 -0
- package/src/admin/mail-test-card.tsx +190 -0
- package/src/components/appearance-labels.ts +16 -0
- package/src/components/data-table-column-settings.tsx +2 -2
- package/src/components/data-table-head.tsx +2 -2
- package/src/components/data-table-mobile.tsx +26 -3
- package/src/components/data-table-row-actions.tsx +118 -2
- package/src/components/data-table-shared.tsx +39 -3
- package/src/components/data-table.tsx +52 -21
- package/src/components/date-format-setting.tsx +64 -0
- package/src/components/swipeable-row.tsx +8 -0
- package/src/components/tabs.tsx +4 -3
- package/src/components/translation-review-editor.tsx +2 -0
- package/src/components/translation-review-labels.ts +7 -0
- package/src/components/translation-review.tsx +102 -14
- package/src/components/write-lock-labels.ts +1 -0
- package/src/components/write-lock.tsx +36 -3
- package/src/data-table.ts +1 -0
- package/src/feedback/feedback-row-detail.tsx +18 -23
- package/src/feedback/feedback-swipe.tsx +28 -1
- package/src/i18n/defaults.ts +8 -0
- package/src/i18n/german.ts +90 -4
- package/src/i18n/kit-labels.tsx +31 -1
- package/src/i18n/locales/en.ts +13 -0
- package/src/i18n/locales/es.ts +90 -4
- package/src/i18n/locales/fr.ts +90 -4
- package/src/i18n/locales/hu.ts +86 -4
- package/src/i18n/locales/it.ts +90 -4
- package/src/i18n/locales/zh.ts +84 -2
- package/src/i18n/review.ts +15 -1
- package/src/index.ts +45 -0
- package/src/lib/date-format.ts +151 -0
- package/src/lib/format.ts +23 -6
- package/src/notifications/device-zone-report.ts +54 -0
- package/src/notifications/feedback-notification-types-labels.ts +10 -1
- package/src/notifications/feedback-notification-types.ts +12 -5
- package/src/notifications/notification-bell.tsx +116 -0
- package/src/notifications/notification-inbox.tsx +304 -0
- package/src/notifications/notification-types-setting.tsx +151 -55
- package/src/notifications/notification-types.ts +20 -8
- package/src/notifications/notifications-labels.ts +69 -11
- package/src/notifications/push-devices-setting.tsx +123 -34
- package/src/notifications/push-worker.ts +160 -0
- package/src/notifications/quiet-hours-setting.tsx +41 -58
- package/src/notifications/time-zone-select.tsx +5 -2
- package/src/notifications/use-push-device.ts +20 -33
- package/src/offline/local-first.ts +76 -0
- package/src/offline/pwa-update-prompt-labels.ts +9 -0
- package/src/offline/pwa-update-prompt.tsx +72 -0
- package/src/offline/query-persistence.ts +356 -0
- package/src/offline/service-worker-updates.ts +81 -0
- package/src/offline/shared-computer-labels.ts +8 -0
- package/src/offline/shared-computer.tsx +87 -0
- package/src/offline/sign-out-pending-dialog-labels.ts +21 -0
- package/src/offline/sign-out-pending-dialog.tsx +135 -0
- package/src/offline.ts +32 -0
- package/src/push-worker.ts +25 -0
- package/src/shell/account-menu-text-size.tsx +77 -0
- package/src/shell/admin-menu.tsx +40 -3
- package/src/shell/top-bar-actions.tsx +6 -1
- package/src/shell.ts +7 -0
- package/src/sync/db.ts +103 -0
- package/src/sync/engine.ts +699 -0
- package/src/sync/hooks.ts +93 -0
- package/src/sync/testing.ts +73 -0
- package/src/sync/wire.ts +61 -0
- package/src/sync.ts +29 -0
- package/src/tour/tour-catalogue.tsx +7 -1
- package/src/tour/tour-labels.ts +1 -0
- package/src/tour/tour.tsx +105 -9
- package/src/tour/use-tour-progress.ts +378 -12
- package/src/tour.ts +3 -0
package/src/i18n/locales/zh.ts
CHANGED
|
@@ -134,6 +134,9 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
134
134
|
answeredDescription: "当您提交或评论过的反馈收到回复时。",
|
|
135
135
|
comment: "您关注的反馈中的评论",
|
|
136
136
|
commentDescription: "当有人评论您提交或评论过的反馈时。",
|
|
137
|
+
received: "新反馈和返工",
|
|
138
|
+
receivedDescription: "当有人提交反馈或退回反馈时。",
|
|
139
|
+
adminGroup: "管理员",
|
|
137
140
|
},
|
|
138
141
|
feedbackContext: {
|
|
139
142
|
user: "用户",
|
|
@@ -590,6 +593,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
590
593
|
skip: "跳过",
|
|
591
594
|
done: "完成",
|
|
592
595
|
awaitClickHint: "点击高亮的元素以继续",
|
|
596
|
+
awaitInputHint: "在高亮的字段中输入一个值以继续", // 0.46: TourStep.awaitInput (keksdose live #436).
|
|
593
597
|
step: (current, total) => `${n(current)} / ${n(total)}`,
|
|
594
598
|
},
|
|
595
599
|
// 0.43.0 — docs/tours-harmonization.md §3, §8: the tours page.
|
|
@@ -797,6 +801,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
797
801
|
},
|
|
798
802
|
writeLock: {
|
|
799
803
|
reason: "仅可查看,无法修改。",
|
|
804
|
+
offline: "需要网络连接。",
|
|
800
805
|
},
|
|
801
806
|
accountState: {
|
|
802
807
|
active: "已启用",
|
|
@@ -1125,6 +1130,9 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1125
1130
|
typesTitle: "通知我以下内容",
|
|
1126
1131
|
typesDescription: "选择要接收哪些通知,以及接收方式。",
|
|
1127
1132
|
byEmail: "通过电子邮件",
|
|
1133
|
+
channelApp: "应用内",
|
|
1134
|
+
channelPush: "推送",
|
|
1135
|
+
channelEmail: "电子邮件",
|
|
1128
1136
|
emailUnverified: "请先确认您的邮箱地址,才能收到这些邮件。",
|
|
1129
1137
|
digestCadence: "频率",
|
|
1130
1138
|
digestWeekly: "每周",
|
|
@@ -1132,8 +1140,15 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1132
1140
|
typesLoading: "正在加载通知设置…",
|
|
1133
1141
|
typesEmpty: "此账户没有可用的通知。",
|
|
1134
1142
|
quietTitle: "免打扰时段",
|
|
1135
|
-
quietDescription: (inbox) =>
|
|
1136
|
-
|
|
1143
|
+
quietDescription: (inbox, held) =>
|
|
1144
|
+
held
|
|
1145
|
+
? inbox
|
|
1146
|
+
? "此时段内推送通知会等待,并在时段结束时送达。通知会立即进入您的收件箱。"
|
|
1147
|
+
: "此时段内推送通知会等待,并在时段结束时送达。"
|
|
1148
|
+
: inbox
|
|
1149
|
+
? "此时段内不发送推送通知。通知仍会进入您的收件箱。"
|
|
1150
|
+
: "此时段内不发送推送通知。",
|
|
1151
|
+
quietZone: (zone) => `按您的时区(${zone}),随您最近使用的设备而定。`,
|
|
1137
1152
|
quietFrom: "从",
|
|
1138
1153
|
quietUntil: "到",
|
|
1139
1154
|
quietIncomplete: "请同时设置开始和结束时间。两者都设置后,免打扰时段才会生效。",
|
|
@@ -1188,6 +1203,19 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1188
1203
|
// Starts with `remove`, the button's visible text, for voice control.
|
|
1189
1204
|
removeItem: (device) => `移除:${device}`,
|
|
1190
1205
|
removePrompt: "停止向此设备发送通知?",
|
|
1206
|
+
deviceTypes: "此设备上的类型",
|
|
1207
|
+
deviceTypesDescription: "关闭某个类型,即可仅在此设备上将其静音。",
|
|
1208
|
+
deviceTypesEmpty: "没有开启推送通知的类型。",
|
|
1209
|
+
inboxTitle: "收件箱",
|
|
1210
|
+
inboxLoading: "正在加载通知…",
|
|
1211
|
+
inboxEmpty: "没有新通知。",
|
|
1212
|
+
markAllRead: "全部标为已读",
|
|
1213
|
+
clearAll: "全部清除",
|
|
1214
|
+
clearPrompt: "删除收件箱中的所有通知?",
|
|
1215
|
+
showMore: "显示更多",
|
|
1216
|
+
bellLabel: "通知",
|
|
1217
|
+
bellUnread: (count) => `${n(count)} 条未读`,
|
|
1218
|
+
allNotifications: "所有通知",
|
|
1191
1219
|
unsubscribeTitle: "电子邮件通知",
|
|
1192
1220
|
unsubscribeAbout: (type) => `您会收到有关以下内容的邮件:${type}。`,
|
|
1193
1221
|
unsubscribeGeneric: "您会收到通知邮件。",
|
|
@@ -1321,11 +1349,14 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1321
1349
|
},
|
|
1322
1350
|
// 0.34 (§11.5): whole phrases per noun. `team` is Kurvenschmiede's "团队"; `company`
|
|
1323
1351
|
// keeps `actions.membership_remove`'s string.
|
|
1352
|
+
renamedFromTo: (from, to) => `从 ${from} 改为 ${to}`,
|
|
1324
1353
|
groupActions: {
|
|
1325
1354
|
group: {
|
|
1326
1355
|
group_create: "群组已创建",
|
|
1327
1356
|
group_rename: "群组已重命名",
|
|
1328
1357
|
group_delete: "群组已删除",
|
|
1358
|
+
group_deactivate: "群组已停用",
|
|
1359
|
+
group_reactivate: "群组已重新激活",
|
|
1329
1360
|
membership_add: "已添加到群组",
|
|
1330
1361
|
membership_role: "群组中的角色已更改",
|
|
1331
1362
|
membership_remove: "已从群组移除",
|
|
@@ -1335,6 +1366,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1335
1366
|
group_create: "团队已创建",
|
|
1336
1367
|
group_rename: "团队已重命名",
|
|
1337
1368
|
group_delete: "团队已删除",
|
|
1369
|
+
group_deactivate: "团队已停用",
|
|
1370
|
+
group_reactivate: "团队已重新激活",
|
|
1338
1371
|
membership_add: "已添加到团队",
|
|
1339
1372
|
membership_role: "团队中的角色已更改",
|
|
1340
1373
|
membership_remove: "已从团队移除",
|
|
@@ -1344,6 +1377,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1344
1377
|
group_create: "公司已创建",
|
|
1345
1378
|
group_rename: "公司已重命名",
|
|
1346
1379
|
group_delete: "公司已删除",
|
|
1380
|
+
group_deactivate: "公司已停用",
|
|
1381
|
+
group_reactivate: "公司已重新激活",
|
|
1347
1382
|
membership_add: "已添加到公司",
|
|
1348
1383
|
membership_role: "公司中的角色已更改",
|
|
1349
1384
|
membership_remove: "已从公司移除",
|
|
@@ -1353,6 +1388,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1353
1388
|
group_create: "预算已创建",
|
|
1354
1389
|
group_rename: "预算已重命名",
|
|
1355
1390
|
group_delete: "预算已删除",
|
|
1391
|
+
group_deactivate: "预算已停用",
|
|
1392
|
+
group_reactivate: "预算已重新激活",
|
|
1356
1393
|
membership_add: "已添加到预算",
|
|
1357
1394
|
membership_role: "预算中的角色已更改",
|
|
1358
1395
|
membership_remove: "已从预算移除",
|
|
@@ -1487,6 +1524,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1487
1524
|
approve: "通过",
|
|
1488
1525
|
flag: "需要修改",
|
|
1489
1526
|
suggest: "建议译文",
|
|
1527
|
+
flagRow: "标记为需修改…",
|
|
1528
|
+
suggestRow: "添加译文…",
|
|
1490
1529
|
reset: "标记为未审校",
|
|
1491
1530
|
cancel: "取消",
|
|
1492
1531
|
approveSelected: "通过所选项",
|
|
@@ -1731,6 +1770,14 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1731
1770
|
contrastHelp: "增强对比度会加深浅色文字和线条,并加粗焦点框。“跟随系统”沿用此设备的设置。",
|
|
1732
1771
|
contrastModes: { system: "跟随系统", standard: "标准", more: "增强" },
|
|
1733
1772
|
saveFailed: "无法保存到您的账户。此设备上仍然生效。",
|
|
1773
|
+
// 0.46: DateFormatSetting (keksdose's words where it has them).
|
|
1774
|
+
dateFormat: "日期格式",
|
|
1775
|
+
dateFormatHelp:
|
|
1776
|
+
"整个应用中日期的书写方式。与界面语言无关,因此您可以用英文阅读应用,同时仍看到德式日期。",
|
|
1777
|
+
dateFormatAuto: "跟随语言",
|
|
1778
|
+
// 0.46: AccountMenuTextSize (keksdose live #426).
|
|
1779
|
+
textSmaller: "缩小文字",
|
|
1780
|
+
textLarger: "放大文字",
|
|
1734
1781
|
},
|
|
1735
1782
|
// 0.36.0 — docs/settings-harmonization.md §11.3: resetting this device's settings, the
|
|
1736
1783
|
// last card of "外观".
|
|
@@ -1754,9 +1801,21 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1754
1801
|
moreTitle: "更多页面",
|
|
1755
1802
|
},
|
|
1756
1803
|
// 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words.
|
|
1804
|
+
mailTestCard: {
|
|
1805
|
+
title: "测试邮件",
|
|
1806
|
+
description: "通过本安装发送所有邮件所用的传输方式,向您自己的地址发送一封邮件。",
|
|
1807
|
+
transport: (backend) => `传输方式:${backend}`,
|
|
1808
|
+
action: "给我发送测试邮件",
|
|
1809
|
+
sent: (address) => `已发送至 ${address}。请查看收件箱。`,
|
|
1810
|
+
console: "控制台传输只记录邮件,未发送任何邮件。",
|
|
1811
|
+
failed: "无法发送。服务器日志中有服务商的回复。",
|
|
1812
|
+
rateLimited,
|
|
1813
|
+
keywords: "测试邮件 邮件 电子邮件 传输 smtp resend 控制台 发送 投递",
|
|
1814
|
+
},
|
|
1757
1815
|
adminArea: {
|
|
1758
1816
|
title: "管理",
|
|
1759
1817
|
waiting: (count) => `${n(count)} 项待处理`,
|
|
1818
|
+
waitingLabel: (label, count) => `${label},${n(count)} 项待处理`,
|
|
1760
1819
|
forbiddenTitle: "仅限管理员",
|
|
1761
1820
|
forbiddenBody: (appName) => `管理员在这里管理 ${appName}。您的账户无法打开此页面。`,
|
|
1762
1821
|
forbiddenHome: "前往首页",
|
|
@@ -1774,6 +1833,10 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1774
1833
|
invitationsPending: "待接受的邀请",
|
|
1775
1834
|
users: "用户",
|
|
1776
1835
|
usersNew: (count) => `近 7 天新增 ${n(count)} 人`,
|
|
1836
|
+
awaitingVerdict: "待确认",
|
|
1837
|
+
awaitingVerdictHint: "已答复,等待您或报告人确认已解决",
|
|
1838
|
+
releasedForWork: "待处理",
|
|
1839
|
+
releasedForWorkHint: "已就绪、已要求返工或进行中",
|
|
1777
1840
|
},
|
|
1778
1841
|
overviewError: "无法加载概览。",
|
|
1779
1842
|
retry: "重试",
|
|
@@ -1822,6 +1885,25 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
1822
1885
|
billingOff: "本地已关闭计费:切换套餐不会改变任何内容",
|
|
1823
1886
|
},
|
|
1824
1887
|
},
|
|
1888
|
+
// 0.45.0 — docs/local-first-harmonization.md §1.6 (`@eifi1/ui-kit/offline`): keksdose's
|
|
1889
|
+
// reviewed words (nav.logout_queued_*, pwa.*) where it has the language.
|
|
1890
|
+
signOutPending: {
|
|
1891
|
+
title: "部分修改尚未保存到服务器",
|
|
1892
|
+
lost: (count) => `${n(count)} 项修改仍在等待同步。退出登录会清除此设备上的副本,而这是唯一的副本。`,
|
|
1893
|
+
kept: (count) => `${n(count)} 项修改仍在等待同步。它们会为您保留在此设备上,并在您下次在此登录时同步。`,
|
|
1894
|
+
syncNow: "立即同步",
|
|
1895
|
+
signOutAnyway: "仍然退出登录",
|
|
1896
|
+
discardWaiting: "放弃等待中的修改",
|
|
1897
|
+
cancel: "取消",
|
|
1898
|
+
},
|
|
1899
|
+
pwaUpdate: {
|
|
1900
|
+
updateAvailable: "有新版本可用。",
|
|
1901
|
+
reload: "重新加载",
|
|
1902
|
+
},
|
|
1903
|
+
sharedComputer: {
|
|
1904
|
+
label: "这是一台共用电脑",
|
|
1905
|
+
hint: "此设备上不会保留您的数据副本。页面将只能在联网时打开。",
|
|
1906
|
+
},
|
|
1825
1907
|
};
|
|
1826
1908
|
}
|
|
1827
1909
|
|
package/src/i18n/review.ts
CHANGED
|
@@ -330,7 +330,9 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
|
|
|
330
330
|
"sessions.lastActive": [["{{when}}"]],
|
|
331
331
|
"sessions.ip": [["{{address}}"]],
|
|
332
332
|
"sessions.revokeItem": [["{{device}}"]],
|
|
333
|
-
"notifications.quietDescription": [[false], [true]],
|
|
333
|
+
"notifications.quietDescription": [[false], [true], [false, true], [true, true]],
|
|
334
|
+
"notifications.quietZone": [["{{zone}}"]],
|
|
335
|
+
"notifications.bellUnread": one,
|
|
334
336
|
"notifications.deviceZone": [["{{zone}}"]],
|
|
335
337
|
"notifications.iosInstall": [["{{app}}"]],
|
|
336
338
|
"notifications.testSent": one,
|
|
@@ -353,6 +355,8 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
|
|
|
353
355
|
"adminActionLog.by": [["{{actor}}"]],
|
|
354
356
|
"adminActionLog.filteredTo": [["{{target}}"]],
|
|
355
357
|
"adminActionLog.filterBy": [["{{target}}"]],
|
|
358
|
+
// 0.45.0 (admin §9.4): a company rename that logged both names.
|
|
359
|
+
"adminActionLog.renamedFromTo": [["{{from}}", "{{to}}"]],
|
|
356
360
|
"transferOwnership.title": [["{{name}}"]],
|
|
357
361
|
"transferOwnership.unavailable": [["{{account}}", "{{reason}}"]],
|
|
358
362
|
"invitations.sent": [["{{date}}"]],
|
|
@@ -406,6 +410,12 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
|
|
|
406
410
|
// 0.42.0 (docs/admin-harmonization.md §3.1): the menu row's count, the 403 page's app,
|
|
407
411
|
// the users tile's new accounts.
|
|
408
412
|
"adminArea.waiting": one,
|
|
413
|
+
// 0.45.0 (admin §9.3.4): an IconButton's name with its waiting count.
|
|
414
|
+
"adminArea.waitingLabel": [["{{label}}", 1], ["{{label}}", 3]],
|
|
415
|
+
// 0.45.0 (admin §9.2.4): the test mail's transport, masked address and limiter.
|
|
416
|
+
"mailTestCard.transport": [["{{backend}}"]],
|
|
417
|
+
"mailTestCard.sent": [["{{address}}"]],
|
|
418
|
+
"mailTestCard.rateLimited": [[], [30], [300]],
|
|
409
419
|
"adminArea.forbiddenBody": [["{{app}}"]],
|
|
410
420
|
"adminArea.tiles.usersNew": one,
|
|
411
421
|
// 0.42.0 (docs/top-bar-harmonization.md §3.1): the sync indicator's counts, its time, the
|
|
@@ -431,4 +441,8 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
|
|
|
431
441
|
"tourCatalogue.steps": one,
|
|
432
442
|
"tourCatalogue.duration": one,
|
|
433
443
|
"tourCatalogue.completedOn": [["{{date}}"]],
|
|
444
|
+
// 0.45.0 (docs/local-first-harmonization.md §1.6): the sign-out question's count, in
|
|
445
|
+
// both word sets.
|
|
446
|
+
"signOutPending.lost": one,
|
|
447
|
+
"signOutPending.kept": one,
|
|
434
448
|
};
|
package/src/index.ts
CHANGED
|
@@ -408,6 +408,8 @@ export * from "./components/form-actions";
|
|
|
408
408
|
// (docs/billing-harmonization.md §12.36): the kinds, the holds, `CommitScope`,
|
|
409
409
|
// `COMMIT_EXCEPT_BILLING` and the pure `writeLockFor`.
|
|
410
410
|
export { WriteLockProvider, useWriteLock, writeLockFor, COMMIT_EXCEPT_BILLING, DEFAULT_WRITE_LOCK_LABELS } from "./components/write-lock";
|
|
411
|
+
// 0.46 (kastlan K4): the offline lock kind, its scope and its source.
|
|
412
|
+
export { OFFLINE_LOCK, COMMIT_EXCEPT_OFFLINE, useOfflineWriteLock } from "./components/write-lock";
|
|
411
413
|
export type { WriteLock, WriteLockLabels, WriteLockProviderProps, WriteLockKind, WriteLockHold, CommitScope } from "./components/write-lock";
|
|
412
414
|
// 0.18: the parts both apps' admin rosters repeat (Kurvenschmiede 6).
|
|
413
415
|
export { RoleChip, AccountStateChip, DateMark, dateColumn, DEFAULT_ACCOUNT_STATE_LABELS, ACCOUNT_STATE_TONES } from "./components/account-chips";
|
|
@@ -552,6 +554,19 @@ export type {
|
|
|
552
554
|
// actions, one of the behaviours at Large (§4, §10.7–10.9).
|
|
553
555
|
export { DEFAULT_APPEARANCE_LABELS, useAppearanceLabels } from "./components/appearance-labels";
|
|
554
556
|
export type { AppearanceLabels } from "./components/appearance-labels";
|
|
557
|
+
// 0.46 (keksdose's date format, lifted): the preference, its resolver and renderer, the
|
|
558
|
+
// provider's formatter for it, and the settings field.
|
|
559
|
+
export {
|
|
560
|
+
DATE_FORMAT_PREFERENCES,
|
|
561
|
+
isDateFormatPreference,
|
|
562
|
+
resolveDateFormat,
|
|
563
|
+
formatDateWithPreference,
|
|
564
|
+
dateFormatSample,
|
|
565
|
+
dateFormatterFor,
|
|
566
|
+
} from "./lib/date-format";
|
|
567
|
+
export type { DateFormatPreference, DateFormatOptions, ResolvedDateFormat } from "./lib/date-format";
|
|
568
|
+
export { DateFormatSetting } from "./components/date-format-setting";
|
|
569
|
+
export type { DateFormatSettingProps } from "./components/date-format-setting";
|
|
555
570
|
export { RowActions, rowActionsColumn, DEFAULT_ROW_ACTIONS_LABELS } from "./components/row-actions";
|
|
556
571
|
export type {
|
|
557
572
|
RowAction,
|
|
@@ -808,9 +823,16 @@ export type {
|
|
|
808
823
|
AdminExtraTile,
|
|
809
824
|
AdminOverviewProps,
|
|
810
825
|
AttentionTileProps,
|
|
826
|
+
// 0.46 (Kurvenschmiede 1): the feedback tiles with the kit's words.
|
|
827
|
+
AdminFeedbackTile,
|
|
828
|
+
AdminFeedbackTileKey,
|
|
829
|
+
AdminOwnTile,
|
|
811
830
|
} from "./admin/admin-overview";
|
|
812
831
|
export { AdminToolLinks } from "./admin/admin-tool-links";
|
|
813
832
|
export type { AdminToolLink, AdminToolLinksProps } from "./admin/admin-tool-links";
|
|
833
|
+
// 0.45.0 (docs/admin-harmonization.md §9.2.4): "Send a test mail to me".
|
|
834
|
+
export { MailTestCard, DEFAULT_MAIL_TEST_CARD_LABELS, MAIL_TEST_ANCHOR, maskMailAddress } from "./admin/mail-test-card";
|
|
835
|
+
export type { MailTestCardProps, MailTestCardLabels, MailTestAnswer } from "./admin/mail-test-card";
|
|
814
836
|
// 0.33.0: the operator's plan parts (docs/billing-harmonization.md §14.12).
|
|
815
837
|
export {
|
|
816
838
|
planColumn,
|
|
@@ -943,6 +965,7 @@ export type {
|
|
|
943
965
|
NotificationTypeGroup,
|
|
944
966
|
NotificationSettingsPatch,
|
|
945
967
|
PushDeviceItem,
|
|
968
|
+
NotificationChannel,
|
|
946
969
|
} from "./notifications/notification-types";
|
|
947
970
|
export { NotificationTypesSetting } from "./notifications/notification-types-setting";
|
|
948
971
|
export type { NotificationTypesSettingProps } from "./notifications/notification-types-setting";
|
|
@@ -950,6 +973,14 @@ export { QuietHoursSetting } from "./notifications/quiet-hours-setting";
|
|
|
950
973
|
export type { QuietHoursSettingProps } from "./notifications/quiet-hours-setting";
|
|
951
974
|
export { TimeZoneSelect, timeZoneOptions, deviceTimeZone } from "./notifications/time-zone-select";
|
|
952
975
|
export type { TimeZoneSelectProps } from "./notifications/time-zone-select";
|
|
976
|
+
// 0.46 (docs/notifications-harmonization.md §10.1, §10.3): the inbox list, the bell and
|
|
977
|
+
// the device-zone report (the account's zone is automatic only, amended 2026-10-20).
|
|
978
|
+
export { NotificationInbox, INBOX_BADGE_INTERVAL } from "./notifications/notification-inbox";
|
|
979
|
+
export type { NotificationInboxItem, NotificationInboxProps } from "./notifications/notification-inbox";
|
|
980
|
+
export { NotificationBell } from "./notifications/notification-bell";
|
|
981
|
+
export type { NotificationBellProps } from "./notifications/notification-bell";
|
|
982
|
+
export { useDeviceZoneReport } from "./notifications/device-zone-report";
|
|
983
|
+
export type { DeviceZoneReportOptions } from "./notifications/device-zone-report";
|
|
953
984
|
export {
|
|
954
985
|
usePushDevice,
|
|
955
986
|
PUSH_SUBSCRIPTION_CHANGED,
|
|
@@ -964,6 +995,20 @@ export type {
|
|
|
964
995
|
PushPermission,
|
|
965
996
|
PushDeviceErrorCode,
|
|
966
997
|
} from "./notifications/use-push-device";
|
|
998
|
+
// 0.46 (NT:1028): the service worker re-registers a rotated subscription. A worker imports
|
|
999
|
+
// them from `@eifi1/ui-kit/push-worker` (§10.6 rule 5); the barrel keeps them too.
|
|
1000
|
+
export {
|
|
1001
|
+
handlePushSubscriptionChange,
|
|
1002
|
+
resubscribeAfterChange,
|
|
1003
|
+
pushSubscriptionInput,
|
|
1004
|
+
} from "./notifications/push-worker";
|
|
1005
|
+
export type {
|
|
1006
|
+
PushReplaceInput,
|
|
1007
|
+
PushSubscriptionChangeOptions,
|
|
1008
|
+
PushSubscriptionChangeEventLike,
|
|
1009
|
+
PushResubscribeOutcome,
|
|
1010
|
+
PushWorkerScope,
|
|
1011
|
+
} from "./notifications/push-worker";
|
|
967
1012
|
export { PushDeviceSetting } from "./notifications/push-device-setting";
|
|
968
1013
|
export type { PushDeviceSettingProps, PushDeviceAction } from "./notifications/push-device-setting";
|
|
969
1014
|
export { PushDevicesSetting, describeUserAgent } from "./notifications/push-devices-setting";
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { formatLocaleOf } from "../i18n/languages";
|
|
2
|
+
import type { KitDateFormatter } from "../i18n/kit-labels";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The date-format preference (0.46, lifted from keksdose's `date-format-store.ts`,
|
|
6
|
+
* feedback #180): how a DAY is written — the order of day, month and year and the
|
|
7
|
+
* separator between them — as a choice of its own, apart from the interface language.
|
|
8
|
+
* "Read the app in English and still see German dates", or ISO everywhere.
|
|
9
|
+
*
|
|
10
|
+
* keksdose's rules, which the kit keeps:
|
|
11
|
+
*
|
|
12
|
+
* 1. **A label that identifies a day follows the preference** — anything that prints
|
|
13
|
+
* day, month and year goes through {@link formatDateWithPreference} (or the
|
|
14
|
+
* provider's `formatDate`, {@link dateFormatterFor}, for the kit's own date fields).
|
|
15
|
+
* 2. **A label without a day follows the language**: "August 2026", a month or weekday
|
|
16
|
+
* NAME has no order to arrange. {@link dateFormatterFor} answers `""` for a month or a
|
|
17
|
+
* year, so the kit keeps its own rendering in the language.
|
|
18
|
+
* 3. **A clock time follows the language**: `sv-SE` is a statement about digit order in
|
|
19
|
+
* a date, not about a 12- or 24-hour clock.
|
|
20
|
+
* 4. **A weekday's NAME follows the language** (dev#546): `sv-SE` means "year first,
|
|
21
|
+
* with dashes", not "the reader speaks Swedish" — so with `weekday` the day's name is
|
|
22
|
+
* the language's, joined to the date the preference renders.
|
|
23
|
+
*
|
|
24
|
+
* The preference is the APP's to keep — keksdose holds it per device (a persisted store,
|
|
25
|
+
* not an account column: nothing server-written formats a date for the reader). The kit
|
|
26
|
+
* owns the vocabulary, the resolver, the renderer and the settings field
|
|
27
|
+
* ({@link DateFormatSetting}).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** `"auto"` follows the interface language (the default — nobody's dates move until
|
|
31
|
+
* they ask); the rest are BCP 47 tags chosen for the SHAPE they produce: German
|
|
32
|
+
* 01.08.2026, British 01/08/2026, US 08/01/2026, ISO 2026-08-01. */
|
|
33
|
+
export type DateFormatPreference = "auto" | "de-DE" | "en-GB" | "en-US" | "sv-SE";
|
|
34
|
+
|
|
35
|
+
/** The options in the order the setting lists them: `auto` first (the default), then
|
|
36
|
+
* German → UK → US → ISO. */
|
|
37
|
+
export const DATE_FORMAT_PREFERENCES: readonly DateFormatPreference[] = ["auto", "de-DE", "en-GB", "en-US", "sv-SE"];
|
|
38
|
+
|
|
39
|
+
/** Whether `value` is one of {@link DATE_FORMAT_PREFERENCES} — for a value read back from
|
|
40
|
+
* storage, where anything else falls back to `"auto"`. */
|
|
41
|
+
export function isDateFormatPreference(value: unknown): value is DateFormatPreference {
|
|
42
|
+
return typeof value === "string" && (DATE_FORMAT_PREFERENCES as readonly string[]).includes(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A resolved preference: the locale and `Intl` options the date itself is rendered
|
|
46
|
+
* with, and — only when asked for — the locale the weekday's NAME comes from. */
|
|
47
|
+
export interface ResolvedDateFormat {
|
|
48
|
+
locale: string | undefined;
|
|
49
|
+
options: Intl.DateTimeFormatOptions;
|
|
50
|
+
/** The language's format locale, present when `weekday` was asked for. */
|
|
51
|
+
weekday?: { locale: string | undefined };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** What {@link resolveDateFormat} and the renderers take beyond the preference. */
|
|
55
|
+
export interface DateFormatOptions {
|
|
56
|
+
/** Name the day of the week too, in the interface language ("Mo., 01.08.2026"). A date
|
|
57
|
+
* in a COLUMN carries it; a date in a SENTENCE does not (keksdose dev#546). */
|
|
58
|
+
weekday?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* What a date is rendered with, given the preference and the interface language
|
|
63
|
+
* (`language`: a code or tag — `"de"`, `"en"`, i18next's `resolvedLanguage`; it is read
|
|
64
|
+
* through `formatLocaleOf`, so English is British and an unshipped tag stays itself).
|
|
65
|
+
*
|
|
66
|
+
* `"auto"` keeps the short-month shape ("01 Aug 2026", "01. Aug. 2026"); every explicit
|
|
67
|
+
* choice is all-numeric, the only way it produces the shape its option promises —
|
|
68
|
+
* `month: "short"` under `sv-SE` would print "2026 aug. 01", not ISO.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveDateFormat(
|
|
71
|
+
preference: DateFormatPreference,
|
|
72
|
+
language: string | undefined,
|
|
73
|
+
options: DateFormatOptions = {},
|
|
74
|
+
): ResolvedDateFormat {
|
|
75
|
+
const languageLocale = language ? formatLocaleOf(language) : undefined;
|
|
76
|
+
const weekday = options.weekday ? { weekday: { locale: languageLocale } } : {};
|
|
77
|
+
if (preference === "auto") {
|
|
78
|
+
return { locale: languageLocale, options: { year: "numeric", month: "short", day: "2-digit" }, ...weekday };
|
|
79
|
+
}
|
|
80
|
+
return { locale: preference, options: { year: "numeric", month: "2-digit", day: "2-digit" }, ...weekday };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A bare calendar day: no time, no zone. */
|
|
84
|
+
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* `dateText`, already rendered under the preference, with the day's NAME around it in
|
|
88
|
+
* `locale` (the language's). Not "weekday alone + comma": German says "So" alone and
|
|
89
|
+
* "So., 06.09.2026" in company. The full date is formatted in the language and split
|
|
90
|
+
* into parts; what that language puts before the first and after the last day/month/
|
|
91
|
+
* year part (the weekday, its comma) frames the preference's digits — so where the two
|
|
92
|
+
* locales coincide the output is the native string, character for character.
|
|
93
|
+
*/
|
|
94
|
+
function withWeekday(date: Date, dateText: string, locale: string | undefined, options: Intl.DateTimeFormatOptions) {
|
|
95
|
+
const isDatePart = (p: Intl.DateTimeFormatPart) => p.type === "day" || p.type === "month" || p.type === "year";
|
|
96
|
+
const parts = new Intl.DateTimeFormat(locale, { weekday: "short", ...options }).formatToParts(date);
|
|
97
|
+
const first = parts.findIndex(isDatePart);
|
|
98
|
+
if (first === -1) return dateText;
|
|
99
|
+
let last = first;
|
|
100
|
+
parts.forEach((p, i) => {
|
|
101
|
+
if (isDatePart(p)) last = i;
|
|
102
|
+
});
|
|
103
|
+
const join = (ps: Intl.DateTimeFormatPart[]) => ps.map((p) => p.value).join("");
|
|
104
|
+
return join(parts.slice(0, first)) + dateText + join(parts.slice(last + 1));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A date under the preference: `""` for no value, the input itself when it does not
|
|
109
|
+
* parse. A bare "YYYY-MM-DD" is the calendar day it names in every zone (formatted in
|
|
110
|
+
* UTC, undoing `new Date`'s UTC-midnight parse — west of Greenwich it read a day early);
|
|
111
|
+
* a timestamp is an instant and stays in the reader's zone. The weekday, when asked for,
|
|
112
|
+
* is rendered in the same zone, so a Sunday is never captioned Saturday.
|
|
113
|
+
*/
|
|
114
|
+
export function formatDateWithPreference(
|
|
115
|
+
value: string | Date | null | undefined,
|
|
116
|
+
preference: DateFormatPreference,
|
|
117
|
+
language: string | undefined,
|
|
118
|
+
options: DateFormatOptions = {},
|
|
119
|
+
): string {
|
|
120
|
+
if (!value) return "";
|
|
121
|
+
const resolved = resolveDateFormat(preference, language, options);
|
|
122
|
+
const dateOnly = typeof value === "string" && DATE_ONLY.test(value);
|
|
123
|
+
const date = typeof value === "string" ? new Date(value) : value;
|
|
124
|
+
if (Number.isNaN(date.getTime())) return String(value);
|
|
125
|
+
const intl: Intl.DateTimeFormatOptions = dateOnly ? { ...resolved.options, timeZone: "UTC" } : resolved.options;
|
|
126
|
+
try {
|
|
127
|
+
const text = date.toLocaleDateString(resolved.locale, intl);
|
|
128
|
+
return resolved.weekday ? withWeekday(date, text, resolved.weekday.locale, intl) : text;
|
|
129
|
+
} catch {
|
|
130
|
+
// A language tag `Intl` refuses: the ISO day rather than nothing (keksdose's fallback).
|
|
131
|
+
return date.toISOString().slice(0, 10);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The sample an option is labelled with — 1 August 2026 in its shape, the shape being
|
|
136
|
+
* the whole choice (a country name would say nothing about the digit order). */
|
|
137
|
+
export function dateFormatSample(preference: DateFormatPreference, language: string | undefined): string {
|
|
138
|
+
return formatDateWithPreference("2026-08-01", preference, language);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The `<UiKitProvider formatDate>` for a preference — so the kit's own date fields
|
|
143
|
+
* (DatePicker, DateRangePicker, DateMark) write a day as the app's dates do. A day
|
|
144
|
+
* follows the preference, with the weekday where the kit suggests it; a month or a year
|
|
145
|
+
* answers `""` and keeps the kit's rendering in the language (rule 2). Rebuild it when
|
|
146
|
+
* the preference or the language changes (a `useMemo` on both).
|
|
147
|
+
*/
|
|
148
|
+
export function dateFormatterFor(preference: DateFormatPreference, language: string | undefined): KitDateFormatter {
|
|
149
|
+
return (iso, { unit, weekday, locale }) =>
|
|
150
|
+
unit === "day" ? formatDateWithPreference(iso, preference, locale ?? language, { weekday }) : "";
|
|
151
|
+
}
|
package/src/lib/format.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import { useKitLocale } from "../i18n/kit-labels";
|
|
2
|
+
import { useKitLocale, useKitTimeZone } from "../i18n/kit-labels";
|
|
3
3
|
import { parseIsoDate } from "./dates";
|
|
4
4
|
import { roundHalfAway } from "./round";
|
|
5
5
|
|
|
@@ -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" &&
|
|
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,10 +233,20 @@ 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
|
|
|
235
246
|
export interface FormatRelativeTimeOptions extends BaseOptions {
|
|
247
|
+
/** The zone of the date printed past `absoluteAfterDays` (0.46). Left out: the
|
|
248
|
+
* device's. */
|
|
249
|
+
timeZone?: string;
|
|
236
250
|
/** The moment it is measured from. Default: now. */
|
|
237
251
|
now?: DateInput;
|
|
238
252
|
/** `"auto"` (default): "yesterday", "now", "next week". `"always"`: "1 day ago". */
|
|
@@ -337,16 +351,19 @@ export interface KitFormat {
|
|
|
337
351
|
*/
|
|
338
352
|
export function useKitFormat(locale?: string): KitFormat {
|
|
339
353
|
const kitLocale = useKitLocale(locale);
|
|
354
|
+
// 0.46 (§10.3): the provider's `timeZone` for timestamps; a call's own wins, and a bare
|
|
355
|
+
// "YYYY-MM-DD" ignores it (formatDate).
|
|
356
|
+
const timeZone = useKitTimeZone();
|
|
340
357
|
return useMemo<KitFormat>(
|
|
341
358
|
() => ({
|
|
342
359
|
locale: kitLocale,
|
|
343
360
|
formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),
|
|
344
361
|
formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),
|
|
345
362
|
formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),
|
|
346
|
-
formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, ...options }),
|
|
347
|
-
formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, ...options }),
|
|
363
|
+
formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, timeZone, ...options }),
|
|
364
|
+
formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, timeZone, ...options }),
|
|
348
365
|
}),
|
|
349
|
-
[kitLocale],
|
|
366
|
+
[kitLocale, timeZone],
|
|
350
367
|
);
|
|
351
368
|
}
|
|
352
369
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import { settle } from "../account/account-parts";
|
|
4
|
+
import { deviceTimeZone } from "./time-zone-select";
|
|
5
|
+
|
|
6
|
+
export interface DeviceZoneReportOptions {
|
|
7
|
+
/** The account's `device_timezone` from `/auth/me`; `undefined` while it loads (no
|
|
8
|
+
* report until it is known). */
|
|
9
|
+
stored: string | null | undefined;
|
|
10
|
+
/** `PATCH /auth/me {device_timezone}` with this device's zone. */
|
|
11
|
+
report: (deviceTimezone: string) => Promise<unknown> | void;
|
|
12
|
+
/** A demo account: never reported (the server ignores it too, §10.3 Settled). */
|
|
13
|
+
isDemo?: boolean;
|
|
14
|
+
/** The signed-in account (its user id): the once-per-start guard is per account, so a
|
|
15
|
+
* second person signed in on a shared PC in the same start is reported too. Without it
|
|
16
|
+
* the guard is cleared when `stored` goes `undefined` (signed out). */
|
|
17
|
+
account?: string | number | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The (account, stored, device) triples already reported in this start of the app. */
|
|
21
|
+
const reportedThisStart = new Set<string>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Report this device's zone to the account (0.46, §10.3): the account's zone follows the
|
|
25
|
+
* device last used, else `Europe/Zurich`; screens keep showing device time. Once per start
|
|
26
|
+
* of the app, and only when it differs from the stored `device_timezone` — a bench PC in Zurich never
|
|
27
|
+
* PATCHes on a sign-in — and never for a demo. Per account: a sign-out (`stored` back to
|
|
28
|
+
* `undefined`) clears the guard, and `account` keys it. The report IS the account's zone: it is
|
|
29
|
+
* automatic only (§10.3, amended 2026-10-20), so nothing is ever picked. A failed report
|
|
30
|
+
* is not retried in the same start; the next start tries again. Sign-up sends the zone
|
|
31
|
+
* itself (`RegisterRequest.device_timezone`).
|
|
32
|
+
*
|
|
33
|
+
* useDeviceZoneReport({ stored: me?.device_timezone, isDemo: me?.is_demo, account: me?.id,
|
|
34
|
+
* report: async (zone) => setUser((await api.patch("/auth/me", { device_timezone: zone })).data) });
|
|
35
|
+
*/
|
|
36
|
+
export function useDeviceZoneReport({ stored, report, isDemo = false, account = null }: DeviceZoneReportOptions): void {
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (stored === undefined) {
|
|
39
|
+
// Signed out (or still loading): the next account in this start is reported afresh.
|
|
40
|
+
reportedThisStart.clear();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (isDemo) return;
|
|
44
|
+
const device = deviceTimeZone();
|
|
45
|
+
if (device === stored) return;
|
|
46
|
+
const key = `${account ?? ""}|${stored ?? ""}→${device}`;
|
|
47
|
+
if (reportedThisStart.has(key)) return;
|
|
48
|
+
reportedThisStart.add(key);
|
|
49
|
+
void settle(() => report(device)).catch(() => undefined);
|
|
50
|
+
// `report` is the app's request function: a new closure each render must not report
|
|
51
|
+
// again, so only the values decide.
|
|
52
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
53
|
+
}, [stored, isDemo, account]);
|
|
54
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// component code, as every `*-labels.ts`: the English catalogue imports them alone.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* `feedbackNotificationTypes` — the setting words of the
|
|
5
|
+
* `feedbackNotificationTypes` — the setting words of the feedback notification types
|
|
6
6
|
* (docs/feedback-harmonization.md §9.11). In the kit, not in each app, because two apps
|
|
7
7
|
* declare them (keksdose, Kurvenschmiede; kastlan `feedback_answered` alone). The mail and
|
|
8
8
|
* push texts stay the app's.
|
|
@@ -14,6 +14,12 @@ export interface FeedbackNotificationTypesLabels {
|
|
|
14
14
|
/** `feedback_comment`: a visible comment on such a report. */
|
|
15
15
|
comment: string;
|
|
16
16
|
commentDescription: string;
|
|
17
|
+
/** 0.46 (feedback §11): `feedback_received`, an admin's alert for a new report, a
|
|
18
|
+
* rework and a new crash. */
|
|
19
|
+
received: string;
|
|
20
|
+
receivedDescription: string;
|
|
21
|
+
/** A heading the apps may put over the admin's types ("Admin"). */
|
|
22
|
+
adminGroup: string;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export const DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS: FeedbackNotificationTypesLabels = {
|
|
@@ -21,4 +27,7 @@ export const DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS: FeedbackNotificationTyp
|
|
|
21
27
|
answeredDescription: "When a report you wrote or commented on gets an answer.",
|
|
22
28
|
comment: "Comments on reports you follow",
|
|
23
29
|
commentDescription: "When someone comments on a report you wrote or commented on.",
|
|
30
|
+
received: "New feedback and reworks",
|
|
31
|
+
receivedDescription: "When someone files a report or sends one back.",
|
|
32
|
+
adminGroup: "Admin",
|
|
24
33
|
};
|