@protobase/ui 0.25.2 → 0.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/dist/account/account-pages.js +3 -2
  2. package/dist/account/account-security-page.js +9 -4
  3. package/dist/account/backup-codes.js +7 -6
  4. package/dist/account/messages.js +214 -0
  5. package/dist/account/passkeys-section.js +23 -22
  6. package/dist/account/password-dialog.js +3 -2
  7. package/dist/account/sign-in-policy-form.js +11 -10
  8. package/dist/account/sign-in-policy-labels.js +30 -29
  9. package/dist/account/sign-in-policy-page.js +11 -6
  10. package/dist/account/sign-in-providers-section.js +6 -5
  11. package/dist/account/staff-sign-in-log.js +11 -10
  12. package/dist/account/totp-qr.js +2 -1
  13. package/dist/account/two-factor-section.js +24 -23
  14. package/dist/account/two-factor-setup.js +18 -17
  15. package/dist/action-card/action-card.js +2 -1
  16. package/dist/action-card/messages.js +8 -0
  17. package/dist/activity-chart/activity-chart.js +3 -2
  18. package/dist/activity-chart/messages.js +14 -0
  19. package/dist/app/app.d.ts +1 -1
  20. package/dist/app/app.js +20 -21
  21. package/dist/app/assistant-shell.js +2 -1
  22. package/dist/app/batch-failure.js +2 -1
  23. package/dist/app/breadcrumb-for.js +4 -3
  24. package/dist/app/charts/chart-groups.js +3 -2
  25. package/dist/app/charts/chart-view.js +3 -2
  26. package/dist/app/charts/group-bars.js +2 -1
  27. package/dist/app/charts/messages.js +26 -0
  28. package/dist/app/charts/period-chart.js +3 -2
  29. package/dist/app/charts/split-points.js +2 -1
  30. package/dist/app/comments/comment-composer.js +19 -18
  31. package/dist/app/comments/comments-section.js +4 -3
  32. package/dist/app/comments/messages.js +26 -0
  33. package/dist/app/create/create-page.js +11 -18
  34. package/dist/app/custom-section.js +4 -6
  35. package/dist/app/displays/board-column.js +6 -5
  36. package/dist/app/displays/board-columns.js +3 -2
  37. package/dist/app/displays/board-display.js +13 -15
  38. package/dist/app/displays/calendar-controls.js +3 -2
  39. package/dist/app/displays/calendar-display.js +12 -17
  40. package/dist/app/displays/card-carousel.js +4 -3
  41. package/dist/app/displays/cards-display.js +2 -1
  42. package/dist/app/displays/custom-display.js +4 -6
  43. package/dist/app/displays/display-frame.js +3 -2
  44. package/dist/app/displays/list-cards.js +5 -8
  45. package/dist/app/displays/list-displays.js +3 -3
  46. package/dist/app/displays/map-display.js +15 -14
  47. package/dist/app/displays/messages.js +128 -2
  48. package/dist/app/displays/period-controls.js +4 -3
  49. package/dist/app/displays/record-card.js +7 -6
  50. package/dist/app/displays/timeline-display.js +17 -26
  51. package/dist/app/displays/timeline-range.js +2 -1
  52. package/dist/app/displays/use-board-move.js +2 -2
  53. package/dist/app/displays/use-calendar-move.js +2 -1
  54. package/dist/app/document-title.js +2 -1
  55. package/dist/app/export/builder-port.js +2 -1
  56. package/dist/app/export/export-dialog.js +36 -40
  57. package/dist/app/export/export-worker.js +2 -0
  58. package/dist/app/export/messages.js +78 -0
  59. package/dist/app/export/run-export.js +5 -2
  60. package/dist/app/export/sink.js +4 -3
  61. package/dist/app/export/use-export-flow.js +4 -3
  62. package/dist/app/field-editor.js +4 -3
  63. package/dist/app/field-types/messages.js +8 -0
  64. package/dist/app/field-types/widget-part.js +2 -1
  65. package/dist/app/file-field.js +11 -14
  66. package/dist/app/gallery/gallery-section.js +4 -3
  67. package/dist/app/gallery/lightbox.js +5 -4
  68. package/dist/app/gallery/messages.js +30 -0
  69. package/dist/app/gallery/photo-details.js +3 -2
  70. package/dist/app/hand-order/hand-sorted-list.js +3 -6
  71. package/dist/app/hand-order/messages.js +4 -2
  72. package/dist/app/history/describe-change.js +3 -8
  73. package/dist/app/history/messages.js +32 -0
  74. package/dist/app/import/convert.js +20 -19
  75. package/dist/app/import/failed-rows.js +3 -2
  76. package/dist/app/import/import-button.js +2 -1
  77. package/dist/app/import/import-check-step.js +19 -35
  78. package/dist/app/import/import-dialog.js +13 -17
  79. package/dist/app/import/import-file-step.js +5 -9
  80. package/dist/app/import/import-mapping-step.js +32 -35
  81. package/dist/app/import/import-result-step.js +13 -12
  82. package/dist/app/import/messages.js +200 -0
  83. package/dist/app/import/parse-worker.js +2 -0
  84. package/dist/app/import/prepare-rows.js +5 -4
  85. package/dist/app/import/sheet-cell.js +2 -1
  86. package/dist/app/import/sheet-file.js +3 -2
  87. package/dist/app/import/sheet-source.js +7 -3
  88. package/dist/app/import/xlsx-reader.js +4 -3
  89. package/dist/app/import/xlsx-xml.js +2 -1
  90. package/dist/app/import/zip-reader.js +4 -3
  91. package/dist/app/list-edit/use-list-edits.js +1 -1
  92. package/dist/app/list-group-chart.js +2 -1
  93. package/dist/app/list-page.js +1 -1
  94. package/dist/app/map/map-layer-switch.js +3 -2
  95. package/dist/app/map/messages.js +14 -0
  96. package/dist/app/messages.js +116 -2
  97. package/dist/app/meta-gate.js +1 -1
  98. package/dist/app/nav-from-meta.js +11 -10
  99. package/dist/app/pages/action-inputs/action-inputs-dialog.js +3 -2
  100. package/dist/app/pages/action-inputs/input-answers.js +6 -5
  101. package/dist/app/pages/blocks/card-row.js +3 -2
  102. package/dist/app/pages/blocks/modal-form.js +5 -4
  103. package/dist/app/pages/blocks/record-card.js +3 -2
  104. package/dist/app/pages/blocks/stat.js +2 -1
  105. package/dist/app/pages/composed-page.js +6 -8
  106. package/dist/app/pages/confirm-action.js +2 -1
  107. package/dist/app/pages/custom-boundary.js +4 -2
  108. package/dist/app/pages/messages.js +64 -0
  109. package/dist/app/pages/render-node.js +5 -7
  110. package/dist/app/pages/use-run-action.js +11 -10
  111. package/dist/app/record-page.js +1 -1
  112. package/dist/app/record-row/messages.js +26 -0
  113. package/dist/app/record-row/record-row-body.js +12 -7
  114. package/dist/app/record-row/record-row.js +4 -3
  115. package/dist/app/record-row/saved-record-row.js +3 -2
  116. package/dist/app/related-dots/dots-summary.js +2 -1
  117. package/dist/app/related-dots/messages.js +8 -0
  118. package/dist/app/relation-picker.js +9 -8
  119. package/dist/app/router.js +5 -4
  120. package/dist/app/runtime/messages.js +58 -0
  121. package/dist/app/runtime/runtime-status.js +10 -23
  122. package/dist/app/runtime/runtime-update-dialog.js +7 -6
  123. package/dist/app/runtime/runtime-update.js +4 -3
  124. package/dist/app/saved/favorite-button.js +2 -1
  125. package/dist/app/saved/messages.js +22 -0
  126. package/dist/app/saved/saved-list.js +4 -7
  127. package/dist/app/saved/use-favorite.js +3 -2
  128. package/dist/app/search/messages.js +6 -2
  129. package/dist/app/search/navigation-hits.js +9 -7
  130. package/dist/app/searches/filter-chip-list.js +2 -1
  131. package/dist/app/searches/messages.js +96 -0
  132. package/dist/app/searches/save-search-button.js +5 -4
  133. package/dist/app/searches/saved-search-rows.js +3 -2
  134. package/dist/app/searches/saved-search-section.js +6 -5
  135. package/dist/app/searches/saved-search.js +4 -3
  136. package/dist/app/searches/saved-searches.js +15 -25
  137. package/dist/app/searches/search-controls.js +5 -4
  138. package/dist/app/searches/search-diff.js +4 -2
  139. package/dist/app/searches/search-name-dialog.js +4 -3
  140. package/dist/app/searches/update-search-dialog.js +15 -30
  141. package/dist/app/searches/use-save-search.js +8 -7
  142. package/dist/app/searches/use-search-row.js +4 -3
  143. package/dist/app/sensitive-field.js +5 -4
  144. package/dist/app/source-notice.js +13 -8
  145. package/dist/app/staff-banner.js +12 -19
  146. package/dist/app-shell/global-search.js +2 -2
  147. package/dist/app-shell/messages.js +4 -2
  148. package/dist/assistant-dock/assistant-dock.js +5 -5
  149. package/dist/assistant-dock/messages.js +26 -0
  150. package/dist/assistant-dock/use-assistant.js +2 -1
  151. package/dist/assistant-dock/use-backend-data.js +2 -1
  152. package/dist/assistant-dock/widget-view.js +3 -2
  153. package/dist/auth/auth-gate.js +10 -5
  154. package/dist/auth/auth-locale.js +14 -0
  155. package/dist/auth/auth-messages.js +122 -43
  156. package/dist/auth/auth-provider.js +6 -1
  157. package/dist/auth/code-form.js +2 -1
  158. package/dist/auth/email-code-form.js +11 -13
  159. package/dist/auth/email-code-page.js +2 -1
  160. package/dist/auth/first-run-page.js +6 -5
  161. package/dist/auth/forgot-password-form.js +14 -16
  162. package/dist/auth/forgot-password-page.js +2 -1
  163. package/dist/auth/messages.js +200 -0
  164. package/dist/auth/provider-sign-in-page.js +5 -5
  165. package/dist/auth/resend-code-button.js +2 -1
  166. package/dist/auth/reset-password-form.js +11 -10
  167. package/dist/auth/reset-password-page.js +4 -3
  168. package/dist/auth/setup-required-form.js +7 -9
  169. package/dist/auth/setup-required-page.js +7 -2
  170. package/dist/auth/sign-in-form.js +15 -18
  171. package/dist/auth/sign-in-prompt.js +4 -3
  172. package/dist/auth/sign-up-code-form.js +11 -13
  173. package/dist/auth/sign-up-details-form.js +14 -21
  174. package/dist/auth/sign-up-email-form.js +8 -7
  175. package/dist/auth/sign-up-page.js +2 -1
  176. package/dist/auth/staff-sign-in-form.js +14 -17
  177. package/dist/auth/two-factor-form.js +17 -16
  178. package/dist/auth/user-to-shell.js +2 -1
  179. package/dist/chat/composer.js +8 -7
  180. package/dist/chat/messages.js +14 -0
  181. package/dist/compact-table/compact-table.js +4 -8
  182. package/dist/compact-table/messages.js +18 -0
  183. package/dist/data/meta-data.d.ts +1 -1
  184. package/dist/data/use-update-record.js +1 -1
  185. package/dist/inline-child-table/inline-child-table.js +12 -12
  186. package/dist/inline-child-table/line-row.js +10 -9
  187. package/dist/inline-child-table/messages.js +52 -0
  188. package/dist/inline-child-table/totals-footer.js +5 -8
  189. package/dist/invitations/invite-form.js +3 -2
  190. package/dist/invitations/link-notice.js +6 -4
  191. package/dist/invitations/messages.js +24 -0
  192. package/dist/invitations/pending-invitation-row.js +4 -3
  193. package/dist/lists/messages.js +8 -0
  194. package/dist/lists/paged-search-list.js +2 -1
  195. package/dist/locale/slotted.js +14 -0
  196. package/dist/markdown/markdown-editor.js +6 -5
  197. package/dist/markdown/messages.js +16 -0
  198. package/dist/organizations/all-organizations.js +6 -5
  199. package/dist/organizations/app-roles-picker.js +2 -1
  200. package/dist/organizations/invitation-page.js +39 -49
  201. package/dist/organizations/invitations-page.js +11 -10
  202. package/dist/organizations/members-page.js +15 -10
  203. package/dist/organizations/messages.js +180 -0
  204. package/dist/organizations/organization-page.js +27 -26
  205. package/dist/organizations/organization-paths.js +16 -13
  206. package/dist/organizations/organization-row.js +3 -2
  207. package/dist/organizations/organizations-page.js +17 -16
  208. package/dist/organizations/use-organizations.js +2 -1
  209. package/dist/package.js +1 -1
  210. package/dist/plugins/before-sign-in.js +6 -1
  211. package/dist/plugins/load-plugin.d.ts +1 -1
  212. package/dist/plugins/load-plugin.js +3 -2
  213. package/dist/plugins/messages.js +36 -0
  214. package/dist/plugins/plugin-gate.js +6 -1
  215. package/dist/plugins/plugin-manifest.js +8 -7
  216. package/dist/plugins/plugin-notice.js +8 -12
  217. package/dist/primitives/messages.js +16 -0
  218. package/dist/primitives/sheet.js +2 -1
  219. package/dist/primitives/spinner.js +2 -1
  220. package/dist/primitives/token.js +2 -1
  221. package/dist/record-view/messages.js +4 -2
  222. package/dist/record-view/record-tabs.js +2 -1
  223. package/dist/roles/messages.js +14 -0
  224. package/dist/roles/use-active-role.js +3 -2
  225. package/dist/shell/assets/app-CW3U56yM.js +69 -0
  226. package/dist/shell/assets/cn-Bi0ZEjD9.js +1 -0
  227. package/dist/shell/assets/export-worker-ClACk00b.js +2 -0
  228. package/dist/shell/assets/{index-xY0Mvv6V.js → index-B7cIjCuz.js} +2 -2
  229. package/dist/shell/assets/maplibre-CVG5L9FD.js +814 -0
  230. package/dist/shell/assets/{markdown-content-ukDTCu6u.js → markdown-content-CtGUP4W6.js} +1 -1
  231. package/dist/shell/assets/parse-worker-UhbwEjS0.js +5 -0
  232. package/dist/shell/assets/preload-helper-BX9CcZkY.js +1 -0
  233. package/dist/shell/assets/{record-map-BJmYZ7kQ.js → record-map-Cw3_NcUl.js} +1 -1
  234. package/dist/shell/assets/records-map-D4f3ZeDL.js +1 -0
  235. package/dist/shell/assets/shared-protobase-ui-CB_i50NP.js +1 -0
  236. package/dist/shell/index.html +5 -3
  237. package/dist/shell/protobase.shell.json +1 -1
  238. package/dist/shell/protobase.sw.js +2 -2
  239. package/dist/side-panel/messages.js +8 -0
  240. package/dist/side-panel/side-panel.js +2 -1
  241. package/dist/toasts/messages.js +8 -0
  242. package/dist/toasts/toast.js +2 -1
  243. package/dist/users/app-invitations.js +5 -4
  244. package/dist/users/create-user-form.js +9 -8
  245. package/dist/users/messages.js +52 -0
  246. package/dist/users/use-user-roles.js +2 -1
  247. package/dist/users/user-list.js +6 -5
  248. package/dist/users/user-row.js +5 -4
  249. package/package.json +4 -4
  250. package/dist/shell/assets/app-C-OLrsRR.js +0 -69
  251. package/dist/shell/assets/export-worker-Bz57Pf5g.js +0 -2
  252. package/dist/shell/assets/maplibre-Dov6nhPX.js +0 -814
  253. package/dist/shell/assets/parse-worker-BtcY0dQw.js +0 -5
  254. package/dist/shell/assets/records-map-C1_gDKHR.js +0 -1
  255. package/dist/shell/assets/shared-protobase-ui-CFYJAbMR.js +0 -1
