@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,28 +1,29 @@
1
1
  import { Segmented } from "../../primitives/segmented.js";
2
+ import { text } from "./messages.js";
2
3
  import { cellText, columnName } from "./sheet-cell.js";
3
4
  import { fieldLabel, matchFields, relationModel, requiredOnCreate, unmappedRequired } from "./mapping.js";
4
5
  import { ImportSelect } from "./import-select.js";
5
6
  import { TriangleAlert } from "lucide-react";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
  //#region src/app/import/import-mapping-step.tsx
8
- const modes = [
9
+ const modes = () => [
9
10
  {
10
11
  value: "create",
11
- label: "Create"
12
+ label: text("create")
12
13
  },
13
14
  {
14
15
  value: "update",
15
- label: "Update"
16
+ label: text("update")
16
17
  },
17
18
  {
18
19
  value: "upsert",
19
- label: "Create or update"
20
+ label: text("upsert")
20
21
  }
21
22
  ];
22
23
  const modeHelp = {
23
- create: "Each row creates a record.",
24
- update: "Each row updates the record its value finds; a row that finds none is not imported.",
25
- upsert: "Each row updates the record its value finds, or creates one when it finds none."
24
+ create: "createHint",
25
+ update: "updateHint",
26
+ upsert: "upsertHint"
26
27
  };
27
28
  const skip = "";
28
29
  /**
@@ -37,7 +38,7 @@ const ImportMappingStep = ({ flow, model, resources, views, canUpdate }) => {
37
38
  const missing = unmappedRequired(flow.mode, flow.fields, flow.columns);
38
39
  const fieldOptions = [{
39
40
  value: skip,
40
- label: "Do not import"
41
+ label: text("doNotImport")
41
42
  }, ...flow.fields.map((field) => ({
42
43
  value: field.name,
43
44
  label: `${fieldLabel(view, field.name)}${requiredOnCreate(field) ? " *" : ""}`
@@ -55,29 +56,29 @@ const ImportMappingStep = ({ flow, model, resources, views, canUpdate }) => {
55
56
  children: [
56
57
  /* @__PURE__ */ jsx("span", {
57
58
  className: "font-medium",
58
- children: "Rows"
59
+ children: text("rows")
59
60
  }),
60
61
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Segmented, {
61
- label: "What each row does",
62
+ label: text("whatEachRowDoes"),
62
63
  size: "md",
63
64
  value: flow.mode,
64
- options: modes,
65
+ options: modes(),
65
66
  onChange: flow.setMode
66
67
  }) }),
67
68
  /* @__PURE__ */ jsx("p", {
68
69
  className: "text-xs text-muted-foreground",
69
- children: modeHelp[flow.mode]
70
+ children: text(modeHelp[flow.mode])
70
71
  })
71
72
  ]
72
73
  }),
