@protobase/ui 0.23.9 → 0.23.11

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 (85) hide show
  1. package/dist/app/create/create-page.js +3 -2
  2. package/dist/app/create/create-record-form.js +4 -3
  3. package/dist/app/create/create-route.js +5 -8
  4. package/dist/app/create/create-values.js +2 -1
  5. package/dist/app/create/form.js +3 -2
  6. package/dist/app/create/messages.js +32 -0
  7. package/dist/app/delete/blocked-delete.js +6 -5
  8. package/dist/app/delete/dialogs.js +21 -20
  9. package/dist/app/delete/messages.js +66 -0
  10. package/dist/app/delete/use-delete-flow.js +7 -6
  11. package/dist/app/displays/display-controls.js +5 -4
  12. package/dist/app/displays/list-displays.js +2 -1
  13. package/dist/app/displays/messages.js +20 -0
  14. package/dist/app/embedded-list/cut-off-note.js +2 -1
  15. package/dist/app/embedded-list/embedded-list.js +11 -6
  16. package/dist/app/embedded-list/embedded-row-targets.js +4 -3
  17. package/dist/app/embedded-list/messages.js +24 -0
  18. package/dist/app/error-banner.js +2 -1
  19. package/dist/app/field-errors.js +19 -19
  20. package/dist/app/field-messages.js +48 -0
  21. package/dist/app/hand-order/hand-sorted-list.js +6 -11
  22. package/dist/app/hand-order/messages.js +22 -0
  23. package/dist/app/hand-order/sort-button.js +2 -1
  24. package/dist/app/hand-order/use-hand-order.js +2 -1
  25. package/dist/app/incoming-card.js +2 -1
  26. package/dist/app/list-edit/messages.js +14 -0
  27. package/dist/app/list-edit/use-list-edits.js +2 -1
  28. package/dist/app/list-empty-state.js +5 -5
  29. package/dist/app/list-empty.js +3 -2
  30. package/dist/app/list-messages.js +54 -0
  31. package/dist/app/list-page.js +24 -38
  32. package/dist/app/list-search.js +8 -7
  33. package/dist/app/messages.js +16 -0
  34. package/dist/app/pages/record-actions.js +4 -3
  35. package/dist/app/record-lines.js +3 -2
  36. package/dist/app/record-messages.js +78 -0
  37. package/dist/app/record-page.js +31 -32
  38. package/dist/app/related-section.js +5 -4
  39. package/dist/app/use-live-save.js +4 -3
  40. package/dist/data-grid/cell-text.js +2 -1
  41. package/dist/data-grid/cells.js +15 -14
  42. package/dist/data-grid/grid-footer.js +3 -2
  43. package/dist/data-grid/grid-header.js +4 -3
  44. package/dist/data-grid/grid-row.js +2 -1
  45. package/dist/data-grid/header-menu.js +5 -4
  46. package/dist/data-grid/messages.js +50 -0
  47. package/dist/data-grid/row-menu.js +2 -1
  48. package/dist/data-grid/row-reorder.js +8 -8
  49. package/dist/filter-panel/active-filter-chips.js +3 -2
  50. package/dist/filter-panel/date-presets.js +5 -4
  51. package/dist/filter-panel/facet-combobox.js +3 -2
  52. package/dist/filter-panel/facet-group.js +3 -2
  53. package/dist/filter-panel/filter-config.js +15 -12
  54. package/dist/filter-panel/filter-panel.js +4 -3
  55. package/dist/filter-panel/filter-state.js +2 -2
  56. package/dist/filter-panel/messages.js +52 -0
  57. package/dist/filter-panel/more-filters-menu.js +3 -2
  58. package/dist/filter-panel/range-filter.js +2 -1
  59. package/dist/filter-panel/secondary-filters.js +2 -1
  60. package/dist/forms/messages.js +24 -0
  61. package/dist/forms/prove-human.js +2 -1
  62. package/dist/forms/public-form-page.js +14 -8
  63. package/dist/live/display-value.js +3 -2
  64. package/dist/live/file-values.js +2 -1
  65. package/dist/live/messages.js +18 -0
  66. package/dist/package.js +1 -1
  67. package/dist/record-view/action-menu.js +2 -1
  68. package/dist/record-view/action-rail.js +2 -1
  69. package/dist/record-view/field.js +12 -7
  70. package/dist/record-view/messages.js +38 -0
  71. package/dist/record-view/record-header.js +8 -10
  72. package/dist/shell/assets/app-Bc3JCvDX.js +69 -0
  73. package/dist/shell/assets/export-worker-Bz57Pf5g.js +2 -0
  74. package/dist/shell/assets/{index-AuXmSIOC.js → index-DMPV9_11.js} +1 -1
  75. package/dist/shell/assets/{maplibre-ZHMezcAD.js → maplibre-CqykHSB6.js} +1 -1
  76. package/dist/shell/assets/{markdown-content-DDgQDs-m.js → markdown-content-BmKfQOZ6.js} +1 -1
  77. package/dist/shell/assets/{record-map-CkWfz5Uz.js → record-map-D2ccAykf.js} +1 -1
  78. package/dist/shell/assets/{records-map-VqY_uAtH.js → records-map-DKQG53I0.js} +1 -1
  79. package/dist/shell/assets/{shared-protobase-ui-ClGFtlOR.js → shared-protobase-ui-BCsFQ2PD.js} +1 -1
  80. package/dist/shell/index.html +3 -3
  81. package/dist/shell/protobase.shell.json +1 -1
  82. package/dist/shell/protobase.sw.js +2 -2
  83. package/package.json +4 -4
  84. package/dist/shell/assets/app-DCsXeyw4.js +0 -69
  85. package/dist/shell/assets/export-worker-O1HF9R4j.js +0 -2