@@ -1,4 +1,5 @@
1
1
  import { Skeleton } from "../../primitives/skeleton.js";
2
+ import { slotted } from "../../locale/slotted.js";
2
3
  import { Problem } from "../pages/problem.js";
3
4
  import { recordKey, recordTitle } from "../../live/model-helpers.js";
4
5
  import { loadedFields } from "../displays/list-displays.js";
@@ -11,7 +12,7 @@ import { embeddedColumns } from "../embedded-list/embedded-columns.js";
11
12
  import { text } from "./messages.js";
12
13
  import { useHandOrder } from "./use-hand-order.js";
13
14
  import { useMemo } from "react";
14
- import { jsx, jsxs } from "react/jsx-runtime";
15
+ import { jsx } from "react/jsx-runtime";
15
16
  //#region src/app/hand-order/hand-sorted-list.tsx
16
17
  /**
17
18
  * The records in their order by hand, in the list's table with a drag handle in each row's checkbox place: dragging a
@@ -58,11 +59,7 @@ const HandSortedList = ({ model, view, field, filter, columns, sort }) => {
58
59
  total: rows.length,
59
60
  rowAt: (index) => rows[index]
60
61
  }, [rows]);
61
- if (ruleName !== void 0 && !rule) return /* @__PURE__ */ jsxs(Problem, { children: [
62
- "No drop rule is registered as ",
63
- ruleName,
64
- ". Give it in dropRules in protobase.ui.tsx, or load the plugin that provides it."
65
- ] });
62
+ if (ruleName !== void 0 && !rule) return /* @__PURE__ */ jsx(Problem, { children: slotted(text("noRule"), ruleName) });
66
63
  if (order.loading) return /* @__PURE__ */ jsx("div", {
67
64
  className: "flex flex-col gap-2",
68
65
  children: Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }, index))
