@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
@@ -3,33 +3,34 @@ import { formatApproxRows, formatInt } from "../../format/number.js";
3
3
  import { Button } from "../../primitives/button.js";
4
4
  import { Checkbox } from "../../primitives/checkbox.js";
5
5
  import { ResponsiveDialog } from "../../primitives/responsive-dialog.js";
6
+ import { text } from "./messages.js";
6
7
  import { exportLimit } from "./export-options.js";
7
8
  import { useState } from "react";
8
9
  import { ArrowDown, ArrowUp } from "lucide-react";
9
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
11
  //#region src/app/export/export-dialog.tsx
11
- const formatOptions = [{
12
+ const formatOptions = () => [{
12
13
  value: "xlsx",
13
- label: "Excel (.xlsx)"
14
+ label: text("excel")
14
15
  }, {
15
16
  value: "csv",
16
- label: "CSV"
17
+ label: text("csv")
17
18
  }];
18
- const modeOptions = [{
19
+ const modeOptions = () => [{
19
20
  value: "shown",
20
- label: "As shown"
21
+ label: text("asShown")
21
22
  }, {
22
23
  value: "raw",
23
- label: "Raw values"
24
+ label: text("rawValues")
24
25
  }];
25
26
  const modeHelp = {
26
- shown: "Labels, dates and numbers as the list shows them.",
27
- raw: "Values as they are stored, such as keys and codes."
27
+ shown: "shownHint",
28
+ raw: "rawHint"
28
29
  };
29
30
  const countText = (count) => {
30
- if (count.kind === "counting") return "Counting rows…";
31
+ if (count.kind === "counting") return text("counting");
31
32
  if (count.kind === "exact") return formatApproxRows(count.rows);
32
- return count.rows > 1e4 ? formatApproxRows(count.rows) : "more than 10,000 rows";
33
+ return count.rows > 1e4 ? formatApproxRows(count.rows) : text("moreThan");
33
34
  };
34
35
  /** The fewest rows the list finds, as far as the count tells: an estimate stands only above 10,000. */
35
36
  const countedRows = (count) => {
@@ -66,7 +67,7 @@ const ColumnPicker = ({ fields, columns, onChange }) => {
66
67
  const ordered = [...columns, ...fields.map((field) => field.name).filter((name) => !columns.includes(name))];
67
68
  return /* @__PURE__ */ jsx("ul", {
68
69
  className: "divide-y self-stretch rounded-md border",
69
- "aria-label": "Columns",
70
+ "aria-label": text("columns"),
70
71
  children: ordered.map((name) => {
71
72
  const chosen = columns.includes(name);
72
73
  const index = columns.indexOf(name);
@@ -88,7 +89,7 @@ const ColumnPicker = ({ fields, columns, onChange }) => {
88
89
  size: "sm",
89
90
  variant: "ghost",
90
91
  className: "min-h-8 px-2",
91
- "aria-label": `Move ${labels[name]} up`,
92
+ "aria-label": text("moveUp", labels[name]),
92
93
  disabled: index === 0,
93
94
  onClick: () => onChange(move(columns, name, -1)),
94
95
  children: /* @__PURE__ */ jsx(ArrowUp, { className: "size-3.5" })
@@ -96,7 +97,7 @@ const ColumnPicker = ({ fields, columns, onChange }) => {
96
97
  size: "sm",
97
98
  variant: "ghost",
98
99
  className: "min-h-8 px-2",
99
- "aria-label": `Move ${labels[name]} down`,
100
+ "aria-label": text("moveDown", labels[name]),
100
101
  disabled: index === columns.length - 1,
101
102
  onClick: () => onChange(move(columns, name, 1)),
102
103
  children: /* @__PURE__ */ jsx(ArrowDown, { className: "size-3.5" })
@@ -116,22 +117,22 @@ const ExportDialog = ({ plural, view, fields, initialColumns, count, selected, o
116
117
  const [columns, setColumns] = useState(initialColumns);
117
118
  const [mode, setMode] = useState("shown");
118
119
  const limit = exportLimit(view, format);
119
- const rows = scope === "selected" ? `${formatInt(selected ?? 0)} selected ${selected === 1 ? "row" : "rows"}` : countText(count);
120
+ const rows = scope === "selected" ? text("selectedRows", formatInt(selected ?? 0), selected ?? 0) : countText(count);
120
121
  const overLimit = (scope === "selected" ? selected ?? 0 : countedRows(count) ?? 0) > limit;
121
122
  const scopeOptions = [{
122
123
  value: "list",
123
- label: "Rows in the list"
124
+ label: text("rowsInList")
124
125
  }, {
125
126
  value: "selected",
126
- label: `Selected (${formatInt(selected ?? 0)})`
127
+ label: text("selected", formatInt(selected ?? 0))
127
128
  }];
128
129
  return /* @__PURE__ */ jsx(ResponsiveDialog, {
129
- title: `Export ${plural.toLowerCase()}`,
130
+ title: text("exportPlural", plural),
130
131
  open,
131
132
  onClose,
132
133
  actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
133
134
  onClick: onClose,
134
- children: "Cancel"
135
+ children: text("cancel")
135
136
  }), /* @__PURE__ */ jsx(Button, {
136
137
  variant: "primary",
137
138
  disabled: columns.length === 0,
@@ -141,45 +142,45 @@ const ExportDialog = ({ plural, view, fields, initialColumns, count, selected, o
141
142
  columns,
142
143
  mode
143
144
  }),
144
- children: "Export"
145
+ children: text("export")
145
146
  })] }),
146
147
  children: /* @__PURE__ */ jsxs("div", {
147
148
  className: "flex flex-col gap-4 text-[13px]",
148
149
  children: [
149
150
  /* @__PURE__ */ jsx(Setting, {
150
- label: "Format",
151
+ label: text("format"),
151
152
  children: /* @__PURE__ */ jsx(Segmented, {
152
153
  size: "md",
153
- label: "Format",
154
+ label: text("format"),
154
155
  value: format,
155
- options: formatOptions,
156
+ options: formatOptions(),
156
157
  onChange: setFormat
157
158
  })
158
159
  }),
159
160
  selected !== void 0 && selected > 0 && /* @__PURE__ */ jsx(Setting, {
160
- label: "Rows",
161
- help: scope === "list" ? "With the list’s filters, search and sort." : void 0,
161
+ label: text("rows"),
162
+ help: scope === "list" ? text("listHint") : void 0,
162
163
  children: /* @__PURE__ */ jsx(Segmented, {
163
164
  size: "md",
164
- label: "Rows",
165
+ label: text("rows"),
165
166
  value: scope,
166
167
  options: scopeOptions,
167
168
  onChange: setScope
168
169
  })
169
170
  }),
170
171
  /* @__PURE__ */ jsx(Setting, {
171
- label: "Values",
172
- help: modeHelp[mode],
172
+ label: text("values"),
173
+ help: text(modeHelp[mode]),
173
174
  children: /* @__PURE__ */ jsx(Segmented, {
174
175
  size: "md",
175
- label: "Values",
176
+ label: text("values"),
176
177
  value: mode,
177
- options: modeOptions,
178
+ options: modeOptions(),
178
179
  onChange: setMode
179
180
  })
180
181
  }),
181
182
  /* @__PURE__ */ jsx(Setting, {
182
- label: "Columns",
183
+ label: text("columns"),
183
184
  children: /* @__PURE__ */ jsx(ColumnPicker, {
184
185
  fields,
185
186
  columns,
@@ -189,12 +190,7 @@ const ExportDialog = ({ plural, view, fields, initialColumns, count, selected, o
189
190
  /* @__PURE__ */ jsxs("p", {
190
191
  role: "status",
191
192
  className: "text-muted-foreground",
192
- children: [
193
- "Exports ",
194
- rows,
195
- ".",
196
- overLimit && ` A file holds at most ${formatInt(limit)} rows; the rest are left out.`
197
- ]
193
+ children: [text("exports", rows), overLimit && text("overLimit", formatInt(limit))]
198
194
  })
199
195
  ]
200
196
  })
@@ -204,18 +200,18 @@ const ExportDialog = ({ plural, view, fields, initialColumns, count, selected, o
204
200
  const ExportProgressDialog = ({ plural, done, total, onCancel }) => {
205
201
  const share = total ? Math.min(1, done / total) : void 0;
206
202
  return /* @__PURE__ */ jsx(ResponsiveDialog, {
207
- title: `Exporting ${plural.toLowerCase()}`,
203
+ title: text("exporting", plural),
208
204
  open: true,
209
205
  onClose: onCancel,
210
206
  actions: /* @__PURE__ */ jsx(Button, {
211
207
  onClick: onCancel,
212
- children: "Cancel"
208
+ children: text("cancel")
213
209
  }),
214
210
  children: /* @__PURE__ */ jsxs("div", {
215
211
  className: "flex flex-col gap-2 text-[13px]",
216
212
  children: [/* @__PURE__ */ jsx("div", {
217
213
  role: "progressbar",
218
- "aria-label": "Rows exported",
214
+ "aria-label": text("rowsExported"),
219
215
  "aria-valuemin": 0,
220
216
  "aria-valuemax": total,
221
217
  "aria-valuenow": done,
@@ -226,7 +222,7 @@ const ExportProgressDialog = ({ plural, done, total, onCancel }) => {
226
222
  })
227
223
  }), /* @__PURE__ */ jsx("p", {
228
224
  className: "text-muted-foreground",
229
- children: total ? `${formatInt(done)} of ${formatInt(total)} rows` : `${formatInt(done)} rows`
225
+ children: total ? text("doneOf", formatInt(done), formatInt(total)) : text("done", formatInt(done))
230
226
  })]
231
227
  })
232
228
  });
@@ -1,8 +1,10 @@
1
+ import { setFallbackLocale } from "../../locale/active-locale.js";
1
2
  import { createBuilderHandler } from "./builder-port.js";
2
3
  //#region src/app/export/export-worker.ts
3
4
  const handle = createBuilderHandler();
4
5
  const scope = self;
5
6
  scope.onmessage = (event) => {
7
+ if (event.data.kind === "start") setFallbackLocale(event.data.locale);
6
8
  const bytes = handle(event.data);
7
9
  scope.postMessage({ bytes }, [bytes.buffer]);
8
10
  };
@@ -0,0 +1,78 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/export/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ excel: "Excel (.xlsx)",
6
+ csv: "CSV",
7
+ asShown: "As shown",
8
+ rawValues: "Raw values",
9
+ shownHint: "Labels, dates and numbers as the list shows them.",
10
+ rawHint: "Values as they are stored, such as keys and codes.",
11
+ counting: "Counting rows…",
12
+ moreThan: "more than 10,000 rows",
13
+ columns: "Columns",
14
+ moveUp: (label) => `Move ${label} up`,
15
+ moveDown: (label) => `Move ${label} down`,
16
+ selectedRows: (count, rows) => `${count} selected ${rows === 1 ? "row" : "rows"}`,
17
+ rowsInList: "Rows in the list",
18
+ selected: (count) => `Selected (${count})`,
19
+ exportPlural: (plural) => `Export ${plural.toLowerCase()}`,
20
+ cancel: "Cancel",
21
+ export: "Export",
22
+ format: "Format",
23
+ rows: "Rows",
24
+ listHint: "With the list’s filters, search and sort.",
25
+ values: "Values",
26
+ exports: (rows) => `Exports ${rows}.`,
27
+ overLimit: (limit) => ` A file holds at most ${limit} rows; the rest are left out.`,
28
+ exporting: (plural) => `Exporting ${plural.toLowerCase()}`,
29
+ rowsExported: "Rows exported",
30
+ doneOf: (done, total) => `${done} of ${total} rows`,
31
+ done: (done) => `${done} rows`,
32
+ exported: (count, plural) => `Exported ${count} ${plural.toLowerCase()}`,
33
+ truncated: (name, count) => `${name}: the first ${count} rows, as many as a file holds.`,
34
+ couldNotExport: (plural) => `Could not export ${plural.toLowerCase()}`,
35
+ cancelled: "The export was cancelled",
36
+ couldNotBuild: (message) => `The export could not be built: ${message}`,
37
+ workbook: "Excel workbook",
38
+ csvFile: "CSV file"
39
+ },
40
+ nl: {
41
+ excel: "Excel (.xlsx)",
42
+ csv: "CSV",
43
+ asShown: "Zoals getoond",
44
+ rawValues: "Ruwe waarden",
45
+ shownHint: "Labels, datums en getallen zoals de lijst ze toont.",
46
+ rawHint: "Waarden zoals ze zijn opgeslagen, zoals sleutels en codes.",
47
+ counting: "Rijen tellen…",
48
+ moreThan: "meer dan 10.000 rijen",
49
+ columns: "Kolommen",
50
+ moveUp: (label) => `${label} omhoog`,
51
+ moveDown: (label) => `${label} omlaag`,
52
+ selectedRows: (count, rows) => `${count} geselecteerde ${rows === 1 ? "rij" : "rijen"}`,
53
+ rowsInList: "Rijen in de lijst",
54
+ selected: (count) => `Geselecteerd (${count})`,
55
+ exportPlural: (plural) => `${plural} exporteren`,
56
+ cancel: "Annuleren",
57
+ export: "Exporteren",
58
+ format: "Formaat",
59
+ rows: "Rijen",
60
+ listHint: "Met de filters, zoekopdracht en sortering van de lijst.",
61
+ values: "Waarden",
62
+ exports: (rows) => `Exporteert ${rows}.`,
63
+ overLimit: (limit) => ` Een bestand bevat hoogstens ${limit} rijen; de rest blijft weg.`,
64
+ exporting: (plural) => `${plural} exporteren`,
65
+ rowsExported: "Geëxporteerde rijen",
66
+ doneOf: (done, total) => `${done} van ${total} rijen`,
67
+ done: (done) => `${done} rijen`,
68
+ exported: (count, plural) => `${count} ${plural.toLowerCase()} geëxporteerd`,
69
+ truncated: (name, count) => `${name}: de eerste ${count} rijen, zoveel als een bestand bevat.`,
70
+ couldNotExport: (plural) => `Kon ${plural.toLowerCase()} niet exporteren`,
71
+ cancelled: "De export is geannuleerd",
72
+ couldNotBuild: (message) => `De export kon niet worden gemaakt: ${message}`,
73
+ workbook: "Excel-werkmap",
74
+ csvFile: "CSV-bestand"
75
+ }
76
+ });
77
+ //#endregion
78
+ export { text };
@@ -1,4 +1,6 @@
1
- const aborted = () => new DOMException("The export was cancelled", "AbortError");
1
+ import { activeLocale } from "../../locale/active-locale.js";
2
+ import { text } from "./messages.js";
3
+ const aborted = () => new DOMException(text("cancelled"), "AbortError");
2
4
  /**
3
5
  * Reads the rows through the list API, page by page with its page tokens, and has the port build the file from each;
4
6
  * each piece is written to the sink as it is built, so only a page is held at a time. A cancel stops at the next page
@@ -10,7 +12,8 @@ const runExport = async ({ client, resource, filters, orderBy, plan, labels, lim
10
12
  try {
11
13
  await sink.write(await port.send({
12
14
  kind: "start",
13
- plan
15
+ plan,
16
+ locale: activeLocale()
14
17
  }));
15
18
  for (const [index, filter] of filters.entries()) {
16
19
  let pageToken = "";
@@ -1,14 +1,15 @@
1
+ import { text } from "./messages.js";
1
2
  //#region src/app/export/sink.ts
2
3
  const types = {
3
4
  xlsx: {
4
5
  mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
5
6
  extension: ".xlsx",
6
- description: "Excel workbook"
7
+ description: "workbook"
7
8
  },
8
9
  csv: {
9
10
  mime: "text/csv",
10
11
  extension: ".csv",
11
- description: "CSV file"
12
+ description: "csvFile"
12
13
  }
13
14
  };
14
15
  const unsafeName = /[\\/:*?"<>|\u0000-\u001f]/g;
@@ -70,7 +71,7 @@ const pickFileSink = async (name, format) => {
70
71
  handle = await pick({
71
72
  suggestedName: name,
72
73
  types: [{
73
- description: type.description,
74
+ description: text(type.description),
74
75
  accept: { [type.mime]: [type.extension] }
75
76
  }]
76
77
  });
@@ -3,6 +3,7 @@ import { keys } from "../../data/query-keys.js";
3
3
  import { useClient } from "../../data/api-provider.js";
4
4
  import { useToast } from "../../toasts/toast-provider.js";
5
5
  import { listLabels } from "../../live/relation-labels.js";
6
+ import { text } from "./messages.js";
6
7
  import { runExport } from "./run-export.js";
7
8
  import { defaultExportColumns, exportColumns, exportHeaders, exportLimit, exportableFields, exportsSelection, selectionFilters } from "./export-options.js";
8
9
  import { createWorkerPort } from "./builder-port.js";
@@ -90,15 +91,15 @@ const useExportFlow = ({ model, view, resources, views, plural, filter, orderBy,
90
91
  });
91
92
  toast.show({
92
93
  state: "success",
93
- title: `Exported ${formatInt(result.rows)} ${plural.toLowerCase()}`,
94
- description: result.truncated ? `${name}: the first ${formatInt(result.rows)} rows, as many as a file holds.` : name
94
+ title: text("exported", formatInt(result.rows), plural),
95
+ description: result.truncated ? text("truncated", name, formatInt(result.rows)) : name
95
96
  });
96
97
  } catch (error) {
97
98
  if (error instanceof DOMException && error.name === "AbortError") return;
98
99
  if (!isApiError(error)) throw error;
99
100
  toast.show({
100
101
  state: "error",
101
- title: `Could not export ${plural.toLowerCase()}`,
102
+ title: text("couldNotExport", plural),
102
103
  description: error.message
103
104
  });
104
105
  } finally {
@@ -2,6 +2,7 @@ import { cn } from "../lib/cn.js";
2
2
  import { useFieldControl } from "../primitives/field-control.js";
3
3
  import { Input } from "../primitives/input.js";
4
4
  import { buttonClass } from "../primitives/button.js";
5
+ import { text } from "./messages.js";
5
6
  import { humanize } from "../live/naming.js";
6
7
  import { isMarkdown } from "../markdown/markdown-hint.js";
7
8
  import { contactFormat, contactInputProps } from "../live/contact-links.js";
@@ -64,8 +65,8 @@ const BuiltInEditor = ({ field, hints, value, stored, relationLabel, relationTo,
64
65
  children: picker
65
66
  }), /* @__PURE__ */ jsx(AppLink, {
66
67
  to: relationTo,
67
- "aria-label": `Open ${relationLabel ?? String(value)}`,
68
- title: "Open",
68
+ "aria-label": text("openLabel", relationLabel ?? String(value)),
69
+ title: text("open"),
69
70
  className: cn(buttonClass({ variant: "ghost" }), "w-8 px-0"),
70
71
  children: /* @__PURE__ */ jsx(ArrowUpRight, { className: "size-4" })
71
72
  })]
@@ -118,7 +119,7 @@ const BuiltInEditor = ({ field, hints, value, stored, relationLabel, relationTo,
118
119
  className: "h-8 w-full rounded-md border border-border-strong bg-background px-2 shadow-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20",
119
120
  children: [value === "" && /* @__PURE__ */ jsx("option", {
120
121
  value: "",
121
- children: "Choose..."
122
+ children: text("choose")
122
123
  }), (field.enumValues ?? []).map((option) => /* @__PURE__ */ jsx("option", {
123
124
  value: option,
124
125
  children: enumLabel(option, hints?.valueLabels)
@@ -0,0 +1,8 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/field-types/messages.ts
3
+ const text = defineMessages({
4
+ en: { fieldType: (name) => `The field type ${name}` },
5
+ nl: { fieldType: (name) => `Het veldtype ${name}` }
6
+ });
7
+ //#endregion
8
+ export { text };
@@ -1,4 +1,5 @@
1
1
  import { CustomBoundary } from "../pages/custom-boundary.js";
2
+ import { text } from "./messages.js";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
  //#region src/app/field-types/widget-part.tsx
4
5
  const partOf = ({ shown, part, field, value, onChange, invalid = false }) => {
@@ -24,7 +25,7 @@ const WidgetPart = ({ fallback, ...rest }) => {
24
25
  const element = partOf(rest);
25
26
  if (!element) return fallback;
26
27
  return /* @__PURE__ */ jsx(CustomBoundary, {
27
- name: `The field type ${rest.shown.name}`,
28
+ name: text("fieldType", rest.shown.name),
28
29
  fallback,
29
30
  children: element
30
31
  });
@@ -1,6 +1,7 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { Button } from "../primitives/button.js";
3
3
  import { useClient } from "../data/api-provider.js";
4
+ import { text } from "./messages.js";
4
5
  import { humanize } from "../live/naming.js";
5
6
  import { acceptOf, fileShown, formatSize, isPreviewable, shownImage } from "../live/file-values.js";
6
7
  import { ErrorBanner } from "./error-banner.js";
@@ -32,7 +33,7 @@ const FileSummary = ({ value }) => {
32
33
  });
33
34
  if ("missing" in shown) return /* @__PURE__ */ jsx("div", {
34
35
  className: "py-1.5 text-danger-text",
35
- children: "The file is missing from storage"
36
+ children: text("fileMissing")
36
37
  });
37
38
  const url = "url" in shown ? shown.url : void 0;
38
39
  const corrected = "corrected" in shown ? shown.corrected : void 0;
@@ -97,33 +98,29 @@ const FileField = ({ field, hints, resource, value, locked, invalid, onChange })
97
98
  type: "file",
98
99
  hidden: true,
99
100
  accept: acceptOf(field),
100
- "aria-label": `Choose a file for ${label}`,
101
+ "aria-label": text("chooseFileFor", label),
101
102
  onChange: (event) => choose(event.target.files)
102
103
  });
103
104
  const error = upload.error && /* @__PURE__ */ jsx("div", {
104
105
  className: "mt-2",
105
106
  children: /* @__PURE__ */ jsx(ErrorBanner, {
106
- title: "Not uploaded",
107
+ title: text("notUploaded"),
107
108
  error: upload.error
108
109
  })
109
110
  });
110
111
  if (upload.isPending) return /* @__PURE__ */ jsxs("div", {
111
112
  className: "py-1.5",
112
113
  role: "status",
113
- "aria-label": `Uploading ${label}`,
114
+ "aria-label": text("uploadingLabel", label),
114
115
  children: [/* @__PURE__ */ jsx("div", {
115
116
  className: "h-1.5 overflow-hidden rounded-full bg-muted",
116
117
  children: /* @__PURE__ */ jsx("div", {
117
118
  className: "h-full rounded-full bg-primary transition-[width]",
118
119
  style: { width: `${Math.round((progress ?? 0) * 100)}%` }
119
120
  })
120
- }), /* @__PURE__ */ jsxs("span", {
121
+ }), /* @__PURE__ */ jsx("span", {
121
122
  className: "text-xs text-muted-foreground",
122
- children: [
123
- "Uploading… ",
124
- Math.round((progress ?? 0) * 100),
125
- "%"
126
- ]
123
+ children: text("uploading", Math.round((progress ?? 0) * 100))
127
124
  })]
128
125
  });
129
126
  if (fileShown(value)) return /* @__PURE__ */ jsxs("div", { children: [
@@ -137,13 +134,13 @@ const FileField = ({ field, hints, resource, value, locked, invalid, onChange })
137
134
  /* @__PURE__ */ jsx(Button, {
138
135
  size: "sm",
139
136
  onClick: () => input.current?.click(),
140
- children: "Replace"
137
+ children: text("replace")
141
138
  }),
142
139
  field.nullable && /* @__PURE__ */ jsx(Button, {
143
140
  size: "sm",
144
141
  variant: "ghost",
145
142
  onClick: () => onChange(null),
146
- children: "Remove"
143
+ children: text("remove")
147
144
  })
148
145
  ]
149
146
  }),
@@ -163,12 +160,12 @@ const FileField = ({ field, hints, resource, value, locked, invalid, onChange })
163
160
  /* @__PURE__ */ jsx(Upload, { className: "size-4 shrink-0" }),
164
161
  /* @__PURE__ */ jsx("span", {
165
162
  className: "flex-1",
166
- children: "Drop a file here or"
163
+ children: text("dropFile")
167
164
  }),
168
165
  /* @__PURE__ */ jsxs(Button, {
169
166
  size: "sm",
170
167
  onClick: () => input.current?.click(),
171
- children: [/* @__PURE__ */ jsx(Paperclip, { className: "size-3.5" }), "Choose a file"]
168
+ children: [/* @__PURE__ */ jsx(Paperclip, { className: "size-3.5" }), text("chooseFile")]
172
169
  })
173
170
  ]
174
171
  }),
@@ -7,6 +7,7 @@ import { useAdminMeta, usePermissions } from "../meta-gate.js";
7
7
  import { ErrorBanner } from "../error-banner.js";
8
8
  import { FileImage } from "../../live/file-image.js";
9
9
  import { galleryTiles } from "./gallery-layout.js";
10
+ import { text } from "./messages.js";
10
11
  import { Lightbox } from "./lightbox.js";
11
12
  import { detailFields } from "./detail-fields.js";
12
13
  import { PhotoDetails } from "./photo-details.js";
@@ -63,7 +64,7 @@ const GallerySection = ({ item, record, recordKey }) => {
63
64
  if (photos.error) return /* @__PURE__ */ jsx(Section, {
64
65
  title: item.title,
65
66
  children: /* @__PURE__ */ jsx(ErrorBanner, {
66
- title: "Could not load the photos",
67
+ title: text("couldNotLoad"),
67
68
  error: photos.error
68
69
  })
69
70
  });
@@ -91,7 +92,7 @@ const GallerySection = ({ item, record, recordKey }) => {
91
92
  const last = index === shown - 1 && more > 0;
92
93
  return /* @__PURE__ */ jsxs("button", {
93
94
  type: "button",
94
- "aria-label": last ? `Show all ${count} photos` : `Show photo ${index + 1} of ${count}`,
95
+ "aria-label": last ? text("showAll", count) : text("showPhoto", index + 1, count),
95
96
  onClick: () => setOpen(index),
96
97
  className: cn("group/tile relative overflow-hidden bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary", index === 0 ? "col-span-4 row-span-2" : "max-md:hidden", spans[shown][index]),
97
98
  children: [
@@ -119,7 +120,7 @@ const GallerySection = ({ item, record, recordKey }) => {
119
120
  onIndex: setOpen,
120
121
  onClose: () => setOpen(void 0),
121
122
  details,
122
- detailsTitle: holder && `${holderView?.names?.singular ?? humanize(holder.name)} details`
123
+ detailsTitle: holder && text("holderDetails", holderView?.names?.singular ?? humanize(holder.name))
123
124
  })]
124
125
  });
125
126
  };
@@ -6,6 +6,7 @@ import { FileImage } from "../../live/file-image.js";
6
6
  import { Sheet } from "../../primitives/sheet.js";
7
7
  import { keyStep, steppedPhoto, swipeStep, togglesDetails } from "./gallery-layout.js";
8
8
  import { readDetailsOpen, writeDetailsOpen } from "./details-open-storage.js";
9
+ import { text } from "./messages.js";
9
10
  import { useEffect, useId, useRef, useState } from "react";
10
11
  import { ChevronLeft, ChevronRight, Info, X } from "lucide-react";
11
12
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -18,7 +19,7 @@ const control = "flex size-10 shrink-0 items-center justify-center rounded-full
18
19
  * lower part of a mobile screen; this browser remembers whether they were left shown. The page does not scroll behind
19
20
  * it, and focus returns to what opened it.
20
21
  */
21
- const Lightbox = ({ title, photos, index, onIndex, onClose, details, detailsTitle = "Details" }) => {
22
+ const Lightbox = ({ title, photos, index, onIndex, onClose, details, detailsTitle = text("details") }) => {
22
23
  const panel = useRef(null);
23
24
  const start = useRef(void 0);
24
25
  const sidebarId = useId();
@@ -97,7 +98,7 @@ const Lightbox = ({ title, photos, index, onIndex, onClose, details, detailsTitl
97
98
  }), /* @__PURE__ */ jsx("button", {
98
99
  type: "button",
99
100
  className: control,
100
- "aria-label": "Close",
101
+ "aria-label": text("close"),
101
102
  onClick: onClose,
102
103
  children: /* @__PURE__ */ jsx(X, {
103
104
  className: "size-4",
@@ -116,7 +117,7 @@ const Lightbox = ({ title, photos, index, onIndex, onClose, details, detailsTitl
116
117
  }, image.url), count > 1 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
117
118
  type: "button",
118
119
  className: cn(control, "absolute left-3 top-1/2 -translate-y-1/2 aria-disabled:cursor-default aria-disabled:opacity-40"),
119
- "aria-label": "Previous photo",
120
+ "aria-label": text("previousPhoto"),
120
121
  "aria-disabled": index === 0,
121
122
  onClick: () => step(-1),
122
123
  children: /* @__PURE__ */ jsx(ChevronLeft, {
@@ -126,7 +127,7 @@ const Lightbox = ({ title, photos, index, onIndex, onClose, details, detailsTitl
126
127
  }), /* @__PURE__ */ jsx("button", {
127
128
  type: "button",
128
129
  className: cn(control, "absolute right-3 top-1/2 -translate-y-1/2 aria-disabled:cursor-default aria-disabled:opacity-40"),
129
- "aria-label": "Next photo",
130
+ "aria-label": text("nextPhoto"),
130
131
  "aria-disabled": index === count - 1,
131
132
  onClick: () => step(1),
132
133
  children: /* @__PURE__ */ jsx(ChevronRight, {
@@ -0,0 +1,30 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/gallery/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ couldNotLoad: "Could not load the photos",
6
+ showAll: (count) => `Show all ${count} photos`,
7
+ showPhoto: (index, count) => `Show photo ${index} of ${count}`,
8
+ details: "Details",
9
+ holderDetails: (singular) => `${singular} details`,
10
+ close: "Close",
11
+ previousPhoto: "Previous photo",
12
+ nextPhoto: "Next photo",
13
+ couldNotLoadOne: (singular) => `Could not load the ${singular}`,
14
+ open: (singular) => `Open ${singular}`
15
+ },
16
+ nl: {
17
+ couldNotLoad: "Kon de foto’s niet laden",
18
+ showAll: (count) => `Alle ${count} foto’s tonen`,
19
+ showPhoto: (index, count) => `Foto ${index} van ${count} tonen`,
20
+ details: "Gegevens",
21
+ holderDetails: (singular) => `Gegevens van ${singular.toLowerCase()}`,
22
+ close: "Sluiten",
23
+ previousPhoto: "Vorige foto",
24
+ nextPhoto: "Volgende foto",
25
+ couldNotLoadOne: (singular) => `Kon ${singular} niet laden`,
26
+ open: (singular) => `${singular} openen`
27
+ }
28
+ });
29
+ //#endregion
30
+ export { text };
@@ -6,6 +6,7 @@ import { SpecTable } from "../../record-view/spec-table.js";
6
6
  import { ErrorBanner } from "../error-banner.js";
7
7
  import { fieldAccess } from "../permissions.js";
8
8
  import { FieldValue } from "../pages/field-value.js";
9
+ import { text } from "./messages.js";
9
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
11
  //#region src/app/gallery/photo-details.tsx
11
12
  /**
@@ -17,7 +18,7 @@ const PhotoDetails = ({ model, view, photoKey, fields }) => {
17
18
  const singular = (view?.names?.singular ?? humanize(model.name)).toLowerCase();
18
19
  if (record.isPending) return /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-48" });
19
20
  if (record.error) return /* @__PURE__ */ jsx(ErrorBanner, {
20
- title: `Could not load the ${singular}`,
21
+ title: text("couldNotLoadOne", singular),
21
22
  error: record.error
22
23
  });
23
24
  const shown = fields.filter((name) => fieldAccess(record.data.permissions, name) !== "hidden");
@@ -35,7 +36,7 @@ const PhotoDetails = ({ model, view, photoKey, fields }) => {
35
36
  key: photoKey
36
37
  },
37
38
  className: "mt-3 inline-block font-medium text-primary-text hover:underline",
38
- children: `Open ${singular}`
39
+ children: text("open", singular)
39
40
  })] });
40
41
  };
41
42
  //#endregion