73
74
  flow.mode !== "create" && /* @__PURE__ */ jsxs("div", {
74
75
  className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
75
76
  children: [/* @__PURE__ */ jsx(ImportSelect, {
76
- label: "Find records by",
77
+ label: text("findBy"),
77
78
  value: flow.match?.field ?? skip,
78
79
  options: [{
79
80
  value: skip,
80
- label: "Choose a field"
81
+ label: text("chooseField")
81
82
  }, ...keys.map((field) => ({
82
83
  value: field.name,
83
84
  label: fieldLabel(view, field.name)
@@ -86,13 +87,13 @@ const ImportMappingStep = ({ flow, model, resources, views, canUpdate }) => {
86
87
  field,
87
88
  column: flow.match?.column ?? 0
88
89
  } : void 0),
89
- help: "A field no two records share, such as the key or an email."
90
+ help: text("findByHint")
90
91
  }), /* @__PURE__ */ jsx(ImportSelect, {
91
- label: "Found in column",
92
+ label: text("foundInColumn"),
92
93
  value: flow.match ? String(flow.match.column) : skip,
93
94
  options: [{
94
95
  value: skip,
95
- label: "Choose a column"
96
+ label: text("chooseColumn")
96
97
  }, ...columnOptions],
97
98
  onChange: (column) => flow.setMatch(column && flow.match ? {
98
99
  ...flow.match,
@@ -103,7 +104,7 @@ const ImportMappingStep = ({ flow, model, resources, views, canUpdate }) => {
103
104
  /* @__PURE__ */ jsxs("div", {
104
105
  className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
105
106
  children: [/* @__PURE__ */ jsx(ImportSelect, {
106
- label: "Numbers",
107
+ label: text("numbers"),
107
108
  value: flow.options.decimal,
108
109
  options: [{
109
110
  value: ".",
@@ -117,34 +118,34 @@ const ImportMappingStep = ({ flow, model, resources, views, canUpdate }) => {
117
118
  decimal
118
119
  })
119
120
  }), /* @__PURE__ */ jsx(ImportSelect, {
120
- label: "Dates",
121
+ label: text("dates"),
121
122
  value: flow.options.dateOrder,
122
123
  options: [{
123
124
  value: "dmy",
124
- label: "Day first (31/12/2026)"
125
+ label: text("dayFirst")
125
126
  }, {
126
127
  value: "mdy",
127
- label: "Month first (12/31/2026)"
128
+ label: text("monthFirst")
128
129
  }],
129
130
  onChange: (dateOrder) => flow.setOptions({
130
131
  ...flow.options,
131
132
  dateOrder
132
133
  }),
133
- help: "Dates written as 2026-12-31 read the same either way. Times without a zone are your own time."
134
+ help: text("datesHint")
134
135
  })]
135
136
  }),
136
137
  flow.mode !== "update" && /* @__PURE__ */ jsx("p", {
137
138
  className: "-mb-3 text-xs text-muted-foreground",
138
- children: "New records need the fields marked *."
139
+ children: text("requiredMarked")
139
140
  }),
140
141
  /* @__PURE__ */ jsx("section", {
141
- "aria-label": "Columns",
142
+ "aria-label": text("columns"),
142
143
  className: "flex flex-col divide-y divide-border border-y",
143
144
  children: headers.map((_, column) => {
144
145
  const mapped = flow.columns.find((entry) => entry.column === column);
145
146
  const field = mapped ? model.fields[mapped.field] : void 0;
146
147
  const target = field ? relationModel(field, resources) : void 0;
147
- const examples = sample.map((row) => cellText(row.cells[column] ?? null)).filter((text) => text !== "").slice(0, 3);
148
+ const examples = sample.map((row) => cellText(row.cells[column] ?? null)).filter((example) => example !== "").slice(0, 3);
148
149
  const name = columnName(headers, column);
149
150
  return /* @__PURE__ */ jsxs("div", {
150
151
  className: "grid grid-cols-1 gap-2 py-3 sm:grid-cols-2 sm:gap-4",
@@ -155,19 +156,19 @@ const ImportMappingStep = ({ flow, model, resources, views, canUpdate }) => {
155
156
  children: name
156
157
  }), /* @__PURE__ */ jsx("p", {
157
158
  className: "truncate text-xs text-muted-foreground",
158
- children: examples.length > 0 ? examples.join(", ") : "Empty in the first rows"
159
+ children: examples.length > 0 ? examples.join(", ") : text("emptyInFirstRows")
159
160
  })]
160
161
  }), /* @__PURE__ */ jsxs("div", {
161
162
  className: "flex min-w-0 flex-col gap-2",
162
163
  children: [/* @__PURE__ */ jsx(ImportSelect, {
163
- label: "Field",
164
- name: `Field for ${name}`,
164
+ label: text("field"),
165
+ name: text("fieldFor", name),
165
166
  value: mapped?.field ?? skip,
166
167
  options: fieldOptions,
167
168
  onChange: (next) => flow.mapColumn(column, next || void 0)
168
169
  }), target && /* @__PURE__ */ jsx(ImportSelect, {
169
- label: "Found by",
170
- name: `${views[target.name]?.names?.plural ?? fieldLabel(view, field.name)} found by`,
170
+ label: text("foundBy"),
171
+ name: text("pluralFoundBy", views[target.name]?.names?.plural ?? fieldLabel(view, field.name)),
171
172
  value: mapped.matchBy ?? target.primaryKey[0],
172
173
  options: matchFields(target).map((candidate) => ({
173
174
  value: candidate.name,
@@ -182,11 +183,7 @@ const ImportMappingStep = ({ flow, model, resources, views, canUpdate }) => {
182
183
  missing.length > 0 && /* @__PURE__ */ jsxs("p", {
183
184
  role: "alert",
184
185
  className: "flex gap-2 rounded-lg border border-warning/30 bg-warning-soft px-3 py-2 text-warning-text",
185
- children: [/* @__PURE__ */ jsx(TriangleAlert, { className: "mt-0.5 size-4 shrink-0" }), /* @__PURE__ */ jsxs("span", { children: [
186
- "No column fills ",
187
- missing.map((field) => fieldLabel(view, field.name)).join(", "),
188
- ", which new records need."
189
- ] })]
186
+ children: [/* @__PURE__ */ jsx(TriangleAlert, { className: "mt-0.5 size-4 shrink-0" }), /* @__PURE__ */ jsx("span", { children: text("noColumnFills", missing.map((field) => fieldLabel(view, field.name)).join(", ")) })]
190
187
  })
191
188
  ]
192
189
  });
@@ -1,6 +1,7 @@
1
1
  import { formatTag } from "../../locale/active-locale.js";
2
2
  import { Button } from "../../primitives/button.js";
3
3
  import { ErrorBanner } from "../error-banner.js";
4
+ import { text } from "./messages.js";
4
5
  import { ImportCounts } from "./import-counts.js";
5
6
  import { ProblemList, downloadRows } from "./import-check-step.js";
6
7
  import { Download } from "lucide-react";
@@ -16,7 +17,7 @@ const ImportProgressStep = ({ progress, total, cancelling }) => {
16
17
  className: "flex items-baseline justify-between gap-2 text-xs",
17
18
  children: [/* @__PURE__ */ jsx("span", {
18
19
  className: "text-muted-foreground",
19
- children: cancelling ? "Stopping after this batch…" : "Saving rows…"
20
+ children: cancelling ? text("stopping") : text("saving")
20
21
  }), /* @__PURE__ */ jsxs("span", {
21
22
  className: "tabular-nums",
22
23
  children: [
@@ -27,7 +28,7 @@ const ImportProgressStep = ({ progress, total, cancelling }) => {
27
28
  })]
28
29
  }), /* @__PURE__ */ jsx("div", {
29
30
  role: "progressbar",
30
- "aria-label": "Rows saved",
31
+ "aria-label": text("rowsSaved"),
31
32
  "aria-valuenow": done,
32
33
  "aria-valuemin": 0,
33
34
  "aria-valuemax": total,
@@ -44,20 +45,20 @@ const ImportSummary = ({ result, stopped, headers, fileName }) => /* @__PURE__ *
44
45
  className: "flex flex-col gap-4",
45
46
  children: [
46
47
  stopped !== void 0 && /* @__PURE__ */ jsx(ErrorBanner, {
47
- title: "The import stopped. The rows counted below were saved.",
48
+ title: text("stopped"),
48
49
  error: stopped
49
50
  }),
50
51
  result.cancelled && /* @__PURE__ */ jsx("p", {
51
52
  role: "status",
52
- children: "Cancelled. The rows counted below were saved."
53
+ children: text("cancelled")
53
54
  }),
54
55
  /* @__PURE__ */ jsx(ImportCounts, {
55
- label: "Import summary",
56
+ label: text("summary"),
56
57
  counts: [
57
- ["Created", result.created],
58
- ["Updated", result.updated],
59
- ["Skipped", result.skipped],
60
- ["Failed", result.failed]
58
+ [text("created"), result.created],
59
+ [text("updated"), result.updated],
60
+ [text("skipped"), result.skipped],
61
+ [text("failed"), result.failed]
61
62
  ]
62
63
  }),
63
64
  result.notWritten.length > 0 && /* @__PURE__ */ jsxs("div", {
@@ -67,16 +68,16 @@ const ImportSummary = ({ result, stopped, headers, fileName }) => /* @__PURE__ *
67
68
  className: "flex items-center justify-between gap-2",
68
69
  children: [/* @__PURE__ */ jsx("h3", {
69
70
  className: "font-medium",
70
- children: "Rows not imported"
71
+ children: text("rowsNotImported")
71
72
  }), /* @__PURE__ */ jsxs(Button, {
72
73
  size: "sm",
73
74
  onClick: () => downloadRows(fileName, headers, result.notWritten),
74
- children: [/* @__PURE__ */ jsx(Download, { className: "size-3.5" }), "Download"]
75
+ children: [/* @__PURE__ */ jsx(Download, { className: "size-3.5" }), text("download")]
75
76
  })]
76
77
  }),
77
78
  /* @__PURE__ */ jsx("p", {
78
79
  className: "text-xs text-muted-foreground",
79
- children: "Skipped rows had problems in the file; the server refused the failed ones. The file holds each with the reason, to fix and import again."
80
+ children: text("notWrittenHint")
80
81
  }),
81
82
  /* @__PURE__ */ jsx(ProblemList, { rows: [...result.notWritten].sort((a, b) => a.line - b.line) })
82
83
  ]
@@ -0,0 +1,200 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/import/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ import: "Import",
6
+ importPlural: (plural) => `Import ${plural.toLowerCase()}`,
7
+ couldNotReadFile: "Could not read the file",
8
+ couldNotCheck: "Could not check the rows",
9
+ cancel: "Cancel",
10
+ next: "Next",
11
+ checkRows: "Check rows",
12
+ back: "Back",
13
+ checking: "Checking",
14
+ importRows: (count, rows) => `Import ${count} ${rows === 1 ? "row" : "rows"}`,
15
+ cancelImport: "Cancel import",
16
+ done: "Done",
17
+ file: "File",
18
+ fileHint: "An Excel workbook (.xlsx) or a CSV file saved as UTF-8. Its first row names the columns.",
19
+ reading: (name) => `Reading ${name}…`,
20
+ theFile: "the file",
21
+ sheet: "Sheet",
22
+ create: "Create",
23
+ update: "Update",
24
+ upsert: "Create or update",
25
+ createHint: "Each row creates a record.",
26
+ updateHint: "Each row updates the record its value finds; a row that finds none is not imported.",
27
+ upsertHint: "Each row updates the record its value finds, or creates one when it finds none.",
28
+ doNotImport: "Do not import",
29
+ rows: "Rows",
30
+ whatEachRowDoes: "What each row does",
31
+ findBy: "Find records by",
32
+ chooseField: "Choose a field",
33
+ findByHint: "A field no two records share, such as the key or an email.",
34
+ foundInColumn: "Found in column",
35
+ chooseColumn: "Choose a column",
36
+ numbers: "Numbers",
37
+ dates: "Dates",
38
+ dayFirst: "Day first (31/12/2026)",
39
+ monthFirst: "Month first (12/31/2026)",
40
+ datesHint: "Dates written as 2026-12-31 read the same either way. Times without a zone are your own time.",
41
+ requiredMarked: "New records need the fields marked *.",
42
+ columns: "Columns",
43
+ emptyInFirstRows: "Empty in the first rows",
44
+ field: "Field",
45
+ fieldFor: (name) => `Field for ${name}`,
46
+ foundBy: "Found by",
47
+ pluralFoundBy: (plural) => `${plural} found by`,
48
+ noColumnFills: (fields) => `No column fills ${fields}, which new records need.`,
49
+ rowsNotImported: "Rows not imported",
50
+ rowProblems: (line) => `Row ${line}:`,
51
+ andMore: (count) => `And ${count} more rows, all in the file.`,
52
+ checkingRows: (count) => `Checking rows… ${count} read`,
53
+ check: "Check",
54
+ toCreate: "To create",
55
+ toUpdate: "To update",
56
+ withProblems: "With problems",
57
+ firstRows: "First rows",
58
+ row: "Row",
59
+ problemRows: "Rows with problems, which are skipped",
60
+ download: "Download",
61
+ batches: (size) => `Rows are saved in batches of ${size}. Cancelling, or an error, keeps the batches already saved; a row the server refuses is listed and the others are saved.`,
62
+ stopping: "Stopping after this batch…",
63
+ saving: "Saving rows…",
64
+ rowsSaved: "Rows saved",
65
+ stopped: "The import stopped. The rows counted below were saved.",
66
+ cancelled: "Cancelled. The rows counted below were saved.",
67
+ summary: "Import summary",
68
+ created: "Created",
69
+ updated: "Updated",
70
+ skipped: "Skipped",
71
+ failed: "Failed",
72
+ notWrittenHint: "Skipped rows had problems in the file; the server refused the failed ones. The file holds each with the reason, to fix and import again.",
73
+ notImportedFile: (name) => `${name} - not imported.csv`,
74
+ line: "Line",
75
+ problems: "Problems",
76
+ notNumber: (cell) => `${cell} is not a number`,
77
+ notWholeNumber: (cell) => `${cell} is not a whole number`,
78
+ tooLarge: (cell) => `${cell} is too large`,
79
+ notBoolean: (cell) => `${cell} is not true or false (true, false, yes, no, 1 or 0)`,
80
+ notDate: (cell, dayFirst) => `${cell} is not a date (2026-04-03 or ${dayFirst ? "day/month/year" : "month/day/year"})`,
81
+ hasTime: (cell) => `${cell} has a time of day; the field holds a date`,
82
+ severalValues: (cell, values) => `${cell} matches more than one value: ${values}`,
83
+ notOneOf: (cell, values) => `${cell} is not one of ${values}`,
84
+ notText: (cell) => `${cell} is true or false, not text`,
85
+ emptyRequired: "is empty, and it is required",
86
+ noneHas: (singular, named) => `no ${singular.toLowerCase()} has ${named}`,
87
+ severalHave: (count, plural, named) => `${count} ${plural.toLowerCase()} have ${named}`,
88
+ notOneRecord: (found) => `Not one record: ${found}`,
89
+ nothingToUpdate: (found) => `Nothing to update: ${found}`,
90
+ fileNotUtf8: "The file is not UTF-8 text. Save it as \"CSV UTF-8\" and choose it again.",
91
+ noSheets: "The workbook has no sheets",
92
+ missingSheetPart: "The workbook names a sheet it does not hold",
93
+ notZip: "The file is not an Excel workbook (.xlsx): it is no ZIP archive",
94
+ zip64: "The workbook is too large to read: it is a ZIP64 archive",
95
+ compression: (part, method) => `The workbook's part ${part} is compressed in a way that cannot be read (method ${method})`,
96
+ notWorkbook: (part) => `The file is not an Excel workbook (.xlsx): it has no ${part}`,
97
+ noSheetCalled: (name) => `The workbook has no sheet called ${name}`,
98
+ unreadable: "The file could not be read",
99
+ column: (letters) => `Column ${letters}`
100
+ },
101
+ nl: {
102
+ import: "Importeren",
103
+ importPlural: (plural) => `${plural} importeren`,
104
+ couldNotReadFile: "Kon het bestand niet lezen",
105
+ couldNotCheck: "Kon de rijen niet controleren",
106
+ cancel: "Annuleren",
107
+ next: "Volgende",
108
+ checkRows: "Rijen controleren",
109
+ back: "Terug",
110
+ checking: "Controleren",
111
+ importRows: (count, rows) => `${count} ${rows === 1 ? "rij" : "rijen"} importeren`,
112
+ cancelImport: "Import annuleren",
113
+ done: "Klaar",
114
+ file: "Bestand",
115
+ fileHint: "Een Excel-werkmap (.xlsx) of een CSV-bestand bewaard als UTF-8. De eerste rij noemt de kolommen.",
116
+ reading: (name) => `${name} lezen…`,
117
+ theFile: "het bestand",
118
+ sheet: "Werkblad",
119
+ create: "Maken",
120
+ update: "Bijwerken",
121
+ upsert: "Maken of bijwerken",
122
+ createHint: "Elke rij maakt een record.",
123
+ updateHint: "Elke rij werkt het record bij dat haar waarde vindt; een rij die er geen vindt, wordt niet geïmporteerd.",
124
+ upsertHint: "Elke rij werkt het record bij dat haar waarde vindt, of maakt er een als ze er geen vindt.",
125
+ doNotImport: "Niet importeren",
126
+ rows: "Rijen",
127
+ whatEachRowDoes: "Wat elke rij doet",
128
+ findBy: "Records vinden op",
129
+ chooseField: "Kies een veld",
130
+ findByHint: "Een veld dat geen twee records delen, zoals de sleutel of een e-mailadres.",
131
+ foundInColumn: "Gevonden in kolom",
132
+ chooseColumn: "Kies een kolom",
133
+ numbers: "Getallen",
134
+ dates: "Datums",
135
+ dayFirst: "Dag eerst (31/12/2026)",
136
+ monthFirst: "Maand eerst (12/31/2026)",
137
+ datesHint: "Datums geschreven als 2026-12-31 lezen hoe dan ook hetzelfde. Tijden zonder zone zijn in je eigen tijd.",
138
+ requiredMarked: "Nieuwe records hebben de velden met * nodig.",
139
+ columns: "Kolommen",
140
+ emptyInFirstRows: "Leeg in de eerste rijen",
141
+ field: "Veld",
142
+ fieldFor: (name) => `Veld voor ${name}`,
143
+ foundBy: "Gevonden op",
144
+ pluralFoundBy: (plural) => `${plural} gevonden op`,
145
+ noColumnFills: (fields) => `Geen kolom vult ${fields}, wat nieuwe records nodig hebben.`,
146
+ rowsNotImported: "Niet geïmporteerde rijen",
147
+ rowProblems: (line) => `Rij ${line}:`,
148
+ andMore: (count) => `En nog ${count} rijen, allemaal in het bestand.`,
149
+ checkingRows: (count) => `Rijen controleren… ${count} gelezen`,
150
+ check: "Controle",
151
+ toCreate: "Te maken",
152
+ toUpdate: "Bij te werken",
153
+ withProblems: "Met problemen",
154
+ firstRows: "Eerste rijen",
155
+ row: "Rij",
156
+ problemRows: "Rijen met problemen, die worden overgeslagen",
157
+ download: "Downloaden",
158
+ batches: (size) => `Rijen worden bewaard in groepen van ${size}. Annuleren, of een fout, houdt de groepen die al bewaard zijn; een rij die de server weigert, wordt vermeld en de andere worden bewaard.`,
159
+ stopping: "Stoppen na deze groep…",
160
+ saving: "Rijen bewaren…",
161
+ rowsSaved: "Bewaarde rijen",
162
+ stopped: "De import is gestopt. De rijen die hieronder zijn geteld, zijn bewaard.",
163
+ cancelled: "Geannuleerd. De rijen die hieronder zijn geteld, zijn bewaard.",
164
+ summary: "Samenvatting van de import",
165
+ created: "Gemaakt",
166
+ updated: "Bijgewerkt",
167
+ skipped: "Overgeslagen",
168
+ failed: "Mislukt",
169
+ notWrittenHint: "Overgeslagen rijen hadden problemen in het bestand; de mislukte weigerde de server. Het bestand bevat elke rij met de reden, om te verbeteren en opnieuw te importeren.",
170
+ notImportedFile: (name) => `${name} - niet geïmporteerd.csv`,
171
+ line: "Regel",
172
+ problems: "Problemen",
173
+ notNumber: (cell) => `${cell} is geen getal`,
174
+ notWholeNumber: (cell) => `${cell} is geen geheel getal`,
175
+ tooLarge: (cell) => `${cell} is te groot`,
176
+ notBoolean: (cell) => `${cell} is niet waar of onwaar (true, false, yes, no, 1 of 0)`,
177
+ notDate: (cell, dayFirst) => `${cell} is geen datum (2026-04-03 of ${dayFirst ? "dag/maand/jaar" : "maand/dag/jaar"})`,
178
+ hasTime: (cell) => `${cell} heeft een tijd van de dag; het veld bevat een datum`,
179
+ severalValues: (cell, values) => `${cell} past bij meer dan één waarde: ${values}`,
180
+ notOneOf: (cell, values) => `${cell} is niet een van ${values}`,
181
+ notText: (cell) => `${cell} is waar of onwaar, geen tekst`,
182
+ emptyRequired: "is leeg, en is verplicht",
183
+ noneHas: (singular, named) => `geen ${singular.toLowerCase()} heeft ${named}`,
184
+ severalHave: (count, plural, named) => `${count} ${plural.toLowerCase()} hebben ${named}`,
185
+ notOneRecord: (found) => `Niet één record: ${found}`,
186
+ nothingToUpdate: (found) => `Niets om bij te werken: ${found}`,
187
+ fileNotUtf8: "Het bestand is geen UTF-8-tekst. Bewaar het als \"CSV UTF-8\" en kies het opnieuw.",
188
+ noSheets: "De werkmap heeft geen werkbladen",
189
+ missingSheetPart: "De werkmap noemt een werkblad dat er niet in zit",
190
+ notZip: "Het bestand is geen Excel-werkmap (.xlsx): het is geen ZIP-archief",
191
+ zip64: "De werkmap is te groot om te lezen: het is een ZIP64-archief",
192
+ compression: (part, method) => `Het deel ${part} van de werkmap is gecomprimeerd op een manier die niet kan worden gelezen (methode ${method})`,
193
+ notWorkbook: (part) => `Het bestand is geen Excel-werkmap (.xlsx): het heeft geen ${part}`,
194
+ noSheetCalled: (name) => `De werkmap heeft geen werkblad met de naam ${name}`,
195
+ unreadable: "Het bestand kon niet worden gelezen",
196
+ column: (letters) => `Kolom ${letters}`
197
+ }
198
+ });
199
+ //#endregion
200
+ export { text };
@@ -1,3 +1,4 @@
1
+ import { setFallbackLocale } from "../../locale/active-locale.js";
1
2
  import { sheetNames, sheetRows } from "./sheet-file.js";
2
3
  //#region src/app/import/parse-worker.ts
3
4
  const scope = self;
@@ -24,6 +25,7 @@ const read = async (request) => {
24
25
  };
25
26
  const handle = async (request) => {
26
27
  if (request.type === "next") return next?.();
28
+ setFallbackLocale(request.locale);
27
29
  if (request.type === "sheets") return reply({
28
30
  type: "sheets",
29
31
  sheets: await sheetNames(request.file)
@@ -1,4 +1,5 @@
1
1
  import { humanize } from "../../live/naming.js";
2
+ import { text } from "./messages.js";
2
3
  import { cellText, isEmptyCell } from "./sheet-cell.js";
3
4
  import { fieldLabel, relationModel } from "./mapping.js";
4
5
  import { convertCell } from "./convert.js";
@@ -62,8 +63,8 @@ const foundOne = (context, finding) => {
62
63
  const keys = context.lookups.keysOf(finding.model.name, finding.field, finding.value);
63
64
  const named = `${fieldLabel(context.views[finding.model.name], finding.field).toLowerCase()} “${String(finding.value)}”`;
64
65
  if (keys.length === 1) return { key: keys[0] };
65
- if (keys.length === 0) return { none: `no ${singularOf(context.views, finding.model.name).toLowerCase()} has ${named}` };
66
- return { several: `${keys.length} ${pluralOf(context.views, finding.model.name).toLowerCase()} have ${named}` };
66
+ if (keys.length === 0) return { none: text("noneHas", singularOf(context.views, finding.model.name), named) };
67
+ return { several: text("severalHave", keys.length, pluralOf(context.views, finding.model.name), named) };
67
68
  };
68
69
  const isBlank = (row) => row.cells.every(isEmptyCell);
69
70
  /**
@@ -96,8 +97,8 @@ const prepareRows = async (rows, plan, context) => {
96
97
  else problems.push(`${fieldLabel(views[model.name], field)}: ${found.none ?? found.several}`);
97
98
  }
98
99
  const existing = match && foundOne(context, match);
99
- if (existing?.several) problems.push(`Not one record: ${existing.several}`);
100
- if (existing?.none && plan.mode === "update") problems.push(`Nothing to update: ${existing.none}`);
100
+ if (existing?.several) problems.push(text("notOneRecord", existing.several));
101
+ if (existing?.none && plan.mode === "update") problems.push(text("nothingToUpdate", existing.none));
101
102
  const prepared = {
102
103
  line: row.line,
103
104
  cells: row.cells,
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  //#region src/app/import/sheet-cell.ts
2
3
  const isSheetDate = (cell) => typeof cell === "object" && cell !== null;
3
4
  /** Whether the cell holds nothing: no value, or only spaces. */
@@ -12,6 +13,6 @@ const cellText = (cell) => {
12
13
  /** A column's letters as a spreadsheet names it: 0 is A, 27 is AB. */
13
14
  const columnLetters = (index) => index < 26 ? String.fromCharCode(65 + index) : columnLetters(Math.floor(index / 26) - 1) + columnLetters(index % 26);
14
15
  /** How the import names a column: its header, or "Column C" when the header cell is empty. */
15
- const columnName = (headers, index) => headers[index]?.trim() ? headers[index] : `Column ${columnLetters(index)}`;
16
+ const columnName = (headers, index) => headers[index]?.trim() ? headers[index] : text("column", columnLetters(index));
16
17
  //#endregion
17
18
  export { cellText, columnLetters, columnName, isEmptyCell, isSheetDate };
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  import { createCsvParser } from "./csv-parser.js";
2
3
  import { openWorkbook } from "./xlsx-reader.js";
3
4
  //#region src/app/import/sheet-file.ts
@@ -14,7 +15,7 @@ const sheetFormat = async (file) => {
14
15
  };
15
16
  /** The workbook's sheet names, in order; a CSV has none. */
16
17
  const sheetNames = async (file) => await sheetFormat(file) === "xlsx" ? (await openWorkbook(file)).sheets : [];
17
- const notText = () => /* @__PURE__ */ new Error("The file is not UTF-8 text. Save it as \"CSV UTF-8\" and choose it again.");
18
+ const notText = () => new Error(text("fileNotUtf8"));
18
19
  async function* csvRows(file) {
19
20
  const parser = createCsvParser();
20
21
  const decoder = new TextDecoder("utf-8", { fatal: true });
@@ -51,7 +52,7 @@ async function* sheetRows(file, sheet) {
51
52
  if (await sheetFormat(file) === "csv") return yield* csvRows(file);
52
53
  const workbook = await openWorkbook(file);
53
54
  const name = sheet ?? workbook.sheets[0];
54
- if (name === void 0) throw new Error("The workbook has no sheets");
55
+ if (name === void 0) throw new Error(text("noSheets"));
55
56
  yield* workbook.rows(name);
56
57
  }
57
58
  //#endregion
@@ -1,3 +1,5 @@
1
+ import { activeLocale } from "../../locale/active-locale.js";
2
+ import { text } from "./messages.js";
1
3
  //#region src/app/import/sheet-source.ts
2
4
  /** How many rows the worker sends at a time. */
3
5
  const chunkRows = 500;
@@ -16,7 +18,7 @@ const startWorker = () => {
16
18
  event.preventDefault();
17
19
  const failed = {
18
20
  type: "error",
19
- message: "The file could not be read"
21
+ message: text("unreadable")
20
22
  };
21
23
  if (waiting) waiting(failed);
22
24
  else replies.push(failed);
@@ -36,7 +38,8 @@ const workerSheetSource = (file) => ({
36
38
  try {
37
39
  worker.send({
38
40
  type: "sheets",
39
- file
41
+ file,
42
+ locale: activeLocale()
40
43
  });
41
44
  const reply = await worker.receive();
42
45
  if (reply.type === "error") throw new Error(reply.message);
@@ -53,7 +56,8 @@ const workerSheetSource = (file) => ({
53
56
  type: "read",
54
57
  file,
55
58
  sheet,
56
- chunk: chunkRows
59
+ chunk: chunkRows,
60
+ locale: activeLocale()
57
61
  });
58
62
  for (;;) {
59
63
  const reply = await worker.receive();
@@ -1,9 +1,10 @@
1
+ import { text } from "./messages.js";
1
2
  import { createRowScanner, dateStyles, parseRow, sharedStrings, workbookSheets } from "./xlsx-xml.js";
2
3
  import { zipEntries, zipText, zipTextChunks } from "./zip-reader.js";
3
4
  //#region src/app/import/xlsx-reader.ts
4
5
  const part = (entries, name) => {
5
6
  const entry = entries.get(name);
6
- if (!entry) throw new Error(`The file is not an Excel workbook (.xlsx): it has no ${name}`);
7
+ if (!entry) throw new Error(text("notWorkbook", name));
7
8
  return entry;
8
9
  };
9
10
  /**
@@ -24,11 +25,11 @@ const openWorkbook = async (blob) => {
24
25
  };
25
26
  async function* rows(name) {
26
27
  const sheet = sheets.find((candidate) => candidate.name === name);
27
- if (!sheet) throw new Error(`The workbook has no sheet called ${name}`);
28
+ if (!sheet) throw new Error(text("noSheetCalled", name));
28
29
  const shared = context ?? await sheetContext();
29
30
  const scan = createRowScanner();
30
31
  let line = 0;
31
- for await (const text of zipTextChunks(blob, part(entries, sheet.path))) for (const xml of scan(text)) {
32
+ for await (const chunk of zipTextChunks(blob, part(entries, sheet.path))) for (const xml of scan(chunk)) {
32
33
  const row = parseRow(xml, shared, line);
33
34
  line = row.line;
34
35
  yield row;
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  //#region src/app/import/xlsx-xml.ts
2
3
  const entities = {
3
4
  amp: "&",
@@ -35,7 +36,7 @@ const workbookSheets = (workbookXml, relsXml) => {
35
36
  const sheets = [...workbookXml.matchAll(/<sheet\b([^>]*)>/g)].map((match) => {
36
37
  const { name, id } = attributes(match[1]);
37
38
  const path = targets.get(id);
38
- if (!name || !path) throw new Error("The workbook names a sheet it does not hold");
39
+ if (!name || !path) throw new Error(text("missingSheetPart"));
39
40
  return {
40
41
  name,
41
42
  path
@@ -1,10 +1,11 @@
1
+ import { text } from "./messages.js";
1
2
  //#region src/app/import/zip-reader.ts
2
3
  const endSignature = 101010256;
3
4
  const entrySignature = 33639248;
4
5
  const localSignature = 67324752;
5
6
  const endSearch = 65557;
6
7
  const bytesOf = async (blob, start, end) => new DataView(await blob.slice(start, end).arrayBuffer());
7
- const notZip = () => /* @__PURE__ */ new Error("The file is not an Excel workbook (.xlsx): it is no ZIP archive");
8
+ const notZip = () => new Error(text("notZip"));
8
9
  const endRecordAt = (view) => {
9
10
  for (let at = view.byteLength - 22; at >= 0; at--) if (view.getUint32(at, true) === endSignature) return at;
10
11
  return -1;
@@ -18,7 +19,7 @@ const zipEntries = async (blob) => {
18
19
  const count = tail.getUint16(end + 10, true);
19
20
  const size = tail.getUint32(end + 12, true);
20
21
  const start = tail.getUint32(end + 16, true);
21
- if (count === 65535 || size === 4294967295 || start === 4294967295) throw new Error("The workbook is too large to read: it is a ZIP64 archive");
22
+ if (count === 65535 || size === 4294967295 || start === 4294967295) throw new Error(text("zip64"));
22
23
  const directory = await bytesOf(blob, start, start + size);
23
24
  const decoder = new TextDecoder();
24
25
  const entries = /* @__PURE__ */ new Map();
@@ -47,7 +48,7 @@ const zipEntryStream = async (blob, entry) => {
47
48
  const raw = blob.slice(dataStart, dataStart + entry.size).stream();
48
49
  if (entry.method === 0) return raw;
49
50
  if (entry.method === 8) return raw.pipeThrough(new DecompressionStream("deflate-raw"));
50
- throw new Error(`The workbook's part ${entry.name} is compressed in a way that cannot be read (method ${entry.method})`);
51
+ throw new Error(text("compression", entry.name, entry.method));
51
52
  };
52
53
  /** The entry's content as text, decoded as UTF-8 a chunk at a time. */
53
54
  async function* zipTextChunks(blob, entry) {