@@ -11,7 +11,8 @@ const text = defineMessages({
11
11
  undo: "Undo",
12
12
  couldNotUndo: "Could not undo",
13
13
  movedToPlace: (place) => `Moved to place ${place}`,
14
- notMoved: "Left in its place"
14
+ notMoved: "Left in its place",
15
+ noRule: "No drop rule is registered as {}. Give it in dropRules in protobase.ui.tsx, or load the plugin that provides it."
15
16
  },
16
17
  nl: {
17
18
  done: "Klaar",
@@ -23,7 +24,8 @@ const text = defineMessages({
23
24
  undo: "Ongedaan maken",
24
25
  couldNotUndo: "Kon niet ongedaan maken",
25
26
  movedToPlace: (place) => `Verplaatst naar plaats ${place}`,
26
- notMoved: "Op zijn plaats gelaten"
27
+ notMoved: "Op zijn plaats gelaten",
28
+ noRule: "Er is geen sleepregel geregistreerd als {}. Geef die in dropRules in protobase.ui.tsx, of laad de plug-in die haar levert."
27
29
  }
28
30
  });
29
31
  //#endregion
@@ -1,16 +1,11 @@
1
1
  import { humanize } from "../../live/naming.js";
2
2
  import { displayValue } from "../../live/display-value.js";
3
+ import { text } from "./messages.js";
3
4
  //#region src/app/history/describe-change.ts
4
- const verbs = {
5
- create: "created this",
6
- update: "changed",
7
- delete: "deleted this",
8
- undelete: "restored this"
9
- };
10
5
  /** Who made a change: their name, else their id; a write from outside the app names the database role. */
11
6
  const changedBy = (change) => {
12
7
  if (change.actor) return change.actor.name ?? change.actor.id;
13
- return change.databaseRole ? `Database (${change.databaseRole})` : "Database";
8
+ return change.databaseRole ? text("databaseRole", change.databaseRole) : text("database");
14
9
  };
15
10
  /**
16
11
  * A change as the record page tells it: who, what they did, and for an update each changed field the model still has,
@@ -33,7 +28,7 @@ const describeChange = (change, model, view) => {
33
28
  after: displayValue(field, change.after[name], hints)
34
29
  }];
35
30
  });
36
- const verb = change.operation === "update" && fields.length === 0 ? "changed this" : verbs[change.operation];
31
+ const verb = change.operation === "update" && fields.length === 0 ? text("changedThis") : text(change.operation);
37
32
  return {
38
33
  who: changedBy(change),
39
34
  verb,
@@ -0,0 +1,32 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/history/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ create: "created this",
6
+ update: "changed",
7
+ delete: "deleted this",
8
+ undelete: "restored this",
9
+ database: "Database",
10
+ databaseRole: (role) => `Database (${role})`,
11
+ changed: "changed",
12
+ changedThis: "changed this",
13
+ couldNotLoad: "Could not load the history",
14
+ noChanges: "No changes kept yet.",
15
+ showMore: "Show more"
16
+ },
17
+ nl: {
18
+ create: "maakte dit",
19
+ update: "wijzigde",
20
+ delete: "verwijderde dit",
21
+ undelete: "zette dit terug",
22
+ database: "Database",
23
+ databaseRole: (role) => `Database (${role})`,
24
+ changed: "gewijzigd",
25
+ changedThis: "wijzigde dit",
26
+ couldNotLoad: "Kon de geschiedenis niet laden",
27
+ noChanges: "Nog geen wijzigingen bewaard.",
28
+ showMore: "Meer tonen"
29
+ }
30
+ });
31
+ //#endregion
32
+ export { text };
@@ -1,4 +1,5 @@
1
1
  import { parseNumberText } from "../../format/number-text.js";
2
+ import { text } from "./messages.js";
2
3
  import { cellText, isEmptyCell, isSheetDate } from "./sheet-cell.js";
3
4
  import { enumLabel } from "@protobase/schema";
4
5
  //#region src/app/import/convert.ts
@@ -16,17 +17,17 @@ const toNumberText = (cell, options) => {
16
17
  if (typeof cell === "string") return parseNumberText(cell, options.decimal);
17
18
  };
18
19
  const integerValue = (field, cell, options) => {
19
- const text = toNumberText(cell, options);
20
- if (text === void 0) return problem(`${quoted(cell)} is not a number`);
21
- const [whole, fraction = ""] = text.split(".");
22
- if (/[1-9]/.test(fraction) || /e/i.test(text)) return problem(`${quoted(cell)} is not a whole number`);
20
+ const text$1 = toNumberText(cell, options);
21
+ if (text$1 === void 0) return problem(text("notNumber", quoted(cell)));
22
+ const [whole, fraction = ""] = text$1.split(".");
23
+ if (/[1-9]/.test(fraction) || /e/i.test(text$1)) return problem(text("notWholeNumber", quoted(cell)));
23
24
  if (field.type === "bigint") return ok(String(BigInt(whole)));
24
25
  const value = Number(whole);
25
- return Number.isSafeInteger(value) ? ok(value) : problem(`${quoted(cell)} is too large`);
26
+ return Number.isSafeInteger(value) ? ok(value) : problem(text("tooLarge", quoted(cell)));
26
27
  };
27
28
  const decimalValue = (cell, options) => {
28
- const text = toNumberText(cell, options);
29
- return text === void 0 ? problem(`${quoted(cell)} is not a number`) : ok(text);
29
+ const text$2 = toNumberText(cell, options);
30
+ return text$2 === void 0 ? problem(text("notNumber", quoted(cell))) : ok(text$2);
30
31
  };
31
32
  const trueWords = /* @__PURE__ */ new Set([
32
33
  "true",
@@ -40,10 +41,10 @@ const falseWords = /* @__PURE__ */ new Set([
40
41
  ]);
41
42
  const booleanValue = (cell) => {
42
43
  if (typeof cell === "boolean") return ok(cell);
43
- const text = typeof cell === "string" || typeof cell === "number" ? String(cell).trim().toLowerCase() : "";
44
- if (trueWords.has(text)) return ok(true);
45
- if (falseWords.has(text)) return ok(false);
46
- return problem(`${quoted(cell)} is not true or false (true, false, yes, no, 1 or 0)`);
44
+ const text$3 = typeof cell === "string" || typeof cell === "number" ? String(cell).trim().toLowerCase() : "";
45
+ if (trueWords.has(text$3)) return ok(true);
46
+ if (falseWords.has(text$3)) return ok(false);
47
+ return problem(text("notBoolean", quoted(cell)));
47
48
  };
48
49
  const pad = (value) => String(value).padStart(2, "0");
49
50
  const calendarDate = (year, month, day) => {
@@ -90,11 +91,11 @@ const wallTimeOf = (cell, options) => {
90
91
  }
91
92
  return typeof cell === "string" ? readWallTime(cell, options.dateOrder) : void 0;
92
93
  };
93
- const notDate = (cell, options) => problem(`${quoted(cell)} is not a date (2026-04-03 or ${options.dateOrder === "dmy" ? "day/month/year" : "month/day/year"})`);
94
+ const notDate = (cell, options) => problem(text("notDate", quoted(cell), options.dateOrder === "dmy"));
94
95
  const dateValue = (cell, options) => {
95
96
  const at = wallTimeOf(cell, options);
96
97
  if (!at) return notDate(cell, options);
97
- if (at.time && at.time !== "00:00:00") return problem(`${quoted(cell)} has a time of day; the field holds a date`);
98
+ if (at.time && at.time !== "00:00:00") return problem(text("hasTime", quoted(cell)));
98
99
  return ok(at.date);
99
100
  };
100
101
  const timestampValue = (cell, options) => {
@@ -108,20 +109,20 @@ const timestampValue = (cell, options) => {
108
109
  const normalized = (text) => text.trim().toLowerCase().replace(/\s+/g, " ");
109
110
  const enumValue = (field, hints, cell) => {
110
111
  const values = field.enumValues ?? [];
111
- const text = normalized(cellText(cell));
112
- const matches = values.filter((value) => normalized(value) === text || normalized(enumLabel(value, hints?.valueLabels)) === text);
112
+ const text$4 = normalized(cellText(cell));
113
+ const matches = values.filter((value) => normalized(value) === text$4 || normalized(enumLabel(value, hints?.valueLabels)) === text$4);
113
114
  if (matches.length === 1) return ok(matches[0]);
114
- if (matches.length > 1) return problem(`${quoted(cell)} matches more than one value: ${matches.map((value) => enumLabel(value, hints?.valueLabels)).join(", ")}`);
115
- return problem(`${quoted(cell)} is not one of ${values.map((value) => enumLabel(value, hints?.valueLabels)).join(", ")}`);
115
+ if (matches.length > 1) return problem(text("severalValues", quoted(cell), matches.map((value) => enumLabel(value, hints?.valueLabels)).join(", ")));
116
+ return problem(text("notOneOf", quoted(cell), values.map((value) => enumLabel(value, hints?.valueLabels)).join(", ")));
116
117
  };
117
- const textValue = (cell) => typeof cell === "boolean" ? problem(`${quoted(cell)} is true or false, not text`) : ok(cellText(cell));
118
+ const textValue = (cell) => typeof cell === "boolean" ? problem(text("notText", quoted(cell))) : ok(cellText(cell));
118
119
  /**
119
120
  * The value a cell gives a field, as the API takes it, or the problem that keeps it from being one: never a guess, and an
120
121
  * empty cell is null, never a default. A relation's cell is the value of the field its records are found by, so it
121
122
  * converts as that field (`convertCell(targetField, ...)`).
122
123
  */
123
124
  const convertCell = (field, hints, cell, options) => {
124
- if (isEmptyCell(cell)) return field.nullable ? ok(null) : problem("is empty, and it is required");
125
+ if (isEmptyCell(cell)) return field.nullable ? ok(null) : problem(text("emptyRequired"));
125
126
  switch (field.type) {
126
127
  case "integer":
127
128
  case "bigint": return integerValue(field, cell, options);
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  import { cellText } from "./sheet-cell.js";
2
3
  //#region src/app/import/failed-rows.ts
3
4
  const csvField = (text) => /[",\r\n]/.test(text) ? `"${text.replace(/"/g, "\"\"")}"` : text;
@@ -13,8 +14,8 @@ const failedRowsCsv = (headers, rows) => {
13
14
  ]);
14
15
  return `${[[
15
16
  ...headers,
16
- "Line",
17
- "Problems"
17
+ text("line"),
18
+ text("problems")
18
19
  ], ...lines].map((fields) => fields.map(csvField).join(",")).join("\r\n")}\r\n`;
19
20
  };
20
21
  //#endregion
@@ -1,6 +1,7 @@
1
1
  import { Button } from "../../primitives/button.js";
2
2
  import { useAdminMeta, usePermissions } from "../meta-gate.js";
3
3
  import { can } from "../permissions.js";
4
+ import { text } from "./messages.js";
4
5
  import { ImportDialog } from "./import-dialog.js";
5
6
  import { useState } from "react";
6
7
  import { Upload } from "lucide-react";
@@ -18,7 +19,7 @@ const ImportButton = ({ model }) => {
18
19
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Button, {
19
20
  onClick: () => setOpen(true),
20
21
  "aria-haspopup": "dialog",
21
- children: [/* @__PURE__ */ jsx(Upload, { className: "size-3.5" }), "Import"]
22
+ children: [/* @__PURE__ */ jsx(Upload, { className: "size-3.5" }), text("import")]
22
23
  }), open && /* @__PURE__ */ jsx(ImportDialog, {
23
24
  model,
24
25
  resources,
@@ -3,6 +3,7 @@ import { Spinner } from "../../primitives/spinner.js";
3
3
  import { Button } from "../../primitives/button.js";
4
4
  import { saveFile } from "../../lib/save-file.js";
5
5
  import { displayValue } from "../../live/display-value.js";
6
+ import { text } from "./messages.js";
6
7
  import { failedRowsCsv } from "./failed-rows.js";
7
8
  import { ImportCounts } from "./import-counts.js";
8
9
  import { fieldLabel, relationModel } from "./mapping.js";
@@ -12,40 +13,27 @@ import { jsx, jsxs } from "react/jsx-runtime";
12
13
  /** How many rows with problems the check lists; the file of them holds every one. */
13
14
  const listedProblems = 50;
14
15
  /** Saves the rows with their problems as a CSV named after the imported file. */
15
- const downloadRows = (fileName, headers, rows) => saveFile(failedRowsCsv(headers, rows), `${(fileName ?? "import").replace(/\.[^.]+$/, "")} - not imported.csv`, "text/csv");
16
+ const downloadRows = (fileName, headers, rows) => saveFile(failedRowsCsv(headers, rows), text("notImportedFile", (fileName ?? "import").replace(/\.[^.]+$/, "")), "text/csv");
16
17
  /** The rows with what keeps each from being imported, at most `listedProblems` of them. */
17
18
  const ProblemList = ({ rows }) => /* @__PURE__ */ jsxs("ul", {
18
- "aria-label": "Rows not imported",
19
+ "aria-label": text("rowsNotImported"),
19
20
  className: "flex flex-col gap-1 text-xs",
20
21
  children: [rows.slice(0, listedProblems).map((row) => /* @__PURE__ */ jsxs("li", { children: [
21
- /* @__PURE__ */ jsxs("span", {
22
+ /* @__PURE__ */ jsx("span", {
22
23
  className: "font-medium",
23
- children: [
24
- "Row ",
25
- row.line,
26
- ":"
27
- ]
24
+ children: text("rowProblems", row.line)
28
25
  }),
29
26
  " ",
30
27
  row.problems.join("; ")
31
- ] }, row.line)), rows.length > listedProblems && /* @__PURE__ */ jsxs("li", {
28
+ ] }, row.line)), rows.length > listedProblems && /* @__PURE__ */ jsx("li", {
32
29
  className: "text-muted-foreground",
33
- children: [
34
- "And ",
35
- (rows.length - listedProblems).toLocaleString(formatTag()),
36
- " more rows, all in the file."
37
- ]
30
+ children: text("andMore", (rows.length - listedProblems).toLocaleString(formatTag()))
38
31
  })]
39
32
  });
40
33
  const ImportChecking = ({ rows }) => /* @__PURE__ */ jsxs("p", {
41
34
  className: "flex items-center gap-2 text-muted-foreground",
42
35
  role: "status",
43
- children: [
44
- /* @__PURE__ */ jsx(Spinner, { className: "size-3.5" }),
45
- "Checking rows… ",
46
- rows.toLocaleString(formatTag()),
47
- " read"
48
- ]
36
+ children: [/* @__PURE__ */ jsx(Spinner, { className: "size-3.5" }), text("checkingRows", rows.toLocaleString(formatTag()))]
49
37
  });
50
38
  /**
51
39
  * What the import would do, before it writes anything: how many rows create or update a record, the first rows as they
@@ -58,24 +46,24 @@ const ImportCheckStep = ({ check, model, resources, views, columns, headers, fil
58
46
  className: "flex flex-col gap-4",
59
47
  children: [
60
48
  /* @__PURE__ */ jsx(ImportCounts, {
61
- label: "Check",
49
+ label: text("check"),
62
50
  counts: [
63
- ["Rows", check.rows],
64
- ["To create", check.creates],
65
- ["To update", check.updates],
66
- ["With problems", check.problems.length]
51
+ [text("rows"), check.rows],
52
+ [text("toCreate"), check.creates],
53
+ [text("toUpdate"), check.updates],
54
+ [text("withProblems"), check.problems.length]
67
55
  ]
68
56
  }),
69
57
  check.preview.length > 0 && /* @__PURE__ */ jsx("div", {
70
58
  className: "overflow-x-auto rounded-lg border",
71
59
  children: /* @__PURE__ */ jsxs("table", {
72
- "aria-label": "First rows",
60
+ "aria-label": text("firstRows"),
73
61
  className: "w-full text-left text-xs",
74
62
  children: [/* @__PURE__ */ jsx("thead", {
75
63
  className: "bg-muted text-muted-foreground",
76
64
  children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
77
65
  className: "px-2 py-1.5 font-medium",
78
- children: "Row"
66
+ children: text("row")
79
67
  }), shown.map((field) => /* @__PURE__ */ jsx("th", {
80
68
  className: "whitespace-nowrap px-2 py-1.5 font-medium",
81
69
  children: fieldLabel(view, field.name)
@@ -101,21 +89,17 @@ const ImportCheckStep = ({ check, model, resources, views, columns, headers, fil
101
89
  className: "flex items-center justify-between gap-2",
102
90
  children: [/* @__PURE__ */ jsx("h3", {
103
91
  className: "font-medium",
104
- children: "Rows with problems, which are skipped"
92
+ children: text("problemRows")
105
93
  }), /* @__PURE__ */ jsxs(Button, {
106
94
  size: "sm",
107
95
  onClick: () => downloadRows(fileName, headers, check.problems),
108
- children: [/* @__PURE__ */ jsx(Download, { className: "size-3.5" }), "Download"]
96
+ children: [/* @__PURE__ */ jsx(Download, { className: "size-3.5" }), text("download")]
109
97
  })]
110
98
  }), /* @__PURE__ */ jsx(ProblemList, { rows: check.problems })]
111
99
  }),
112
- /* @__PURE__ */ jsxs("p", {
100
+ /* @__PURE__ */ jsx("p", {
113
101
  className: "text-xs text-muted-foreground",
114
- children: [
115
- "Rows are saved in batches of ",
116
- 100,
117
- ". Cancelling, or an error, keeps the batches already saved; a row the server refuses is listed and the others are saved."
118
- ]
102
+ children: text("batches", 100)
119
103
  })
120
104
  ]
121
105
  });
@@ -3,6 +3,7 @@ import { Button } from "../../primitives/button.js";
3
3
  import { humanize } from "../../live/naming.js";
4
4
  import { ErrorBanner } from "../error-banner.js";
5
5
  import { ResponsiveDialog } from "../../primitives/responsive-dialog.js";
6
+ import { text } from "./messages.js";
6
7
  import { unmappedRequired } from "./mapping.js";
7
8
  import { ImportCheckStep, ImportChecking } from "./import-check-step.js";
8
9
  import { ImportFileStep } from "./import-file-step.js";
@@ -11,7 +12,7 @@ import { ImportProgressStep, ImportSummary } from "./import-result-step.js";
11
12
  import { useImportFlow } from "./use-import-flow.js";
12
13
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
14
  //#region src/app/import/import-dialog.tsx
14
- const errorTitle = (flow) => flow.step.kind === "file" ? "Could not read the file" : "Could not check the rows";
15
+ const errorTitle = (flow) => flow.step.kind === "file" ? text("couldNotReadFile") : text("couldNotCheck");
15
16
  const readyToCheck = (flow) => {
16
17
  if (flow.columns.length === 0) return false;
17
18
  if (flow.mode === "create") return unmappedRequired("create", flow.fields, flow.columns).length === 0;
@@ -39,7 +40,7 @@ const ImportDialog = ({ model, resources, views, canUpdate, onClose, sourceFor }
39
40
  };
40
41
  const cancelButton = /* @__PURE__ */ jsx(Button, {
41
42
  onClick: close,
42
- children: "Cancel"
43
+ children: text("cancel")
43
44
  });
44
45
  const actions = () => {
45
46
  switch (step.kind) {
@@ -47,49 +48,44 @@ const ImportDialog = ({ model, resources, views, canUpdate, onClose, sourceFor }
47
48
  variant: "primary",
48
49
  loading: step.reading,
49
50
  onClick: flow.openSheet,
50
- children: "Next"
51
+ children: text("next")
51
52
  })] });
52
53
  case "map": return /* @__PURE__ */ jsxs(Fragment, { children: [cancelButton, /* @__PURE__ */ jsx(Button, {
53
54
  variant: "primary",
54
55
  disabled: !readyToCheck(flow),
55
56
  onClick: flow.check,
56
- children: "Check rows"
57
+ children: text("checkRows")
57
58
  })] });
58
59
  case "checking": return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
59
60
  onClick: flow.back,
60
- children: "Back"
61
+ children: text("back")
61
62
  }), /* @__PURE__ */ jsx(Button, {
62
63
  variant: "primary",
63
64
  loading: true,
64
- children: "Checking"
65
+ children: text("checking")
65
66
  })] });
66
67
  case "checked": {
67
68
  const rows = step.check.creates + step.check.updates;
68
69
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
69
70
  onClick: flow.back,
70
- children: "Back"
71
- }), /* @__PURE__ */ jsxs(Button, {
71
+ children: text("back")
72
+ }), /* @__PURE__ */ jsx(Button, {
72
73
  variant: "primary",
73
74
  disabled: rows === 0,
74
75
  onClick: () => flow.start(rows),
75
- children: [
76
- "Import ",
77
- rows.toLocaleString(formatTag()),
78
- " ",
79
- rows === 1 ? "row" : "rows"
80
- ]
76
+ children: text("importRows", rows.toLocaleString(formatTag()), rows)
81
77
  })] });
82
78
  }
83
79
  case "importing": return /* @__PURE__ */ jsx(Button, {
84
80
  variant: "danger",
85
81
  disabled: step.cancelling,
86
82
  onClick: flow.cancel,
87
- children: "Cancel import"
83
+ children: text("cancelImport")
88
84
  });
89
85
  case "done": return /* @__PURE__ */ jsx(Button, {
90
86
  variant: "primary",
91
87
  onClick: onClose,
92
- children: "Done"
88
+ children: text("done")
93
89
  });
94
90
  }