@@ -3,6 +3,7 @@ import { humanize } from "../../live/naming.js";
3
3
  import { recordKey } from "../../live/model-helpers.js";
4
4
  import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
5
5
  import { useAppAddress } from "../use-app-address.js";
6
+ import { text } from "./messages.js";
6
7
  import { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isRequired } from "./create-values.js";
7
8
  import { FieldEditor } from "../field-editor.js";
8
9
  import { fieldErrors, fieldMessage } from "../field-errors.js";
@@ -50,7 +51,7 @@ const LoadedCreate = ({ resource, values }) => {
50
51
  const queryClient = useQueryClient();
51
52
  const { go } = useAppAddress();
52
53
  const noun = view?.names?.singular ?? humanize(resource);
53
- const feedback = useLiveSave(view?.saveFeedback ?? "toast", `New ${noun.toLowerCase()}`);
54
+ const feedback = useLiveSave(view?.saveFeedback ?? "toast", text("subject", noun));
54
55
  const sections = createSections(model, view);
55
56
  const [draft, setDraft] = useState(() => initialDraft(model, sections, values));
56
57
  const [errors, setErrors] = useState({});
@@ -131,7 +132,7 @@ const LoadedCreate = ({ resource, values }) => {
131
132
  value: isChosen(value) ? value.key : value ?? (field.type === "boolean" ? false : ""),
132
133
  stored: void 0,
133
134
  relationLabel: isChosen(value) ? value.label : void 0,
134
- placeholder: hasDatabaseDefault(field) ? "Set by the database" : void 0,
135
+ placeholder: hasDatabaseDefault(field) ? text("setByDatabase") : void 0,
135
136
  relationPicker: target ? {
136
137
  target,
137
138
  view: meta.views[target.name]
@@ -1,17 +1,18 @@
1
1
  import { Field } from "../../record-view/field.js";
2
2
  import { Section } from "../../record-view/section.js";
3
+ import { text } from "./messages.js";
3
4
  import { Form } from "./form.js";
4
5
  import { jsx } from "react/jsx-runtime";
5
6
  //#region src/app/create/create-record-form.tsx
6
7
  /** The presentation of "New <thing>": the view's sections, each field with its editor, and Create or Cancel. */
7
8
  const CreateRecordForm = ({ noun, collection, sections, saving, message, onSubmit, onCancel, ref }) => /* @__PURE__ */ jsx(Form, {
8
9
  ref,
9
- title: `New ${noun.toLowerCase()}`,
10
+ title: text("title", noun),
10
11
  eyebrow: collection,
11
- submit: `Create ${noun.toLowerCase()}`,
12
+ submit: text("submit", noun),
12
13
  saving,
13
14
  error: message ? new Error(message) : void 0,
14
- errorTitle: "Not created",
15
+ errorTitle: text("notCreated"),
15
16
  onSubmit,
16
17
  onCancel,
17
18
  children: sections.map((section) => /* @__PURE__ */ jsx(Section, {
@@ -1,8 +1,9 @@
1
1
  import { humanize } from "../../live/naming.js";
2
2
  import { Notice, useAdminMeta } from "../meta-gate.js";
3
+ import { text } from "./messages.js";
3
4
  import { ComposedPage } from "../pages/composed-page.js";
4
5
  import { CreatePage } from "./create-page.js";
5
- import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { jsx } from "react/jsx-runtime";
6
7
  //#region src/app/create/create-route.tsx
7
8
  /**
8
9
  * `/:resource/new`: the view's create page when it names one, which reads the address's `values` itself and may change
@@ -15,13 +16,9 @@ const CreateRoute = ({ resource, values }) => {
15
16
  resource,
16
17
  values
17
18
  }, new URLSearchParams(values).toString());
18
- if (!meta.pages[view.create.page]) return /* @__PURE__ */ jsxs(Notice, {
19
- title: "Not allowed",
20
- children: [
21
- "You cannot create ",
22
- view.names?.plural.toLowerCase() ?? humanize(resource),
23
- "."
24
- ]
19
+ if (!meta.pages[view.create.page]) return /* @__PURE__ */ jsx(Notice, {
20
+ title: text("notAllowed"),
21
+ children: text("cannotCreate", view.names?.plural.toLowerCase() ?? humanize(resource))
25
22
  });
26
23
  return /* @__PURE__ */ jsx(ComposedPage, { name: view.create.page });
27
24
  };
@@ -1,5 +1,6 @@
1
1
  import { isEditable, toPatchValue } from "../../live/field-values.js";
2
2
  import { whenShown } from "../record-mode/record-mode.js";
3
+ import { text } from "./messages.js";
3
4
  import { layoutItems } from "@protobase/schema/internal";
4
5
  import { isRecordCondition } from "@protobase/schema";
5
6
  //#region src/app/create/create-values.ts
@@ -27,7 +28,7 @@ const createSections = (model, view) => {
27
28
  const rest = creatable.filter((name) => !placed.has(name));
28
29
  if (rest.length === 0) return sections;
29
30
  return [...sections, {
30
- title: sections.length === 0 ? "Details" : "Other fields",
31
+ title: sections.length === 0 ? text("details") : text("otherFields"),
31
32
  fields: rest
32
33
  }];
33
34
  };
@@ -1,15 +1,16 @@
1
1
  import { Button } from "../../primitives/button.js";
2
+ import { text } from "./messages.js";
2
3
  import { ErrorBanner } from "../error-banner.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region src/app/create/form.tsx
5
6
  /** The frame of the app's forms: Cancel and the submit button, a failure that belongs to no field, and the sections. */
6
- const Form = ({ title, eyebrow, submit, saving, disabled, error, errorTitle = "Not saved", onSubmit, onCancel, children, ref }) => {
7
+ const Form = ({ title, eyebrow, submit, saving, disabled, error, errorTitle = text("notSaved"), onSubmit, onCancel, children, ref }) => {
7
8
  const buttons = /* @__PURE__ */ jsxs("div", {
8
9
  className: "flex items-center gap-2",
9
10
  children: [onCancel && /* @__PURE__ */ jsx(Button, {
10
11
  className: "max-sm:min-h-11",
11
12
  onClick: onCancel,
12
- children: "Cancel"
13
+ children: text("cancel")
13
14
  }), /* @__PURE__ */ jsx(Button, {
14
15
  variant: "primary",
15
16
  type: "submit",
@@ -0,0 +1,32 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/create/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ title: (noun) => `New ${noun.toLowerCase()}`,
6
+ subject: (noun) => `New ${noun.toLowerCase()}`,
7
+ submit: (noun) => `Create ${noun.toLowerCase()}`,
8
+ notCreated: "Not created",
9
+ notSaved: "Not saved",
10
+ cancel: "Cancel",
11
+ setByDatabase: "Set by the database",
12
+ details: "Details",
13
+ otherFields: "Other fields",
14
+ notAllowed: "Not allowed",
15
+ cannotCreate: (plural) => `You cannot create ${plural}.`
16
+ },
17
+ nl: {
18
+ title: (noun) => `${noun} toevoegen`,
19
+ subject: (noun) => noun,
20
+ submit: (noun) => `${noun} aanmaken`,
21
+ notCreated: "Niet aangemaakt",
22
+ notSaved: "Niet bewaard",
23
+ cancel: "Annuleren",
24
+ setByDatabase: "Ingevuld door de database",
25
+ details: "Details",
26
+ otherFields: "Overige velden",
27
+ notAllowed: "Niet toegestaan",
28
+ cannotCreate: (plural) => `Je mag geen ${plural} aanmaken.`
29
+ }
30
+ });
31
+ //#endregion
32
+ export { text };
@@ -1,9 +1,10 @@
1
1
  import { humanize } from "../../live/naming.js";
2
+ import { text } from "./messages.js";
2
3
  import { createWhere, printFilter } from "@protobase/schema";
3
4
  //#region src/app/delete/blocked-delete.ts
4
5
  const where$1 = createWhere();
5
6
  /** What a delete other records still point at says, such as "Orders still point at this company." */
6
- const blockedDeleteText = ({ blockingPlural, noun }) => `${blockingPlural} still point at this ${noun.toLowerCase()}.`;
7
+ const blockedDeleteText = ({ blockingPlural, noun }) => text("stillPointAt", blockingPlural, noun);
7
8
  /**
8
9
  * Why the delete was refused, naming the records that point at it by their view's plural, and, when the server names
9
10
  * the field pointing at it, a link to their list filtered to those records. `undefined` for a resource the app does not show.
@@ -12,20 +13,20 @@ const blockedDelete = ({ blockedBy, resources, views, noun, key }) => {
12
13
  const { resource, field } = blockedBy;
13
14
  if (!resources[resource]) return void 0;
14
15
  const plural = views[resource]?.names?.plural ?? humanize(resource);
15
- const text = blockedDeleteText({
16
+ const text$1 = blockedDeleteText({
16
17
  blockingPlural: plural,
17
18
  noun
18
19
  });
19
- if (!field || key === void 0) return { text };
20
+ if (!field || key === void 0) return { text: text$1 };
20
21
  const list = {
21
22
  kind: "list",
22
23
  resource,
23
24
  filter: printFilter(where$1.eq(field, key))
24
25
  };
25
26
  return {
26
- text,
27
+ text: text$1,
27
28
  link: {
28
- label: `Show ${plural.toLowerCase()}`,
29
+ label: text("show", plural),
29
30
  to: list
30
31
  }
31
32
  };
@@ -1,39 +1,40 @@
1
1
  import { Button } from "../../primitives/button.js";
2
2
  import { Dialog } from "../../primitives/dialog.js";
3
+ import { text } from "./messages.js";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region src/app/delete/dialogs.tsx
5
6
  const ConfirmDeleteDialog = ({ title, noun, busy, onConfirm, onCancel }) => /* @__PURE__ */ jsx(Dialog, {
6
- title: `Delete ${title}?`,
7
- description: `This ${noun.toLowerCase()} will be removed. This cannot be undone.`,
7
+ title: text("confirmTitle", title),
8
+ description: text("confirmDescription", noun, title),
8
9
  onClose: onCancel,
9
10
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
10
11
  onClick: onCancel,
11
- children: "Cancel"
12
+ children: text("cancel")
12
13
  }), /* @__PURE__ */ jsx(Button, {
13
14
  variant: "primary",
14
15
  className: "bg-danger hover:bg-danger/90",
15
16
  loading: busy,
16
17
  onClick: onConfirm,
17
- children: "Delete"
18
+ children: text("delete")
18
19
  })] })
19
20
  });
20
21
  const ConflictDialog = ({ title, changes, busy, onReview, onDeleteAnyway, onCancel }) => /* @__PURE__ */ jsx(Dialog, {
21
- title: `${title} was changed since you opened it`,
22
- description: "Someone saved this record in the meantime. Review what changed before you delete it.",
22
+ title: text("changedTitle", title),
23
+ description: text("changedDescription"),
23
24
  onClose: onCancel,
24
25
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
25
26
  onClick: onReview,
26
- children: "Review changes"
27
+ children: text("reviewChanges")
27
28
  }), /* @__PURE__ */ jsx(Button, {
28
29
  variant: "primary",
29
30
  className: "bg-danger hover:bg-danger/90",
30
31
  loading: busy,
31
32
  onClick: onDeleteAnyway,
32
- children: "Delete anyway"
33
+ children: text("deleteAnyway")
33
34
  })] }),
34
35
  children: changes.length === 0 ? /* @__PURE__ */ jsx("p", {
35
36
  className: "text-muted-foreground",
36
- children: "No visible fields differ; the record was saved again with the same values."
37
+ children: text("noVisibleChanges")
37
38
  }) : /* @__PURE__ */ jsx("ul", {
38
39
  className: "divide-y rounded-md border",
39
40
  children: changes.map((change) => /* @__PURE__ */ jsxs("li", {
@@ -57,38 +58,38 @@ const ConflictDialog = ({ title, changes, busy, onReview, onDeleteAnyway, onCanc
57
58
  });
58
59
  const BulkDeleteDialog = (props) => {
59
60
  if (props.stage === "confirm") return /* @__PURE__ */ jsx(Dialog, {
60
- title: `Delete ${props.count} ${props.count === 1 ? props.plural.replace(/s$/, "") : props.plural.toLowerCase()}?`,
61
- description: "This cannot be undone. Each record is deleted only if it has not changed since the list loaded; anything that changed is reported and kept.",
61
+ title: text("bulkConfirmTitle", props.count, props.plural),
62
+ description: text("bulkConfirmDescription"),
62
63
  onClose: props.onCancel,
63
64
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
64
65
  onClick: props.onCancel,
65
- children: "Cancel"
66
- }), /* @__PURE__ */ jsxs(Button, {
66
+ children: text("cancel")
67
+ }), /* @__PURE__ */ jsx(Button, {
67
68
  variant: "primary",
68
69
  className: "bg-danger hover:bg-danger/90",
69
70
  onClick: props.onConfirm,
70
- children: ["Delete ", props.count]
71
+ children: text("deleteCount", props.count)
71
72
  })] })
72
73
  });
73
74
  if (props.stage === "running") return /* @__PURE__ */ jsx(Dialog, {
74
- title: "Deleting",
75
- description: `${props.done} of ${props.total} done`,
75
+ title: text("deleting"),
76
+ description: text("progress", props.done, props.total),
76
77
  onClose: () => void 0,
77
78
  actions: /* @__PURE__ */ jsx(Button, {
78
79
  loading: true,
79
80
  disabled: true,
80
- children: "Working"
81
+ children: text("working")
81
82
  })
82
83
  });
83
84
  const deleted = props.total - props.failures.length;
84
85
  return /* @__PURE__ */ jsx(Dialog, {
85
- title: props.failures.length === 0 ? `Deleted ${deleted}` : `Deleted ${deleted} of ${props.total}`,
86
- description: props.failures.length === 0 ? void 0 : `${props.failures.length} could not be deleted and were kept.`,
86
+ title: props.failures.length === 0 ? text("deleted", deleted) : text("deletedOf", deleted, props.total),
87
+ description: props.failures.length === 0 ? void 0 : text("kept", props.failures.length),
87
88
  onClose: props.onClose,
88
89
  actions: /* @__PURE__ */ jsx(Button, {
89
90
  variant: "primary",
90
91
  onClick: props.onClose,
91
- children: "Done"
92
+ children: text("done")
92
93
  }),
93
94
  children: props.failures.length > 0 && /* @__PURE__ */ jsx("ul", {
94
95
  className: "max-h-48 divide-y overflow-y-auto rounded-md border",
@@ -0,0 +1,66 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/delete/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ confirmTitle: (title) => `Delete ${title}?`,
6
+ confirmDescription: (noun, _title) => `This ${noun.toLowerCase()} will be removed. This cannot be undone.`,
7
+ cancel: "Cancel",
8
+ delete: "Delete",
9
+ changedTitle: (title) => `${title} was changed since you opened it`,
10
+ changedDescription: "Someone saved this record in the meantime. Review what changed before you delete it.",
11
+ reviewChanges: "Review changes",
12
+ deleteAnyway: "Delete anyway",
13
+ noVisibleChanges: "No visible fields differ; the record was saved again with the same values.",
14
+ bulkConfirmTitle: (count, plural) => `Delete ${count} ${count === 1 ? plural.replace(/s$/, "") : plural.toLowerCase()}?`,
15
+ bulkConfirmDescription: "This cannot be undone. Each record is deleted only if it has not changed since the list loaded; anything that changed is reported and kept.",
16
+ deleteCount: (count) => `Delete ${count}`,
17
+ deleting: "Deleting",
18
+ progress: (done, total) => `${done} of ${total} done`,
19
+ working: "Working",
20
+ deleted: (count) => `Deleted ${count}`,
21
+ deletedOf: (count, total) => `Deleted ${count} of ${total}`,
22
+ kept: (count) => `${count} could not be deleted and were kept.`,
23
+ done: "Done",
24
+ deletedToast: (title) => `${title} deleted`,
25
+ undo: "Undo",
26
+ restored: (title) => `${title} restored`,
27
+ alreadyBack: (title) => `${title} is already back`,
28
+ couldNotRestore: (title) => `Could not restore ${title}`,
29
+ couldNotDelete: (title) => `Could not delete ${title}`,
30
+ changedSinceLoaded: "Changed since the list loaded, so it was kept.",
31
+ stillPointAt: (plural, noun) => `${plural} still point at this ${noun.toLowerCase()}.`,
32
+ show: (plural) => `Show ${plural.toLowerCase()}`
33
+ },
34
+ nl: {
35
+ confirmTitle: (title) => `${title} verwijderen?`,
36
+ confirmDescription: (_noun, title) => `${title} wordt verwijderd. Dit kan niet ongedaan worden gemaakt.`,
37
+ cancel: "Annuleren",
38
+ delete: "Verwijderen",
39
+ changedTitle: (title) => `${title} is gewijzigd sinds je het opende`,
40
+ changedDescription: "Iemand heeft dit record intussen bewaard. Bekijk wat er veranderde voordat je het verwijdert.",
41
+ reviewChanges: "Wijzigingen bekijken",
42
+ deleteAnyway: "Toch verwijderen",
43
+ noVisibleChanges: "Geen zichtbaar veld verschilt; het record is opnieuw bewaard met dezelfde waarden.",
44
+ bulkConfirmTitle: (count, plural) => count === 1 ? "Eén record verwijderen?" : `${count} ${plural.toLowerCase()} verwijderen?`,
45
+ bulkConfirmDescription: "Dit kan niet ongedaan worden gemaakt. Elk record wordt alleen verwijderd als het niet is gewijzigd sinds de lijst laadde; wat wel is gewijzigd, wordt gemeld en behouden.",
46
+ deleteCount: (count) => `${count} verwijderen`,
47
+ deleting: "Verwijderen",
48
+ progress: (done, total) => `${done} van ${total} klaar`,
49
+ working: "Bezig",
50
+ deleted: (count) => `${count} verwijderd`,
51
+ deletedOf: (count, total) => `${count} van ${total} verwijderd`,
52
+ kept: (count) => `${count} kon${count === 1 ? "" : "den"} niet worden verwijderd en ${count === 1 ? "is" : "zijn"} behouden.`,
53
+ done: "Klaar",
54
+ deletedToast: (title) => `${title} verwijderd`,
55
+ undo: "Ongedaan maken",
56
+ restored: (title) => `${title} teruggezet`,
57
+ alreadyBack: (title) => `${title} is al terug`,
58
+ couldNotRestore: (title) => `Kon ${title} niet terugzetten`,
59
+ couldNotDelete: (title) => `Kon ${title} niet verwijderen`,
60
+ changedSinceLoaded: "Gewijzigd sinds de lijst laadde, dus behouden.",
61
+ stillPointAt: (plural, _noun) => `${plural} verwijzen hier nog naar.`,
62
+ show: (plural) => `${plural} tonen`
63
+ }
64
+ });
65
+ //#endregion
66
+ export { text };
@@ -3,6 +3,7 @@ import { humanize } from "../../live/naming.js";
3
3
  import { useToast } from "../../toasts/toast-provider.js";
4
4
  import { useAdminMeta } from "../meta-gate.js";
5
5
  import { useAppAddress } from "../use-app-address.js";
6
+ import { text } from "./messages.js";
6
7
  import { blockedDelete } from "./blocked-delete.js";
7
8
  import { BulkDeleteDialog, ConfirmDeleteDialog, ConflictDialog } from "./dialogs.js";
8
9
  import { diffRecords } from "./record-diff.js";
@@ -62,22 +63,22 @@ const useDeleteFlow = ({ model, view, onDeleted, onReview, restore }) => {
62
63
  onDeleted?.(target);
63
64
  toast.show({
64
65
  state: "success",
65
- title: `${target.title} deleted`,
66
+ title: text("deletedToast", target.title),
66
67
  ...model.softDelete && restore && { action: {
67
- label: "Undo",
68
+ label: text("undo"),
68
69
  onClick: () => {
69
70
  restore(target).then(() => {
70
71
  refresh();
71
72
  toast.show({
72
73
  state: "success",
73
- title: `${target.title} restored`
74
+ title: text("restored", target.title)
74
75
  });
75
76
  }, (error) => {
76
77
  if (!isApiError(error)) throw error;
77
78
  refresh();
78
79
  toast.show({
79
80
  state: error.status === 409 ? "success" : "error",
80
- title: error.status === 409 ? `${target.title} is already back` : `Could not restore ${target.title}`,
81
+ title: error.status === 409 ? text("alreadyBack", target.title) : text("couldNotRestore", target.title),
81
82
  description: error.status === 409 ? void 0 : error.message
82
83
  });
83
84
  });
@@ -102,7 +103,7 @@ const useDeleteFlow = ({ model, view, onDeleted, onReview, restore }) => {
102
103
  const why = blocked(error, target);
103
104
  toast.show({
104
105
  state: "error",
105
- title: `Could not delete ${target.title}`,
106
+ title: text("couldNotDelete", target.title),
106
107
  description: why?.text ?? error.message,
107
108
  ...why?.link && { action: {
108
109
  label: why.link.label,
@@ -118,7 +119,7 @@ const useDeleteFlow = ({ model, view, onDeleted, onReview, restore }) => {
118
119
  try {
119
120
  return await remove(target, target.etag) ? {
120
121
  title: target.title,
121
- message: "Changed since the list loaded, so it was kept."
122
+ message: text("changedSinceLoaded")
122
123
  } : void 0;
123
124
  } catch (error) {
124
125
  if (!isApiError(error)) throw error;
@@ -1,6 +1,7 @@
1
1
  import { cn } from "../../lib/cn.js";
2
2
  import { Segmented } from "../../primitives/segmented.js";
3
3
  import { humanize } from "../../live/naming.js";
4
+ import { text } from "./messages.js";
4
5
  import { displayChoices, sortFields } from "./list-displays.js";
5
6
  import { parseSort } from "../list-order.js";
6
7
  import { MenuSelect } from "../../primitives/menu-select.js";
@@ -30,13 +31,13 @@ const DisplaySwitch = ({ view, value, onChange, menu = false, choices = displayC
30
31
  icon: choice.icon === void 0 ? void 0 : icons[choice.icon]
31
32
  }));
32
33
  if (menu) return /* @__PURE__ */ jsx(MenuSelect, {
33
- label: "Display",
34
+ label: text("display"),
34
35
  value,
35
36
  onChange,
36
37
  options
37
38
  });
38
39
  return /* @__PURE__ */ jsx(Segmented, {
39
- label: "Display",
40
+ label: text("display"),
40
41
  size: "md",
41
42
  value,
42
43
  onChange,
@@ -51,7 +52,7 @@ const SortMenu = ({ model, view, display, orderBy, onChange, className }) => {
51
52
  const names = fields.includes(current.columnId) ? fields : [current.columnId, ...fields];
52
53
  const label = (name) => view?.fields[name]?.label ?? humanize(name);
53
54
  return /* @__PURE__ */ jsx("select", {
54
- "aria-label": "Sort",
55
+ "aria-label": text("sort"),
55
56
  value: `${current.columnId} ${current.direction}`,
56
57
  onChange: (event) => onChange(event.target.value),
57
58
  className: cn("h-8 rounded-md border border-border-strong bg-background px-2.5 shadow-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20", className),
@@ -60,7 +61,7 @@ const SortMenu = ({ model, view, display, orderBy, onChange, className }) => {
60
61
  children: [
61
62
  label(name),
62
63
  ", ",
63
- word
64
+ text(word)
64
65
  ]
65
66
  }, `${name} ${direction}`)))
66
67
  });
@@ -5,6 +5,7 @@ import { displayValue } from "../../live/display-value.js";
5
5
  import { photosKey } from "../../live/row-photos.js";
6
6
  import { filledFields } from "../filled-fields.js";
7
7
  import { boardCard } from "./board-columns.js";
8
+ import { text } from "./messages.js";
8
9
  import { encodeKey, tableDisplay } from "@protobase/schema/internal";
9
10
  //#region src/app/displays/list-displays.ts
10
11
  const displaysOf = (view) => view?.list?.displays ?? [];
@@ -68,7 +69,7 @@ const defaultIcons = {
68
69
  };
69
70
  const displayChoices = (view) => [{
70
71
  name: tableDisplay,
71
- label: "Table",
72
+ label: text("table"),
72
73
  icon: "table"
73
74
  }, ...displaysOf(view).map((display) => ({
74
75
  name: display.name,
@@ -0,0 +1,20 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/displays/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ table: "Table",
6
+ display: "Display",
7
+ sort: "Sort",
8
+ ascending: "ascending",
9
+ descending: "descending"
10
+ },
11
+ nl: {
12
+ table: "Tabel",
13
+ display: "Weergave",
14
+ sort: "Sorteren",
15
+ ascending: "oplopend",
16
+ descending: "aflopend"
17
+ }
18
+ });
19
+ //#endregion
20
+ export { text };
@@ -1,4 +1,5 @@
1
1
  import { formatTag } from "../../locale/active-locale.js";
2
+ import { text } from "./messages.js";
2
3
  //#region src/app/embedded-list/cut-off-note.ts
3
4
  /**
4
5
  * What a list that shows only its first `shown` of `total` records says about the rest, as "The first 10 of 37.";
@@ -6,7 +7,7 @@ import { formatTag } from "../../locale/active-locale.js";
6
7
  */
7
8
  const cutOffNote = (shown, total, estimated) => {
8
9
  if (shown >= total) return void 0;
9
- return `The first ${shown} of ${estimated ? "about " : ""}${total.toLocaleString(formatTag())}.`;
10
+ return estimated ? text("firstOfAbout", shown, total.toLocaleString(formatTag())) : text("firstOf", shown, total.toLocaleString(formatTag()));
10
11
  };
11
12
  //#endregion
12
13
  export { cutOffNote };
@@ -19,6 +19,7 @@ import { CustomDisplay } from "../displays/custom-display.js";
19
19
  import { MapDisplay } from "../displays/map-display.js";
20
20
  import { DisplaySwitch } from "../displays/display-controls.js";
21
21
  import { embeddedColumns } from "./embedded-columns.js";
22
+ import { text } from "./messages.js";
22
23
  import { cutOffNote } from "./cut-off-note.js";
23
24
  import { embeddedRowTargets } from "./embedded-row-targets.js";
24
25
  import { useEmbeddedRows } from "./use-embedded-rows.js";
@@ -94,23 +95,27 @@ const EmbeddedList = ({ model, view, filter, sort, columns, limit, display, empt
94
95
  }),
95
96
  onClick: viewAll?.onOpen,
96
97
  className: "flex items-center gap-1 self-end font-medium text-primary-text hover:underline",
97
- children: [viewAll ? viewAll.label : /* @__PURE__ */ jsxs(Fragment$1, { children: ["View all ", /* @__PURE__ */ jsx("span", {
98
- className: "sr-only",
99
- children: plural.toLowerCase()
100
- })] }), /* @__PURE__ */ jsx(ArrowRight, {
98
+ children: [viewAll ? viewAll.label : /* @__PURE__ */ jsxs(Fragment$1, { children: [
99
+ text("viewAll"),
100
+ " ",
101
+ /* @__PURE__ */ jsx("span", {
102
+ className: "sr-only",
103
+ children: plural.toLowerCase()
104
+ })
105
+ ] }), /* @__PURE__ */ jsx(ArrowRight, {
101
106
  className: "size-3.5",
102
107
  "aria-hidden": true
103
108
  })]
104
109
  });
105
110
  if (rows.isPending) return /* @__PURE__ */ jsx(Loading, {});
106
111
  if (rows.error) return /* @__PURE__ */ jsx(ErrorBanner, {
107
- title: "Could not load",
112
+ title: text("couldNotLoad"),
108
113
  error: rows.error
109
114
  });
110
115
  if (!source || source.total === 0) {
111
116
  const nothing = /* @__PURE__ */ jsx("p", {
112
117
  className: "text-muted-foreground",
113
- children: empty ?? "Nothing here yet."
118
+ children: empty ?? text("nothingYet")
114
119
  });
115
120
  return viewAll && link ? /* @__PURE__ */ jsxs("div", {
116
121
  className: "flex flex-col gap-3",
@@ -1,6 +1,7 @@
1
1
  import { humanize } from "../../live/naming.js";
2
2
  import { recordKey } from "../../live/model-helpers.js";
3
3
  import { rowAllows } from "../permissions.js";
4
+ import { text } from "./messages.js";
4
5
  import { relationTarget } from "../related-opens.js";
5
6
  //#region src/app/embedded-list/embedded-row-targets.ts
6
7
  const singularOf = (model, view) => (view?.names?.singular ?? humanize(model.name)).toLowerCase();
@@ -29,19 +30,19 @@ const embeddedRowTargets = ({ model, view, opens, resources, views, canUpdate })
29
30
  const singular = singularOf(model, view);
30
31
  const actions = (linkTo) => {
31
32
  const openOwn = {
32
- label: `Open ${singular}`,
33
+ label: text("open", singular),
33
34
  link: (row) => linkTo(own(row))
34
35
  };
35
36
  if (!far) return [openOwn];
36
37
  const editable = (row) => canUpdate && rowAllows(row, "update");
37
38
  return [
38
39
  {
39
- label: `Open ${singularOf(far, views[far.name])}`,
40
+ label: text("open", singularOf(far, views[far.name])),
40
41
  available: (row) => other(row) !== void 0,
41
42
  link: (row) => linkTo(other(row))
42
43
  },
43
44
  {
44
- label: `Edit ${singular}`,
45
+ label: text("edit", singular),
45
46
  available: editable,
46
47
  link: (row) => linkTo({
47
48
  kind: "record",
@@ -0,0 +1,24 @@
1
+ import { defineMessages } from "../../locale/messages.js";
2
+ //#region src/app/embedded-list/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ firstOf: (shown, total) => `The first ${shown} of ${total}.`,
6
+ firstOfAbout: (shown, total) => `The first ${shown} of about ${total}.`,
7
+ open: (singular) => `Open ${singular}`,
8
+ edit: (singular) => `Edit ${singular}`,
9
+ viewAll: "View all",
10
+ couldNotLoad: "Could not load",
11
+ nothingYet: "Nothing here yet."
12
+ },
13
+ nl: {
14
+ firstOf: (shown, total) => `De eerste ${shown} van ${total}.`,
15
+ firstOfAbout: (shown, total) => `De eerste ${shown} van ongeveer ${total}.`,
16
+ open: (singular) => `${singular} openen`,
17
+ edit: (singular) => `${singular} bewerken`,
18
+ viewAll: "Alles bekijken",
19
+ couldNotLoad: "Kon niet laden",
20
+ nothingYet: "Hier staat nog niets."
21
+ }
22
+ });
23
+ //#endregion
24
+ export { text };
@@ -1,9 +1,10 @@
1
+ import { text } from "./messages.js";
1
2
  import { TriangleAlert } from "lucide-react";
2
3
  import { isApiError } from "@protobase/client";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region src/app/error-banner.tsx
5
6
  const describe = (error) => {
6
- if (!isApiError(error)) return [{ message: error instanceof Error ? error.message : "Something went wrong" }];
7
+ if (!isApiError(error)) return [{ message: error instanceof Error ? error.message : text("somethingWentWrong") }];
7
8
  const detailed = error.errors.map((entry) => ({
8
9
  message: entry.message,
9
10
  hint: entry.hint