95
91
  };
@@ -136,7 +132,7 @@ const ImportDialog = ({ model, resources, views, canUpdate, onClose, sourceFor }
136
132
  return /* @__PURE__ */ jsx(ResponsiveDialog, {
137
133
  open: true,
138
134
  wide: true,
139
- title: `Import ${plural.toLowerCase()}`,
135
+ title: text("importPlural", plural),
140
136
  description: flow.file && step.kind !== "file" ? flow.file.name : void 0,
141
137
  onClose: close,
142
138
  actions: actions(),
@@ -1,4 +1,5 @@
1
1
  import { Spinner } from "../../primitives/spinner.js";
2
+ import { text } from "./messages.js";
2
3
  import { ImportSelect } from "./import-select.js";
3
4
  import { useId } from "react";
4
5
  import { FileSpreadsheet } from "lucide-react";
@@ -16,7 +17,7 @@ const ImportFileStep = ({ file, sheets, sheet, reading, onChoose, onSheet }) =>
16
17
  /* @__PURE__ */ jsx("label", {
17
18
  htmlFor: id,
18
19
  className: "font-medium",
19
- children: "File"
20
+ children: text("file")
20
21
  }),
21
22
  /* @__PURE__ */ jsx("input", {
22
23
  id,
@@ -31,25 +32,20 @@ const ImportFileStep = ({ file, sheets, sheet, reading, onChoose, onSheet }) =>
31
32
  }),
32
33
  /* @__PURE__ */ jsx("p", {
33
34
  className: "text-xs text-muted-foreground",
34
- children: "An Excel workbook (.xlsx) or a CSV file saved as UTF-8. Its first row names the columns."
35
+ children: text("fileHint")
35
36
  })
36
37
  ]
37
38
  }),
38
39
  reading && /* @__PURE__ */ jsxs("p", {
39
40
  className: "flex items-center gap-2 text-muted-foreground",
40
41
  role: "status",
41
- children: [
42
- /* @__PURE__ */ jsx(Spinner, { className: "size-3.5" }),
43
- "Reading ",
44
- file?.name ?? "the file",
45
- "…"
46
- ]
42
+ children: [/* @__PURE__ */ jsx(Spinner, { className: "size-3.5" }), text("reading", file?.name ?? text("theFile"))]
47
43
  }),
48
44
  !reading && sheets.length > 1 && /* @__PURE__ */ jsxs("div", {
49
45
  className: "flex items-end gap-3",
50
46
  children: [/* @__PURE__ */ jsx(FileSpreadsheet, { className: "mb-2 size-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx(ImportSelect, {
51
47
  className: "flex-1",
52
- label: "Sheet",
48
+ label: text("sheet"),
53
49
  value: sheet ?? "",
54
50
  options: sheets.map((name) => ({
55
51
  value: name,